Changes
23 changed files (+1211/-57)
-
-
@@ -32,6 +32,21 @@ func GetRawDiff(ctx context.Context, repoPath, commitID string, diffType RawDiffreturn 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. func GetRawDiffForFile(ctx context.Context, repoPath, startCommit, endCommit string, diffType RawDiffType, file string, writer io.Writer) error { 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 != nil { if err := scanner.Err(); err != nil { return "", err }
-
-
-
@@ -50,6 +50,14 @@ type UpdateFileOptions struct {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 type FileLinksResponse struct { Self *string `json:"self"`
-
-
-
@@ -1075,6 +1075,10 @@ editor.add_tmpl = Add '<filename>'editor.add = Add '%s' editor.update = Update '%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.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.
-
@@ -1110,6 +1114,8 @@ editor.cannot_commit_to_protected_branch = Cannot commit to protected branch '%seditor.no_commit_to_branch = Unable to commit directly to branch because: editor.user_no_push_to_branch = User cannot push to branch 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.commits = Commits
-
@@ -1130,6 +1136,14 @@ commits.signed_by_untrusted_user_unmatched = Signed by untrusted user who does ncommits.gpg_key_id = GPG Key ID 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.desc = Link to an external issue tracker.
-
@@ -2215,11 +2229,16 @@ branch.included_desc = This branch is part of the default branchbranch.included = Included branch.create_new_branch = Create branch from branch: branch.confirm_create_branch = Create branch branch.create_branch_operation = Create branch branch.new_branch = Create new branch branch.new_branch_from = Create new branch from '%s' branch.renamed = Branch %s was renamed to %s. 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. 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("/notes/{sha}", repo.GetNote) }, reqRepoReader(unit.TypeCode)) m.Post("/diffpatch", reqRepoWriter(unit.TypeCode), reqToken(), bind(api.ApplyDiffPatchFileOptions{}), repo.ApplyDiffPatch) m.Group("/contents", func() { m.Get("", repo.GetContentsList) m.Get("/*", repo.GetContents)
-
-
-
@@ -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) } }
-
-
-
@@ -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)) } }
-
-
-
@@ -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). Get(repo.UploadFile). 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) m.Group("", func() { m.Post("/upload-file", repo.UploadFileToServer)
-
@@ -1029,6 +1033,7 @@ func RegisterRoutes(m *web.Route) {m.Group("", func() { m.Get("/graph", repo.Graph) 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) m.Group("/src", func() {
-
-
-
@@ -754,6 +754,30 @@ func (f *EditPreviewDiffForm) Validate(req *http.Request, errs binding.Errors) breturn 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) // 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 }
-
@@ -217,19 +217,20 @@ func attemptMerge(ctx context.Context, file *unmergedFile, tmpBasePath string, greturn nil } func checkConflicts(pr *models.PullRequest, gitRepo *git.Repository, tmpBasePath string) (bool, error) { 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)) defer finished() // 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 func AttemptThreeWayMerge(ctx context.Context, gitPath string, gitRepo *git.Repository, base, ours, theirs, description string) (bool, []string, error) { ctx, cancel := context.WithCancel(ctx) defer cancel() // 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) 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 unmerged := make(chan *unmergedFile) go unmergedFiles(ctx, tmpBasePath, unmerged) go unmergedFiles(ctx, gitPath, unmerged) defer func() { cancel()
-
@@ -239,8 +240,8 @@ func checkConflicts(pr *models.PullRequest, gitRepo *git.Repository, tmpBasePath}() numberOfConflicts := 0 pr.ConflictedFiles = make([]string, 0, 5) conflict := false conflictedFiles := make([]string, 0, 5) for file := range unmerged { if file == nil {
-
@@ -248,23 +249,33 @@ func checkConflicts(pr *models.PullRequest, gitRepo *git.Repository, tmpBasePath} if file.err != nil { cancel() return false, file.err return false, nil, file.err } // 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 { 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 if numberOfConflicts < 10 { pr.ConflictedFiles = append(pr.ConflictedFiles, conflictErr.filename) conflictedFiles = append(conflictedFiles, conflictErr.filename) } numberOfConflicts++ 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 { treeHash, err := git.NewCommand(ctx, "write-tree").RunInDir(tmpBasePath)
-
-
-
@@ -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 }
-
-
-
@@ -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}} {{$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}} <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"> <span class="text"> {{if $release}} {{.root.i18n.Tr "repo.release.compare"}} {{else}} {{if .root.IsViewTag}}{{svg "octicon-tag"}}{{else}}{{svg "octicon-git-branch"}}{{end}} {{if .root.IsViewBranch}}{{.root.i18n.Tr "repo.branch"}}{{else if .root.IsViewTag}}{{.root.i18n.Tr "repo.tag"}}{{else}}{{.root.i18n.Tr "repo.tree"}}{{end}}: <strong>{{if .root.IsViewBranch}}{{.root.BranchName}}{{else if .root.IsViewTag}}{{.root.TagName}}{{else}}{{ShortSha .root.CommitID}}{{end}}</strong> <span :class="{visible: isViewTag}" v-if="isViewTag" v-cloak>{{svg "octicon-tag"}} {{.root.i18n.Tr "repo.tag"}}:</span> <span :class="{visible: isViewBranch}" v-if="isViewBranch" v-cloak>{{svg "octicon-git-branch"}} {{.root.i18n.Tr "repo.branch"}}:</span> <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}} </span> {{svg "octicon-triangle-down" 14 "dropdown icon"}} </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}} {{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}} {{range .root.Tags}} {{if $release}} <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> {{else}} <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> {{if (not .noTag)}} {{range .root.Tags}} {{if $release}} <div class="item tag {{if eq $release.TagName .}}selected{{end}}" data-url="{{PathEscapeSegments .}}">{{.}}</div> {{else}} <div class="item tag {{if eq $defaultBranch .}}selected{{end}}" data-url="{{PathEscapeSegments .}}">{{.}}</div> {{end}} {{end}} {{end}} </div> <div class="menu transition" :class="{visible: menuVisible}" v-if="menuVisible" v-cloak> <div class="ui icon search input"> <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> {{if $showBranchesInDropdown}} <div class="header branch-tag-choice">
-
@@ -42,11 +56,13 @@{{svg "octicon-git-branch" 16 "mr-2"}}{{.root.i18n.Tr "repo.branches"}} </span> </a> <a class="reference column" href="#" @click="createTag = true; mode = 'tags'; focusSearchField()"> <span class="text" :class="{black: mode == 'tags'}"> {{svg "octicon-tag" 16 "mr-2"}}{{.root.i18n.Tr "repo.tags"}} </span> </a> {{if not .noTag}} <a class="reference column" href="#" @click="createTag = true; mode = 'tags'; focusSearchField()"> <span class="text" :class="{black: mode == 'tags'}"> {{svg "octicon-tag" 16 "mr-2"}}{{.root.i18n.Tr "repo.tags"}} </span> </a> {{end}} </div> </div> </div>
-
-
-
@@ -18,14 +18,123 @@{{end}} {{end}} <div class="ui top attached header clearing segment pr {{$class}}"> {{if not $.PageIsWiki}} <a class="ui blue tiny button browse-button" href="{{.SourcePath}}"> {{.i18n.Tr "repo.diff.browse_source"}} </a> {{end}} <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> <div class="df mb-4"> <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> {{if not $.PageIsWiki}} <div class="ui"> <a class="ui blue tiny button" href="{{.SourcePath}}"> {{.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}} <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}} {{if .BranchName}} <span class="text grey mr-3">{{svg "octicon-git-branch" 16 "mr-2"}}{{.BranchName}}</span>
-
-
-
@@ -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" .}}
-
-
-
@@ -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"}} </a> {{end}} {{if .CanAddFile}} <a href="{{.RepoLink}}/_diffpatch/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}" class="ui button"> {{.i18n.Tr "repo.editor.patch"}} </a> {{end}} {{end}} {{if and (ne $n 0) (not .IsViewFile) (not .IsBlame) }} <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}": { "get": { "produces": [
-
-
-
@@ -10,11 +10,22 @@ export function initRepoBranchTagDropdown(selector) {items: [], mode: $data.data('mode'), searchTerm: '', refName: '', noResults: '', canCreateBranch: false, menuVisible: 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.items.push({
-
@@ -64,6 +75,26 @@ export function initRepoBranchTagDropdown(selector) {beforeMount() { this.noResults = this.$el.getAttribute('data-no-results'); 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) => { if (this.$el.contains(event.target)) return;
-
@@ -80,7 +111,32 @@ export function initRepoBranchTagDropdown(selector) {prev.selected = false; } 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() { if (!this.showCreateNewBranch) return;
-
-
-
@@ -313,9 +313,22 @@ export function initGlobalButtons() {alert('Nothing to hide'); }); $('.show-modal.button').on('click', function () { $($(this).data('modal')).modal('show'); const colorPickers = $($(this).data('modal')).find('.color-picker'); $('.show-modal').on('click', function () { const modalDiv = $($(this).attr('data-modal')); 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) { initCompColorPicker(); }
-
@@ -323,10 +336,10 @@ export function initGlobalButtons() {$('.delete-post.button').on('click', function () { const $this = $(this); $.post($this.data('request-url'), { $.post($this.attr('data-request-url'), { _csrf: csrfToken }).done(() => { window.location.href = $this.data('done-url'); window.location.href = $this.attr('data-done-url'); }); }); }
-
-
-
@@ -1,9 +1,18 @@import $ from 'jquery'; export function initRepoBranchButton() { $('.show-create-branch-modal.button').on('click', function () { $('#create-branch-form')[0].action = $('#create-branch-form').data('base-action') + $(this).data('branch-from-urlcomponent'); $('#modal-create-branch-from-span').text($(this).data('branch-from')); $($(this).data('modal')).modal('show'); $('.show-create-branch-modal').on('click', function () { let modalFormName = $(this).attr('data-modal-form'); if (!modalFormName) { 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 if ($('.repository.file.list').length > 0 || if ($('.repository.file.list').length > 0 || $('.branch-dropdown').length > 0 || $('.repository.commits').length > 0 || $('.repository.release').length > 0) { initRepoBranchTagDropdown('.choose.reference .dropdown'); }
-
-
-
@@ -2551,12 +2551,6 @@padding-top: 15px; } .browse-button { position: absolute; right: 1rem; top: .75rem; } .commit-header-row { min-height: 50px !important; padding-top: 0 !important;
-