fix(web): populate the reason for "cannot commit to branch" in web editor commit form (#39155)

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
Nitish-1303
2026-08-28 21:46:58 +00:00
committed by GitHub
co-authored by wxiaoguang
parent 60326ca03d
commit e806566b39
12 changed files with 54 additions and 51 deletions
+5 -17
View File
@@ -575,14 +575,13 @@ func (repo *Repository) IsOwnedBy(userID int64) bool {
return repo.OwnerID == userID return repo.OwnerID == userID
} }
// CanCreateBranch returns true if repository meets the requirements for creating new branches.
func (repo *Repository) CanCreateBranch() bool {
return !repo.IsMirror
}
// CanEnablePulls returns true if repository meets the requirements of accepting pulls. // CanEnablePulls returns true if repository meets the requirements of accepting pulls.
func (repo *Repository) CanEnablePulls() bool { func (repo *Repository) CanEnablePulls() bool {
return !repo.IsMirror && !repo.IsEmpty return repo.CanContentChange() && !repo.IsEmpty
}
func (repo *Repository) CanContentChange() bool {
return !repo.IsMirror && !repo.IsArchived
} }
// AllowsPulls returns true if repository meets the requirements of accepting pulls and has them enabled. // AllowsPulls returns true if repository meets the requirements of accepting pulls and has them enabled.
@@ -590,17 +589,6 @@ func (repo *Repository) AllowsPulls(ctx context.Context) bool {
return repo.CanEnablePulls() && repo.UnitEnabled(ctx, unit.TypePullRequests) return repo.CanEnablePulls() && repo.UnitEnabled(ctx, unit.TypePullRequests)
} }
// CanEnableEditor returns true if repository meets the requirements of web editor.
// FIXME: most CanEnableEditor calls should be replaced with CanContentChange
// And all other like CanCreateBranch / CanEnablePulls should also be updated
func (repo *Repository) CanEnableEditor() bool {
return repo.CanContentChange()
}
func (repo *Repository) CanContentChange() bool {
return !repo.IsMirror && !repo.IsArchived
}
// DescriptionHTML does special handles to description and return HTML string. // DescriptionHTML does special handles to description and return HTML string.
func (repo *Repository) DescriptionHTML(ctx context.Context) template.HTML { func (repo *Repository) DescriptionHTML(ctx context.Context) template.HTML {
return markup.PostProcessDescriptionHTML(markup.NewRenderContext(ctx), htmlutil.EscapeString(repo.Description)) return markup.PostProcessDescriptionHTML(markup.NewRenderContext(ctx), htmlutil.EscapeString(repo.Description))
+3 -1
View File
@@ -1324,7 +1324,9 @@
"repo.editor.upload_files_to_dir": "Upload files to \"%s\"", "repo.editor.upload_files_to_dir": "Upload files to \"%s\"",
"repo.editor.cannot_commit_to_protected_branch": "Cannot commit to protected branch \"%s\".", "repo.editor.cannot_commit_to_protected_branch": "Cannot commit to protected branch \"%s\".",
"repo.editor.no_commit_to_branch": "Not allowed to commit directly to branch because:", "repo.editor.no_commit_to_branch": "Not allowed to commit directly to branch because:",
"repo.editor.user_no_push_to_branch": "User cannot push to branch", "repo.editor.no_write_permission": "No write permission.",
"repo.editor.repo_not_editable": "Repository is not editable.",
"repo.editor.branch_is_protected": "Branch is protected",
"repo.editor.require_signed_commit": "Branch requires a signed commit", "repo.editor.require_signed_commit": "Branch requires a signed commit",
"repo.editor.cherry_pick": "Cherry-pick %s onto:", "repo.editor.cherry_pick": "Cherry-pick %s onto:",
"repo.editor.revert": "Revert %s onto:", "repo.editor.revert": "Revert %s onto:",
+1 -1
View File
@@ -874,7 +874,7 @@ func mustNotBeArchived(ctx *context.APIContext) {
} }
func mustEnableEditor(ctx *context.APIContext) { func mustEnableEditor(ctx *context.APIContext) {
if !ctx.Repo.Repository.CanEnableEditor() { if !ctx.Repo.Repository.CanContentChange() {
ctx.APIError(http.StatusLocked, "repo is not allowed to edit") ctx.APIError(http.StatusLocked, "repo is not allowed to edit")
return return
} }
+1 -1
View File
@@ -67,7 +67,7 @@ func prepareEditorPageFormOptions(ctx *context.Context, editorAction string) *co
return nil return nil
} }
if commitFormOptions.WillSubmitToFork && !commitFormOptions.TargetRepo.CanEnableEditor() { if commitFormOptions.WillSubmitToFork && !commitFormOptions.TargetRepo.CanContentChange() {
ctx.Data["NotFoundPrompt"] = ctx.Locale.Tr("repo.editor.fork_not_editable") ctx.Data["NotFoundPrompt"] = ctx.Locale.Tr("repo.editor.fork_not_editable")
ctx.NotFound(nil) ctx.NotFound(nil)
} }
+1 -1
View File
@@ -49,7 +49,7 @@ func MustBeNotEmpty(ctx *context.Context) {
// MustBeEditable check that repo can be edited // MustBeEditable check that repo can be edited
func MustBeEditable(ctx *context.Context) { func MustBeEditable(ctx *context.Context) {
if !ctx.Repo.Repository.CanEnableEditor() { if !ctx.Repo.Repository.CanContentChange() {
ctx.NotFound(nil) ctx.NotFound(nil)
return return
} }
+1 -1
View File
@@ -255,7 +255,7 @@ func prepareFileView(ctx *context.Context, entry *git.TreeEntry) {
func prepareFileViewEditorButtons(ctx *context.Context) bool { func prepareFileViewEditorButtons(ctx *context.Context) bool {
// archived or mirror repository, the buttons should not be shown // archived or mirror repository, the buttons should not be shown
if !ctx.Repo.Repository.CanEnableEditor() { if !ctx.Repo.Repository.CanContentChange() {
return true return true
} }
+1 -1
View File
@@ -220,7 +220,7 @@ func prepareToRenderReadmeFile(ctx *context.Context, subfolder string, readmeFil
ctx.Data["EscapeStatus"], ctx.Data["FileContent"] = charset.EscapeControlHTML(template.HTML(contentEscaped), ctx.Locale) ctx.Data["EscapeStatus"], ctx.Data["FileContent"] = charset.EscapeControlHTML(template.HTML(contentEscaped), ctx.Locale)
} }
if !fInfo.isLFSFile() && ctx.Repo.Repository.CanEnableEditor() { if !fInfo.isLFSFile() && ctx.Repo.Repository.CanContentChange() {
ctx.Data["CanEditReadmeFile"] = true ctx.Data["CanEditReadmeFile"] = true
} }
} }
+35 -21
View File
@@ -9,6 +9,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"html" "html"
"html/template"
"net/http" "net/http"
"net/url" "net/url"
"path" "path"
@@ -124,7 +125,7 @@ func (r *Repository) CanWriteToBranch(ctx context.Context, user *user_model.User
// CanCreateBranch returns true if repository is editable and user has proper access level. // CanCreateBranch returns true if repository is editable and user has proper access level.
func (r *Repository) CanCreateBranch() bool { func (r *Repository) CanCreateBranch() bool {
return r.Permission.CanWrite(unit_model.TypeCode) && r.Repository.CanCreateBranch() return r.Permission.CanWrite(unit_model.TypeCode) && r.Repository.CanContentChange()
} }
func (r *Repository) GetObjectFormat() git.ObjectFormat { func (r *Repository) GetObjectFormat() git.ObjectFormat {
@@ -143,15 +144,18 @@ func RepoMustNotBeArchived() func(ctx *Context) {
type CommitFormOptions struct { type CommitFormOptions struct {
NeedFork bool NeedFork bool
TargetRepo *repo_model.Repository TargetRepo *repo_model.Repository
TargetFormAction string TargetFormAction string
WillSubmitToFork bool
WillSubmitToFork bool
CanCommitToBranch bool CanCommitToBranch bool
UserCanPush bool DenyCommitToBranchReason template.HTML
RequireSigned bool
WillSign bool WillSign bool
SigningKeyFormDisplay string SigningKeyFormDisplay string
WontSignReason string WontSignReason string
CanCreatePullRequest bool CanCreatePullRequest bool
CanCreateBasePullRequest bool CanCreateBasePullRequest bool
} }
@@ -172,7 +176,7 @@ func PrepareCommitFormOptions(ctx *Context, doer *user_model.User, targetRepo *r
} }
// now, we get our own forked repo; it must be writable by us. // now, we get our own forked repo; it must be writable by us.
} }
submitToForkedRepo := targetRepo.ID != originRepo.ID
err := targetRepo.GetBaseRepo(ctx) err := targetRepo.GetBaseRepo(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -214,20 +218,14 @@ func PrepareCommitFormOptions(ctx *Context, doer *user_model.User, targetRepo *r
return nil, err return nil, err
} }
canCommitToBranch := !submitToForkedRepo /* same repo */ && targetRepo.CanEnableEditor() && canPushWithProtection
if protectionRequireSigned {
canCommitToBranch = canCommitToBranch && willSign
}
canCreateBasePullRequest := targetRepo.BaseRepo != nil && targetRepo.BaseRepo.UnitEnabled(ctx, unit_model.TypePullRequests) canCreateBasePullRequest := targetRepo.BaseRepo != nil && targetRepo.BaseRepo.UnitEnabled(ctx, unit_model.TypePullRequests)
canCreatePullRequest := targetRepo.UnitEnabled(ctx, unit_model.TypePullRequests) || canCreateBasePullRequest canCreatePullRequest := targetRepo.UnitEnabled(ctx, unit_model.TypePullRequests) || canCreateBasePullRequest
opts := &CommitFormOptions{ opts := &CommitFormOptions{
TargetRepo: targetRepo, TargetRepo: targetRepo,
WillSubmitToFork: submitToForkedRepo,
CanCommitToBranch: canCommitToBranch, WillSubmitToFork: targetRepo.ID != originRepo.ID,
UserCanPush: canPushWithProtection,
RequireSigned: protectionRequireSigned,
WillSign: willSign, WillSign: willSign,
SigningKeyFormDisplay: asymkey_model.GetDisplaySigningKey(signKey), SigningKeyFormDisplay: asymkey_model.GetDisplaySigningKey(signKey),
WontSignReason: wontSignReason, WontSignReason: wontSignReason,
@@ -235,12 +233,28 @@ func PrepareCommitFormOptions(ctx *Context, doer *user_model.User, targetRepo *r
CanCreatePullRequest: canCreatePullRequest, CanCreatePullRequest: canCreatePullRequest,
CanCreateBasePullRequest: canCreateBasePullRequest, CanCreateBasePullRequest: canCreateBasePullRequest,
} }
editorAction := ctx.PathParam("editor_action") editorAction := ctx.PathParam("editor_action")
editorPathParamRemaining := util.PathEscapeSegments(branchName) + "/" + util.PathEscapeSegments(ctx.Repo.TreePath) editorPathParamRemaining := util.PathEscapeSegments(branchName) + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
if submitToForkedRepo {
opts.CanCommitToBranch = false
if opts.WillSubmitToFork {
opts.DenyCommitToBranchReason = ctx.Locale.Tr("repo.editor.no_write_permission")
// there is only "default branch" in forked repo, we will use "from_base_branch" to get a new branch from base repo // there is only "default branch" in forked repo, we will use "from_base_branch" to get a new branch from base repo
editorPathParamRemaining = util.PathEscapeSegments(targetRepo.DefaultBranch) + "/" + util.PathEscapeSegments(ctx.Repo.TreePath) + "?from_base_branch=" + url.QueryEscape(branchName) editorPathParamRemaining = util.PathEscapeSegments(targetRepo.DefaultBranch) + "/" + util.PathEscapeSegments(ctx.Repo.TreePath) + "?from_base_branch=" + url.QueryEscape(branchName)
} else {
// if the user is committing to the same repo, we need to check if the branch is protected and if the user can push to it
if !targetRepo.CanContentChange() {
opts.DenyCommitToBranchReason = ctx.Locale.Tr("repo.editor.repo_not_editable")
} else if !canPushWithProtection {
opts.DenyCommitToBranchReason = ctx.Locale.Tr("repo.editor.branch_is_protected")
} else if protectionRequireSigned && !willSign {
opts.DenyCommitToBranchReason = ctx.Locale.Tr("repo.editor.require_signed_commit")
} else {
opts.CanCommitToBranch = true
}
} }
if editorAction == "_cherrypick" { if editorAction == "_cherrypick" {
opts.TargetFormAction = targetRepo.Link() + "/" + editorAction + "/" + ctx.PathParam("sha") + "/" + editorPathParamRemaining opts.TargetFormAction = targetRepo.Link() + "/" + editorAction + "/" + ctx.PathParam("sha") + "/" + editorPathParamRemaining
} else { } else {
+1 -1
View File
@@ -160,7 +160,7 @@
<a class="item" rel="nofollow" href="{{$.BeforeSourcePath}}/{{PathEscapeSegments .Name}}">{{ctx.Locale.Tr "repo.diff.view_file"}}</a> <a class="item" rel="nofollow" href="{{$.BeforeSourcePath}}/{{PathEscapeSegments .Name}}">{{ctx.Locale.Tr "repo.diff.view_file"}}</a>
{{else}} {{else}}
<a class="item" rel="nofollow" href="{{$.SourcePath}}/{{PathEscapeSegments .Name}}">{{ctx.Locale.Tr "repo.diff.view_file"}}</a> <a class="item" rel="nofollow" href="{{$.SourcePath}}/{{PathEscapeSegments .Name}}">{{ctx.Locale.Tr "repo.diff.view_file"}}</a>
{{if and $.Repository.CanEnableEditor $.CanEditFile}} {{if and $.Repository.CanContentChange $.CanEditFile}}
<a class="item" rel="nofollow" href="{{$.HeadRepoLink}}/_edit/{{PathEscapeSegments $.HeadBranchName}}/{{PathEscapeSegments $file.Name}}?return_uri={{print $.BackToLink "#diff-" $file.NameHash | QueryEscape}}">{{ctx.Locale.Tr "repo.editor.edit_this_file"}}</a> <a class="item" rel="nofollow" href="{{$.HeadRepoLink}}/_edit/{{PathEscapeSegments $.HeadBranchName}}/{{PathEscapeSegments $file.Name}}?return_uri={{print $.BackToLink "#diff-" $file.NameHash | QueryEscape}}">{{ctx.Locale.Tr "repo.editor.edit_this_file"}}</a>
{{end}} {{end}}
{{end}} {{end}}
+3 -4
View File
@@ -32,10 +32,9 @@
{{if not .CommitFormOptions.CanCommitToBranch}} {{if not .CommitFormOptions.CanCommitToBranch}}
<div class="tw-mt-2"> <div class="tw-mt-2">
{{ctx.Locale.Tr "repo.editor.no_commit_to_branch"}} {{ctx.Locale.Tr "repo.editor.no_commit_to_branch"}}
<ul class="tw-mb-0"> {{if .CommitFormOptions.DenyCommitToBranchReason}}
{{if not .CommitFormOptions.UserCanPush}}<li>{{ctx.Locale.Tr "repo.editor.user_no_push_to_branch"}}</li>{{end}} <ul class="tw-mb-0"><li>{{.CommitFormOptions.DenyCommitToBranchReason}}</li></ul>
{{if and .CommitFormOptions.RequireSigned (not .CommitFormOptions.WillSign)}}<li>{{ctx.Locale.Tr "repo.editor.require_signed_commit"}}</li>{{end}} {{end}}
</ul>
</div> </div>
{{end}} {{end}}
</label> </label>
+1 -1
View File
@@ -70,7 +70,7 @@
{{$addFilePath = ""}} {{$addFilePath = ""}}
{{end}} {{end}}
{{end}} {{end}}
<button class="ui dropdown basic compact jump button repo-add-file" {{if not .Repository.CanEnableEditor}}disabled{{end}}> <button class="ui dropdown basic compact jump button repo-add-file" {{if not .Repository.CanContentChange}}disabled{{end}}>
{{ctx.Locale.Tr "repo.editor.add_file"}} {{ctx.Locale.Tr "repo.editor.add_file"}}
{{svg "octicon-triangle-down" 14 "dropdown icon"}} {{svg "octicon-triangle-down" 14 "dropdown icon"}}
<div class="menu"> <div class="menu">
+1 -1
View File
@@ -69,7 +69,7 @@
{{svg "octicon-rss"}} {{svg "octicon-rss"}}
</a> </a>
{{end}} {{end}}
{{if .Repository.CanEnableEditor}} {{if .Repository.CanContentChange}}
{{if .CanEditFile}} {{if .CanEditFile}}
<a class="btn-octicon" data-tooltip-content="{{.EditFileTooltip}}" href="{{.RepoLink}}/_edit/{{PathEscapeSegments .BranchName}}/{{PathEscapeSegments .TreePath}}">{{svg "octicon-pencil"}}</a> <a class="btn-octicon" data-tooltip-content="{{.EditFileTooltip}}" href="{{.RepoLink}}/_edit/{{PathEscapeSegments .BranchName}}/{{PathEscapeSegments .TreePath}}">{{svg "octicon-pencil"}}</a>
{{else}} {{else}}