Merge pull request '[v1.22/gitea] week 2024-17 cherry pick v7.0' (#3354) from earl-warren/forgejo:wip-v7.0-gitea-cherry-pick into v7.0/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/3354 Reviewed-by: Gergely Nagy <algernon@noreply.codeberg.org>
This commit is contained in:
commit
4433cd9793
35 changed files with 284 additions and 159 deletions
|
@ -66,6 +66,7 @@ package "code.gitea.io/gitea/models/migrations/base"
|
||||||
func MainTest
|
func MainTest
|
||||||
|
|
||||||
package "code.gitea.io/gitea/models/organization"
|
package "code.gitea.io/gitea/models/organization"
|
||||||
|
func GetTeamNamesByID
|
||||||
func UpdateTeamUnits
|
func UpdateTeamUnits
|
||||||
func (SearchMembersOptions).ToConds
|
func (SearchMembersOptions).ToConds
|
||||||
func UsersInTeamsCount
|
func UsersInTeamsCount
|
||||||
|
@ -131,6 +132,7 @@ package "code.gitea.io/gitea/models/user"
|
||||||
func GetUserAllSettings
|
func GetUserAllSettings
|
||||||
func DeleteUserSetting
|
func DeleteUserSetting
|
||||||
func GetUserEmailsByNames
|
func GetUserEmailsByNames
|
||||||
|
func GetUserNamesByIDs
|
||||||
|
|
||||||
package "code.gitea.io/gitea/modules/activitypub"
|
package "code.gitea.io/gitea/modules/activitypub"
|
||||||
func CurrentTime
|
func CurrentTime
|
||||||
|
|
5
Makefile
5
Makefile
|
@ -121,7 +121,6 @@ LDFLAGS := $(LDFLAGS) -X "main.ReleaseVersion=$(RELEASE_VERSION)" -X "main.MakeV
|
||||||
LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64
|
LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64
|
||||||
|
|
||||||
ifeq ($(HAS_GO), yes)
|
ifeq ($(HAS_GO), yes)
|
||||||
GO_PACKAGES ?= $(filter-out code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./... | grep -v /vendor/))
|
|
||||||
GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) $(shell $(GO) list code.gitea.io/gitea/models/forgejo_migrations/...) code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./... | grep -v /vendor/))
|
GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) $(shell $(GO) list code.gitea.io/gitea/models/forgejo_migrations/...) code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./... | grep -v /vendor/))
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
@ -457,7 +456,7 @@ lint-go-windows:
|
||||||
.PHONY: lint-go-vet
|
.PHONY: lint-go-vet
|
||||||
lint-go-vet:
|
lint-go-vet:
|
||||||
@echo "Running go vet..."
|
@echo "Running go vet..."
|
||||||
@$(GO) vet $(GO_PACKAGES)
|
@$(GO) vet ./...
|
||||||
|
|
||||||
.PHONY: lint-editorconfig
|
.PHONY: lint-editorconfig
|
||||||
lint-editorconfig:
|
lint-editorconfig:
|
||||||
|
@ -823,7 +822,7 @@ generate-backend: $(TAGS_PREREQ) generate-go
|
||||||
.PHONY: generate-go
|
.PHONY: generate-go
|
||||||
generate-go: $(TAGS_PREREQ)
|
generate-go: $(TAGS_PREREQ)
|
||||||
@echo "Running go generate..."
|
@echo "Running go generate..."
|
||||||
@CC= GOOS= GOARCH= $(GO) generate -tags '$(TAGS)' $(GO_PACKAGES)
|
@CC= GOOS= GOARCH= $(GO) generate -tags '$(TAGS)' ./...
|
||||||
|
|
||||||
.PHONY: merge-locales
|
.PHONY: merge-locales
|
||||||
merge-locales:
|
merge-locales:
|
||||||
|
|
|
@ -36,6 +36,7 @@ var microcmdUserChangePassword = &cli.Command{
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "must-change-password",
|
Name: "must-change-password",
|
||||||
Usage: "User must change password",
|
Usage: "User must change password",
|
||||||
|
Value: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -57,23 +58,18 @@ func runChangePassword(c *cli.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var mustChangePassword optional.Option[bool]
|
|
||||||
if c.IsSet("must-change-password") {
|
|
||||||
mustChangePassword = optional.Some(c.Bool("must-change-password"))
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := &user_service.UpdateAuthOptions{
|
opts := &user_service.UpdateAuthOptions{
|
||||||
Password: optional.Some(c.String("password")),
|
Password: optional.Some(c.String("password")),
|
||||||
MustChangePassword: mustChangePassword,
|
MustChangePassword: optional.Some(c.Bool("must-change-password")),
|
||||||
}
|
}
|
||||||
if err := user_service.UpdateAuth(ctx, user, opts); err != nil {
|
if err := user_service.UpdateAuth(ctx, user, opts); err != nil {
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, password.ErrMinLength):
|
case errors.Is(err, password.ErrMinLength):
|
||||||
return fmt.Errorf("Password is not long enough. Needs to be at least %d", setting.MinPasswordLength)
|
return fmt.Errorf("password is not long enough, needs to be at least %d characters", setting.MinPasswordLength)
|
||||||
case errors.Is(err, password.ErrComplexity):
|
case errors.Is(err, password.ErrComplexity):
|
||||||
return errors.New("Password does not meet complexity requirements")
|
return errors.New("password does not meet complexity requirements")
|
||||||
case errors.Is(err, password.ErrIsPwned):
|
case errors.Is(err, password.ErrIsPwned):
|
||||||
return errors.New("The password you chose is on a list of stolen passwords previously exposed in public data breaches. Please try again with a different password.\nFor more details, see https://haveibeenpwned.com/Passwords")
|
return errors.New("the password is in a list of stolen passwords previously exposed in public data breaches, please try again with a different password, to see more details: https://haveibeenpwned.com/Passwords")
|
||||||
default:
|
default:
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
pwd "code.gitea.io/gitea/modules/auth/password"
|
pwd "code.gitea.io/gitea/modules/auth/password"
|
||||||
"code.gitea.io/gitea/modules/optional"
|
"code.gitea.io/gitea/modules/optional"
|
||||||
|
@ -49,6 +50,7 @@ var microcmdUserCreate = &cli.Command{
|
||||||
Name: "must-change-password",
|
Name: "must-change-password",
|
||||||
Usage: "Set this option to false to prevent forcing the user to change their password after initial login",
|
Usage: "Set this option to false to prevent forcing the user to change their password after initial login",
|
||||||
Value: true,
|
Value: true,
|
||||||
|
DisableDefaultText: true,
|
||||||
},
|
},
|
||||||
&cli.IntFlag{
|
&cli.IntFlag{
|
||||||
Name: "random-password-length",
|
Name: "random-password-length",
|
||||||
|
@ -72,10 +74,10 @@ func runCreateUser(c *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.IsSet("name") && c.IsSet("username") {
|
if c.IsSet("name") && c.IsSet("username") {
|
||||||
return errors.New("Cannot set both --name and --username flags")
|
return errors.New("cannot set both --name and --username flags")
|
||||||
}
|
}
|
||||||
if !c.IsSet("name") && !c.IsSet("username") {
|
if !c.IsSet("name") && !c.IsSet("username") {
|
||||||
return errors.New("One of --name or --username flags must be set")
|
return errors.New("one of --name or --username flags must be set")
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.IsSet("password") && c.IsSet("random-password") {
|
if c.IsSet("password") && c.IsSet("random-password") {
|
||||||
|
@ -111,12 +113,21 @@ func runCreateUser(c *cli.Context) error {
|
||||||
return errors.New("must set either password or random-password flag")
|
return errors.New("must set either password or random-password flag")
|
||||||
}
|
}
|
||||||
|
|
||||||
changePassword := c.Bool("must-change-password")
|
isAdmin := c.Bool("admin")
|
||||||
|
mustChangePassword := true // always default to true
|
||||||
// If this is the first user being created.
|
if c.IsSet("must-change-password") {
|
||||||
// Take it as the admin and don't force a password update.
|
// if the flag is set, use the value provided by the user
|
||||||
if n := user_model.CountUsers(ctx, nil); n == 0 {
|
mustChangePassword = c.Bool("must-change-password")
|
||||||
changePassword = false
|
} else {
|
||||||
|
// check whether there are users in the database
|
||||||
|
hasUserRecord, err := db.IsTableNotEmpty(&user_model.User{})
|
||||||
|
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)
|
||||||
|
mustChangePassword = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
restricted := optional.None[bool]()
|
restricted := optional.None[bool]()
|
||||||
|
@ -132,8 +143,8 @@ func runCreateUser(c *cli.Context) error {
|
||||||
Name: username,
|
Name: username,
|
||||||
Email: c.String("email"),
|
Email: c.String("email"),
|
||||||
Passwd: password,
|
Passwd: password,
|
||||||
IsAdmin: c.Bool("admin"),
|
IsAdmin: isAdmin,
|
||||||
MustChangePassword: changePassword,
|
MustChangePassword: mustChangePassword,
|
||||||
Visibility: visibility,
|
Visibility: visibility,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2387,22 +2387,6 @@ LEVEL = Info
|
||||||
;; Enable issue by repository metrics; default is false
|
;; Enable issue by repository metrics; default is false
|
||||||
;ENABLED_ISSUE_BY_REPOSITORY = false
|
;ENABLED_ISSUE_BY_REPOSITORY = false
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
;[task]
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
;;
|
|
||||||
;; Task queue type, could be `channel` or `redis`.
|
|
||||||
;QUEUE_TYPE = channel
|
|
||||||
;;
|
|
||||||
;; Task queue length, available only when `QUEUE_TYPE` is `channel`.
|
|
||||||
;QUEUE_LENGTH = 1000
|
|
||||||
;;
|
|
||||||
;; Task queue connection string, available only when `QUEUE_TYPE` is `redis`.
|
|
||||||
;; If there is a password of redis, use `redis://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s` or `redis+cluster://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s` for `redis-clsuter`.
|
|
||||||
;QUEUE_CONN_STR = "redis://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s"
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;[migrations]
|
;[migrations]
|
||||||
|
|
|
@ -296,8 +296,8 @@ func MaxBatchInsertSize(bean any) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsTableNotEmpty returns true if table has at least one record
|
// IsTableNotEmpty returns true if table has at least one record
|
||||||
func IsTableNotEmpty(tableName string) (bool, error) {
|
func IsTableNotEmpty(beanOrTableName any) (bool, error) {
|
||||||
return x.Table(tableName).Exist()
|
return x.Table(beanOrTableName).Exist()
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteAllRecords will delete all the records of this table
|
// DeleteAllRecords will delete all the records of this table
|
||||||
|
|
|
@ -287,9 +287,10 @@ func (opts *PackageSearchOptions) configureOrderBy(e db.Engine) {
|
||||||
// SearchVersions gets all versions of packages matching the search options
|
// SearchVersions gets all versions of packages matching the search options
|
||||||
func SearchVersions(ctx context.Context, opts *PackageSearchOptions) ([]*PackageVersion, int64, error) {
|
func SearchVersions(ctx context.Context, opts *PackageSearchOptions) ([]*PackageVersion, int64, error) {
|
||||||
sess := db.GetEngine(ctx).
|
sess := db.GetEngine(ctx).
|
||||||
Where(opts.ToConds()).
|
Select("package_version.*").
|
||||||
Table("package_version").
|
Table("package_version").
|
||||||
Join("INNER", "package", "package.id = package_version.package_id")
|
Join("INNER", "package", "package.id = package_version.package_id").
|
||||||
|
Where(opts.ToConds())
|
||||||
|
|
||||||
opts.configureOrderBy(sess)
|
opts.configureOrderBy(sess)
|
||||||
|
|
||||||
|
@ -304,19 +305,18 @@ func SearchVersions(ctx context.Context, opts *PackageSearchOptions) ([]*Package
|
||||||
|
|
||||||
// SearchLatestVersions gets the latest version of every package matching the search options
|
// SearchLatestVersions gets the latest version of every package matching the search options
|
||||||
func SearchLatestVersions(ctx context.Context, opts *PackageSearchOptions) ([]*PackageVersion, int64, error) {
|
func SearchLatestVersions(ctx context.Context, opts *PackageSearchOptions) ([]*PackageVersion, int64, error) {
|
||||||
cond := opts.ToConds().
|
in := builder.
|
||||||
And(builder.Expr("pv2.id IS NULL"))
|
Select("MAX(package_version.id)").
|
||||||
|
From("package_version").
|
||||||
joinCond := builder.Expr("package_version.package_id = pv2.package_id AND (package_version.created_unix < pv2.created_unix OR (package_version.created_unix = pv2.created_unix AND package_version.id < pv2.id))")
|
InnerJoin("package", "package.id = package_version.package_id").
|
||||||
if opts.IsInternal.Has() {
|
Where(opts.ToConds()).
|
||||||
joinCond = joinCond.And(builder.Eq{"pv2.is_internal": opts.IsInternal.Value()})
|
GroupBy("package_version.package_id")
|
||||||
}
|
|
||||||
|
|
||||||
sess := db.GetEngine(ctx).
|
sess := db.GetEngine(ctx).
|
||||||
|
Select("package_version.*").
|
||||||
Table("package_version").
|
Table("package_version").
|
||||||
Join("LEFT", "package_version pv2", joinCond).
|
|
||||||
Join("INNER", "package", "package.id = package_version.package_id").
|
Join("INNER", "package", "package.id = package_version.package_id").
|
||||||
Where(cond)
|
Where(builder.In("package_version.id", in))
|
||||||
|
|
||||||
opts.configureOrderBy(sess)
|
opts.configureOrderBy(sess)
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,12 @@ type Store interface {
|
||||||
|
|
||||||
// RegenerateSession regenerates the underlying session and returns the new store
|
// RegenerateSession regenerates the underlying session and returns the new store
|
||||||
func RegenerateSession(resp http.ResponseWriter, req *http.Request) (Store, error) {
|
func RegenerateSession(resp http.ResponseWriter, req *http.Request) (Store, error) {
|
||||||
|
for _, f := range BeforeRegenerateSession {
|
||||||
|
f(resp, req)
|
||||||
|
}
|
||||||
s, err := session.RegenerateSession(resp, req)
|
s, err := session.RegenerateSession(resp, req)
|
||||||
return s, err
|
return s, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BeforeRegenerateSession is a list of functions that are called before a session is regenerated.
|
||||||
|
var BeforeRegenerateSession []func(http.ResponseWriter, *http.Request)
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/session"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -45,10 +46,40 @@ func SetSiteCookie(resp http.ResponseWriter, name, value string, maxAge int) {
|
||||||
SameSite: setting.SessionConfig.SameSite,
|
SameSite: setting.SessionConfig.SameSite,
|
||||||
}
|
}
|
||||||
resp.Header().Add("Set-Cookie", cookie.String())
|
resp.Header().Add("Set-Cookie", cookie.String())
|
||||||
if maxAge < 0 {
|
// Previous versions would use a cookie path with a trailing /.
|
||||||
// There was a bug in "setting.SessionConfig.CookiePath" code, the old default value of it was empty "".
|
// These are more specific than cookies without a trailing /, so
|
||||||
// So we have to delete the cookie on path="" again, because some old code leaves cookies on path="".
|
// we need to delete these if they exist.
|
||||||
cookie.Path = strings.TrimSuffix(setting.SessionConfig.CookiePath, "/")
|
deleteLegacySiteCookie(resp, name)
|
||||||
resp.Header().Add("Set-Cookie", cookie.String())
|
}
|
||||||
}
|
|
||||||
|
// deleteLegacySiteCookie deletes the cookie with the given name at the cookie
|
||||||
|
// path with a trailing /, which would unintentionally override the cookie.
|
||||||
|
func deleteLegacySiteCookie(resp http.ResponseWriter, name string) {
|
||||||
|
if setting.SessionConfig.CookiePath == "" || strings.HasSuffix(setting.SessionConfig.CookiePath, "/") {
|
||||||
|
// If the cookie path ends with /, no legacy cookies will take
|
||||||
|
// precedence, so do nothing. The exception is that cookies with no
|
||||||
|
// path could override other cookies, but it's complicated and we don't
|
||||||
|
// currently handle that.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cookie := &http.Cookie{
|
||||||
|
Name: name,
|
||||||
|
Value: "",
|
||||||
|
MaxAge: -1,
|
||||||
|
Path: setting.SessionConfig.CookiePath + "/",
|
||||||
|
Domain: setting.SessionConfig.Domain,
|
||||||
|
Secure: setting.SessionConfig.Secure,
|
||||||
|
HttpOnly: true,
|
||||||
|
SameSite: setting.SessionConfig.SameSite,
|
||||||
|
}
|
||||||
|
resp.Header().Add("Set-Cookie", cookie.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
session.BeforeRegenerateSession = append(session.BeforeRegenerateSession, func(resp http.ResponseWriter, _ *http.Request) {
|
||||||
|
// Ensure that a cookie with a trailing slash does not take precedence over
|
||||||
|
// the cookie written by the middleware.
|
||||||
|
deleteLegacySiteCookie(resp, setting.SessionConfig.CookieName)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ import (
|
||||||
user_service "code.gitea.io/gitea/services/user"
|
user_service "code.gitea.io/gitea/services/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseAuthSource(ctx *context.APIContext, u *user_model.User, sourceID int64, loginName string) {
|
func parseAuthSource(ctx *context.APIContext, u *user_model.User, sourceID int64) {
|
||||||
if sourceID == 0 {
|
if sourceID == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,6 @@ func parseAuthSource(ctx *context.APIContext, u *user_model.User, sourceID int64
|
||||||
|
|
||||||
u.LoginType = source.Type
|
u.LoginType = source.Type
|
||||||
u.LoginSource = source.ID
|
u.LoginSource = source.ID
|
||||||
u.LoginName = loginName
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateUser create a user
|
// CreateUser create a user
|
||||||
|
@ -83,12 +82,13 @@ func CreateUser(ctx *context.APIContext) {
|
||||||
Passwd: form.Password,
|
Passwd: form.Password,
|
||||||
MustChangePassword: true,
|
MustChangePassword: true,
|
||||||
LoginType: auth.Plain,
|
LoginType: auth.Plain,
|
||||||
|
LoginName: form.LoginName,
|
||||||
}
|
}
|
||||||
if form.MustChangePassword != nil {
|
if form.MustChangePassword != nil {
|
||||||
u.MustChangePassword = *form.MustChangePassword
|
u.MustChangePassword = *form.MustChangePassword
|
||||||
}
|
}
|
||||||
|
|
||||||
parseAuthSource(ctx, u, form.SourceID, form.LoginName)
|
parseAuthSource(ctx, u, form.SourceID)
|
||||||
if ctx.Written() {
|
if ctx.Written() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -437,7 +437,7 @@ func GetBranchProtection(ctx *context.APIContext) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.JSON(http.StatusOK, convert.ToBranchProtection(ctx, bp))
|
ctx.JSON(http.StatusOK, convert.ToBranchProtection(ctx, bp, repo))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListBranchProtections list branch protections for a repo
|
// ListBranchProtections list branch protections for a repo
|
||||||
|
@ -470,7 +470,7 @@ func ListBranchProtections(ctx *context.APIContext) {
|
||||||
}
|
}
|
||||||
apiBps := make([]*api.BranchProtection, len(bps))
|
apiBps := make([]*api.BranchProtection, len(bps))
|
||||||
for i := range bps {
|
for i := range bps {
|
||||||
apiBps[i] = convert.ToBranchProtection(ctx, bps[i])
|
apiBps[i] = convert.ToBranchProtection(ctx, bps[i], repo)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.JSON(http.StatusOK, apiBps)
|
ctx.JSON(http.StatusOK, apiBps)
|
||||||
|
@ -682,7 +682,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.JSON(http.StatusCreated, convert.ToBranchProtection(ctx, bp))
|
ctx.JSON(http.StatusCreated, convert.ToBranchProtection(ctx, bp, repo))
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditBranchProtection edits a branch protection for a repo
|
// EditBranchProtection edits a branch protection for a repo
|
||||||
|
@ -964,7 +964,7 @@ func EditBranchProtection(ctx *context.APIContext) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.JSON(http.StatusOK, convert.ToBranchProtection(ctx, bp))
|
ctx.JSON(http.StatusOK, convert.ToBranchProtection(ctx, bp, repo))
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteBranchProtection deletes a branch protection for a repo
|
// DeleteBranchProtection deletes a branch protection for a repo
|
||||||
|
|
|
@ -258,7 +258,7 @@ func Routes() *web.Route {
|
||||||
routes.Get("/metrics", append(mid, Metrics)...)
|
routes.Get("/metrics", append(mid, Metrics)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
routes.Get("/robots.txt", append(mid, misc.RobotsTxt)...)
|
routes.Methods("GET,HEAD", "/robots.txt", append(mid, misc.RobotsTxt)...)
|
||||||
routes.Get("/ssh_info", misc.SSHInfo)
|
routes.Get("/ssh_info", misc.SSHInfo)
|
||||||
routes.Get("/api/healthz", healthcheck.Check)
|
routes.Get("/api/healthz", healthcheck.Check)
|
||||||
|
|
||||||
|
|
|
@ -80,6 +80,11 @@ func newNotifyInput(repo *repo_model.Repository, doer *user_model.User, event we
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newNotifyInputForSchedules(repo *repo_model.Repository) *notifyInput {
|
||||||
|
// the doer here will be ignored as we force using action user when handling schedules
|
||||||
|
return newNotifyInput(repo, user_model.NewActionsUser(), webhook_module.HookEventSchedule)
|
||||||
|
}
|
||||||
|
|
||||||
func (input *notifyInput) WithDoer(doer *user_model.User) *notifyInput {
|
func (input *notifyInput) WithDoer(doer *user_model.User) *notifyInput {
|
||||||
input.Doer = doer
|
input.Doer = doer
|
||||||
return input
|
return input
|
||||||
|
@ -562,7 +567,7 @@ func DetectAndHandleSchedules(ctx context.Context, repo *repo_model.Repository)
|
||||||
// We need a notifyInput to call handleSchedules
|
// We need a notifyInput to call handleSchedules
|
||||||
// if repo is a mirror, commit author maybe an external user,
|
// if repo is a mirror, commit author maybe an external user,
|
||||||
// so we use action user as the Doer of the notifyInput
|
// so we use action user as the Doer of the notifyInput
|
||||||
notifyInput := newNotifyInput(repo, user_model.NewActionsUser(), webhook_module.HookEventSchedule)
|
notifyInput := newNotifyInputForSchedules(repo)
|
||||||
|
|
||||||
return handleSchedules(ctx, scheduleWorkflows, commit, notifyInput, repo.DefaultBranch)
|
return handleSchedules(ctx, scheduleWorkflows, commit, notifyInput, repo.DefaultBranch)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
session_module "code.gitea.io/gitea/modules/session"
|
||||||
|
|
||||||
chiSession "gitea.com/go-chi/session"
|
chiSession "gitea.com/go-chi/session"
|
||||||
"github.com/gorilla/sessions"
|
"github.com/gorilla/sessions"
|
||||||
|
@ -65,7 +66,7 @@ func (st *SessionsStore) Save(r *http.Request, w http.ResponseWriter, session *s
|
||||||
chiStore := chiSession.GetSession(r)
|
chiStore := chiSession.GetSession(r)
|
||||||
|
|
||||||
if session.IsNew {
|
if session.IsNew {
|
||||||
_, _ = chiSession.RegenerateSession(w, r)
|
_, _ = session_module.RegenerateSession(w, r)
|
||||||
session.IsNew = false
|
session.IsNew = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/container"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
@ -105,33 +106,46 @@ func ToBranch(ctx context.Context, repo *repo_model.Repository, branchName strin
|
||||||
return branch, nil
|
return branch, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getWhitelistEntities returns the names of the entities that are in the whitelist
|
||||||
|
func getWhitelistEntities[T *user_model.User | *organization.Team](entities []T, whitelistIDs []int64) []string {
|
||||||
|
whitelistUserIDsSet := container.SetOf(whitelistIDs...)
|
||||||
|
whitelistNames := make([]string, 0)
|
||||||
|
for _, entity := range entities {
|
||||||
|
switch v := any(entity).(type) {
|
||||||
|
case *user_model.User:
|
||||||
|
if whitelistUserIDsSet.Contains(v.ID) {
|
||||||
|
whitelistNames = append(whitelistNames, v.Name)
|
||||||
|
}
|
||||||
|
case *organization.Team:
|
||||||
|
if whitelistUserIDsSet.Contains(v.ID) {
|
||||||
|
whitelistNames = append(whitelistNames, v.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return whitelistNames
|
||||||
|
}
|
||||||
|
|
||||||
// ToBranchProtection convert a ProtectedBranch to api.BranchProtection
|
// ToBranchProtection convert a ProtectedBranch to api.BranchProtection
|
||||||
func ToBranchProtection(ctx context.Context, bp *git_model.ProtectedBranch) *api.BranchProtection {
|
func ToBranchProtection(ctx context.Context, bp *git_model.ProtectedBranch, repo *repo_model.Repository) *api.BranchProtection {
|
||||||
pushWhitelistUsernames, err := user_model.GetUserNamesByIDs(ctx, bp.WhitelistUserIDs)
|
readers, err := access_model.GetRepoReaders(ctx, repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetUserNamesByIDs (WhitelistUserIDs): %v", err)
|
log.Error("GetRepoReaders: %v", err)
|
||||||
}
|
}
|
||||||
mergeWhitelistUsernames, err := user_model.GetUserNamesByIDs(ctx, bp.MergeWhitelistUserIDs)
|
|
||||||
|
pushWhitelistUsernames := getWhitelistEntities(readers, bp.WhitelistUserIDs)
|
||||||
|
mergeWhitelistUsernames := getWhitelistEntities(readers, bp.MergeWhitelistUserIDs)
|
||||||
|
approvalsWhitelistUsernames := getWhitelistEntities(readers, bp.ApprovalsWhitelistUserIDs)
|
||||||
|
|
||||||
|
teamReaders, err := organization.OrgFromUser(repo.Owner).TeamsWithAccessToRepo(ctx, repo.ID, perm.AccessModeRead)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetUserNamesByIDs (MergeWhitelistUserIDs): %v", err)
|
log.Error("Repo.Owner.TeamsWithAccessToRepo: %v", err)
|
||||||
}
|
|
||||||
approvalsWhitelistUsernames, err := user_model.GetUserNamesByIDs(ctx, bp.ApprovalsWhitelistUserIDs)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("GetUserNamesByIDs (ApprovalsWhitelistUserIDs): %v", err)
|
|
||||||
}
|
|
||||||
pushWhitelistTeams, err := organization.GetTeamNamesByID(ctx, bp.WhitelistTeamIDs)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("GetTeamNamesByID (WhitelistTeamIDs): %v", err)
|
|
||||||
}
|
|
||||||
mergeWhitelistTeams, err := organization.GetTeamNamesByID(ctx, bp.MergeWhitelistTeamIDs)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("GetTeamNamesByID (MergeWhitelistTeamIDs): %v", err)
|
|
||||||
}
|
|
||||||
approvalsWhitelistTeams, err := organization.GetTeamNamesByID(ctx, bp.ApprovalsWhitelistTeamIDs)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("GetTeamNamesByID (ApprovalsWhitelistTeamIDs): %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pushWhitelistTeams := getWhitelistEntities(teamReaders, bp.WhitelistTeamIDs)
|
||||||
|
mergeWhitelistTeams := getWhitelistEntities(teamReaders, bp.MergeWhitelistTeamIDs)
|
||||||
|
approvalsWhitelistTeams := getWhitelistEntities(teamReaders, bp.ApprovalsWhitelistTeamIDs)
|
||||||
|
|
||||||
branchName := ""
|
branchName := ""
|
||||||
if !git_model.IsRuleNameSpecial(bp.RuleName) {
|
if !git_model.IsRuleNameSpecial(bp.RuleName) {
|
||||||
branchName = bp.RuleName
|
branchName = bp.RuleName
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/cache"
|
"code.gitea.io/gitea/modules/cache"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/gitrepo"
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
|
"code.gitea.io/gitea/modules/json"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/services/automerge"
|
"code.gitea.io/gitea/services/automerge"
|
||||||
|
@ -25,12 +26,41 @@ func getCacheKey(repoID int64, brancheName string) string {
|
||||||
return fmt.Sprintf("commit_status:%x", hashBytes)
|
return fmt.Sprintf("commit_status:%x", hashBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateCommitStatusCache(ctx context.Context, repoID int64, branchName string, status api.CommitStatusState) error {
|
type commitStatusCacheValue struct {
|
||||||
c := cache.GetCache()
|
State string `json:"state"`
|
||||||
return c.Put(getCacheKey(repoID, branchName), string(status), 3*24*60)
|
TargetURL string `json:"target_url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteCommitStatusCache(ctx context.Context, repoID int64, branchName string) error {
|
func getCommitStatusCache(repoID int64, branchName string) *commitStatusCacheValue {
|
||||||
|
c := cache.GetCache()
|
||||||
|
statusStr, ok := c.Get(getCacheKey(repoID, branchName)).(string)
|
||||||
|
if ok && statusStr != "" {
|
||||||
|
var cv commitStatusCacheValue
|
||||||
|
err := json.Unmarshal([]byte(statusStr), &cv)
|
||||||
|
if err == nil && cv.State != "" {
|
||||||
|
return &cv
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("getCommitStatusCache: json.Unmarshal failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateCommitStatusCache(repoID int64, branchName string, state api.CommitStatusState, targetURL string) error {
|
||||||
|
c := cache.GetCache()
|
||||||
|
bs, err := json.Marshal(commitStatusCacheValue{
|
||||||
|
State: state.String(),
|
||||||
|
TargetURL: targetURL,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("updateCommitStatusCache: json.Marshal failed: %v", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return c.Put(getCacheKey(repoID, branchName), string(bs), 3*24*60)
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteCommitStatusCache(repoID int64, branchName string) error {
|
||||||
c := cache.GetCache()
|
c := cache.GetCache()
|
||||||
return c.Delete(getCacheKey(repoID, branchName))
|
return c.Delete(getCacheKey(repoID, branchName))
|
||||||
}
|
}
|
||||||
|
@ -74,7 +104,7 @@ func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, creato
|
||||||
}
|
}
|
||||||
|
|
||||||
if commit.ID.String() == defaultBranchCommit.ID.String() { // since one commit status updated, the combined commit status should be invalid
|
if commit.ID.String() == defaultBranchCommit.ID.String() { // since one commit status updated, the combined commit status should be invalid
|
||||||
if err := deleteCommitStatusCache(ctx, repo.ID, repo.DefaultBranch); err != nil {
|
if err := deleteCommitStatusCache(repo.ID, repo.DefaultBranch); err != nil {
|
||||||
log.Error("deleteCommitStatusCache[%d:%s] failed: %v", repo.ID, repo.DefaultBranch, err)
|
log.Error("deleteCommitStatusCache[%d:%s] failed: %v", repo.ID, repo.DefaultBranch, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,12 +121,12 @@ func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, creato
|
||||||
// FindReposLastestCommitStatuses loading repository default branch latest combinded commit status with cache
|
// FindReposLastestCommitStatuses loading repository default branch latest combinded commit status with cache
|
||||||
func FindReposLastestCommitStatuses(ctx context.Context, repos []*repo_model.Repository) ([]*git_model.CommitStatus, error) {
|
func FindReposLastestCommitStatuses(ctx context.Context, repos []*repo_model.Repository) ([]*git_model.CommitStatus, error) {
|
||||||
results := make([]*git_model.CommitStatus, len(repos))
|
results := make([]*git_model.CommitStatus, len(repos))
|
||||||
c := cache.GetCache()
|
|
||||||
|
|
||||||
for i, repo := range repos {
|
for i, repo := range repos {
|
||||||
status, ok := c.Get(getCacheKey(repo.ID, repo.DefaultBranch)).(string)
|
if cv := getCommitStatusCache(repo.ID, repo.DefaultBranch); cv != nil {
|
||||||
if ok && status != "" {
|
results[i] = &git_model.CommitStatus{
|
||||||
results[i] = &git_model.CommitStatus{State: api.CommitStatusState(status)}
|
State: api.CommitStatusState(cv.State),
|
||||||
|
TargetURL: cv.TargetURL,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,8 +153,8 @@ func FindReposLastestCommitStatuses(ctx context.Context, repos []*repo_model.Rep
|
||||||
for i, repo := range repos {
|
for i, repo := range repos {
|
||||||
if results[i] == nil {
|
if results[i] == nil {
|
||||||
results[i] = git_model.CalcCommitStatus(repoToItsLatestCommitStatuses[repo.ID])
|
results[i] = git_model.CalcCommitStatus(repoToItsLatestCommitStatuses[repo.ID])
|
||||||
if results[i] != nil {
|
if results[i] != nil && results[i].State != "" {
|
||||||
if err := updateCommitStatusCache(ctx, repo.ID, repo.DefaultBranch, results[i].State); err != nil {
|
if err := updateCommitStatusCache(repo.ID, repo.DefaultBranch, results[i].State, results[i].TargetURL); err != nil {
|
||||||
log.Error("updateCommitStatusCache[%d:%s] failed: %v", repo.ID, repo.DefaultBranch, err)
|
log.Error("updateCommitStatusCache[%d:%s] failed: %v", repo.ID, repo.DefaultBranch, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<div class="flex-list">
|
<div class="flex-list run-list">
|
||||||
{{if not .Runs}}
|
{{if not .Runs}}
|
||||||
<div class="empty-placeholder">
|
<div class="empty-placeholder">
|
||||||
{{svg "octicon-no-entry" 48}}
|
{{svg "octicon-no-entry" 48}}
|
||||||
|
@ -28,16 +28,16 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-item-trailing">
|
<div class="flex-item-trailing">
|
||||||
{{if .RefLink}}
|
{{if .RefLink}}
|
||||||
<a class="ui label tw-px-1 tw-mx-0" href="{{.RefLink}}">{{.PrettyRef}}</a>
|
<a class="ui label run-list-ref gt-ellipsis" href="{{.RefLink}}">{{.PrettyRef}}</a>
|
||||||
{{else}}
|
{{else}}
|
||||||
<span class="ui label tw-px-1 tw-mx-0">{{.PrettyRef}}</span>
|
<span class="ui label run-list-ref gt-ellipsis">{{.PrettyRef}}</span>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
|
||||||
<div class="run-list-item-right">
|
<div class="run-list-item-right">
|
||||||
<div class="run-list-meta">{{svg "octicon-calendar" 16}}{{TimeSinceUnix .Updated ctx.Locale}}</div>
|
<div class="run-list-meta">{{svg "octicon-calendar" 16}}{{TimeSinceUnix .Updated ctx.Locale}}</div>
|
||||||
<div class="run-list-meta">{{svg "octicon-stopwatch" 16}}{{.Duration}}</div>
|
<div class="run-list-meta">{{svg "octicon-stopwatch" 16}}{{.Duration}}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
{{template "base/paginate" .}}
|
{{template "base/paginate" .}}
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</h4>
|
</h4>
|
||||||
<div class="ui attached table unstackable segment">
|
<div class="ui bottom attached table unstackable segment">
|
||||||
<div class="file-view code-view unicode-escaped">
|
<div class="file-view code-view unicode-escaped">
|
||||||
{{if .IsFileTooLarge}}
|
{{if .IsFileTooLarge}}
|
||||||
<table>
|
<table>
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
{{range $i, $v := .TreeNames}}
|
{{range $i, $v := .TreeNames}}
|
||||||
<div class="breadcrumb-divider">/</div>
|
<div class="breadcrumb-divider">/</div>
|
||||||
{{if eq $i $l}}
|
{{if eq $i $l}}
|
||||||
<input id="file-name" maxlength="500" value="{{$v}}" placeholder="{{ctx.Locale.Tr "repo.editor.name_your_file"}}" data-editorconfig="{{$.EditorconfigJson}}" required autofocus>
|
<input id="file-name" maxlength="255" value="{{$v}}" placeholder="{{ctx.Locale.Tr "repo.editor.name_your_file"}}" data-editorconfig="{{$.EditorconfigJson}}" required autofocus>
|
||||||
<span data-tooltip-content="{{ctx.Locale.Tr "repo.editor.filename_help"}}">{{svg "octicon-info"}}</span>
|
<span data-tooltip-content="{{ctx.Locale.Tr "repo.editor.filename_help"}}">{{svg "octicon-info"}}</span>
|
||||||
{{else}}
|
{{else}}
|
||||||
<span class="section"><a href="{{$.BranchLink}}/{{index $.TreePaths $i | PathEscapeSegments}}">{{$v}}</a></span>
|
<span class="section"><a href="{{$.BranchLink}}/{{index $.TreePaths $i | PathEscapeSegments}}">{{$v}}</a></span>
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
{{range $i, $v := .TreeNames}}
|
{{range $i, $v := .TreeNames}}
|
||||||
<div class="breadcrumb-divider">/</div>
|
<div class="breadcrumb-divider">/</div>
|
||||||
{{if eq $i $l}}
|
{{if eq $i $l}}
|
||||||
<input type="text" id="file-name" maxlength="500" value="{{$v}}" placeholder="{{ctx.Locale.Tr "repo.editor.add_subdir"}}" autofocus>
|
<input type="text" id="file-name" maxlength="255" value="{{$v}}" placeholder="{{ctx.Locale.Tr "repo.editor.add_subdir"}}" autofocus>
|
||||||
<span data-tooltip-content="{{ctx.Locale.Tr "repo.editor.filename_help"}}">{{svg "octicon-info"}}</span>
|
<span data-tooltip-content="{{ctx.Locale.Tr "repo.editor.filename_help"}}">{{svg "octicon-info"}}</span>
|
||||||
{{else}}
|
{{else}}
|
||||||
<span class="section"><a href="{{$.BranchLink}}/{{index $.TreePaths $i | PathEscapeSegments}}">{{$v}}</a></span>
|
<span class="section"><a href="{{$.BranchLink}}/{{index $.TreePaths $i | PathEscapeSegments}}">{{$v}}</a></span>
|
||||||
|
|
|
@ -5,26 +5,24 @@
|
||||||
<div class="content tw-text-left">
|
<div class="content tw-text-left">
|
||||||
<form class="ui form form-fetch-action" action="{{printf "%s/issues/new" .Repository.Link}}" method="post">
|
<form class="ui form form-fetch-action" action="{{printf "%s/issues/new" .Repository.Link}}" method="post">
|
||||||
{{.CsrfTokenHtml}}
|
{{.CsrfTokenHtml}}
|
||||||
<div class="ui segment content">
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<span class="text"><strong>{{ctx.Locale.Tr "repository"}}</strong></span>
|
<label><strong>{{ctx.Locale.Tr "repository"}}</strong></label>
|
||||||
<div class="ui search normal selection dropdown issue_reference_repository_search">
|
<div class="ui search selection dropdown issue_reference_repository_search">
|
||||||
<div class="default text">{{.Repository.FullName}}</div>
|
<div class="default text">{{.Repository.FullName}}</div>
|
||||||
<div class="menu"></div>
|
<div class="menu"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<span class="text"><strong>{{ctx.Locale.Tr "repo.milestones.title"}}</strong></span>
|
<label><strong>{{ctx.Locale.Tr "repo.milestones.title"}}</strong></label>
|
||||||
<input name="title" value="" autofocus required maxlength="255" autocomplete="off">
|
<input name="title" value="" autofocus required maxlength="255" autocomplete="off">
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<span class="text"><strong>{{ctx.Locale.Tr "repo.issues.reference_issue.body"}}</strong></span>
|
<label><strong>{{ctx.Locale.Tr "repo.issues.reference_issue.body"}}</strong></label>
|
||||||
<textarea name="content" class="form-control"></textarea>
|
<textarea name="content" class="form-control"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="text right">
|
<div class="text right">
|
||||||
<button class="ui primary button">{{ctx.Locale.Tr "repo.issues.create"}}</button>
|
<button class="ui primary button">{{ctx.Locale.Tr "repo.issues.create"}}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
<a class="ui primary tiny button" href="{{.LFSFilesLink}}/find?oid={{.LFSFile.Oid}}&size={{.LFSFile.Size}}">{{ctx.Locale.Tr "repo.settings.lfs_findcommits"}}</a>
|
<a class="ui primary tiny button" href="{{.LFSFilesLink}}/find?oid={{.LFSFile.Oid}}&size={{.LFSFile.Size}}">{{ctx.Locale.Tr "repo.settings.lfs_findcommits"}}</a>
|
||||||
</div>
|
</div>
|
||||||
</h4>
|
</h4>
|
||||||
<div class="ui attached table unstackable segment">
|
<div class="ui bottom attached table unstackable segment">
|
||||||
{{template "repo/unicode_escape_prompt" dict "EscapeStatus" .EscapeStatus "root" $}}
|
{{template "repo/unicode_escape_prompt" dict "EscapeStatus" .EscapeStatus "root" $}}
|
||||||
<div class="file-view{{if .IsMarkup}} markup {{.MarkupType}}{{else if .IsPlainText}} plain-text{{else if .IsTextFile}} code-view{{end}}">
|
<div class="file-view{{if .IsMarkup}} markup {{.MarkupType}}{{else if .IsPlainText}} plain-text{{else if .IsTextFile}} code-view{{end}}">
|
||||||
{{if .IsMarkup}}
|
{{if .IsMarkup}}
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{if not .ReadmeInList}}
|
{{if not .ReadmeInList}}
|
||||||
<div id="repo-file-commit-box" class="ui top attached header list-header tw-mb-4 tw-flex tw-justify-between">
|
<div id="repo-file-commit-box" class="ui segment list-header tw-mb-4 tw-flex tw-justify-between">
|
||||||
<div class="latest-commit">
|
<div class="latest-commit">
|
||||||
{{template "repo/latest_commit" .}}
|
{{template "repo/latest_commit" .}}
|
||||||
</div>
|
</div>
|
||||||
|
@ -93,7 +93,7 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</h4>
|
</h4>
|
||||||
<div class="ui attached table unstackable segment">
|
<div class="ui bottom attached table unstackable segment">
|
||||||
{{if not (or .IsMarkup .IsRenderedHTML)}}
|
{{if not (or .IsMarkup .IsRenderedHTML)}}
|
||||||
{{template "repo/unicode_escape_prompt" dict "EscapeStatus" .EscapeStatus "root" $}}
|
{{template "repo/unicode_escape_prompt" dict "EscapeStatus" .EscapeStatus "root" $}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a MIT-style
|
// SPDX-License-Identifier: MIT
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package integration
|
package integration
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a MIT-style
|
// SPDX-License-Identifier: MIT
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package integration
|
package integration
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a MIT-style
|
// SPDX-License-Identifier: MIT
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package integration
|
package integration
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a MIT-style
|
// SPDX-License-Identifier: MIT
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package integration
|
package integration
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
package integration
|
package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -44,9 +44,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.run-list-item-right {
|
.run-list-item-right {
|
||||||
flex: 0 0 min(20%, 130px);
|
width: 130px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
flex-shrink: 0;
|
||||||
gap: 3px;
|
gap: 3px;
|
||||||
color: var(--color-text-light);
|
color: var(--color-text-light);
|
||||||
}
|
}
|
||||||
|
@ -57,3 +58,26 @@
|
||||||
gap: .25rem;
|
gap: .25rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.run-list .flex-item-trailing {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
width: 280px;
|
||||||
|
flex: 0 0 280px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.run-list-ref {
|
||||||
|
display: inline-block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767.98px) {
|
||||||
|
.run-list .flex-item-trailing {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
width: auto;
|
||||||
|
flex-basis: auto;
|
||||||
|
}
|
||||||
|
.run-list-item-right,
|
||||||
|
.run-list-ref {
|
||||||
|
max-width: 110px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -496,6 +496,7 @@ ol.ui.list li,
|
||||||
|
|
||||||
.ui.selection.dropdown .menu > .item {
|
.ui.selection.dropdown .menu > .item {
|
||||||
border-color: var(--color-secondary);
|
border-color: var(--color-secondary);
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.selection.visible.dropdown > .text:not(.default) {
|
.ui.selection.visible.dropdown > .text:not(.default) {
|
||||||
|
@ -517,6 +518,12 @@ ol.ui.list li,
|
||||||
color: var(--color-text-light-2);
|
color: var(--color-text-light-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ui.dropdown > .text {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
/* extend fomantic style '.ui.dropdown > .text > img' to include svg.img */
|
/* extend fomantic style '.ui.dropdown > .text > img' to include svg.img */
|
||||||
.ui.dropdown > .text > .img {
|
.ui.dropdown > .text > .img {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
|
|
|
@ -18,7 +18,8 @@
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-content.install form.ui.form input {
|
.page-content.install form.ui.form input:not([type="checkbox"],[type="radio"]),
|
||||||
|
.page-content.install form.ui.form .ui.selection.dropdown {
|
||||||
width: 60%;
|
width: 60%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,7 @@ input[type="radio"] {
|
||||||
}
|
}
|
||||||
.ui.toggle.checkbox input {
|
.ui.toggle.checkbox input {
|
||||||
width: 3.5rem;
|
width: 3.5rem;
|
||||||
height: 1.5rem;
|
height: 21px;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
}
|
}
|
||||||
|
@ -81,29 +81,30 @@ input[type="radio"] {
|
||||||
content: "";
|
content: "";
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
top: 0;
|
top: 0;
|
||||||
width: 3.5rem;
|
width: 49px;
|
||||||
height: 1.5rem;
|
height: 21px;
|
||||||
border-radius: 500rem;
|
border-radius: 500rem;
|
||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
.ui.toggle.checkbox label::after {
|
.ui.toggle.checkbox label::after {
|
||||||
background: var(--color-white);
|
background: var(--color-white);
|
||||||
|
box-shadow: 1px 1px 4px 1px var(--color-shadow);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
content: "";
|
content: "";
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
width: 1.5rem;
|
width: 18px;
|
||||||
height: 1.5rem;
|
height: 18px;
|
||||||
top: 0;
|
top: 1.5px;
|
||||||
left: 0;
|
left: 1.5px;
|
||||||
border-radius: 500rem;
|
border-radius: 500rem;
|
||||||
transition: background 0.3s ease, left 0.3s ease;
|
transition: background 0.3s ease, left 0.3s ease;
|
||||||
}
|
}
|
||||||
.ui.toggle.checkbox input ~ label::after {
|
.ui.toggle.checkbox input ~ label::after {
|
||||||
left: -0.05rem;
|
left: 1.5px;
|
||||||
}
|
}
|
||||||
.ui.toggle.checkbox input:checked ~ label::after {
|
.ui.toggle.checkbox input:checked ~ label::after {
|
||||||
left: 2.15rem;
|
left: 29px;
|
||||||
}
|
}
|
||||||
.ui.toggle.checkbox input:focus ~ label::before,
|
.ui.toggle.checkbox input:focus ~ label::before,
|
||||||
.ui.toggle.checkbox label::before {
|
.ui.toggle.checkbox label::before {
|
||||||
|
|
|
@ -435,7 +435,6 @@ td .commit-summary {
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.non-diff-file-content .attached.segment,
|
|
||||||
.non-diff-file-content .pdfobject {
|
.non-diff-file-content .pdfobject {
|
||||||
border-radius: 0 0 var(--border-radius) var(--border-radius);
|
border-radius: 0 0 var(--border-radius) var(--border-radius);
|
||||||
}
|
}
|
||||||
|
@ -1083,6 +1082,12 @@ td .commit-summary {
|
||||||
margin-left: 15px;
|
margin-left: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.repository.view.issue .comment-list .event .detail .text {
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
.repository.view.issue .comment-list .event .segments {
|
.repository.view.issue .comment-list .event .segments {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
@ -2518,6 +2523,7 @@ tbody.commit-list {
|
||||||
.author-wrapper {
|
.author-wrapper {
|
||||||
max-width: 180px;
|
max-width: 180px;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* in the commit list, messages can wrap so we can use inline */
|
/* in the commit list, messages can wrap so we can use inline */
|
||||||
|
|
|
@ -382,7 +382,7 @@ export function initRepositoryActionView() {
|
||||||
<button class="ui basic small compact button red" @click="cancelRun()" v-else-if="run.canCancel">
|
<button class="ui basic small compact button red" @click="cancelRun()" v-else-if="run.canCancel">
|
||||||
{{ locale.cancel }}
|
{{ locale.cancel }}
|
||||||
</button>
|
</button>
|
||||||
<button class="ui basic small compact button tw-mr-0 link-action" :data-url="`${run.link}/rerun`" v-else-if="run.canRerun">
|
<button class="ui basic small compact button tw-mr-0 tw-whitespace-nowrap link-action" :data-url="`${run.link}/rerun`" v-else-if="run.canRerun">
|
||||||
{{ locale.rerun_all }}
|
{{ locale.rerun_all }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -391,8 +391,8 @@ export function initRepositoryActionView() {
|
||||||
<a class="muted" :href="run.commit.link">{{ run.commit.shortSHA }}</a>
|
<a class="muted" :href="run.commit.link">{{ run.commit.shortSHA }}</a>
|
||||||
{{ run.commit.localePushedBy }}
|
{{ run.commit.localePushedBy }}
|
||||||
<a class="muted" :href="run.commit.pusher.link">{{ run.commit.pusher.displayName }}</a>
|
<a class="muted" :href="run.commit.pusher.link">{{ run.commit.pusher.displayName }}</a>
|
||||||
<span class="ui label" v-if="run.commit.shortSHA">
|
<span class="ui label tw-max-w-full" v-if="run.commit.shortSHA">
|
||||||
<a :href="run.commit.branch.link">{{ run.commit.branch.name }}</a>
|
<a class="gt-ellipsis" :href="run.commit.branch.link">{{ run.commit.branch.name }}</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="action-summary">
|
<div class="action-summary">
|
||||||
|
@ -435,8 +435,8 @@ export function initRepositoryActionView() {
|
||||||
|
|
||||||
<div class="action-view-right">
|
<div class="action-view-right">
|
||||||
<div class="job-info-header">
|
<div class="job-info-header">
|
||||||
<div class="job-info-header-left">
|
<div class="job-info-header-left gt-ellipsis">
|
||||||
<h3 class="job-info-header-title">
|
<h3 class="job-info-header-title gt-ellipsis">
|
||||||
{{ currentJob.title }}
|
{{ currentJob.title }}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="job-info-header-detail">
|
<p class="job-info-header-detail">
|
||||||
|
@ -512,6 +512,7 @@ export function initRepositoryActionView() {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-info-summary-title {
|
.action-info-summary-title {
|
||||||
|
@ -522,6 +523,7 @@ export function initRepositoryActionView() {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
margin: 0 0 0 8px;
|
margin: 0 0 0 8px;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-summary {
|
.action-summary {
|
||||||
|
@ -737,6 +739,10 @@ export function initRepositoryActionView() {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.job-info-header-left {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.job-step-container {
|
.job-step-container {
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
border-radius: 0 0 var(--border-radius) var(--border-radius);
|
border-radius: 0 0 var(--border-radius) var(--border-radius);
|
||||||
|
|
|
@ -112,6 +112,10 @@ export async function createMonaco(textarea, filename, editorOpts) {
|
||||||
...other,
|
...other,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
monaco.editor.addKeybindingRules([
|
||||||
|
{keybinding: monaco.KeyCode.Enter, command: null}, // disable enter from accepting code completion
|
||||||
|
]);
|
||||||
|
|
||||||
const model = editor.getModel();
|
const model = editor.getModel();
|
||||||
model.onDidChangeContent(() => {
|
model.onDidChangeContent(() => {
|
||||||
textarea.value = editor.getValue({preserveBOM: true});
|
textarea.value = editor.getValue({preserveBOM: true});
|
||||||
|
|
Loading…
Reference in a new issue