Skip to content

Commit 13ba415

Browse files
committed
Add apply-patch button
This code adds a simple endpoint to apply patches to repositories and branches on gitea. Signed-off-by: Andrew Thornton <[email protected]>
1 parent aa79738 commit 13ba415

File tree

13 files changed

+510
-6
lines changed

13 files changed

+510
-6
lines changed

modules/git/diff.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,13 @@ const (
3030
)
3131

3232
// GetRawDiff dumps diff results of repository in given commit ID to io.Writer.
33-
func GetRawDiff(repoPath, commitID string, diffType RawDiffType, writer io.Writer) error {
34-
return GetRawDiffForFile(repoPath, "", commitID, diffType, "", writer)
33+
func GetRawDiff(ctx context.Context, repoPath, commitID string, diffType RawDiffType, writer io.Writer) error {
34+
return GetRawDiffForFile(ctx, repoPath, "", commitID, diffType, "", writer)
3535
}
3636

3737
// GetRawDiffForFile dumps diff results of file in given commit ID to io.Writer.
38-
func GetRawDiffForFile(repoPath, startCommit, endCommit string, diffType RawDiffType, file string, writer io.Writer) error {
39-
repo, err := OpenRepository(repoPath)
38+
func GetRawDiffForFile(ctx context.Context, repoPath, startCommit, endCommit string, diffType RawDiffType, file string, writer io.Writer) error {
39+
repo, err := OpenRepositoryCtx(ctx, repoPath)
4040
if err != nil {
4141
return fmt.Errorf("OpenRepository: %v", err)
4242
}
@@ -223,8 +223,7 @@ func CutDiffAroundLine(originalDiff io.Reader, line int64, old bool, numbersOfLi
223223
}
224224
}
225225
}
226-
err := scanner.Err()
227-
if err != nil {
226+
if err := scanner.Err(); err != nil {
228227
return "", err
229228
}
230229

modules/structs/repo_file.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ type UpdateFileOptions struct {
5050
FromPath string `json:"from_path" binding:"MaxSize(500)"`
5151
}
5252

53+
// ApplyDiffPatchFileOptions options for applying a diff patch
54+
// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
55+
type ApplyDiffPatchFileOptions struct {
56+
DeleteFileOptions
57+
// required: true
58+
Content string `json:"content"`
59+
}
60+
5361
// FileLinksResponse contains the links for a repo's file
5462
type FileLinksResponse struct {
5563
Self *string `json:"self"`

options/locale/locale_en-US.ini

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,6 +1028,10 @@ editor.add_tmpl = Add '<filename>'
10281028
editor.add = Add '%s'
10291029
editor.update = Update '%s'
10301030
editor.delete = Delete '%s'
1031+
editor.patch = Apply Patch
1032+
editor.patching = Patching:
1033+
editor.fail_to_apply_patch = Unable to apply patch '%s'
1034+
editor.new_patch = New Patch
10311035
editor.commit_message_desc = Add an optional extended description…
10321036
editor.signoff_desc = Add a Signed-off-by trailer by the committer at the end of the commit log message.
10331037
editor.commit_directly_to_this_branch = Commit directly to the <strong class="branch-name">%s</strong> branch.

routers/api/v1/api.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -971,6 +971,7 @@ func Routes(sessioner func(http.Handler) http.Handler) *web.Route {
971971
m.Get("/tags/{sha}", context.RepoRefForAPI, repo.GetAnnotatedTag)
972972
m.Get("/notes/{sha}", repo.GetNote)
973973
}, reqRepoReader(unit.TypeCode))
974+
m.Post("/diffpatch", reqRepoWriter(unit.TypeCode), reqToken(), bind(api.ApplyDiffPatchFileOptions{}), repo.ApplyDiffPatch)
974975
m.Group("/contents", func() {
975976
m.Get("", repo.GetContentsList)
976977
m.Get("/*", repo.GetContents)

routers/api/v1/repo/commits.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,7 @@ func DownloadCommitDiffOrPatch(ctx *context.APIContext) {
251251
// "$ref": "#/responses/notFound"
252252
repoPath := models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
253253
if err := git.GetRawDiff(
254+
ctx,
254255
repoPath,
255256
ctx.Params(":sha"),
256257
git.RawDiffType(ctx.Params(":diffType")),

routers/api/v1/repo/patch.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Copyright 2020 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package repo
6+
7+
import (
8+
"net/http"
9+
"time"
10+
11+
"code.gitea.io/gitea/models"
12+
"code.gitea.io/gitea/modules/context"
13+
"code.gitea.io/gitea/modules/git"
14+
api "code.gitea.io/gitea/modules/structs"
15+
"code.gitea.io/gitea/modules/web"
16+
"code.gitea.io/gitea/services/repository/files"
17+
)
18+
19+
// ApplyDiffPatch handles API call for applying a patch
20+
func ApplyDiffPatch(ctx *context.APIContext) {
21+
// swagger:operation POST /repos/{owner}/{repo}/diffpatch repository repoApplyDiffPatch
22+
// ---
23+
// summary: Apply diff patch to repository
24+
// consumes:
25+
// - application/json
26+
// produces:
27+
// - application/json
28+
// parameters:
29+
// - name: owner
30+
// in: path
31+
// description: owner of the repo
32+
// type: string
33+
// required: true
34+
// - name: repo
35+
// in: path
36+
// description: name of the repo
37+
// type: string
38+
// required: true
39+
// - name: body
40+
// in: body
41+
// required: true
42+
// schema:
43+
// "$ref": "#/definitions/UpdateFileOptions"
44+
// responses:
45+
// "200":
46+
// "$ref": "#/responses/FileResponse"
47+
apiOpts := web.GetForm(ctx).(*api.ApplyDiffPatchFileOptions)
48+
49+
opts := &files.ApplyDiffPatchOptions{
50+
Content: apiOpts.Content,
51+
SHA: apiOpts.SHA,
52+
Message: apiOpts.Message,
53+
OldBranch: apiOpts.BranchName,
54+
NewBranch: apiOpts.NewBranchName,
55+
Committer: &files.IdentityOptions{
56+
Name: apiOpts.Committer.Name,
57+
Email: apiOpts.Committer.Email,
58+
},
59+
Author: &files.IdentityOptions{
60+
Name: apiOpts.Author.Name,
61+
Email: apiOpts.Author.Email,
62+
},
63+
Dates: &files.CommitDateOptions{
64+
Author: apiOpts.Dates.Author,
65+
Committer: apiOpts.Dates.Committer,
66+
},
67+
Signoff: apiOpts.Signoff,
68+
}
69+
if opts.Dates.Author.IsZero() {
70+
opts.Dates.Author = time.Now()
71+
}
72+
if opts.Dates.Committer.IsZero() {
73+
opts.Dates.Committer = time.Now()
74+
}
75+
76+
if opts.Message == "" {
77+
opts.Message = "apply-patch"
78+
}
79+
80+
if !canWriteFiles(ctx.Repo) {
81+
ctx.Error(http.StatusInternalServerError, "ApplyPatch", models.ErrUserDoesNotHaveAccessToRepo{
82+
UserID: ctx.User.ID,
83+
RepoName: ctx.Repo.Repository.LowerName,
84+
})
85+
}
86+
87+
fileResponse, err := files.ApplyDiffPatch(ctx.Repo.Repository, ctx.User, opts)
88+
if err != nil {
89+
if models.IsErrUserCannotCommit(err) || models.IsErrFilePathProtected(err) {
90+
ctx.Error(http.StatusForbidden, "Access", err)
91+
return
92+
}
93+
if models.IsErrBranchAlreadyExists(err) || models.IsErrFilenameInvalid(err) || models.IsErrSHADoesNotMatch(err) ||
94+
models.IsErrFilePathInvalid(err) || models.IsErrRepoFileAlreadyExists(err) {
95+
ctx.Error(http.StatusUnprocessableEntity, "Invalid", err)
96+
return
97+
}
98+
if models.IsErrBranchDoesNotExist(err) || git.IsErrBranchNotExist(err) {
99+
ctx.Error(http.StatusNotFound, "BranchDoesNotExist", err)
100+
return
101+
}
102+
ctx.Error(http.StatusInternalServerError, "ApplyPatch", err)
103+
} else {
104+
ctx.JSON(http.StatusCreated, fileResponse)
105+
}
106+
}

routers/web/repo/commit.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,7 @@ func RawDiff(ctx *context.Context) {
385385
repoPath = models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
386386
}
387387
if err := git.GetRawDiff(
388+
ctx,
388389
repoPath,
389390
ctx.Params(":sha"),
390391
git.RawDiffType(ctx.Params(":ext")),

routers/web/repo/patch.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Copyright 2020 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package repo
6+
7+
import (
8+
"strings"
9+
10+
"code.gitea.io/gitea/models"
11+
"code.gitea.io/gitea/modules/base"
12+
"code.gitea.io/gitea/modules/context"
13+
"code.gitea.io/gitea/modules/setting"
14+
"code.gitea.io/gitea/modules/web"
15+
"code.gitea.io/gitea/services/forms"
16+
"code.gitea.io/gitea/services/repository/files"
17+
)
18+
19+
const (
20+
tplPatchFile base.TplName = "repo/editor/patch"
21+
)
22+
23+
// NewDiffPatch render create patch page
24+
func NewDiffPatch(ctx *context.Context) {
25+
ctx.Data["RequireHighlightJS"] = true
26+
27+
canCommit := renderCommitRights(ctx)
28+
29+
ctx.Data["TreePath"] = "patch"
30+
31+
ctx.Data["commit_summary"] = ""
32+
ctx.Data["commit_message"] = ""
33+
if canCommit {
34+
ctx.Data["commit_choice"] = frmCommitChoiceDirect
35+
} else {
36+
ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
37+
}
38+
ctx.Data["new_branch_name"] = GetUniquePatchBranchName(ctx)
39+
ctx.Data["last_commit"] = ctx.Repo.CommitID
40+
ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
41+
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
42+
43+
ctx.HTML(200, tplPatchFile)
44+
}
45+
46+
// NewDiffPatchPost response for sending patch page
47+
func NewDiffPatchPost(ctx *context.Context) {
48+
form := web.GetForm(ctx).(*forms.EditRepoFileForm)
49+
50+
canCommit := renderCommitRights(ctx)
51+
branchName := ctx.Repo.BranchName
52+
if form.CommitChoice == frmCommitChoiceNewBranch {
53+
branchName = form.NewBranchName
54+
}
55+
ctx.Data["RequireHighlightJS"] = true
56+
ctx.Data["TreePath"] = "patch"
57+
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/branch/" + ctx.Repo.BranchName
58+
ctx.Data["FileContent"] = form.Content
59+
ctx.Data["commit_summary"] = form.CommitSummary
60+
ctx.Data["commit_message"] = form.CommitMessage
61+
ctx.Data["commit_choice"] = form.CommitChoice
62+
ctx.Data["new_branch_name"] = form.NewBranchName
63+
ctx.Data["last_commit"] = ctx.Repo.CommitID
64+
ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
65+
66+
if ctx.HasError() {
67+
ctx.HTML(200, tplPatchFile)
68+
return
69+
}
70+
71+
// Cannot commit to a an existing branch if user doesn't have rights
72+
if branchName == ctx.Repo.BranchName && !canCommit {
73+
ctx.Data["Err_NewBranchName"] = true
74+
ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
75+
ctx.RenderWithErr(ctx.Tr("repo.editor.cannot_commit_to_protected_branch", branchName), tplEditFile, &form)
76+
return
77+
}
78+
79+
// CommitSummary is optional in the web form, if empty, give it a default message based on add or update
80+
// `message` will be both the summary and message combined
81+
message := strings.TrimSpace(form.CommitSummary)
82+
if len(message) == 0 {
83+
message = ctx.Tr("repo.editor.patch")
84+
}
85+
86+
form.CommitMessage = strings.TrimSpace(form.CommitMessage)
87+
if len(form.CommitMessage) > 0 {
88+
message += "\n\n" + form.CommitMessage
89+
}
90+
91+
if _, err := files.ApplyDiffPatch(ctx.Repo.Repository, ctx.User, &files.ApplyDiffPatchOptions{
92+
LastCommitID: form.LastCommit,
93+
OldBranch: ctx.Repo.BranchName,
94+
NewBranch: branchName,
95+
Message: message,
96+
Content: strings.Replace(form.Content, "\r", "", -1),
97+
}); err != nil {
98+
if models.IsErrBranchAlreadyExists(err) {
99+
// For when a user specifies a new branch that already exists
100+
ctx.Data["Err_NewBranchName"] = true
101+
if branchErr, ok := err.(models.ErrBranchAlreadyExists); ok {
102+
ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplEditFile, &form)
103+
} else {
104+
ctx.Error(500, err.Error())
105+
}
106+
} else if models.IsErrCommitIDDoesNotMatch(err) {
107+
ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_editing", ctx.Repo.RepoLink+"/compare/"+form.LastCommit+"..."+ctx.Repo.CommitID), tplPatchFile, &form)
108+
} else {
109+
ctx.RenderWithErr(ctx.Tr("repo.editor.fail_to_apply_patch", err), tplPatchFile, &form)
110+
}
111+
}
112+
ctx.Redirect(ctx.Repo.RepoLink + "/compare/" + ctx.Repo.BranchName + "..." + form.NewBranchName)
113+
}

routers/web/web.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -798,6 +798,8 @@ func RegisterRoutes(m *web.Route) {
798798
m.Combo("/_upload/*", repo.MustBeAbleToUpload).
799799
Get(repo.UploadFile).
800800
Post(bindIgnErr(forms.UploadRepoFileForm{}), repo.UploadFilePost)
801+
m.Combo("/_diffpatch/*").Get(repo.NewDiffPatch).
802+
Post(bindIgnErr(forms.EditRepoFileForm{}), repo.NewDiffPatchPost)
801803
}, context.RepoRefByType(context.RepoRefBranch), repo.MustBeEditable)
802804
m.Group("", func() {
803805
m.Post("/upload-file", repo.UploadFileToServer)

0 commit comments

Comments
 (0)