mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-04 02:04:11 +01:00 
			
		
		
		
	Merge 1ac03bb35e9053819b0c59c0543eae6106afb968 into 98ff7d077376db1225f266095788c6bd9414288a
This commit is contained in:
		
						commit
						a908cb26e3
					
				@ -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
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										59
									
								
								models/issues/issue_dev_link.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								models/issues/issue_dev_link.go
									
									
									
									
									
										Normal file
									
								
							@ -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
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
@ -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"
 | 
			
		||||
@ -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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										14
									
								
								models/migrations/v1_26/main_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								models/migrations/v1_26/main_test.go
									
									
									
									
									
										Normal file
									
								
							@ -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)
 | 
			
		||||
}
 | 
			
		||||
@ -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"
 | 
			
		||||
							
								
								
									
										22
									
								
								models/migrations/v1_26/v324.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								models/migrations/v1_26/v324.go
									
									
									
									
									
										Normal file
									
								
							@ -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
									
								
								models/migrations/v1_26/v324_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								models/migrations/v1_26/v324_test.go
									
									
									
									
									
										Normal file
									
								
							@ -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))
 | 
			
		||||
}
 | 
			
		||||
@ -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.
 | 
			
		||||
 | 
			
		||||
@ -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"`
 | 
			
		||||
@ -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.
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										112
									
								
								routers/web/repo/issue_dev.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								routers/web/repo/issue_dev.go
									
									
									
									
									
										Normal file
									
								
							@ -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())
 | 
			
		||||
}
 | 
			
		||||
@ -383,6 +383,7 @@ func ViewIssue(ctx *context.Context) {
 | 
			
		||||
	prepareFuncs := []func(*context.Context, *issues_model.Issue){
 | 
			
		||||
		prepareIssueViewContent,
 | 
			
		||||
		prepareIssueViewCommentsAndSidebarParticipants,
 | 
			
		||||
		prepareIssueViewSidebarDevLinks,
 | 
			
		||||
		prepareIssueViewSidebarWatch,
 | 
			
		||||
		prepareIssueViewSidebarTimeTracker,
 | 
			
		||||
		prepareIssueViewSidebarDependency,
 | 
			
		||||
@ -1011,3 +1012,50 @@ func prepareIssueViewContent(ctx *context.Context, issue *issues_model.Issue) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func prepareIssueViewSidebarDevLinks(ctx *context.Context, issue *issues_model.Issue) {
 | 
			
		||||
	if issue.IsPull {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !ctx.IsSigned {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Get all possible repositories for creating branch model dropdown list
 | 
			
		||||
	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
 | 
			
		||||
	ctx.Data["ShowCreateBranchLink"] = !ctx.Repo.Repository.IsEmpty &&
 | 
			
		||||
		ctx.Repo.Repository.CanCreateBranch() &&
 | 
			
		||||
		len(allowedRepos) > 0 && !issue.IsClosed
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1281,7 +1281,8 @@ func registerWebRoutes(m *web.Router) {
 | 
			
		||||
				m.Post("/unlock", reqRepoIssuesOrPullsWriter, repo.UnlockIssue)
 | 
			
		||||
				m.Post("/delete", reqRepoAdmin, repo.DeleteIssue)
 | 
			
		||||
				m.Post("/content-history/soft-delete", repo.SoftDeleteContentHistory)
 | 
			
		||||
			})
 | 
			
		||||
				m.Post("/create_branch", web.Bind(forms.NewBranchForm{}), repo.CreateBranchFromIssue)
 | 
			
		||||
			}, context.RepoMustNotBeArchived())
 | 
			
		||||
 | 
			
		||||
			m.Post("/attachments", repo.UploadIssueAttachment)
 | 
			
		||||
			m.Post("/attachments/remove", repo.DeleteAttachment)
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										75
									
								
								services/issue/dev_link.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								services/issue/dev_link.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,75 @@
 | 
			
		||||
// Copyright 2024 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package issue
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"sort"
 | 
			
		||||
 | 
			
		||||
	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) {
 | 
			
		||||
	devLinks, err := issues_model.FindIssueDevLinksByIssueID(ctx, issue.ID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := issue.LoadRepo(ctx); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sort.Slice(devLinks, func(i, j int) bool {
 | 
			
		||||
		return devLinks[j].LinkType != issues_model.IssueDevLinkTypePullRequest
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	branchPRExists := make(container.Set[string])
 | 
			
		||||
 | 
			
		||||
	for _, link := range devLinks {
 | 
			
		||||
		link.Repo = issue.Repo
 | 
			
		||||
		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:
 | 
			
		||||
			pull, err := issues_model.GetPullRequestByID(ctx, link.LinkID)
 | 
			
		||||
			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
 | 
			
		||||
			branchPRExists.Add(fmt.Sprintf("%d-%d-%s", link.LinkedRepoID, link.LinkType, pull.HeadBranch))
 | 
			
		||||
		case issues_model.IssueDevLinkTypeBranch:
 | 
			
		||||
			branch, err := git_model.GetBranchByID(ctx, link.LinkID)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			link.Branch = branch
 | 
			
		||||
			link.Branch.Repo = link.LinkedRepo
 | 
			
		||||
			link.DisplayBranch = !branchPRExists.Contains(fmt.Sprintf("%d-%d-%d", link.LinkedRepoID, link.LinkType, link.LinkID))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return devLinks, nil
 | 
			
		||||
}
 | 
			
		||||
@ -286,6 +286,16 @@ func deleteIssue(ctx context.Context, issue *issues_model.Issue) ([]string, erro
 | 
			
		||||
				issue.MilestoneID, err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if issue.IsPull {
 | 
			
		||||
			if err := issue.LoadPullRequest(ctx); err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			if _, err := db.GetEngine(ctx).Where("link_type = ? AND link_id = ?", issues_model.IssueDevLinkTypePullRequest, issue.PullRequest.ID).
 | 
			
		||||
				Delete(new(issues_model.IssueDevLink)); err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := activities_model.DeleteIssueActions(ctx, issue.RepoID, issue.ID, issue.Index); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
@ -320,6 +330,7 @@ func deleteIssue(ctx context.Context, issue *issues_model.Issue) ([]string, erro
 | 
			
		||||
			&issues_model.IssueDependency{DependencyID: issue.ID},
 | 
			
		||||
			&issues_model.Comment{DependentIssueID: issue.ID},
 | 
			
		||||
			&issues_model.IssuePin{IssueID: issue.ID},
 | 
			
		||||
			&issues_model.IssueDevLink{IssueID: issue.ID},
 | 
			
		||||
		); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@ -127,6 +127,35 @@ func NewPullRequest(ctx context.Context, opts *NewPullRequestOptions) error {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// add dev links
 | 
			
		||||
		if pr.Flow == issues_model.PullRequestFlowGithub {
 | 
			
		||||
			branch, err := git_model.GetBranch(ctx, pr.HeadRepoID, pr.HeadBranch)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			devLinks := make(issues_model.IssueDevLinks, 0, 5)
 | 
			
		||||
			if err := db.GetEngine(ctx).
 | 
			
		||||
				Join("INNER", "issue", "issue_dev_link.issue_id = issue.id").
 | 
			
		||||
				Where("link_type = ? AND link_id = ? AND linked_repo_id = ?",
 | 
			
		||||
					issues_model.IssueDevLinkTypeBranch, branch.ID, issue.RepoID).
 | 
			
		||||
				And("issue.repo_id=?", pr.HeadRepoID).
 | 
			
		||||
				Find(&devLinks); 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,
 | 
			
		||||
					LinkID:       pr.ID,
 | 
			
		||||
				}); err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Update Commit Divergence
 | 
			
		||||
		err = syncCommitDivergence(ctx, pr)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
 | 
			
		||||
@ -508,6 +508,22 @@ func CanDeleteBranch(ctx context.Context, repo *repo_model.Repository, branchNam
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func DeleteIssueDevLinkByBranchName(ctx context.Context, repoID int64, branchName string) error {
 | 
			
		||||
	awBranch, err := git_model.GetBranch(ctx, repoID, branchName)
 | 
			
		||||
	if err != nil && !git_model.IsErrBranchNotExist(err) {
 | 
			
		||||
		return fmt.Errorf("GetBranch: %vc", err)
 | 
			
		||||
	}
 | 
			
		||||
	if awBranch == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err = db.GetEngine(ctx).
 | 
			
		||||
		Where("linked_repo_id = ? AND link_type = ? AND link_id = ?",
 | 
			
		||||
			repoID, issues_model.IssueDevLinkTypeBranch, awBranch.ID).
 | 
			
		||||
		Delete(new(issues_model.IssueDevLink))
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeleteBranch delete branch
 | 
			
		||||
func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, branchName string, pr *issues_model.PullRequest) error {
 | 
			
		||||
	err := repo.MustNotBeArchived()
 | 
			
		||||
@ -539,6 +555,9 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := DeleteIssueDevLinkByBranchName(ctx, repo.ID, branchName); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if pr != nil {
 | 
			
		||||
			if err := issues_model.AddDeletePRBranchComment(ctx, doer, pr.BaseRepo, pr.Issue.ID, pr.HeadBranch); err != nil {
 | 
			
		||||
				return fmt.Errorf("DeleteBranch: %v", err)
 | 
			
		||||
 | 
			
		||||
@ -188,17 +188,18 @@ func DeleteRepositoryDirectly(ctx context.Context, repoID int64, ignoreOrgTeams
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Delete Pulls and related objects
 | 
			
		||||
	if err := issues_model.DeletePullsByBaseRepoID(ctx, repoID); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Delete Issues and related objects
 | 
			
		||||
	var attachmentPaths []string
 | 
			
		||||
	if attachmentPaths, err = issue_service.DeleteIssuesByRepoID(ctx, repoID); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Delete Pulls and related objects
 | 
			
		||||
	// Notice: we should delete issue first because issue may load pull request
 | 
			
		||||
	if err := issues_model.DeletePullsByBaseRepoID(ctx, repoID); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Delete issue index
 | 
			
		||||
	if err := db.DeleteResourceIndex(ctx, "issue_index", repoID); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										105
									
								
								templates/repo/issue/sidebar/development.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								templates/repo/issue/sidebar/development.tmpl
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,105 @@
 | 
			
		||||
{{if not .Issue.IsPull}}
 | 
			
		||||
	<div class="divider"></div>
 | 
			
		||||
 | 
			
		||||
	<span class="text"><strong>{{ctx.Locale.Tr "repo.issues.development"}}</strong></span>
 | 
			
		||||
	<div class="ui devlinks list">
 | 
			
		||||
	{{/* AllowedRepos not empty means login user can create new branch in some of the repositories */}}
 | 
			
		||||
	{{if .ShowCreateBranchLink}}
 | 
			
		||||
		<div class="tw-items-center">
 | 
			
		||||
			<a class="tw-mt-1 fluid ui show-modal" data-modal="#create_branch">{{ctx.Locale.Tr "repo.branch.new_branch"}}</a>
 | 
			
		||||
		</div>
 | 
			
		||||
	{{end}}
 | 
			
		||||
	{{range .DevLinks}}
 | 
			
		||||
		{{if .PullRequest}}
 | 
			
		||||
			<div class="tw-flex tw-items-center tw-overflow-hidden tw-max-w-full tw-mt-2">
 | 
			
		||||
				<span class="tw-mr-1">{{template "shared/issueicon" .PullRequest.Issue}}</span>
 | 
			
		||||
				<a href="{{.PullRequest.Issue.Link}}" class="ref-issue item tw-overflow-hidden gt-ellipsis tw-whitespace-nowrap">
 | 
			
		||||
					{{.PullRequest.Issue.Title}}
 | 
			
		||||
				</a>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div>
 | 
			
		||||
			{{ctx.Locale.Tr "repo.issues.link.created" (DateUtils.AbsoluteShort .PullRequest.Issue.CreatedUnix)}}
 | 
			
		||||
			{{if .PullRequest.HasMerged}}
 | 
			
		||||
				{{ctx.Locale.Tr "repo.issues.pr.completed"}}
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="tw-flex tw-items-center tw-overflow-hidden tw-max-w-full">
 | 
			
		||||
				{{svg "octicon-git-commit" 14 "tw-mr-1"}} <a href="{{.PullRequest.BaseRepo.Link}}/src/commit/{{.PullRequest.MergedCommitID}}" data-tooltip-content="{{.PullRequest.MergedCommitID}}" class="tw-overflow-hidden gt-ellipsis tw-whitespace-nowrap">{{.PullRequest.MergedCommitID | ShortSha}}</a>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div>
 | 
			
		||||
				{{ctx.Locale.Tr "repo.issues.link.created" (DateUtils.AbsoluteShort .PullRequest.MergedUnix)}}
 | 
			
		||||
			{{else if .PullRequest.ChangedProtectedFiles}}
 | 
			
		||||
				{{ctx.Locale.Tr "repo.issues.pr.conflicted"}}
 | 
			
		||||
			{{end}}
 | 
			
		||||
			</div>
 | 
			
		||||
		{{else if and .Branch .DisplayBranch}}
 | 
			
		||||
			<div class="tw-flex tw-justify-between tw-items-center tw-mt-2">
 | 
			
		||||
				<div class="tw-flex tw-left tw-items-center tw-overflow-hidden tw-max-w-full">
 | 
			
		||||
					{{svg "octicon-git-branch" 14 "tw-mr-1"}}
 | 
			
		||||
					<a href="{{.Branch.Repo.Link}}/src/branch/{{.Branch.Name}}" data-tooltip-content="{{.BranchFullName}}" class="tw-overflow-hidden gt-ellipsis tw-whitespace-nowrap">
 | 
			
		||||
						{{.BranchFullName}}
 | 
			
		||||
					</a>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="tw-right tw-items-center">
 | 
			
		||||
					<a class="ui button mini compact basic icon" href="{{$.Issue.Repo.Link}}/compare/{{$.Issue.Repo.DefaultBranch}}...{{.Branch.Repo.FullName}}:{{.Branch.Name}}?ref_issue_index={{$.Issue.Index}}">
 | 
			
		||||
					{{svg "octicon-git-pull-request"}}
 | 
			
		||||
					</a>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div>{{ctx.Locale.Tr "repo.issues.branch.latest" (DateUtils.AbsoluteShort .Branch.CommitTime)}}</div>
 | 
			
		||||
		{{end}}
 | 
			
		||||
	{{end}}
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div class="ui tiny modal" id="create_branch">
 | 
			
		||||
		<div class="header">
 | 
			
		||||
			{{ctx.Locale.Tr "repo.branch.new_branch"}}
 | 
			
		||||
		</div>
 | 
			
		||||
		<div class="content">
 | 
			
		||||
			<form class="ui form form-fetch-action" action="{{.Issue.Link}}/create_branch"
 | 
			
		||||
				method="post">
 | 
			
		||||
				{{.CsrfTokenHtml}}
 | 
			
		||||
				<div class="required field">
 | 
			
		||||
					<label for="new_branch_name">{{ctx.Locale.Tr "form.NewBranchName"}}</label>
 | 
			
		||||
					<input name="new_branch_name" type="text" required>
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
				<div class="required field">
 | 
			
		||||
					<label for="source_repository">{{ctx.Locale.Tr "repo.issues.create_branch_from_repository"}}</label>
 | 
			
		||||
					<div class="ui selection dropdown ellipsis-items-nowrap">
 | 
			
		||||
						<input type="hidden" name="repo_id" value="{{.Issue.Repo.ID}}">
 | 
			
		||||
						<div class="text">
 | 
			
		||||
							<strong id="repo-branch-current">{{.Issue.Repo.FullName}}</strong>
 | 
			
		||||
						</div>
 | 
			
		||||
						{{svg "octicon-triangle-down" 14 "dropdown icon"}}
 | 
			
		||||
						<div class="menu">
 | 
			
		||||
							{{range .AllowedRepos}}
 | 
			
		||||
								<div class="item" data-value="{{.ID}}" title="{{.FullName}}">{{.FullName}}</div>
 | 
			
		||||
							{{end}}
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
				<div class="required field">
 | 
			
		||||
					<label for="source_branch_name">{{ctx.Locale.Tr "repo.issues.base_branch"}}</label>
 | 
			
		||||
					<div class="ui selection dropdown ellipsis-items-nowrap">
 | 
			
		||||
						<input type="hidden" name="source_branch_name" value="{{.Issue.Repo.DefaultBranch}}">
 | 
			
		||||
						<div class="text">
 | 
			
		||||
							<strong id="repo-branch-current">{{.Issue.Repo.DefaultBranch}}</strong>
 | 
			
		||||
						</div>
 | 
			
		||||
						{{svg "octicon-triangle-down" 14 "dropdown icon"}}
 | 
			
		||||
						<div class="menu">
 | 
			
		||||
							{{range .Branches}}
 | 
			
		||||
								<div class="item" data-value="{{.}}" title="{{.}}">{{.}}</div>
 | 
			
		||||
							{{end}}
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
				<div class="text right actions">
 | 
			
		||||
					<button class="ui cancel button">{{ctx.Locale.Tr "settings.cancel"}}</button>
 | 
			
		||||
					<button class="ui primary button">{{ctx.Locale.Tr "repo.branch.new_branch"}}</button>
 | 
			
		||||
				</div>
 | 
			
		||||
			</form>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
{{end}}
 | 
			
		||||
@ -15,6 +15,7 @@
 | 
			
		||||
	{{end}}
 | 
			
		||||
	{{template "repo/issue/sidebar/assignee_list" $.IssuePageMetaData}}
 | 
			
		||||
 | 
			
		||||
	{{template "repo/issue/sidebar/development" .}}
 | 
			
		||||
	{{template "repo/issue/sidebar/participant_list" $}}
 | 
			
		||||
	{{template "repo/issue/sidebar/watch_notification" $}}
 | 
			
		||||
	{{template "repo/issue/sidebar/stopwatch_timetracker" $}}
 | 
			
		||||
 | 
			
		||||
@ -133,6 +133,10 @@
 | 
			
		||||
					·
 | 
			
		||||
					{{ctx.Locale.TrN .Issue.NumComments "repo.issues.num_comments_1" "repo.issues.num_comments" .Issue.NumComments}}
 | 
			
		||||
				</span>
 | 
			
		||||
				{{if .MaybeFixed}}
 | 
			
		||||
					{{$fixedStr := HTMLFormat `<a href="%s" class="ref-issue">#%d</a>` .MaybeFixed.Issue.Link .MaybeFixed.Index}}
 | 
			
		||||
					· <span>{{ctx.Locale.Tr "repo.issues.maybefixed" $fixedStr}}</span>
 | 
			
		||||
				{{end}}
 | 
			
		||||
			{{end}}
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
@ -303,7 +303,7 @@ func TestAPICreateBranchWithSyncBranches(t *testing.T) {
 | 
			
		||||
		RepoID: 1,
 | 
			
		||||
	})
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Len(t, branches, 8)
 | 
			
		||||
	assert.Len(t, branches, 9)
 | 
			
		||||
 | 
			
		||||
	// make a broke repository with no branch on database
 | 
			
		||||
	_, err = db.DeleteByBean(t.Context(), git_model.Branch{RepoID: 1})
 | 
			
		||||
@ -320,7 +320,7 @@ func TestAPICreateBranchWithSyncBranches(t *testing.T) {
 | 
			
		||||
		RepoID: 1,
 | 
			
		||||
	})
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Len(t, branches, 9)
 | 
			
		||||
	assert.Len(t, branches, 10)
 | 
			
		||||
 | 
			
		||||
	branches, err = db.Find[git_model.Branch](t.Context(), git_model.FindBranchOptions{
 | 
			
		||||
		RepoID:  1,
 | 
			
		||||
 | 
			
		||||
@ -405,11 +405,11 @@ func TestCantMergeUnrelated(t *testing.T) {
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		commitSha := strings.TrimSpace(stdout.String())
 | 
			
		||||
 | 
			
		||||
		_, _, err = gitcmd.NewCommand("branch", "unrelated").
 | 
			
		||||
			AddDynamicArguments(commitSha).
 | 
			
		||||
			WithDir(path).
 | 
			
		||||
			RunStdString(t.Context())
 | 
			
		||||
		gitRepo1, err := gitrepo.OpenRepository(t.Context(), repo1)
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		defer gitRepo1.Close()
 | 
			
		||||
 | 
			
		||||
		assert.NoError(t, repo_service.CreateNewBranchFromCommit(t.Context(), user1, repo1, commitSha, "unrelated"))
 | 
			
		||||
 | 
			
		||||
		testEditFileToNewBranch(t, session, "user1", "repo1", "master", "conflict", "README.md", "Hello, World (Edited Once)\n")
 | 
			
		||||
 | 
			
		||||
@ -423,8 +423,6 @@ func TestCantMergeUnrelated(t *testing.T) {
 | 
			
		||||
		session.MakeRequest(t, req, http.StatusCreated)
 | 
			
		||||
 | 
			
		||||
		// Now this PR could be marked conflict - or at least a race may occur - so drop down to pure code at this point...
 | 
			
		||||
		gitRepo, err := gitrepo.OpenRepository(t.Context(), repo1)
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{
 | 
			
		||||
			HeadRepoID: repo1.ID,
 | 
			
		||||
			BaseRepoID: repo1.ID,
 | 
			
		||||
@ -432,10 +430,9 @@ func TestCantMergeUnrelated(t *testing.T) {
 | 
			
		||||
			BaseBranch: "base",
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		err = pull_service.Merge(t.Context(), pr, user1, gitRepo, repo_model.MergeStyleMerge, "", "UNRELATED", false)
 | 
			
		||||
		err = pull_service.Merge(t.Context(), pr, user1, gitRepo1, repo_model.MergeStyleMerge, "", "UNRELATED", false)
 | 
			
		||||
		assert.Error(t, err, "Merge should return an error due to unrelated")
 | 
			
		||||
		assert.True(t, pull_service.IsErrMergeUnrelatedHistories(err), "Merge error is not a unrelated histories error")
 | 
			
		||||
		gitRepo.Close()
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user