@@ -5,17 +5,21 @@ package repo
55
66import  (
77	"bytes" 
8+ 	gocontext "context" 
9+ 	"errors" 
810	"fmt" 
911	"io" 
1012	"net/http" 
1113	"path" 
1214	"strings" 
1315
1416	git_model "code.gitea.io/gitea/models/git" 
17+ 	repo_model "code.gitea.io/gitea/models/repo" 
1518	"code.gitea.io/gitea/models/unit" 
1619	"code.gitea.io/gitea/modules/charset" 
1720	"code.gitea.io/gitea/modules/git" 
1821	"code.gitea.io/gitea/modules/httplib" 
22+ 	"code.gitea.io/gitea/modules/log" 
1923	"code.gitea.io/gitea/modules/markup" 
2024	"code.gitea.io/gitea/modules/setting" 
2125	"code.gitea.io/gitea/modules/templates" 
@@ -39,26 +43,36 @@ const (
3943	editorCommitChoiceNewBranch  string  =  "commit-to-new-branch" 
4044)
4145
42- func  prepareEditorCommitFormOptions (ctx  * context.Context , editorAction  string ) {
46+ func  prepareEditorCommitFormOptions (ctx  * context.Context , editorAction  string ) * context. CommitFormOptions   {
4347	cleanedTreePath  :=  files_service .CleanGitTreePath (ctx .Repo .TreePath )
4448	if  cleanedTreePath  !=  ctx .Repo .TreePath  {
4549		redirectTo  :=  fmt .Sprintf ("%s/%s/%s/%s" , ctx .Repo .RepoLink , editorAction , util .PathEscapeSegments (ctx .Repo .BranchName ), util .PathEscapeSegments (cleanedTreePath ))
4650		if  ctx .Req .URL .RawQuery  !=  ""  {
4751			redirectTo  +=  "?"  +  ctx .Req .URL .RawQuery 
4852		}
4953		ctx .Redirect (redirectTo )
50- 		return 
54+ 		return   nil 
5155	}
5256
53- 	commitFormBehaviors , err  :=  ctx . Repo .PrepareCommitFormBehaviors ( ctx , ctx .Doer )
57+ 	commitFormOptions , err  :=  context . PrepareCommitFormOptions ( ctx ,  ctx . Doer ,  ctx . Repo .Repository ,  ctx . Repo . Permission , ctx .Repo . RefFullName )
5458	if  err  !=  nil  {
55- 		ctx .ServerError ("PrepareCommitFormBehaviors" , err )
56- 		return 
59+ 		ctx .ServerError ("PrepareCommitFormOptions" , err )
60+ 		return  nil 
61+ 	}
62+ 
63+ 	if  commitFormOptions .NeedFork  {
64+ 		ForkToEdit (ctx )
65+ 		return  nil 
66+ 	}
67+ 
68+ 	if  commitFormOptions .WillSubmitToFork  &&  ! commitFormOptions .TargetRepo .CanEnableEditor () {
69+ 		ctx .Data ["NotFoundPrompt" ] =  ctx .Locale .Tr ("repo.editor.fork_not_editable" )
70+ 		ctx .NotFound (nil )
5771	}
5872
5973	ctx .Data ["BranchLink" ] =  ctx .Repo .RepoLink  +  "/src/"  +  ctx .Repo .RefTypeNameSubURL ()
6074	ctx .Data ["TreePath" ] =  ctx .Repo .TreePath 
61- 	ctx .Data ["CommitFormBehaviors " ] =  commitFormBehaviors 
75+ 	ctx .Data ["CommitFormOptions " ] =  commitFormOptions 
6276
6377	// for online editor 
6478	ctx .Data ["PreviewableExtensions" ] =  strings .Join (markup .PreviewableExtensions (), "," )
@@ -69,33 +83,47 @@ func prepareEditorCommitFormOptions(ctx *context.Context, editorAction string) {
6983	// form fields 
7084	ctx .Data ["commit_summary" ] =  "" 
7185	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 )
86+ 	ctx .Data ["commit_choice" ] =  util .Iif (commitFormOptions .CanCommitToBranch , editorCommitChoiceDirect , editorCommitChoiceNewBranch )
87+ 	ctx .Data ["new_branch_name" ] =  getUniquePatchBranchName (ctx , ctx .Doer .LowerName , commitFormOptions . TargetRepo )
7488	ctx .Data ["last_commit" ] =  ctx .Repo .CommitID 
89+ 	return  commitFormOptions 
7590}
7691
7792func  prepareTreePathFieldsAndPaths (ctx  * context.Context , treePath  string ) {
7893	// show the tree path fields in the "breadcrumb" and help users to edit the target tree path 
7994	ctx .Data ["TreeNames" ], ctx .Data ["TreePaths" ] =  getParentTreeFields (treePath )
8095}
8196
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 
97+ type  preparedEditorCommitForm [T  any ] struct  {
98+ 	form               T 
99+ 	commonForm         * forms.CommitCommonForm 
100+ 	CommitFormOptions  * context.CommitFormOptions 
101+ 	TargetBranchName   string 
102+ 	GitCommitter       * files_service.IdentityOptions 
88103}
89104
90- func  (f  * parsedEditorCommitForm [T ]) GetCommitMessage (defaultCommitMessage  string ) string  {
105+ func  (f  * preparedEditorCommitForm [T ]) GetCommitMessage (defaultCommitMessage  string ) string  {
91106	commitMessage  :=  util .IfZero (strings .TrimSpace (f .commonForm .CommitSummary ), defaultCommitMessage )
92107	if  body  :=  strings .TrimSpace (f .commonForm .CommitMessage ); body  !=  ""  {
93108		commitMessage  +=  "\n \n "  +  body 
94109	}
95110	return  commitMessage 
96111}
97112
98- func  parseEditorCommitSubmittedForm [T  forms.CommitCommonFormInterface ](ctx  * context.Context ) * parsedEditorCommitForm [T ] {
113+ func  parseBaseRepoBranch (ctx  gocontext.Context , input  string ) (* repo_model.Repository , string , error ) {
114+ 	baseRepoFullName , baseBranchName , ok  :=  strings .Cut (input , ":" )
115+ 	if  ! ok  {
116+ 		return  nil , "" , util .NewInvalidArgumentErrorf ("invalid base repo name: %s" , input )
117+ 	}
118+ 	baseOwnerName , baseRepoName , ok  :=  strings .Cut (baseRepoFullName , "/" )
119+ 	if  ! ok  {
120+ 		return  nil , "" , util .NewInvalidArgumentErrorf ("invalid base repo name: %s" , input )
121+ 	}
122+ 	baseRepo , err  :=  repo_model .GetRepositoryByOwnerAndName (ctx , baseOwnerName , baseRepoName )
123+ 	return  baseRepo , baseBranchName , err 
124+ }
125+ 
126+ func  prepareEditorCommitSubmittedForm [T  forms.CommitCommonFormInterface ](ctx  * context.Context ) * preparedEditorCommitForm [T ] {
99127	form  :=  web .GetForm (ctx ).(T )
100128	if  ctx .HasError () {
101129		ctx .JSONError (ctx .GetErrMsg ())
@@ -105,15 +133,20 @@ func parseEditorCommitSubmittedForm[T forms.CommitCommonFormInterface](ctx *cont
105133	commonForm  :=  form .GetCommitCommonForm ()
106134	commonForm .TreePath  =  files_service .CleanGitTreePath (commonForm .TreePath )
107135
108- 	commitFormBehaviors , err  :=  ctx . Repo .PrepareCommitFormBehaviors ( ctx , ctx .Doer )
136+ 	commitFormOptions , err  :=  context . PrepareCommitFormOptions ( ctx ,  ctx . Doer ,  ctx . Repo .Repository ,  ctx . Repo . Permission , ctx .Repo . RefFullName )
109137	if  err  !=  nil  {
110- 		ctx .ServerError ("PrepareCommitFormBehaviors" , err )
138+ 		ctx .ServerError ("PrepareCommitFormOptions" , err )
139+ 		return  nil 
140+ 	}
141+ 	if  commitFormOptions .NeedFork  {
142+ 		// It shouldn't happen, because we should have done the checks in the "GET" request. But just in case. 
143+ 		ctx .JSONError (ctx .Locale .TrString ("error.not_found" ))
111144		return  nil 
112145	}
113146
114147	// check commit behavior 
115148	targetBranchName  :=  util .Iif (commonForm .CommitChoice  ==  editorCommitChoiceNewBranch , commonForm .NewBranchName , ctx .Repo .BranchName )
116- 	if  targetBranchName  ==  ctx .Repo .BranchName  &&  ! commitFormBehaviors .CanCommitToBranch  {
149+ 	if  targetBranchName  ==  ctx .Repo .BranchName  &&  ! commitFormOptions .CanCommitToBranch  {
117150		ctx .JSONError (ctx .Tr ("repo.editor.cannot_commit_to_protected_branch" , targetBranchName ))
118151		return  nil 
119152	}
@@ -125,28 +158,46 @@ func parseEditorCommitSubmittedForm[T forms.CommitCommonFormInterface](ctx *cont
125158		return  nil 
126159	}
127160
128- 	return  & parsedEditorCommitForm [T ]{
129- 		form :                form ,
130- 		commonForm :          commonForm ,
131- 		CommitFormBehaviors : commitFormBehaviors ,
132- 		TargetBranchName :    targetBranchName ,
133- 		GitCommitter :        gitCommitter ,
161+ 	fromBase  :=  ctx .FormString ("from_base" )
162+ 	if  fromBase  !=  ""  {
163+ 		baseRepo , baseBranchName , err  :=  parseBaseRepoBranch (ctx , fromBase )
164+ 		if  errors .Is (err , util .ErrNotExist ) ||  errors .Is (err , util .ErrInvalidArgument ) {
165+ 			ctx .JSONError (ctx .Tr ("error.not_found" ))
166+ 			return  nil 
167+ 		} else  if  err  !=  nil  {
168+ 			ctx .ServerError ("parseBaseRepoBranch" , err )
169+ 			return  nil 
170+ 		}
171+ 		err  =  editorPushBranchToForkedRepository (ctx , ctx .Doer , baseRepo , baseBranchName , ctx .Repo .Repository , ctx .Repo .RefFullName .BranchName ())
172+ 		if  err  !=  nil  {
173+ 			log .Error ("Unable to editorPushBranchToForkedRepository: %v" , err )
174+ 			ctx .JSONError (ctx .Tr ("repo.editor.fork_failed_to_push_branch" , targetBranchName ))
175+ 			return  nil 
176+ 		}
177+ 	}
178+ 
179+ 	return  & preparedEditorCommitForm [T ]{
180+ 		form :              form ,
181+ 		commonForm :        commonForm ,
182+ 		CommitFormOptions : commitFormOptions ,
183+ 		TargetBranchName :  targetBranchName ,
184+ 		GitCommitter :      gitCommitter ,
134185	}
135186}
136187
137188// redirectForCommitChoice redirects after committing the edit to a branch 
138- func  redirectForCommitChoice [T  any ](ctx  * context.Context , parsed  * parsedEditorCommitForm [T ], treePath  string ) {
189+ func  redirectForCommitChoice [T  any ](ctx  * context.Context , parsed  * preparedEditorCommitForm [T ], treePath  string ) {
139190	if  parsed .commonForm .CommitChoice  ==  editorCommitChoiceNewBranch  {
140191		// Redirect to a pull request when possible 
141192		redirectToPullRequest  :=  false 
142193		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  {
194+ 		if  ctx .Repo .Repository .IsFork  &&  parsed .CommitFormOptions .CanCreateBasePullRequest  {
146195			redirectToPullRequest  =  true 
147196			baseBranch  =  repo .BaseRepo .DefaultBranch 
148197			headBranch  =  repo .Owner .Name  +  "/"  +  repo .Name  +  ":"  +  headBranch 
149198			repo  =  repo .BaseRepo 
199+ 		} else  if  repo .UnitEnabled (ctx , unit .TypePullRequests ) {
200+ 			redirectToPullRequest  =  true 
150201		}
151202		if  redirectToPullRequest  {
152203			ctx .JSONRedirect (repo .Link () +  "/compare/"  +  util .PathEscapeSegments (baseBranch ) +  "..."  +  util .PathEscapeSegments (headBranch ))
@@ -268,7 +319,7 @@ func EditFile(ctx *context.Context) {
268319func  EditFilePost (ctx  * context.Context ) {
269320	editorAction  :=  ctx .PathParam ("editor_action" )
270321	isNewFile  :=  editorAction  ==  "_new" 
271- 	parsed  :=  parseEditorCommitSubmittedForm [* forms.EditRepoFileForm ](ctx )
322+ 	parsed  :=  prepareEditorCommitSubmittedForm [* forms.EditRepoFileForm ](ctx )
272323	if  ctx .Written () {
273324		return 
274325	}
@@ -327,7 +378,7 @@ func DeleteFile(ctx *context.Context) {
327378
328379// DeleteFilePost response for deleting file 
329380func  DeleteFilePost (ctx  * context.Context ) {
330- 	parsed  :=  parseEditorCommitSubmittedForm [* forms.DeleteRepoFileForm ](ctx )
381+ 	parsed  :=  prepareEditorCommitSubmittedForm [* forms.DeleteRepoFileForm ](ctx )
331382	if  ctx .Written () {
332383		return 
333384	}
@@ -360,18 +411,18 @@ func DeleteFilePost(ctx *context.Context) {
360411
361412func  UploadFile (ctx  * context.Context ) {
362413	ctx .Data ["PageIsUpload" ] =  true 
363- 	upload .AddUploadContext (ctx , "repo" )
364414	prepareTreePathFieldsAndPaths (ctx , ctx .Repo .TreePath )
365- 
366- 	prepareEditorCommitFormOptions (ctx , "_upload" )
415+ 	opts  :=  prepareEditorCommitFormOptions (ctx , "_upload" )
367416	if  ctx .Written () {
368417		return 
369418	}
419+ 	upload .AddUploadContextForRepo (ctx , opts .TargetRepo )
420+ 
370421	ctx .HTML (http .StatusOK , tplUploadFile )
371422}
372423
373424func  UploadFilePost (ctx  * context.Context ) {
374- 	parsed  :=  parseEditorCommitSubmittedForm [* forms.UploadRepoFileForm ](ctx )
425+ 	parsed  :=  prepareEditorCommitSubmittedForm [* forms.UploadRepoFileForm ](ctx )
375426	if  ctx .Written () {
376427		return 
377428	}
0 commit comments