@@ -16,6 +16,7 @@ import (
16
16
"code.gitea.io/gitea/modules/charset"
17
17
"code.gitea.io/gitea/modules/git"
18
18
"code.gitea.io/gitea/modules/httplib"
19
+ "code.gitea.io/gitea/modules/log"
19
20
"code.gitea.io/gitea/modules/markup"
20
21
"code.gitea.io/gitea/modules/setting"
21
22
"code.gitea.io/gitea/modules/templates"
@@ -39,26 +40,36 @@ const (
39
40
editorCommitChoiceNewBranch string = "commit-to-new-branch"
40
41
)
41
42
42
- func prepareEditorCommitFormOptions (ctx * context.Context , editorAction string ) {
43
+ func prepareEditorCommitFormOptions (ctx * context.Context , editorAction string ) * context. CommitFormOptions {
43
44
cleanedTreePath := files_service .CleanGitTreePath (ctx .Repo .TreePath )
44
45
if cleanedTreePath != ctx .Repo .TreePath {
45
46
redirectTo := fmt .Sprintf ("%s/%s/%s/%s" , ctx .Repo .RepoLink , editorAction , util .PathEscapeSegments (ctx .Repo .BranchName ), util .PathEscapeSegments (cleanedTreePath ))
46
47
if ctx .Req .URL .RawQuery != "" {
47
48
redirectTo += "?" + ctx .Req .URL .RawQuery
48
49
}
49
50
ctx .Redirect (redirectTo )
50
- return
51
+ return nil
51
52
}
52
53
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 )
54
55
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 )
57
68
}
58
69
59
70
ctx .Data ["BranchLink" ] = ctx .Repo .RepoLink + "/src/" + ctx .Repo .RefTypeNameSubURL ()
60
71
ctx .Data ["TreePath" ] = ctx .Repo .TreePath
61
- ctx .Data ["CommitFormBehaviors " ] = commitFormBehaviors
72
+ ctx .Data ["CommitFormOptions " ] = commitFormOptions
62
73
63
74
// for online editor
64
75
ctx .Data ["PreviewableExtensions" ] = strings .Join (markup .PreviewableExtensions (), "," )
@@ -69,33 +80,35 @@ func prepareEditorCommitFormOptions(ctx *context.Context, editorAction string) {
69
80
// form fields
70
81
ctx .Data ["commit_summary" ] = ""
71
82
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 )
74
85
ctx .Data ["last_commit" ] = ctx .Repo .CommitID
86
+ return commitFormOptions
75
87
}
76
88
77
89
func prepareTreePathFieldsAndPaths (ctx * context.Context , treePath string ) {
78
90
// 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 , "/" ) )
80
92
}
81
93
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
88
101
}
89
102
90
- func (f * parsedEditorCommitForm [T ]) GetCommitMessage (defaultCommitMessage string ) string {
103
+ func (f * preparedEditorCommitForm [T ]) GetCommitMessage (defaultCommitMessage string ) string {
91
104
commitMessage := util .IfZero (strings .TrimSpace (f .commonForm .CommitSummary ), defaultCommitMessage )
92
105
if body := strings .TrimSpace (f .commonForm .CommitMessage ); body != "" {
93
106
commitMessage += "\n \n " + body
94
107
}
95
108
return commitMessage
96
109
}
97
110
98
- func parseEditorCommitSubmittedForm [T forms.CommitCommonFormInterface ](ctx * context.Context ) * parsedEditorCommitForm [T ] {
111
+ func prepareEditorCommitSubmittedForm [T forms.CommitCommonFormInterface ](ctx * context.Context ) * preparedEditorCommitForm [T ] {
99
112
form := web .GetForm (ctx ).(T )
100
113
if ctx .HasError () {
101
114
ctx .JSONError (ctx .GetErrMsg ())
@@ -105,15 +118,22 @@ func parseEditorCommitSubmittedForm[T forms.CommitCommonFormInterface](ctx *cont
105
118
commonForm := form .GetCommitCommonForm ()
106
119
commonForm .TreePath = files_service .CleanGitTreePath (commonForm .TreePath )
107
120
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 )
109
122
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" ))
111
129
return nil
112
130
}
113
131
114
132
// 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 {
117
137
ctx .JSONError (ctx .Tr ("repo.editor.cannot_commit_to_protected_branch" , targetBranchName ))
118
138
return nil
119
139
}
@@ -125,40 +145,73 @@ func parseEditorCommitSubmittedForm[T forms.CommitCommonFormInterface](ctx *cont
125
145
return nil
126
146
}
127
147
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 ,
134
183
}
135
184
}
136
185
137
186
// 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
+
139
194
if parsed .commonForm .CommitChoice == editorCommitChoiceNewBranch {
140
195
// Redirect to a pull request when possible
141
196
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 {
146
199
redirectToPullRequest = true
147
200
baseBranch = repo .BaseRepo .DefaultBranch
148
201
headBranch = repo .Owner .Name + "/" + repo .Name + ":" + headBranch
149
202
repo = repo .BaseRepo
203
+ } else if repo .UnitEnabled (ctx , unit .TypePullRequests ) {
204
+ redirectToPullRequest = true
150
205
}
151
206
if redirectToPullRequest {
152
207
ctx .JSONRedirect (repo .Link () + "/compare/" + util .PathEscapeSegments (baseBranch ) + "..." + util .PathEscapeSegments (headBranch ))
153
208
return
154
209
}
155
210
}
156
211
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 )
162
215
}
163
216
164
217
func editFileOpenExisting (ctx * context.Context ) (prefetch []byte , dataRc io.ReadCloser , fInfo * fileInfo ) {
@@ -268,7 +321,7 @@ func EditFile(ctx *context.Context) {
268
321
func EditFilePost (ctx * context.Context ) {
269
322
editorAction := ctx .PathParam ("editor_action" )
270
323
isNewFile := editorAction == "_new"
271
- parsed := parseEditorCommitSubmittedForm [* forms.EditRepoFileForm ](ctx )
324
+ parsed := prepareEditorCommitSubmittedForm [* forms.EditRepoFileForm ](ctx )
272
325
if ctx .Written () {
273
326
return
274
327
}
@@ -292,8 +345,8 @@ func EditFilePost(ctx *context.Context) {
292
345
293
346
_ , err := files_service .ChangeRepoFiles (ctx , ctx .Repo .Repository , ctx .Doer , & files_service.ChangeRepoFilesOptions {
294
347
LastCommitID : parsed .form .LastCommit ,
295
- OldBranch : ctx . Repo . BranchName ,
296
- NewBranch : parsed .TargetBranchName ,
348
+ OldBranch : parsed . OldBranchName ,
349
+ NewBranch : parsed .NewBranchName ,
297
350
Message : parsed .GetCommitMessage (defaultCommitMessage ),
298
351
Files : []* files_service.ChangeRepoFile {
299
352
{
@@ -308,7 +361,7 @@ func EditFilePost(ctx *context.Context) {
308
361
Committer : parsed .GitCommitter ,
309
362
})
310
363
if err != nil {
311
- editorHandleFileOperationError (ctx , parsed .TargetBranchName , err )
364
+ editorHandleFileOperationError (ctx , parsed .NewBranchName , err )
312
365
return
313
366
}
314
367
@@ -327,16 +380,16 @@ func DeleteFile(ctx *context.Context) {
327
380
328
381
// DeleteFilePost response for deleting file
329
382
func DeleteFilePost (ctx * context.Context ) {
330
- parsed := parseEditorCommitSubmittedForm [* forms.DeleteRepoFileForm ](ctx )
383
+ parsed := prepareEditorCommitSubmittedForm [* forms.DeleteRepoFileForm ](ctx )
331
384
if ctx .Written () {
332
385
return
333
386
}
334
387
335
388
treePath := ctx .Repo .TreePath
336
389
_ , err := files_service .ChangeRepoFiles (ctx , ctx .Repo .Repository , ctx .Doer , & files_service.ChangeRepoFilesOptions {
337
390
LastCommitID : parsed .form .LastCommit ,
338
- OldBranch : ctx . Repo . BranchName ,
339
- NewBranch : parsed .TargetBranchName ,
391
+ OldBranch : parsed . OldBranchName ,
392
+ NewBranch : parsed .NewBranchName ,
340
393
Files : []* files_service.ChangeRepoFile {
341
394
{
342
395
Operation : "delete" ,
@@ -349,38 +402,38 @@ func DeleteFilePost(ctx *context.Context) {
349
402
Committer : parsed .GitCommitter ,
350
403
})
351
404
if err != nil {
352
- editorHandleFileOperationError (ctx , parsed .TargetBranchName , err )
405
+ editorHandleFileOperationError (ctx , parsed .NewBranchName , err )
353
406
return
354
407
}
355
408
356
409
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 )
358
411
redirectForCommitChoice (ctx , parsed , redirectTreePath )
359
412
}
360
413
361
414
func UploadFile (ctx * context.Context ) {
362
415
ctx .Data ["PageIsUpload" ] = true
363
- upload .AddUploadContext (ctx , "repo" )
364
416
prepareTreePathFieldsAndPaths (ctx , ctx .Repo .TreePath )
365
-
366
- prepareEditorCommitFormOptions (ctx , "_upload" )
417
+ opts := prepareEditorCommitFormOptions (ctx , "_upload" )
367
418
if ctx .Written () {
368
419
return
369
420
}
421
+ upload .AddUploadContextForRepo (ctx , opts .TargetRepo )
422
+
370
423
ctx .HTML (http .StatusOK , tplUploadFile )
371
424
}
372
425
373
426
func UploadFilePost (ctx * context.Context ) {
374
- parsed := parseEditorCommitSubmittedForm [* forms.UploadRepoFileForm ](ctx )
427
+ parsed := prepareEditorCommitSubmittedForm [* forms.UploadRepoFileForm ](ctx )
375
428
if ctx .Written () {
376
429
return
377
430
}
378
431
379
432
defaultCommitMessage := ctx .Locale .TrString ("repo.editor.upload_files_to_dir" , util .IfZero (parsed .form .TreePath , "/" ))
380
433
err := files_service .UploadRepoFiles (ctx , ctx .Repo .Repository , ctx .Doer , & files_service.UploadRepoFileOptions {
381
434
LastCommitID : parsed .form .LastCommit ,
382
- OldBranch : ctx . Repo . BranchName ,
383
- NewBranch : parsed .TargetBranchName ,
435
+ OldBranch : parsed . OldBranchName ,
436
+ NewBranch : parsed .NewBranchName ,
384
437
TreePath : parsed .form .TreePath ,
385
438
Message : parsed .GetCommitMessage (defaultCommitMessage ),
386
439
Files : parsed .form .Files ,
@@ -389,7 +442,7 @@ func UploadFilePost(ctx *context.Context) {
389
442
Committer : parsed .GitCommitter ,
390
443
})
391
444
if err != nil {
392
- editorHandleFileOperationError (ctx , parsed .TargetBranchName , err )
445
+ editorHandleFileOperationError (ctx , parsed .NewBranchName , err )
393
446
return
394
447
}
395
448
redirectForCommitChoice (ctx , parsed , parsed .form .TreePath )
0 commit comments