Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 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
260eee2
Merge branch 'main' into lunny/issue_dev
lunny Mar 28, 2025
097c649
Merge branch 'main' into lunny/issue_dev
lunny Mar 29, 2025
7b4c3e2
Fix bug
lunny Mar 29, 2025
eaf998b
Fix bug
lunny Mar 29, 2025
0735d3c
Merge branch 'main' into lunny/issue_dev
lunny Jun 9, 2025
1787e02
Merge branch 'main' into lunny/issue_dev
lunny Aug 28, 2025
5d73889
Use branch id or pull request id as dev link id
lunny Aug 29, 2025
7ea492c
Merge branch 'main' into lunny/issue_dev
lunny Aug 29, 2025
793a9e6
Fix lint
lunny Aug 30, 2025
43cdc71
Merge branch 'main' into lunny/issue_dev
lunny Aug 30, 2025
b580b59
Fix test
lunny Aug 30, 2025
27e0450
Fix test
lunny Aug 30, 2025
c60d6fc
Fix test
lunny Aug 30, 2025
1e4da5b
Fix test
lunny Aug 30, 2025
1d9b7a1
Fix test
lunny Aug 31, 2025
c32d4aa
Merge branch 'main' into lunny/issue_dev
lunny Oct 1, 2025
ed8c578
Merge branch 'main' into lunny/issue_dev
lunny Oct 27, 2025
94424b7
Fix lint
lunny Oct 28, 2025
bad9ae0
Fix test
lunny Oct 28, 2025
eadebf3
Merge branch 'main' into lunny/issue_dev
lunny Oct 28, 2025
9b5e3c5
Fix test
lunny Oct 28, 2025
1ac03bb
Fix lint
lunny Oct 28, 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
24 changes: 24 additions & 0 deletions models/fixtures/branch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -249,3 +249,27 @@
is_deleted: false
deleted_by_id: 0
deleted_unix: 0

-
id: 29
repo_id: 10
name: 'develop'
commit_id: '65f1bf27bc3bf70f64657658635e66094edbcb4d'
commit_message: 'Initial commit'
commit_time: 1489927679
pusher_id: 2
is_deleted: false
deleted_by_id: 0
deleted_unix: 0

-
id: 30
repo_id: 1
name: 'pr-to-update'
commit_id: '62fb502a7172d4453f0322a2cc85bddffa57f07a'
commit_message: 'add WoW File'
commit_time: 1579204295
pusher_id: 2
is_deleted: false
deleted_by_id: 0
deleted_unix: 0
13 changes: 13 additions & 0 deletions models/git/branch.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,19 @@ func init() {
db.RegisterModel(new(RenamedBranch))
}

func GetBranchByID(ctx context.Context, branchID int64) (*Branch, error) {
var branch Branch
has, err := db.GetEngine(ctx).ID(branchID).Get(&branch)
if err != nil {
return nil, err
} else if !has {
return nil, ErrBranchNotExist{
RepoID: branch.RepoID,
}
}
return &branch, nil
}

func GetBranch(ctx context.Context, repoID int64, branchName string) (*Branch, error) {
var branch Branch
has, err := db.GetEngine(ctx).Where("repo_id=?", repoID).And("name=?", branchName).Get(&branch)
Expand Down
59 changes: 59 additions & 0 deletions models/issues/issue_dev_link.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package issues

import (
"context"

"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
LinkID int64 // branch id in branch table or the pull request id(not issue if of the pull request)
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 CreateIssueDevLink(ctx context.Context, link *IssueDevLink) error {
_, err := db.GetEngine(ctx).Insert(link)
return err
}
6 changes: 5 additions & 1 deletion models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"code.gitea.io/gitea/models/migrations/v1_23"
"code.gitea.io/gitea/models/migrations/v1_24"
"code.gitea.io/gitea/models/migrations/v1_25"
"code.gitea.io/gitea/models/migrations/v1_26"
"code.gitea.io/gitea/models/migrations/v1_6"
"code.gitea.io/gitea/models/migrations/v1_7"
"code.gitea.io/gitea/models/migrations/v1_8"
Expand Down Expand Up @@ -394,7 +395,10 @@ func prepareMigrationTasks() []*migration {
// Gitea 1.24.0 ends at database version 321
newMigration(321, "Use LONGTEXT for some columns and fix review_state.updated_files column", v1_25.UseLongTextInSomeColumnsAndFixBugs),
newMigration(322, "Extend comment tree_path length limit", v1_25.ExtendCommentTreePathLength),
newMigration(323, "Add support for actions concurrency", v1_25.AddActionsConcurrency),

// Gitea 1.25.0 ends at database version 323
newMigration(323, "Add support for actions concurrency", v1_26.AddActionsConcurrency),
newMigration(324, "Add table issue_dev_link", v1_26.CreateTableIssueDevLink),
}
return preparedMigrations
}
Expand Down
14 changes: 14 additions & 0 deletions models/migrations/v1_26/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package v1_26

import (
"testing"

"code.gitea.io/gitea/models/migrations/base"
)

func TestMain(m *testing.M) {
base.MainTest(m)
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package v1_25
package v1_26

import (
"xorm.io/xorm"
Expand Down
22 changes: 22 additions & 0 deletions models/migrations/v1_26/v324.go
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_26

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
LinkID int64 // branch id in branch table or pull request id
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
}
return x.Sync(new(IssueDevLink))
}
20 changes: 20 additions & 0 deletions models/migrations/v1_26/v324_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package v1_26

import (
"testing"

"code.gitea.io/gitea/models/migrations/base"

"github.com/stretchr/testify/assert"
)

func Test_CreateTableIssueDevLink(t *testing.T) {
// Prepare and load the testing database
x, deferable := base.PrepareTestEnv(t, 0)
defer deferable()

assert.NoError(t, CreateTableIssueDevLink(x))
}
2 changes: 1 addition & 1 deletion models/repo/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -632,7 +632,7 @@ func (repo *Repository) IsOwnedBy(userID int64) bool {

// CanCreateBranch returns true if repository meets the requirements for creating new branches.
func (repo *Repository) CanCreateBranch() bool {
return !repo.IsMirror
return !repo.IsMirror && !repo.IsArchived
}

// CanEnablePulls returns true if repository meets the requirements of accepting pulls.
Expand Down
12 changes: 12 additions & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -1695,6 +1695,17 @@ 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.create_branch_from_repository = Repository the branch to be created
issues.base_branch = Base Branch in this repository
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 Expand Up @@ -2765,6 +2776,7 @@ branch.create_from = from "%s"
branch.create_success = Branch "%s" has been created.
branch.branch_already_exists = Branch "%s" already exists in this repository.
branch.branch_name_conflict = Branch name "%s" conflicts with the already existing branch "%s".
branch.branch_not_exist = Branch "%s" do not exists in this repository.
branch.tag_collision = Branch "%s" cannot be created as a tag with same name already exists in the repository.
branch.deleted_by = Deleted by %s
branch.restore_success = Branch "%s" has been restored.
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 @@ -113,6 +113,10 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
})
return
}

if err := repo_service.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 @@ -869,6 +869,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
112 changes: 112 additions & 0 deletions routers/web/repo/issue_dev.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// 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/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
// 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
}
}

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, 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
}

branch, err := git_model.GetBranch(ctx, repo.ID, form.NewBranchName)
if err != nil {
ctx.ServerError("GetBranch", err)
return
}

if err := issues_model.CreateIssueDevLink(ctx, &issues_model.IssueDevLink{
IssueID: issue.ID,
LinkType: issues_model.IssueDevLinkTypeBranch,
LinkedRepoID: repo.ID,
LinkID: branch.ID,
}); 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())
}
Loading