Skip to content

Commit 21dd561

Browse files
committed
sync
1 parent 4fc626d commit 21dd561

File tree

20 files changed

+337
-119
lines changed

20 files changed

+337
-119
lines changed

models/repo/repo.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -653,7 +653,7 @@ func (repo *Repository) AllowsPulls(ctx context.Context) bool {
653653

654654
// CanEnableEditor returns true if repository meets the requirements of web editor.
655655
func (repo *Repository) CanEnableEditor() bool {
656-
return !repo.IsMirror
656+
return !repo.IsMirror && !repo.IsArchived
657657
}
658658

659659
// DescriptionHTML does special handles to description and return HTML string.

options/locale/locale_en-US.ini

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1398,6 +1398,12 @@ editor.revert = Revert %s onto:
13981398
editor.failed_to_commit = Failed to commit changes.
13991399
editor.failed_to_commit_summary = Error Message:
14001400
1401+
editor.fork_create = Fork Repository to Propose Changes
1402+
editor.fork_create_description = You can not edit this repository directly. Instead you can create a fork, make edits and create a pull request.
1403+
editor.fork_edit_description = You can not edit this repository directly. The changes will be written to your fork <b>%s</b>, so you can create a pull request.
1404+
editor.fork_not_editable = You have forked this repository but your fork is not editable.
1405+
editor.fork_failed_to_push_branch = Failed to push branch %s to your repository.
1406+
14011407
commits.desc = Browse source code change history.
14021408
commits.commits = Commits
14031409
commits.no_commits = No commits in common. "%s" and "%s" have entirely different histories.

routers/web/repo/editor.go

Lines changed: 62 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"code.gitea.io/gitea/modules/charset"
1717
"code.gitea.io/gitea/modules/git"
1818
"code.gitea.io/gitea/modules/httplib"
19+
"code.gitea.io/gitea/modules/log"
1920
"code.gitea.io/gitea/modules/markup"
2021
"code.gitea.io/gitea/modules/setting"
2122
"code.gitea.io/gitea/modules/templates"
@@ -39,26 +40,36 @@ const (
3940
editorCommitChoiceNewBranch string = "commit-to-new-branch"
4041
)
4142

42-
func prepareEditorCommitFormOptions(ctx *context.Context, editorAction string) {
43+
func prepareEditorCommitFormOptions(ctx *context.Context, editorAction string) *context.CommitFormOptions {
4344
cleanedTreePath := files_service.CleanGitTreePath(ctx.Repo.TreePath)
4445
if cleanedTreePath != ctx.Repo.TreePath {
4546
redirectTo := fmt.Sprintf("%s/%s/%s/%s", ctx.Repo.RepoLink, editorAction, util.PathEscapeSegments(ctx.Repo.BranchName), util.PathEscapeSegments(cleanedTreePath))
4647
if ctx.Req.URL.RawQuery != "" {
4748
redirectTo += "?" + ctx.Req.URL.RawQuery
4849
}
4950
ctx.Redirect(redirectTo)
50-
return
51+
return nil
5152
}
5253

53-
commitFormBehaviors, err := ctx.Repo.PrepareCommitFormBehaviors(ctx, ctx.Doer)
54+
commitFormOptions, err := context.PrepareCommitFormOptions(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.Permission, ctx.Repo.RefFullName)
5455
if err != nil {
55-
ctx.ServerError("PrepareCommitFormBehaviors", err)
56-
return
56+
ctx.ServerError("PrepareCommitFormOptions", err)
57+
return nil
58+
}
59+
60+
if commitFormOptions.NeedFork {
61+
ForkToEdit(ctx)
62+
return nil
63+
}
64+
65+
if commitFormOptions.WillSubmitToFork && !commitFormOptions.TargetRepo.CanEnableEditor() {
66+
ctx.Data["NotFoundPrompt"] = ctx.Locale.Tr("repo.editor.fork_not_editable")
67+
ctx.NotFound(nil)
5768
}
5869

5970
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL()
6071
ctx.Data["TreePath"] = ctx.Repo.TreePath
61-
ctx.Data["CommitFormBehaviors"] = commitFormBehaviors
72+
ctx.Data["CommitFormOptions"] = commitFormOptions
6273

6374
// for online editor
6475
ctx.Data["PreviewableExtensions"] = strings.Join(markup.PreviewableExtensions(), ",")
@@ -69,33 +80,34 @@ func prepareEditorCommitFormOptions(ctx *context.Context, editorAction string) {
6980
// form fields
7081
ctx.Data["commit_summary"] = ""
7182
ctx.Data["commit_message"] = ""
72-
ctx.Data["commit_choice"] = util.Iif(commitFormBehaviors.CanCommitToBranch, editorCommitChoiceDirect, editorCommitChoiceNewBranch)
73-
ctx.Data["new_branch_name"] = getUniquePatchBranchName(ctx, ctx.Doer.LowerName, ctx.Repo.Repository)
83+
ctx.Data["commit_choice"] = util.Iif(commitFormOptions.CanCommitToBranch, editorCommitChoiceDirect, editorCommitChoiceNewBranch)
84+
ctx.Data["new_branch_name"] = getUniquePatchBranchName(ctx, ctx.Doer.LowerName, commitFormOptions.TargetRepo)
7485
ctx.Data["last_commit"] = ctx.Repo.CommitID
86+
return commitFormOptions
7587
}
7688

7789
func prepareTreePathFieldsAndPaths(ctx *context.Context, treePath string) {
7890
// show the tree path fields in the "breadcrumb" and help users to edit the target tree path
7991
ctx.Data["TreeNames"], ctx.Data["TreePaths"] = getParentTreeFields(treePath)
8092
}
8193

82-
type parsedEditorCommitForm[T any] struct {
83-
form T
84-
commonForm *forms.CommitCommonForm
85-
CommitFormBehaviors *context.CommitFormBehaviors
86-
TargetBranchName string
87-
GitCommitter *files_service.IdentityOptions
94+
type preparedEditorCommitForm[T any] struct {
95+
form T
96+
commonForm *forms.CommitCommonForm
97+
CommitFormOptions *context.CommitFormOptions
98+
TargetBranchName string
99+
GitCommitter *files_service.IdentityOptions
88100
}
89101

90-
func (f *parsedEditorCommitForm[T]) GetCommitMessage(defaultCommitMessage string) string {
102+
func (f *preparedEditorCommitForm[T]) GetCommitMessage(defaultCommitMessage string) string {
91103
commitMessage := util.IfZero(strings.TrimSpace(f.commonForm.CommitSummary), defaultCommitMessage)
92104
if body := strings.TrimSpace(f.commonForm.CommitMessage); body != "" {
93105
commitMessage += "\n\n" + body
94106
}
95107
return commitMessage
96108
}
97109

98-
func parseEditorCommitSubmittedForm[T forms.CommitCommonFormInterface](ctx *context.Context) *parsedEditorCommitForm[T] {
110+
func prepareEditorCommitSubmittedForm[T forms.CommitCommonFormInterface](ctx *context.Context) *preparedEditorCommitForm[T] {
99111
form := web.GetForm(ctx).(T)
100112
if ctx.HasError() {
101113
ctx.JSONError(ctx.GetErrMsg())
@@ -105,15 +117,20 @@ func parseEditorCommitSubmittedForm[T forms.CommitCommonFormInterface](ctx *cont
105117
commonForm := form.GetCommitCommonForm()
106118
commonForm.TreePath = files_service.CleanGitTreePath(commonForm.TreePath)
107119

108-
commitFormBehaviors, err := ctx.Repo.PrepareCommitFormBehaviors(ctx, ctx.Doer)
120+
commitFormOptions, err := context.PrepareCommitFormOptions(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.Permission, ctx.Repo.RefFullName)
109121
if err != nil {
110-
ctx.ServerError("PrepareCommitFormBehaviors", err)
122+
ctx.ServerError("PrepareCommitFormOptions", err)
123+
return nil
124+
}
125+
if commitFormOptions.NeedFork {
126+
// It shouldn't happen, because we should have done the checks in the "GET" request. But just in case.
127+
ctx.JSONError(ctx.Locale.TrString("error.not_found"))
111128
return nil
112129
}
113130

114131
// check commit behavior
115132
targetBranchName := util.Iif(commonForm.CommitChoice == editorCommitChoiceNewBranch, commonForm.NewBranchName, ctx.Repo.BranchName)
116-
if targetBranchName == ctx.Repo.BranchName && !commitFormBehaviors.CanCommitToBranch {
133+
if targetBranchName == ctx.Repo.BranchName && !commitFormOptions.CanCommitToBranch {
117134
ctx.JSONError(ctx.Tr("repo.editor.cannot_commit_to_protected_branch", targetBranchName))
118135
return nil
119136
}
@@ -125,28 +142,38 @@ func parseEditorCommitSubmittedForm[T forms.CommitCommonFormInterface](ctx *cont
125142
return nil
126143
}
127144

128-
return &parsedEditorCommitForm[T]{
129-
form: form,
130-
commonForm: commonForm,
131-
CommitFormBehaviors: commitFormBehaviors,
132-
TargetBranchName: targetBranchName,
133-
GitCommitter: gitCommitter,
145+
fromBaseBranch := ctx.FormString("from_base_branch")
146+
if fromBaseBranch != "" {
147+
err = editorPushBranchToForkedRepository(ctx, ctx.Doer, ctx.Repo.Repository.BaseRepo, fromBaseBranch, ctx.Repo.Repository, ctx.Repo.RefFullName.BranchName())
148+
if err != nil {
149+
log.Error("Unable to editorPushBranchToForkedRepository: %v", err)
150+
ctx.JSONError(ctx.Tr("repo.editor.fork_failed_to_push_branch", targetBranchName))
151+
return nil
152+
}
153+
}
154+
155+
return &preparedEditorCommitForm[T]{
156+
form: form,
157+
commonForm: commonForm,
158+
CommitFormOptions: commitFormOptions,
159+
TargetBranchName: targetBranchName,
160+
GitCommitter: gitCommitter,
134161
}
135162
}
136163

137164
// redirectForCommitChoice redirects after committing the edit to a branch
138-
func redirectForCommitChoice[T any](ctx *context.Context, parsed *parsedEditorCommitForm[T], treePath string) {
165+
func redirectForCommitChoice[T any](ctx *context.Context, parsed *preparedEditorCommitForm[T], treePath string) {
139166
if parsed.commonForm.CommitChoice == editorCommitChoiceNewBranch {
140167
// Redirect to a pull request when possible
141168
redirectToPullRequest := false
142169
repo, baseBranch, headBranch := ctx.Repo.Repository, ctx.Repo.BranchName, parsed.TargetBranchName
143-
if repo.UnitEnabled(ctx, unit.TypePullRequests) {
144-
redirectToPullRequest = true
145-
} else if parsed.CommitFormBehaviors.CanCreateBasePullRequest {
170+
if ctx.Repo.Repository.IsFork && parsed.CommitFormOptions.CanCreateBasePullRequest {
146171
redirectToPullRequest = true
147172
baseBranch = repo.BaseRepo.DefaultBranch
148173
headBranch = repo.Owner.Name + "/" + repo.Name + ":" + headBranch
149174
repo = repo.BaseRepo
175+
} else if repo.UnitEnabled(ctx, unit.TypePullRequests) {
176+
redirectToPullRequest = true
150177
}
151178
if redirectToPullRequest {
152179
ctx.JSONRedirect(repo.Link() + "/compare/" + util.PathEscapeSegments(baseBranch) + "..." + util.PathEscapeSegments(headBranch))
@@ -268,7 +295,7 @@ func EditFile(ctx *context.Context) {
268295
func EditFilePost(ctx *context.Context) {
269296
editorAction := ctx.PathParam("editor_action")
270297
isNewFile := editorAction == "_new"
271-
parsed := parseEditorCommitSubmittedForm[*forms.EditRepoFileForm](ctx)
298+
parsed := prepareEditorCommitSubmittedForm[*forms.EditRepoFileForm](ctx)
272299
if ctx.Written() {
273300
return
274301
}
@@ -327,7 +354,7 @@ func DeleteFile(ctx *context.Context) {
327354

328355
// DeleteFilePost response for deleting file
329356
func DeleteFilePost(ctx *context.Context) {
330-
parsed := parseEditorCommitSubmittedForm[*forms.DeleteRepoFileForm](ctx)
357+
parsed := prepareEditorCommitSubmittedForm[*forms.DeleteRepoFileForm](ctx)
331358
if ctx.Written() {
332359
return
333360
}
@@ -360,18 +387,18 @@ func DeleteFilePost(ctx *context.Context) {
360387

361388
func UploadFile(ctx *context.Context) {
362389
ctx.Data["PageIsUpload"] = true
363-
upload.AddUploadContext(ctx, "repo")
364390
prepareTreePathFieldsAndPaths(ctx, ctx.Repo.TreePath)
365-
366-
prepareEditorCommitFormOptions(ctx, "_upload")
391+
opts := prepareEditorCommitFormOptions(ctx, "_upload")
367392
if ctx.Written() {
368393
return
369394
}
395+
upload.AddUploadContextForRepo(ctx, opts.TargetRepo)
396+
370397
ctx.HTML(http.StatusOK, tplUploadFile)
371398
}
372399

373400
func UploadFilePost(ctx *context.Context) {
374-
parsed := parseEditorCommitSubmittedForm[*forms.UploadRepoFileForm](ctx)
401+
parsed := prepareEditorCommitSubmittedForm[*forms.UploadRepoFileForm](ctx)
375402
if ctx.Written() {
376403
return
377404
}

routers/web/repo/editor_apply_patch.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func NewDiffPatch(ctx *context.Context) {
2525

2626
// NewDiffPatchPost response for sending patch page
2727
func NewDiffPatchPost(ctx *context.Context) {
28-
parsed := parseEditorCommitSubmittedForm[*forms.EditRepoFileForm](ctx)
28+
parsed := prepareEditorCommitSubmittedForm[*forms.EditRepoFileForm](ctx)
2929
if ctx.Written() {
3030
return
3131
}

routers/web/repo/editor_cherry_pick.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func CherryPick(ctx *context.Context) {
4545

4646
func CherryPickPost(ctx *context.Context) {
4747
fromCommitID := ctx.PathParam("sha")
48-
parsed := parseEditorCommitSubmittedForm[*forms.CherryPickForm](ctx)
48+
parsed := prepareEditorCommitSubmittedForm[*forms.CherryPickForm](ctx)
4949
if ctx.Written() {
5050
return
5151
}

routers/web/repo/editor_fork.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package repo
5+
6+
import (
7+
"net/http"
8+
9+
"code.gitea.io/gitea/modules/templates"
10+
"code.gitea.io/gitea/services/context"
11+
repo_service "code.gitea.io/gitea/services/repository"
12+
)
13+
14+
const tplEditorFork templates.TplName = "repo/editor/fork"
15+
16+
func ForkToEdit(ctx *context.Context) {
17+
ctx.HTML(http.StatusOK, tplEditorFork)
18+
}
19+
20+
func ForkToEditPost(ctx *context.Context) {
21+
ForkRepoTo(ctx, ctx.Doer, repo_service.ForkRepoOptions{
22+
BaseRepo: ctx.Repo.Repository,
23+
Name: getUniqueRepositoryName(ctx, ctx.Doer.ID, ctx.Repo.Repository.Name),
24+
Description: ctx.Repo.Repository.Description,
25+
SingleBranch: ctx.Repo.Repository.DefaultBranch, // maybe we only need the default branch in the fork?
26+
})
27+
if ctx.Written() {
28+
return
29+
}
30+
ctx.JSONRedirect("") // reload the page, the new fork should be editable now
31+
}

routers/web/repo/editor_util.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ import (
1111

1212
git_model "code.gitea.io/gitea/models/git"
1313
repo_model "code.gitea.io/gitea/models/repo"
14+
user_model "code.gitea.io/gitea/models/user"
1415
"code.gitea.io/gitea/modules/git"
1516
"code.gitea.io/gitea/modules/json"
1617
"code.gitea.io/gitea/modules/log"
18+
repo_module "code.gitea.io/gitea/modules/repository"
1719
context_service "code.gitea.io/gitea/services/context"
1820
)
1921

@@ -83,3 +85,26 @@ func getParentTreeFields(treePath string) (treeNames, treePaths []string) {
8385
}
8486
return treeNames, treePaths
8587
}
88+
89+
// getUniqueRepositoryName Gets a unique repository name for a user
90+
// It will append a -<num> postfix if the name is already taken
91+
func getUniqueRepositoryName(ctx context.Context, ownerID int64, name string) string {
92+
uniqueName := name
93+
for i := 1; i < 1000; i++ {
94+
_, err := repo_model.GetRepositoryByName(ctx, ownerID, uniqueName)
95+
if err != nil || repo_model.IsErrRepoNotExist(err) {
96+
return uniqueName
97+
}
98+
uniqueName = fmt.Sprintf("%s-%d", name, i)
99+
i++
100+
}
101+
return ""
102+
}
103+
104+
func editorPushBranchToForkedRepository(ctx context.Context, doer *user_model.User, baseRepo *repo_model.Repository, baseBranchName string, targetRepo *repo_model.Repository, targetBranchName string) error {
105+
return git.Push(ctx, baseRepo.RepoPath(), git.PushOptions{
106+
Remote: targetRepo.RepoPath(),
107+
Branch: baseBranchName + ":" + targetBranchName,
108+
Env: repo_module.PushingEnvironment(doer, targetRepo),
109+
})
110+
}

routers/web/repo/fork.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -189,17 +189,25 @@ func ForkPost(ctx *context.Context) {
189189
}
190190
}
191191

192-
repo, err := repo_service.ForkRepository(ctx, ctx.Doer, ctxUser, repo_service.ForkRepoOptions{
192+
repo := ForkRepoTo(ctx, ctxUser, repo_service.ForkRepoOptions{
193193
BaseRepo: forkRepo,
194194
Name: form.RepoName,
195195
Description: form.Description,
196196
SingleBranch: form.ForkSingleBranch,
197197
})
198+
if ctx.Written() {
199+
return
200+
}
201+
ctx.JSONRedirect(ctxUser.HomeLink() + "/" + url.PathEscape(repo.Name))
202+
}
203+
204+
func ForkRepoTo(ctx *context.Context, owner *user_model.User, forkOpts repo_service.ForkRepoOptions) *repo_model.Repository {
205+
repo, err := repo_service.ForkRepository(ctx, ctx.Doer, owner, forkOpts)
198206
if err != nil {
199207
ctx.Data["Err_RepoName"] = true
200208
switch {
201209
case repo_model.IsErrReachLimitOfRepo(err):
202-
maxCreationLimit := ctxUser.MaxCreationLimit()
210+
maxCreationLimit := owner.MaxCreationLimit()
203211
msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit)
204212
ctx.JSONError(msg)
205213
case repo_model.IsErrRepoAlreadyExist(err):
@@ -224,9 +232,7 @@ func ForkPost(ctx *context.Context) {
224232
default:
225233
ctx.ServerError("ForkPost", err)
226234
}
227-
return
235+
return nil
228236
}
229-
230-
log.Trace("Repository forked[%d]: %s/%s", forkRepo.ID, ctxUser.Name, repo.Name)
231-
ctx.JSONRedirect(ctxUser.HomeLink() + "/" + url.PathEscape(repo.Name))
237+
return repo
232238
}

routers/web/repo/view_readme.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ func prepareToRenderReadmeFile(ctx *context.Context, subfolder string, readmeFil
212212
ctx.Data["EscapeStatus"], ctx.Data["FileContent"] = charset.EscapeControlHTML(template.HTML(contentEscaped), ctx.Locale)
213213
}
214214

215-
if !fInfo.isLFSFile && ctx.Repo.CanEnableEditor(ctx, ctx.Doer) {
215+
if !fInfo.isLFSFile && ctx.Repo.Repository.CanEnableEditor() {
216216
ctx.Data["CanEditReadmeFile"] = true
217217
}
218218
}

0 commit comments

Comments
 (0)