Skip to content

Commit be4a263

Browse files
committed
Redirect to fork or propose to create one when editing files
When viewing a file that the user can't edit because they can't write to the branch, the edit and delete buttons are no longer disabled. Instead they redirect to a the corresponding page in the user's fork, or to the form to create a fork. The form to create a fork will have an info message at the top explaining why the fork is needed. After creating the fork, it redirects to the relevant edit or delete page. The redirect happens when accessing /_edit/, so that for example online documentation can have an "edit this page" link to the base repository that does the right thing.
1 parent 2683adf commit be4a263

File tree

7 files changed

+93
-9
lines changed

7 files changed

+93
-9
lines changed

models/repo/repo.go

+5
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,11 @@ func (repo *Repository) Link() string {
605605
return setting.AppSubURL + "/" + url.PathEscape(repo.OwnerName) + "/" + url.PathEscape(repo.Name)
606606
}
607607

608+
// Change the repository in a link from the base to a fork repository
609+
func (repo *Repository) AdaptLinkToFork(forkRepo *Repository, link string) string {
610+
return forkRepo.Link() + strings.TrimPrefix(link, repo.Link())
611+
}
612+
608613
// ComposeCompareURL returns the repository comparison URL
609614
func (repo *Repository) ComposeCompareURL(oldCommitID, newCommitID string) string {
610615
return fmt.Sprintf("%s/%s/compare/%s...%s", url.PathEscape(repo.OwnerName), url.PathEscape(repo.Name), util.PathEscapeSegments(oldCommitID), util.PathEscapeSegments(newCommitID))

options/locale/locale_en-US.ini

+3-2
Original file line numberDiff line numberDiff line change
@@ -1051,6 +1051,7 @@ fork_from = Fork From
10511051
already_forked = You've already forked %s
10521052
fork_to_different_account = Fork to a different account
10531053
fork_visibility_helper = The visibility of a forked repository cannot be changed.
1054+
fork_to_edit_description = The %[1]s repository can not be edited directly.<br>To propose changes, you can fork the repository and create a pull request.
10541055
fork_branch = Branch to be cloned to the fork
10551056
all_branches = All branches
10561057
view_all_branches = View all branches
@@ -1334,9 +1335,9 @@ editor.cannot_edit_non_text_files = Binary files cannot be edited in the web int
13341335
editor.edit_this_file = Edit File
13351336
editor.this_file_locked = File is locked
13361337
editor.must_be_on_a_branch = You must be on a branch to make or propose changes to this file.
1337-
editor.fork_before_edit = You must fork this repository to make or propose changes to this file.
1338+
editor.fork_before_edit = Edit this file in a fork to propose changes.
13381339
editor.delete_this_file = Delete File
1339-
editor.must_have_write_access = You must have write access to make or propose changes to this file.
1340+
editor.fork_before_delete = Delete this file in a fork to propose changes.
13401341
editor.file_delete_success = File "%s" has been deleted.
13411342
editor.name_your_file = Name your file…
13421343
editor.filename_help = Add a directory by typing its name followed by a slash ('/'). Remove a directory by typing backspace at the beginning of the input field.

routers/web/repo/editor.go

+52
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"io"
99
"net/http"
10+
"net/url"
1011
"path"
1112
"strings"
1213

@@ -45,6 +46,41 @@ func canCreateBasePullRequest(ctx *context.Context) bool {
4546
return baseRepo != nil && baseRepo.UnitEnabled(ctx, unit.TypePullRequests)
4647
}
4748

49+
// redirectToFork redirects to the user's fork when editing is not possible
50+
func redirectToFork(ctx *context.Context) bool {
51+
if ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, ctx.Repo.BranchName) {
52+
return false
53+
}
54+
55+
repo := ctx.Repo.Repository
56+
userAndOrgForks, err := repo_model.GetForksByUserAndOrgs(ctx, ctx.Doer, repo)
57+
if err != nil {
58+
ctx.ServerError("GetForksByUserAndOrgs", err)
59+
return true
60+
}
61+
62+
// When already on a repo the user owns, don't redirect further even if
63+
// writing to the branch is not possible.
64+
for _, forkRepo := range userAndOrgForks {
65+
if forkRepo.ID == repo.ID {
66+
ctx.NotFound(nil)
67+
return true
68+
}
69+
}
70+
71+
link := setting.AppSubURL + ctx.Req.URL.RequestURI()
72+
if len(userAndOrgForks) > 0 {
73+
// Redirect to user repository
74+
forkRepo := userAndOrgForks[0]
75+
ctx.Redirect(repo.AdaptLinkToFork(forkRepo, link))
76+
} else {
77+
// Redirect to create new user repository
78+
ctx.Redirect(repo.Link() + "/fork/?redirect_to_edit=" + url.QueryEscape(link))
79+
}
80+
81+
return true
82+
}
83+
4884
func renderCommitRights(ctx *context.Context) bool {
4985
canCommitToBranch, err := ctx.Repo.CanCommitToBranch(ctx, ctx.Doer)
5086
if err != nil {
@@ -213,11 +249,19 @@ func GetEditorConfig(ctx *context.Context, treePath string) string {
213249

214250
// EditFile render edit file page
215251
func EditFile(ctx *context.Context) {
252+
if redirectToFork(ctx) {
253+
return
254+
}
255+
216256
editFile(ctx, false)
217257
}
218258

219259
// NewFile render create file page
220260
func NewFile(ctx *context.Context) {
261+
if redirectToFork(ctx) {
262+
return
263+
}
264+
221265
editFile(ctx, true)
222266
}
223267

@@ -431,6 +475,10 @@ func DiffPreviewPost(ctx *context.Context) {
431475

432476
// DeleteFile render delete file page
433477
func DeleteFile(ctx *context.Context) {
478+
if redirectToFork(ctx) {
479+
return
480+
}
481+
434482
ctx.Data["PageIsDelete"] = true
435483
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL()
436484
treePath := cleanUploadFileName(ctx.Repo.TreePath)
@@ -599,6 +647,10 @@ func DeleteFilePost(ctx *context.Context) {
599647

600648
// UploadFile render upload file page
601649
func UploadFile(ctx *context.Context) {
650+
if redirectToFork(ctx) {
651+
return
652+
}
653+
602654
ctx.Data["PageIsUpload"] = true
603655
upload.AddUploadContext(ctx, "repo")
604656
canCommit := renderCommitRights(ctx)

routers/web/repo/fork.go

+12-1
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,11 @@ func getForkRepository(ctx *context.Context) *repo_model.Repository {
125125
// Fork render repository fork page
126126
func Fork(ctx *context.Context) {
127127
ctx.Data["Title"] = ctx.Tr("new_fork")
128+
129+
redirectTo := ctx.FormString("redirect_to_edit")
130+
ctx.Data["ForkForEdit"] = len(redirectTo) > 0
131+
ctx.Data["ForkRedirectTo"] = redirectTo
132+
128133
getForkRepository(ctx)
129134
if ctx.Written() {
130135
return
@@ -228,5 +233,11 @@ func ForkPost(ctx *context.Context) {
228233
}
229234

230235
log.Trace("Repository forked[%d]: %s/%s", forkRepo.ID, ctxUser.Name, repo.Name)
231-
ctx.Redirect(ctxUser.HomeLink() + "/" + url.PathEscape(repo.Name))
236+
237+
redirectTo := ctx.FormString("redirect_to_edit")
238+
if len(redirectTo) > 0 {
239+
ctx.Redirect(forkRepo.AdaptLinkToFork(repo, redirectTo))
240+
} else {
241+
ctx.Redirect(ctxUser.HomeLink() + "/" + url.PathEscape(repo.Name))
242+
}
232243
}

routers/web/repo/view_file.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ func prepareToRenderFile(ctx *context.Context, entry *git.TreeEntry) {
255255
} else if !ctx.Repo.RefFullName.IsBranch() {
256256
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch")
257257
} else if !ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, ctx.Repo.BranchName) {
258+
ctx.Data["CanEditFile"] = true
258259
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.fork_before_edit")
259260
}
260261
}
@@ -318,6 +319,7 @@ func prepareToRenderFile(ctx *context.Context, entry *git.TreeEntry) {
318319
} else if !ctx.Repo.RefFullName.IsBranch() {
319320
ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch")
320321
} else if !ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, ctx.Repo.BranchName) {
321-
ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_have_write_access")
322+
ctx.Data["CanDeleteFile"] = true
323+
ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.fork_before_delete")
322324
}
323325
}

routers/web/web.go

+12-5
Original file line numberDiff line numberDiff line change
@@ -1314,15 +1314,22 @@ func registerWebRoutes(m *web.Router) {
13141314
m.Group("/{username}/{reponame}", func() { // repo code
13151315
m.Group("", func() {
13161316
m.Group("", func() {
1317-
m.Post("/_preview/*", web.Bind(forms.EditPreviewDiffForm{}), repo.DiffPreviewPost)
1318-
m.Combo("/_edit/*").Get(repo.EditFile).
1317+
// These will redirect to a fork if writing to the branch is not possible
1318+
m.Combo("/_edit/*").Get(repo.EditFile)
1319+
m.Combo("/_new/*").Get(repo.NewFile)
1320+
m.Combo("/_delete/*").Get(repo.DeleteFile)
1321+
m.Combo("/_upload/*", repo.MustBeAbleToUpload).Get(repo.UploadFile)
1322+
}, context.RepoRefByType(git.RefTypeBranch), repo.WebGitOperationCommonData)
1323+
m.Group("", func() {
1324+
m.Combo("/_edit/*").
13191325
Post(web.Bind(forms.EditRepoFileForm{}), repo.EditFilePost)
1320-
m.Combo("/_new/*").Get(repo.NewFile).
1326+
m.Combo("/_new/*").
13211327
Post(web.Bind(forms.EditRepoFileForm{}), repo.NewFilePost)
1322-
m.Combo("/_delete/*").Get(repo.DeleteFile).
1328+
m.Combo("/_delete/*").
13231329
Post(web.Bind(forms.DeleteRepoFileForm{}), repo.DeleteFilePost)
1324-
m.Combo("/_upload/*", repo.MustBeAbleToUpload).Get(repo.UploadFile).
1330+
m.Combo("/_upload/*", repo.MustBeAbleToUpload).
13251331
Post(web.Bind(forms.UploadRepoFileForm{}), repo.UploadFilePost)
1332+
m.Post("/_preview/*", web.Bind(forms.EditPreviewDiffForm{}), repo.DiffPreviewPost)
13261333
m.Combo("/_diffpatch/*").Get(repo.NewDiffPatch).
13271334
Post(web.Bind(forms.EditRepoFileForm{}), repo.NewDiffPatchPost)
13281335
m.Combo("/_cherrypick/{sha:([a-f0-9]{7,64})}/*").Get(repo.CherryPick).

templates/repo/pulls/fork.tmpl

+6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
{{template "base/head" .}}
22
<div role="main" aria-label="{{.Title}}" class="page-content repository new fork">
33
<div class="ui container medium-width">
4+
{{if .ForkForEdit}}
5+
<div class="ui blue message">
6+
<p>{{ctx.Locale.Tr "repo.fork_to_edit_description" .ForkRepo.FullName}}</p>
7+
</div>
8+
{{end}}
49
<h3 class="ui top attached header">
510
{{ctx.Locale.Tr "new_fork"}}
611
</h3>
@@ -72,6 +77,7 @@
7277
<label for="description">{{ctx.Locale.Tr "repo.repo_desc"}}</label>
7378
<textarea id="description" name="description">{{.description}}</textarea>
7479
</div>
80+
<input type="hidden" id="redirect_to_edit" name="redirect_to_edit" value="{{.ForkRedirectTo}}">
7581

7682
<div class="inline field">
7783
<label></label>

0 commit comments

Comments
 (0)