@@ -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,35 @@ 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
7789func prepareTreePathFieldsAndPaths (ctx * context.Context , treePath string ) {
7890 // show the tree path fields in the "breadcrumb" and help users to edit the target tree path
79- ctx .Data ["TreeNames" ], ctx .Data ["TreePaths" ] = getParentTreeFields (treePath )
91+ ctx .Data ["TreeNames" ], ctx .Data ["TreePaths" ] = getParentTreeFields (strings . TrimPrefix ( 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+ OldBranchName string
99+ NewBranchName string
100+ GitCommitter * files_service.IdentityOptions
88101}
89102
90- func (f * parsedEditorCommitForm [T ]) GetCommitMessage (defaultCommitMessage string ) string {
103+ func (f * preparedEditorCommitForm [T ]) GetCommitMessage (defaultCommitMessage string ) string {
91104 commitMessage := util .IfZero (strings .TrimSpace (f .commonForm .CommitSummary ), defaultCommitMessage )
92105 if body := strings .TrimSpace (f .commonForm .CommitMessage ); body != "" {
93106 commitMessage += "\n \n " + body
94107 }
95108 return commitMessage
96109}
97110
98- func parseEditorCommitSubmittedForm [T forms.CommitCommonFormInterface ](ctx * context.Context ) * parsedEditorCommitForm [T ] {
111+ func prepareEditorCommitSubmittedForm [T forms.CommitCommonFormInterface ](ctx * context.Context ) * preparedEditorCommitForm [T ] {
99112 form := web .GetForm (ctx ).(T )
100113 if ctx .HasError () {
101114 ctx .JSONError (ctx .GetErrMsg ())
@@ -105,15 +118,22 @@ func parseEditorCommitSubmittedForm[T forms.CommitCommonFormInterface](ctx *cont
105118 commonForm := form .GetCommitCommonForm ()
106119 commonForm .TreePath = files_service .CleanGitTreePath (commonForm .TreePath )
107120
108- commitFormBehaviors , err := ctx . Repo .PrepareCommitFormBehaviors ( ctx , ctx .Doer )
121+ commitFormOptions , err := context . PrepareCommitFormOptions ( ctx , ctx . Doer , ctx . Repo .Repository , ctx . Repo . Permission , ctx .Repo . RefFullName )
109122 if err != nil {
110- ctx .ServerError ("PrepareCommitFormBehaviors" , err )
123+ ctx .ServerError ("PrepareCommitFormOptions" , err )
124+ return nil
125+ }
126+ if commitFormOptions .NeedFork {
127+ // It shouldn't happen, because we should have done the checks in the "GET" request. But just in case.
128+ ctx .JSONError (ctx .Locale .TrString ("error.not_found" ))
111129 return nil
112130 }
113131
114132 // check commit behavior
115- targetBranchName := util .Iif (commonForm .CommitChoice == editorCommitChoiceNewBranch , commonForm .NewBranchName , ctx .Repo .BranchName )
116- if targetBranchName == ctx .Repo .BranchName && ! commitFormBehaviors .CanCommitToBranch {
133+ fromBaseBranch := ctx .FormString ("from_base_branch" )
134+ commitToNewBranch := commonForm .CommitChoice == editorCommitChoiceNewBranch || fromBaseBranch != ""
135+ targetBranchName := util .Iif (commitToNewBranch , commonForm .NewBranchName , ctx .Repo .BranchName )
136+ if targetBranchName == ctx .Repo .BranchName && ! commitFormOptions .CanCommitToBranch {
117137 ctx .JSONError (ctx .Tr ("repo.editor.cannot_commit_to_protected_branch" , targetBranchName ))
118138 return nil
119139 }
@@ -125,40 +145,73 @@ func parseEditorCommitSubmittedForm[T forms.CommitCommonFormInterface](ctx *cont
125145 return nil
126146 }
127147
128- return & parsedEditorCommitForm [T ]{
129- form : form ,
130- commonForm : commonForm ,
131- CommitFormBehaviors : commitFormBehaviors ,
132- TargetBranchName : targetBranchName ,
133- GitCommitter : gitCommitter ,
148+ if commitToNewBranch {
149+ // if target branch exists, we should stop
150+ targetBranchExists , err := git_model .IsBranchExist (ctx , commitFormOptions .TargetRepo .ID , targetBranchName )
151+ if err != nil {
152+ ctx .ServerError ("IsBranchExist" , err )
153+ return nil
154+ } else if targetBranchExists {
155+ if fromBaseBranch != "" {
156+ ctx .JSONError (ctx .Tr ("repo.editor.fork_branch_exists" , targetBranchName ))
157+ } else {
158+ ctx .JSONError (ctx .Tr ("repo.editor.branch_already_exists" , targetBranchName ))
159+ }
160+ return nil
161+ }
162+ }
163+
164+ oldBranchName := ctx .Repo .BranchName
165+ if fromBaseBranch != "" {
166+ err = editorPushBranchToForkedRepository (ctx , ctx .Doer , ctx .Repo .Repository .BaseRepo , fromBaseBranch , commitFormOptions .TargetRepo , targetBranchName )
167+ if err != nil {
168+ log .Error ("Unable to editorPushBranchToForkedRepository: %v" , err )
169+ ctx .JSONError (ctx .Tr ("repo.editor.fork_failed_to_push_branch" , targetBranchName ))
170+ return nil
171+ }
172+ // we have pushed the base branch as the new branch, now we need to commit the changes directly to the new branch
173+ oldBranchName = targetBranchName
174+ }
175+
176+ return & preparedEditorCommitForm [T ]{
177+ form : form ,
178+ commonForm : commonForm ,
179+ CommitFormOptions : commitFormOptions ,
180+ OldBranchName : oldBranchName ,
181+ NewBranchName : targetBranchName ,
182+ GitCommitter : gitCommitter ,
134183 }
135184}
136185
137186// redirectForCommitChoice redirects after committing the edit to a branch
138- func redirectForCommitChoice [T any ](ctx * context.Context , parsed * parsedEditorCommitForm [T ], treePath string ) {
187+ func redirectForCommitChoice [T any ](ctx * context.Context , parsed * preparedEditorCommitForm [T ], treePath string ) {
188+ // when editing a file in a PR, it should return to the origin location
189+ if returnURI := ctx .FormString ("return_uri" ); returnURI != "" && httplib .IsCurrentGiteaSiteURL (ctx , returnURI ) {
190+ ctx .JSONRedirect (returnURI )
191+ return
192+ }
193+
139194 if parsed .commonForm .CommitChoice == editorCommitChoiceNewBranch {
140195 // Redirect to a pull request when possible
141196 redirectToPullRequest := false
142- 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 {
197+ repo , baseBranch , headBranch := ctx .Repo .Repository , parsed .OldBranchName , parsed .NewBranchName
198+ if ctx .Repo .Repository .IsFork && parsed .CommitFormOptions .CanCreateBasePullRequest {
146199 redirectToPullRequest = true
147200 baseBranch = repo .BaseRepo .DefaultBranch
148201 headBranch = repo .Owner .Name + "/" + repo .Name + ":" + headBranch
149202 repo = repo .BaseRepo
203+ } else if repo .UnitEnabled (ctx , unit .TypePullRequests ) {
204+ redirectToPullRequest = true
150205 }
151206 if redirectToPullRequest {
152207 ctx .JSONRedirect (repo .Link () + "/compare/" + util .PathEscapeSegments (baseBranch ) + "..." + util .PathEscapeSegments (headBranch ))
153208 return
154209 }
155210 }
156211
157- returnURI := ctx .FormString ("return_uri" )
158- if returnURI == "" || ! httplib .IsCurrentGiteaSiteURL (ctx , returnURI ) {
159- returnURI = util .URLJoin (ctx .Repo .RepoLink , "src/branch" , util .PathEscapeSegments (parsed .TargetBranchName ), util .PathEscapeSegments (treePath ))
160- }
161- ctx .JSONRedirect (returnURI )
212+ // redirect to the newly updated file
213+ redirectTo := util .URLJoin (ctx .Repo .RepoLink , "src/branch" , util .PathEscapeSegments (parsed .NewBranchName ), util .PathEscapeSegments (treePath ))
214+ ctx .JSONRedirect (redirectTo )
162215}
163216
164217func editFileOpenExisting (ctx * context.Context ) (prefetch []byte , dataRc io.ReadCloser , fInfo * fileInfo ) {
@@ -268,7 +321,7 @@ func EditFile(ctx *context.Context) {
268321func EditFilePost (ctx * context.Context ) {
269322 editorAction := ctx .PathParam ("editor_action" )
270323 isNewFile := editorAction == "_new"
271- parsed := parseEditorCommitSubmittedForm [* forms.EditRepoFileForm ](ctx )
324+ parsed := prepareEditorCommitSubmittedForm [* forms.EditRepoFileForm ](ctx )
272325 if ctx .Written () {
273326 return
274327 }
@@ -292,8 +345,8 @@ func EditFilePost(ctx *context.Context) {
292345
293346 _ , err := files_service .ChangeRepoFiles (ctx , ctx .Repo .Repository , ctx .Doer , & files_service.ChangeRepoFilesOptions {
294347 LastCommitID : parsed .form .LastCommit ,
295- OldBranch : ctx . Repo . BranchName ,
296- NewBranch : parsed .TargetBranchName ,
348+ OldBranch : parsed . OldBranchName ,
349+ NewBranch : parsed .NewBranchName ,
297350 Message : parsed .GetCommitMessage (defaultCommitMessage ),
298351 Files : []* files_service.ChangeRepoFile {
299352 {
@@ -308,7 +361,7 @@ func EditFilePost(ctx *context.Context) {
308361 Committer : parsed .GitCommitter ,
309362 })
310363 if err != nil {
311- editorHandleFileOperationError (ctx , parsed .TargetBranchName , err )
364+ editorHandleFileOperationError (ctx , parsed .NewBranchName , err )
312365 return
313366 }
314367
@@ -327,16 +380,16 @@ func DeleteFile(ctx *context.Context) {
327380
328381// DeleteFilePost response for deleting file
329382func DeleteFilePost (ctx * context.Context ) {
330- parsed := parseEditorCommitSubmittedForm [* forms.DeleteRepoFileForm ](ctx )
383+ parsed := prepareEditorCommitSubmittedForm [* forms.DeleteRepoFileForm ](ctx )
331384 if ctx .Written () {
332385 return
333386 }
334387
335388 treePath := ctx .Repo .TreePath
336389 _ , err := files_service .ChangeRepoFiles (ctx , ctx .Repo .Repository , ctx .Doer , & files_service.ChangeRepoFilesOptions {
337390 LastCommitID : parsed .form .LastCommit ,
338- OldBranch : ctx . Repo . BranchName ,
339- NewBranch : parsed .TargetBranchName ,
391+ OldBranch : parsed . OldBranchName ,
392+ NewBranch : parsed .NewBranchName ,
340393 Files : []* files_service.ChangeRepoFile {
341394 {
342395 Operation : "delete" ,
@@ -349,38 +402,38 @@ func DeleteFilePost(ctx *context.Context) {
349402 Committer : parsed .GitCommitter ,
350403 })
351404 if err != nil {
352- editorHandleFileOperationError (ctx , parsed .TargetBranchName , err )
405+ editorHandleFileOperationError (ctx , parsed .NewBranchName , err )
353406 return
354407 }
355408
356409 ctx .Flash .Success (ctx .Tr ("repo.editor.file_delete_success" , treePath ))
357- redirectTreePath := getClosestParentWithFiles (ctx .Repo .GitRepo , parsed .TargetBranchName , treePath )
410+ redirectTreePath := getClosestParentWithFiles (ctx .Repo .GitRepo , parsed .NewBranchName , treePath )
358411 redirectForCommitChoice (ctx , parsed , redirectTreePath )
359412}
360413
361414func UploadFile (ctx * context.Context ) {
362415 ctx .Data ["PageIsUpload" ] = true
363- upload .AddUploadContext (ctx , "repo" )
364416 prepareTreePathFieldsAndPaths (ctx , ctx .Repo .TreePath )
365-
366- prepareEditorCommitFormOptions (ctx , "_upload" )
417+ opts := prepareEditorCommitFormOptions (ctx , "_upload" )
367418 if ctx .Written () {
368419 return
369420 }
421+ upload .AddUploadContextForRepo (ctx , opts .TargetRepo )
422+
370423 ctx .HTML (http .StatusOK , tplUploadFile )
371424}
372425
373426func UploadFilePost (ctx * context.Context ) {
374- parsed := parseEditorCommitSubmittedForm [* forms.UploadRepoFileForm ](ctx )
427+ parsed := prepareEditorCommitSubmittedForm [* forms.UploadRepoFileForm ](ctx )
375428 if ctx .Written () {
376429 return
377430 }
378431
379432 defaultCommitMessage := ctx .Locale .TrString ("repo.editor.upload_files_to_dir" , util .IfZero (parsed .form .TreePath , "/" ))
380433 err := files_service .UploadRepoFiles (ctx , ctx .Repo .Repository , ctx .Doer , & files_service.UploadRepoFileOptions {
381434 LastCommitID : parsed .form .LastCommit ,
382- OldBranch : ctx . Repo . BranchName ,
383- NewBranch : parsed .TargetBranchName ,
435+ OldBranch : parsed . OldBranchName ,
436+ NewBranch : parsed .NewBranchName ,
384437 TreePath : parsed .form .TreePath ,
385438 Message : parsed .GetCommitMessage (defaultCommitMessage ),
386439 Files : parsed .form .Files ,
@@ -389,7 +442,7 @@ func UploadFilePost(ctx *context.Context) {
389442 Committer : parsed .GitCommitter ,
390443 })
391444 if err != nil {
392- editorHandleFileOperationError (ctx , parsed .TargetBranchName , err )
445+ editorHandleFileOperationError (ctx , parsed .NewBranchName , err )
393446 return
394447 }
395448 redirectForCommitChoice (ctx , parsed , parsed .form .TreePath )
0 commit comments