-
-
Notifications
You must be signed in to change notification settings - Fork 5.7k
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
base: main
Are you sure you want to change the base?
Changes from all commits
62fda25
2361ec5
b4eac75
359e660
9ea3376
bc1b296
e2d7980
6feb5a1
bb98848
e64f232
6b829f7
0208f5b
3abb729
6e0bc0d
5fb581a
003707f
837526a
8951291
abe592c
cb37b59
e49445b
0e05274
cbeed11
86e4f29
f11fc41
7fd210b
30d4010
66681c3
f3c1634
c8a8fc6
f17020c
570f338
d3f3fb1
fcc2c57
72d06ee
3d8ed0e
121b823
703eebf
ab6d2ed
79cb889
b3086c9
e5b581c
c0d1960
267a2ec
17956ae
f52a57d
f77d7e7
35b7d32
3556305
029a444
66dbadc
051b42a
f2a28d0
478fbd5
d869d32
a81c785
da7f700
832b78e
2d49aec
3addd3a
a84034a
479364a
b3e6d4b
260eee2
097c649
7b4c3e2
eaf998b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
// 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have branch table, so this can be int64? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In future PRs, we can link a commit to this issue. So it will be kept to support that. BTW: Azure supports linking to a commit. I think for some development model, they just push commits with no feature branches or pull requests, they can use this feature. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Then it can be split into to columns. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The conversion is on the memory and we don't know what data we will store according to different LinkType. And this column will never be used as index, so we don't need to consider performance or as search condition. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I also agree it doesn't seem to be a good name or proper design. There are already too many "index" in code: search index, database index, issue index, etc. And using "string" to store "int" in database would cause problems, if you write |
||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` | ||
Repo *repo_model.Repository `xorm:"-"` // current repo of issue | ||
LinkedRepo *repo_model.Repository `xorm:"-"` | ||
PullRequest *PullRequest `xorm:"-"` | ||
Branch *git_model.Branch `xorm:"-"` | ||
DisplayBranch bool `xorm:"-"` | ||
} | ||
|
||
func init() { | ||
db.RegisterModel(new(IssueDevLink)) | ||
} | ||
|
||
func (i *IssueDevLink) BranchFullName() string { | ||
if i.Repo.ID == i.LinkedRepo.ID { | ||
return i.Branch.Name | ||
} | ||
return i.LinkedRepo.FullName() + ":" + i.Branch.Name | ||
} | ||
|
||
// 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 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// Copyright 2025 The Gitea Authors. All rights reserved. | ||
// SPDX-License-Identifier: MIT | ||
|
||
package v1_24 //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)) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
// 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")) | ||
lunny marked this conversation as resolved.
Show resolved
Hide resolved
|
||
ctx.JSONRedirect(issue.Link()) | ||
return | ||
} | ||
|
||
form := web.GetForm(ctx).(*forms.NewBranchForm) | ||
repo := ctx.Repo.Repository | ||
gitRepo := ctx.Repo.GitRepo | ||
// if create branch in a forked repository | ||
if form.RepoID > 0 && form.RepoID != repo.ID { | ||
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.HTTPError(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_model.IsErrBranchNotExist(err): | ||
ctx.JSONError(ctx.Tr("repo.branch.branch_not_exist", form.SourceBranchName)) | ||
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", form.NewBranchName)) | ||
ctx.JSONRedirect(issue.Link()) | ||
} |
There was a problem hiding this comment.
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:
123-my-issue
, rename the branch to123-my-issue-other
, the link disappears, rename another branch to123-my-issue
, the link won't appear again.123-my-issue
, create a PR from123-my-issue
, then the link is updated to PR, the branch link is replaced.I believe these details need enough documents(comments) and tests.