Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a new section named development in issue view sidebar to interact with branch/pr #31899

Draft
wants to merge 63 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
62fda25
Add a new section named development in issue view sidebar to interact…
lunny Aug 21, 2024
2361ec5
Improvements for creating branch model
lunny Aug 22, 2024
b4eac75
Some improvements
lunny Aug 22, 2024
359e660
some improvements
lunny Aug 22, 2024
9ea3376
revert unnecessary change
lunny Aug 22, 2024
bc1b296
revert unnecessary change
lunny Aug 22, 2024
e2d7980
Fix template
lunny Aug 29, 2024
6feb5a1
Merge branch 'main' into lunny/issue_dev
lunny Aug 29, 2024
bb98848
Allow multiple branches, pull requests
lunny Aug 29, 2024
e64f232
Avoid template lint bug
lunny Aug 29, 2024
6b829f7
Delete dev links when repository/issue/pull/branch deleted
lunny Aug 29, 2024
0208f5b
Add ref issue when creating pull request from issue
lunny Aug 29, 2024
3abb729
Revert unnecessary change
lunny Aug 29, 2024
6e0bc0d
Fix test
lunny Sep 3, 2024
5fb581a
Merge branch 'main' into lunny/issue_dev
lunny Sep 3, 2024
003707f
Improve the name of branch creation dialog
lunny Sep 9, 2024
837526a
Merge branch 'main' into lunny/issue_dev
lunny Sep 9, 2024
8951291
Merge branch 'main' into lunny/issue_dev
techknowlogick Sep 24, 2024
abe592c
Fix repository list permissions
lunny Sep 30, 2024
cb37b59
Merge branch 'main' into lunny/issue_dev
lunny Sep 30, 2024
e49445b
Merge branch 'lunny/issue_dev' of github.com:lunny/gitea into lunny/i…
lunny Sep 30, 2024
0e05274
Don't use SafeHTML
lunny Sep 30, 2024
cbeed11
Merge branch 'main' into lunny/issue_dev
lunny Oct 2, 2024
86e4f29
Update routers/web/repo/issue_dev.go
lunny Oct 10, 2024
f11fc41
Update routers/web/repo/issue_dev.go
lunny Oct 10, 2024
7fd210b
Add missed language content
lunny Oct 10, 2024
30d4010
Some improvements
lunny Oct 10, 2024
66681c3
merge if conditions
lunny Oct 10, 2024
f3c1634
Merge branch 'main' into lunny/issue_dev
lunny Oct 13, 2024
c8a8fc6
Don't display create branch link for closed issue
lunny Oct 22, 2024
f17020c
Merge branch 'main' into lunny/issue_dev
lunny Oct 22, 2024
570f338
Merge branch 'lunny/issue_dev' of github.com:lunny/gitea into lunny/i…
lunny Oct 22, 2024
d3f3fb1
Merge branch 'main' into lunny/issue_dev
lunny Nov 6, 2024
fcc2c57
Merge branch 'main' into lunny/issue_dev
lunny Nov 6, 2024
72d06ee
Remove issue.ref
lunny Nov 28, 2024
3d8ed0e
Merge branch 'main' into lunny/issue_dev
lunny Nov 28, 2024
121b823
Follow template change
lunny Nov 28, 2024
703eebf
Adjust development sidebar
lunny Nov 28, 2024
ab6d2ed
Display forked repository's branch
lunny Nov 29, 2024
79cb889
display pull request on float window
lunny Nov 29, 2024
b3086c9
improve the popup branch create window
lunny Nov 29, 2024
e5b581c
More ui improvements
lunny Nov 29, 2024
c0d1960
Fix lint
lunny Nov 29, 2024
267a2ec
fill default branch name
lunny Nov 29, 2024
17956ae
Make toast in front of modal
lunny Nov 29, 2024
f52a57d
Fix bug
lunny Dec 3, 2024
f77d7e7
Merge branch 'main' into lunny/issue_dev
lunny Dec 3, 2024
35b7d32
add margin top 2 for item
lunny Dec 4, 2024
3556305
Merge branch 'main' into lunny/issue_dev
lunny Dec 4, 2024
029a444
Fix dropdown list
lunny Dec 4, 2024
66dbadc
Merge branch 'main' into lunny/issue_dev
lunny Dec 4, 2024
051b42a
Add ellipsis
lunny Dec 4, 2024
f2a28d0
Add missing locale string
lunny Dec 4, 2024
478fbd5
Merge branch 'main' into lunny/issue_dev
lunny Dec 4, 2024
d869d32
Merge branch 'main' into lunny/issue_dev
lunny Dec 5, 2024
a81c785
Merge branch 'main' into lunny/issue_dev
lunny Dec 8, 2024
da7f700
Fix create branch permission
lunny Dec 8, 2024
832b78e
Empty, mirror & archived repository will not allow create branch
lunny Dec 10, 2024
2d49aec
Merge branch 'main' into lunny/issue_dev
lunny Dec 10, 2024
3addd3a
Move migration to v1.24
lunny Dec 31, 2024
a84034a
Merge branch 'main' into lunny/issue_dev
lunny Dec 31, 2024
479364a
Merge branch 'main' into lunny/issue_dev
lunny Feb 8, 2025
b3e6d4b
Merge branch 'main' into lunny/issue_dev
lunny Feb 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions models/issues/issue_dev_link.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// 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
Copy link
Contributor

@wxiaoguang wxiaoguang Dec 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just tried to play with GitHub's development sidebar for a while. I can see there could be a lot of edge cases:

  1. create a new branch 123-my-issue, rename the branch to 123-my-issue-other, the link disappears, rename another branch to 123-my-issue, the link won't appear again.
  2. create a new branch 123-my-issue, create a PR from 123-my-issue, then the link is updated to PR, the branch link is replaced.

I believe these details need enough documents(comments) and tests.

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:"-"`
DisplayBranch bool `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("linked_repo_id = ? AND link_type = ? AND link_index = ?",
repoID, IssueDevLinkTypeBranch, branchName).
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
}
4 changes: 4 additions & 0 deletions models/issues/issue_update.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@ func prepareMigrationTasks() []*migration {
newMigration(306, "Add BlockAdminMergeOverride to ProtectedBranch", v1_23.AddBlockAdminMergeOverrideBranchProtection),
newMigration(307, "Fix milestone deadline_unix when there is no due date", v1_23.FixMilestoneNoDueDate),
newMigration(308, "Add index(user_id, is_deleted) for action table", v1_23.AddNewIndexForUserDashboard),
newMigration(309, "Add table issue_dev_link", v1_23.CreateTableIssueDevLink),
}
return preparedMigrations
}
Expand Down
22 changes: 22 additions & 0 deletions models/migrations/v1_23/v309.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// 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
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))
}
10 changes: 10 additions & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -1635,6 +1635,16 @@ 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.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.
issues.branch.latest = Latest commit %s
issues.link.created = Created %s
issues.create_branch_from_issue_error_is_pull = Issue links cannot be created with pull request
issues.num_participants = %d Participants
issues.attachment.open_tab = `Click to see "%s" in a new tab`
issues.attachment.download = `Click to download "%s"`
Expand Down
4 changes: 4 additions & 0 deletions routers/private/hook_post_receive.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
15 changes: 15 additions & 0 deletions routers/web/repo/compare.go
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,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)
}

Expand Down
35 changes: 35 additions & 0 deletions routers/web/repo/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -2077,6 +2077,41 @@ func ViewIssue(ctx *context.Context) {
return user_service.CanBlockUser(ctx, ctx.Doer, blocker, blockee)
}

if ctx.IsSigned {
forkedRepos, err := repo_model.GetForksByUserAndOrgs(ctx, ctx.Doer, ctx.Repo.Repository)
if err != nil {
ctx.ServerError("GetForksByUserAndOrgs", err)
return
}
allowedRepos := make([]*repo_model.Repository, 0, len(forkedRepos)+1)
for _, repo := range append(forkedRepos, ctx.Repo.Repository) {
perm, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
return
}
if perm.CanWrite(unit.TypeCode) {
allowedRepos = append(allowedRepos, repo)
}
}

ctx.Data["AllowedRepos"] = allowedRepos

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 &&
!(link.PullRequest.Issue.IsClosed && !link.PullRequest.HasMerged) {
ctx.Data["MaybeFixed"] = link.PullRequest
break
}
}
}

ctx.HTML(http.StatusOK, tplIssueView)
}

Expand Down
111 changes: 111 additions & 0 deletions routers/web/repo/issue_dev.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

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"
)

func CreateBranchFromIssue(ctx *context.Context) {
if ctx.HasError() { // form binding error check
ctx.JSONError(ctx.GetErrMsg())
return
}

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.JSONRedirect(issue.Link())
return
}

form := web.GetForm(ctx).(*forms.NewBranchForm)
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("OpenRepository", err)
return
}
defer gitRepo.Close()
}

perm, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
return
}

canCreateBranch := perm.CanWrite(unit_model.TypeCode) && repo.CanCreateBranch()
if !canCreateBranch {
ctx.Error(http.StatusForbidden, "No permission to create branch in this repository")
return
}

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)
return
}

ctx.Flash.Success(ctx.Tr("repo.issues.create_branch_from_issue_success", ctx.Repo.BranchName))
ctx.JSONRedirect(issue.Link())
}
1 change: 1 addition & 0 deletions routers/web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -1230,6 +1230,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() {
Expand Down
8 changes: 5 additions & 3 deletions services/forms/repo_branch_form.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ 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"`
RepoID int64
SourceBranchName string
CurrentPath string
CreateTag bool
}

// Validate validates the fields
Expand Down
Loading
Loading