Improvements for creating branch model

This commit is contained in:
Lunny Xiao 2024-08-21 19:08:18 -07:00
parent 62fda252bd
commit 2361ec5a44
No known key found for this signature in database
GPG Key ID: C3B7C91B632F738A
11 changed files with 149 additions and 53 deletions

View File

@ -31,6 +31,7 @@ type IssueDevLink struct {
LinkedRepo *repo_model.Repository `xorm:"-"` LinkedRepo *repo_model.Repository `xorm:"-"`
PullRequest *PullRequest `xorm:"-"` PullRequest *PullRequest `xorm:"-"`
Branch *git_model.Branch `xorm:"-"` Branch *git_model.Branch `xorm:"-"`
DisplayBranch bool `xorm:"-"`
} }
func init() { func init() {

View File

@ -14,6 +14,7 @@ func CreateTableIssueDevLink(x *xorm.Engine) error {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
IssueID int64 `xorm:"INDEX"` IssueID int64 `xorm:"INDEX"`
LinkType int LinkType int
LinkedRepoID int64 `xorm:"INDEX"` // it can link to self repo or other repo
LinkIndex string // branch name, pull request number or commit sha LinkIndex string // branch name, pull request number or commit sha
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
} }

View File

@ -1623,6 +1623,8 @@ issues.label.filter_sort.reverse_alphabetically = Reverse alphabetically
issues.label.filter_sort.by_size = Smallest size issues.label.filter_sort.by_size = Smallest size
issues.label.filter_sort.reverse_by_size = Largest size issues.label.filter_sort.reverse_by_size = Largest size
issues.development = Development issues.development = Development
issues.maybefixed = May be fixed by %s
issues.create_branch_from_issue_success = Create branch %s from issue successfully
issues.num_participants = %d Participants issues.num_participants = %d Participants
issues.attachment.open_tab = `Click to see "%s" in a new tab` issues.attachment.open_tab = `Click to see "%s" in a new tab`
issues.attachment.download = `Click to download "%s"` issues.attachment.download = `Click to download "%s"`

View File

@ -2085,10 +2085,18 @@ func ViewIssue(ctx *context.Context) {
devLinks, err := issue_service.FindIssueDevLinksByIssue(ctx, issue) devLinks, err := issue_service.FindIssueDevLinksByIssue(ctx, issue)
if err != nil { if err != nil {
ctx.ServerError("FindIssueDevLinksByIssueID", err) ctx.ServerError("FindIssueDevLinksByIssue", err)
return return
} }
ctx.Data["DevLinks"] = devLinks ctx.Data["DevLinks"] = devLinks
for _, link := range devLinks {
if link.LinkType == issues_model.IssueDevLinkTypePullRequest {
if !(link.PullRequest.Issue.IsClosed && !link.PullRequest.HasMerged) {
ctx.Data["MaybeFixed"] = link.PullRequest
break
}
}
}
ctx.HTML(http.StatusOK, tplIssueView) ctx.HTML(http.StatusOK, tplIssueView)
} }

View File

@ -4,10 +4,15 @@
package repo package repo
import ( import (
"net/http" git_model "code.gitea.io/gitea/models/git"
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
unit_model "code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/utils"
"code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/forms"
repo_service "code.gitea.io/gitea/services/repository" repo_service "code.gitea.io/gitea/services/repository"
@ -21,30 +26,78 @@ func CreateBranchFromIssue(ctx *context.Context) {
if issue.IsPull { if issue.IsPull {
ctx.Flash.Error(ctx.Tr("repo.issues.create_branch_from_issue_error_is_pull")) ctx.Flash.Error(ctx.Tr("repo.issues.create_branch_from_issue_error_is_pull"))
ctx.Redirect(issue.Link(), http.StatusSeeOther) ctx.JSONRedirect(issue.Link())
return return
} }
form := web.GetForm(ctx).(*forms.NewBranchForm) form := web.GetForm(ctx).(*forms.NewBranchForm)
if !ctx.Repo.CanCreateBranch() { repo := ctx.Repo.Repository
gitRepo := ctx.Repo.GitRepo
if form.RepoID > 0 {
var err error
repo, err = repo_model.GetRepositoryByID(ctx, form.RepoID)
if err != nil {
ctx.ServerError("GetRepositoryByID", err)
return
}
gitRepo, err = gitrepo.OpenRepository(ctx, repo)
if err != nil {
ctx.ServerError("GetRepositoryByID", err)
return
}
defer gitRepo.Close()
}
perm, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
if err != nil {
ctx.ServerError("GetRepositoryByID", err)
return
}
canCreateBranch := perm.CanWrite(unit_model.TypeCode) && repo.CanCreateBranch()
if !canCreateBranch {
ctx.NotFound("CreateBranch", nil) ctx.NotFound("CreateBranch", nil)
return return
} }
if ctx.HasError() { if ctx.HasError() {
ctx.Flash.Error(ctx.GetErrMsg()) ctx.JSONError(ctx.GetErrMsg())
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
return return
} }
if err := repo_service.CreateNewBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, form.SourceBranchName, form.NewBranchName); err != nil { if err := repo_service.CreateNewBranch(ctx, ctx.Doer, repo, gitRepo, form.SourceBranchName, form.NewBranchName); err != nil {
handleCreateBranchError(ctx, err, form) switch {
case git_model.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err):
ctx.JSONError(ctx.Tr("repo.branch.branch_already_exists", form.NewBranchName))
case git_model.IsErrBranchNameConflict(err):
e := err.(git_model.ErrBranchNameConflict)
ctx.JSONError(ctx.Tr("repo.branch.branch_name_conflict", form.NewBranchName, e.BranchName))
case git.IsErrPushRejected(err):
e := err.(*git.ErrPushRejected)
if len(e.Message) == 0 {
ctx.Flash.Error(ctx.Tr("repo.editor.push_rejected_no_message"))
} else {
flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
"Message": ctx.Tr("repo.editor.push_rejected"),
"Summary": ctx.Tr("repo.editor.push_rejected_summary"),
"Details": utils.SanitizeFlashErrorString(e.Message),
})
if err != nil {
ctx.ServerError("UpdatePullRequest.HTMLString", err)
return
}
ctx.JSONError(flashError)
}
default:
ctx.ServerError("CreateNewBranch", err)
}
return return
} }
if err := issues_model.CreateIssueDevLink(ctx, &issues_model.IssueDevLink{ if err := issues_model.CreateIssueDevLink(ctx, &issues_model.IssueDevLink{
IssueID: issue.ID, IssueID: issue.ID,
LinkType: issues_model.IssueDevLinkTypeBranch, LinkType: issues_model.IssueDevLinkTypeBranch,
LinkedRepoID: repo.ID,
LinkIndex: form.NewBranchName, LinkIndex: form.NewBranchName,
}); err != nil { }); err != nil {
ctx.ServerError("CreateIssueDevLink", err) ctx.ServerError("CreateIssueDevLink", err)
@ -52,5 +105,5 @@ func CreateBranchFromIssue(ctx *context.Context) {
} }
ctx.Flash.Success(ctx.Tr("repo.issues.create_branch_from_issue_success", ctx.Repo.BranchName)) ctx.Flash.Success(ctx.Tr("repo.issues.create_branch_from_issue_success", ctx.Repo.BranchName))
ctx.Redirect(issue.Link()) ctx.JSONRedirect(issue.Link())
} }

View File

@ -15,6 +15,7 @@ import (
// NewBranchForm form for creating a new branch // NewBranchForm form for creating a new branch
type NewBranchForm struct { type NewBranchForm struct {
NewBranchName string `binding:"Required;MaxSize(100);GitRefName"` NewBranchName string `binding:"Required;MaxSize(100);GitRefName"`
RepoID int64
SourceBranchName string SourceBranchName string
CurrentPath string CurrentPath string
CreateTag bool CreateTag bool

View File

@ -5,11 +5,14 @@ package issue
import ( import (
"context" "context"
"fmt"
"sort"
"strconv" "strconv"
git_model "code.gitea.io/gitea/models/git" git_model "code.gitea.io/gitea/models/git"
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/container"
) )
func FindIssueDevLinksByIssue(ctx context.Context, issue *issues_model.Issue) (issues_model.IssueDevLinks, error) { func FindIssueDevLinksByIssue(ctx context.Context, issue *issues_model.Issue) (issues_model.IssueDevLinks, error) {
@ -22,6 +25,17 @@ func FindIssueDevLinksByIssue(ctx context.Context, issue *issues_model.Issue) (i
return nil, err return nil, err
} }
sort.Slice(devLinks, func(i, j int) bool {
switch {
case devLinks[j].LinkType == issues_model.IssueDevLinkTypePullRequest:
return false
default:
return true
}
})
branchPRExists := make(container.Set[string])
for _, link := range devLinks { for _, link := range devLinks {
if link.LinkedRepoID == 0 { if link.LinkedRepoID == 0 {
link.LinkedRepoID = issue.RepoID link.LinkedRepoID = issue.RepoID
@ -47,9 +61,14 @@ func FindIssueDevLinksByIssue(ctx context.Context, issue *issues_model.Issue) (i
if err != nil { if err != nil {
return nil, err return nil, err
} }
pull.BaseRepo = issue.Repo
pull.HeadRepo = link.LinkedRepo
if err := pull.LoadIssue(ctx); err != nil {
return nil, err
}
pull.Issue.Repo = issue.Repo
link.PullRequest = pull link.PullRequest = pull
link.PullRequest.Issue = issue branchPRExists.Add(fmt.Sprintf("%d-%s", link.LinkedRepoID, pull.HeadBranch))
link.PullRequest.BaseRepo = issue.Repo
case issues_model.IssueDevLinkTypeBranch: case issues_model.IssueDevLinkTypeBranch:
branch, err := git_model.GetBranch(ctx, link.LinkedRepoID, link.LinkIndex) branch, err := git_model.GetBranch(ctx, link.LinkedRepoID, link.LinkIndex)
if err != nil { if err != nil {
@ -57,6 +76,7 @@ func FindIssueDevLinksByIssue(ctx context.Context, issue *issues_model.Issue) (i
} }
link.Branch = branch link.Branch = branch
link.Branch.Repo = link.LinkedRepo link.Branch.Repo = link.LinkedRepo
link.DisplayBranch = !branchPRExists.Contains(fmt.Sprintf("%d-%s", link.LinkedRepoID, link.LinkIndex))
} }
} }

View File

@ -128,6 +128,31 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *iss
return err return err
} }
if !pr.IsWorkInProgress(ctx) {
reviewNotifiers, err = issue_service.PullRequestCodeOwnersReview(ctx, issue, pr)
if err != nil {
return err
}
}
if pr.Flow == issues_model.PullRequestFlowGithub {
devLinks, err := issues_model.FindDevLinksByBranch(ctx, issue.RepoID, pr.HeadRepoID, pr.HeadBranch)
if err != nil {
return err
}
for _, link := range devLinks {
if err := issues_model.CreateIssueDevLink(ctx, &issues_model.IssueDevLink{
IssueID: link.IssueID,
LinkType: issues_model.IssueDevLinkTypePullRequest,
LinkedRepoID: pr.HeadRepoID,
LinkIndex: strconv.FormatInt(pr.ID, 10),
}); err != nil {
return err
}
}
}
// leave creating comment last
compareInfo, err := baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(), compareInfo, err := baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(),
git.BranchPrefix+pr.BaseBranch, pr.GetGitRefName(), false, false) git.BranchPrefix+pr.BaseBranch, pr.GetGitRefName(), false, false)
if err != nil { if err != nil {
@ -161,30 +186,6 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *iss
return err return err
} }
if !pr.IsWorkInProgress(ctx) {
reviewNotifiers, err = issue_service.PullRequestCodeOwnersReview(ctx, issue, pr)
if err != nil {
return err
}
}
if pr.Flow == issues_model.PullRequestFlowGithub {
devLinks, err := issues_model.FindDevLinksByBranch(ctx, issue.RepoID, pr.HeadRepoID, pr.HeadBranch)
if err != nil {
return err
}
for _, link := range devLinks {
if err := issues_model.CreateIssueDevLink(ctx, &issues_model.IssueDevLink{
IssueID: link.IssueID,
LinkType: issues_model.IssueDevLinkTypePullRequest,
LinkedRepoID: pr.HeadRepoID,
LinkIndex: strconv.FormatInt(pr.ID, 10),
}); err != nil {
return err
}
}
}
return nil return nil
}); err != nil { }); err != nil {
// cleanup: this will only remove the reference, the real commit will be clean up when next GC // cleanup: this will only remove the reference, the real commit will be clean up when next GC

View File

@ -253,9 +253,10 @@
</div> </div>
</div> </div>
{{if not .Issue.IsPull}}
<div class="divider"></div> <div class="divider"></div>
{{template "repo/issue/view_content/sidebar_development" .}} {{template "repo/issue/view_content/sidebar_development" .}}
{{end}}
<div class="divider"></div> <div class="divider"></div>

View File

@ -5,10 +5,13 @@
{{end}} {{end}}
{{range .DevLinks}} {{range .DevLinks}}
{{if .PullRequest}} {{if .PullRequest}}
<span>{{template "shared/issueicon" .PullRequest.Issue}}
<a href="{{.PullRequest.Issue.Link}}" class="item"> <a href="{{.PullRequest.Issue.Link}}" class="item">
{{.PullRequest.Issue.Title}} {{.PullRequest.Issue.Title}}
</a> </a>
Created {{.PullRequest.Issue.CreatedAt}} </span>
<div>
Created {{DateTime "short" .PullRequest.Issue.CreatedUnix}}
{{if .PullRequest.HasMerged}} {{if .PullRequest.HasMerged}}
Completed Completed
{{.PullRequest.MergedCommitID}} {{.PullRequest.MergedCommitID}}
@ -16,15 +19,16 @@
{{else if .PullRequest.ChangedProtectedFiles}} {{else if .PullRequest.ChangedProtectedFiles}}
Merge conflicts Merge conflicts
{{end}} {{end}}
{{else if .Branch}} </div>
{{else if and .Branch .DisplayBranch}}
<span> <span>
{{svg "octicon-git-branch" 14}} {{svg "octicon-git-branch" 14}}
<a href="{{.Branch}}" class="item"> <a href="{{.Branch.Repo.Link}}/src/branch/{{.Branch.Name}}" class="item">
<span class="gt-ellipsis">{{.Branch.Name}}</span> <span class="gt-ellipsis">{{.Branch.Name}}</span>
</a> </a>
</span> </span>
<div>Latest commit {{DateTime "short" .Branch.CommitTime}}</div> <div>Latest commit {{DateTime "short" .Branch.CommitTime}}</div>
<a href="{{$.Issue.Repo.Link}}/compare/main...{{.Branch.Name}}">{{ctx.Locale.Tr "repo.pulls.new"}}</a> <a href="{{$.Issue.Repo.Link}}/compare/{{$.Issue.Repo.DefaultBranch}}...{{.Branch.Repo.FullName}}:{{.Branch.Name}}">{{ctx.Locale.Tr "repo.pulls.new"}}</a>
{{end}} {{end}}
{{end}} {{end}}
</div> </div>
@ -45,7 +49,7 @@
<div class="field"> <div class="field">
<label for="source_repository">{{ctx.Locale.Tr "repository"}}</label> <label for="source_repository">{{ctx.Locale.Tr "repository"}}</label>
<div class="ui fluid dropdown selection"> <div class="ui fluid dropdown selection">
<select name="source_repository"> <select name="repo_id">
<option value=""> </option> <option value=""> </option>
{{range .AllowedRepos}} {{range .AllowedRepos}}
<option value="{{.ID}}">{{.FullName}}</option> <option value="{{.ID}}">{{.FullName}}</option>
@ -86,7 +90,7 @@
<div class="text right actions"> <div class="text right actions">
<button class="ui cancel button">{{ctx.Locale.Tr "settings.cancel"}}</button> <button class="ui cancel button">{{ctx.Locale.Tr "settings.cancel"}}</button>
<button class="ui red button">{{ctx.Locale.Tr "repo.branch.new_branch"}}</button> <button class="ui primary button">{{ctx.Locale.Tr "repo.branch.new_branch"}}</button>
</div> </div>
</form> </form>
</div> </div>

View File

@ -118,6 +118,10 @@
· ·
{{ctx.Locale.TrN .Issue.NumComments "repo.issues.num_comments_1" "repo.issues.num_comments" .Issue.NumComments}} {{ctx.Locale.TrN .Issue.NumComments "repo.issues.num_comments_1" "repo.issues.num_comments" .Issue.NumComments}}
</span> </span>
{{if .MaybeFixed}}
{{$fixedStr:= printf "<a href=\"%s\">#%d</a>" .MaybeFixed.Issue.Link .MaybeFixed.Index}}
· <span>{{ctx.Locale.Tr "repo.issues.maybefixed" ($fixedStr|SafeHTML)}}</span>
{{end}}
{{end}} {{end}}
</div> </div>
</div> </div>