From 62fda252bd482ce5427ac7eb86155e24159c926b Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 21 Aug 2024 15:34:03 -0700 Subject: [PATCH 01/40] Add a new section named development in issue view sidebar to interact with branch/pr --- models/issues/issue_dev_link.go | 77 +++++++++++++++ models/migrations/migrations.go | 2 + models/migrations/v1_23/v305.go | 21 +++++ models/organization/org.go | 14 ++- options/locale/locale_en-US.ini | 1 + routers/web/repo/branch.go | 93 ++++++++++--------- routers/web/repo/issue.go | 15 +++ routers/web/repo/issue_dev.go | 56 +++++++++++ routers/web/web.go | 1 + services/forms/repo_branch_form.go | 7 +- services/issue/dev_link.go | 64 +++++++++++++ services/pull/pull.go | 19 ++++ .../repo/issue/view_content/sidebar.tmpl | 4 + .../view_content/sidebar_development.tmpl | 93 +++++++++++++++++++ 14 files changed, 415 insertions(+), 52 deletions(-) create mode 100644 models/issues/issue_dev_link.go create mode 100644 models/migrations/v1_23/v305.go create mode 100644 routers/web/repo/issue_dev.go create mode 100644 services/issue/dev_link.go create mode 100644 templates/repo/issue/view_content/sidebar_development.tmpl diff --git a/models/issues/issue_dev_link.go b/models/issues/issue_dev_link.go new file mode 100644 index 0000000000000..cdb9e5d040a4b --- /dev/null +++ b/models/issues/issue_dev_link.go @@ -0,0 +1,77 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package issues + +import ( + "context" + "strconv" + + "code.gitea.io/gitea/models/db" + git_model "code.gitea.io/gitea/models/git" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/timeutil" +) + +type IssueDevLinkType int + +const ( + IssueDevLinkTypeBranch IssueDevLinkType = iota + 1 + IssueDevLinkTypePullRequest +) + +type IssueDevLink struct { + ID int64 `xorm:"pk autoincr"` + IssueID int64 `xorm:"INDEX"` + LinkType IssueDevLinkType + LinkedRepoID int64 `xorm:"INDEX"` // it can link to self repo or other repo + LinkIndex string // branch name, pull request number or commit sha + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + + LinkedRepo *repo_model.Repository `xorm:"-"` + PullRequest *PullRequest `xorm:"-"` + Branch *git_model.Branch `xorm:"-"` +} + +func init() { + db.RegisterModel(new(IssueDevLink)) +} + +// IssueDevLinks represents a list of issue development links +type IssueDevLinks []*IssueDevLink + +// FindIssueDevLinksByIssueID returns a list of issue development links by issue ID +func FindIssueDevLinksByIssueID(ctx context.Context, issueID int64) (IssueDevLinks, error) { + links := make(IssueDevLinks, 0, 5) + return links, db.GetEngine(ctx).Where("issue_id = ?", issueID).Find(&links) +} + +func FindDevLinksByBranch(ctx context.Context, repoID, linkedRepoID int64, branchName string) (IssueDevLinks, error) { + links := make(IssueDevLinks, 0, 5) + return links, db.GetEngine(ctx). + Join("INNER", "issue", "issue_dev_link.issue_id = issue.id"). + Where("link_type = ? AND link_index = ? AND linked_repo_id = ?", + IssueDevLinkTypeBranch, branchName, linkedRepoID). + And("issue.repo_id=?", repoID). + Find(&links) +} + +func CreateIssueDevLink(ctx context.Context, link *IssueDevLink) error { + _, err := db.GetEngine(ctx).Insert(link) + return err +} + +func DeleteIssueDevLinkByBranchName(ctx context.Context, repoID int64, branchName string) error { + _, err := db.GetEngine(ctx). + Where("link_type = ? AND link_index = ? AND linked_repo_id = ?", + IssueDevLinkTypeBranch, branchName, repoID). + Delete(new(IssueDevLink)) + return err +} + +func DeleteIssueDevLinkByPullRequestID(ctx context.Context, pullID int64) error { + pullIDStr := strconv.FormatInt(pullID, 10) + _, err := db.GetEngine(ctx).Where("link_type = ? AND link_index = ?", IssueDevLinkTypePullRequest, pullIDStr). + Delete(new(IssueDevLink)) + return err +} diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 13551423ce470..6db078cd2c151 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -601,6 +601,8 @@ var migrations = []Migration{ NewMigration("Add metadata column for comment table", v1_23.AddCommentMetaDataColumn), // v304 -> v305 NewMigration("Add index for release sha1", v1_23.AddIndexForReleaseSha1), + // v305 -> v306 + NewMigration("Add table issue_dev_link", v1_23.CreateTableIssueDevLink), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v1_23/v305.go b/models/migrations/v1_23/v305.go new file mode 100644 index 0000000000000..fe696984a3a02 --- /dev/null +++ b/models/migrations/v1_23/v305.go @@ -0,0 +1,21 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_23 //nolint + +import ( + "code.gitea.io/gitea/modules/timeutil" + + "xorm.io/xorm" +) + +func CreateTableIssueDevLink(x *xorm.Engine) error { + type IssueDevLink struct { + ID int64 `xorm:"pk autoincr"` + IssueID int64 `xorm:"INDEX"` + LinkType int + LinkIndex string // branch name, pull request number or commit sha + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + } + return x.Sync(new(IssueDevLink)) +} diff --git a/models/organization/org.go b/models/organization/org.go index b33d15d29c129..16eb8f77b7137 100644 --- a/models/organization/org.go +++ b/models/organization/org.go @@ -508,16 +508,20 @@ func HasOrgsVisible(ctx context.Context, orgs []*Organization, user *user_model. return false } +func orgAllowedCreatedRepoSubQuery(userID int64) *builder.Builder { + return builder.Select("`user`.id").From("`user`"). + Join("INNER", "`team_user`", "`team_user`.org_id = `user`.id"). + Join("INNER", "`team`", "`team`.id = `team_user`.team_id"). + Where(builder.Eq{"`team_user`.uid": userID}). + And(builder.Eq{"`team`.authorize": perm.AccessModeOwner}.Or(builder.Eq{"`team`.can_create_org_repo": true})) +} + // GetOrgsCanCreateRepoByUserID returns a list of organizations where given user ID // are allowed to create repos. func GetOrgsCanCreateRepoByUserID(ctx context.Context, userID int64) ([]*Organization, error) { orgs := make([]*Organization, 0, 10) - return orgs, db.GetEngine(ctx).Where(builder.In("id", builder.Select("`user`.id").From("`user`"). - Join("INNER", "`team_user`", "`team_user`.org_id = `user`.id"). - Join("INNER", "`team`", "`team`.id = `team_user`.team_id"). - Where(builder.Eq{"`team_user`.uid": userID}). - And(builder.Eq{"`team`.authorize": perm.AccessModeOwner}.Or(builder.Eq{"`team`.can_create_org_repo": true})))). + return orgs, db.GetEngine(ctx).Where(builder.In("id", orgAllowedCreatedRepoSubQuery(userID))). Asc("`user`.name"). Find(&orgs) } diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 042fd549a0b30..59ff860d1fd76 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1622,6 +1622,7 @@ issues.label.filter_sort.alphabetically = Alphabetically issues.label.filter_sort.reverse_alphabetically = Reverse alphabetically issues.label.filter_sort.by_size = Smallest size issues.label.filter_sort.reverse_by_size = Largest size +issues.development = Development issues.num_participants = %d Participants issues.attachment.open_tab = `Click to see "%s" in a new tab` issues.attachment.download = `Click to download "%s"` diff --git a/routers/web/repo/branch.go b/routers/web/repo/branch.go index 4897a5f4fcdf6..c5ac40226f2bb 100644 --- a/routers/web/repo/branch.go +++ b/routers/web/repo/branch.go @@ -176,6 +176,54 @@ func redirect(ctx *context.Context) { ctx.JSONRedirect(ctx.Repo.RepoLink + "/branches?page=" + url.QueryEscape(ctx.FormString("page"))) } +func handleCreateBranchError(ctx *context.Context, err error, form *forms.NewBranchForm) { + if models.IsErrProtectedTagName(err) { + ctx.Flash.Error(ctx.Tr("repo.release.tag_name_protected")) + ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) + return + } + + if models.IsErrTagAlreadyExists(err) { + e := err.(models.ErrTagAlreadyExists) + ctx.Flash.Error(ctx.Tr("repo.branch.tag_collision", e.TagName)) + ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) + return + } + if git_model.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) { + ctx.Flash.Error(ctx.Tr("repo.branch.branch_already_exists", form.NewBranchName)) + ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) + return + } + if git_model.IsErrBranchNameConflict(err) { + e := err.(git_model.ErrBranchNameConflict) + ctx.Flash.Error(ctx.Tr("repo.branch.branch_name_conflict", form.NewBranchName, e.BranchName)) + ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) + return + } + if 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.Flash.Error(flashError) + } + ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) + return + } + + ctx.ServerError("CreateNewBranch", err) + return +} + // CreateBranch creates new branch in repository func CreateBranch(ctx *context.Context) { form := web.GetForm(ctx).(*forms.NewBranchForm) @@ -204,50 +252,7 @@ func CreateBranch(ctx *context.Context) { err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.Repo.CommitID, form.NewBranchName) } if err != nil { - if models.IsErrProtectedTagName(err) { - ctx.Flash.Error(ctx.Tr("repo.release.tag_name_protected")) - ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) - return - } - - if models.IsErrTagAlreadyExists(err) { - e := err.(models.ErrTagAlreadyExists) - ctx.Flash.Error(ctx.Tr("repo.branch.tag_collision", e.TagName)) - ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) - return - } - if git_model.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) { - ctx.Flash.Error(ctx.Tr("repo.branch.branch_already_exists", form.NewBranchName)) - ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) - return - } - if git_model.IsErrBranchNameConflict(err) { - e := err.(git_model.ErrBranchNameConflict) - ctx.Flash.Error(ctx.Tr("repo.branch.branch_name_conflict", form.NewBranchName, e.BranchName)) - ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) - return - } - if 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.Flash.Error(flashError) - } - ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) - return - } - - ctx.ServerError("CreateNewBranch", err) + handleCreateBranchError(ctx, err, form) return } diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index 856e2f7392bae..dc8c706332e08 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -2075,6 +2075,21 @@ func ViewIssue(ctx *context.Context) { return user_service.CanBlockUser(ctx, ctx.Doer, blocker, blockee) } + forkedRepos, err := repo_model.FindUserOrgForks(ctx, ctx.Repo.Repository.ID, ctx.Doer.ID) + if err != nil { + ctx.ServerError("FindUserOrgForks", err) + return + } + + ctx.Data["AllowedRepos"] = append(forkedRepos, ctx.Repo.Repository) + + devLinks, err := issue_service.FindIssueDevLinksByIssue(ctx, issue) + if err != nil { + ctx.ServerError("FindIssueDevLinksByIssueID", err) + return + } + ctx.Data["DevLinks"] = devLinks + ctx.HTML(http.StatusOK, tplIssueView) } diff --git a/routers/web/repo/issue_dev.go b/routers/web/repo/issue_dev.go new file mode 100644 index 0000000000000..fadf59cc0b1a9 --- /dev/null +++ b/routers/web/repo/issue_dev.go @@ -0,0 +1,56 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repo + +import ( + "net/http" + + issues_model "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/services/context" + "code.gitea.io/gitea/services/forms" + repo_service "code.gitea.io/gitea/services/repository" +) + +func CreateBranchFromIssue(ctx *context.Context) { + issue := GetActionIssue(ctx) + if ctx.Written() { + return + } + + if issue.IsPull { + ctx.Flash.Error(ctx.Tr("repo.issues.create_branch_from_issue_error_is_pull")) + ctx.Redirect(issue.Link(), http.StatusSeeOther) + return + } + + form := web.GetForm(ctx).(*forms.NewBranchForm) + if !ctx.Repo.CanCreateBranch() { + ctx.NotFound("CreateBranch", nil) + return + } + + if ctx.HasError() { + ctx.Flash.Error(ctx.GetErrMsg()) + ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) + return + } + + if err := repo_service.CreateNewBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, form.SourceBranchName, form.NewBranchName); err != nil { + handleCreateBranchError(ctx, err, form) + return + } + + if err := issues_model.CreateIssueDevLink(ctx, &issues_model.IssueDevLink{ + IssueID: issue.ID, + LinkType: issues_model.IssueDevLinkTypeBranch, + LinkIndex: form.NewBranchName, + }); err != nil { + ctx.ServerError("CreateIssueDevLink", err) + return + } + + ctx.Flash.Success(ctx.Tr("repo.issues.create_branch_from_issue_success", ctx.Repo.BranchName)) + ctx.Redirect(issue.Link()) +} diff --git a/routers/web/web.go b/routers/web/web.go index 4e917b5ede678..79ececc613b35 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1216,6 +1216,7 @@ func registerRoutes(m *web.Router) { m.Post("/lock", reqRepoIssuesOrPullsWriter, web.Bind(forms.IssueLockForm{}), repo.LockIssue) m.Post("/unlock", reqRepoIssuesOrPullsWriter, repo.UnlockIssue) m.Post("/delete", reqRepoAdmin, repo.DeleteIssue) + m.Post("/create_branch", web.Bind(forms.NewBranchForm{}), repo.CreateBranchFromIssue) }, context.RepoMustNotBeArchived()) m.Group("/{index}", func() { diff --git a/services/forms/repo_branch_form.go b/services/forms/repo_branch_form.go index 42e6c85c371a8..32b17c7d54c14 100644 --- a/services/forms/repo_branch_form.go +++ b/services/forms/repo_branch_form.go @@ -14,9 +14,10 @@ import ( // NewBranchForm form for creating a new branch type NewBranchForm struct { - NewBranchName string `binding:"Required;MaxSize(100);GitRefName"` - CurrentPath string - CreateTag bool + NewBranchName string `binding:"Required;MaxSize(100);GitRefName"` + SourceBranchName string + CurrentPath string + CreateTag bool } // Validate validates the fields diff --git a/services/issue/dev_link.go b/services/issue/dev_link.go new file mode 100644 index 0000000000000..37544444faf92 --- /dev/null +++ b/services/issue/dev_link.go @@ -0,0 +1,64 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package issue + +import ( + "context" + "strconv" + + git_model "code.gitea.io/gitea/models/git" + issues_model "code.gitea.io/gitea/models/issues" + repo_model "code.gitea.io/gitea/models/repo" +) + +func FindIssueDevLinksByIssue(ctx context.Context, issue *issues_model.Issue) (issues_model.IssueDevLinks, error) { + devLinks, err := issues_model.FindIssueDevLinksByIssueID(ctx, issue.ID) + if err != nil { + return nil, err + } + + if err := issue.LoadRepo(ctx); err != nil { + return nil, err + } + + for _, link := range devLinks { + if link.LinkedRepoID == 0 { + link.LinkedRepoID = issue.RepoID + } + isSameRepo := issue.RepoID == link.LinkedRepoID + if isSameRepo { + link.LinkedRepo = issue.Repo + } else if link.LinkedRepoID > 0 { + repo, err := repo_model.GetRepositoryByID(ctx, link.LinkedRepoID) + if err != nil { + return nil, err + } + link.LinkedRepo = repo + } + + switch link.LinkType { + case issues_model.IssueDevLinkTypePullRequest: + pullID, err := strconv.ParseInt(link.LinkIndex, 10, 64) + if err != nil { + return nil, err + } + pull, err := issues_model.GetPullRequestByID(ctx, pullID) + if err != nil { + return nil, err + } + link.PullRequest = pull + link.PullRequest.Issue = issue + link.PullRequest.BaseRepo = issue.Repo + case issues_model.IssueDevLinkTypeBranch: + branch, err := git_model.GetBranch(ctx, link.LinkedRepoID, link.LinkIndex) + if err != nil { + return nil, err + } + link.Branch = branch + link.Branch.Repo = link.LinkedRepo + } + } + + return devLinks, nil +} diff --git a/services/pull/pull.go b/services/pull/pull.go index e69c842a2d4b5..fd316fcb19f82 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -10,6 +10,7 @@ import ( "io" "os" "regexp" + "strconv" "strings" "time" @@ -166,6 +167,24 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *iss 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 }); err != nil { // cleanup: this will only remove the reference, the real commit will be clean up when next GC diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index ce34c5e9392ff..a37b055501766 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -255,6 +255,10 @@
+ {{template "repo/issue/view_content/sidebar_development" .}} + +
+ {{if .Participants}} {{ctx.Locale.Tr "repo.issues.num_participants" .NumParticipants}}
diff --git a/templates/repo/issue/view_content/sidebar_development.tmpl b/templates/repo/issue/view_content/sidebar_development.tmpl new file mode 100644 index 0000000000000..ac4045fd3fc99 --- /dev/null +++ b/templates/repo/issue/view_content/sidebar_development.tmpl @@ -0,0 +1,93 @@ +{{ctx.Locale.Tr "repo.issues.development"}} + + + \ No newline at end of file From 2361ec5a444f6b00da502af0c1b5ca62a87a80ae Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 21 Aug 2024 19:08:18 -0700 Subject: [PATCH 02/40] Improvements for creating branch model --- models/issues/issue_dev_link.go | 7 +- models/migrations/v1_23/v305.go | 11 +-- options/locale/locale_en-US.ini | 2 + routers/web/repo/issue.go | 10 ++- routers/web/repo/issue_dev.go | 71 ++++++++++++++++--- services/forms/repo_branch_form.go | 1 + services/issue/dev_link.go | 24 ++++++- services/pull/pull.go | 49 ++++++------- .../repo/issue/view_content/sidebar.tmpl | 7 +- .../view_content/sidebar_development.tmpl | 16 +++-- templates/repo/issue/view_title.tmpl | 4 ++ 11 files changed, 149 insertions(+), 53 deletions(-) diff --git a/models/issues/issue_dev_link.go b/models/issues/issue_dev_link.go index cdb9e5d040a4b..9e6c7cfc25df9 100644 --- a/models/issues/issue_dev_link.go +++ b/models/issues/issue_dev_link.go @@ -28,9 +28,10 @@ type IssueDevLink struct { LinkIndex string // branch name, pull request number or commit sha CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` - LinkedRepo *repo_model.Repository `xorm:"-"` - PullRequest *PullRequest `xorm:"-"` - Branch *git_model.Branch `xorm:"-"` + LinkedRepo *repo_model.Repository `xorm:"-"` + PullRequest *PullRequest `xorm:"-"` + Branch *git_model.Branch `xorm:"-"` + DisplayBranch bool `xorm:"-"` } func init() { diff --git a/models/migrations/v1_23/v305.go b/models/migrations/v1_23/v305.go index fe696984a3a02..c017890b509ba 100644 --- a/models/migrations/v1_23/v305.go +++ b/models/migrations/v1_23/v305.go @@ -11,11 +11,12 @@ import ( func CreateTableIssueDevLink(x *xorm.Engine) error { type IssueDevLink struct { - ID int64 `xorm:"pk autoincr"` - IssueID int64 `xorm:"INDEX"` - LinkType int - LinkIndex string // branch name, pull request number or commit sha - CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + ID int64 `xorm:"pk autoincr"` + IssueID int64 `xorm:"INDEX"` + 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 + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` } return x.Sync(new(IssueDevLink)) } diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 59ff860d1fd76..850b3e6a3dfa3 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1623,6 +1623,8 @@ issues.label.filter_sort.reverse_alphabetically = Reverse alphabetically issues.label.filter_sort.by_size = Smallest size issues.label.filter_sort.reverse_by_size = Largest size 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.attachment.open_tab = `Click to see "%s" in a new tab` issues.attachment.download = `Click to download "%s"` diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index dc8c706332e08..1217a6e65915c 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -2085,10 +2085,18 @@ func ViewIssue(ctx *context.Context) { devLinks, err := issue_service.FindIssueDevLinksByIssue(ctx, issue) if err != nil { - ctx.ServerError("FindIssueDevLinksByIssueID", err) + ctx.ServerError("FindIssueDevLinksByIssue", err) return } 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) } diff --git a/routers/web/repo/issue_dev.go b/routers/web/repo/issue_dev.go index fadf59cc0b1a9..a32c051961125 100644 --- a/routers/web/repo/issue_dev.go +++ b/routers/web/repo/issue_dev.go @@ -4,10 +4,15 @@ package repo import ( - "net/http" - + git_model "code.gitea.io/gitea/models/git" 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/routers/utils" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/forms" repo_service "code.gitea.io/gitea/services/repository" @@ -21,30 +26,78 @@ func CreateBranchFromIssue(ctx *context.Context) { if issue.IsPull { 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 } 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) return } if ctx.HasError() { - ctx.Flash.Error(ctx.GetErrMsg()) - ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) + ctx.JSONError(ctx.GetErrMsg()) return } - if err := repo_service.CreateNewBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, form.SourceBranchName, form.NewBranchName); err != nil { - handleCreateBranchError(ctx, err, form) + if err := repo_service.CreateNewBranch(ctx, ctx.Doer, repo, gitRepo, form.SourceBranchName, form.NewBranchName); err != nil { + 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 } if err := issues_model.CreateIssueDevLink(ctx, &issues_model.IssueDevLink{ IssueID: issue.ID, LinkType: issues_model.IssueDevLinkTypeBranch, + LinkedRepoID: repo.ID, LinkIndex: form.NewBranchName, }); err != nil { 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.Redirect(issue.Link()) + ctx.JSONRedirect(issue.Link()) } diff --git a/services/forms/repo_branch_form.go b/services/forms/repo_branch_form.go index 32b17c7d54c14..16ebddf07d74e 100644 --- a/services/forms/repo_branch_form.go +++ b/services/forms/repo_branch_form.go @@ -15,6 +15,7 @@ import ( // NewBranchForm form for creating a new branch type NewBranchForm struct { NewBranchName string `binding:"Required;MaxSize(100);GitRefName"` + RepoID int64 SourceBranchName string CurrentPath string CreateTag bool diff --git a/services/issue/dev_link.go b/services/issue/dev_link.go index 37544444faf92..1e0f4b14c9c56 100644 --- a/services/issue/dev_link.go +++ b/services/issue/dev_link.go @@ -5,11 +5,14 @@ package issue import ( "context" + "fmt" + "sort" "strconv" git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" 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) { @@ -22,6 +25,17 @@ func FindIssueDevLinksByIssue(ctx context.Context, issue *issues_model.Issue) (i 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 { if link.LinkedRepoID == 0 { link.LinkedRepoID = issue.RepoID @@ -47,9 +61,14 @@ func FindIssueDevLinksByIssue(ctx context.Context, issue *issues_model.Issue) (i if err != nil { 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.Issue = issue - link.PullRequest.BaseRepo = issue.Repo + branchPRExists.Add(fmt.Sprintf("%d-%s", link.LinkedRepoID, pull.HeadBranch)) case issues_model.IssueDevLinkTypeBranch: branch, err := git_model.GetBranch(ctx, link.LinkedRepoID, link.LinkIndex) if err != nil { @@ -57,6 +76,7 @@ func FindIssueDevLinksByIssue(ctx context.Context, issue *issues_model.Issue) (i } link.Branch = branch link.Branch.Repo = link.LinkedRepo + link.DisplayBranch = !branchPRExists.Contains(fmt.Sprintf("%d-%s", link.LinkedRepoID, link.LinkIndex)) } } diff --git a/services/pull/pull.go b/services/pull/pull.go index fd316fcb19f82..4f68a3084c220 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -128,6 +128,31 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *iss 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(), git.BranchPrefix+pr.BaseBranch, pr.GetGitRefName(), false, false) if err != nil { @@ -161,30 +186,6 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *iss 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 }); err != nil { // cleanup: this will only remove the reference, the real commit will be clean up when next GC diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index a37b055501766..b27e06623825b 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -253,9 +253,10 @@
-
- - {{template "repo/issue/view_content/sidebar_development" .}} + {{if not .Issue.IsPull}} +
+ {{template "repo/issue/view_content/sidebar_development" .}} + {{end}}
diff --git a/templates/repo/issue/view_content/sidebar_development.tmpl b/templates/repo/issue/view_content/sidebar_development.tmpl index ac4045fd3fc99..f50966ad4a30a 100644 --- a/templates/repo/issue/view_content/sidebar_development.tmpl +++ b/templates/repo/issue/view_content/sidebar_development.tmpl @@ -5,10 +5,13 @@ {{end}} {{range .DevLinks}} {{if .PullRequest}} + {{template "shared/issueicon" .PullRequest.Issue}} {{.PullRequest.Issue.Title}} - Created {{.PullRequest.Issue.CreatedAt}} + +
+ Created {{DateTime "short" .PullRequest.Issue.CreatedUnix}} {{if .PullRequest.HasMerged}} Completed {{.PullRequest.MergedCommitID}} @@ -16,15 +19,16 @@ {{else if .PullRequest.ChangedProtectedFiles}} Merge conflicts {{end}} - {{else if .Branch}} +
+ {{else if and .Branch .DisplayBranch}} {{svg "octicon-git-branch" 14}} - + {{.Branch.Name}}
Latest commit {{DateTime "short" .Branch.CommitTime}}
- {{ctx.Locale.Tr "repo.pulls.new"}} + {{ctx.Locale.Tr "repo.pulls.new"}} {{end}} {{end}} @@ -45,7 +49,7 @@
diff --git a/templates/repo/issue/view_title.tmpl b/templates/repo/issue/view_title.tmpl index 1243681f3a219..42bb421201e0f 100644 --- a/templates/repo/issue/view_title.tmpl +++ b/templates/repo/issue/view_title.tmpl @@ -118,6 +118,10 @@ · {{ctx.Locale.TrN .Issue.NumComments "repo.issues.num_comments_1" "repo.issues.num_comments" .Issue.NumComments}} + {{if .MaybeFixed}} + {{$fixedStr:= printf "#%d" .MaybeFixed.Issue.Link .MaybeFixed.Index}} + · {{ctx.Locale.Tr "repo.issues.maybefixed" ($fixedStr|SafeHTML)}} + {{end}} {{end}}
From b4eac75dba50301bc28bfad98997349b01799dc4 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 21 Aug 2024 19:40:32 -0700 Subject: [PATCH 03/40] Some improvements --- options/locale/locale_en-US.ini | 3 +++ routers/web/repo/branch.go | 1 - routers/web/repo/issue_dev.go | 6 +++--- .../issue/view_content/sidebar_development.tmpl | 14 +++++++++----- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 850b3e6a3dfa3..b286e8be13c1d 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1625,6 +1625,9 @@ issues.label.filter_sort.reverse_by_size = Largest size issues.development = Development issues.maybefixed = May be fixed by %s issues.create_branch_from_issue_success = Create branch %s from issue successfully +issues.pr.completed = Completed +issues.pr.conflicted = Merge conflicts +issues.link.created = Created %s issues.num_participants = %d Participants issues.attachment.open_tab = `Click to see "%s" in a new tab` issues.attachment.download = `Click to download "%s"` diff --git a/routers/web/repo/branch.go b/routers/web/repo/branch.go index c5ac40226f2bb..649d8c7ca4463 100644 --- a/routers/web/repo/branch.go +++ b/routers/web/repo/branch.go @@ -221,7 +221,6 @@ func handleCreateBranchError(ctx *context.Context, err error, form *forms.NewBra } ctx.ServerError("CreateNewBranch", err) - return } // CreateBranch creates new branch in repository diff --git a/routers/web/repo/issue_dev.go b/routers/web/repo/issue_dev.go index a32c051961125..8f9548beb8547 100644 --- a/routers/web/repo/issue_dev.go +++ b/routers/web/repo/issue_dev.go @@ -95,10 +95,10 @@ func CreateBranchFromIssue(ctx *context.Context) { } if err := issues_model.CreateIssueDevLink(ctx, &issues_model.IssueDevLink{ - IssueID: issue.ID, - LinkType: issues_model.IssueDevLinkTypeBranch, + IssueID: issue.ID, + LinkType: issues_model.IssueDevLinkTypeBranch, LinkedRepoID: repo.ID, - LinkIndex: form.NewBranchName, + LinkIndex: form.NewBranchName, }); err != nil { ctx.ServerError("CreateIssueDevLink", err) return diff --git a/templates/repo/issue/view_content/sidebar_development.tmpl b/templates/repo/issue/view_content/sidebar_development.tmpl index f50966ad4a30a..12e3e8bd70f47 100644 --- a/templates/repo/issue/view_content/sidebar_development.tmpl +++ b/templates/repo/issue/view_content/sidebar_development.tmpl @@ -11,13 +11,17 @@
- Created {{DateTime "short" .PullRequest.Issue.CreatedUnix}} + {{ctx.Locale.Tr "repo.issues.link.created" (DateTime "short" .PullRequest.Issue.CreatedUnix)}} {{if .PullRequest.HasMerged}} - Completed - {{.PullRequest.MergedCommitID}} - Created {{.PullRequest.MergedUnix}} + {{ctx.Locale.Tr "repo.issues.pr.completed"}} +
+
+ {{svg "octicon-git-commit" 14}} {{.PullRequest.MergedCommitID | ShortSha}} +
+
+ {{ctx.Locale.Tr "repo.issues.link.created" (DateTime "short" .PullRequest.MergedUnix)}} {{else if .PullRequest.ChangedProtectedFiles}} - Merge conflicts + {{ctx.Locale.Tr "repo.issues.pr.conflicted"}} {{end}}
{{else if and .Branch .DisplayBranch}} From 359e660f0f109538adabd83856ec2cba3d6df543 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 21 Aug 2024 19:49:09 -0700 Subject: [PATCH 04/40] some improvements --- templates/repo/issue/view_title.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/repo/issue/view_title.tmpl b/templates/repo/issue/view_title.tmpl index 42bb421201e0f..c5ec8c46d24a0 100644 --- a/templates/repo/issue/view_title.tmpl +++ b/templates/repo/issue/view_title.tmpl @@ -119,7 +119,7 @@ {{ctx.Locale.TrN .Issue.NumComments "repo.issues.num_comments_1" "repo.issues.num_comments" .Issue.NumComments}} {{if .MaybeFixed}} - {{$fixedStr:= printf "#%d" .MaybeFixed.Issue.Link .MaybeFixed.Index}} + {{$fixedStr := printf "#%d" .MaybeFixed.Issue.Link .MaybeFixed.Index}} · {{ctx.Locale.Tr "repo.issues.maybefixed" ($fixedStr|SafeHTML)}} {{end}} {{end}} From 9ea33761a6f29dd0e44bab78732c404df597029c Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 21 Aug 2024 19:58:25 -0700 Subject: [PATCH 05/40] revert unnecessary change --- models/organization/org.go | 14 +++--- routers/web/repo/branch.go | 92 ++++++++++++++++++-------------------- 2 files changed, 49 insertions(+), 57 deletions(-) diff --git a/models/organization/org.go b/models/organization/org.go index 16eb8f77b7137..b33d15d29c129 100644 --- a/models/organization/org.go +++ b/models/organization/org.go @@ -508,20 +508,16 @@ func HasOrgsVisible(ctx context.Context, orgs []*Organization, user *user_model. return false } -func orgAllowedCreatedRepoSubQuery(userID int64) *builder.Builder { - return builder.Select("`user`.id").From("`user`"). - Join("INNER", "`team_user`", "`team_user`.org_id = `user`.id"). - Join("INNER", "`team`", "`team`.id = `team_user`.team_id"). - Where(builder.Eq{"`team_user`.uid": userID}). - And(builder.Eq{"`team`.authorize": perm.AccessModeOwner}.Or(builder.Eq{"`team`.can_create_org_repo": true})) -} - // GetOrgsCanCreateRepoByUserID returns a list of organizations where given user ID // are allowed to create repos. func GetOrgsCanCreateRepoByUserID(ctx context.Context, userID int64) ([]*Organization, error) { orgs := make([]*Organization, 0, 10) - return orgs, db.GetEngine(ctx).Where(builder.In("id", orgAllowedCreatedRepoSubQuery(userID))). + return orgs, db.GetEngine(ctx).Where(builder.In("id", builder.Select("`user`.id").From("`user`"). + Join("INNER", "`team_user`", "`team_user`.org_id = `user`.id"). + Join("INNER", "`team`", "`team`.id = `team_user`.team_id"). + Where(builder.Eq{"`team_user`.uid": userID}). + And(builder.Eq{"`team`.authorize": perm.AccessModeOwner}.Or(builder.Eq{"`team`.can_create_org_repo": true})))). Asc("`user`.name"). Find(&orgs) } diff --git a/routers/web/repo/branch.go b/routers/web/repo/branch.go index 649d8c7ca4463..4897a5f4fcdf6 100644 --- a/routers/web/repo/branch.go +++ b/routers/web/repo/branch.go @@ -176,53 +176,6 @@ func redirect(ctx *context.Context) { ctx.JSONRedirect(ctx.Repo.RepoLink + "/branches?page=" + url.QueryEscape(ctx.FormString("page"))) } -func handleCreateBranchError(ctx *context.Context, err error, form *forms.NewBranchForm) { - if models.IsErrProtectedTagName(err) { - ctx.Flash.Error(ctx.Tr("repo.release.tag_name_protected")) - ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) - return - } - - if models.IsErrTagAlreadyExists(err) { - e := err.(models.ErrTagAlreadyExists) - ctx.Flash.Error(ctx.Tr("repo.branch.tag_collision", e.TagName)) - ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) - return - } - if git_model.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) { - ctx.Flash.Error(ctx.Tr("repo.branch.branch_already_exists", form.NewBranchName)) - ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) - return - } - if git_model.IsErrBranchNameConflict(err) { - e := err.(git_model.ErrBranchNameConflict) - ctx.Flash.Error(ctx.Tr("repo.branch.branch_name_conflict", form.NewBranchName, e.BranchName)) - ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) - return - } - if 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.Flash.Error(flashError) - } - ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) - return - } - - ctx.ServerError("CreateNewBranch", err) -} - // CreateBranch creates new branch in repository func CreateBranch(ctx *context.Context) { form := web.GetForm(ctx).(*forms.NewBranchForm) @@ -251,7 +204,50 @@ func CreateBranch(ctx *context.Context) { err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.Repo.CommitID, form.NewBranchName) } if err != nil { - handleCreateBranchError(ctx, err, form) + if models.IsErrProtectedTagName(err) { + ctx.Flash.Error(ctx.Tr("repo.release.tag_name_protected")) + ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) + return + } + + if models.IsErrTagAlreadyExists(err) { + e := err.(models.ErrTagAlreadyExists) + ctx.Flash.Error(ctx.Tr("repo.branch.tag_collision", e.TagName)) + ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) + return + } + if git_model.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) { + ctx.Flash.Error(ctx.Tr("repo.branch.branch_already_exists", form.NewBranchName)) + ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) + return + } + if git_model.IsErrBranchNameConflict(err) { + e := err.(git_model.ErrBranchNameConflict) + ctx.Flash.Error(ctx.Tr("repo.branch.branch_name_conflict", form.NewBranchName, e.BranchName)) + ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) + return + } + if 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.Flash.Error(flashError) + } + ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) + return + } + + ctx.ServerError("CreateNewBranch", err) return } From bc1b2969ef318f2068d48d8957b1d0df52486c7b Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 21 Aug 2024 20:17:07 -0700 Subject: [PATCH 06/40] revert unnecessary change --- services/pull/pull.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/services/pull/pull.go b/services/pull/pull.go index 4f68a3084c220..cba94a711f03e 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -128,13 +128,6 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *iss 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 { @@ -152,7 +145,6 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *iss } } - // leave creating comment last compareInfo, err := baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(), git.BranchPrefix+pr.BaseBranch, pr.GetGitRefName(), false, false) if err != nil { @@ -186,6 +178,13 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *iss return err } + if !pr.IsWorkInProgress(ctx) { + reviewNotifiers, err = issue_service.PullRequestCodeOwnersReview(ctx, issue, pr) + if err != nil { + return err + } + } + return nil }); err != nil { // cleanup: this will only remove the reference, the real commit will be clean up when next GC From e2d7980a844febf95a6ddbe3a211a5fc20423840 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 29 Aug 2024 09:59:51 -0700 Subject: [PATCH 07/40] Fix template --- templates/repo/issue/view_content/sidebar_development.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/repo/issue/view_content/sidebar_development.tmpl b/templates/repo/issue/view_content/sidebar_development.tmpl index 12e3e8bd70f47..d48c7c1e8d20e 100644 --- a/templates/repo/issue/view_content/sidebar_development.tmpl +++ b/templates/repo/issue/view_content/sidebar_development.tmpl @@ -98,4 +98,4 @@ - \ No newline at end of file + From bb988483bd271e81eacf93ff61594e82c0049eb2 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 29 Aug 2024 11:50:03 -0700 Subject: [PATCH 08/40] Allow multiple branches, pull requests --- options/locale/locale_en-US.ini | 1 + .../view_content/sidebar_development.tmpl | 26 ++++++++++++------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index b286e8be13c1d..157565539c4a8 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1627,6 +1627,7 @@ issues.maybefixed = May be fixed by %s issues.create_branch_from_issue_success = Create branch %s from issue successfully issues.pr.completed = Completed issues.pr.conflicted = Merge conflicts +issues.branch.latest = Latest commit %s issues.link.created = Created %s issues.num_participants = %d Participants issues.attachment.open_tab = `Click to see "%s" in a new tab` diff --git a/templates/repo/issue/view_content/sidebar_development.tmpl b/templates/repo/issue/view_content/sidebar_development.tmpl index d48c7c1e8d20e..dd3bc6b2db695 100644 --- a/templates/repo/issue/view_content/sidebar_development.tmpl +++ b/templates/repo/issue/view_content/sidebar_development.tmpl @@ -1,8 +1,8 @@ {{ctx.Locale.Tr "repo.issues.development"}} {{else if and .Branch .DisplayBranch}} - - {{svg "octicon-git-branch" 14}} - - {{.Branch.Name}} - - -
Latest commit {{DateTime "short" .Branch.CommitTime}}
- {{ctx.Locale.Tr "repo.pulls.new"}} +
+
+ {{svg "octicon-git-branch" 14}} + + {{.Branch.Name}} + +
+ +
+
{{ctx.Locale.Tr "repo.issues.branch.latest" (DateTime "short" .Branch.CommitTime)}}
{{end}} {{end}} From e64f2322dedd3e7de6cdf486cc97b3fe3f21b9c0 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 29 Aug 2024 11:53:41 -0700 Subject: [PATCH 09/40] Avoid template lint bug --- templates/repo/issue/view_title.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/repo/issue/view_title.tmpl b/templates/repo/issue/view_title.tmpl index c5ec8c46d24a0..f7a452c913af7 100644 --- a/templates/repo/issue/view_title.tmpl +++ b/templates/repo/issue/view_title.tmpl @@ -119,7 +119,7 @@ {{ctx.Locale.TrN .Issue.NumComments "repo.issues.num_comments_1" "repo.issues.num_comments" .Issue.NumComments}} {{if .MaybeFixed}} - {{$fixedStr := printf "#%d" .MaybeFixed.Issue.Link .MaybeFixed.Index}} + {{$fixedStr := printf `#%d` .MaybeFixed.Issue.Link .MaybeFixed.Index}} · {{ctx.Locale.Tr "repo.issues.maybefixed" ($fixedStr|SafeHTML)}} {{end}} {{end}} From 6b829f77a37cb58ab41b70ea1e1eae1defe3275a Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 29 Aug 2024 12:13:49 -0700 Subject: [PATCH 10/40] Delete dev links when repository/issue/pull/branch deleted --- models/issues/issue_dev_link.go | 4 ++-- models/issues/issue_update.go | 4 ++++ routers/private/hook_post_receive.go | 4 ++++ services/issue/issue.go | 7 +++++++ services/repository/branch.go | 4 ++++ 5 files changed, 21 insertions(+), 2 deletions(-) diff --git a/models/issues/issue_dev_link.go b/models/issues/issue_dev_link.go index 9e6c7cfc25df9..bb816ad160598 100644 --- a/models/issues/issue_dev_link.go +++ b/models/issues/issue_dev_link.go @@ -64,8 +64,8 @@ func CreateIssueDevLink(ctx context.Context, link *IssueDevLink) error { func DeleteIssueDevLinkByBranchName(ctx context.Context, repoID int64, branchName string) error { _, err := db.GetEngine(ctx). - Where("link_type = ? AND link_index = ? AND linked_repo_id = ?", - IssueDevLinkTypeBranch, branchName, repoID). + Where("linked_repo_id = ? AND link_type = ? AND link_index = ?", + repoID, IssueDevLinkTypeBranch, branchName). Delete(new(IssueDevLink)) return err } diff --git a/models/issues/issue_update.go b/models/issues/issue_update.go index 31d76be5e0aea..fffca446b8320 100644 --- a/models/issues/issue_update.go +++ b/models/issues/issue_update.go @@ -718,6 +718,10 @@ func DeleteIssuesByRepoID(ctx context.Context, repoID int64) (attachmentPaths [] return nil, err } + if _, err = sess.In("issue_id", issueIDs).Delete(&IssueDevLink{}); err != nil { + return nil, err + } + var attachments []*repo_model.Attachment err = sess.In("issue_id", issueIDs).Find(&attachments) if err != nil { diff --git a/routers/private/hook_post_receive.go b/routers/private/hook_post_receive.go index 2d1688523c488..d5ddeaf4ec937 100644 --- a/routers/private/hook_post_receive.go +++ b/routers/private/hook_post_receive.go @@ -114,6 +114,10 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) { }) return } + + if err := issues_model.DeleteIssueDevLinkByBranchName(ctx, repo.ID, update.RefFullName.BranchName()); err != nil { + log.Error("Failed to DeleteIssueDevLinkByBranchName: %s/%s %s Error: %v", ownerName, repoName, update.RefFullName.BranchName(), err) + } } else { branchesToSync = append(branchesToSync, update) diff --git a/services/issue/issue.go b/services/issue/issue.go index 72ea66c8d98c5..4180c7264d88c 100644 --- a/services/issue/issue.go +++ b/services/issue/issue.go @@ -283,6 +283,12 @@ func deleteIssue(ctx context.Context, issue *issues_model.Issue) error { return err } + if issue.IsPull { + if err := issues_model.DeleteIssueDevLinkByPullRequestID(ctx, issue.ID); err != nil { + return err + } + } + // find attachments related to this issue and remove them if err := issue.LoadAttributes(ctx); err != nil { return err @@ -311,6 +317,7 @@ func deleteIssue(ctx context.Context, issue *issues_model.Issue) error { &issues_model.Comment{RefIssueID: issue.ID}, &issues_model.IssueDependency{DependencyID: issue.ID}, &issues_model.Comment{DependentIssueID: issue.ID}, + &issues_model.IssueDevLink{IssueID: issue.ID}, ); err != nil { return err } diff --git a/services/repository/branch.go b/services/repository/branch.go index 7fc99930776a1..a8454471addb1 100644 --- a/services/repository/branch.go +++ b/services/repository/branch.go @@ -501,6 +501,10 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R return err } + if err := issues_model.DeleteIssueDevLinkByBranchName(ctx, repo.ID, branchName); err != nil { + return err + } + return gitRepo.DeleteBranch(branchName, git.DeleteBranchOptions{ Force: true, }) From 0208f5bc4ce761ab54af1f29ff9f0422a08c9911 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 29 Aug 2024 12:43:47 -0700 Subject: [PATCH 11/40] Add ref issue when creating pull request from issue --- options/locale/locale_en-US.ini | 1 + routers/web/repo/compare.go | 15 +++++++++++++++ .../issue/view_content/sidebar_development.tmpl | 2 +- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 157565539c4a8..c19788bf7dcf5 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1627,6 +1627,7 @@ issues.maybefixed = May be fixed by %s issues.create_branch_from_issue_success = Create branch %s from issue successfully issues.pr.completed = Completed issues.pr.conflicted = Merge conflicts +issues.pr.not_exist_issue = Reference issue does not exist. issues.branch.latest = Latest commit %s issues.link.created = Created %s issues.num_participants = %d Participants diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index e6a04782e5bbf..91f69c3c25dae 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -845,6 +845,21 @@ func CompareDiff(ctx *context.Context) { ctx.Data["AllowMaintainerEdit"] = false } + refIssueIndex := ctx.FormInt64("ref_issue_index") + if refIssueIndex > 0 { + refIssue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, refIssueIndex) + if err != nil { + ctx.Flash.Warning(ctx.Tr("repo.issues.pr.not_exist_issue"), true) + } else { + keyword := "Resolve" + if len(setting.Repository.PullRequest.CloseKeywords) > 0 { + keyword = setting.Repository.PullRequest.CloseKeywords[0] + } + ctx.Data["TitleQuery"] = fmt.Sprintf("%s %s", keyword, refIssue.Title) + ctx.Data["BodyQuery"] = fmt.Sprintf("%s #%d", keyword, refIssueIndex) + } + } + ctx.HTML(http.StatusOK, tplCompare) } diff --git a/templates/repo/issue/view_content/sidebar_development.tmpl b/templates/repo/issue/view_content/sidebar_development.tmpl index dd3bc6b2db695..5b451aa989b2d 100644 --- a/templates/repo/issue/view_content/sidebar_development.tmpl +++ b/templates/repo/issue/view_content/sidebar_development.tmpl @@ -33,7 +33,7 @@ From 3abb72946c479d7b80415918f1677f44c16cea99 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 29 Aug 2024 12:55:03 -0700 Subject: [PATCH 12/40] Revert unnecessary change --- services/pull/pull.go | 1 - 1 file changed, 1 deletion(-) diff --git a/services/pull/pull.go b/services/pull/pull.go index cba94a711f03e..9c518ef5435bf 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -184,7 +184,6 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *iss return err } } - return nil }); err != nil { // cleanup: this will only remove the reference, the real commit will be clean up when next GC From 6e0bc0d3be879bbd25596cc3569bbf0d275c240b Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 2 Sep 2024 23:48:53 -0700 Subject: [PATCH 13/40] Fix test --- routers/web/repo/issue.go | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index 1217a6e65915c..859bc27c2b16f 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -2075,25 +2075,27 @@ func ViewIssue(ctx *context.Context) { return user_service.CanBlockUser(ctx, ctx.Doer, blocker, blockee) } - forkedRepos, err := repo_model.FindUserOrgForks(ctx, ctx.Repo.Repository.ID, ctx.Doer.ID) - if err != nil { - ctx.ServerError("FindUserOrgForks", err) - return - } + if ctx.IsSigned { + forkedRepos, err := repo_model.FindUserOrgForks(ctx, ctx.Repo.Repository.ID, ctx.Doer.ID) + if err != nil { + ctx.ServerError("FindUserOrgForks", err) + return + } - ctx.Data["AllowedRepos"] = append(forkedRepos, ctx.Repo.Repository) + ctx.Data["AllowedRepos"] = append(forkedRepos, ctx.Repo.Repository) - devLinks, err := issue_service.FindIssueDevLinksByIssue(ctx, issue) - if err != nil { - ctx.ServerError("FindIssueDevLinksByIssue", err) - return - } - 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 + devLinks, err := issue_service.FindIssueDevLinksByIssue(ctx, issue) + if err != nil { + ctx.ServerError("FindIssueDevLinksByIssue", err) + return + } + 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 + } } } } From 003707ff9761538e94b32de67a0f20557e6502ba Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 8 Sep 2024 22:19:58 -0700 Subject: [PATCH 14/40] Improve the name of branch creation dialog --- options/locale/locale_en-US.ini | 1 + templates/repo/issue/view_content/sidebar_development.tmpl | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 7dfee41bbf0ca..bc054718e1463 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1625,6 +1625,7 @@ issues.label.filter_sort.reverse_by_size = Largest size issues.development = Development issues.maybefixed = May be fixed by %s issues.create_branch_from_issue_success = Create branch %s from issue successfully +issues.base_branch = Base Branch issues.pr.completed = Completed issues.pr.conflicted = Merge conflicts issues.pr.not_exist_issue = Reference issue does not exist. diff --git a/templates/repo/issue/view_content/sidebar_development.tmpl b/templates/repo/issue/view_content/sidebar_development.tmpl index 5b451aa989b2d..bc1df4b2d72f3 100644 --- a/templates/repo/issue/view_content/sidebar_development.tmpl +++ b/templates/repo/issue/view_content/sidebar_development.tmpl @@ -78,7 +78,7 @@
- + -
{{ctx.Locale.Tr "repo.issues.branch.latest" (DateTime "short" .Branch.CommitTime)}}
+
{{ctx.Locale.Tr "repo.issues.branch.latest" (DateUtils.AbsoluteShort .Branch.CommitTime)}}
{{end}} {{end}}
diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index 948f5b73ade9b..42b93478d4af2 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -15,7 +15,7 @@ {{end}} {{template "repo/issue/sidebar/assignee_list" $.IssuePageMetaData}} - {{template "repo/issue/view_content/sidebar_development" .}} + {{template "repo/issue/sidebar/development" .}} {{template "repo/issue/sidebar/participant_list" $}} {{template "repo/issue/sidebar/watch_notification" $}} {{template "repo/issue/sidebar/stopwatch_timetracker" $}} From 703eebfa92a8d0182a0ba6b015f07baef3847715 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 28 Nov 2024 15:54:27 -0800 Subject: [PATCH 25/40] Adjust development sidebar --- templates/repo/issue/sidebar/development.tmpl | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/templates/repo/issue/sidebar/development.tmpl b/templates/repo/issue/sidebar/development.tmpl index 7f9ba6ed63c38..fc18cf05fdd79 100644 --- a/templates/repo/issue/sidebar/development.tmpl +++ b/templates/repo/issue/sidebar/development.tmpl @@ -1,26 +1,28 @@ {{if not .Issue.IsPull}} -
+
+ {{ctx.Locale.Tr "repo.issues.development"}}