Compare commits

...

29 Commits

Author SHA1 Message Date
Lunny Xiao a6b4dbc4ec
Merge 235c9d4fd0 into 9a0b449c4f 2024-04-28 07:28:15 +00:00
Lunny Xiao 235c9d4fd0
Fix bug 2024-04-28 15:28:06 +08:00
Lunny Xiao 6844f6f43f
Fix function name 2024-04-28 15:22:29 +08:00
Lunny Xiao 3fb24e53f1 Merge branch 'lunny/fix_move_column' of github.com:lunny/gitea into lunny/fix_move_column 2024-04-28 15:18:13 +08:00
Lunny Xiao b987f00778 Merge branch 'main' into lunny/fix_move_column 2024-04-28 15:17:53 +08:00
Lunny Xiao c2158063c1
Fix bug 2024-04-28 15:17:03 +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
Lunny Xiao 29d40083f0
Update models/project/issue.go
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-04-27 21:02:16 +08:00
Lunny Xiao 1f4852e551 Merge branch 'lunny/fix_move_column' of github.com:lunny/gitea into lunny/fix_move_column 2024-04-27 21:01:01 +08:00
Lunny Xiao da7eb69a03 Merge branch 'main' into lunny/fix_move_column 2024-04-27 21:00:40 +08:00
Lunny Xiao d300bfdc9c
Follow maintainers' suggestions 2024-04-27 21:00:21 +08:00
silverwind 2d078a7f5a
Merge branch 'main' into lunny/fix_move_column 2024-04-26 21:43:04 +02:00
Lunny Xiao 8229bedc3f Merge branch 'main' into lunny/fix_move_column 2024-04-26 22:07:25 +08:00
Lunny Xiao 53aea60bbc
Fix lint 2024-04-26 22:07:23 +08:00
Lunny Xiao 7bd212afa7 Merge branch 'lunny/fix_move_column' of github.com:lunny/gitea into lunny/fix_move_column 2024-04-26 17:19:23 +08:00
Lunny Xiao aeb0bd89a5 Merge branch 'main' into lunny/fix_move_column 2024-04-26 17:14:23 +08:00
Lunny Xiao 0613d7f8d5
Add the issue to default column when adding to a project 2024-04-26 17:14:09 +08:00
Lunny Xiao 1c67fe6f5f
Remove default column icon 2024-04-26 16:34:55 +08:00
Lunny Xiao 0610385dbd
Update templates/projects/view.tmpl
Co-authored-by: silverwind <me@silverwind.io>
2024-04-26 09:33:25 +08:00
Lunny Xiao 715ec4cebb
Update web_src/js/features/repo-projects.js
Co-authored-by: silverwind <me@silverwind.io>
2024-04-25 21:48:47 +08:00
Lunny Xiao 42bfa04a04
Fix lint 2024-04-25 14:14:52 +08:00
Lunny Xiao c041e36715
Fix repository project sorting 2024-04-25 14:11:06 +08:00
Lunny Xiao 753608a6cf
Use star as defult column's icon but not a pin and add a star icon on title of column 2024-04-25 12:01:07 +08:00
Lunny Xiao 75d10316a1 Merge branch 'main' into lunny/fix_move_column 2024-04-25 11:53:58 +08:00
Lunny Xiao 50a573f753
Fix lint 2024-04-25 11:53:46 +08:00
Lunny Xiao ad1095a5ec
Fix moving column start multiple POST requests and default column will not be moved. 2024-04-25 11:44:04 +08:00
24 changed files with 277 additions and 92 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

@ -57,6 +57,7 @@ type Engine interface {
SumInt(bean any, columnName string) (res int64, err error)
Sync(...any) error
Select(string) *xorm.Session
SetExpr(string, any) *xorm.Session
NotIn(string, ...any) *xorm.Session
OrderBy(any, ...any) *xorm.Session
Exist(...any) (bool, error)

View File

@ -90,22 +90,16 @@ func LoadIssuesFromBoardList(ctx context.Context, bs project_model.BoardList) (m
return issuesMap, nil
}
// ChangeProjectAssign changes the project associated with an issue
func ChangeProjectAssign(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID int64) error {
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return err
}
defer committer.Close()
if err := addUpdateIssueProject(ctx, issue, doer, newProjectID); err != nil {
return err
}
return committer.Commit()
// ChangeProjectAssign changes the project associated with an issue, if newProjectID is 0, the issue is removed from the project
func ChangeProjectAssign(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID, newColumnID int64) error {
return db.WithTx(ctx, func(ctx context.Context) error {
return addUpdateIssueProject(ctx, issue, doer, newProjectID, newColumnID)
})
}
func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID int64) error {
// addUpdateIssueProject adds or updates the project the default column associated with an issue
// If newProjectID is 0, the issue is removed from the project
func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID, newColumnID int64) error {
oldProjectID := issue.projectID(ctx)
if err := issue.LoadRepo(ctx); err != nil {
@ -139,9 +133,25 @@ func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.U
return err
}
}
if newProjectID == 0 || newColumnID == 0 {
return nil
}
var maxSorting int64
if _, err := db.GetEngine(ctx).Select("Max(sorting)").Table("project_issue").
Where("project_id=?", newProjectID).
And("project_board_id=?", newColumnID).
Get(&maxSorting); err != nil {
return err
}
if maxSorting > 0 {
maxSorting++
}
return db.Insert(ctx, &project_model.ProjectIssue{
IssueID: issue.ID,
ProjectID: newProjectID,
IssueID: issue.ID,
ProjectID: newProjectID,
ProjectBoardID: newColumnID,
Sorting: maxSorting,
})
}

View File

@ -156,6 +156,15 @@ func NewBoard(ctx context.Context, board *Board) error {
return fmt.Errorf("bad color code: %s", board.Color)
}
var maxSorting int8
if _, err := db.GetEngine(ctx).Select("Max(sorting)").Table("project_board").
Where("project_id=?", board.ProjectID).Get(&maxSorting); err != nil {
return err
}
if maxSorting > 0 {
board.Sorting = maxSorting + 1
}
_, err := db.GetEngine(ctx).Insert(board)
return err
}
@ -189,7 +198,17 @@ func deleteBoardByID(ctx context.Context, boardID int64) error {
return fmt.Errorf("deleteBoardByID: cannot delete default board")
}
if err = board.removeIssues(ctx); err != nil {
// move all issues to the default column
project, err := GetProjectByID(ctx, board.ProjectID)
if err != nil {
return err
}
defaultColumn, err := project.GetDefaultBoard(ctx)
if err != nil {
return err
}
if err = board.moveIssuesToAnotherColumn(ctx, defaultColumn.ID); err != nil {
return err
}
@ -242,21 +261,15 @@ func UpdateBoard(ctx context.Context, board *Board) error {
// GetBoards fetches all boards related to a project
func (p *Project) GetBoards(ctx context.Context) (BoardList, error) {
boards := make([]*Board, 0, 5)
if err := db.GetEngine(ctx).Where("project_id=? AND `default`=?", p.ID, false).OrderBy("sorting").Find(&boards); err != nil {
if err := db.GetEngine(ctx).Where("project_id=?", p.ID).OrderBy("sorting").Find(&boards); err != nil {
return nil, err
}
defaultB, err := p.getDefaultBoard(ctx)
if err != nil {
return nil, err
}
return append([]*Board{defaultB}, boards...), nil
return 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).
@ -316,3 +329,12 @@ func UpdateBoardSorting(ctx context.Context, bs BoardList) error {
return nil
})
}
func GetColumnsByIDs(ctx context.Context, columnsIDs []int64) (BoardList, error) {
columns := make([]*Board, 0, 5)
if err := db.GetEngine(ctx).In("id", columnsIDs).OrderBy("sorting").Find(&columns); err != nil {
return nil, err
}
return columns, nil
}

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,7 +17,7 @@ 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 should not be zero since 1.22. If it's zero, the issue will not be displayed on UI and it might result in errors.
ProjectBoardID int64 `xorm:"INDEX"`
// the sorting order on the board
@ -102,7 +102,37 @@ func MoveIssuesOnProjectBoard(ctx context.Context, board *Board, sortedIssueIDs
})
}
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)
func (b *Board) moveIssuesToAnotherColumn(ctx context.Context, columnID int64) error {
if b.ID == columnID {
return nil
}
_, err := db.GetEngine(ctx).Exec("UPDATE `project_issue` SET project_board_id = ? WHERE project_board_id = ? ", columnID, b.ID)
return err
}
// MoveColumnsOnProject moves or keeps issues in a column and sorts them inside that column
func MoveColumnsOnProject(ctx context.Context, project *Project, sortedColumnIDs map[int64]int64) error {
return db.WithTx(ctx, func(ctx context.Context) error {
sess := db.GetEngine(ctx)
columnIDs := make([]int64, 0, len(sortedColumnIDs))
for _, columnID := range sortedColumnIDs {
columnIDs = append(columnIDs, columnID)
}
count, err := sess.Table(new(Board)).Where("project_id=?", project.ID).In("id", columnIDs).Count()
if err != nil {
return err
}
if int(count) != len(sortedColumnIDs) {
return fmt.Errorf("all issues have to be added to a project first")
}
for sorting, columnID := range sortedColumnIDs {
_, err = sess.Exec("UPDATE `project_board` SET sorting=? WHERE id=?", sorting, columnID)
if err != nil {
return err
}
}
return nil
})
}

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

@ -442,6 +442,28 @@ func UpdateIssueProject(ctx *context.Context) {
}
projectID := ctx.FormInt64("id")
var dstColumnID int64
if projectID > 0 {
dstProject, err := project_model.GetProjectByID(ctx, projectID)
if err != nil {
ctx.ServerError("GetProjectByID", err)
return
}
if dstProject.OwnerID != ctx.ContextUser.ID {
ctx.JSON(http.StatusUnprocessableEntity, map[string]string{
"message": fmt.Sprintf("Project[%d] is not in Owner[%d] as expected", dstProject.ID, ctx.ContextUser.ID),
})
return
}
dstDefaultColumn, err := dstProject.GetDefaultBoard(ctx)
if err != nil {
ctx.ServerError("GetDefaultBoard", err)
return
}
dstColumnID = dstDefaultColumn.ID
}
for _, issue := range issues {
if issue.Project != nil {
if issue.Project.ID == projectID {
@ -449,7 +471,7 @@ func UpdateIssueProject(ctx *context.Context) {
}
}
if err := issues_model.ChangeProjectAssign(ctx, issue, ctx.Doer, projectID); err != nil {
if err := issues_model.ChangeProjectAssign(ctx, issue, ctx.Doer, projectID, dstColumnID); err != nil {
ctx.ServerError("ChangeProjectAssign", 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

@ -383,8 +383,33 @@ func UpdateIssueProject(ctx *context.Context) {
ctx.ServerError("LoadProjects", err)
return
}
if _, err := issues.LoadRepositories(ctx); err != nil {
ctx.ServerError("LoadProjects", err)
return
}
projectID := ctx.FormInt64("id")
var dstColumnID int64
if projectID > 0 {
dstProject, err := project_model.GetProjectByID(ctx, projectID)
if err != nil {
ctx.ServerError("GetProjectByID", err)
return
}
for _, issue := range issues {
if dstProject.RepoID != ctx.Repo.Repository.ID && dstProject.OwnerID != issue.Repo.OwnerID {
ctx.Error(http.StatusBadRequest, "project doesn't belong to the repository")
return
}
}
dstDefaultColumn, err := dstProject.GetDefaultBoard(ctx)
if err != nil {
ctx.ServerError("GetDefaultBoard", err)
return
}
dstColumnID = dstDefaultColumn.ID
}
for _, issue := range issues {
if issue.Project != nil {
if issue.Project.ID == projectID {
@ -392,7 +417,7 @@ func UpdateIssueProject(ctx *context.Context) {
}
}
if err := issues_model.ChangeProjectAssign(ctx, issue, ctx.Doer, projectID); err != nil {
if err := issues_model.ChangeProjectAssign(ctx, issue, ctx.Doer, projectID, dstColumnID); err != nil {
ctx.ServerError("ChangeProjectAssign", err)
return
}

View File

@ -20,6 +20,7 @@ import (
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"
project_model "code.gitea.io/gitea/models/project"
pull_model "code.gitea.io/gitea/models/pull"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
@ -1326,7 +1327,6 @@ func CompareAndPullRequestPost(ctx *context.Context) {
}
ctx.JSONError(flashError)
}
return
}
if projectID > 0 {
@ -1334,7 +1334,21 @@ func CompareAndPullRequestPost(ctx *context.Context) {
ctx.Error(http.StatusBadRequest, "user hasn't the permission to write to projects")
return
}
if err := issues_model.ChangeProjectAssign(ctx, pullIssue, ctx.Doer, projectID); err != nil {
dstProject, err := project_model.GetProjectByID(ctx, projectID)
if err != nil {
ctx.ServerError("GetProjectByID", err)
return
}
if dstProject.RepoID != ctx.Repo.Repository.ID && dstProject.OwnerID != repo.OwnerID {
ctx.Error(http.StatusBadRequest, "project doesn't belong to the repository")
return
}
dstDefaultColumn, err := dstProject.GetDefaultBoard(ctx)
if err != nil {
ctx.ServerError("GetDefaultBoard", err)
return
}
if err := issues_model.ChangeProjectAssign(ctx, pullIssue, ctx.Doer, projectID, dstDefaultColumn.ID); err != nil {
ctx.ServerError("ChangeProjectAssign", err)
return
}

View File

@ -0,0 +1,74 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package project
import (
"errors"
issues_model "code.gitea.io/gitea/models/issues"
project_model "code.gitea.io/gitea/models/project"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/services/context"
)
// MoveColumns moves or keeps columns in a project and sorts them inside that project
func MoveColumns(ctx *context.Context) {
project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
if err != nil {
ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
return
}
if project.OwnerID > 0 && project.OwnerID != ctx.ContextUser.ID {
ctx.NotFound("InvalidOwnerID", nil)
return
}
if project.RepoID > 0 && project.RepoID != ctx.Repo.Repository.ID {
ctx.NotFound("InvalidRepoID", nil)
return
}
type movedColumnsForm struct {
Columns []struct {
ColumnID int64 `json:"columnID"`
Sorting int64 `json:"sorting"`
} `json:"columns"`
}
form := &movedColumnsForm{}
if err = json.NewDecoder(ctx.Req.Body).Decode(&form); err != nil {
ctx.ServerError("DecodeMovedColumnsForm", err)
return
}
columnIDs := make([]int64, 0, len(form.Columns))
sortedColumnIDs := make(map[int64]int64)
for _, column := range form.Columns {
columnIDs = append(columnIDs, column.ColumnID)
sortedColumnIDs[column.Sorting] = column.ColumnID
}
movedColumns, err := project_model.GetColumnsByIDs(ctx, columnIDs)
if err != nil {
ctx.NotFoundOrServerError("GetColumnsByIDs", issues_model.IsErrIssueNotExist, err)
return
}
if len(movedColumns) != len(form.Columns) {
ctx.ServerError("some columns do not exist", errors.New("some columns do not exist"))
return
}
for _, column := range movedColumns {
if column.ProjectID != project.ID {
ctx.ServerError("Some column's projectID is not equal to project's ID", errors.New("Some column's projectID is not equal to project's ID"))
return
}
}
if err = project_model.MoveColumnsOnProject(ctx, project, sortedColumnIDs); err != nil {
ctx.ServerError("MoveColumnsOnProject", err)
return
}
ctx.JSONOK()
}

View File

@ -37,6 +37,7 @@ import (
"code.gitea.io/gitea/routers/web/repo"
"code.gitea.io/gitea/routers/web/repo/actions"
repo_setting "code.gitea.io/gitea/routers/web/repo/setting"
"code.gitea.io/gitea/routers/web/shared/project"
"code.gitea.io/gitea/routers/web/user"
user_setting "code.gitea.io/gitea/routers/web/user/setting"
"code.gitea.io/gitea/routers/web/user/setting/security"
@ -999,6 +1000,7 @@ func registerRoutes(m *web.Route) {
m.Post("/new", web.Bind(forms.CreateProjectForm{}), org.NewProjectPost)
m.Group("/{id}", func() {
m.Post("", web.Bind(forms.EditProjectBoardForm{}), org.AddBoardToProjectPost)
m.Post("/move", project.MoveColumns)
m.Post("/delete", org.DeleteProject)
m.Get("/edit", org.RenderEditProject)
@ -1354,6 +1356,7 @@ func registerRoutes(m *web.Route) {
m.Post("/new", web.Bind(forms.CreateProjectForm{}), repo.NewProjectPost)
m.Group("/{id}", func() {
m.Post("", web.Bind(forms.EditProjectBoardForm{}), repo.AddBoardToProjectPost)
m.Post("/move", project.MoveColumns)
m.Post("/delete", repo.DeleteProject)
m.Get("/edit", repo.RenderEditProject)

View File

@ -42,7 +42,15 @@ func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *issues_mo
}
}
if projectID > 0 {
if err := issues_model.ChangeProjectAssign(ctx, issue, issue.Poster, projectID); err != nil {
project, err := project_model.GetProjectByID(ctx, projectID)
if err != nil {
return err
}
defaultBoard, err := project.GetDefaultBoard(ctx)
if err != nil {
return err
}
if err := issues_model.ChangeProjectAssign(ctx, issue, issue.Poster, projectID, defaultBoard.ID); err != nil {
return err
}
}

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

@ -64,7 +64,7 @@
</div>
<div id="project-board">
<div class="board {{if .CanWriteProjects}}sortable{{end}}">
<div class="board {{if .CanWriteProjects}}sortable{{end}}"{{if .CanWriteProjects}} data-url="{{$.Link}}/move"{{end}}>
{{range .Columns}}
<div class="ui segment project-column"{{if .Color}} style="background: {{.Color}} !important; color: {{ContrastColor .Color}} !important"{{end}} data-id="{{.ID}}" data-sorting="{{.Sorting}}" data-url="{{$.Link}}/{{.ID}}">
<div class="project-column-header{{if $canWriteProject}} tw-cursor-grab{{end}}">

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

View File

@ -2,7 +2,6 @@ import $ from 'jquery';
import {contrastColor} from '../utils/color.js';
import {createSortable} from '../modules/sortable.js';
import {POST, DELETE, PUT} from '../modules/fetch.js';
import tinycolor from 'tinycolor2';
function updateIssueCount(cards) {
const parent = cards.parentElement;
@ -63,17 +62,20 @@ async function initRepoProjectSortable() {
delay: 500,
onSort: async () => {
boardColumns = mainBoard.getElementsByClassName('project-column');
for (let i = 0; i < boardColumns.length; i++) {
const column = boardColumns[i];
if (parseInt(column.getAttribute('data-sorting')) !== i) {
try {
const bgColor = column.style.backgroundColor; // will be rgb() string
const color = bgColor ? tinycolor(bgColor).toHexString() : '';
await PUT(column.getAttribute('data-url'), {data: {sorting: i, color}});
} catch (error) {
console.error(error);
}
}
const columnSorting = {
columns: Array.from(boardColumns, (column, i) => ({
columnID: parseInt(column.getAttribute('data-id')),
sorting: i,
})),
};
try {
await POST(mainBoard.getAttribute('data-url'), {
data: columnSorting,
});
} catch (error) {
console.error(error);
}
},
});