fix: Fix issue target branch selection for non-collaborators (#36916) (#38164)

Backport #36916 

This PR fixes a bug in the UI that prevented non-collaborator users (the
issue poster or creator) from setting the target branch (ref) of an
issue. The backend API already supports this, but the UI was rigidly
disabling the dropdown based only on collaborator status.

Changes:
- Enable the branch selector for the issue poster and during new issue
creation.
- Fix a typo (.IsIssueWriter -> .IsIssuePoster) that was preventing the
reference update URL from being correctly set for posters.

Co-authored-by: fwag <30782430+fwag@users.noreply.github.com>
This commit is contained in:
bircni
2026-06-19 18:16:02 +02:00
committed by GitHub
parent 0b9623e188
commit 42eb7945d1
3 changed files with 58 additions and 3 deletions

View File

@@ -145,6 +145,7 @@ func NewIssue(ctx *context.Context) {
}
ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWrite(unit.TypeIssues)
ctx.Data["IsIssuePoster"] = true // the current user will be the poster of the new issue
if !issueConfig.BlankIssuesEnabled && hasTemplates && !templateLoaded {
// The "issues/new" and "issues/new/choose" share the same query parameters "project" and "milestone", if blank issues are disabled, just redirect to the "issues/choose" page with these parameters.

View File

@@ -14,14 +14,15 @@ Still needs to figure out:
* Is "GitHub-like development sidebar (`#31899`)" good enough (or better) for your usage?
*/}}
{{if and (not .Issue.IsPull) (not .PageIsComparePull)}}
{{$canChangeRef := or .IsIssuePoster .HasIssuesOrPullsWritePermission}}
<input id="ref_selector" name="ref" type="hidden" value="{{.Reference}}">
<div class="ui dropdown select-branch branch-selector-dropdown ellipsis-text-items {{if not .HasIssuesOrPullsWritePermission}}disabled{{end}}"
<div class="ui dropdown select-branch branch-selector-dropdown ellipsis-text-items {{if not $canChangeRef}}disabled{{end}}"
data-no-results="{{ctx.Locale.Tr "no_results_found"}}"
{{if and .Issue (or .IsIssueWriter .HasIssuesOrPullsWritePermission)}}data-url-update-issueref="{{$.RepoLink}}/issues/{{.Issue.Index}}/ref"{{end}}
{{if and .Issue $canChangeRef}}data-url-update-issueref="{{$.RepoLink}}/issues/{{.Issue.Index}}/ref"{{end}}
>
<div class="ui button branch-dropdown-button">
<span class="text-branch-name gt-ellipsis">{{if .Reference}}{{$.RefEndName}}{{else}}{{ctx.Locale.Tr "repo.issues.no_ref"}}{{end}}</span>
{{if .HasIssuesOrPullsWritePermission}}{{svg "octicon-triangle-down" 14 "dropdown icon"}}{{end}}
{{if $canChangeRef}}{{svg "octicon-triangle-down" 14 "dropdown icon"}}{{end}}
</div>
<div class="menu">
<div class="ui icon search input">

View File

@@ -682,6 +682,59 @@ func TestUpdateIssueDeadline(t *testing.T) {
assert.True(t, issueAfter.DeadlineUnix.IsZero())
}
func TestUpdateIssueRefByPoster(t *testing.T) {
defer tests.PrepareTestEnv(t)()
// user4 is a non-admin, non-collaborator on user2/repo1.
// They create an issue, making them the poster.
posterSession := loginUser(t, "user4")
issueURL := testNewIssue(t, posterSession, "user2", "repo1", "Poster ref test", "body")
refURL := issueURL + "/ref"
// The poster (non-collaborator) must be able to update the ref.
req := NewRequestWithValues(t, "POST", refURL, map[string]string{"ref": "refs/heads/main"})
posterSession.MakeRequest(t, req, http.StatusOK)
// A different non-collaborator non-poster must be forbidden.
otherSession := loginUser(t, "user5")
req = NewRequestWithValues(t, "POST", refURL, map[string]string{"ref": "refs/heads/main"})
otherSession.MakeRequest(t, req, http.StatusForbidden)
}
func TestIssueRefSelectorEnabledForPoster(t *testing.T) {
defer tests.PrepareTestEnv(t)()
// user4 creates an issue in user2/repo1 (user4 has no write permission there).
posterSession := loginUser(t, "user4")
issueURL := testNewIssue(t, posterSession, "user2", "repo1", "Ref selector test", "body")
resp := posterSession.MakeRequest(t, NewRequest(t, "GET", issueURL), http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
// The branch selector must not carry the "disabled" CSS class for the poster.
sel := htmlDoc.Find(".branch-selector-dropdown")
assert.Equal(t, 1, sel.Length())
assert.False(t, sel.HasClass("disabled"), "branch selector should be enabled for the issue poster")
// The update-ref URL must be present so JS can send the POST request.
_, hasURL := sel.Attr("data-url-update-issueref")
assert.True(t, hasURL, "data-url-update-issueref must be set for the issue poster")
}
func TestIssueRefSelectorEnabledForNewIssue(t *testing.T) {
defer tests.PrepareTestEnv(t)()
// user4 (non-collaborator on user2/repo1) must see an enabled ref selector
// when creating a new issue.
session := loginUser(t, "user4")
req := NewRequest(t, "GET", "/user2/repo1/issues/new")
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
sel := htmlDoc.Find(".branch-selector-dropdown")
assert.Equal(t, 1, sel.Length())
assert.False(t, sel.HasClass("disabled"), "branch selector should be enabled on the new issue form")
}
func TestIssueReferenceURL(t *testing.T) {
defer tests.PrepareTestEnv(t)()
session := loginUser(t, "user2")