diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 83410dc07c0b7..f2af1709e4d55 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -336,7 +336,7 @@ module.exports = { '@typescript-eslint/no-unsafe-unary-minus': [2], '@typescript-eslint/no-unused-expressions': [0], '@typescript-eslint/no-unused-vars': [2, {vars: 'all', args: 'all', caughtErrors: 'all', ignoreRestSiblings: false, argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_', destructuredArrayIgnorePattern: '^_'}], - '@typescript-eslint/no-use-before-define': [0], + '@typescript-eslint/no-use-before-define': [2, {functions: false, classes: true, variables: true, allowNamedExports: true, typedefs: false, enums: false, ignoreTypeReferences: true}], '@typescript-eslint/no-useless-constructor': [0], '@typescript-eslint/no-useless-empty-export': [0], '@typescript-eslint/no-wrapper-object-types': [2], @@ -693,7 +693,7 @@ module.exports = { 'no-unused-labels': [2], 'no-unused-private-class-members': [2], 'no-unused-vars': [0], // handled by @typescript-eslint/no-unused-vars - 'no-use-before-define': [2, {functions: false, classes: true, variables: true, allowNamedExports: true}], + 'no-use-before-define': [0], // handled by @typescript-eslint/no-use-before-define 'no-use-extend-native/no-use-extend-native': [2], 'no-useless-backreference': [2], 'no-useless-call': [2], diff --git a/.github/labeler.yml b/.github/labeler.yml index 46efbcb1949b6..0af43cd029936 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -41,7 +41,7 @@ modifies/internal: - ".dockerignore" - "docker/**" - ".editorconfig" - - ".eslintrc.yaml" + - ".eslintrc.cjs" - ".golangci.yml" - ".gitpod.yml" - ".markdownlint.yaml" @@ -49,7 +49,7 @@ modifies/internal: - "stylelint.config.js" - ".yamllint.yaml" - ".github/**" - - ".gitea/" + - ".gitea/**" - ".devcontainer/**" - "build.go" - "build/**" @@ -73,9 +73,9 @@ modifies/go: modifies/frontend: - changed-files: - any-glob-to-any-file: - - "**/*.js" - - "**/*.ts" - - "**/*.vue" + - "*.js" + - "*.ts" + - "web_src/**" docs-update-needed: - changed-files: diff --git a/.github/workflows/files-changed.yml b/.github/workflows/files-changed.yml index b3ee93e6f8331..be27537924336 100644 --- a/.github/workflows/files-changed.yml +++ b/.github/workflows/files-changed.yml @@ -51,14 +51,16 @@ jobs: - "options/locale/locale_en-US.ini" frontend: - - "**/*.js" + - "*.js" + - "*.ts" - "web_src/**" + - "tools/*.js" + - "tools/*.ts" - "assets/emoji.json" - "package.json" - "package-lock.json" - "Makefile" - - ".eslintrc.yaml" - - "stylelint.config.js" + - ".eslintrc.cjs" - ".npmrc" docs: diff --git a/.github/workflows/release-tag-version.yml b/.github/workflows/release-tag-version.yml index f67b76f40873b..08bb9baecfebb 100644 --- a/.github/workflows/release-tag-version.yml +++ b/.github/workflows/release-tag-version.yml @@ -88,9 +88,9 @@ jobs: # 1.2 # 1.2.3 tags: | + type=semver,pattern={{version}} type=semver,pattern={{major}} type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{version}} - name: Login to Docker Hub uses: docker/login-action@v3 with: @@ -126,9 +126,9 @@ jobs: # 1.2 # 1.2.3 tags: | + type=semver,pattern={{version}} type=semver,pattern={{major}} type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{version}} - name: Login to Docker Hub uses: docker/login-action@v3 with: diff --git a/cmd/web_acme.go b/cmd/web_acme.go index 5daf0f55f243f..bca4ae021217b 100644 --- a/cmd/web_acme.go +++ b/cmd/web_acme.go @@ -54,10 +54,6 @@ func runACME(listenAddr string, m http.Handler) error { altTLSALPNPort = p } - // FIXME: this path is not right, it uses "AppWorkPath" incorrectly, and writes the data into "AppWorkPath/https" - // Ideally it should migrate to AppDataPath write to "AppDataPath/https" - certmagic.Default.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory} - magic := certmagic.NewDefault() // Try to use private CA root if provided, otherwise defaults to system's trust var certPool *x509.CertPool if setting.AcmeCARoot != "" { @@ -67,7 +63,13 @@ func runACME(listenAddr string, m http.Handler) error { log.Warn("Failed to parse CA Root certificate, using default CA trust: %v", err) } } - myACME := certmagic.NewACMEIssuer(magic, certmagic.ACMEIssuer{ + // FIXME: this path is not right, it uses "AppWorkPath" incorrectly, and writes the data into "AppWorkPath/https" + // Ideally it should migrate to AppDataPath write to "AppDataPath/https" + // And one more thing, no idea why we should set the global default variables here + // But it seems that the current ACME code needs these global variables to make renew work. + // Otherwise, "renew" will use incorrect storage path + certmagic.Default.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory} + certmagic.DefaultACME = certmagic.ACMEIssuer{ CA: setting.AcmeURL, TrustedRoots: certPool, Email: setting.AcmeEmail, @@ -77,8 +79,10 @@ func runACME(listenAddr string, m http.Handler) error { ListenHost: setting.HTTPAddr, AltTLSALPNPort: altTLSALPNPort, AltHTTPPort: altHTTPPort, - }) + } + magic := certmagic.NewDefault() + myACME := certmagic.NewACMEIssuer(magic, certmagic.DefaultACME) magic.Issuers = []certmagic.Issuer{myACME} // this obtains certificates or renews them if necessary diff --git a/go.mod b/go.mod index ca5d47aff46ae..f2213b584e806 100644 --- a/go.mod +++ b/go.mod @@ -117,10 +117,10 @@ require ( github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc github.com/yuin/goldmark-meta v1.1.0 gitlab.com/gitlab-org/api/client-go v0.123.0 - golang.org/x/crypto v0.33.0 + golang.org/x/crypto v0.35.0 golang.org/x/image v0.24.0 golang.org/x/net v0.35.0 - golang.org/x/oauth2 v0.26.0 + golang.org/x/oauth2 v0.27.0 golang.org/x/sync v0.11.0 golang.org/x/sys v0.30.0 golang.org/x/text v0.22.0 diff --git a/go.sum b/go.sum index f2bae164055be..4047c846e4aae 100644 --- a/go.sum +++ b/go.sum @@ -831,8 +831,9 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= +golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrCPyJZ+eyRsABUPs1mz4= golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk= golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ= @@ -868,8 +869,8 @@ golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= -golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE= -golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= +golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/models/activities/action.go b/models/activities/action.go index adc442b88bc10..52dffe07fdc93 100644 --- a/models/activities/action.go +++ b/models/activities/action.go @@ -454,6 +454,24 @@ func ActivityReadable(user, doer *user_model.User) bool { doer != nil && (doer.IsAdmin || user.ID == doer.ID) } +func FeedDateCond(opts GetFeedsOptions) builder.Cond { + cond := builder.NewCond() + if opts.Date == "" { + return cond + } + + dateLow, err := time.ParseInLocation("2006-01-02", opts.Date, setting.DefaultUILocation) + if err != nil { + log.Warn("Unable to parse %s, filter not applied: %v", opts.Date, err) + } else { + dateHigh := dateLow.Add(86399000000000) // 23h59m59s + + cond = cond.And(builder.Gte{"`action`.created_unix": dateLow.Unix()}) + cond = cond.And(builder.Lte{"`action`.created_unix": dateHigh.Unix()}) + } + return cond +} + func ActivityQueryCondition(ctx context.Context, opts GetFeedsOptions) (builder.Cond, error) { cond := builder.NewCond() @@ -534,17 +552,7 @@ func ActivityQueryCondition(ctx context.Context, opts GetFeedsOptions) (builder. cond = cond.And(builder.Eq{"is_deleted": false}) } - if opts.Date != "" { - dateLow, err := time.ParseInLocation("2006-01-02", opts.Date, setting.DefaultUILocation) - if err != nil { - log.Warn("Unable to parse %s, filter not applied: %v", opts.Date, err) - } else { - dateHigh := dateLow.Add(86399000000000) // 23h59m59s - - cond = cond.And(builder.Gte{"`action`.created_unix": dateLow.Unix()}) - cond = cond.And(builder.Lte{"`action`.created_unix": dateHigh.Unix()}) - } - } + cond = cond.And(FeedDateCond(opts)) return cond, nil } diff --git a/models/activities/action_list.go b/models/activities/action_list.go index 5f9acb8f2aa46..f7ea48f03e7ab 100644 --- a/models/activities/action_list.go +++ b/models/activities/action_list.go @@ -208,9 +208,31 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err return nil, 0, fmt.Errorf("need at least one of these filters: RequestedUser, RequestedTeam, RequestedRepo") } - cond, err := ActivityQueryCondition(ctx, opts) - if err != nil { - return nil, 0, err + var err error + var cond builder.Cond + // if the actor is the requested user or is an administrator, we can skip the ActivityQueryCondition + if opts.Actor != nil && opts.RequestedUser != nil && (opts.Actor.IsAdmin || opts.Actor.ID == opts.RequestedUser.ID) { + cond = builder.Eq{ + "user_id": opts.RequestedUser.ID, + }.And( + FeedDateCond(opts), + ) + + if !opts.IncludeDeleted { + cond = cond.And(builder.Eq{"is_deleted": false}) + } + + if !opts.IncludePrivate { + cond = cond.And(builder.Eq{"is_private": false}) + } + if opts.OnlyPerformedBy { + cond = cond.And(builder.Eq{"act_user_id": opts.RequestedUser.ID}) + } + } else { + cond, err = ActivityQueryCondition(ctx, opts) + if err != nil { + return nil, 0, err + } } actions := make([]*Action, 0, opts.PageSize) diff --git a/models/organization/org_list.go b/models/organization/org_list.go index 4c4168af1f826..78ac0e704a1fb 100644 --- a/models/organization/org_list.go +++ b/models/organization/org_list.go @@ -124,6 +124,7 @@ func GetUserOrgsList(ctx context.Context, user *user_model.User) ([]*MinimalOrg, if err := db.GetEngine(ctx).Select(columnsStr). Table("user"). Where(builder.In("`user`.`id`", queryUserOrgIDs(user.ID, true))). + OrderBy("`user`.lower_name ASC"). Find(&orgs); err != nil { return nil, err } diff --git a/models/repo/user_repo.go b/models/repo/user_repo.go index a9b1360df1410..232087d86594e 100644 --- a/models/repo/user_repo.go +++ b/models/repo/user_repo.go @@ -5,6 +5,7 @@ package repo import ( "context" + "strings" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/perm" @@ -149,9 +150,9 @@ func GetRepoAssignees(ctx context.Context, repo *Repository) (_ []*user_model.Us // If isShowFullName is set to true, also include full name prefix search func GetIssuePostersWithSearch(ctx context.Context, repo *Repository, isPull bool, search string, isShowFullName bool) ([]*user_model.User, error) { users := make([]*user_model.User, 0, 30) - var prefixCond builder.Cond = builder.Like{"name", search + "%"} + var prefixCond builder.Cond = builder.Like{"lower_name", strings.ToLower(search) + "%"} if isShowFullName { - prefixCond = prefixCond.Or(builder.Like{"full_name", "%" + search + "%"}) + prefixCond = prefixCond.Or(db.BuildCaseInsensitiveLike("full_name", "%"+search+"%")) } cond := builder.In("`user`.id", diff --git a/models/repo/user_repo_test.go b/models/repo/user_repo_test.go index 44ebe5f214c6e..50c970344cb3f 100644 --- a/models/repo/user_repo_test.go +++ b/models/repo/user_repo_test.go @@ -12,6 +12,7 @@ import ( user_model "code.gitea.io/gitea/models/user" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestRepoAssignees(t *testing.T) { @@ -38,3 +39,19 @@ func TestRepoAssignees(t *testing.T) { assert.NotContains(t, []int64{users[0].ID, users[1].ID, users[2].ID}, 15) } } + +func TestGetIssuePostersWithSearch(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) + + users, err := repo_model.GetIssuePostersWithSearch(db.DefaultContext, repo2, false, "USER", false /* full name */) + require.NoError(t, err) + require.Len(t, users, 1) + assert.Equal(t, "user2", users[0].Name) + + users, err = repo_model.GetIssuePostersWithSearch(db.DefaultContext, repo2, false, "TW%O", true /* full name */) + require.NoError(t, err) + require.Len(t, users, 1) + assert.Equal(t, "user2", users[0].Name) +} diff --git a/modules/setting/server.go b/modules/setting/server.go index d7a71578d4ab6..e15b790906738 100644 --- a/modules/setting/server.go +++ b/modules/setting/server.go @@ -169,20 +169,24 @@ func loadServerFrom(rootCfg ConfigProvider) { HTTPAddr = sec.Key("HTTP_ADDR").MustString("0.0.0.0") HTTPPort = sec.Key("HTTP_PORT").MustString("3000") + // DEPRECATED should not be removed because users maybe upgrade from lower version to the latest version + // if these are removed, the warning will not be shown + if sec.HasKey("ENABLE_ACME") { + EnableAcme = sec.Key("ENABLE_ACME").MustBool(false) + } else { + deprecatedSetting(rootCfg, "server", "ENABLE_LETSENCRYPT", "server", "ENABLE_ACME", "v1.19.0") + EnableAcme = sec.Key("ENABLE_LETSENCRYPT").MustBool(false) + } + Protocol = HTTP protocolCfg := sec.Key("PROTOCOL").String() + if protocolCfg != "https" && EnableAcme { + log.Fatal("ACME could only be used with HTTPS protocol") + } + switch protocolCfg { case "https": Protocol = HTTPS - - // DEPRECATED should not be removed because users maybe upgrade from lower version to the latest version - // if these are removed, the warning will not be shown - if sec.HasKey("ENABLE_ACME") { - EnableAcme = sec.Key("ENABLE_ACME").MustBool(false) - } else { - deprecatedSetting(rootCfg, "server", "ENABLE_LETSENCRYPT", "server", "ENABLE_ACME", "v1.19.0") - EnableAcme = sec.Key("ENABLE_LETSENCRYPT").MustBool(false) - } if EnableAcme { AcmeURL = sec.Key("ACME_URL").MustString("") AcmeCARoot = sec.Key("ACME_CA_ROOT").MustString("") @@ -210,6 +214,9 @@ func loadServerFrom(rootCfg ConfigProvider) { deprecatedSetting(rootCfg, "server", "LETSENCRYPT_EMAIL", "server", "ACME_EMAIL", "v1.19.0") AcmeEmail = sec.Key("LETSENCRYPT_EMAIL").MustString("") } + if AcmeEmail == "" { + log.Fatal("ACME Email is not set (ACME_EMAIL).") + } } else { CertFile = sec.Key("CERT_FILE").String() KeyFile = sec.Key("KEY_FILE").String() diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index c2c5b07b653af..4c25ba320555d 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1465,6 +1465,8 @@ issues.filter_milestones = Filter Milestone issues.filter_projects = Filter Project issues.filter_labels = Filter Label issues.filter_reviewers = Filter Reviewer +issues.filter_no_results = No results +issues.filter_no_results_placeholder = Try adjusting your search filters. issues.new = New Issue issues.new.title_empty = Title cannot be empty issues.new.labels = Labels diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index 9d652fabadf92..87e878ebef168 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -1701,7 +1701,9 @@ issues.time_estimate_invalid=Le format du temps estimé est invalide issues.start_tracking_history=`a commencé son travail %s.` issues.tracker_auto_close=Le minuteur sera automatiquement arrêté quand le ticket sera fermé. issues.tracking_already_started=`Vous avez déjà un minuteur en cours sur un autre ticket !` +issues.stop_tracking=Arrêter le minuteur issues.stop_tracking_history=a travaillé sur %[1]s %[2]s +issues.cancel_tracking=Abandonner issues.cancel_tracking_history=`a abandonné son minuteur %s.` issues.del_time=Supprimer ce minuteur du journal issues.add_time_history=a pointé du temps de travail sur %[1]s, %[2]s diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index 3582755d5e7d3..9f5be0a4ef24a 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -1464,6 +1464,8 @@ issues.filter_milestones=Filtrar etapa issues.filter_projects=Filtrar planeamento issues.filter_labels=Filtrar rótulo issues.filter_reviewers=Filtrar revisor +issues.filter_no_results=Sem resultados +issues.filter_no_results_placeholder=Tente ajustar os seus filtros de pesquisa. issues.new=Questão nova issues.new.title_empty=O título não pode estar vazio issues.new.labels=Rótulos diff --git a/routers/api/v1/repo/collaborators.go b/routers/api/v1/repo/collaborators.go index c397d7972b56d..a54225f0fd76d 100644 --- a/routers/api/v1/repo/collaborators.go +++ b/routers/api/v1/repo/collaborators.go @@ -7,6 +7,7 @@ package repo import ( "errors" "net/http" + "strings" "code.gitea.io/gitea/models/perm" access_model "code.gitea.io/gitea/models/perm/access" @@ -274,12 +275,13 @@ func GetRepoPermissions(ctx *context.APIContext) { // "403": // "$ref": "#/responses/forbidden" - if !ctx.Doer.IsAdmin && ctx.Doer.LoginName != ctx.PathParam("collaborator") && !ctx.IsUserRepoAdmin() { + collaboratorUsername := ctx.PathParam("collaborator") + if !ctx.Doer.IsAdmin && ctx.Doer.LowerName != strings.ToLower(collaboratorUsername) && !ctx.IsUserRepoAdmin() { ctx.APIError(http.StatusForbidden, "Only admins can query all permissions, repo admins can query all repo permissions, collaborators can query only their own") return } - collaborator, err := user_model.GetUserByName(ctx, ctx.PathParam("collaborator")) + collaborator, err := user_model.GetUserByName(ctx, collaboratorUsername) if err != nil { if user_model.IsErrUserNotExist(err) { ctx.APIError(http.StatusNotFound, err) diff --git a/routers/web/devtest/mock_actions.go b/routers/web/devtest/mock_actions.go index e6539bb31fac6..3ce75dfad2d76 100644 --- a/routers/web/devtest/mock_actions.go +++ b/routers/web/devtest/mock_actions.go @@ -52,13 +52,22 @@ func generateMockStepsLog(logCur actions.LogCursor) (stepsLog []*actions.ViewSte return stepsLog } +func MockActionsView(ctx *context.Context) { + ctx.Data["RunID"] = ctx.PathParam("run") + ctx.Data["JobID"] = ctx.PathParam("job") + ctx.HTML(http.StatusOK, "devtest/repo-action-view") +} + func MockActionsRunsJobs(ctx *context.Context) { - req := web.GetForm(ctx).(*actions.ViewRequest) + runID := ctx.PathParamInt64("run") + req := web.GetForm(ctx).(*actions.ViewRequest) resp := &actions.ViewResponse{} resp.State.Run.TitleHTML = `mock run title link` resp.State.Run.Status = actions_model.StatusRunning.String() - resp.State.Run.CanCancel = true + resp.State.Run.CanCancel = runID == 10 + resp.State.Run.CanApprove = runID == 20 + resp.State.Run.CanRerun = runID == 30 resp.State.Run.CanDeleteArtifact = true resp.State.Run.WorkflowID = "workflow-id" resp.State.Run.WorkflowLink = "./workflow-link" @@ -85,6 +94,29 @@ func MockActionsRunsJobs(ctx *context.Context) { Size: 1024 * 1024, Status: "completed", }) + + resp.State.Run.Jobs = append(resp.State.Run.Jobs, &actions.ViewJob{ + ID: runID * 10, + Name: "job 100", + Status: actions_model.StatusRunning.String(), + CanRerun: true, + Duration: "1h", + }) + resp.State.Run.Jobs = append(resp.State.Run.Jobs, &actions.ViewJob{ + ID: runID*10 + 1, + Name: "job 101", + Status: actions_model.StatusWaiting.String(), + CanRerun: false, + Duration: "2h", + }) + resp.State.Run.Jobs = append(resp.State.Run.Jobs, &actions.ViewJob{ + ID: runID*10 + 2, + Name: "job 102", + Status: actions_model.StatusFailure.String(), + CanRerun: false, + Duration: "3h", + }) + resp.State.CurrentJob.Steps = append(resp.State.CurrentJob.Steps, &actions.ViewJobStep{ Summary: "step 0 (mock slow)", Duration: time.Hour.String(), diff --git a/routers/web/repo/githttp.go b/routers/web/repo/githttp.go index e27040edc6512..5b7b0188dc13b 100644 --- a/routers/web/repo/githttp.go +++ b/routers/web/repo/githttp.go @@ -78,7 +78,7 @@ func httpBase(ctx *context.Context) *serviceHandler { strings.HasSuffix(ctx.Req.URL.Path, "git-upload-archive") { isPull = true } else { - isPull = ctx.Req.Method == "GET" + isPull = ctx.Req.Method == "HEAD" || ctx.Req.Method == "GET" } var accessMode perm.AccessMode diff --git a/routers/web/web.go b/routers/web/web.go index a5175e8830703..01dc8cf697c5b 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1634,6 +1634,7 @@ func registerRoutes(m *web.Router) { m.Any("", devtest.List) m.Any("/fetch-action-test", devtest.FetchActionTest) m.Any("/{sub}", devtest.Tmpl) + m.Get("/repo-action-view/{run}/{job}", devtest.MockActionsView) m.Post("/actions-mock/runs/{run}/jobs/{job}", web.Bind(actions.ViewRequest{}), devtest.MockActionsRunsJobs) }) } diff --git a/services/context/api.go b/services/context/api.go index 230c3456d1996..c163de036c719 100644 --- a/services/context/api.go +++ b/services/context/api.go @@ -291,6 +291,11 @@ func RepoRefForAPI(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { ctx := GetAPIContext(req) + if ctx.Repo.Repository.IsEmpty { + ctx.APIErrorNotFound("repository is empty") + return + } + if ctx.Repo.GitRepo == nil { ctx.APIErrorInternal(fmt.Errorf("no open git repo")) return diff --git a/templates/devtest/repo-action-view.tmpl b/templates/devtest/repo-action-view.tmpl index 9c6bdf13daafa..677eccc062d13 100644 --- a/templates/devtest/repo-action-view.tmpl +++ b/templates/devtest/repo-action-view.tmpl @@ -1,8 +1,13 @@ {{template "base/head" .}}
+
+ Run:CanCancel + Run:CanApprove + Run:CanRerun +
{{template "repo/actions/view_component" (dict - "RunIndex" 1 - "JobIndex" 2 + "RunIndex" (or .RunID 10) + "JobIndex" (or .JobID 100) "ActionsURL" (print AppSubUrl "/devtest/actions-mock") )}}
diff --git a/templates/repo/issue/openclose.tmpl b/templates/repo/issue/openclose.tmpl index b9dd04a7db226..00a31b5fad91a 100644 --- a/templates/repo/issue/openclose.tmpl +++ b/templates/repo/issue/openclose.tmpl @@ -17,7 +17,7 @@ {{ctx.Locale.PrettyNumber .OpenCount}} {{ctx.Locale.Tr "repo.issues.open_title"}} - {{svg "octicon-check"}} + {{svg "octicon-issue-closed"}} {{ctx.Locale.PrettyNumber .ClosedCount}} {{ctx.Locale.Tr "repo.issues.closed_title"}} diff --git a/templates/shared/issuelist.tmpl b/templates/shared/issuelist.tmpl index fe7f2fd8bfd61..30670c3b0fc74 100644 --- a/templates/shared/issuelist.tmpl +++ b/templates/shared/issuelist.tmpl @@ -153,6 +153,11 @@ {{end}} + {{else}} +
+

{{ctx.Locale.Tr "repo.issues.filter_no_results"}}

+

{{ctx.Locale.Tr "repo.issues.filter_no_results_placeholder"}}

+
{{end}} {{if .IssueIndexerUnavailable}}
diff --git a/tests/integration/api_repo_collaborator_test.go b/tests/integration/api_repo_collaborator_test.go index 463db1dfb1305..11e2924e8423e 100644 --- a/tests/integration/api_repo_collaborator_test.go +++ b/tests/integration/api_repo_collaborator_test.go @@ -5,7 +5,6 @@ package integration import ( "net/http" - "net/url" "testing" auth_model "code.gitea.io/gitea/models/auth" @@ -14,132 +13,145 @@ import ( "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" ) func TestAPIRepoCollaboratorPermission(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) - repo2Owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo2.OwnerID}) + defer tests.PrepareTestEnv(t)() + repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) + repo2Owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo2.OwnerID}) - user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) - user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) - user10 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 10}) - user11 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 11}) - user34 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 34}) + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) + user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) + user10 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 10}) + user11 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 11}) + user34 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 34}) - testCtx := NewAPITestContext(t, repo2Owner.Name, repo2.Name, auth_model.AccessTokenScopeWriteRepository) + testCtx := NewAPITestContext(t, repo2Owner.Name, repo2.Name, auth_model.AccessTokenScopeWriteRepository) - t.Run("RepoOwnerShouldBeOwner", func(t *testing.T) { - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, repo2Owner.Name). - AddTokenAuth(testCtx.Token) - resp := MakeRequest(t, req, http.StatusOK) + t.Run("RepoOwnerShouldBeOwner", func(t *testing.T) { + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, repo2Owner.Name). + AddTokenAuth(testCtx.Token) + resp := MakeRequest(t, req, http.StatusOK) - var repoPermission api.RepoCollaboratorPermission - DecodeJSON(t, resp, &repoPermission) + var repoPermission api.RepoCollaboratorPermission + DecodeJSON(t, resp, &repoPermission) - assert.Equal(t, "owner", repoPermission.Permission) - }) + assert.Equal(t, "owner", repoPermission.Permission) + }) - t.Run("CollaboratorWithReadAccess", func(t *testing.T) { - t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeRead)) + t.Run("CollaboratorWithReadAccess", func(t *testing.T) { + t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeRead)) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name). - AddTokenAuth(testCtx.Token) - resp := MakeRequest(t, req, http.StatusOK) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name). + AddTokenAuth(testCtx.Token) + resp := MakeRequest(t, req, http.StatusOK) - var repoPermission api.RepoCollaboratorPermission - DecodeJSON(t, resp, &repoPermission) + var repoPermission api.RepoCollaboratorPermission + DecodeJSON(t, resp, &repoPermission) - assert.Equal(t, "read", repoPermission.Permission) - }) + assert.Equal(t, "read", repoPermission.Permission) + }) - t.Run("CollaboratorWithWriteAccess", func(t *testing.T) { - t.Run("AddUserAsCollaboratorWithWriteAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeWrite)) + t.Run("CollaboratorWithWriteAccess", func(t *testing.T) { + t.Run("AddUserAsCollaboratorWithWriteAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeWrite)) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name). - AddTokenAuth(testCtx.Token) - resp := MakeRequest(t, req, http.StatusOK) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name). + AddTokenAuth(testCtx.Token) + resp := MakeRequest(t, req, http.StatusOK) - var repoPermission api.RepoCollaboratorPermission - DecodeJSON(t, resp, &repoPermission) + var repoPermission api.RepoCollaboratorPermission + DecodeJSON(t, resp, &repoPermission) - assert.Equal(t, "write", repoPermission.Permission) - }) + assert.Equal(t, "write", repoPermission.Permission) + }) - t.Run("CollaboratorWithAdminAccess", func(t *testing.T) { - t.Run("AddUserAsCollaboratorWithAdminAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeAdmin)) + t.Run("CollaboratorWithAdminAccess", func(t *testing.T) { + t.Run("AddUserAsCollaboratorWithAdminAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeAdmin)) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name). - AddTokenAuth(testCtx.Token) - resp := MakeRequest(t, req, http.StatusOK) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name). + AddTokenAuth(testCtx.Token) + resp := MakeRequest(t, req, http.StatusOK) - var repoPermission api.RepoCollaboratorPermission - DecodeJSON(t, resp, &repoPermission) + var repoPermission api.RepoCollaboratorPermission + DecodeJSON(t, resp, &repoPermission) - assert.Equal(t, "admin", repoPermission.Permission) - }) + assert.Equal(t, "admin", repoPermission.Permission) + }) - t.Run("CollaboratorNotFound", func(t *testing.T) { - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, "non-existent-user"). - AddTokenAuth(testCtx.Token) - MakeRequest(t, req, http.StatusNotFound) - }) + t.Run("CollaboratorNotFound", func(t *testing.T) { + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, "non-existent-user"). + AddTokenAuth(testCtx.Token) + MakeRequest(t, req, http.StatusNotFound) + }) - t.Run("CollaboratorBlocked", func(t *testing.T) { - ctx := NewAPITestContext(t, repo2Owner.Name, repo2.Name, auth_model.AccessTokenScopeWriteRepository) - ctx.ExpectedCode = http.StatusForbidden - doAPIAddCollaborator(ctx, user34.Name, perm.AccessModeAdmin)(t) - }) + t.Run("CollaboratorBlocked", func(t *testing.T) { + ctx := NewAPITestContext(t, repo2Owner.Name, repo2.Name, auth_model.AccessTokenScopeWriteRepository) + ctx.ExpectedCode = http.StatusForbidden + doAPIAddCollaborator(ctx, user34.Name, perm.AccessModeAdmin)(t) + }) + + t.Run("CollaboratorCanQueryItsPermissions", func(t *testing.T) { + t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user5.Name, perm.AccessModeRead)) + + _session := loginUser(t, user5.Name) + _testCtx := NewAPITestContext(t, user5.Name, repo2.Name, auth_model.AccessTokenScopeReadRepository) + + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user5.Name). + AddTokenAuth(_testCtx.Token) + resp := _session.MakeRequest(t, req, http.StatusOK) - t.Run("CollaboratorCanQueryItsPermissions", func(t *testing.T) { - t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user5.Name, perm.AccessModeRead)) + var repoPermission api.RepoCollaboratorPermission + DecodeJSON(t, resp, &repoPermission) - _session := loginUser(t, user5.Name) - _testCtx := NewAPITestContext(t, user5.Name, repo2.Name, auth_model.AccessTokenScopeReadRepository) + assert.Equal(t, "read", repoPermission.Permission) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user5.Name). - AddTokenAuth(_testCtx.Token) - resp := _session.MakeRequest(t, req, http.StatusOK) + t.Run("CollaboratorCanReadOwnPermission", func(t *testing.T) { + session := loginUser(t, user5.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - var repoPermission api.RepoCollaboratorPermission - DecodeJSON(t, resp, &repoPermission) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user5.Name).AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) - assert.Equal(t, "read", repoPermission.Permission) + repoCollPerm := api.RepoCollaboratorPermission{} + DecodeJSON(t, resp, &repoCollPerm) + + assert.Equal(t, "read", repoCollPerm.Permission) }) + }) - t.Run("CollaboratorCanQueryItsPermissions", func(t *testing.T) { - t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user5.Name, perm.AccessModeRead)) + t.Run("CollaboratorCanQueryItsPermissions", func(t *testing.T) { + t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user5.Name, perm.AccessModeRead)) - _session := loginUser(t, user5.Name) - _testCtx := NewAPITestContext(t, user5.Name, repo2.Name, auth_model.AccessTokenScopeReadRepository) + _session := loginUser(t, user5.Name) + _testCtx := NewAPITestContext(t, user5.Name, repo2.Name, auth_model.AccessTokenScopeReadRepository) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user5.Name). - AddTokenAuth(_testCtx.Token) - resp := _session.MakeRequest(t, req, http.StatusOK) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user5.Name). + AddTokenAuth(_testCtx.Token) + resp := _session.MakeRequest(t, req, http.StatusOK) - var repoPermission api.RepoCollaboratorPermission - DecodeJSON(t, resp, &repoPermission) + var repoPermission api.RepoCollaboratorPermission + DecodeJSON(t, resp, &repoPermission) - assert.Equal(t, "read", repoPermission.Permission) - }) + assert.Equal(t, "read", repoPermission.Permission) + }) - t.Run("RepoAdminCanQueryACollaboratorsPermissions", func(t *testing.T) { - t.Run("AddUserAsCollaboratorWithAdminAccess", doAPIAddCollaborator(testCtx, user10.Name, perm.AccessModeAdmin)) - t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user11.Name, perm.AccessModeRead)) + t.Run("RepoAdminCanQueryACollaboratorsPermissions", func(t *testing.T) { + t.Run("AddUserAsCollaboratorWithAdminAccess", doAPIAddCollaborator(testCtx, user10.Name, perm.AccessModeAdmin)) + t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user11.Name, perm.AccessModeRead)) - _session := loginUser(t, user10.Name) - _testCtx := NewAPITestContext(t, user10.Name, repo2.Name, auth_model.AccessTokenScopeReadRepository) + _session := loginUser(t, user10.Name) + _testCtx := NewAPITestContext(t, user10.Name, repo2.Name, auth_model.AccessTokenScopeReadRepository) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user11.Name). - AddTokenAuth(_testCtx.Token) - resp := _session.MakeRequest(t, req, http.StatusOK) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user11.Name). + AddTokenAuth(_testCtx.Token) + resp := _session.MakeRequest(t, req, http.StatusOK) - var repoPermission api.RepoCollaboratorPermission - DecodeJSON(t, resp, &repoPermission) + var repoPermission api.RepoCollaboratorPermission + DecodeJSON(t, resp, &repoPermission) - assert.Equal(t, "read", repoPermission.Permission) - }) + assert.Equal(t, "read", repoPermission.Permission) }) } diff --git a/tests/integration/empty_repo_test.go b/tests/integration/empty_repo_test.go index b19774a826814..e122531dc9877 100644 --- a/tests/integration/empty_repo_test.go +++ b/tests/integration/empty_repo_test.go @@ -60,12 +60,20 @@ func TestEmptyRepoAddFile(t *testing.T) { defer tests.PrepareTestEnv(t)() session := loginUser(t, "user30") + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) + + // test web page req := NewRequest(t, "GET", "/user30/empty") resp := session.MakeRequest(t, req, http.StatusOK) bodyString := resp.Body.String() assert.Contains(t, bodyString, "empty-repo-guide") assert.True(t, test.IsNormalPageCompleted(bodyString)) + // test api + req = NewRequest(t, "GET", "/api/v1/repos/user30/empty/raw/main/README.md").AddTokenAuth(token) + session.MakeRequest(t, req, http.StatusNotFound) + + // create a new file req = NewRequest(t, "GET", "/user30/empty/_new/"+setting.Repository.DefaultBranch) resp = session.MakeRequest(t, req, http.StatusOK) doc := NewHTMLParser(t, resp.Body).Find(`input[name="commit_choice"]`) diff --git a/tests/integration/git_smart_http_test.go b/tests/integration/git_smart_http_test.go index 15336b9b81f39..55d647672a2ba 100644 --- a/tests/integration/git_smart_http_test.go +++ b/tests/integration/git_smart_http_test.go @@ -9,7 +9,10 @@ import ( "net/url" "testing" + "code.gitea.io/gitea/modules/util" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestGitSmartHTTP(t *testing.T) { @@ -18,51 +21,55 @@ func TestGitSmartHTTP(t *testing.T) { func testGitSmartHTTP(t *testing.T, u *url.URL) { kases := []struct { - p string - code int + method, path string + code int }{ { - p: "user2/repo1/info/refs", + path: "user2/repo1/info/refs", code: http.StatusOK, }, { - p: "user2/repo1/HEAD", + method: "HEAD", + path: "user2/repo1/info/refs", + code: http.StatusOK, + }, + { + path: "user2/repo1/HEAD", code: http.StatusOK, }, { - p: "user2/repo1/objects/info/alternates", + path: "user2/repo1/objects/info/alternates", code: http.StatusNotFound, }, { - p: "user2/repo1/objects/info/http-alternates", + path: "user2/repo1/objects/info/http-alternates", code: http.StatusNotFound, }, { - p: "user2/repo1/../../custom/conf/app.ini", + path: "user2/repo1/../../custom/conf/app.ini", code: http.StatusNotFound, }, { - p: "user2/repo1/objects/info/../../../../custom/conf/app.ini", + path: "user2/repo1/objects/info/../../../../custom/conf/app.ini", code: http.StatusNotFound, }, { - p: `user2/repo1/objects/info/..\..\..\..\custom\conf\app.ini`, + path: `user2/repo1/objects/info/..\..\..\..\custom\conf\app.ini`, code: http.StatusBadRequest, }, } for _, kase := range kases { - t.Run(kase.p, func(t *testing.T) { - p := u.String() + kase.p - req, err := http.NewRequest("GET", p, nil) - assert.NoError(t, err) + t.Run(kase.path, func(t *testing.T) { + req, err := http.NewRequest(util.IfZero(kase.method, "GET"), u.String()+kase.path, nil) + require.NoError(t, err) req.SetBasicAuth("user2", userPassword) resp, err := http.DefaultClient.Do(req) - assert.NoError(t, err) + require.NoError(t, err) defer resp.Body.Close() assert.EqualValues(t, kase.code, resp.StatusCode) _, err = io.ReadAll(resp.Body) - assert.NoError(t, err) + require.NoError(t, err) }) } } diff --git a/web_src/js/components/RepoActionView.vue b/web_src/js/components/RepoActionView.vue index 03c8464060950..2ef528620d7cf 100644 --- a/web_src/js/components/RepoActionView.vue +++ b/web_src/js/components/RepoActionView.vue @@ -105,7 +105,6 @@ export default defineComponent({ intervalID: null as IntervalId | null, currentJobStepsStates: [] as Array>, artifacts: [] as Array>, - onHoverRerunIndex: -1, menuVisible: false, isFullScreen: false, timeVisible: { @@ -120,7 +119,7 @@ export default defineComponent({ link: '', title: '', titleHTML: '', - status: 'unknown' as RunStatus, + status: '' as RunStatus, // do not show the status before initialized, otherwise it would show an incorrect "error" icon canCancel: false, canApprove: false, canRerun: false, @@ -492,13 +491,13 @@ export default defineComponent({
- +
{{ job.name }}
- + {{ job.duration }}
@@ -721,11 +720,6 @@ export default defineComponent({ .job-brief-item .job-brief-rerun { cursor: pointer; - transition: transform 0.2s; -} - -.job-brief-item .job-brief-rerun:hover { - transform: scale(130%); } .job-brief-item .job-brief-item-left {