- 
          
- 
                Notifications
    You must be signed in to change notification settings 
- Fork 6.2k
Add a new section named development in issue view sidebar to interact with branch/pr #31899
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
base: main
Are you sure you want to change the base?
Changes from 59 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
              0735d3c
              1787e02
              5d73889
              7ea492c
              793a9e6
              43cdc71
              b580b59
              27e0450
              c60d6fc
              1e4da5b
              1d9b7a1
              c32d4aa
              ed8c578
              94424b7
              bad9ae0
              eadebf3
              9b5e3c5
              1ac03bb
              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 | ||
|          | ||
| 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 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)) | ||
| } | 
| 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.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_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()) | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
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.