mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08:00 
			
		
		
		
	Add apply-patch, basic revert and cherry-pick functionality (#17902)
This code adds a simple endpoint to apply patches to repositories and branches on gitea. This is then used along with the conflicting checking code in #18004 to provide a basic implementation of cherry-pick revert. Now because the buttons necessary for cherry-pick and revert have required us to create a dropdown next to the Browse Source button I've also implemented Create Branch and Create Tag operations. Fix #3880 Fix #17986 Signed-off-by: Andrew Thornton <art27@cantab.net>
This commit is contained in:
		@@ -32,6 +32,21 @@ func GetRawDiff(ctx context.Context, repoPath, commitID string, diffType RawDiff
 | 
				
			|||||||
	return GetRawDiffForFile(ctx, repoPath, "", commitID, diffType, "", writer)
 | 
						return GetRawDiffForFile(ctx, repoPath, "", commitID, diffType, "", writer)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetReverseRawDiff dumps the reverse diff results of repository in given commit ID to io.Writer.
 | 
				
			||||||
 | 
					func GetReverseRawDiff(ctx context.Context, repoPath, commitID string, writer io.Writer) error {
 | 
				
			||||||
 | 
						stderr := new(bytes.Buffer)
 | 
				
			||||||
 | 
						cmd := NewCommand(ctx, "show", "--pretty=format:revert %H%n", "-R", commitID)
 | 
				
			||||||
 | 
						if err := cmd.RunWithContext(&RunContext{
 | 
				
			||||||
 | 
							Timeout: -1,
 | 
				
			||||||
 | 
							Dir:     repoPath,
 | 
				
			||||||
 | 
							Stdout:  writer,
 | 
				
			||||||
 | 
							Stderr:  stderr,
 | 
				
			||||||
 | 
						}); err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("Run: %v - %s", err, stderr)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetRawDiffForFile dumps diff results of file in given commit ID to io.Writer.
 | 
					// GetRawDiffForFile dumps diff results of file in given commit ID to io.Writer.
 | 
				
			||||||
func GetRawDiffForFile(ctx context.Context, repoPath, startCommit, endCommit string, diffType RawDiffType, file string, writer io.Writer) error {
 | 
					func GetRawDiffForFile(ctx context.Context, repoPath, startCommit, endCommit string, diffType RawDiffType, file string, writer io.Writer) error {
 | 
				
			||||||
	repo, closer, err := RepositoryFromContextOrOpen(ctx, repoPath)
 | 
						repo, closer, err := RepositoryFromContextOrOpen(ctx, repoPath)
 | 
				
			||||||
@@ -221,8 +236,7 @@ func CutDiffAroundLine(originalDiff io.Reader, line int64, old bool, numbersOfLi
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	err := scanner.Err()
 | 
						if err := scanner.Err(); err != nil {
 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return "", err
 | 
							return "", err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -50,6 +50,14 @@ type UpdateFileOptions struct {
 | 
				
			|||||||
	FromPath string `json:"from_path" binding:"MaxSize(500)"`
 | 
						FromPath string `json:"from_path" binding:"MaxSize(500)"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ApplyDiffPatchFileOptions options for applying a diff patch
 | 
				
			||||||
 | 
					// 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)
 | 
				
			||||||
 | 
					type ApplyDiffPatchFileOptions struct {
 | 
				
			||||||
 | 
						DeleteFileOptions
 | 
				
			||||||
 | 
						// required: true
 | 
				
			||||||
 | 
						Content string `json:"content"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// FileLinksResponse contains the links for a repo's file
 | 
					// FileLinksResponse contains the links for a repo's file
 | 
				
			||||||
type FileLinksResponse struct {
 | 
					type FileLinksResponse struct {
 | 
				
			||||||
	Self    *string `json:"self"`
 | 
						Self    *string `json:"self"`
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1075,6 +1075,10 @@ editor.add_tmpl = Add '<filename>'
 | 
				
			|||||||
editor.add = Add '%s'
 | 
					editor.add = Add '%s'
 | 
				
			||||||
editor.update = Update '%s'
 | 
					editor.update = Update '%s'
 | 
				
			||||||
editor.delete = Delete '%s'
 | 
					editor.delete = Delete '%s'
 | 
				
			||||||
 | 
					editor.patch = Apply Patch
 | 
				
			||||||
 | 
					editor.patching = Patching:
 | 
				
			||||||
 | 
					editor.fail_to_apply_patch = Unable to apply patch '%s'
 | 
				
			||||||
 | 
					editor.new_patch = New Patch
 | 
				
			||||||
editor.commit_message_desc = Add an optional extended description…
 | 
					editor.commit_message_desc = Add an optional extended description…
 | 
				
			||||||
editor.signoff_desc = Add a Signed-off-by trailer by the committer at the end of the commit log message.
 | 
					editor.signoff_desc = Add a Signed-off-by trailer by the committer at the end of the commit log message.
 | 
				
			||||||
editor.commit_directly_to_this_branch = Commit directly to the <strong class="branch-name">%s</strong> branch.
 | 
					editor.commit_directly_to_this_branch = Commit directly to the <strong class="branch-name">%s</strong> branch.
 | 
				
			||||||
@@ -1110,6 +1114,8 @@ editor.cannot_commit_to_protected_branch = Cannot commit to protected branch '%s
 | 
				
			|||||||
editor.no_commit_to_branch = Unable to commit directly to branch because:
 | 
					editor.no_commit_to_branch = Unable to commit directly to branch because:
 | 
				
			||||||
editor.user_no_push_to_branch = User cannot push to branch
 | 
					editor.user_no_push_to_branch = User cannot push to branch
 | 
				
			||||||
editor.require_signed_commit = Branch requires a signed commit
 | 
					editor.require_signed_commit = Branch requires a signed commit
 | 
				
			||||||
 | 
					editor.cherry_pick = Cherry-pick %s onto:
 | 
				
			||||||
 | 
					editor.revert = Revert %s onto:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
commits.desc = Browse source code change history.
 | 
					commits.desc = Browse source code change history.
 | 
				
			||||||
commits.commits = Commits
 | 
					commits.commits = Commits
 | 
				
			||||||
@@ -1130,6 +1136,14 @@ commits.signed_by_untrusted_user_unmatched = Signed by untrusted user who does n
 | 
				
			|||||||
commits.gpg_key_id = GPG Key ID
 | 
					commits.gpg_key_id = GPG Key ID
 | 
				
			||||||
commits.ssh_key_fingerprint = SSH Key Fingerprint
 | 
					commits.ssh_key_fingerprint = SSH Key Fingerprint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					commit.actions = Actions
 | 
				
			||||||
 | 
					commit.revert = Revert
 | 
				
			||||||
 | 
					commit.revert-header = Revert: %s
 | 
				
			||||||
 | 
					commit.revert-content = Select branch to revert onto:
 | 
				
			||||||
 | 
					commit.cherry-pick = Cherry-pick
 | 
				
			||||||
 | 
					commit.cherry-pick-header = Cherry-pick: %s
 | 
				
			||||||
 | 
					commit.cherry-pick-content = Select branch to cherry-pick onto:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ext_issues = Access to External Issues
 | 
					ext_issues = Access to External Issues
 | 
				
			||||||
ext_issues.desc = Link to an external issue tracker.
 | 
					ext_issues.desc = Link to an external issue tracker.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -2215,11 +2229,16 @@ branch.included_desc = This branch is part of the default branch
 | 
				
			|||||||
branch.included = Included
 | 
					branch.included = Included
 | 
				
			||||||
branch.create_new_branch = Create branch from branch:
 | 
					branch.create_new_branch = Create branch from branch:
 | 
				
			||||||
branch.confirm_create_branch = Create branch
 | 
					branch.confirm_create_branch = Create branch
 | 
				
			||||||
 | 
					branch.create_branch_operation = Create branch
 | 
				
			||||||
branch.new_branch = Create new branch
 | 
					branch.new_branch = Create new branch
 | 
				
			||||||
branch.new_branch_from = Create new branch from '%s'
 | 
					branch.new_branch_from = Create new branch from '%s'
 | 
				
			||||||
branch.renamed = Branch %s was renamed to %s.
 | 
					branch.renamed = Branch %s was renamed to %s.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
tag.create_tag = Create tag <strong>%s</strong>
 | 
					tag.create_tag = Create tag <strong>%s</strong>
 | 
				
			||||||
 | 
					tag.create_tag_operation = Create tag
 | 
				
			||||||
 | 
					tag.confirm_create_tag = Create tag
 | 
				
			||||||
 | 
					tag.create_tag_from = Create new tag from '%s'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
tag.create_success = Tag '%s' has been created.
 | 
					tag.create_success = Tag '%s' has been created.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
topic.manage_topics = Manage Topics
 | 
					topic.manage_topics = Manage Topics
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -975,6 +975,7 @@ func Routes(sessioner func(http.Handler) http.Handler) *web.Route {
 | 
				
			|||||||
					m.Get("/tags/{sha}", context.RepoRefForAPI, repo.GetAnnotatedTag)
 | 
										m.Get("/tags/{sha}", context.RepoRefForAPI, repo.GetAnnotatedTag)
 | 
				
			||||||
					m.Get("/notes/{sha}", repo.GetNote)
 | 
										m.Get("/notes/{sha}", repo.GetNote)
 | 
				
			||||||
				}, reqRepoReader(unit.TypeCode))
 | 
									}, reqRepoReader(unit.TypeCode))
 | 
				
			||||||
 | 
									m.Post("/diffpatch", reqRepoWriter(unit.TypeCode), reqToken(), bind(api.ApplyDiffPatchFileOptions{}), repo.ApplyDiffPatch)
 | 
				
			||||||
				m.Group("/contents", func() {
 | 
									m.Group("/contents", func() {
 | 
				
			||||||
					m.Get("", repo.GetContentsList)
 | 
										m.Get("", repo.GetContentsList)
 | 
				
			||||||
					m.Get("/*", repo.GetContents)
 | 
										m.Get("/*", repo.GetContents)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										107
									
								
								routers/api/v1/repo/patch.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								routers/api/v1/repo/patch.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,107 @@
 | 
				
			|||||||
 | 
					// Copyright 2021 The Gitea Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package repo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/context"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/git"
 | 
				
			||||||
 | 
						api "code.gitea.io/gitea/modules/structs"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/web"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/services/repository/files"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ApplyDiffPatch handles API call for applying a patch
 | 
				
			||||||
 | 
					func ApplyDiffPatch(ctx *context.APIContext) {
 | 
				
			||||||
 | 
						// swagger:operation POST /repos/{owner}/{repo}/diffpatch repository repoApplyDiffPatch
 | 
				
			||||||
 | 
						// ---
 | 
				
			||||||
 | 
						// summary: Apply diff patch to repository
 | 
				
			||||||
 | 
						// consumes:
 | 
				
			||||||
 | 
						// - application/json
 | 
				
			||||||
 | 
						// produces:
 | 
				
			||||||
 | 
						// - application/json
 | 
				
			||||||
 | 
						// parameters:
 | 
				
			||||||
 | 
						// - name: owner
 | 
				
			||||||
 | 
						//   in: path
 | 
				
			||||||
 | 
						//   description: owner of the repo
 | 
				
			||||||
 | 
						//   type: string
 | 
				
			||||||
 | 
						//   required: true
 | 
				
			||||||
 | 
						// - name: repo
 | 
				
			||||||
 | 
						//   in: path
 | 
				
			||||||
 | 
						//   description: name of the repo
 | 
				
			||||||
 | 
						//   type: string
 | 
				
			||||||
 | 
						//   required: true
 | 
				
			||||||
 | 
						// - name: body
 | 
				
			||||||
 | 
						//   in: body
 | 
				
			||||||
 | 
						//   required: true
 | 
				
			||||||
 | 
						//   schema:
 | 
				
			||||||
 | 
						//     "$ref": "#/definitions/UpdateFileOptions"
 | 
				
			||||||
 | 
						// responses:
 | 
				
			||||||
 | 
						//   "200":
 | 
				
			||||||
 | 
						//     "$ref": "#/responses/FileResponse"
 | 
				
			||||||
 | 
						apiOpts := web.GetForm(ctx).(*api.ApplyDiffPatchFileOptions)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						opts := &files.ApplyDiffPatchOptions{
 | 
				
			||||||
 | 
							Content:   apiOpts.Content,
 | 
				
			||||||
 | 
							SHA:       apiOpts.SHA,
 | 
				
			||||||
 | 
							Message:   apiOpts.Message,
 | 
				
			||||||
 | 
							OldBranch: apiOpts.BranchName,
 | 
				
			||||||
 | 
							NewBranch: apiOpts.NewBranchName,
 | 
				
			||||||
 | 
							Committer: &files.IdentityOptions{
 | 
				
			||||||
 | 
								Name:  apiOpts.Committer.Name,
 | 
				
			||||||
 | 
								Email: apiOpts.Committer.Email,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							Author: &files.IdentityOptions{
 | 
				
			||||||
 | 
								Name:  apiOpts.Author.Name,
 | 
				
			||||||
 | 
								Email: apiOpts.Author.Email,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							Dates: &files.CommitDateOptions{
 | 
				
			||||||
 | 
								Author:    apiOpts.Dates.Author,
 | 
				
			||||||
 | 
								Committer: apiOpts.Dates.Committer,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							Signoff: apiOpts.Signoff,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if opts.Dates.Author.IsZero() {
 | 
				
			||||||
 | 
							opts.Dates.Author = time.Now()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if opts.Dates.Committer.IsZero() {
 | 
				
			||||||
 | 
							opts.Dates.Committer = time.Now()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if opts.Message == "" {
 | 
				
			||||||
 | 
							opts.Message = "apply-patch"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !canWriteFiles(ctx.Repo) {
 | 
				
			||||||
 | 
							ctx.Error(http.StatusInternalServerError, "ApplyPatch", models.ErrUserDoesNotHaveAccessToRepo{
 | 
				
			||||||
 | 
								UserID:   ctx.User.ID,
 | 
				
			||||||
 | 
								RepoName: ctx.Repo.Repository.LowerName,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fileResponse, err := files.ApplyDiffPatch(ctx, ctx.Repo.Repository, ctx.User, opts)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if models.IsErrUserCannotCommit(err) || models.IsErrFilePathProtected(err) {
 | 
				
			||||||
 | 
								ctx.Error(http.StatusForbidden, "Access", err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if models.IsErrBranchAlreadyExists(err) || models.IsErrFilenameInvalid(err) || models.IsErrSHADoesNotMatch(err) ||
 | 
				
			||||||
 | 
								models.IsErrFilePathInvalid(err) || models.IsErrRepoFileAlreadyExists(err) {
 | 
				
			||||||
 | 
								ctx.Error(http.StatusUnprocessableEntity, "Invalid", err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if models.IsErrBranchDoesNotExist(err) || git.IsErrBranchNotExist(err) {
 | 
				
			||||||
 | 
								ctx.Error(http.StatusNotFound, "BranchDoesNotExist", err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							ctx.Error(http.StatusInternalServerError, "ApplyPatch", err)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							ctx.JSON(http.StatusCreated, fileResponse)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										189
									
								
								routers/web/repo/cherry_pick.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								routers/web/repo/cherry_pick.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,189 @@
 | 
				
			|||||||
 | 
					// Copyright 2021 The Gitea Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package repo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/models/unit"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/base"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/context"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/git"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/util"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/web"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/services/forms"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/services/repository/files"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var tplCherryPick base.TplName = "repo/editor/cherry_pick"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CherryPick handles cherrypick GETs
 | 
				
			||||||
 | 
					func CherryPick(ctx *context.Context) {
 | 
				
			||||||
 | 
						ctx.Data["SHA"] = ctx.Params(":sha")
 | 
				
			||||||
 | 
						cherryPickCommit, err := ctx.Repo.GitRepo.GetCommit(ctx.Params(":sha"))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if git.IsErrNotExist(err) {
 | 
				
			||||||
 | 
								ctx.NotFound("Missing Commit", err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							ctx.ServerError("GetCommit", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if ctx.FormString("cherry-pick-type") == "revert" {
 | 
				
			||||||
 | 
							ctx.Data["CherryPickType"] = "revert"
 | 
				
			||||||
 | 
							ctx.Data["commit_summary"] = "revert " + ctx.Params(":sha")
 | 
				
			||||||
 | 
							ctx.Data["commit_message"] = "revert " + cherryPickCommit.Message()
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							ctx.Data["CherryPickType"] = "cherry-pick"
 | 
				
			||||||
 | 
							splits := strings.SplitN(cherryPickCommit.Message(), "\n", 2)
 | 
				
			||||||
 | 
							ctx.Data["commit_summary"] = splits[0]
 | 
				
			||||||
 | 
							ctx.Data["commit_message"] = splits[1]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx.Data["RequireHighlightJS"] = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						canCommit := renderCommitRights(ctx)
 | 
				
			||||||
 | 
						ctx.Data["TreePath"] = "patch"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if canCommit {
 | 
				
			||||||
 | 
							ctx.Data["commit_choice"] = frmCommitChoiceDirect
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						ctx.Data["new_branch_name"] = GetUniquePatchBranchName(ctx)
 | 
				
			||||||
 | 
						ctx.Data["last_commit"] = ctx.Repo.CommitID
 | 
				
			||||||
 | 
						ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
 | 
				
			||||||
 | 
						ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx.HTML(200, tplCherryPick)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CherryPickPost handles cherrypick POSTs
 | 
				
			||||||
 | 
					func CherryPickPost(ctx *context.Context) {
 | 
				
			||||||
 | 
						form := web.GetForm(ctx).(*forms.CherryPickForm)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sha := ctx.Params(":sha")
 | 
				
			||||||
 | 
						ctx.Data["SHA"] = sha
 | 
				
			||||||
 | 
						if form.Revert {
 | 
				
			||||||
 | 
							ctx.Data["CherryPickType"] = "revert"
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							ctx.Data["CherryPickType"] = "cherry-pick"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx.Data["RequireHighlightJS"] = true
 | 
				
			||||||
 | 
						canCommit := renderCommitRights(ctx)
 | 
				
			||||||
 | 
						branchName := ctx.Repo.BranchName
 | 
				
			||||||
 | 
						if form.CommitChoice == frmCommitChoiceNewBranch {
 | 
				
			||||||
 | 
							branchName = form.NewBranchName
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						ctx.Data["commit_summary"] = form.CommitSummary
 | 
				
			||||||
 | 
						ctx.Data["commit_message"] = form.CommitMessage
 | 
				
			||||||
 | 
						ctx.Data["commit_choice"] = form.CommitChoice
 | 
				
			||||||
 | 
						ctx.Data["new_branch_name"] = form.NewBranchName
 | 
				
			||||||
 | 
						ctx.Data["last_commit"] = ctx.Repo.CommitID
 | 
				
			||||||
 | 
						ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
 | 
				
			||||||
 | 
						ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if ctx.HasError() {
 | 
				
			||||||
 | 
							ctx.HTML(200, tplCherryPick)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Cannot commit to a an existing branch if user doesn't have rights
 | 
				
			||||||
 | 
						if branchName == ctx.Repo.BranchName && !canCommit {
 | 
				
			||||||
 | 
							ctx.Data["Err_NewBranchName"] = true
 | 
				
			||||||
 | 
							ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
 | 
				
			||||||
 | 
							ctx.RenderWithErr(ctx.Tr("repo.editor.cannot_commit_to_protected_branch", branchName), tplCherryPick, &form)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						message := strings.TrimSpace(form.CommitSummary)
 | 
				
			||||||
 | 
						if message == "" {
 | 
				
			||||||
 | 
							if form.Revert {
 | 
				
			||||||
 | 
								message = ctx.Tr("repo.commit.revert-header", sha)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								message = ctx.Tr("repo.commit.cherry-pick-header", sha)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						form.CommitMessage = strings.TrimSpace(form.CommitMessage)
 | 
				
			||||||
 | 
						if len(form.CommitMessage) > 0 {
 | 
				
			||||||
 | 
							message += "\n\n" + form.CommitMessage
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						opts := &files.ApplyDiffPatchOptions{
 | 
				
			||||||
 | 
							LastCommitID: form.LastCommit,
 | 
				
			||||||
 | 
							OldBranch:    ctx.Repo.BranchName,
 | 
				
			||||||
 | 
							NewBranch:    branchName,
 | 
				
			||||||
 | 
							Message:      message,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// First lets try the simple plain read-tree -m approach
 | 
				
			||||||
 | 
						opts.Content = sha
 | 
				
			||||||
 | 
						if _, err := files.CherryPick(ctx, ctx.Repo.Repository, ctx.User, form.Revert, opts); err != nil {
 | 
				
			||||||
 | 
							if models.IsErrBranchAlreadyExists(err) {
 | 
				
			||||||
 | 
								// User has specified a branch that already exists
 | 
				
			||||||
 | 
								branchErr := err.(models.ErrBranchAlreadyExists)
 | 
				
			||||||
 | 
								ctx.Data["Err_NewBranchName"] = true
 | 
				
			||||||
 | 
								ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplCherryPick, &form)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							} else if models.IsErrCommitIDDoesNotMatch(err) {
 | 
				
			||||||
 | 
								ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_editing", ctx.Repo.RepoLink+"/compare/"+form.LastCommit+"..."+ctx.Repo.CommitID), tplPatchFile, &form)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// Drop through to the apply technique
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							buf := &bytes.Buffer{}
 | 
				
			||||||
 | 
							if form.Revert {
 | 
				
			||||||
 | 
								if err := git.GetReverseRawDiff(ctx, ctx.Repo.Repository.RepoPath(), sha, buf); err != nil {
 | 
				
			||||||
 | 
									if git.IsErrNotExist(err) {
 | 
				
			||||||
 | 
										ctx.NotFound("GetRawDiff", errors.New("commit "+ctx.Params(":sha")+" does not exist."))
 | 
				
			||||||
 | 
										return
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									ctx.ServerError("GetRawDiff", err)
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								if err := git.GetRawDiff(ctx, ctx.Repo.Repository.RepoPath(), sha, git.RawDiffType("patch"), buf); err != nil {
 | 
				
			||||||
 | 
									if git.IsErrNotExist(err) {
 | 
				
			||||||
 | 
										ctx.NotFound("GetRawDiff", errors.New("commit "+ctx.Params(":sha")+" does not exist."))
 | 
				
			||||||
 | 
										return
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									ctx.ServerError("GetRawDiff", err)
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							opts.Content = buf.String()
 | 
				
			||||||
 | 
							ctx.Data["FileContent"] = opts.Content
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if _, err := files.ApplyDiffPatch(ctx, ctx.Repo.Repository, ctx.User, opts); err != nil {
 | 
				
			||||||
 | 
								if models.IsErrBranchAlreadyExists(err) {
 | 
				
			||||||
 | 
									// User has specified a branch that already exists
 | 
				
			||||||
 | 
									branchErr := err.(models.ErrBranchAlreadyExists)
 | 
				
			||||||
 | 
									ctx.Data["Err_NewBranchName"] = true
 | 
				
			||||||
 | 
									ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplCherryPick, &form)
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								} else if models.IsErrCommitIDDoesNotMatch(err) {
 | 
				
			||||||
 | 
									ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_editing", ctx.Repo.RepoLink+"/compare/"+form.LastCommit+"..."+ctx.Repo.CommitID), tplPatchFile, &form)
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									ctx.RenderWithErr(ctx.Tr("repo.editor.fail_to_apply_patch", err), tplPatchFile, &form)
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if form.CommitChoice == frmCommitChoiceNewBranch && ctx.Repo.Repository.UnitEnabled(unit.TypePullRequests) {
 | 
				
			||||||
 | 
							ctx.Redirect(ctx.Repo.RepoLink + "/compare/" + util.PathEscapeSegments(ctx.Repo.BranchName) + "..." + util.PathEscapeSegments(form.NewBranchName))
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							ctx.Redirect(ctx.Repo.RepoLink + "/src/branch/" + util.PathEscapeSegments(branchName))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										120
									
								
								routers/web/repo/patch.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								routers/web/repo/patch.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,120 @@
 | 
				
			|||||||
 | 
					// Copyright 2021 The Gitea Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package repo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/models/unit"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/base"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/context"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/util"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/web"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/services/forms"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/services/repository/files"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						tplPatchFile base.TplName = "repo/editor/patch"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewDiffPatch render create patch page
 | 
				
			||||||
 | 
					func NewDiffPatch(ctx *context.Context) {
 | 
				
			||||||
 | 
						ctx.Data["RequireHighlightJS"] = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						canCommit := renderCommitRights(ctx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx.Data["TreePath"] = "patch"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx.Data["commit_summary"] = ""
 | 
				
			||||||
 | 
						ctx.Data["commit_message"] = ""
 | 
				
			||||||
 | 
						if canCommit {
 | 
				
			||||||
 | 
							ctx.Data["commit_choice"] = frmCommitChoiceDirect
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						ctx.Data["new_branch_name"] = GetUniquePatchBranchName(ctx)
 | 
				
			||||||
 | 
						ctx.Data["last_commit"] = ctx.Repo.CommitID
 | 
				
			||||||
 | 
						ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
 | 
				
			||||||
 | 
						ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx.HTML(200, tplPatchFile)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewDiffPatchPost response for sending patch page
 | 
				
			||||||
 | 
					func NewDiffPatchPost(ctx *context.Context) {
 | 
				
			||||||
 | 
						form := web.GetForm(ctx).(*forms.EditRepoFileForm)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						canCommit := renderCommitRights(ctx)
 | 
				
			||||||
 | 
						branchName := ctx.Repo.BranchName
 | 
				
			||||||
 | 
						if form.CommitChoice == frmCommitChoiceNewBranch {
 | 
				
			||||||
 | 
							branchName = form.NewBranchName
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						ctx.Data["RequireHighlightJS"] = true
 | 
				
			||||||
 | 
						ctx.Data["TreePath"] = "patch"
 | 
				
			||||||
 | 
						ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
 | 
				
			||||||
 | 
						ctx.Data["FileContent"] = form.Content
 | 
				
			||||||
 | 
						ctx.Data["commit_summary"] = form.CommitSummary
 | 
				
			||||||
 | 
						ctx.Data["commit_message"] = form.CommitMessage
 | 
				
			||||||
 | 
						ctx.Data["commit_choice"] = form.CommitChoice
 | 
				
			||||||
 | 
						ctx.Data["new_branch_name"] = form.NewBranchName
 | 
				
			||||||
 | 
						ctx.Data["last_commit"] = ctx.Repo.CommitID
 | 
				
			||||||
 | 
						ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if ctx.HasError() {
 | 
				
			||||||
 | 
							ctx.HTML(200, tplPatchFile)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Cannot commit to a an existing branch if user doesn't have rights
 | 
				
			||||||
 | 
						if branchName == ctx.Repo.BranchName && !canCommit {
 | 
				
			||||||
 | 
							ctx.Data["Err_NewBranchName"] = true
 | 
				
			||||||
 | 
							ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
 | 
				
			||||||
 | 
							ctx.RenderWithErr(ctx.Tr("repo.editor.cannot_commit_to_protected_branch", branchName), tplEditFile, &form)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// CommitSummary is optional in the web form, if empty, give it a default message based on add or update
 | 
				
			||||||
 | 
						// `message` will be both the summary and message combined
 | 
				
			||||||
 | 
						message := strings.TrimSpace(form.CommitSummary)
 | 
				
			||||||
 | 
						if len(message) == 0 {
 | 
				
			||||||
 | 
							message = ctx.Tr("repo.editor.patch")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						form.CommitMessage = strings.TrimSpace(form.CommitMessage)
 | 
				
			||||||
 | 
						if len(form.CommitMessage) > 0 {
 | 
				
			||||||
 | 
							message += "\n\n" + form.CommitMessage
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if _, err := files.ApplyDiffPatch(ctx, ctx.Repo.Repository, ctx.User, &files.ApplyDiffPatchOptions{
 | 
				
			||||||
 | 
							LastCommitID: form.LastCommit,
 | 
				
			||||||
 | 
							OldBranch:    ctx.Repo.BranchName,
 | 
				
			||||||
 | 
							NewBranch:    branchName,
 | 
				
			||||||
 | 
							Message:      message,
 | 
				
			||||||
 | 
							Content:      strings.ReplaceAll(form.Content, "\r", ""),
 | 
				
			||||||
 | 
						}); err != nil {
 | 
				
			||||||
 | 
							if models.IsErrBranchAlreadyExists(err) {
 | 
				
			||||||
 | 
								// User has specified a branch that already exists
 | 
				
			||||||
 | 
								branchErr := err.(models.ErrBranchAlreadyExists)
 | 
				
			||||||
 | 
								ctx.Data["Err_NewBranchName"] = true
 | 
				
			||||||
 | 
								ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplEditFile, &form)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							} else if models.IsErrCommitIDDoesNotMatch(err) {
 | 
				
			||||||
 | 
								ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_editing", ctx.Repo.RepoLink+"/compare/"+form.LastCommit+"..."+ctx.Repo.CommitID), tplPatchFile, &form)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								ctx.RenderWithErr(ctx.Tr("repo.editor.fail_to_apply_patch", err), tplPatchFile, &form)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if form.CommitChoice == frmCommitChoiceNewBranch && ctx.Repo.Repository.UnitEnabled(unit.TypePullRequests) {
 | 
				
			||||||
 | 
							ctx.Redirect(ctx.Repo.RepoLink + "/compare/" + util.PathEscapeSegments(ctx.Repo.BranchName) + "..." + util.PathEscapeSegments(form.NewBranchName))
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							ctx.Redirect(ctx.Repo.RepoLink + "/src/branch/" + util.PathEscapeSegments(branchName) + "/" + util.PathEscapeSegments(form.TreePath))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -808,6 +808,10 @@ func RegisterRoutes(m *web.Route) {
 | 
				
			|||||||
				m.Combo("/_upload/*", repo.MustBeAbleToUpload).
 | 
									m.Combo("/_upload/*", repo.MustBeAbleToUpload).
 | 
				
			||||||
					Get(repo.UploadFile).
 | 
										Get(repo.UploadFile).
 | 
				
			||||||
					Post(bindIgnErr(forms.UploadRepoFileForm{}), repo.UploadFilePost)
 | 
										Post(bindIgnErr(forms.UploadRepoFileForm{}), repo.UploadFilePost)
 | 
				
			||||||
 | 
									m.Combo("/_diffpatch/*").Get(repo.NewDiffPatch).
 | 
				
			||||||
 | 
										Post(bindIgnErr(forms.EditRepoFileForm{}), repo.NewDiffPatchPost)
 | 
				
			||||||
 | 
									m.Combo("/_cherrypick/{sha:([a-f0-9]{7,40})}/*").Get(repo.CherryPick).
 | 
				
			||||||
 | 
										Post(bindIgnErr(forms.CherryPickForm{}), repo.CherryPickPost)
 | 
				
			||||||
			}, context.RepoRefByType(context.RepoRefBranch), repo.MustBeEditable)
 | 
								}, context.RepoRefByType(context.RepoRefBranch), repo.MustBeEditable)
 | 
				
			||||||
			m.Group("", func() {
 | 
								m.Group("", func() {
 | 
				
			||||||
				m.Post("/upload-file", repo.UploadFileToServer)
 | 
									m.Post("/upload-file", repo.UploadFileToServer)
 | 
				
			||||||
@@ -1029,6 +1033,7 @@ func RegisterRoutes(m *web.Route) {
 | 
				
			|||||||
		m.Group("", func() {
 | 
							m.Group("", func() {
 | 
				
			||||||
			m.Get("/graph", repo.Graph)
 | 
								m.Get("/graph", repo.Graph)
 | 
				
			||||||
			m.Get("/commit/{sha:([a-f0-9]{7,40})$}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.Diff)
 | 
								m.Get("/commit/{sha:([a-f0-9]{7,40})$}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.Diff)
 | 
				
			||||||
 | 
								m.Get("/cherry-pick/{sha:([a-f0-9]{7,40})$}", repo.SetEditorconfigIfExists, repo.CherryPick)
 | 
				
			||||||
		}, repo.MustBeNotEmpty, context.RepoRef(), reqRepoCodeReader)
 | 
							}, repo.MustBeNotEmpty, context.RepoRef(), reqRepoCodeReader)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		m.Group("/src", func() {
 | 
							m.Group("/src", func() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -754,6 +754,30 @@ func (f *EditPreviewDiffForm) Validate(req *http.Request, errs binding.Errors) b
 | 
				
			|||||||
	return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
 | 
						return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// _________ .__                                 __________.__        __
 | 
				
			||||||
 | 
					// \_   ___ \|  |__   __________________ ___.__. \______   \__| ____ |  | __
 | 
				
			||||||
 | 
					// /    \  \/|  |  \_/ __ \_  __ \_  __ <   |  |  |     ___/  |/ ___\|  |/ /
 | 
				
			||||||
 | 
					// \     \___|   Y  \  ___/|  | \/|  | \/\___  |  |    |   |  \  \___|    <
 | 
				
			||||||
 | 
					//  \______  /___|  /\___  >__|   |__|   / ____|  |____|   |__|\___  >__|_ \
 | 
				
			||||||
 | 
					//         \/     \/     \/              \/                        \/     \/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CherryPickForm form for changing repository file
 | 
				
			||||||
 | 
					type CherryPickForm struct {
 | 
				
			||||||
 | 
						CommitSummary string `binding:"MaxSize(100)"`
 | 
				
			||||||
 | 
						CommitMessage string
 | 
				
			||||||
 | 
						CommitChoice  string `binding:"Required;MaxSize(50)"`
 | 
				
			||||||
 | 
						NewBranchName string `binding:"GitRefName;MaxSize(100)"`
 | 
				
			||||||
 | 
						LastCommit    string
 | 
				
			||||||
 | 
						Revert        bool
 | 
				
			||||||
 | 
						Signoff       bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Validate validates the fields
 | 
				
			||||||
 | 
					func (f *CherryPickForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
 | 
				
			||||||
 | 
						ctx := context.GetContext(req)
 | 
				
			||||||
 | 
						return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//  ____ ___        .__                    .___
 | 
					//  ____ ___        .__                    .___
 | 
				
			||||||
// |    |   \______ |  |   _________     __| _/
 | 
					// |    |   \______ |  |   _________     __| _/
 | 
				
			||||||
// |    |   /\____ \|  |  /  _ \__  \   / __ |
 | 
					// |    |   /\____ \|  |  /  _ \__  \   / __ |
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -87,7 +87,7 @@ func TestPatch(pr *models.PullRequest) error {
 | 
				
			|||||||
	pr.MergeBase = strings.TrimSpace(pr.MergeBase)
 | 
						pr.MergeBase = strings.TrimSpace(pr.MergeBase)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 2. Check for conflicts
 | 
						// 2. Check for conflicts
 | 
				
			||||||
	if conflicts, err := checkConflicts(pr, gitRepo, tmpBasePath); err != nil || conflicts || pr.Status == models.PullRequestStatusEmpty {
 | 
						if conflicts, err := checkConflicts(ctx, pr, gitRepo, tmpBasePath); err != nil || conflicts || pr.Status == models.PullRequestStatusEmpty {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -217,19 +217,20 @@ func attemptMerge(ctx context.Context, file *unmergedFile, tmpBasePath string, g
 | 
				
			|||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func checkConflicts(pr *models.PullRequest, gitRepo *git.Repository, tmpBasePath string) (bool, error) {
 | 
					// AttemptThreeWayMerge will attempt to three way merge using git read-tree and then follow the git merge-one-file algorithm to attempt to resolve basic conflicts
 | 
				
			||||||
	ctx, cancel, finished := process.GetManager().AddContext(graceful.GetManager().HammerContext(), fmt.Sprintf("checkConflicts: pr[%d] %s/%s#%d", pr.ID, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, pr.Index))
 | 
					func AttemptThreeWayMerge(ctx context.Context, gitPath string, gitRepo *git.Repository, base, ours, theirs, description string) (bool, []string, error) {
 | 
				
			||||||
	defer finished()
 | 
						ctx, cancel := context.WithCancel(ctx)
 | 
				
			||||||
 | 
						defer cancel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// First we use read-tree to do a simple three-way merge
 | 
						// First we use read-tree to do a simple three-way merge
 | 
				
			||||||
	if _, err := git.NewCommand(ctx, "read-tree", "-m", pr.MergeBase, "base", "tracking").RunInDir(tmpBasePath); err != nil {
 | 
						if _, err := git.NewCommand(ctx, "read-tree", "-m", base, ours, theirs).RunInDir(gitPath); err != nil {
 | 
				
			||||||
		log.Error("Unable to run read-tree -m! Error: %v", err)
 | 
							log.Error("Unable to run read-tree -m! Error: %v", err)
 | 
				
			||||||
		return false, fmt.Errorf("unable to run read-tree -m! Error: %v", err)
 | 
							return false, nil, fmt.Errorf("unable to run read-tree -m! Error: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Then we use git ls-files -u to list the unmerged files and collate the triples in unmergedfiles
 | 
						// Then we use git ls-files -u to list the unmerged files and collate the triples in unmergedfiles
 | 
				
			||||||
	unmerged := make(chan *unmergedFile)
 | 
						unmerged := make(chan *unmergedFile)
 | 
				
			||||||
	go unmergedFiles(ctx, tmpBasePath, unmerged)
 | 
						go unmergedFiles(ctx, gitPath, unmerged)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	defer func() {
 | 
						defer func() {
 | 
				
			||||||
		cancel()
 | 
							cancel()
 | 
				
			||||||
@@ -239,8 +240,8 @@ func checkConflicts(pr *models.PullRequest, gitRepo *git.Repository, tmpBasePath
 | 
				
			|||||||
	}()
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	numberOfConflicts := 0
 | 
						numberOfConflicts := 0
 | 
				
			||||||
	pr.ConflictedFiles = make([]string, 0, 5)
 | 
					 | 
				
			||||||
	conflict := false
 | 
						conflict := false
 | 
				
			||||||
 | 
						conflictedFiles := make([]string, 0, 5)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for file := range unmerged {
 | 
						for file := range unmerged {
 | 
				
			||||||
		if file == nil {
 | 
							if file == nil {
 | 
				
			||||||
@@ -248,23 +249,33 @@ func checkConflicts(pr *models.PullRequest, gitRepo *git.Repository, tmpBasePath
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
		if file.err != nil {
 | 
							if file.err != nil {
 | 
				
			||||||
			cancel()
 | 
								cancel()
 | 
				
			||||||
			return false, file.err
 | 
								return false, nil, file.err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// OK now we have the unmerged file triplet attempt to merge it
 | 
							// OK now we have the unmerged file triplet attempt to merge it
 | 
				
			||||||
		if err := attemptMerge(ctx, file, tmpBasePath, gitRepo); err != nil {
 | 
							if err := attemptMerge(ctx, file, gitPath, gitRepo); err != nil {
 | 
				
			||||||
			if conflictErr, ok := err.(*errMergeConflict); ok {
 | 
								if conflictErr, ok := err.(*errMergeConflict); ok {
 | 
				
			||||||
				log.Trace("Conflict: %s in PR[%d] %s/%s#%d", conflictErr.filename, pr.ID, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, pr.Index)
 | 
									log.Trace("Conflict: %s in %s", conflictErr.filename, description)
 | 
				
			||||||
				conflict = true
 | 
									conflict = true
 | 
				
			||||||
				if numberOfConflicts < 10 {
 | 
									if numberOfConflicts < 10 {
 | 
				
			||||||
					pr.ConflictedFiles = append(pr.ConflictedFiles, conflictErr.filename)
 | 
										conflictedFiles = append(conflictedFiles, conflictErr.filename)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				numberOfConflicts++
 | 
									numberOfConflicts++
 | 
				
			||||||
				continue
 | 
									continue
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			return false, err
 | 
								return false, nil, err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						return conflict, conflictedFiles, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func checkConflicts(ctx context.Context, pr *models.PullRequest, gitRepo *git.Repository, tmpBasePath string) (bool, error) {
 | 
				
			||||||
 | 
						description := fmt.Sprintf("PR[%d] %s/%s#%d", pr.ID, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, pr.Index)
 | 
				
			||||||
 | 
						conflict, _, err := AttemptThreeWayMerge(ctx,
 | 
				
			||||||
 | 
							tmpBasePath, gitRepo, pr.MergeBase, "base", "tracking", description)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return false, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if !conflict {
 | 
						if !conflict {
 | 
				
			||||||
		treeHash, err := git.NewCommand(ctx, "write-tree").RunInDir(tmpBasePath)
 | 
							treeHash, err := git.NewCommand(ctx, "write-tree").RunInDir(tmpBasePath)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										126
									
								
								services/repository/files/cherry_pick.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								services/repository/files/cherry_pick.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,126 @@
 | 
				
			|||||||
 | 
					// Copyright 2021 The Gitea Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package files
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
 | 
						repo_model "code.gitea.io/gitea/models/repo"
 | 
				
			||||||
 | 
						user_model "code.gitea.io/gitea/models/user"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/git"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/structs"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/services/pull"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CherryPick cherrypicks or reverts a commit to the given repository
 | 
				
			||||||
 | 
					func CherryPick(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, revert bool, opts *ApplyDiffPatchOptions) (*structs.FileResponse, error) {
 | 
				
			||||||
 | 
						if err := opts.Validate(ctx, repo, doer); err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						message := strings.TrimSpace(opts.Message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						author, committer := GetAuthorAndCommitterUsers(opts.Author, opts.Committer, doer)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t, err := NewTemporaryUploadRepository(ctx, repo)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Error("%v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer t.Close()
 | 
				
			||||||
 | 
						if err := t.Clone(opts.OldBranch); err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := t.SetDefaultIndex(); err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get the commit of the original branch
 | 
				
			||||||
 | 
						commit, err := t.GetBranchCommit(opts.OldBranch)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err // Couldn't get a commit for the branch
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Assigned LastCommitID in opts if it hasn't been set
 | 
				
			||||||
 | 
						if opts.LastCommitID == "" {
 | 
				
			||||||
 | 
							opts.LastCommitID = commit.ID.String()
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							lastCommitID, err := t.gitRepo.ConvertToSHA1(opts.LastCommitID)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, fmt.Errorf("CherryPick: Invalid last commit ID: %v", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							opts.LastCommitID = lastCommitID.String()
 | 
				
			||||||
 | 
							if commit.ID.String() != opts.LastCommitID {
 | 
				
			||||||
 | 
								return nil, models.ErrCommitIDDoesNotMatch{
 | 
				
			||||||
 | 
									GivenCommitID:   opts.LastCommitID,
 | 
				
			||||||
 | 
									CurrentCommitID: opts.LastCommitID,
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						commit, err = t.GetCommit(strings.TrimSpace(opts.Content))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						parent, err := commit.ParentID(0)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							parent = git.MustIDFromString(git.EmptyTreeSHA)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						base, right := parent.String(), commit.ID.String()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if revert {
 | 
				
			||||||
 | 
							right, base = base, right
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						description := fmt.Sprintf("CherryPick %s onto %s", right, opts.OldBranch)
 | 
				
			||||||
 | 
						conflict, _, err := pull.AttemptThreeWayMerge(ctx,
 | 
				
			||||||
 | 
							t.basePath, t.gitRepo, base, opts.LastCommitID, right, description)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("failed to three-way merge %s onto %s: %v", right, opts.OldBranch, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if conflict {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("failed to merge due to conflicts")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						treeHash, err := t.WriteTree()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							// likely non-sensical tree due to merge conflicts...
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Now commit the tree
 | 
				
			||||||
 | 
						var commitHash string
 | 
				
			||||||
 | 
						if opts.Dates != nil {
 | 
				
			||||||
 | 
							commitHash, err = t.CommitTreeWithDate(author, committer, treeHash, message, opts.Signoff, opts.Dates.Author, opts.Dates.Committer)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							commitHash, err = t.CommitTree(author, committer, treeHash, message, opts.Signoff)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Then push this tree to NewBranch
 | 
				
			||||||
 | 
						if err := t.Push(doer, commitHash, opts.NewBranch); err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						commit, err = t.GetCommit(commitHash)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fileCommitResponse, _ := GetFileCommitResponse(repo, commit) // ok if fails, then will be nil
 | 
				
			||||||
 | 
						verification := GetPayloadCommitVerification(commit)
 | 
				
			||||||
 | 
						fileResponse := &structs.FileResponse{
 | 
				
			||||||
 | 
							Commit:       fileCommitResponse,
 | 
				
			||||||
 | 
							Verification: verification,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return fileResponse, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										193
									
								
								services/repository/files/patch.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								services/repository/files/patch.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,193 @@
 | 
				
			|||||||
 | 
					// Copyright 2021 The Gitea Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package files
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
 | 
						repo_model "code.gitea.io/gitea/models/repo"
 | 
				
			||||||
 | 
						user_model "code.gitea.io/gitea/models/user"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/git"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/structs"
 | 
				
			||||||
 | 
						asymkey_service "code.gitea.io/gitea/services/asymkey"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ApplyDiffPatchOptions holds the repository diff patch update options
 | 
				
			||||||
 | 
					type ApplyDiffPatchOptions struct {
 | 
				
			||||||
 | 
						LastCommitID string
 | 
				
			||||||
 | 
						OldBranch    string
 | 
				
			||||||
 | 
						NewBranch    string
 | 
				
			||||||
 | 
						Message      string
 | 
				
			||||||
 | 
						Content      string
 | 
				
			||||||
 | 
						SHA          string
 | 
				
			||||||
 | 
						Author       *IdentityOptions
 | 
				
			||||||
 | 
						Committer    *IdentityOptions
 | 
				
			||||||
 | 
						Dates        *CommitDateOptions
 | 
				
			||||||
 | 
						Signoff      bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Validate validates the provided options
 | 
				
			||||||
 | 
					func (opts *ApplyDiffPatchOptions) Validate(ctx context.Context, repo *repo_model.Repository, doer *user_model.User) error {
 | 
				
			||||||
 | 
						// If no branch name is set, assume master
 | 
				
			||||||
 | 
						if opts.OldBranch == "" {
 | 
				
			||||||
 | 
							opts.OldBranch = repo.DefaultBranch
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if opts.NewBranch == "" {
 | 
				
			||||||
 | 
							opts.NewBranch = opts.OldBranch
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						gitRepo, closer, err := git.RepositoryFromContextOrOpen(ctx, repo.RepoPath())
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer closer.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// oldBranch must exist for this operation
 | 
				
			||||||
 | 
						if _, err := gitRepo.GetBranch(opts.OldBranch); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// A NewBranch can be specified for the patch to be applied to.
 | 
				
			||||||
 | 
						// Check to make sure the branch does not already exist, otherwise we can't proceed.
 | 
				
			||||||
 | 
						// If we aren't branching to a new branch, make sure user can commit to the given branch
 | 
				
			||||||
 | 
						if opts.NewBranch != opts.OldBranch {
 | 
				
			||||||
 | 
							existingBranch, err := gitRepo.GetBranch(opts.NewBranch)
 | 
				
			||||||
 | 
							if existingBranch != nil {
 | 
				
			||||||
 | 
								return models.ErrBranchAlreadyExists{
 | 
				
			||||||
 | 
									BranchName: opts.NewBranch,
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if err != nil && !git.IsErrBranchNotExist(err) {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							protectedBranch, err := models.GetProtectedBranchBy(repo.ID, opts.OldBranch)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if protectedBranch != nil && !protectedBranch.CanUserPush(doer.ID) {
 | 
				
			||||||
 | 
								return models.ErrUserCannotCommit{
 | 
				
			||||||
 | 
									UserName: doer.LowerName,
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if protectedBranch != nil && protectedBranch.RequireSignedCommits {
 | 
				
			||||||
 | 
								_, _, _, err := asymkey_service.SignCRUDAction(ctx, repo.RepoPath(), doer, repo.RepoPath(), opts.OldBranch)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									if !asymkey_service.IsErrWontSign(err) {
 | 
				
			||||||
 | 
										return err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									return models.ErrUserCannotCommit{
 | 
				
			||||||
 | 
										UserName: doer.LowerName,
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ApplyDiffPatch applies a patch to the given repository
 | 
				
			||||||
 | 
					func ApplyDiffPatch(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, opts *ApplyDiffPatchOptions) (*structs.FileResponse, error) {
 | 
				
			||||||
 | 
						if err := opts.Validate(ctx, repo, doer); err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						message := strings.TrimSpace(opts.Message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						author, committer := GetAuthorAndCommitterUsers(opts.Author, opts.Committer, doer)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t, err := NewTemporaryUploadRepository(ctx, repo)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Error("%v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer t.Close()
 | 
				
			||||||
 | 
						if err := t.Clone(opts.OldBranch); err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := t.SetDefaultIndex(); err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get the commit of the original branch
 | 
				
			||||||
 | 
						commit, err := t.GetBranchCommit(opts.OldBranch)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err // Couldn't get a commit for the branch
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Assigned LastCommitID in opts if it hasn't been set
 | 
				
			||||||
 | 
						if opts.LastCommitID == "" {
 | 
				
			||||||
 | 
							opts.LastCommitID = commit.ID.String()
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							lastCommitID, err := t.gitRepo.ConvertToSHA1(opts.LastCommitID)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, fmt.Errorf("ApplyPatch: Invalid last commit ID: %v", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							opts.LastCommitID = lastCommitID.String()
 | 
				
			||||||
 | 
							if commit.ID.String() != opts.LastCommitID {
 | 
				
			||||||
 | 
								return nil, models.ErrCommitIDDoesNotMatch{
 | 
				
			||||||
 | 
									GivenCommitID:   opts.LastCommitID,
 | 
				
			||||||
 | 
									CurrentCommitID: opts.LastCommitID,
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						stdout := &strings.Builder{}
 | 
				
			||||||
 | 
						stderr := &strings.Builder{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						args := []string{"apply", "--index", "--recount", "--cached", "--ignore-whitespace", "--whitespace=fix", "--binary"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if git.CheckGitVersionAtLeast("2.32") == nil {
 | 
				
			||||||
 | 
							args = append(args, "-3")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cmd := git.NewCommand(ctx, args...)
 | 
				
			||||||
 | 
						if err := cmd.RunWithContext(&git.RunContext{
 | 
				
			||||||
 | 
							Timeout: -1,
 | 
				
			||||||
 | 
							Dir:     t.basePath,
 | 
				
			||||||
 | 
							Stdout:  stdout,
 | 
				
			||||||
 | 
							Stderr:  stderr,
 | 
				
			||||||
 | 
							Stdin:   strings.NewReader(opts.Content),
 | 
				
			||||||
 | 
						}); err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("Error: Stdout: %s\nStderr: %s\nErr: %v", stdout.String(), stderr.String(), err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Now write the tree
 | 
				
			||||||
 | 
						treeHash, err := t.WriteTree()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Now commit the tree
 | 
				
			||||||
 | 
						var commitHash string
 | 
				
			||||||
 | 
						if opts.Dates != nil {
 | 
				
			||||||
 | 
							commitHash, err = t.CommitTreeWithDate(author, committer, treeHash, message, opts.Signoff, opts.Dates.Author, opts.Dates.Committer)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							commitHash, err = t.CommitTree(author, committer, treeHash, message, opts.Signoff)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Then push this tree to NewBranch
 | 
				
			||||||
 | 
						if err := t.Push(doer, commitHash, opts.NewBranch); err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						commit, err = t.GetCommit(commitHash)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fileCommitResponse, _ := GetFileCommitResponse(repo, commit) // ok if fails, then will be nil
 | 
				
			||||||
 | 
						verification := GetPayloadCommitVerification(commit)
 | 
				
			||||||
 | 
						fileResponse := &structs.FileResponse{
 | 
				
			||||||
 | 
							Commit:       fileCommitResponse,
 | 
				
			||||||
 | 
							Verification: verification,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return fileResponse, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,37 +1,51 @@
 | 
				
			|||||||
{{$release := .release}}
 | 
					{{$release := .release}}
 | 
				
			||||||
 | 
					{{$defaultBranch := $.root.BranchName}}{{if and .root.IsViewTag (not .noTag)}}{{$defaultBranch = .root.TagName}}{{end}}{{if eq $defaultBranch ""}}{{$defaultBranch = $.root.Repository.DefaultBranch}}{{end}}
 | 
				
			||||||
{{$showBranchesInDropdown := not .root.HideBranchesInDropdown}}
 | 
					{{$showBranchesInDropdown := not .root.HideBranchesInDropdown}}
 | 
				
			||||||
<div class="fitted item choose reference{{if not $release}} mr-1{{end}}">
 | 
					<div class="fitted item choose reference{{if not $release}} mr-1{{end}}">
 | 
				
			||||||
	<div class="ui floating filter dropdown custom" data-can-create-branch="{{.root.CanCreateBranch}}" data-no-results="{{.root.i18n.Tr "repo.pulls.no_results"}}">
 | 
						<div class="ui floating filter dropdown custom"
 | 
				
			||||||
 | 
							data-branch-form="{{if $.branchForm}}{{$.branchForm}}{{end}}"
 | 
				
			||||||
 | 
							data-can-create-branch="{{if .canCreateBranch}}{{.canCreateBranch}}{{else}}{{.root.CanCreateBranch}}{{end}}"
 | 
				
			||||||
 | 
							data-no-results="{{.root.i18n.Tr "repo.pulls.no_results"}}"
 | 
				
			||||||
 | 
							data-set-action="{{.setAction}}" data-submit-form="{{.submitForm}}"
 | 
				
			||||||
 | 
							data-view-type="{{if and .root.IsViewTag (not .noTag)}}tag{{else if .root.IsViewBranch}}branch{{else}}tree{{end}}"
 | 
				
			||||||
 | 
							data-ref-name="{{if and .root.IsViewTag (not .noTag)}}{{.root.TagName}}{{else if .root.IsViewBranch}}{{.root.BranchName}}{{else}}{{ShortSha .root.CommitID}}{{end}}"
 | 
				
			||||||
 | 
							data-branch-url-prefix="{{if .branchURLPrefix}}{{.branchURLPrefix}}{{else}}{{$.root.RepoLink}}/{{if $.root.PageIsCommits}}commits{{else}}src{{end}}/branch/{{end}}"
 | 
				
			||||||
 | 
							data-branch-url-suffix="{{if .branchURLSuffix}}{{.branchURLSuffix}}{{else}}{{if $.root.TreePath}}/{{PathEscapeSegments $.root.TreePath}}{{end}}{{end}}"
 | 
				
			||||||
 | 
							data-tag-url-prefix="{{if .tagURLPrefix}}{{.tagURLPrefix}}{{else if $release}}{{$.root.RepoLink}}/compare/{{else}}{{$.root.RepoLink}}/{{if $.root.PageIsCommits}}commits{{else}}src{{end}}/tag/{{end}}"
 | 
				
			||||||
 | 
							data-tag-url-suffix="{{if .tagURLSuffix}}{{.tagURLSuffix}}{{else if $release}}...{{if $release.IsDraft}}{{PathEscapeSegments $release.Target}}{{else}}{{if $release.TagName}}{{PathEscapeSegments $release.TagName}}{{else}}{{PathEscapeSegments $release.Sha1}}{{end}}{{end}}{{else}}{{if $.root.TreePath}}/{{PathEscapeSegments $.root.TreePath}}{{end}}{{end}}">
 | 
				
			||||||
		<div class="ui basic small compact button" @click="menuVisible = !menuVisible" @keyup.enter="menuVisible = !menuVisible">
 | 
							<div class="ui basic small compact button" @click="menuVisible = !menuVisible" @keyup.enter="menuVisible = !menuVisible">
 | 
				
			||||||
			<span class="text">
 | 
								<span class="text">
 | 
				
			||||||
				{{if $release}}
 | 
									{{if $release}}
 | 
				
			||||||
					{{.root.i18n.Tr "repo.release.compare"}}
 | 
										{{.root.i18n.Tr "repo.release.compare"}}
 | 
				
			||||||
				{{else}}
 | 
									{{else}}
 | 
				
			||||||
					{{if .root.IsViewTag}}{{svg "octicon-tag"}}{{else}}{{svg "octicon-git-branch"}}{{end}}
 | 
										<span :class="{visible: isViewTag}" v-if="isViewTag" v-cloak>{{svg "octicon-tag"}} {{.root.i18n.Tr "repo.tag"}}:</span>
 | 
				
			||||||
					{{if .root.IsViewBranch}}{{.root.i18n.Tr "repo.branch"}}{{else if .root.IsViewTag}}{{.root.i18n.Tr "repo.tag"}}{{else}}{{.root.i18n.Tr "repo.tree"}}{{end}}:
 | 
										<span :class="{visible: isViewBranch}" v-if="isViewBranch" v-cloak>{{svg "octicon-git-branch"}} {{.root.i18n.Tr "repo.branch"}}:</span>
 | 
				
			||||||
					<strong>{{if .root.IsViewBranch}}{{.root.BranchName}}{{else if .root.IsViewTag}}{{.root.TagName}}{{else}}{{ShortSha .root.CommitID}}{{end}}</strong>
 | 
										<span :class="{visible: isViewTree}" v-if="isViewTree" v-cloak>{{svg "octicon-git-branch"}} {{.root.i18n.Tr "repo.tree"}}:</span>
 | 
				
			||||||
 | 
										<strong ref="dropdownRefName">{{if and .root.IsViewTag (not .noTag)}}{{.root.TagName}}{{else if .root.IsViewBranch}}{{.root.BranchName}}{{else}}{{ShortSha .root.CommitID}}{{end}}</strong>
 | 
				
			||||||
				{{end}}
 | 
									{{end}}
 | 
				
			||||||
			</span>
 | 
								</span>
 | 
				
			||||||
			{{svg "octicon-triangle-down" 14 "dropdown icon"}}
 | 
								{{svg "octicon-triangle-down" 14 "dropdown icon"}}
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
		<div class="data" style="display: none" data-mode="{{if .root.IsViewTag}}tags{{else}}branches{{end}}">
 | 
							<div class="data" style="display: none" data-mode="{{if or .root.IsViewTag .isTag}}tags{{else}}branches{{end}}">
 | 
				
			||||||
			{{if $showBranchesInDropdown}}
 | 
								{{if $showBranchesInDropdown}}
 | 
				
			||||||
				{{range .root.Branches}}
 | 
									{{range .root.Branches}}
 | 
				
			||||||
					<div class="item branch {{if eq $.root.BranchName .}}selected{{end}}" data-url="{{$.root.RepoLink}}/{{if $.root.PageIsCommits}}commits{{else}}src{{end}}/branch/{{PathEscapeSegments .}}{{if $.root.TreePath}}/{{PathEscapeSegments $.root.TreePath}}{{end}}">{{.}}</div>
 | 
										<div class="item branch {{if eq $defaultBranch .}}selected{{end}}" data-url="{{PathEscapeSegments .}}">{{.}}</div>
 | 
				
			||||||
				{{end}}
 | 
									{{end}}
 | 
				
			||||||
			{{end}}
 | 
								{{end}}
 | 
				
			||||||
			{{range .root.Tags}}
 | 
								{{if (not .noTag)}}
 | 
				
			||||||
				{{if $release}}
 | 
									{{range .root.Tags}}
 | 
				
			||||||
					<div class="item tag {{if eq $release.TagName .}}selected{{end}}" data-url="{{$.root.RepoLink}}/compare/{{PathEscapeSegments .}}...{{if $release.IsDraft}}{{PathEscapeSegments $release.Target}}{{else}}{{if $release.TagName}}{{PathEscapeSegments $release.TagName}}{{else}}{{PathEscapeSegments $release.Sha1}}{{end}}{{end}}">{{.}}</div>
 | 
										{{if $release}}
 | 
				
			||||||
				{{else}}
 | 
											<div class="item tag {{if eq $release.TagName .}}selected{{end}}" data-url="{{PathEscapeSegments .}}">{{.}}</div>
 | 
				
			||||||
					<div class="item tag {{if eq $.root.BranchName .}}selected{{end}}" data-url="{{$.root.RepoLink}}/{{if $.root.PageIsCommits}}commits{{else}}src{{end}}/tag/{{PathEscapeSegments .}}{{if $.root.TreePath}}/{{PathEscapeSegments $.root.TreePath}}{{end}}">{{.}}</div>
 | 
										{{else}}
 | 
				
			||||||
 | 
											<div class="item tag {{if eq $defaultBranch .}}selected{{end}}" data-url="{{PathEscapeSegments .}}">{{.}}</div>
 | 
				
			||||||
 | 
										{{end}}
 | 
				
			||||||
				{{end}}
 | 
									{{end}}
 | 
				
			||||||
			{{end}}
 | 
								{{end}}
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
		<div class="menu transition" :class="{visible: menuVisible}" v-if="menuVisible" v-cloak>
 | 
							<div class="menu transition" :class="{visible: menuVisible}" v-if="menuVisible" v-cloak>
 | 
				
			||||||
			<div class="ui icon search input">
 | 
								<div class="ui icon search input">
 | 
				
			||||||
				<i class="icon df ac jc m-0">{{svg "octicon-filter" 16}}</i>
 | 
									<i class="icon df ac jc m-0">{{svg "octicon-filter" 16}}</i>
 | 
				
			||||||
				<input name="search" ref="searchField" autocomplete="off" v-model="searchTerm" @keydown="keydown($event)" placeholder="{{if $showBranchesInDropdown}}{{.root.i18n.Tr "repo.filter_branch_and_tag"}}{{else}}{{.root.i18n.Tr "repo.find_tag"}}{{end}}...">
 | 
									<input name="search" ref="searchField" autocomplete="off" v-model="searchTerm" @keydown="keydown($event)" placeholder="{{if $.noTag}}{{.root.i18n.Tr "repo.filter_branch"}}{{else if $showBranchesInDropdown}}{{.root.i18n.Tr "repo.filter_branch_and_tag"}}{{else}}{{.root.i18n.Tr "repo.find_tag"}}{{end}}...">
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
			{{if $showBranchesInDropdown}}
 | 
								{{if $showBranchesInDropdown}}
 | 
				
			||||||
				<div class="header branch-tag-choice">
 | 
									<div class="header branch-tag-choice">
 | 
				
			||||||
@@ -42,11 +56,13 @@
 | 
				
			|||||||
									{{svg "octicon-git-branch" 16 "mr-2"}}{{.root.i18n.Tr "repo.branches"}}
 | 
														{{svg "octicon-git-branch" 16 "mr-2"}}{{.root.i18n.Tr "repo.branches"}}
 | 
				
			||||||
								</span>
 | 
													</span>
 | 
				
			||||||
							</a>
 | 
												</a>
 | 
				
			||||||
							<a class="reference column" href="#" @click="createTag = true; mode = 'tags'; focusSearchField()">
 | 
												{{if not .noTag}}
 | 
				
			||||||
								<span class="text" :class="{black: mode == 'tags'}">
 | 
													<a class="reference column" href="#" @click="createTag = true; mode = 'tags'; focusSearchField()">
 | 
				
			||||||
									{{svg "octicon-tag" 16 "mr-2"}}{{.root.i18n.Tr "repo.tags"}}
 | 
														<span class="text" :class="{black: mode == 'tags'}">
 | 
				
			||||||
								</span>
 | 
															{{svg "octicon-tag" 16 "mr-2"}}{{.root.i18n.Tr "repo.tags"}}
 | 
				
			||||||
							</a>
 | 
														</span>
 | 
				
			||||||
 | 
													</a>
 | 
				
			||||||
 | 
												{{end}}
 | 
				
			||||||
						</div>
 | 
											</div>
 | 
				
			||||||
					</div>
 | 
										</div>
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,14 +18,123 @@
 | 
				
			|||||||
			{{end}}
 | 
								{{end}}
 | 
				
			||||||
		{{end}}
 | 
							{{end}}
 | 
				
			||||||
		<div class="ui top attached header clearing segment pr {{$class}}">
 | 
							<div class="ui top attached header clearing segment pr {{$class}}">
 | 
				
			||||||
			{{if not $.PageIsWiki}}
 | 
								<div class="df mb-4">
 | 
				
			||||||
			<a class="ui blue tiny button browse-button" href="{{.SourcePath}}">
 | 
									<h3 class="mb-0 f1"><span class="commit-summary" title="{{.Commit.Summary}}">{{RenderCommitMessage $.Context .Commit.Message $.RepoLink $.Repository.ComposeMetas}}</span>{{template "repo/commit_statuses" dict "Status" .CommitStatus "Statuses" .CommitStatuses  "root" $}}</h3>
 | 
				
			||||||
				{{.i18n.Tr "repo.diff.browse_source"}}
 | 
									{{if not $.PageIsWiki}}
 | 
				
			||||||
			</a>
 | 
										<div class="ui">
 | 
				
			||||||
			{{end}}
 | 
											<a class="ui blue tiny button" href="{{.SourcePath}}">
 | 
				
			||||||
			<h3 class="mt-0"><span class="message-wrapper"><span class="commit-summary" title="{{.Commit.Summary}}">{{RenderCommitMessage $.Context .Commit.Message $.RepoLink $.Repository.ComposeMetas}}</span></span>{{template "repo/commit_statuses" dict "Status" .CommitStatus "Statuses" .CommitStatuses  "root" $}}</h3>
 | 
												{{.i18n.Tr "repo.diff.browse_source"}}
 | 
				
			||||||
 | 
											</a>
 | 
				
			||||||
 | 
											{{if and ($.Permission.CanWrite $.UnitTypeCode) (not $.Repository.IsArchived) (not .IsDeleted)}}{{- /* */ -}}
 | 
				
			||||||
 | 
												<div class="ui blue tiny floating dropdown icon button">{{.i18n.Tr "repo.commit.actions"}}
 | 
				
			||||||
 | 
													{{svg "octicon-triangle-down" 14 "dropdown icon"}}<span class="sr-mobile-only">{{.i18n.Tr "repo.commit.actions"}}</span>
 | 
				
			||||||
 | 
													<div class="menu">
 | 
				
			||||||
 | 
														<div class="ui header">{{.i18n.Tr "repo.commit.actions"}}</div>
 | 
				
			||||||
 | 
														<div class="divider"></div>
 | 
				
			||||||
 | 
														<div class="item show-create-branch-modal"
 | 
				
			||||||
 | 
															data-content="{{$.i18n.Tr "repo.branch.new_branch_from" (.CommitID)}}"
 | 
				
			||||||
 | 
															data-branch-from="{{ShortSha .CommitID}}"
 | 
				
			||||||
 | 
															data-branch-from-urlcomponent="{{.CommitID}}"
 | 
				
			||||||
 | 
															data-modal="#create-branch-modal">
 | 
				
			||||||
 | 
															{{.i18n.Tr "repo.branch.create_branch_operation"}}
 | 
				
			||||||
 | 
														</div>
 | 
				
			||||||
 | 
														<div class="item show-create-branch-modal"
 | 
				
			||||||
 | 
															data-content="{{$.i18n.Tr "repo.branch.new_branch_from" (.CommitID)}}"
 | 
				
			||||||
 | 
															data-branch-from="{{ShortSha .CommitID}}"
 | 
				
			||||||
 | 
															data-branch-from-urlcomponent="{{.CommitID}}"
 | 
				
			||||||
 | 
															data-modal="#create-tag-modal"
 | 
				
			||||||
 | 
															data-modal-from-span="#modal-create-tag-from-span"
 | 
				
			||||||
 | 
															data-modal-form="#create-tag-form">
 | 
				
			||||||
 | 
															{{.i18n.Tr "repo.tag.create_tag_operation"}}
 | 
				
			||||||
 | 
														</div>
 | 
				
			||||||
 | 
														<div class="item show-modal revert-button"
 | 
				
			||||||
 | 
															data-modal="#cherry-pick-modal"
 | 
				
			||||||
 | 
															data-modal-cherry-pick-type="revert"
 | 
				
			||||||
 | 
															data-modal-cherry-pick-header="{{$.i18n.Tr "repo.commit.revert-header" (ShortSha .CommitID)}}"
 | 
				
			||||||
 | 
															data-modal-cherry-pick-content="{{$.i18n.Tr "repo.commit.revert-content"}}"
 | 
				
			||||||
 | 
															data-modal-cherry-pick-submit="{{.i18n.Tr "repo.commit.revert"}}">{{.i18n.Tr "repo.commit.revert"}}</a></div>
 | 
				
			||||||
 | 
														<div class="item cherry-pick-button show-modal"
 | 
				
			||||||
 | 
															data-modal="#cherry-pick-modal"
 | 
				
			||||||
 | 
															data-modal-cherry-pick-type="cherry-pick"
 | 
				
			||||||
 | 
															data-modal-cherry-pick-header="{{$.i18n.Tr "repo.commit.cherry-pick-header" (ShortSha .CommitID)}}"
 | 
				
			||||||
 | 
															data-modal-cherry-pick-content="{{$.i18n.Tr "repo.commit.cherry-pick-content"}}"
 | 
				
			||||||
 | 
															data-modal-cherry-pick-submit="{{.i18n.Tr "repo.commit.cherry-pick"}}">{{.i18n.Tr "repo.commit.cherry-pick"}}</a></div>
 | 
				
			||||||
 | 
														<div class="ui basic modal" id="cherry-pick-modal">
 | 
				
			||||||
 | 
															<div class="ui icon header">
 | 
				
			||||||
 | 
																<span id="cherry-pick-header"></span>
 | 
				
			||||||
 | 
															</div>
 | 
				
			||||||
 | 
															<div class="content center">
 | 
				
			||||||
 | 
																<p id="cherry-pick-content" class="branch-dropdown"></p>
 | 
				
			||||||
 | 
																{{template "repo/branch_dropdown" dict "root" .
 | 
				
			||||||
 | 
																	"noTag" "true" "canCreateBranch" "false"
 | 
				
			||||||
 | 
																	"branchForm" "branch-dropdown-form"
 | 
				
			||||||
 | 
																	"branchURLPrefix" (printf "%s/_cherrypick/%s/" $.RepoLink .CommitID) "branchURLSuffix" ""
 | 
				
			||||||
 | 
																	"setAction" "true" "submitForm" "true"}}
 | 
				
			||||||
 | 
																<form method="GET" action="{{$.RepoLink}}/_cherrypick/{{.CommitID}}/{{if $.BranchName}}{{PathEscapeSegments $.BranchName}}{{else}}{{PathEscapeSegments $.Repository.DefaultBranch}}{{end}}" id="branch-dropdown-form">
 | 
				
			||||||
 | 
																	<input type="hidden" name="ref" value="{{if $.BranchName}}{{$.BranchName}}{{else}}{{$.Repository.DefaultBranch}}{{end}}">
 | 
				
			||||||
 | 
																	<input type="hidden" name="refType" value="branch">
 | 
				
			||||||
 | 
																	<input type="hidden" id="cherry-pick-type" name="cherry-pick-type"><br/>
 | 
				
			||||||
 | 
																	<button type="submit" id="cherry-pick-submit" class="ui green button"></button>
 | 
				
			||||||
 | 
																</form>
 | 
				
			||||||
 | 
															</div>
 | 
				
			||||||
 | 
														</div>
 | 
				
			||||||
 | 
														<div class="ui small modal" id="create-branch-modal">
 | 
				
			||||||
 | 
															<div class="header">
 | 
				
			||||||
 | 
																{{.i18n.Tr "repo.branch.new_branch"}}
 | 
				
			||||||
 | 
															</div>
 | 
				
			||||||
 | 
															<div class="content">
 | 
				
			||||||
 | 
																<form class="ui form" id="create-branch-form" action="" data-base-action="{{.RepoLink}}/branches/_new/commit/" method="post">
 | 
				
			||||||
 | 
																	{{.CsrfTokenHtml}}
 | 
				
			||||||
 | 
																	<div class="field">
 | 
				
			||||||
 | 
																		<label>
 | 
				
			||||||
 | 
																			{{.i18n.Tr "repo.branch.new_branch_from" "<span class=\"text\" id=\"modal-create-branch-from-span\"></span>" | Safe }}
 | 
				
			||||||
 | 
																		</label>
 | 
				
			||||||
 | 
																	</div>
 | 
				
			||||||
 | 
																	<div class="required field">
 | 
				
			||||||
 | 
																		<label for="new_branch_name">{{.i18n.Tr "repo.branch.name"}}</label>
 | 
				
			||||||
 | 
																		<input id="new_branch_name" name="new_branch_name" required>
 | 
				
			||||||
 | 
																	</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
																	<div class="text right actions">
 | 
				
			||||||
 | 
																		<div class="ui cancel button">{{.i18n.Tr "settings.cancel"}}</div>
 | 
				
			||||||
 | 
																		<button class="ui green button">{{.i18n.Tr "repo.branch.confirm_create_branch"}}</button>
 | 
				
			||||||
 | 
																	</div>
 | 
				
			||||||
 | 
																</form>
 | 
				
			||||||
 | 
															</div>
 | 
				
			||||||
 | 
														</div>
 | 
				
			||||||
 | 
														<div class="ui small modal" id="create-tag-modal">
 | 
				
			||||||
 | 
															<div class="header">
 | 
				
			||||||
 | 
																{{.i18n.Tr "repo.tag.create_tag_operation"}}
 | 
				
			||||||
 | 
															</div>
 | 
				
			||||||
 | 
															<div class="content">
 | 
				
			||||||
 | 
																<form class="ui form" id="create-tag-form" action="" data-base-action="{{.RepoLink}}/branches/_new/commit/" method="post">
 | 
				
			||||||
 | 
																	{{.CsrfTokenHtml}}
 | 
				
			||||||
 | 
																	<input type="hidden" name="create_tag" value="true">
 | 
				
			||||||
 | 
																	<div class="field">
 | 
				
			||||||
 | 
																		<label>
 | 
				
			||||||
 | 
																			{{.i18n.Tr "repo.tag.create_tag_from" "<span class=\"text\" id=\"modal-create-tag-from-span\"></span>" | Safe }}
 | 
				
			||||||
 | 
																		</label>
 | 
				
			||||||
 | 
																	</div>
 | 
				
			||||||
 | 
																	<div class="required field">
 | 
				
			||||||
 | 
																		<label for="new_branch_name">{{.i18n.Tr "repo.release.tag_name"}}</label>
 | 
				
			||||||
 | 
																		<input id="new_branch_name" name="new_branch_name" required>
 | 
				
			||||||
 | 
																	</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
																	<div class="text right actions">
 | 
				
			||||||
 | 
																		<div class="ui cancel button">{{.i18n.Tr "settings.cancel"}}</div>
 | 
				
			||||||
 | 
																		<button class="ui green button">{{.i18n.Tr "repo.tag.confirm_create_tag"}}</button>
 | 
				
			||||||
 | 
																	</div>
 | 
				
			||||||
 | 
																</form>
 | 
				
			||||||
 | 
															</div>
 | 
				
			||||||
 | 
														</div>
 | 
				
			||||||
 | 
													</div>
 | 
				
			||||||
 | 
												</div>
 | 
				
			||||||
 | 
											{{end}}
 | 
				
			||||||
 | 
										</div>
 | 
				
			||||||
 | 
									{{end}}
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
			{{if IsMultilineCommitMessage .Commit.Message}}
 | 
								{{if IsMultilineCommitMessage .Commit.Message}}
 | 
				
			||||||
				<pre class="commit-body">{{RenderCommitBody $.Context .Commit.Message $.RepoLink $.Repository.ComposeMetas}}</pre>
 | 
									<pre class="commit-body mt-0">{{RenderCommitBody $.Context .Commit.Message $.RepoLink $.Repository.ComposeMetas}}</pre>
 | 
				
			||||||
			{{end}}
 | 
								{{end}}
 | 
				
			||||||
			{{if .BranchName}}
 | 
								{{if .BranchName}}
 | 
				
			||||||
				<span class="text grey mr-3">{{svg "octicon-git-branch" 16 "mr-2"}}{{.BranchName}}</span>
 | 
									<span class="text grey mr-3">{{svg "octicon-git-branch" 16 "mr-2"}}{{.BranchName}}</span>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										32
									
								
								templates/repo/editor/cherry_pick.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								templates/repo/editor/cherry_pick.tmpl
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					{{template "base/head" .}}
 | 
				
			||||||
 | 
					<div class="page-content repository file editor edit">
 | 
				
			||||||
 | 
						{{template "repo/header" .}}
 | 
				
			||||||
 | 
						<div class="ui container">
 | 
				
			||||||
 | 
							{{template "base/alert" .}}
 | 
				
			||||||
 | 
							<form class="ui edit form" method="post" action="{{.RepoLink}}/_cherrypick/{{.SHA}}/{{.BranchName  | PathEscapeSegments}}">
 | 
				
			||||||
 | 
								{{.CsrfTokenHtml}}
 | 
				
			||||||
 | 
								<input type="hidden" name="last_commit" value="{{.last_commit}}">
 | 
				
			||||||
 | 
								<input type="hidden" name="page_has_posted" value="true">
 | 
				
			||||||
 | 
								<input type="hidden" name="revert" value="{{if eq .CherryPickType "revert"}}true{{else}}false{{end}}">
 | 
				
			||||||
 | 
								<div class="ui secondary menu">
 | 
				
			||||||
 | 
									<div class="fitted item treepath">
 | 
				
			||||||
 | 
										<div class="ui breadcrumb field {{if .Err_TreePath}}error{{end}}">
 | 
				
			||||||
 | 
											{{$shaurl := printf "%s/commit/%s" $.RepoLink (PathEscape .SHA)}}
 | 
				
			||||||
 | 
											{{$shalink := printf "<a class=\"ui blue sha label\" href=\"%s\">%s</a>" (Escape $shaurl) (ShortSha .SHA)}}
 | 
				
			||||||
 | 
											{{if eq .CherryPickType "revert"}}
 | 
				
			||||||
 | 
												{{.i18n.Tr "repo.editor.revert" $shalink | Str2html}}
 | 
				
			||||||
 | 
											{{else}}
 | 
				
			||||||
 | 
												{{.i18n.Tr "repo.editor.cherry_pick" $shalink | Str2html}}
 | 
				
			||||||
 | 
											{{end}}
 | 
				
			||||||
 | 
											<a class="section" href="{{$.RepoLink}}">{{.Repository.FullName}}</a>
 | 
				
			||||||
 | 
											<div class="divider">:</div>
 | 
				
			||||||
 | 
											<a class="section" href="{{$.BranchLink}}">{{.BranchName}}</a>
 | 
				
			||||||
 | 
											<span>{{.i18n.Tr "repo.editor.or"}} <a href="{{$shaurl}}">{{.i18n.Tr "repo.editor.cancel_lower"}}</a></span>
 | 
				
			||||||
 | 
										</div>
 | 
				
			||||||
 | 
									</div>
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
 | 
								{{template "repo/editor/commit_form" .}}
 | 
				
			||||||
 | 
							</form>
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					{{template "base/footer" .}}
 | 
				
			||||||
							
								
								
									
										59
									
								
								templates/repo/editor/patch.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								templates/repo/editor/patch.tmpl
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,59 @@
 | 
				
			|||||||
 | 
					{{template "base/head" .}}
 | 
				
			||||||
 | 
					<div class="page-content repository file editor edit">
 | 
				
			||||||
 | 
						{{template "repo/header" .}}
 | 
				
			||||||
 | 
						<div class="ui container">
 | 
				
			||||||
 | 
							{{template "base/alert" .}}
 | 
				
			||||||
 | 
							<form class="ui edit form" method="post" action="{{.RepoLink}}/_diffpatch/{{.BranchName  | PathEscapeSegments}}">
 | 
				
			||||||
 | 
								{{.CsrfTokenHtml}}
 | 
				
			||||||
 | 
								<input type="hidden" name="last_commit" value="{{.last_commit}}">
 | 
				
			||||||
 | 
								<input type="hidden" name="page_has_posted" value="{{.PageHasPosted}}">
 | 
				
			||||||
 | 
								<div class="ui secondary menu">
 | 
				
			||||||
 | 
									<div class="fitted item treepath">
 | 
				
			||||||
 | 
										<div class="ui breadcrumb field {{if .Err_TreePath}}error{{end}}">
 | 
				
			||||||
 | 
											{{.i18n.Tr "repo.editor.patching"}}
 | 
				
			||||||
 | 
											<a class="section" href="{{$.RepoLink}}">{{.Repository.FullName}}</a>
 | 
				
			||||||
 | 
											<div class="divider">:</div>
 | 
				
			||||||
 | 
											<a class="section" href="{{$.BranchLink}}">{{.BranchName}}</a>
 | 
				
			||||||
 | 
											<span>{{.i18n.Tr "repo.editor.or"}} <a href="{{$.BranchLink}}">{{.i18n.Tr "repo.editor.cancel_lower"}}</a></span>
 | 
				
			||||||
 | 
											<input type="hidden" id="tree_path" name="tree_path" value="patch" required>
 | 
				
			||||||
 | 
											<input id="file-name" type="hidden" value="diff.patch">
 | 
				
			||||||
 | 
										</div>
 | 
				
			||||||
 | 
									</div>
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
 | 
								<div class="field">
 | 
				
			||||||
 | 
									<div class="ui top attached tabular menu" data-write="write">
 | 
				
			||||||
 | 
										<a class="active item" data-tab="write">{{svg "octicon-code" 16 "mr-2"}}{{.i18n.Tr "repo.editor.new_patch"}}</a>
 | 
				
			||||||
 | 
									</div>
 | 
				
			||||||
 | 
									<div class="ui bottom attached active tab segment" data-tab="write">
 | 
				
			||||||
 | 
										<textarea id="edit_area" name="content" class="hide" data-id="repo-{{.Repository.Name}}-patch"
 | 
				
			||||||
 | 
											data-context="{{.RepoLink}}"
 | 
				
			||||||
 | 
											data-line-wrap-extensions="{{.LineWrapExtensions}}">
 | 
				
			||||||
 | 
					{{.FileContent}}</textarea>
 | 
				
			||||||
 | 
										<div class="editor-loading is-loading"></div>
 | 
				
			||||||
 | 
									</div>
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
 | 
								{{template "repo/editor/commit_form" .}}
 | 
				
			||||||
 | 
							</form>
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<div class="ui small basic modal" id="edit-empty-content-modal">
 | 
				
			||||||
 | 
							<div class="ui icon header">
 | 
				
			||||||
 | 
								<i class="file icon"></i>
 | 
				
			||||||
 | 
								{{.i18n.Tr "repo.editor.commit_empty_file_header"}}
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
							<div class="center content">
 | 
				
			||||||
 | 
								<p>{{.i18n.Tr "repo.editor.commit_empty_file_text"}}</p>
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
							<div class="actions">
 | 
				
			||||||
 | 
								<div class="ui red basic cancel inverted button">
 | 
				
			||||||
 | 
									<i class="remove icon"></i>
 | 
				
			||||||
 | 
									{{.i18n.Tr "repo.editor.cancel"}}
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
 | 
								<div class="ui green basic ok inverted button">
 | 
				
			||||||
 | 
									<i class="save icon"></i>
 | 
				
			||||||
 | 
									{{.i18n.Tr "repo.editor.commit_changes"}}
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					{{template "base/footer" .}}
 | 
				
			||||||
@@ -89,6 +89,11 @@
 | 
				
			|||||||
								{{.i18n.Tr "repo.editor.upload_file"}}
 | 
													{{.i18n.Tr "repo.editor.upload_file"}}
 | 
				
			||||||
							</a>
 | 
												</a>
 | 
				
			||||||
						{{end}}
 | 
											{{end}}
 | 
				
			||||||
 | 
											{{if .CanAddFile}}
 | 
				
			||||||
 | 
												<a href="{{.RepoLink}}/_diffpatch/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}" class="ui button">
 | 
				
			||||||
 | 
													{{.i18n.Tr "repo.editor.patch"}}
 | 
				
			||||||
 | 
												</a>
 | 
				
			||||||
 | 
											{{end}}
 | 
				
			||||||
					{{end}}
 | 
										{{end}}
 | 
				
			||||||
					{{if and (ne $n 0) (not .IsViewFile) (not .IsBlame) }}
 | 
										{{if and (ne $n 0) (not .IsViewFile) (not .IsBlame) }}
 | 
				
			||||||
						<a href="{{.RepoLink}}/commits/{{.BranchNameSubURL}}/{{.TreePath | PathEscapeSegments}}" class="ui button">
 | 
											<a href="{{.RepoLink}}/commits/{{.BranchNameSubURL}}/{{.TreePath | PathEscapeSegments}}" class="ui button">
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3383,6 +3383,50 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "/repos/{owner}/{repo}/diffpatch": {
 | 
				
			||||||
 | 
					      "post": {
 | 
				
			||||||
 | 
					        "consumes": [
 | 
				
			||||||
 | 
					          "application/json"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "produces": [
 | 
				
			||||||
 | 
					          "application/json"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "tags": [
 | 
				
			||||||
 | 
					          "repository"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "summary": "Apply diff patch to repository",
 | 
				
			||||||
 | 
					        "operationId": "repoApplyDiffPatch",
 | 
				
			||||||
 | 
					        "parameters": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "description": "owner of the repo",
 | 
				
			||||||
 | 
					            "name": "owner",
 | 
				
			||||||
 | 
					            "in": "path",
 | 
				
			||||||
 | 
					            "required": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "description": "name of the repo",
 | 
				
			||||||
 | 
					            "name": "repo",
 | 
				
			||||||
 | 
					            "in": "path",
 | 
				
			||||||
 | 
					            "required": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "name": "body",
 | 
				
			||||||
 | 
					            "in": "body",
 | 
				
			||||||
 | 
					            "required": true,
 | 
				
			||||||
 | 
					            "schema": {
 | 
				
			||||||
 | 
					              "$ref": "#/definitions/UpdateFileOptions"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "responses": {
 | 
				
			||||||
 | 
					          "200": {
 | 
				
			||||||
 | 
					            "$ref": "#/responses/FileResponse"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "/repos/{owner}/{repo}/editorconfig/{filepath}": {
 | 
					    "/repos/{owner}/{repo}/editorconfig/{filepath}": {
 | 
				
			||||||
      "get": {
 | 
					      "get": {
 | 
				
			||||||
        "produces": [
 | 
					        "produces": [
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,11 +10,22 @@ export function initRepoBranchTagDropdown(selector) {
 | 
				
			|||||||
      items: [],
 | 
					      items: [],
 | 
				
			||||||
      mode: $data.data('mode'),
 | 
					      mode: $data.data('mode'),
 | 
				
			||||||
      searchTerm: '',
 | 
					      searchTerm: '',
 | 
				
			||||||
 | 
					      refName: '',
 | 
				
			||||||
      noResults: '',
 | 
					      noResults: '',
 | 
				
			||||||
      canCreateBranch: false,
 | 
					      canCreateBranch: false,
 | 
				
			||||||
      menuVisible: false,
 | 
					      menuVisible: false,
 | 
				
			||||||
      createTag: false,
 | 
					      createTag: false,
 | 
				
			||||||
      active: 0
 | 
					      isViewTag: false,
 | 
				
			||||||
 | 
					      isViewBranch: false,
 | 
				
			||||||
 | 
					      isViewTree: false,
 | 
				
			||||||
 | 
					      active: 0,
 | 
				
			||||||
 | 
					      branchForm: '',
 | 
				
			||||||
 | 
					      branchURLPrefix: '',
 | 
				
			||||||
 | 
					      branchURLSuffix: '',
 | 
				
			||||||
 | 
					      tagURLPrefix: '',
 | 
				
			||||||
 | 
					      tagURLSuffix: '',
 | 
				
			||||||
 | 
					      setAction: false,
 | 
				
			||||||
 | 
					      submitForm: false,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    $data.find('.item').each(function () {
 | 
					    $data.find('.item').each(function () {
 | 
				
			||||||
      data.items.push({
 | 
					      data.items.push({
 | 
				
			||||||
@@ -64,6 +75,26 @@ export function initRepoBranchTagDropdown(selector) {
 | 
				
			|||||||
      beforeMount() {
 | 
					      beforeMount() {
 | 
				
			||||||
        this.noResults = this.$el.getAttribute('data-no-results');
 | 
					        this.noResults = this.$el.getAttribute('data-no-results');
 | 
				
			||||||
        this.canCreateBranch = this.$el.getAttribute('data-can-create-branch') === 'true';
 | 
					        this.canCreateBranch = this.$el.getAttribute('data-can-create-branch') === 'true';
 | 
				
			||||||
 | 
					        this.branchForm = this.$el.getAttribute('data-branch-form');
 | 
				
			||||||
 | 
					        switch (this.$el.getAttribute('data-view-type')) {
 | 
				
			||||||
 | 
					          case 'tree':
 | 
				
			||||||
 | 
					            this.isViewTree = true;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					          case 'tag':
 | 
				
			||||||
 | 
					            this.isViewTag = true;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					          default:
 | 
				
			||||||
 | 
					            this.isViewBranch = true;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.refName = this.$el.getAttribute('data-ref-name');
 | 
				
			||||||
 | 
					        this.branchURLPrefix = this.$el.getAttribute('data-branch-url-prefix');
 | 
				
			||||||
 | 
					        this.branchURLSuffix = this.$el.getAttribute('data-branch-url-suffix');
 | 
				
			||||||
 | 
					        this.tagURLPrefix = this.$el.getAttribute('data-tag-url-prefix');
 | 
				
			||||||
 | 
					        this.tagURLSuffix = this.$el.getAttribute('data-tag-url-suffix');
 | 
				
			||||||
 | 
					        this.setAction = this.$el.getAttribute('data-set-action') === 'true';
 | 
				
			||||||
 | 
					        this.submitForm = this.$el.getAttribute('data-submit-form') === 'true';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        document.body.addEventListener('click', (event) => {
 | 
					        document.body.addEventListener('click', (event) => {
 | 
				
			||||||
          if (this.$el.contains(event.target)) return;
 | 
					          if (this.$el.contains(event.target)) return;
 | 
				
			||||||
@@ -80,7 +111,32 @@ export function initRepoBranchTagDropdown(selector) {
 | 
				
			|||||||
            prev.selected = false;
 | 
					            prev.selected = false;
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          item.selected = true;
 | 
					          item.selected = true;
 | 
				
			||||||
          window.location.href = item.url;
 | 
					          const url = (item.tag) ? this.tagURLPrefix + item.url + this.tagURLSuffix : this.branchURLPrefix + item.url + this.branchURLSuffix;
 | 
				
			||||||
 | 
					          if (this.branchForm === '') {
 | 
				
			||||||
 | 
					            window.location.href = url;
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            this.isViewTree = false;
 | 
				
			||||||
 | 
					            this.isViewTag = false;
 | 
				
			||||||
 | 
					            this.isViewBranch = false;
 | 
				
			||||||
 | 
					            this.$refs.dropdownRefName.textContent = item.name;
 | 
				
			||||||
 | 
					            if (this.setAction) {
 | 
				
			||||||
 | 
					              $(`#${this.branchForm}`).attr('action', url);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              $(`#${this.branchForm} input[name="refURL"]`).val(url);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            $(`#${this.branchForm} input[name="ref"]`).val(item.name);
 | 
				
			||||||
 | 
					            if (item.tag) {
 | 
				
			||||||
 | 
					              this.isViewTag = true;
 | 
				
			||||||
 | 
					              $(`#${this.branchForm} input[name="refType"]`).val('tag');
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              this.isViewBranch = true;
 | 
				
			||||||
 | 
					              $(`#${this.branchForm} input[name="refType"]`).val('branch');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (this.submitForm) {
 | 
				
			||||||
 | 
					              $(`#${this.branchForm}`).trigger('submit');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            Vue.set(this, 'menuVisible', false);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        createNewBranch() {
 | 
					        createNewBranch() {
 | 
				
			||||||
          if (!this.showCreateNewBranch) return;
 | 
					          if (!this.showCreateNewBranch) return;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -313,9 +313,22 @@ export function initGlobalButtons() {
 | 
				
			|||||||
    alert('Nothing to hide');
 | 
					    alert('Nothing to hide');
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $('.show-modal.button').on('click', function () {
 | 
					  $('.show-modal').on('click', function () {
 | 
				
			||||||
    $($(this).data('modal')).modal('show');
 | 
					    const modalDiv = $($(this).attr('data-modal'));
 | 
				
			||||||
    const colorPickers = $($(this).data('modal')).find('.color-picker');
 | 
					    for (const attrib of this.attributes) {
 | 
				
			||||||
 | 
					      if (!attrib.name.startsWith('data-modal-')) {
 | 
				
			||||||
 | 
					        continue;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      const id = attrib.name.substring(11);
 | 
				
			||||||
 | 
					      const target = modalDiv.find(`#${id}`);
 | 
				
			||||||
 | 
					      if (target.is('input')) {
 | 
				
			||||||
 | 
					        target.val(attrib.value);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        target.text(attrib.value);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    modalDiv.modal('show');
 | 
				
			||||||
 | 
					    const colorPickers = $($(this).attr('data-modal')).find('.color-picker');
 | 
				
			||||||
    if (colorPickers.length > 0) {
 | 
					    if (colorPickers.length > 0) {
 | 
				
			||||||
      initCompColorPicker();
 | 
					      initCompColorPicker();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -323,10 +336,10 @@ export function initGlobalButtons() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  $('.delete-post.button').on('click', function () {
 | 
					  $('.delete-post.button').on('click', function () {
 | 
				
			||||||
    const $this = $(this);
 | 
					    const $this = $(this);
 | 
				
			||||||
    $.post($this.data('request-url'), {
 | 
					    $.post($this.attr('data-request-url'), {
 | 
				
			||||||
      _csrf: csrfToken
 | 
					      _csrf: csrfToken
 | 
				
			||||||
    }).done(() => {
 | 
					    }).done(() => {
 | 
				
			||||||
      window.location.href = $this.data('done-url');
 | 
					      window.location.href = $this.attr('data-done-url');
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,18 @@
 | 
				
			|||||||
import $ from 'jquery';
 | 
					import $ from 'jquery';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function initRepoBranchButton() {
 | 
					export function initRepoBranchButton() {
 | 
				
			||||||
  $('.show-create-branch-modal.button').on('click', function () {
 | 
					  $('.show-create-branch-modal').on('click', function () {
 | 
				
			||||||
    $('#create-branch-form')[0].action = $('#create-branch-form').data('base-action') + $(this).data('branch-from-urlcomponent');
 | 
					    let modalFormName = $(this).attr('data-modal-form');
 | 
				
			||||||
    $('#modal-create-branch-from-span').text($(this).data('branch-from'));
 | 
					    if (!modalFormName) {
 | 
				
			||||||
    $($(this).data('modal')).modal('show');
 | 
					      modalFormName = '#create-branch-form';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    $(modalFormName)[0].action = $(modalFormName).attr('data-base-action') + $(this).attr('data-branch-from-urlcomponent');
 | 
				
			||||||
 | 
					    let fromSpanName = $(this).attr('data-modal-from-span');
 | 
				
			||||||
 | 
					    if (!fromSpanName) {
 | 
				
			||||||
 | 
					      fromSpanName = '#modal-create-branch-from-span';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $(fromSpanName).text($(this).attr('data-branch-from'));
 | 
				
			||||||
 | 
					    $($(this).attr('data-modal')).modal('show');
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -436,7 +436,7 @@ export function initRepository() {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // File list and commits
 | 
					  // File list and commits
 | 
				
			||||||
  if ($('.repository.file.list').length > 0 ||
 | 
					  if ($('.repository.file.list').length > 0 || $('.branch-dropdown').length > 0 ||
 | 
				
			||||||
    $('.repository.commits').length > 0 || $('.repository.release').length > 0) {
 | 
					    $('.repository.commits').length > 0 || $('.repository.release').length > 0) {
 | 
				
			||||||
    initRepoBranchTagDropdown('.choose.reference .dropdown');
 | 
					    initRepoBranchTagDropdown('.choose.reference .dropdown');
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2551,12 +2551,6 @@
 | 
				
			|||||||
  padding-top: 15px;
 | 
					  padding-top: 15px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.browse-button {
 | 
					 | 
				
			||||||
  position: absolute;
 | 
					 | 
				
			||||||
  right: 1rem;
 | 
					 | 
				
			||||||
  top: .75rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.commit-header-row {
 | 
					.commit-header-row {
 | 
				
			||||||
  min-height: 50px !important;
 | 
					  min-height: 50px !important;
 | 
				
			||||||
  padding-top: 0 !important;
 | 
					  padding-top: 0 !important;
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user