-
Notifications
You must be signed in to change notification settings - Fork 0
Change request: enable submitting article edits #101
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
* set fork-related context data * show submit button based on permissions * add ForkAndEdit field to EditRepoFileForm * modify EditFilePost to handle fork creation and commit to fork * reuse existing fork if user already has one
* query repos by owner and subject * check for existing user repo for subject * disable edit/fork buttons when user already has a repo for the subject * show message in edit view explaining why editing is disabled
* fix wrong error condition (returned on any error, not just 'not found') * fix double increment bug (i++ in loop body + loop increment) * optimise from O(n) queries to single query with hash set lookup O(1) * add proper error logging
* run subject ownership check and fork detection concurrently
* add error for subject ownership violations * validate subject ownership before forking
* fix POST handler to validate subject ownership
* create IDX_repository_owner_subject and IDX_repository_owner_fork * optimizes GetRepositoryByOwnerIDAndSubjectID() and GetForkedRepo() queries
* replace 'Cannot Edit' button with 'Edit Your Article' action link * add translation keys for fork-on-edit error messages * link users to their existing article when subject ownership blocks editing
* simplify fork_and_edit hidden field to only use .NeedsFork * extract repeated nil-guard pattern into $subjectName template variable
* add Playwright tests for modal appearance, cancel actions, and tooltip * test modal shows correct header, content, and button text * verify cancel button and Escape key close modal without action * verify fork button has tooltip with confirmation message
* add repository owner tests for Submit Changes button behavior * add non-owner tests for Fork button and disabled submit button * add unauthenticated user test for sign-in button
* accessibility tests verify Enter opens modal, Tab navigates buttons, and buttons have proper accessible text content * unique name tests verify suffix generation when base name is taken
* use RepoOperationsLink instead of RepoLink in form action url * bypass fork_and_edit in CanWriteToBranch middleware to allow non-owners to reach the EditFilePost handler * skip NeedFork workflow when ForkAndEdit is true to prevent conflicting fork creation attempts
* CheckForkOnEditPermissions for owner/non-owner/unauthenticated * CanWriteToBranch middleware bypass with fork_and_edit=true * existing fork detection using fixture data (repo11/repo10) * subject ownership blocking prevents duplicate forks * form action URL uses RepoOperationsLink not RepoLink
* slug generation migration covering basic slugs, mixed-case deduplication, special characters, unique constraint verification, and empty table handling * index creation, idempotency, and index type verification
…order * fork API behavior when user already owns a repository for the same subject * fork blocked by subject ownership (403), fork succeeds for user without subject repo (202), fork blocked even with custom name (403) * fix error handling order in CreateFork
* update GetForkedRepo signature to return (*Repository, error) * update all callers to handle the new error return value * fixes silent error swallowing
* fix modal promise resolution pattern * add PathEscapeSegments to article template URL * update test comment to list all removed special characters
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
…ails * Add branch cleanup to OpenRepository and GetCompareInfo error path in handleSubmitChangeRequest * Prevent orphaned branches from accumulating when these specific errors occur
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* replace byte-level slicing with util.TruncateRunes() to prevent splitting multi-byte UTF-8 characters at byte position 255 * The 255 limit is for characters (runes), not bytes
|
Great work @pedrogaudencio! Also: for this review I just ran Opus 4.5 in two different ways, please let me know which version of Opus 4.5 gave you a better review: Opus 4.5: DetailsI've thoroughly reviewed the changes. Here are the issues I found:Critical Security Issues1. InternalPush Bypasses Branch Protection// services/repository/files/update.go
InternalPush: true,In Recommendation: Instead of bypassing hooks entirely, consider creating a specific hook environment variable that indicates this is a submit-change-request flow, allowing hooks to make informed decisions rather than being bypassed completely. 2. Missing Repository Archive CheckThe func handleSubmitChangeRequest(ctx *context.Context, form *forms.EditRepoFileForm, parsed *preparedEditorCommitForm[*forms.EditRepoFileForm]) *issues_model.PullRequest {
// Verify user is authenticated (defense-in-depth, middleware should already handle this)
if ctx.Doer == nil {
ctx.JSONError(ctx.Tr("error.not_found"))
return nil
}
targetRepo := ctx.Repo.Repository
// Check if the repository allows pull requests
if !targetRepo.AllowsPulls(ctx) {Add this check after the nil check: // Check if the repository is archived
if targetRepo.IsArchived {
ctx.JSONError(ctx.Tr("repo.archive.title"))
return nil
}Bugs3. Typo in Locale Fileissues.ref_pull_from = `<a href="%[3]s">referenced this changchange request %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_closing_from = `<a href="%[3]s">referenced a changchange request %[4]s that will close this issue</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`Fix: Change issues.ref_pull_from = `<a href="%[3]s">referenced this change request %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_closing_from = `<a href="%[3]s">referenced a change request %[4]s that will close this issue</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`4. CSS Styles Incorrectly Commented Out/* web_src/css/repo.css */
/*
.issue-content {
display: flex;
align-items: flex-start;
gap: 16px;
}
...
.issue-content-right .ui.list {
margin: 0.5em 0;
max-width: 100%;
} */These styles are commented out but likely still needed. The PR/issue views will have broken layouts without these flex container styles. Either remove the comments or remove the code entirely if not needed. 5. Redundant Cleanup Attempt Will Faillog.Error("handleSubmitChangeRequest: failed to open git repo: %v", err)
// Attempt to clean up the orphaned branch - need to open repo specifically for cleanup
if cleanupRepo, cleanupErr := gitrepo.OpenRepository(ctx, targetRepo); cleanupErr == nil {
if delErr := repo_service.DeleteBranch(ctx, ctx.Doer, targetRepo, cleanupRepo, branchName, nil); delErr != nil {
log.Error("handleSubmitChangeRequest: failed to cleanup branch %s: %v", branchName, delErr)
}
cleanupRepo.Close()
}If Simplify to: log.Error("handleSubmitChangeRequest: failed to open git repo: %v", err)
// Note: Branch cleanup not attempted as repository is inaccessible
ctx.ServerError("OpenRepository", err)
return nil6. Test Assumes Specific PR Index// tests/integration/submit_change_request_test.go
prs, err := issues_model.GetPullRequestByIndex(t.Context(), repo.ID, 1)
if err == nil && prs != nil {This assumes the created PR has index 1, which is fragile if test fixtures change or tests run in different orders. Fix: Parse the PR index from the redirect URL instead: // Extract PR index from redirect URL and query by that index
parts := strings.Split(redirectURL, "/pulls/")
require.Len(t, parts, 2, "URL should contain /pulls/")
prIndex, err := strconv.ParseInt(strings.TrimSuffix(parts[1], "/"), 10, 64)
require.NoError(t, err)
pr, err := issues_model.GetPullRequestByIndex(t.Context(), repo.ID, prIndex)
require.NoError(t, err)Improvements7. Duplicate Cleanup Logic Should Be ExtractedThe branch cleanup code is duplicated in three places within func cleanupOrphanedBranch(ctx *context.Context, repo *repo_model.Repository, gitRepo *git.Repository, branchName string) {
if gitRepo == nil {
return
}
if err := repo_service.DeleteBranch(ctx, ctx.Doer, repo, gitRepo, branchName, nil); err != nil {
log.Error("cleanupOrphanedBranch: failed to cleanup branch %s: %v", branchName, err)
}
}8. Title Truncation Edge CaseprTitle = util.TruncateRunes(prTitle, 255)After truncation, verify the title isn't empty (edge case where title is >255 combining characters that all get removed): prTitle = util.TruncateRunes(prTitle, 255)
if prTitle == "" {
prTitle = ctx.Locale.TrString("repo.editor.submit_changes_pr_title", path.Base(form.TreePath))
}9. Missing Locale Keys in Options FileSeveral locale keys are added to
For consistency, these should be in 10. Unclear Error Messageif parsed.form.ForkAndEdit && parsed.form.SubmitChangeRequest {
ctx.JSONError(ctx.Tr("error.occurred"))
return
}Using a generic "error.occurred" message when both flags are set doesn't help debugging. Create a specific message: ctx.JSONError(ctx.Tr("repo.editor.cannot_fork_and_submit_cr"))Questions
Opus 4.5 with tools: DetailsCode Review: Submit Change Request Feature1. BUG: Typos in Locale FileIssue: The word "changchange" is a typo - it should be "change". Fix: 2. POTENTIAL RACE CONDITION: Branch Name GenerationIssue: There's a TOCTOU (Time-of-Check-Time-of-Use) race condition. Between checking if the branch exists and actually creating it in Mitigation: The code does handle this gracefully - if the branch creation fails due to conflict, 3. SECURITY CONSIDERATION: InternalPush Bypasses HooksIssue: Using Recommendation: The code already validates that the user is authenticated and the repository allows PRs. However, ensure that:
The current implementation appears acceptable since:
4. BUG: Redundant Repository Open for CleanupIssue: If Fix: Remove the redundant cleanup attempt: 5. CSS COMMENTED OUT - Potential UI IssuesIssue: Large blocks of CSS are commented out. This could cause layout issues for the issue/PR view pages. If this CSS is no longer needed, it should be removed entirely. If it's temporarily disabled for debugging, it should be restored. Recommendation: Either remove the commented CSS entirely or restore it if needed for proper layout. 6. MISSING INPUT VALIDATION: PR Description LengthIssue: While Fix: Add a reasonable limit: 7. IMPROVEMENT: Mutual Exclusivity Check Could Be EarlierIssue: This check happens after Recommendation: Consider adding this validation in the form binding layer or at the very start of 8. IMPROVEMENT: Error Message Could Be More SpecificIssue: Using a generic "error.occurred" message doesn't help users understand what went wrong. Fix: Add a specific error message: 9. GOOD PRACTICES OBSERVEDThe code demonstrates several good practices:
10. MINOR: Unrelated Change in DiffThis change adds SummaryCritical Issues:
Medium Issues: Low/Improvement: |
* extract PR index dynamically from redirect URL instead of assuming index 1 * use require.NoError/NotNil for proper error handling on PR lookup
* add cleanupOrphanedBranch helper to reduce duplication
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| </button> | ||
| <div class="ui dropdown icon button" @click.stop="showMergeStyleMenu = !showMergeStyleMenu"> | ||
| <!-- Only show dropdown when there are multiple merge styles available --> | ||
| <div v-if="mergeStyleAllowedCount > 1" class="ui dropdown icon button" @click.stop="showMergeStyleMenu = !showMergeStyleMenu"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔴 Undefined variable mergeStyleAllowedCount causes merge style dropdown to never render
The Vue template references mergeStyleAllowedCount in a v-if condition, but this variable is never defined in the component's script section.
Click to expand
Analysis
The new code adds a condition to only show the merge style dropdown when multiple styles are available:
<div v-if="mergeStyleAllowedCount > 1" class="ui dropdown icon button" ...>However, mergeStyleAllowedCount is not defined anywhere in the component - there's no ref, computed property, or data property with this name.
Actual vs Expected
- Actual:
mergeStyleAllowedCountisundefined, andundefined > 1evaluates tofalsein JavaScript, so the dropdown is never rendered regardless of how many merge styles are available. - Expected: The dropdown should be hidden only when there's exactly one merge style, and shown when there are multiple styles.
Impact
Users cannot select a different merge style even if the repository is configured to allow multiple merge strategies. While the Forkana-specific configuration only enables squash merge (making this bug's impact minimal in that specific use case), the implementation is broken and would fail silently if additional merge styles were enabled.
Recommendation: Add a computed property to calculate the count of allowed merge styles:
const mergeStyleAllowedCount = computed(() => mergeForm.mergeStyles.filter(s => s.allowed).length);Or inline the condition: v-if="mergeForm.mergeStyles.filter(s => s.allowed).length > 1"
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My AI says No fix is needed - the code is already correct. The mergeStyleAllowedCount variable is declared as a shallowRef(0) at line 24, initialized in onMounted at line 46, and used in the template at line 151.
But probably better for @pieer to confirm.
Addressed
Questions
This is intentional: branch protection applies at merge time, not branch creation.
No rate limiting for non-collaborators creating PRs. The existing (Gitea) rate limiting mechanisms apply and specific PR creation limits can always be added later if required. Skipped
Not Fixed(assigning to @pieer)
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* Update cleanupOrphanedBranch to bypass permission check for branches created via InternalPush * Fixes asymmetry where InternalPush bypasses creation checks but deletion still enforces them, leaving orphaned branches when CR creation fails for non-collaborators
* add CanSubmitChangeRequest field to ForkOnEditPermissions struct * update CheckForkOnEditPermissions to properly set CanSubmitChangeRequest based on fork ownership * expose CanSubmitChangeRequest in prepareArticleForkOnEditData context data
* Separate bypass logic for fork_and_edit vs submit_change_request workflows * submit_change_request now only supports editing existing files, not creating new ones
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* rename SubmitChangeRequestOnlyAllowedForEditAndNew to SubmitChangeRequestOnlyAllowedForEdit * rename NewEndpointAllowsSubmitChangeRequest to NewEndpointBlocksSubmitChangeRequest * change assertion to expect 404 for _new action with submit_change_request=true
* override issue content class
* check both database and git repository for branch existence * prevents error when orphaned branches exist in git but not in database
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
@pieer I've checked the issue regarding the CSS styles and reverted them to the original. Instead of commenting the styles completely (as it would break the main templates and only serve the custom ones) I added a new @taoeffect after fixing a few other things flagged by Devin, I believe this PR is ready to merge. 👍 |
taoeffect
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you @pedrogaudencio!
Closes /issues/90
Closes /issues/92
IMPORTANT: Depends on #100 being merged first.