mirror of https://github.com/go-gitea/gitea.git
Compare commits
12 Commits
6ca247e3bb
...
1b574ee75d
Author | SHA1 | Date |
---|---|---|
Zettat123 | 1b574ee75d | |
wxiaoguang | 8de2992ffb | |
wxiaoguang | 6d2a307ad8 | |
silverwind | b93c87b6fe | |
Yarden Shoham | 51c28d9683 | |
wxiaoguang | d3cdef88ad | |
Kemal Zebari | dd301cae1c | |
silverwind | 238eb3ff9f | |
silverwind | b2abac5e5f | |
Chongyi Zheng | 4ae6b1a553 | |
Zettat123 | 802efa342d | |
Zettat123 | 078d120ac5 |
|
@ -35,7 +35,7 @@ var microcmdUserChangePassword = &cli.Command{
|
|||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "must-change-password",
|
||||
Usage: "User must change password",
|
||||
Usage: "User must change password (can be disabled by --must-change-password=false)",
|
||||
Value: true,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
|
@ -48,7 +49,7 @@ var microcmdUserCreate = &cli.Command{
|
|||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "must-change-password",
|
||||
Usage: "Set to false to prevent forcing the user to change their password after initial login",
|
||||
Usage: "User must change password after initial login, defaults to true for all users except the first one (can be disabled by --must-change-password=false)",
|
||||
DisableDefaultText: true,
|
||||
},
|
||||
&cli.IntFlag{
|
||||
|
@ -91,11 +92,16 @@ func runCreateUser(c *cli.Context) error {
|
|||
_, _ = fmt.Fprintf(c.App.ErrWriter, "--name flag is deprecated. Use --username instead.\n")
|
||||
}
|
||||
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
if err := initDB(ctx); err != nil {
|
||||
return err
|
||||
ctx := c.Context
|
||||
if !setting.IsInTesting {
|
||||
// FIXME: need to refactor the "installSignals/initDB" related code later
|
||||
// it doesn't make sense to call it in (almost) every command action function
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = installSignals()
|
||||
defer cancel()
|
||||
if err := initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var password string
|
||||
|
@ -123,8 +129,8 @@ func runCreateUser(c *cli.Context) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("IsTableNotEmpty: %w", err)
|
||||
}
|
||||
if !hasUserRecord && isAdmin {
|
||||
// if this is the first admin being created, don't force to change password (keep the old behavior)
|
||||
if !hasUserRecord {
|
||||
// if this is the first one being created, don't force to change password (keep the old behavior)
|
||||
mustChangePassword = false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAdminUserCreate(t *testing.T) {
|
||||
app := NewMainApp(AppVersion{})
|
||||
|
||||
reset := func() {
|
||||
assert.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{}))
|
||||
assert.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.EmailAddress{}))
|
||||
}
|
||||
|
||||
type createCheck struct{ IsAdmin, MustChangePassword bool }
|
||||
createUser := func(name, args string) createCheck {
|
||||
assert.NoError(t, app.Run(strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %s@gitea.local %s --password foobar", name, name, args))))
|
||||
u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: name})
|
||||
return createCheck{u.IsAdmin, u.MustChangePassword}
|
||||
}
|
||||
reset()
|
||||
assert.Equal(t, createCheck{IsAdmin: false, MustChangePassword: false}, createUser("u", ""), "first non-admin user doesn't need to change password")
|
||||
|
||||
reset()
|
||||
assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: false}, createUser("u", "--admin"), "first admin user doesn't need to change password")
|
||||
|
||||
reset()
|
||||
assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: true}, createUser("u", "--admin --must-change-password"))
|
||||
assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: true}, createUser("u2", "--admin"))
|
||||
assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: false}, createUser("u3", "--admin --must-change-password=false"))
|
||||
assert.Equal(t, createCheck{IsAdmin: false, MustChangePassword: true}, createUser("u4", ""))
|
||||
assert.Equal(t, createCheck{IsAdmin: false, MustChangePassword: false}, createUser("u5", "--must-change-password=false"))
|
||||
}
|
|
@ -112,13 +112,18 @@ func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(ctx *cli.Context)
|
|||
}
|
||||
}
|
||||
|
||||
func NewMainApp(version, versionExtra string) *cli.App {
|
||||
type AppVersion struct {
|
||||
Version string
|
||||
Extra string
|
||||
}
|
||||
|
||||
func NewMainApp(appVer AppVersion) *cli.App {
|
||||
app := cli.NewApp()
|
||||
app.Name = "Gitea"
|
||||
app.HelpName = "gitea"
|
||||
app.Usage = "A painless self-hosted Git service"
|
||||
app.Description = `Gitea program contains "web" and other subcommands. If no subcommand is given, it starts the web server by default. Use "web" subcommand for more web server arguments, use other subcommands for other purposes.`
|
||||
app.Version = version + versionExtra
|
||||
app.Version = appVer.Version + appVer.Extra
|
||||
app.EnableBashCompletion = true
|
||||
|
||||
// these sub-commands need to use config file
|
||||
|
|
|
@ -28,7 +28,7 @@ func makePathOutput(workPath, customPath, customConf string) string {
|
|||
}
|
||||
|
||||
func newTestApp(testCmdAction func(ctx *cli.Context) error) *cli.App {
|
||||
app := NewMainApp("version", "version-extra")
|
||||
app := NewMainApp(AppVersion{})
|
||||
testCmd := &cli.Command{Name: "test-cmd", Action: testCmdAction}
|
||||
prepareSubcommandWithConfig(testCmd, appGlobalFlags())
|
||||
app.Commands = append(app.Commands, testCmd)
|
||||
|
|
2
main.go
2
main.go
|
@ -42,7 +42,7 @@ func main() {
|
|||
log.GetManager().Close()
|
||||
os.Exit(code)
|
||||
}
|
||||
app := cmd.NewMainApp(Version, formatBuiltWith())
|
||||
app := cmd.NewMainApp(cmd.AppVersion{Version: Version, Extra: formatBuiltWith()})
|
||||
_ = cmd.RunMainApp(app, os.Args...) // all errors should have been handled by the RunMainApp
|
||||
log.GetManager().Close()
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ func TestCreateIssueDependency(t *testing.T) {
|
|||
assert.False(t, left)
|
||||
|
||||
// Close #2 and check again
|
||||
_, err = issues_model.ChangeIssueStatus(db.DefaultContext, issue2, user1, true)
|
||||
_, err = issues_model.ChangeIssueStatus(db.DefaultContext, issue2, user1, true, false)
|
||||
assert.NoError(t, err)
|
||||
|
||||
left, err = issues_model.IssueNoDependenciesLeft(db.DefaultContext, issue1)
|
||||
|
|
|
@ -119,7 +119,7 @@ func doChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.Use
|
|||
}
|
||||
|
||||
// ChangeIssueStatus changes issue status to open or closed.
|
||||
func ChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.User, isClosed bool) (*Comment, error) {
|
||||
func ChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.User, isClosed, isMergePull bool) (*Comment, error) {
|
||||
if err := issue.LoadRepo(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -127,7 +127,7 @@ func ChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.User,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return changeIssueStatus(ctx, issue, doer, isClosed, false)
|
||||
return changeIssueStatus(ctx, issue, doer, isClosed, isMergePull)
|
||||
}
|
||||
|
||||
// ChangeIssueTitle changes the title of this issue, as the given user.
|
||||
|
|
|
@ -98,7 +98,7 @@ func TestXRef_ResolveCrossReferences(t *testing.T) {
|
|||
i1 := testCreateIssue(t, 1, 2, "title1", "content1", false)
|
||||
i2 := testCreateIssue(t, 1, 2, "title2", "content2", false)
|
||||
i3 := testCreateIssue(t, 1, 2, "title3", "content3", false)
|
||||
_, err := issues_model.ChangeIssueStatus(db.DefaultContext, i3, d, true)
|
||||
_, err := issues_model.ChangeIssueStatus(db.DefaultContext, i3, d, true, false)
|
||||
assert.NoError(t, err)
|
||||
|
||||
pr := testCreatePR(t, 1, 2, "titlepr", fmt.Sprintf("closes #%d", i1.Index))
|
||||
|
|
|
@ -499,10 +499,6 @@ func (pr *PullRequest) SetMerged(ctx context.Context) (bool, error) {
|
|||
return false, err
|
||||
}
|
||||
|
||||
if _, err := changeIssueStatus(ctx, pr.Issue, pr.Merger, true, true); err != nil {
|
||||
return false, fmt.Errorf("Issue.changeStatus: %w", err)
|
||||
}
|
||||
|
||||
// reset the conflicted files as there cannot be any if we're merged
|
||||
pr.ConflictedFiles = []string{}
|
||||
|
||||
|
|
|
@ -21,7 +21,6 @@ import (
|
|||
"code.gitea.io/gitea/models/migrations/v1_20"
|
||||
"code.gitea.io/gitea/models/migrations/v1_21"
|
||||
"code.gitea.io/gitea/models/migrations/v1_22"
|
||||
"code.gitea.io/gitea/models/migrations/v1_23"
|
||||
"code.gitea.io/gitea/models/migrations/v1_6"
|
||||
"code.gitea.io/gitea/models/migrations/v1_7"
|
||||
"code.gitea.io/gitea/models/migrations/v1_8"
|
||||
|
@ -574,18 +573,20 @@ var migrations = []Migration{
|
|||
// v293 -> v294
|
||||
NewMigration("Ensure every project has exactly one default column", v1_22.CheckProjectColumnsConsistency),
|
||||
|
||||
// Gitea 1.22.0 ends at 294
|
||||
// Gitea 1.22.0-rc0 ends at 294
|
||||
|
||||
// v294 -> v295
|
||||
NewMigration("Add unique index for project issue table", v1_23.AddUniqueIndexForProjectIssue),
|
||||
NewMigration("Add unique index for project issue table", v1_22.AddUniqueIndexForProjectIssue),
|
||||
// v295 -> v296
|
||||
NewMigration("Add commit status summary table", v1_23.AddCommitStatusSummary),
|
||||
NewMigration("Add commit status summary table", v1_22.AddCommitStatusSummary),
|
||||
// v296 -> v297
|
||||
NewMigration("Add missing field of commit status summary table", v1_23.AddCommitStatusSummary2),
|
||||
NewMigration("Add missing field of commit status summary table", v1_22.AddCommitStatusSummary2),
|
||||
// v297 -> v298
|
||||
NewMigration("Add everyone_access_mode for repo_unit", v1_23.AddRepoUnitEveryoneAccessMode),
|
||||
NewMigration("Add everyone_access_mode for repo_unit", v1_22.AddRepoUnitEveryoneAccessMode),
|
||||
// v298 -> v299
|
||||
NewMigration("Drop wrongly created table o_auth2_application", v1_23.DropWronglyCreatedTable),
|
||||
NewMigration("Drop wrongly created table o_auth2_application", v1_22.DropWronglyCreatedTable),
|
||||
|
||||
// Gitea 1.22.0-rc1 ends at 299
|
||||
}
|
||||
|
||||
// GetCurrentDBVersion returns the current db version
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_23 //nolint
|
||||
package v1_22 //nolint
|
||||
|
||||
import (
|
||||
"fmt"
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_23 //nolint
|
||||
package v1_22 //nolint
|
||||
|
||||
import (
|
||||
"slices"
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_23 //nolint
|
||||
package v1_22 //nolint
|
||||
|
||||
import "xorm.io/xorm"
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_23 //nolint
|
||||
package v1_22 //nolint
|
||||
|
||||
import "xorm.io/xorm"
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_23 //nolint
|
||||
package v1_22 //nolint
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/models/perm"
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_23 //nolint
|
||||
package v1_22 //nolint
|
||||
|
||||
import "xorm.io/xorm"
|
||||
|
|
@ -6,7 +6,6 @@ package unittest
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
@ -18,6 +17,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/cache"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/setting/config"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
|
@ -46,6 +46,14 @@ func fatalTestError(fmtStr string, args ...any) {
|
|||
|
||||
// InitSettings initializes config provider and load common settings for tests
|
||||
func InitSettings() {
|
||||
setting.IsInTesting = true
|
||||
log.OsExiter = func(code int) {
|
||||
if code != 0 {
|
||||
// non-zero exit code (log.Fatal) shouldn't occur during testing, if it happens, show a full stacktrace for more details
|
||||
panic(fmt.Errorf("non-zero exit code during testing: %d", code))
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
if setting.CustomConf == "" {
|
||||
setting.CustomConf = filepath.Join(setting.CustomPath, "conf/app-unittest-tmp.ini")
|
||||
_ = os.Remove(setting.CustomConf)
|
||||
|
@ -54,7 +62,7 @@ func InitSettings() {
|
|||
setting.LoadCommonSettings()
|
||||
|
||||
if err := setting.PrepareAppDataPath(); err != nil {
|
||||
log.Fatalf("Can not prepare APP_DATA_PATH: %v", err)
|
||||
log.Fatal("Can not prepare APP_DATA_PATH: %v", err)
|
||||
}
|
||||
// register the dummy hash algorithm function used in the test fixtures
|
||||
_ = hash.Register("dummy", hash.NewDummyHasher)
|
||||
|
|
|
@ -57,11 +57,13 @@ func Critical(format string, v ...any) {
|
|||
Log(1, ERROR, format, v...)
|
||||
}
|
||||
|
||||
var OsExiter = os.Exit
|
||||
|
||||
// Fatal records fatal log and exit process
|
||||
func Fatal(format string, v ...any) {
|
||||
Log(1, FATAL, format, v...)
|
||||
GetManager().Close()
|
||||
os.Exit(1)
|
||||
OsExiter(1)
|
||||
}
|
||||
|
||||
func GetLogger(name string) Logger {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
26
package.json
26
package.json
|
@ -4,9 +4,9 @@
|
|||
"node": ">= 18.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@citation-js/core": "0.7.9",
|
||||
"@citation-js/plugin-bibtex": "0.7.9",
|
||||
"@citation-js/plugin-csl": "0.7.9",
|
||||
"@citation-js/core": "0.7.11",
|
||||
"@citation-js/plugin-bibtex": "0.7.11",
|
||||
"@citation-js/plugin-csl": "0.7.11",
|
||||
"@citation-js/plugin-software-formats": "0.6.1",
|
||||
"@github/markdown-toolbar-element": "2.2.3",
|
||||
"@github/relative-time-element": "4.4.0",
|
||||
|
@ -33,17 +33,17 @@
|
|||
"katex": "0.16.10",
|
||||
"license-checker-webpack-plugin": "0.2.1",
|
||||
"mermaid": "10.9.0",
|
||||
"mini-css-extract-plugin": "2.8.1",
|
||||
"mini-css-extract-plugin": "2.9.0",
|
||||
"minimatch": "9.0.4",
|
||||
"monaco-editor": "0.47.0",
|
||||
"monaco-editor": "0.48.0",
|
||||
"monaco-editor-webpack-plugin": "7.1.0",
|
||||
"pdfobject": "2.3.0",
|
||||
"postcss": "8.4.38",
|
||||
"postcss-loader": "8.1.1",
|
||||
"postcss-nesting": "12.1.1",
|
||||
"postcss-nesting": "12.1.2",
|
||||
"pretty-ms": "9.0.0",
|
||||
"sortablejs": "1.15.2",
|
||||
"swagger-ui-dist": "5.15.1",
|
||||
"swagger-ui-dist": "5.17.2",
|
||||
"tailwindcss": "3.4.3",
|
||||
"temporal-polyfill": "0.2.4",
|
||||
"throttle-debounce": "5.0.0",
|
||||
|
@ -53,7 +53,7 @@
|
|||
"tributejs": "5.1.3",
|
||||
"uint8-to-base64": "0.2.0",
|
||||
"vanilla-colorful": "0.7.2",
|
||||
"vue": "3.4.21",
|
||||
"vue": "3.4.25",
|
||||
"vue-bar-graph": "2.0.0",
|
||||
"vue-chartjs": "5.3.1",
|
||||
"vue-loader": "17.4.2",
|
||||
|
@ -66,7 +66,7 @@
|
|||
"@eslint-community/eslint-plugin-eslint-comments": "4.3.0",
|
||||
"@playwright/test": "1.43.1",
|
||||
"@stoplight/spectral-cli": "6.11.1",
|
||||
"@stylistic/eslint-plugin-js": "1.7.0",
|
||||
"@stylistic/eslint-plugin-js": "1.7.2",
|
||||
"@stylistic/stylelint-plugin": "2.1.1",
|
||||
"@vitejs/plugin-vue": "5.0.4",
|
||||
"eslint": "8.57.0",
|
||||
|
@ -81,20 +81,20 @@
|
|||
"eslint-plugin-unicorn": "52.0.0",
|
||||
"eslint-plugin-vitest": "0.4.1",
|
||||
"eslint-plugin-vitest-globals": "1.5.0",
|
||||
"eslint-plugin-vue": "9.24.1",
|
||||
"eslint-plugin-vue": "9.25.0",
|
||||
"eslint-plugin-vue-scoped-css": "2.8.0",
|
||||
"eslint-plugin-wc": "2.1.0",
|
||||
"happy-dom": "14.7.1",
|
||||
"markdownlint-cli": "0.39.0",
|
||||
"postcss-html": "1.6.0",
|
||||
"stylelint": "16.3.1",
|
||||
"stylelint": "16.4.0",
|
||||
"stylelint-declaration-block-no-ignored-properties": "2.8.0",
|
||||
"stylelint-declaration-strict-value": "1.10.4",
|
||||
"stylelint-value-no-unknown-custom-properties": "6.0.1",
|
||||
"svgo": "3.2.0",
|
||||
"updates": "16.0.1",
|
||||
"vite-string-plugin": "1.1.5",
|
||||
"vitest": "1.5.0"
|
||||
"vite-string-plugin": "1.2.0",
|
||||
"vitest": "1.5.2"
|
||||
},
|
||||
"browserslist": [
|
||||
"defaults"
|
||||
|
|
|
@ -720,7 +720,7 @@ func CreateIssue(ctx *context.APIContext) {
|
|||
}
|
||||
|
||||
if form.Closed {
|
||||
if err := issue_service.ChangeStatus(ctx, issue, ctx.Doer, "", true); err != nil {
|
||||
if err := issue_service.ChangeStatus(ctx, issue, ctx.Doer, "", true, false); err != nil {
|
||||
if issues_model.IsErrDependenciesLeft(err) {
|
||||
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies")
|
||||
return
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
package repo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
@ -372,7 +373,11 @@ func CreatePullReview(ctx *context.APIContext) {
|
|||
// create review and associate all pending review comments
|
||||
review, _, err := pull_service.SubmitReview(ctx, ctx.Doer, ctx.Repo.GitRepo, pr.Issue, reviewType, opts.Body, opts.CommitID, nil)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "SubmitReview", err)
|
||||
if errors.Is(err, pull_service.ErrSubmitReviewOnClosedPR) {
|
||||
ctx.Error(http.StatusUnprocessableEntity, "", err)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "SubmitReview", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -460,7 +465,11 @@ func SubmitPullReview(ctx *context.APIContext) {
|
|||
// create review and associate all pending review comments
|
||||
review, _, err = pull_service.SubmitReview(ctx, ctx.Doer, ctx.Repo.GitRepo, pr.Issue, reviewType, opts.Body, headCommitID, nil)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "SubmitReview", err)
|
||||
if errors.Is(err, pull_service.ErrSubmitReviewOnClosedPR) {
|
||||
ctx.Error(http.StatusUnprocessableEntity, "", err)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "SubmitReview", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -242,7 +242,8 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
|
|||
}
|
||||
|
||||
var isShowClosed optional.Option[bool]
|
||||
switch ctx.FormString("state") {
|
||||
stateVal := ctx.FormString("state")
|
||||
switch stateVal {
|
||||
case "closed":
|
||||
isShowClosed = optional.Some(true)
|
||||
case "all":
|
||||
|
@ -2924,7 +2925,7 @@ func UpdateIssueStatus(ctx *context.Context) {
|
|||
continue
|
||||
}
|
||||
if issue.IsClosed != isClosed {
|
||||
if err := issue_service.ChangeStatus(ctx, issue, ctx.Doer, "", isClosed); err != nil {
|
||||
if err := issue_service.ChangeStatus(ctx, issue, ctx.Doer, "", isClosed, false); err != nil {
|
||||
if issues_model.IsErrDependenciesLeft(err) {
|
||||
ctx.JSON(http.StatusPreconditionFailed, map[string]any{
|
||||
"error": ctx.Tr("repo.issues.dependency.issue_batch_close_blocked", issue.Index),
|
||||
|
@ -3068,7 +3069,7 @@ func NewComment(ctx *context.Context) {
|
|||
ctx.Flash.Info(ctx.Tr("repo.pulls.open_unmerged_pull_exists", pr.Index))
|
||||
} else {
|
||||
isClosed := form.Status == "close"
|
||||
if err := issue_service.ChangeStatus(ctx, issue, ctx.Doer, "", isClosed); err != nil {
|
||||
if err := issue_service.ChangeStatus(ctx, issue, ctx.Doer, "", isClosed, false); err != nil {
|
||||
log.Error("ChangeStatus: %v", err)
|
||||
|
||||
if issues_model.IsErrDependenciesLeft(err) {
|
||||
|
@ -3079,13 +3080,6 @@ func NewComment(ctx *context.Context) {
|
|||
}
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if err := stopTimerIfAvailable(ctx, ctx.Doer, issue); err != nil {
|
||||
ctx.ServerError("CreateOrStopIssueStopwatch", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("Issue [%d] status changed to closed: %v", issue.ID, issue.IsClosed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1153,13 +1153,6 @@ func MergePullRequest(ctx *context.Context) {
|
|||
}
|
||||
log.Trace("Pull request merged: %d", pr.ID)
|
||||
|
||||
if err := stopTimerIfAvailable(ctx, ctx.Doer, issue); err != nil {
|
||||
ctx.ServerError("stopTimerIfAvailable", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("Pull request merged: %d", pr.ID)
|
||||
|
||||
if form.DeleteBranchAfterMerge {
|
||||
// Don't cleanup when other pr use this branch as head branch
|
||||
exist, err := issues_model.HasUnmergedPullRequestsByHeadInfo(ctx, pr.HeadRepoID, pr.HeadBranch)
|
||||
|
@ -1209,16 +1202,6 @@ func CancelAutoMergePullRequest(ctx *context.Context) {
|
|||
ctx.Redirect(fmt.Sprintf("%s/pulls/%d", ctx.Repo.RepoLink, issue.Index))
|
||||
}
|
||||
|
||||
func stopTimerIfAvailable(ctx *context.Context, user *user_model.User, issue *issues_model.Issue) error {
|
||||
if issues_model.StopwatchExists(ctx, user.ID, issue.ID) {
|
||||
if err := issues_model.CreateOrStopIssueStopwatch(ctx, user, issue); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CompareAndPullRequestPost response for creating pull request
|
||||
func CompareAndPullRequestPost(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*forms.CreateIssueForm)
|
||||
|
|
|
@ -264,6 +264,8 @@ func SubmitReview(ctx *context.Context) {
|
|||
if issues_model.IsContentEmptyErr(err) {
|
||||
ctx.Flash.Error(ctx.Tr("repo.issues.review.content.empty"))
|
||||
ctx.JSONRedirect(fmt.Sprintf("%s/pulls/%d/files", ctx.Repo.RepoLink, issue.Index))
|
||||
} else if errors.Is(err, pull_service.ErrSubmitReviewOnClosedPR) {
|
||||
ctx.Status(http.StatusUnprocessableEntity)
|
||||
} else {
|
||||
ctx.ServerError("SubmitReview", err)
|
||||
}
|
||||
|
|
|
@ -196,7 +196,7 @@ func UpdateIssuesCommit(ctx context.Context, doer *user_model.User, repo *repo_m
|
|||
}
|
||||
if isClosed != refIssue.IsClosed {
|
||||
refIssue.Repo = refRepo
|
||||
if err := ChangeStatus(ctx, refIssue, doer, c.Sha1, isClosed); err != nil {
|
||||
if err := ChangeStatus(ctx, refIssue, doer, c.Sha1, isClosed, false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,8 +13,8 @@ import (
|
|||
)
|
||||
|
||||
// ChangeStatus changes issue status to open or closed.
|
||||
func ChangeStatus(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, commitID string, closed bool) error {
|
||||
comment, err := issues_model.ChangeIssueStatus(ctx, issue, doer, closed)
|
||||
func ChangeStatus(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, commitID string, closed, isMergePull bool) error {
|
||||
comment, err := issues_model.ChangeIssueStatus(ctx, issue, doer, closed, isMergePull)
|
||||
if err != nil {
|
||||
if issues_model.IsErrDependenciesLeft(err) && closed {
|
||||
if err := issues_model.FinishIssueStopwatchIfPossible(ctx, doer, issue); err != nil {
|
||||
|
|
|
@ -40,7 +40,7 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error {
|
|||
}
|
||||
log.Trace("Doing: Update")
|
||||
|
||||
handler := func(idx int, bean any) error {
|
||||
handler := func(bean any) error {
|
||||
var repo *repo_model.Repository
|
||||
var mirrorType SyncType
|
||||
var referenceID int64
|
||||
|
@ -91,7 +91,7 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error {
|
|||
pullMirrorsRequested := 0
|
||||
if pullLimit != 0 {
|
||||
if err := repo_model.MirrorsIterate(ctx, pullLimit, func(idx int, bean any) error {
|
||||
if err := handler(idx, bean); err != nil {
|
||||
if err := handler(bean); err != nil {
|
||||
return err
|
||||
}
|
||||
pullMirrorsRequested++
|
||||
|
@ -105,7 +105,7 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error {
|
|||
pushMirrorsRequested := 0
|
||||
if pushLimit != 0 {
|
||||
if err := repo_model.PushMirrorsIterate(ctx, pushLimit, func(idx int, bean any) error {
|
||||
if err := handler(idx, bean); err != nil {
|
||||
if err := handler(bean); err != nil {
|
||||
return err
|
||||
}
|
||||
pushMirrorsRequested++
|
||||
|
|
|
@ -466,7 +466,7 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool {
|
|||
|
||||
log.Trace("SyncMirrors [repo: %-v]: %d branches updated", m.Repo, len(results))
|
||||
if len(results) > 0 {
|
||||
if ok := checkAndUpdateEmptyRepository(ctx, m, gitRepo, results); !ok {
|
||||
if ok := checkAndUpdateEmptyRepository(ctx, m, results); !ok {
|
||||
log.Error("SyncMirrors [repo: %-v]: checkAndUpdateEmptyRepository: %v", m.Repo, err)
|
||||
return false
|
||||
}
|
||||
|
@ -564,7 +564,7 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func checkAndUpdateEmptyRepository(ctx context.Context, m *repo_model.Mirror, gitRepo *git.Repository, results []*mirrorSyncResult) bool {
|
||||
func checkAndUpdateEmptyRepository(ctx context.Context, m *repo_model.Mirror, results []*mirrorSyncResult) bool {
|
||||
if !m.Repo.IsEmpty {
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/queue"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
||||
issue_service "code.gitea.io/gitea/services/issue"
|
||||
notify_service "code.gitea.io/gitea/services/notify"
|
||||
)
|
||||
|
||||
|
@ -296,6 +297,10 @@ func manuallyMerged(ctx context.Context, pr *issues_model.PullRequest) bool {
|
|||
return false
|
||||
} else if !merged {
|
||||
return false
|
||||
} else {
|
||||
if err = issue_service.ChangeStatus(ctx, pr.Issue, pr.Merger, pr.MergedCommitID, true, true); err != nil {
|
||||
log.Error("ChangeStatus %-v: %v", pr, err)
|
||||
}
|
||||
}
|
||||
|
||||
notify_service.MergePullRequest(ctx, merger, pr)
|
||||
|
|
|
@ -193,8 +193,12 @@ func Merge(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.U
|
|||
pr.Merger = doer
|
||||
pr.MergerID = doer.ID
|
||||
|
||||
if _, err := pr.SetMerged(ctx); err != nil {
|
||||
if merged, err := pr.SetMerged(ctx); err != nil {
|
||||
log.Error("SetMerged %-v: %v", pr, err)
|
||||
} else if merged {
|
||||
if err = issue_service.ChangeStatus(ctx, pr.Issue, pr.Merger, pr.MergedCommitID, true, true); err != nil {
|
||||
log.Error("ChangeStatus %-v: %v", pr, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := pr.LoadIssue(ctx); err != nil {
|
||||
|
@ -233,7 +237,7 @@ func Merge(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.U
|
|||
}
|
||||
isClosed := ref.RefAction == references.XRefActionCloses
|
||||
if isClosed != ref.Issue.IsClosed {
|
||||
if err = issue_service.ChangeStatus(ctx, ref.Issue, doer, pr.MergedCommitID, isClosed); err != nil {
|
||||
if err = issue_service.ChangeStatus(ctx, ref.Issue, doer, pr.MergedCommitID, isClosed, false); err != nil {
|
||||
// Allow ErrDependenciesLeft
|
||||
if !issues_model.IsErrDependenciesLeft(err) {
|
||||
return err
|
||||
|
@ -530,6 +534,10 @@ func MergedManually(ctx context.Context, pr *issues_model.PullRequest, doer *use
|
|||
return err
|
||||
} else if !merged {
|
||||
return fmt.Errorf("SetMerged failed")
|
||||
} else {
|
||||
if err = issue_service.ChangeStatus(ctx, pr.Issue, pr.Merger, pr.MergedCommitID, true, true); err != nil {
|
||||
log.Error("ChangeStatus %-v: %v", pr, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
|
|
|
@ -633,7 +633,7 @@ func CloseBranchPulls(ctx context.Context, doer *user_model.User, repoID int64,
|
|||
|
||||
var errs errlist
|
||||
for _, pr := range prs {
|
||||
if err = issue_service.ChangeStatus(ctx, pr.Issue, doer, "", true); err != nil && !issues_model.IsErrPullWasClosed(err) && !issues_model.IsErrDependenciesLeft(err) {
|
||||
if err = issue_service.ChangeStatus(ctx, pr.Issue, doer, "", true, false); err != nil && !issues_model.IsErrPullWasClosed(err) && !issues_model.IsErrDependenciesLeft(err) {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
@ -667,7 +667,7 @@ func CloseRepoBranchesPulls(ctx context.Context, doer *user_model.User, repo *re
|
|||
if pr.BaseRepoID == repo.ID {
|
||||
continue
|
||||
}
|
||||
if err = issue_service.ChangeStatus(ctx, pr.Issue, doer, "", true); err != nil && !issues_model.IsErrPullWasClosed(err) {
|
||||
if err = issue_service.ChangeStatus(ctx, pr.Issue, doer, "", true, false); err != nil && !issues_model.IsErrPullWasClosed(err) {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ package pull
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
|
@ -43,6 +44,9 @@ func (err ErrDismissRequestOnClosedPR) Unwrap() error {
|
|||
return util.ErrPermissionDenied
|
||||
}
|
||||
|
||||
// ErrSubmitReviewOnClosedPR represents an error when an user tries to submit an approve or reject review associated to a closed or merged PR.
|
||||
var ErrSubmitReviewOnClosedPR = errors.New("can't submit review for a closed or merged PR")
|
||||
|
||||
// checkInvalidation checks if the line of code comment got changed by another commit.
|
||||
// If the line got changed the comment is going to be invalidated.
|
||||
func checkInvalidation(ctx context.Context, c *issues_model.Comment, doer *user_model.User, repo *git.Repository, branch string) error {
|
||||
|
@ -293,6 +297,10 @@ func SubmitReview(ctx context.Context, doer *user_model.User, gitRepo *git.Repos
|
|||
if reviewType != issues_model.ReviewTypeApprove && reviewType != issues_model.ReviewTypeReject {
|
||||
stale = false
|
||||
} else {
|
||||
if issue.IsClosed {
|
||||
return nil, nil, ErrSubmitReviewOnClosedPR
|
||||
}
|
||||
|
||||
headCommitID, err := gitRepo.GetRefCommitID(pr.GetGitRefName())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
|
|
@ -191,8 +191,9 @@ export default {
|
|||
'no-invalid-double-slash-comments': true,
|
||||
'no-invalid-position-at-import-rule': [true, {ignoreAtRules: ['tailwind']}],
|
||||
'no-irregular-whitespace': true,
|
||||
'no-unknown-animations': null,
|
||||
'no-unknown-custom-properties': null,
|
||||
'no-unknown-animations': null, // disabled until stylelint supports multi-file linting
|
||||
'no-unknown-custom-media': null, // disabled until stylelint supports multi-file linting
|
||||
'no-unknown-custom-properties': null, // disabled until stylelint supports multi-file linting
|
||||
'number-max-precision': null,
|
||||
'plugin/declaration-block-no-ignored-properties': true,
|
||||
'property-allowed-list': null,
|
||||
|
|
|
@ -76,7 +76,8 @@
|
|||
{{ctx.Locale.Tr "admin.dashboard.system_status"}}
|
||||
</h4>
|
||||
{{/* TODO: make these stats work in multi-server deployments, likely needs per-server stats in DB */}}
|
||||
<div hx-get="{{$.Link}}/system_status" hx-swap="morph:innerHTML" hx-trigger="every 5s" hx-indicator=".divider" class="ui attached table segment">
|
||||
<div class="no-loading-indicator tw-hidden"></div>
|
||||
<div hx-get="{{$.Link}}/system_status" hx-swap="morph:innerHTML" hx-trigger="every 5s" hx-indicator=".no-loading-indicator" class="ui attached table segment">
|
||||
{{template "admin/system_status" .}}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -235,7 +235,7 @@
|
|||
|
||||
{{if and (not $.Repository.IsArchived) (not .DiffNotAvailable)}}
|
||||
<template id="issue-comment-editor-template">
|
||||
<div class="ui comment form">
|
||||
<div class="ui form comment">
|
||||
{{template "shared/combomarkdowneditor" (dict
|
||||
"MarkdownPreviewUrl" (print $.Repository.Link "/markup")
|
||||
"MarkdownPreviewContext" $.RepoLink
|
||||
|
@ -249,7 +249,7 @@
|
|||
{{end}}
|
||||
<div class="text right edit buttons">
|
||||
<button class="ui cancel button">{{ctx.Locale.Tr "repo.issues.cancel"}}</button>
|
||||
<button class="ui primary save button">{{ctx.Locale.Tr "repo.issues.save"}}</button>
|
||||
<button class="ui primary button">{{ctx.Locale.Tr "repo.issues.save"}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -30,20 +30,24 @@
|
|||
{{end}}
|
||||
<div class="divider"></div>
|
||||
{{$showSelfTooltip := (and $.IsSigned ($.Issue.IsPoster $.SignedUser.ID))}}
|
||||
{{if $showSelfTooltip}}
|
||||
<span class="tw-inline-block" data-tooltip-content="{{ctx.Locale.Tr "repo.diff.review.self_approve"}}">
|
||||
<button type="submit" name="type" value="approve" disabled class="ui submit primary tiny button btn-submit">{{ctx.Locale.Tr "repo.diff.review.approve"}}</button>
|
||||
</span>
|
||||
{{else}}
|
||||
<button type="submit" name="type" value="approve" class="ui submit primary tiny button btn-submit">{{ctx.Locale.Tr "repo.diff.review.approve"}}</button>
|
||||
{{if not $.Issue.IsClosed}}
|
||||
{{if $showSelfTooltip}}
|
||||
<span class="tw-inline-block" data-tooltip-content="{{ctx.Locale.Tr "repo.diff.review.self_approve"}}">
|
||||
<button type="submit" name="type" value="approve" disabled class="ui submit primary tiny button btn-submit">{{ctx.Locale.Tr "repo.diff.review.approve"}}</button>
|
||||
</span>
|
||||
{{else}}
|
||||
<button type="submit" name="type" value="approve" class="ui submit primary tiny button btn-submit">{{ctx.Locale.Tr "repo.diff.review.approve"}}</button>
|
||||
{{end}}
|
||||
{{end}}
|
||||
<button type="submit" name="type" value="comment" class="ui submit tiny basic button btn-submit">{{ctx.Locale.Tr "repo.diff.review.comment"}}</button>
|
||||
{{if $showSelfTooltip}}
|
||||
<span class="tw-inline-block" data-tooltip-content="{{ctx.Locale.Tr "repo.diff.review.self_reject"}}">
|
||||
<button type="submit" name="type" value="reject" disabled class="ui submit red tiny button btn-submit">{{ctx.Locale.Tr "repo.diff.review.reject"}}</button>
|
||||
</span>
|
||||
{{else}}
|
||||
<button type="submit" name="type" value="reject" class="ui submit red tiny button btn-submit">{{ctx.Locale.Tr "repo.diff.review.reject"}}</button>
|
||||
{{if not $.Issue.IsClosed}}
|
||||
{{if $showSelfTooltip}}
|
||||
<span class="tw-inline-block" data-tooltip-content="{{ctx.Locale.Tr "repo.diff.review.self_reject"}}">
|
||||
<button type="submit" name="type" value="reject" disabled class="ui submit red tiny button btn-submit">{{ctx.Locale.Tr "repo.diff.review.reject"}}</button>
|
||||
</span>
|
||||
{{else}}
|
||||
<button type="submit" name="type" value="reject" class="ui submit red tiny button btn-submit">{{ctx.Locale.Tr "repo.diff.review.reject"}}</button>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
{{with .Issue}}
|
||||
{{if eq $.Page.Project.CardType 1}}{{/* Images and Text*/}}
|
||||
{{$attachments := index $.Page.issuesAttachmentMap .ID}}
|
||||
{{if $attachments}}
|
||||
<div class="card-attachment-images">
|
||||
{{range (index $.Page.issuesAttachmentMap .ID)}}
|
||||
{{range $attachments}}
|
||||
<img src="{{.DownloadURL}}" alt="{{.Name}}" />
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
<div class="content tw-p-0 tw-w-full">
|
||||
<div class="tw-flex tw-items-start">
|
||||
<div class="content tw-w-full">
|
||||
<div class="tw-flex tw-items-start tw-gap-[5px]">
|
||||
<div class="issue-card-icon">
|
||||
{{template "shared/issueicon" .}}
|
||||
</div>
|
||||
|
@ -18,7 +21,7 @@
|
|||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="meta tw-my-1">
|
||||
<div class="meta">
|
||||
<span class="text light grey muted-links">
|
||||
{{if not $.Page.Repository}}{{.Repo.FullName}}{{end}}#{{.Index}}
|
||||
{{$timeStr := TimeSinceUnix .GetLastEventTimestamp ctx.Locale}}
|
||||
|
@ -59,13 +62,15 @@
|
|||
</div>
|
||||
|
||||
{{if or .Labels .Assignees}}
|
||||
<div class="extra content labels-list tw-p-0 tw-pt-1">
|
||||
{{range .Labels}}
|
||||
<a target="_blank" href="{{$.Issue.Repo.Link}}/issues?labels={{.ID}}">{{RenderLabel ctx ctx.Locale .}}</a>
|
||||
{{end}}
|
||||
<div class="right floated">
|
||||
<div class="tw-flex tw-justify-between">
|
||||
<div class="labels-list tw-flex-1">
|
||||
{{range .Labels}}
|
||||
<a target="_blank" href="{{$.Issue.Repo.Link}}/issues?labels={{.ID}}">{{RenderLabel ctx ctx.Locale .}}</a>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="tw-flex tw-flex-wrap tw-content-start tw-gap-1">
|
||||
{{range .Assignees}}
|
||||
<a target="_blank" href="{{.HomeLink}}" data-tooltip-content="{{ctx.Locale.Tr "repo.projects.column.assigned_to"}} {{.Name}}">{{ctx.AvatarUtils.Avatar . 28 "mini tw-mr-2"}}</a>
|
||||
<a target="_blank" href="{{.HomeLink}}" data-tooltip-content="{{ctx.Locale.Tr "repo.projects.column.assigned_to"}} {{.Name}}">{{ctx.AvatarUtils.Avatar . 28}}</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -146,7 +146,7 @@
|
|||
</div>
|
||||
|
||||
<template id="issue-comment-editor-template">
|
||||
<div class="ui comment form">
|
||||
<div class="ui form comment">
|
||||
<div class="field">
|
||||
{{template "shared/combomarkdowneditor" (dict
|
||||
"MarkdownPreviewUrl" (print .Repository.Link "/markup")
|
||||
|
@ -164,8 +164,8 @@
|
|||
|
||||
<div class="field">
|
||||
<div class="text right edit">
|
||||
<button class="ui basic cancel button">{{ctx.Locale.Tr "repo.issues.cancel"}}</button>
|
||||
<button class="ui primary save button">{{ctx.Locale.Tr "repo.issues.save"}}</button>
|
||||
<button class="ui cancel button">{{ctx.Locale.Tr "repo.issues.cancel"}}</button>
|
||||
<button class="ui primary button">{{ctx.Locale.Tr "repo.issues.save"}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<span class="ui small label">{{if .NumFiles}}{{.NumFiles}}{{else}}-{{end}}</span>
|
||||
</a>
|
||||
{{if or .Diff.TotalAddition .Diff.TotalDeletion}}
|
||||
<span class="item tw-ml-auto tw-pr-0 tw-font-bold tw-flex tw-items-center tw-gap-2">
|
||||
<span class="tw-ml-auto tw-pl-3 tw-whitespace-nowrap tw-pr-0 tw-font-bold tw-flex tw-items-center tw-gap-2">
|
||||
<span><span class="text green">{{if .Diff.TotalAddition}}+{{.Diff.TotalAddition}}{{end}}</span> <span class="text red">{{if .Diff.TotalDeletion}}-{{.Diff.TotalDeletion}}{{end}}</span></span>
|
||||
<span class="diff-stats-bar">
|
||||
<div class="diff-stats-add-bar" style="width: {{Eval 100 "*" .Diff.TotalAddition "/" "(" .Diff.TotalAddition "+" .Diff.TotalDeletion "+" 0.0 ")"}}%"></div>
|
||||
|
|
|
@ -5,12 +5,15 @@ package integration
|
|||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
|
@ -176,3 +179,82 @@ func TestPullView_CodeOwner(t *testing.T) {
|
|||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestPullView_GivenApproveOrRejectReviewOnClosedPR(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
user1Session := loginUser(t, "user1")
|
||||
user2Session := loginUser(t, "user2")
|
||||
|
||||
// Have user1 create a fork of repo1.
|
||||
testRepoFork(t, user1Session, "user2", "repo1", "user1", "repo1")
|
||||
|
||||
t.Run("Submit approve/reject review on merged PR", func(t *testing.T) {
|
||||
// Create a merged PR (made by user1) in the upstream repo1.
|
||||
testEditFile(t, user1Session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n")
|
||||
resp := testPullCreate(t, user1Session, "user1", "repo1", false, "master", "master", "This is a pull title")
|
||||
elem := strings.Split(test.RedirectURL(resp), "/")
|
||||
assert.EqualValues(t, "pulls", elem[3])
|
||||
testPullMerge(t, user1Session, elem[1], elem[2], elem[4], repo_model.MergeStyleMerge, false)
|
||||
|
||||
// Grab the CSRF token.
|
||||
req := NewRequest(t, "GET", path.Join(elem[1], elem[2], "pulls", elem[4]))
|
||||
resp = user2Session.MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
|
||||
// Submit an approve review on the PR.
|
||||
testSubmitReview(t, user2Session, htmlDoc.GetCSRF(), "user2", "repo1", elem[4], "approve", http.StatusUnprocessableEntity)
|
||||
|
||||
// Submit a reject review on the PR.
|
||||
testSubmitReview(t, user2Session, htmlDoc.GetCSRF(), "user2", "repo1", elem[4], "reject", http.StatusUnprocessableEntity)
|
||||
})
|
||||
|
||||
t.Run("Submit approve/reject review on closed PR", func(t *testing.T) {
|
||||
// Created a closed PR (made by user1) in the upstream repo1.
|
||||
testEditFileToNewBranch(t, user1Session, "user1", "repo1", "master", "a-test-branch", "README.md", "Hello, World (Editied...again)\n")
|
||||
resp := testPullCreate(t, user1Session, "user1", "repo1", false, "master", "a-test-branch", "This is a pull title")
|
||||
elem := strings.Split(test.RedirectURL(resp), "/")
|
||||
assert.EqualValues(t, "pulls", elem[3])
|
||||
testIssueClose(t, user1Session, elem[1], elem[2], elem[4])
|
||||
|
||||
// Grab the CSRF token.
|
||||
req := NewRequest(t, "GET", path.Join(elem[1], elem[2], "pulls", elem[4]))
|
||||
resp = user2Session.MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
|
||||
// Submit an approve review on the PR.
|
||||
testSubmitReview(t, user2Session, htmlDoc.GetCSRF(), "user2", "repo1", elem[4], "approve", http.StatusUnprocessableEntity)
|
||||
|
||||
// Submit a reject review on the PR.
|
||||
testSubmitReview(t, user2Session, htmlDoc.GetCSRF(), "user2", "repo1", elem[4], "reject", http.StatusUnprocessableEntity)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func testSubmitReview(t *testing.T, session *TestSession, csrf, owner, repo, pullNumber, reviewType string, expectedSubmitStatus int) *httptest.ResponseRecorder {
|
||||
options := map[string]string{
|
||||
"_csrf": csrf,
|
||||
"commit_id": "",
|
||||
"content": "test",
|
||||
"type": reviewType,
|
||||
}
|
||||
|
||||
submitURL := path.Join(owner, repo, "pulls", pullNumber, "files", "reviews", "submit")
|
||||
req := NewRequestWithValues(t, "POST", submitURL, options)
|
||||
return session.MakeRequest(t, req, expectedSubmitStatus)
|
||||
}
|
||||
|
||||
func testIssueClose(t *testing.T, session *TestSession, owner, repo, issueNumber string) *httptest.ResponseRecorder {
|
||||
req := NewRequest(t, "GET", path.Join(owner, repo, "pulls", issueNumber))
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
closeURL := path.Join(owner, repo, "issues", issueNumber, "comments")
|
||||
|
||||
options := map[string]string{
|
||||
"_csrf": htmlDoc.GetCSRF(),
|
||||
"status": "close",
|
||||
}
|
||||
|
||||
req = NewRequestWithValues(t, "POST", closeURL, options)
|
||||
return session.MakeRequest(t, req, http.StatusOK)
|
||||
}
|
||||
|
|
|
@ -46,7 +46,6 @@ func InitTest(requireGitea bool) {
|
|||
// TODO: Speedup tests that rely on the event source ticker, confirm whether there is any bug or failure.
|
||||
// setting.UI.Notification.EventSourceUpdateTime = time.Second
|
||||
|
||||
setting.IsInTesting = true
|
||||
setting.AppWorkPath = giteaRoot
|
||||
setting.CustomPath = filepath.Join(setting.AppWorkPath, "custom")
|
||||
if requireGitea {
|
||||
|
|
|
@ -2520,7 +2520,7 @@ tbody.commit-list {
|
|||
display: inline-block;
|
||||
background-color: var(--color-red);
|
||||
height: 12px;
|
||||
width: 40px;
|
||||
width: 44px;
|
||||
}
|
||||
|
||||
.diff-stats-bar .diff-stats-add-bar {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
.issue-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
align-items: start;
|
||||
border-radius: var(--border-radius);
|
||||
padding: 8px 10px;
|
||||
|
@ -17,7 +18,6 @@
|
|||
.issue-card-title {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.issue-card.sortable-chosen .issue-card-title {
|
||||
|
|
|
@ -6,18 +6,10 @@
|
|||
// This file must be imported before any lazy-loading is being attempted.
|
||||
__webpack_public_path__ = `${window.config?.assetUrlPrefix ?? '/assets'}/`;
|
||||
|
||||
const filteredErrors = new Set([
|
||||
'getModifierState is not a function', // https://github.com/microsoft/monaco-editor/issues/4325
|
||||
]);
|
||||
|
||||
export function showGlobalErrorMessage(msg) {
|
||||
const pageContent = document.querySelector('.page-content');
|
||||
if (!pageContent) return;
|
||||
|
||||
for (const filteredError of filteredErrors) {
|
||||
if (msg.includes(filteredError)) return;
|
||||
}
|
||||
|
||||
// compact the message to a data attribute to avoid too many duplicated messages
|
||||
const msgCompact = msg.replace(/\W/g, '').trim();
|
||||
let msgDiv = pageContent.querySelector(`.js-global-error[data-global-error-msg-compact="${msgCompact}"]`);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
export function handleGlobalEnterQuickSubmit(target) {
|
||||
const form = target.closest('form');
|
||||
let form = target.closest('form');
|
||||
if (form) {
|
||||
if (!form.checkValidity()) {
|
||||
form.reportValidity();
|
||||
|
@ -9,5 +9,10 @@ export function handleGlobalEnterQuickSubmit(target) {
|
|||
// here use the event to trigger the submit event (instead of calling `submit()` method directly)
|
||||
// otherwise the `areYouSure` handler won't be executed, then there will be an annoying "confirm to leave" dialog
|
||||
form.dispatchEvent(new SubmitEvent('submit', {bubbles: true, cancelable: true}));
|
||||
return;
|
||||
}
|
||||
form = target.closest('.ui.form');
|
||||
if (form) {
|
||||
form.querySelector('.ui.primary.button')?.click();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -162,8 +162,8 @@ async function onEditContent(event) {
|
|||
editContentZone.innerHTML = document.getElementById('issue-comment-editor-template').innerHTML;
|
||||
comboMarkdownEditor = await initComboMarkdownEditor(editContentZone.querySelector('.combo-markdown-editor'));
|
||||
comboMarkdownEditor.attachedDropzoneInst = await setupDropzone(editContentZone.querySelector('.dropzone'));
|
||||
editContentZone.querySelector('.cancel.button').addEventListener('click', cancelAndReset);
|
||||
editContentZone.querySelector('.save.button').addEventListener('click', saveAndRefresh);
|
||||
editContentZone.querySelector('.ui.cancel.button').addEventListener('click', cancelAndReset);
|
||||
editContentZone.querySelector('.ui.primary.button').addEventListener('click', saveAndRefresh);
|
||||
}
|
||||
|
||||
// Show write/preview tab and copy raw content as needed
|
||||
|
|
Loading…
Reference in New Issue