Compare commits

...

7 Commits

Author SHA1 Message Date
a1012112796 b20c748397
Merge 04bae31b49 into 9a0b449c4f 2024-04-28 14:28:29 +08:00
Chongyi Zheng 9a0b449c4f
Remove disk-clean workflow (#30741)
The jobs in the workflow runs in parallel. The `disk-clean` job actually
does nothing, i.e. it will not clean the disk for `nightly-binary`,
`nightly-docker-rootful`, `nightly-docker-rootless`
2024-04-28 05:47:48 +00:00
Chongyi Zheng b2013be910
Bump `github.com/google/go-github` to v61 (#30738) 2024-04-28 01:20:23 -04:00
Chongyi Zheng 970965f6d8
Fix nil dereference on error (#30740)
In both cases, the `err` is nil because of `if` checks before

Reference: #30729
2024-04-28 12:13:57 +08:00
Chongyi Zheng 8b8b48ef5f
Use `ProtonMail/go-crypto` for `opengpg` in tests (#30736) 2024-04-27 19:21:33 -04:00
Chongyi Zheng 7b8e418da1
Replace deprecated `math/rand` functions (#30733)
Suggested by logs in #30729

- Remove `math/rand.Seed`
`rand.Seed is deprecated: As of Go 1.20 there is no reason to call Seed
with a random value.`
- Replace `math/rand.Read`
`rand.Read is deprecated: For almost all use cases, [crypto/rand.Read]
is more appropriate.`
- Replace `math/rand` with `math/rand/v2`, which is available since Go
1.22
2024-04-27 18:50:35 +02:00
a1012112796 04bae31b49
create issue comment for project board move operation
- as title
- remove logic about zero `project_board_id` in `project_issue`
  table and fix some related test data. because after #29874,
  `project_board_id` willn't be zero
- some small refactoring

Signed-off-by: a1012112796 <1012112796@qq.com>
2024-04-25 10:12:41 +00:00
32 changed files with 440 additions and 173 deletions

View File

@ -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

View File

@ -9,8 +9,6 @@ concurrency:
cancel-in-progress: true
jobs:
disk-clean:
uses: ./.github/workflows/disk-clean.yml
nightly-binary:
runs-on: nscloud
steps:

View File

@ -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"
},
{

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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

View File

@ -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.

View File

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

View File

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

View File

@ -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)
}

View File

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

95
models/issues/project.go Normal file
View File

@ -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
})
}

View File

@ -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)
}

View File

@ -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).

View File

@ -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)

View File

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

View File

@ -5,8 +5,8 @@ package user_test
import (
"context"
"crypto/rand"
"fmt"
"math/rand"
"strings"
"testing"
"time"

View File

@ -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())

View File

@ -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)

View File

@ -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)`

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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

View File

@ -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"
)

View File

@ -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}}">

View File

@ -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) {

View File

@ -5,9 +5,9 @@ package integration
import (
"bytes"
"crypto/rand"
"encoding/hex"
"fmt"
"math/rand"
"net/http"
"net/url"
"os"

View File

@ -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) {