mirror of https://github.com/go-gitea/gitea
Compare commits
7 Commits
cbd7fd9173
...
b20c748397
Author | SHA1 | Date |
---|---|---|
a1012112796 | b20c748397 | |
Chongyi Zheng | 9a0b449c4f | |
Chongyi Zheng | b2013be910 | |
Chongyi Zheng | 970965f6d8 | |
Chongyi Zheng | 8b8b48ef5f | |
Chongyi Zheng | 7b8e418da1 | |
a1012112796 | 04bae31b49 |
|
@ -1,25 +0,0 @@
|
|||
name: disk-clean
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
with:
|
||||
# this might remove tools that are actually needed,
|
||||
# if set to "true" but frees about 6 GB
|
||||
tool-cache: false
|
||||
|
||||
# all of these default to true, but feel free to set to
|
||||
# "false" if necessary for your workflow
|
||||
android: true
|
||||
dotnet: true
|
||||
haskell: true
|
||||
large-packages: false
|
||||
docker-images: false
|
||||
swap-storage: true
|
|
@ -9,8 +9,6 @@ concurrency:
|
|||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
disk-clean:
|
||||
uses: ./.github/workflows/disk-clean.yml
|
||||
nightly-binary:
|
||||
runs-on: nscloud
|
||||
steps:
|
||||
|
|
|
@ -540,8 +540,8 @@
|
|||
"licenseText": "Copyright (c) 2011 The Snappy-Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
|
||||
},
|
||||
{
|
||||
"name": "github.com/google/go-github/v57/github",
|
||||
"path": "github.com/google/go-github/v57/github/LICENSE",
|
||||
"name": "github.com/google/go-github/v61/github",
|
||||
"path": "github.com/google/go-github/v61/github/LICENSE",
|
||||
"licenseText": "Copyright (c) 2013 The go-github AUTHORS. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
|
||||
},
|
||||
{
|
||||
|
|
|
@ -17,7 +17,7 @@ import (
|
|||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/google/go-github/v57/github"
|
||||
"github.com/google/go-github/v61/github"
|
||||
"github.com/urfave/cli/v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
|
4
go.mod
4
go.mod
|
@ -16,6 +16,7 @@ require (
|
|||
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4
|
||||
github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
|
||||
github.com/ProtonMail/go-crypto v1.0.0
|
||||
github.com/PuerkitoBio/goquery v1.9.1
|
||||
github.com/alecthomas/chroma/v2 v2.13.0
|
||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
|
||||
|
@ -53,7 +54,7 @@ require (
|
|||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
|
||||
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||
github.com/google/go-github/v57 v57.0.0
|
||||
github.com/google/go-github/v61 v61.0.0
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/feeds v1.1.2
|
||||
|
@ -135,7 +136,6 @@ require (
|
|||
github.com/Masterminds/semver/v3 v3.2.1 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.0.0 // indirect
|
||||
github.com/RoaringBitmap/roaring v1.9.0 // indirect
|
||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.2 // indirect
|
||||
|
|
4
go.sum
4
go.sum
|
@ -394,8 +394,8 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-github/v57 v57.0.0 h1:L+Y3UPTY8ALM8x+TV0lg+IEBI+upibemtBD8Q9u7zHs=
|
||||
github.com/google/go-github/v57 v57.0.0/go.mod h1:s0omdnye0hvK/ecLvpsGfJMiRt85PimQh4oygmLIxHw=
|
||||
github.com/google/go-github/v61 v61.0.0 h1:VwQCBwhyE9JclCI+22/7mLB1PuU9eowCXKY5pNlu1go=
|
||||
github.com/google/go-github/v61 v61.0.0/go.mod h1:0WR+KmsWX75G2EbpyGsGmradjo3IiciuI4BmdVCobQY=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk=
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
id: 2
|
||||
issue_id: 2
|
||||
project_id: 1
|
||||
project_board_id: 0 # no board assigned
|
||||
project_board_id: 5
|
||||
|
||||
-
|
||||
id: 3
|
||||
|
|
|
@ -220,6 +220,13 @@ func (r RoleInRepo) LocaleHelper(lang translation.Locale) string {
|
|||
return lang.TrString("repo.issues.role." + string(r) + "_helper")
|
||||
}
|
||||
|
||||
// CommentProjectBoardExtendData extend data of CommentTypeProjectBoard,
|
||||
// will be store in `Comment.Content` as json format
|
||||
type CommentProjectBoardExtendData struct {
|
||||
FromBoardTitle string
|
||||
ToBoardTitle string
|
||||
}
|
||||
|
||||
// Comment represents a comment in commit and issue page.
|
||||
type Comment struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
|
@ -301,6 +308,8 @@ type Comment struct {
|
|||
NewCommit string `xorm:"-"`
|
||||
CommitsNum int64 `xorm:"-"`
|
||||
IsForcePush bool `xorm:"-"`
|
||||
|
||||
ProjectBoard *CommentProjectBoardExtendData `xorm:"-"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -539,6 +548,15 @@ func (c *Comment) LoadProject(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *Comment) LoadProjectBoard() error {
|
||||
if c.Type != CommentTypeProjectBoard || c.ProjectBoard != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
c.ProjectBoard = &CommentProjectBoardExtendData{}
|
||||
return json.Unmarshal([]byte(c.Content), c.ProjectBoard)
|
||||
}
|
||||
|
||||
// LoadMilestone if comment.Type is CommentTypeMilestone, then load milestone
|
||||
func (c *Comment) LoadMilestone(ctx context.Context) error {
|
||||
if c.OldMilestoneID > 0 {
|
||||
|
@ -828,6 +846,15 @@ func CreateComment(ctx context.Context, opts *CreateCommentOptions) (_ *Comment,
|
|||
IsForcePush: opts.IsForcePush,
|
||||
Invalidated: opts.Invalidated,
|
||||
}
|
||||
if comment.Type == CommentTypeProjectBoard {
|
||||
extDataJSON, err := json.Marshal(opts.ProjectBoard)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
comment.Content = string(extDataJSON)
|
||||
comment.ProjectBoard = opts.ProjectBoard
|
||||
}
|
||||
|
||||
if _, err = e.Insert(comment); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -1007,6 +1034,8 @@ type CreateCommentOptions struct {
|
|||
RefIsPull bool
|
||||
IsForcePush bool
|
||||
Invalidated bool
|
||||
|
||||
ProjectBoard *CommentProjectBoardExtendData
|
||||
}
|
||||
|
||||
// GetCommentByID returns the comment by given ID.
|
||||
|
|
|
@ -140,6 +140,8 @@ type Issue struct {
|
|||
|
||||
// For view issue page.
|
||||
ShowRole RoleDescriptor `xorm:"-"`
|
||||
|
||||
ProjectIssue *project_model.ProjectIssue `xorm:"-"`
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -315,6 +317,10 @@ func (issue *Issue) LoadAttributes(ctx context.Context) (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
if err = issue.LoadProjectIssue(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = issue.LoadAssignees(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -226,14 +226,15 @@ func (issues IssueList) loadMilestones(ctx context.Context) error {
|
|||
|
||||
func (issues IssueList) LoadProjects(ctx context.Context) error {
|
||||
issueIDs := issues.getIssueIDs()
|
||||
projectMaps := make(map[int64]*project_model.Project, len(issues))
|
||||
left := len(issueIDs)
|
||||
|
||||
type projectWithIssueID struct {
|
||||
*project_model.Project `xorm:"extends"`
|
||||
IssueID int64
|
||||
ProjectIssue *project_model.ProjectIssue `xorm:"extends"`
|
||||
}
|
||||
|
||||
projectMaps := make(map[int64]*projectWithIssueID, len(issues))
|
||||
|
||||
for left > 0 {
|
||||
limit := db.DefaultMaxInSize
|
||||
if left < limit {
|
||||
|
@ -243,7 +244,7 @@ func (issues IssueList) LoadProjects(ctx context.Context) error {
|
|||
projects := make([]*projectWithIssueID, 0, limit)
|
||||
err := db.GetEngine(ctx).
|
||||
Table("project").
|
||||
Select("project.*, project_issue.issue_id").
|
||||
Select("project.*, project_issue.*").
|
||||
Join("INNER", "project_issue", "project.id = project_issue.project_id").
|
||||
In("project_issue.issue_id", issueIDs[:limit]).
|
||||
Find(&projects)
|
||||
|
@ -251,14 +252,20 @@ func (issues IssueList) LoadProjects(ctx context.Context) error {
|
|||
return err
|
||||
}
|
||||
for _, project := range projects {
|
||||
projectMaps[project.IssueID] = project.Project
|
||||
projectMaps[project.ProjectIssue.IssueID] = project
|
||||
}
|
||||
left -= limit
|
||||
issueIDs = issueIDs[limit:]
|
||||
}
|
||||
|
||||
for _, issue := range issues {
|
||||
issue.Project = projectMaps[issue.ID]
|
||||
item, exist := projectMaps[issue.ID]
|
||||
if !exist {
|
||||
continue
|
||||
}
|
||||
|
||||
issue.Project = item.Project
|
||||
issue.ProjectIssue = item.ProjectIssue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -554,6 +561,10 @@ func (issues IssueList) LoadAttributes(ctx context.Context) error {
|
|||
return fmt.Errorf("issue.loadAttributes: loadProjects: %w", err)
|
||||
}
|
||||
|
||||
if err := issues.LoadProjectIssueBoards(ctx); err != nil {
|
||||
return fmt.Errorf("issue.loadAttributes: LoadProjectIssueBoards: %w", err)
|
||||
}
|
||||
|
||||
if err := issues.loadAssignees(ctx); err != nil {
|
||||
return fmt.Errorf("issue.loadAttributes: loadAssignees: %w", err)
|
||||
}
|
||||
|
@ -626,3 +637,60 @@ func (issues IssueList) LoadIsRead(ctx context.Context, userID int64) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (issues IssueList) getProjectIssueBoardIDs() []int64 {
|
||||
boardIDmap := make(map[int64]bool, 5)
|
||||
|
||||
for _, issue := range issues {
|
||||
if issue.ProjectIssue != nil {
|
||||
boardIDmap[issue.ProjectIssue.ProjectBoardID] = true
|
||||
}
|
||||
}
|
||||
|
||||
bordIDs := make([]int64, 0, len(boardIDmap))
|
||||
for id := range boardIDmap {
|
||||
bordIDs = append(bordIDs, id)
|
||||
}
|
||||
|
||||
return bordIDs
|
||||
}
|
||||
|
||||
func (issues IssueList) LoadProjectIssueBoards(ctx context.Context) error {
|
||||
boardIDs := issues.getProjectIssueBoardIDs()
|
||||
if len(boardIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
boardMaps := make(map[int64]*project_model.Board, len(boardIDs))
|
||||
left := len(boardIDs)
|
||||
for left > 0 {
|
||||
limit := db.DefaultMaxInSize
|
||||
if left < limit {
|
||||
limit = left
|
||||
}
|
||||
err := db.GetEngine(ctx).
|
||||
In("id", boardIDs[:limit]).
|
||||
Find(&boardMaps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
left -= limit
|
||||
boardIDs = boardIDs[limit:]
|
||||
}
|
||||
|
||||
for _, issue := range issues {
|
||||
if issue.ProjectIssue != nil {
|
||||
board, exist := boardMaps[issue.ProjectIssue.ProjectBoardID]
|
||||
if exist {
|
||||
issue.ProjectIssue.ProjectBoard = board
|
||||
} else {
|
||||
issue.ProjectIssue.ProjectBoard = &project_model.Board{
|
||||
ID: -1,
|
||||
Title: "Deleted",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -68,6 +68,10 @@ func TestIssueList_LoadAttributes(t *testing.T) {
|
|||
assert.Equal(t, int64(400), issue.TotalTrackedTime)
|
||||
assert.NotNil(t, issue.Project)
|
||||
assert.Equal(t, int64(1), issue.Project.ID)
|
||||
assert.NotNil(t, issue.ProjectIssue)
|
||||
assert.Equal(t, int64(1), issue.ProjectIssue.IssueID)
|
||||
assert.NotNil(t, issue.ProjectIssue.ProjectBoard)
|
||||
assert.Equal(t, int64(1), issue.ProjectIssue.ProjectBoard.ID)
|
||||
} else {
|
||||
assert.Nil(t, issue.Project)
|
||||
}
|
||||
|
|
|
@ -28,6 +28,23 @@ func (issue *Issue) LoadProject(ctx context.Context) (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
func (issue *Issue) LoadProjectIssue(ctx context.Context) (err error) {
|
||||
if issue.Project == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if issue.ProjectIssue != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
issue.ProjectIssue, err = project_model.GetProjectIssueByIssueID(ctx, issue.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return issue.ProjectIssue.LoadProjectBoard(ctx)
|
||||
}
|
||||
|
||||
func (issue *Issue) projectID(ctx context.Context) int64 {
|
||||
var ip project_model.ProjectIssue
|
||||
has, err := db.GetEngine(ctx).Where("issue_id=?", issue.ID).Get(&ip)
|
||||
|
@ -107,6 +124,7 @@ func ChangeProjectAssign(ctx context.Context, issue *Issue, doer *user_model.Use
|
|||
|
||||
func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID int64) error {
|
||||
oldProjectID := issue.projectID(ctx)
|
||||
newBoardID := int64(0)
|
||||
|
||||
if err := issue.LoadRepo(ctx); err != nil {
|
||||
return err
|
||||
|
@ -121,6 +139,12 @@ func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.U
|
|||
if newProject.RepoID != issue.RepoID && newProject.OwnerID != issue.Repo.OwnerID {
|
||||
return fmt.Errorf("issue's repository is not the same as project's repository")
|
||||
}
|
||||
|
||||
newBoard, err := newProject.GetDefaultBoard(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newBoardID = newBoard.ID
|
||||
}
|
||||
|
||||
if _, err := db.GetEngine(ctx).Where("project_issue.issue_id=?", issue.ID).Delete(&project_model.ProjectIssue{}); err != nil {
|
||||
|
@ -141,7 +165,8 @@ func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.U
|
|||
}
|
||||
|
||||
return db.Insert(ctx, &project_model.ProjectIssue{
|
||||
IssueID: issue.ID,
|
||||
ProjectID: newProjectID,
|
||||
IssueID: issue.ID,
|
||||
ProjectID: newProjectID,
|
||||
ProjectBoardID: newBoardID,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package issues
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sort"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
project_model "code.gitea.io/gitea/models/project"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
)
|
||||
|
||||
type ProjectMovedIssuesFormItem struct {
|
||||
IssueID int64 `json:"issueID"`
|
||||
Sorting int64 `json:"sorting"`
|
||||
}
|
||||
|
||||
type ProjectMovedIssuesForm struct {
|
||||
Issues []ProjectMovedIssuesFormItem `json:"issues"`
|
||||
}
|
||||
|
||||
func (p *ProjectMovedIssuesForm) ToSortedIssueIDs() (issueIDs, issueSorts []int64) {
|
||||
sort.Slice(p.Issues, func(i, j int) bool { return p.Issues[i].Sorting < p.Issues[j].Sorting })
|
||||
|
||||
issueIDs = make([]int64, 0, len(p.Issues))
|
||||
issueSorts = make([]int64, 0, len(p.Issues))
|
||||
|
||||
for _, issue := range p.Issues {
|
||||
issueIDs = append(issueIDs, issue.IssueID)
|
||||
issueSorts = append(issueSorts, issue.Sorting)
|
||||
}
|
||||
|
||||
return issueIDs, issueSorts
|
||||
}
|
||||
|
||||
func MoveIssuesOnProjectBoard(ctx context.Context, doer *user_model.User, form *ProjectMovedIssuesForm, project *project_model.Project, board *project_model.Board) error {
|
||||
issueIDs, issueSorts := form.ToSortedIssueIDs()
|
||||
|
||||
movedIssues, err := GetIssuesByIDs(ctx, issueIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(movedIssues) != len(form.Issues) {
|
||||
return errors.New("some issues do not exist")
|
||||
}
|
||||
|
||||
if _, err = movedIssues.LoadRepositories(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = movedIssues.LoadProjects(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = movedIssues.LoadProjectIssueBoards(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, issue := range movedIssues {
|
||||
if issue.RepoID != project.RepoID && issue.Repo.OwnerID != project.OwnerID {
|
||||
return errors.New("Some issue's repoID is not equal to project's repoID")
|
||||
}
|
||||
}
|
||||
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
if err = project_model.MoveIssuesOnProjectBoard(ctx, board, issueIDs, issueSorts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, issue := range movedIssues {
|
||||
if issue.ProjectIssue.ProjectBoardID == board.ID {
|
||||
continue
|
||||
}
|
||||
|
||||
_, err = CreateComment(ctx, &CreateCommentOptions{
|
||||
Type: CommentTypeProjectBoard,
|
||||
Doer: doer,
|
||||
Repo: issue.Repo,
|
||||
Issue: issue,
|
||||
ProjectID: project.ID,
|
||||
ProjectBoard: &CommentProjectBoardExtendData{
|
||||
FromBoardTitle: issue.ProjectIssue.ProjectBoard.Title,
|
||||
ToBoardTitle: board.Title,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package issues
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
project_model "code.gitea.io/gitea/models/project"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestProjectMovedIssuesForm_ToSortedIssueIDs(t *testing.T) {
|
||||
opts := &ProjectMovedIssuesForm{
|
||||
Issues: []ProjectMovedIssuesFormItem{
|
||||
{
|
||||
IssueID: 5,
|
||||
Sorting: 1,
|
||||
},
|
||||
{
|
||||
IssueID: 1,
|
||||
Sorting: 4,
|
||||
},
|
||||
{
|
||||
IssueID: 6,
|
||||
Sorting: 3,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ids, sorts := opts.ToSortedIssueIDs()
|
||||
|
||||
assert.EqualValues(t, sorts, []int64{1, 3, 4})
|
||||
assert.EqualValues(t, ids, []int64{5, 6, 1})
|
||||
}
|
||||
|
||||
func TestMoveIssuesOnProjectBoard(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
project := unittest.AssertExistsAndLoadBean(t, &project_model.Project{ID: 1})
|
||||
toBoard := unittest.AssertExistsAndLoadBean(t, &project_model.Board{ID: 2})
|
||||
|
||||
list, err := LoadIssuesFromBoardList(db.DefaultContext, []*project_model.Board{toBoard})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 1, len(list[toBoard.ID]))
|
||||
assert.EqualValues(t, 3, list[toBoard.ID][0].ID)
|
||||
|
||||
opts := &ProjectMovedIssuesForm{
|
||||
Issues: []ProjectMovedIssuesFormItem{
|
||||
{
|
||||
IssueID: 1,
|
||||
Sorting: 2,
|
||||
},
|
||||
{
|
||||
IssueID: 2,
|
||||
Sorting: 3,
|
||||
},
|
||||
{
|
||||
IssueID: 3,
|
||||
Sorting: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.NoError(t, MoveIssuesOnProjectBoard(db.DefaultContext, doer, opts, project, toBoard))
|
||||
|
||||
list, err = LoadIssuesFromBoardList(db.DefaultContext, []*project_model.Board{toBoard})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 3, len(list[toBoard.ID]))
|
||||
|
||||
assert.EqualValues(t, 3, list[toBoard.ID][0].ID)
|
||||
assert.EqualValues(t, 1, list[toBoard.ID][1].ID)
|
||||
assert.EqualValues(t, 2, list[toBoard.ID][2].ID)
|
||||
}
|
|
@ -247,7 +247,7 @@ func (p *Project) GetBoards(ctx context.Context) (BoardList, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
defaultB, err := p.getDefaultBoard(ctx)
|
||||
defaultB, err := p.GetDefaultBoard(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -255,8 +255,8 @@ func (p *Project) GetBoards(ctx context.Context) (BoardList, error) {
|
|||
return append([]*Board{defaultB}, boards...), nil
|
||||
}
|
||||
|
||||
// getDefaultBoard return default board and ensure only one exists
|
||||
func (p *Project) getDefaultBoard(ctx context.Context) (*Board, error) {
|
||||
// GetDefaultBoard return default board and ensure only one exists
|
||||
func (p *Project) GetDefaultBoard(ctx context.Context) (*Board, error) {
|
||||
var board Board
|
||||
has, err := db.GetEngine(ctx).
|
||||
Where("project_id=? AND `default` = ?", p.ID, true).
|
||||
|
|
|
@ -19,7 +19,7 @@ func TestGetDefaultBoard(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
|
||||
// check if default board was added
|
||||
board, err := projectWithoutDefault.getDefaultBoard(db.DefaultContext)
|
||||
board, err := projectWithoutDefault.GetDefaultBoard(db.DefaultContext)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(5), board.ProjectID)
|
||||
assert.Equal(t, "Uncategorized", board.Title)
|
||||
|
@ -28,7 +28,7 @@ func TestGetDefaultBoard(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
|
||||
// check if multiple defaults were removed
|
||||
board, err = projectWithMultipleDefaults.getDefaultBoard(db.DefaultContext)
|
||||
board, err = projectWithMultipleDefaults.GetDefaultBoard(db.DefaultContext)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(6), board.ProjectID)
|
||||
assert.Equal(t, int64(9), board.ID)
|
||||
|
|
|
@ -17,8 +17,8 @@ type ProjectIssue struct { //revive:disable-line:exported
|
|||
IssueID int64 `xorm:"INDEX"`
|
||||
ProjectID int64 `xorm:"INDEX"`
|
||||
|
||||
// If 0, then it has not been added to a specific board in the project
|
||||
ProjectBoardID int64 `xorm:"INDEX"`
|
||||
ProjectBoardID int64 `xorm:"INDEX"`
|
||||
ProjectBoard *Board `xorm:"-"`
|
||||
|
||||
// the sorting order on the board
|
||||
Sorting int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||
|
@ -76,33 +76,76 @@ func (p *Project) NumOpenIssues(ctx context.Context) int {
|
|||
}
|
||||
|
||||
// MoveIssuesOnProjectBoard moves or keeps issues in a column and sorts them inside that column
|
||||
func MoveIssuesOnProjectBoard(ctx context.Context, board *Board, sortedIssueIDs map[int64]int64) error {
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
sess := db.GetEngine(ctx)
|
||||
func MoveIssuesOnProjectBoard(ctx context.Context, board *Board, issueIDs, issueSorts []int64) error {
|
||||
sess := db.GetEngine(ctx)
|
||||
|
||||
issueIDs := make([]int64, 0, len(sortedIssueIDs))
|
||||
for _, issueID := range sortedIssueIDs {
|
||||
issueIDs = append(issueIDs, issueID)
|
||||
}
|
||||
count, err := sess.Table(new(ProjectIssue)).Where("project_id=?", board.ProjectID).In("issue_id", issueIDs).Count()
|
||||
count, err := sess.Table(new(ProjectIssue)).Where("project_id=?", board.ProjectID).In("issue_id", issueIDs).Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if int(count) != len(issueIDs) {
|
||||
return fmt.Errorf("all issues have to be added to a project first")
|
||||
}
|
||||
|
||||
for i, issueID := range issueIDs {
|
||||
_, err = sess.Exec("UPDATE `project_issue` SET project_board_id=?, sorting=? WHERE issue_id=?", board.ID, issueSorts[i], issueID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if int(count) != len(sortedIssueIDs) {
|
||||
return fmt.Errorf("all issues have to be added to a project first")
|
||||
}
|
||||
}
|
||||
|
||||
for sorting, issueID := range sortedIssueIDs {
|
||||
_, err = sess.Exec("UPDATE `project_issue` SET project_board_id=?, sorting=? WHERE issue_id=?", board.ID, sorting, issueID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Board) removeIssues(ctx context.Context) error {
|
||||
_, err := db.GetEngine(ctx).Exec("UPDATE `project_issue` SET project_board_id = 0 WHERE project_board_id = ? ", b.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
type ErrProjectIssueNotExist struct {
|
||||
IssueID int64
|
||||
}
|
||||
|
||||
func (e ErrProjectIssueNotExist) Error() string {
|
||||
return fmt.Sprintf("can't find project issue [issue_id: %d]", e.IssueID)
|
||||
}
|
||||
|
||||
func IsErrProjectIssueNotExist(e error) bool {
|
||||
_, ok := e.(ErrProjectIssueNotExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func GetProjectIssueByIssueID(ctx context.Context, issueID int64) (*ProjectIssue, error) {
|
||||
issue := &ProjectIssue{}
|
||||
|
||||
has, err := db.GetEngine(ctx).Where("issue_id = ?", issueID).Get(issue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !has {
|
||||
return nil, ErrProjectIssueNotExist{IssueID: issueID}
|
||||
}
|
||||
|
||||
return issue, nil
|
||||
}
|
||||
|
||||
func (issue *ProjectIssue) LoadProjectBoard(ctx context.Context) error {
|
||||
if issue.ProjectBoard != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
issue.ProjectBoard, err = GetBoard(ctx, issue.ProjectBoardID)
|
||||
if IsErrProjectBoardNotExist(err) {
|
||||
issue.ProjectBoard = &Board{
|
||||
ID: -1,
|
||||
Title: "Deleted",
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@ package user_test
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
|
|
@ -4,9 +4,8 @@
|
|||
package pwn
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"math/rand/v2"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -18,11 +17,6 @@ var client = New(WithHTTP(&http.Client{
|
|||
Timeout: time.Second * 2,
|
||||
}))
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
rand.Seed(time.Now().Unix())
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestPassword(t *testing.T) {
|
||||
// Check input error
|
||||
_, err := client.CheckPassword("", false)
|
||||
|
@ -81,24 +75,24 @@ func testPassword() string {
|
|||
|
||||
// Set special character
|
||||
for i := 0; i < 5; i++ {
|
||||
random := rand.Intn(len(specialCharSet))
|
||||
random := rand.IntN(len(specialCharSet))
|
||||
password.WriteString(string(specialCharSet[random]))
|
||||
}
|
||||
|
||||
// Set numeric
|
||||
for i := 0; i < 5; i++ {
|
||||
random := rand.Intn(len(numberSet))
|
||||
random := rand.IntN(len(numberSet))
|
||||
password.WriteString(string(numberSet[random]))
|
||||
}
|
||||
|
||||
// Set uppercase
|
||||
for i := 0; i < 5; i++ {
|
||||
random := rand.Intn(len(upperCharSet))
|
||||
random := rand.IntN(len(upperCharSet))
|
||||
password.WriteString(string(upperCharSet[random]))
|
||||
}
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
random := rand.Intn(len(allCharSet))
|
||||
random := rand.IntN(len(allCharSet))
|
||||
password.WriteString(string(allCharSet[random]))
|
||||
}
|
||||
inRune := []rune(password.String())
|
||||
|
|
|
@ -373,12 +373,6 @@ func searchIssueInProject(t *testing.T) {
|
|||
},
|
||||
[]int64{1},
|
||||
},
|
||||
{
|
||||
SearchOptions{
|
||||
ProjectBoardID: optional.Some(int64(0)), // issue with in default board
|
||||
},
|
||||
[]int64{2},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
issueIDs, _, err := SearchIssues(context.TODO(), &test.opts)
|
||||
|
|
|
@ -1471,6 +1471,7 @@ issues.add_milestone_at = `added this to the <b>%s</b> milestone %s`
|
|||
issues.add_project_at = `added this to the <b>%s</b> project %s`
|
||||
issues.change_milestone_at = `modified the milestone from <b>%s</b> to <b>%s</b> %s`
|
||||
issues.change_project_at = `modified the project from <b>%s</b> to <b>%s</b> %s`
|
||||
issues.change_project_board_at = `moved this from <b>%s</b> to <b>%s</b> in <b>%s</b> %s`
|
||||
issues.remove_milestone_at = `removed this from the <b>%s</b> milestone %s`
|
||||
issues.remove_project_at = `removed this from the <b>%s</b> project %s`
|
||||
issues.deleted_milestone = `(deleted)`
|
||||
|
|
|
@ -466,14 +466,15 @@ func (ar artifactRoutes) downloadArtifact(ctx *ArtifactContext) {
|
|||
log.Error("Error getting artifact: %v", err)
|
||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
} else if !exist {
|
||||
}
|
||||
if !exist {
|
||||
log.Error("artifact with ID %d does not exist", artifactID)
|
||||
ctx.Error(http.StatusNotFound, fmt.Sprintf("artifact with ID %d does not exist", artifactID))
|
||||
return
|
||||
}
|
||||
if artifact.RunID != runID {
|
||||
log.Error("Error dismatch runID and artifactID, task: %v, artifact: %v", runID, artifactID)
|
||||
ctx.Error(http.StatusBadRequest, err.Error())
|
||||
log.Error("Error mismatch runID and artifactID, task: %v, artifact: %v", runID, artifactID)
|
||||
ctx.Error(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -630,48 +630,14 @@ func MoveIssues(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
type movedIssuesForm struct {
|
||||
Issues []struct {
|
||||
IssueID int64 `json:"issueID"`
|
||||
Sorting int64 `json:"sorting"`
|
||||
} `json:"issues"`
|
||||
}
|
||||
|
||||
form := &movedIssuesForm{}
|
||||
form := &issues_model.ProjectMovedIssuesForm{}
|
||||
if err = json.NewDecoder(ctx.Req.Body).Decode(&form); err != nil {
|
||||
ctx.ServerError("DecodeMovedIssuesForm", err)
|
||||
return
|
||||
}
|
||||
|
||||
issueIDs := make([]int64, 0, len(form.Issues))
|
||||
sortedIssueIDs := make(map[int64]int64)
|
||||
for _, issue := range form.Issues {
|
||||
issueIDs = append(issueIDs, issue.IssueID)
|
||||
sortedIssueIDs[issue.Sorting] = issue.IssueID
|
||||
}
|
||||
movedIssues, err := issues_model.GetIssuesByIDs(ctx, issueIDs)
|
||||
err = issues_model.MoveIssuesOnProjectBoard(ctx, ctx.Doer, form, project, board)
|
||||
if err != nil {
|
||||
ctx.NotFoundOrServerError("GetIssueByID", issues_model.IsErrIssueNotExist, err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(movedIssues) != len(form.Issues) {
|
||||
ctx.ServerError("some issues do not exist", errors.New("some issues do not exist"))
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = movedIssues.LoadRepositories(ctx); err != nil {
|
||||
ctx.ServerError("LoadRepositories", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, issue := range movedIssues {
|
||||
if issue.RepoID != project.RepoID && issue.Repo.OwnerID != project.OwnerID {
|
||||
ctx.ServerError("Some issue's repoID is not equal to project's repoID", errors.New("Some issue's repoID is not equal to project's repoID"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err = project_model.MoveIssuesOnProjectBoard(ctx, board, sortedIssueIDs); err != nil {
|
||||
ctx.ServerError("MoveIssuesOnProjectBoard", err)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -504,7 +504,7 @@ func getRunJobs(ctx *context_module.Context, runIndex, jobIndex int64) (*actions
|
|||
return nil, nil
|
||||
}
|
||||
if len(jobs) == 0 {
|
||||
ctx.Error(http.StatusNotFound, err.Error())
|
||||
ctx.Error(http.StatusNotFound)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -1662,11 +1662,15 @@ func ViewIssue(ctx *context.Context) {
|
|||
if comment.MilestoneID > 0 && comment.Milestone == nil {
|
||||
comment.Milestone = ghostMilestone
|
||||
}
|
||||
} else if comment.Type == issues_model.CommentTypeProject {
|
||||
} else if comment.Type == issues_model.CommentTypeProject || comment.Type == issues_model.CommentTypeProjectBoard {
|
||||
if err = comment.LoadProject(ctx); err != nil {
|
||||
ctx.ServerError("LoadProject", err)
|
||||
return
|
||||
}
|
||||
if err = comment.LoadProjectBoard(); err != nil {
|
||||
ctx.ServerError("LoadProjectBoard", err)
|
||||
return
|
||||
}
|
||||
|
||||
ghostProject := &project_model.Project{
|
||||
ID: -1,
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
package repo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
@ -619,47 +618,14 @@ func MoveIssues(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
type movedIssuesForm struct {
|
||||
Issues []struct {
|
||||
IssueID int64 `json:"issueID"`
|
||||
Sorting int64 `json:"sorting"`
|
||||
} `json:"issues"`
|
||||
}
|
||||
|
||||
form := &movedIssuesForm{}
|
||||
form := &issues_model.ProjectMovedIssuesForm{}
|
||||
if err = json.NewDecoder(ctx.Req.Body).Decode(&form); err != nil {
|
||||
ctx.ServerError("DecodeMovedIssuesForm", err)
|
||||
return
|
||||
}
|
||||
|
||||
issueIDs := make([]int64, 0, len(form.Issues))
|
||||
sortedIssueIDs := make(map[int64]int64)
|
||||
for _, issue := range form.Issues {
|
||||
issueIDs = append(issueIDs, issue.IssueID)
|
||||
sortedIssueIDs[issue.Sorting] = issue.IssueID
|
||||
}
|
||||
movedIssues, err := issues_model.GetIssuesByIDs(ctx, issueIDs)
|
||||
err = issues_model.MoveIssuesOnProjectBoard(ctx, ctx.Doer, form, project, board)
|
||||
if err != nil {
|
||||
if issues_model.IsErrIssueNotExist(err) {
|
||||
ctx.NotFound("IssueNotExisting", nil)
|
||||
} else {
|
||||
ctx.ServerError("GetIssueByID", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if len(movedIssues) != len(form.Issues) {
|
||||
ctx.ServerError("some issues do not exist", errors.New("some issues do not exist"))
|
||||
return
|
||||
}
|
||||
|
||||
for _, issue := range movedIssues {
|
||||
if issue.RepoID != project.RepoID {
|
||||
ctx.ServerError("Some issue's repoID is not equal to project's repoID", errors.New("Some issue's repoID is not equal to project's repoID"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err = project_model.MoveIssuesOnProjectBoard(ctx, board, sortedIssueIDs); err != nil {
|
||||
ctx.ServerError("MoveIssuesOnProjectBoard", err)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ package migrations
|
|||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/google/go-github/v57/github"
|
||||
"github.com/google/go-github/v61/github"
|
||||
)
|
||||
|
||||
// ErrRepoNotCreated returns the error that repository not created
|
||||
|
|
|
@ -20,7 +20,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/proxy"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
|
||||
"github.com/google/go-github/v57/github"
|
||||
"github.com/google/go-github/v61/github"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
|
|
|
@ -600,6 +600,26 @@
|
|||
</span>
|
||||
</div>
|
||||
{{end}}
|
||||
{{else if eq .Type 31}}
|
||||
{{if not $.UnitProjectsGlobalDisabled}}
|
||||
<div class="timeline-item event" id="{{.HashTag}}">
|
||||
<span class="badge">{{svg "octicon-project"}}</span>
|
||||
{{template "shared/user/avatarlink" dict "user" .Poster}}
|
||||
<span class="text grey muted-links">
|
||||
{{template "shared/user/authorlink" .Poster}}
|
||||
|
||||
{{$projectDisplayHtml := "Unknown Project"}}
|
||||
{{if .Project}}
|
||||
{{$trKey := printf "projects.type-%d.display_name" .Project.Type}}
|
||||
{{$projectDisplayHtml = HTMLFormat `<span data-tooltip-content="%s">%s</span>` (ctx.Locale.Tr $trKey) .Project.Title}}
|
||||
{{end}}
|
||||
|
||||
{{if gt .ProjectID 0}}
|
||||
{{ctx.Locale.Tr "repo.issues.change_project_board_at" .ProjectBoard.FromBoardTitle .ProjectBoard.ToBoardTitle $projectDisplayHtml $createdStr}}
|
||||
{{end}}
|
||||
</span>
|
||||
</div>
|
||||
{{end}}
|
||||
{{else if eq .Type 32}}
|
||||
<div class="timeline-item-group">
|
||||
<div class="timeline-item event" id="{{.HashTag}}">
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
package integration
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"math/rand/v2"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
@ -18,7 +18,7 @@ import (
|
|||
func StringWithCharset(length int, charset string) string {
|
||||
b := make([]byte, length)
|
||||
for i := range b {
|
||||
b[i] = charset[rand.Intn(len(charset))]
|
||||
b[i] = charset[rand.IntN(len(charset))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ func BenchmarkRepoBranchCommit(b *testing.B) {
|
|||
b.ResetTimer()
|
||||
b.Run("CreateBranch", func(b *testing.B) {
|
||||
b.StopTimer()
|
||||
branchName := StringWithCharset(5+rand.Intn(10), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
||||
branchName := StringWithCharset(5+rand.IntN(10), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
b.Run("new_"+branchName, func(b *testing.B) {
|
||||
|
|
|
@ -5,9 +5,9 @@ package integration
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
|
|
|
@ -19,9 +19,9 @@ import (
|
|||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/armor"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/crypto/openpgp"
|
||||
"golang.org/x/crypto/openpgp/armor"
|
||||
)
|
||||
|
||||
func TestGPGGit(t *testing.T) {
|
||||
|
|
Loading…
Reference in New Issue