Merge branch 'main' into fix-repo-adopt

This commit is contained in:
KN4CK3R 2024-05-06 18:35:57 +02:00 committed by GitHub
commit 196346b6ad
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
50 changed files with 578 additions and 551 deletions

View File

@ -220,10 +220,7 @@ Gitea or set your environment appropriately.`, "")
} }
} }
supportProcReceive := false supportProcReceive := git.DefaultFeatures().SupportProcReceive
if git.CheckGitVersionAtLeast("2.29") == nil {
supportProcReceive = true
}
for scanner.Scan() { for scanner.Scan() {
// TODO: support news feeds for wiki // TODO: support news feeds for wiki
@ -497,7 +494,7 @@ Gitea or set your environment appropriately.`, "")
return nil return nil
} }
if git.CheckGitVersionAtLeast("2.29") != nil { if !git.DefaultFeatures().SupportProcReceive {
return fail(ctx, "No proc-receive support", "current git version doesn't support proc-receive.") return fail(ctx, "No proc-receive support", "current git version doesn't support proc-receive.")
} }

View File

@ -178,7 +178,7 @@ func runServ(c *cli.Context) error {
} }
if len(words) < 2 { if len(words) < 2 {
if git.CheckGitVersionAtLeast("2.29") == nil { if git.DefaultFeatures().SupportProcReceive {
// for AGit Flow // for AGit Flow
if cmd == "ssh_info" { if cmd == "ssh_info" {
fmt.Print(`{"type":"gitea","version":1}`) fmt.Print(`{"type":"gitea","version":1}`)

View File

@ -8,14 +8,14 @@ import "code.gitea.io/gitea/models/db"
// SearchOrderByMap represents all possible search order // SearchOrderByMap represents all possible search order
var SearchOrderByMap = map[string]map[string]db.SearchOrderBy{ var SearchOrderByMap = map[string]map[string]db.SearchOrderBy{
"asc": { "asc": {
"alpha": db.SearchOrderByAlphabetically, "alpha": "owner_name ASC, name ASC",
"created": db.SearchOrderByOldest, "created": db.SearchOrderByOldest,
"updated": db.SearchOrderByLeastUpdated, "updated": db.SearchOrderByLeastUpdated,
"size": db.SearchOrderBySize, "size": db.SearchOrderBySize,
"id": db.SearchOrderByID, "id": db.SearchOrderByID,
}, },
"desc": { "desc": {
"alpha": db.SearchOrderByAlphabeticallyReverse, "alpha": "owner_name DESC, name DESC",
"created": db.SearchOrderByNewest, "created": db.SearchOrderByNewest,
"updated": db.SearchOrderByRecentUpdated, "updated": db.SearchOrderByRecentUpdated,
"size": db.SearchOrderBySizeReverse, "size": db.SearchOrderBySizeReverse,

View File

@ -132,7 +132,7 @@ func (r *BlameReader) Close() error {
// CreateBlameReader creates reader for given repository, commit and file // CreateBlameReader creates reader for given repository, commit and file
func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath string, commit *Commit, file string, bypassBlameIgnore bool) (*BlameReader, error) { func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath string, commit *Commit, file string, bypassBlameIgnore bool) (*BlameReader, error) {
var ignoreRevsFile *string var ignoreRevsFile *string
if CheckGitVersionAtLeast("2.23") == nil && !bypassBlameIgnore { if DefaultFeatures().CheckVersionAtLeast("2.23") && !bypassBlameIgnore {
ignoreRevsFile = tryCreateBlameIgnoreRevsFile(commit) ignoreRevsFile = tryCreateBlameIgnoreRevsFile(commit)
} }

View File

@ -423,7 +423,7 @@ func (c *Commit) GetSubModule(entryname string) (*SubModule, error) {
// GetBranchName gets the closest branch name (as returned by 'git name-rev --name-only') // GetBranchName gets the closest branch name (as returned by 'git name-rev --name-only')
func (c *Commit) GetBranchName() (string, error) { func (c *Commit) GetBranchName() (string, error) {
cmd := NewCommand(c.repo.Ctx, "name-rev") cmd := NewCommand(c.repo.Ctx, "name-rev")
if CheckGitVersionAtLeast("2.13.0") == nil { if DefaultFeatures().CheckVersionAtLeast("2.13.0") {
cmd.AddArguments("--exclude", "refs/tags/*") cmd.AddArguments("--exclude", "refs/tags/*")
} }
cmd.AddArguments("--name-only", "--no-undefined").AddDynamicArguments(c.ID.String()) cmd.AddArguments("--name-only", "--no-undefined").AddDynamicArguments(c.ID.String())

View File

@ -22,42 +22,63 @@ import (
"github.com/hashicorp/go-version" "github.com/hashicorp/go-version"
) )
// RequiredVersion is the minimum Git version required const RequiredVersion = "2.0.0" // the minimum Git version required
const RequiredVersion = "2.0.0"
type Features struct {
gitVersion *version.Version
UsingGogit bool
SupportProcReceive bool // >= 2.29
SupportHashSha256 bool // >= 2.42, SHA-256 repositories no longer an experimental curiosity
SupportedObjectFormats []ObjectFormat // sha1, sha256
}
var ( var (
// GitExecutable is the command name of git GitExecutable = "git" // the command name of git, will be updated to an absolute path during initialization
// Could be updated to an absolute path while initialization DefaultContext context.Context // the default context to run git commands in, must be initialized by git.InitXxx
GitExecutable = "git" defaultFeatures *Features
// DefaultContext is the default context to run git commands in, must be initialized by git.InitXxx
DefaultContext context.Context
DefaultFeatures struct {
GitVersion *version.Version
SupportProcReceive bool // >= 2.29
SupportHashSha256 bool // >= 2.42, SHA-256 repositories no longer an experimental curiosity
}
) )
// loadGitVersion tries to get the current git version and stores it into a global variable func (f *Features) CheckVersionAtLeast(atLeast string) bool {
func loadGitVersion() error { return f.gitVersion.Compare(version.Must(version.NewVersion(atLeast))) >= 0
// doesn't need RWMutex because it's executed by Init() }
if DefaultFeatures.GitVersion != nil {
return nil
}
// VersionInfo returns git version information
func (f *Features) VersionInfo() string {
return f.gitVersion.Original()
}
func DefaultFeatures() *Features {
if defaultFeatures == nil {
if !setting.IsProd || setting.IsInTesting {
log.Warn("git.DefaultFeatures is called before git.InitXxx, initializing with default values")
}
if err := InitSimple(context.Background()); err != nil {
log.Fatal("git.InitSimple failed: %v", err)
}
}
return defaultFeatures
}
func loadGitVersionFeatures() (*Features, error) {
stdout, _, runErr := NewCommand(DefaultContext, "version").RunStdString(nil) stdout, _, runErr := NewCommand(DefaultContext, "version").RunStdString(nil)
if runErr != nil { if runErr != nil {
return runErr return nil, runErr
} }
ver, err := parseGitVersionLine(strings.TrimSpace(stdout)) ver, err := parseGitVersionLine(strings.TrimSpace(stdout))
if err == nil { if err != nil {
DefaultFeatures.GitVersion = ver return nil, err
} }
return err
features := &Features{gitVersion: ver, UsingGogit: isGogit}
features.SupportProcReceive = features.CheckVersionAtLeast("2.29")
features.SupportHashSha256 = features.CheckVersionAtLeast("2.42") && !isGogit
features.SupportedObjectFormats = []ObjectFormat{Sha1ObjectFormat}
if features.SupportHashSha256 {
features.SupportedObjectFormats = append(features.SupportedObjectFormats, Sha256ObjectFormat)
}
return features, nil
} }
func parseGitVersionLine(s string) (*version.Version, error) { func parseGitVersionLine(s string) (*version.Version, error) {
@ -85,56 +106,24 @@ func SetExecutablePath(path string) error {
return fmt.Errorf("git not found: %w", err) return fmt.Errorf("git not found: %w", err)
} }
GitExecutable = absPath GitExecutable = absPath
return nil
}
if err = loadGitVersion(); err != nil { func ensureGitVersion() error {
return fmt.Errorf("unable to load git version: %w", err) if !DefaultFeatures().CheckVersionAtLeast(RequiredVersion) {
}
versionRequired, err := version.NewVersion(RequiredVersion)
if err != nil {
return err
}
if DefaultFeatures.GitVersion.LessThan(versionRequired) {
moreHint := "get git: https://git-scm.com/download/" moreHint := "get git: https://git-scm.com/download/"
if runtime.GOOS == "linux" { if runtime.GOOS == "linux" {
// there are a lot of CentOS/RHEL users using old git, so we add a special hint for them // there are a lot of CentOS/RHEL users using old git, so we add a special hint for them
if _, err = os.Stat("/etc/redhat-release"); err == nil { if _, err := os.Stat("/etc/redhat-release"); err == nil {
// ius.io is the recommended official(git-scm.com) method to install git // ius.io is the recommended official(git-scm.com) method to install git
moreHint = "get git: https://git-scm.com/download/linux and https://ius.io" moreHint = "get git: https://git-scm.com/download/linux and https://ius.io"
} }
} }
return fmt.Errorf("installed git version %q is not supported, Gitea requires git version >= %q, %s", DefaultFeatures.GitVersion.Original(), RequiredVersion, moreHint) return fmt.Errorf("installed git version %q is not supported, Gitea requires git version >= %q, %s", DefaultFeatures().gitVersion.Original(), RequiredVersion, moreHint)
} }
if err = checkGitVersionCompatibility(DefaultFeatures.GitVersion); err != nil { if err := checkGitVersionCompatibility(DefaultFeatures().gitVersion); err != nil {
return fmt.Errorf("installed git version %s has a known compatibility issue with Gitea: %w, please upgrade (or downgrade) git", DefaultFeatures.GitVersion.String(), err) return fmt.Errorf("installed git version %s has a known compatibility issue with Gitea: %w, please upgrade (or downgrade) git", DefaultFeatures().gitVersion.String(), err)
}
return nil
}
// VersionInfo returns git version information
func VersionInfo() string {
if DefaultFeatures.GitVersion == nil {
return "(git not found)"
}
format := "%s"
args := []any{DefaultFeatures.GitVersion.Original()}
// Since git wire protocol has been released from git v2.18
if setting.Git.EnableAutoGitWireProtocol && CheckGitVersionAtLeast("2.18") == nil {
format += ", Wire Protocol %s Enabled"
args = append(args, "Version 2") // for focus color
}
return fmt.Sprintf(format, args...)
}
func checkInit() error {
if setting.Git.HomePath == "" {
return errors.New("unable to init Git's HomeDir, incorrect initialization of the setting and git modules")
}
if DefaultContext != nil {
log.Warn("git module has been initialized already, duplicate init may work but it's better to fix it")
} }
return nil return nil
} }
@ -154,8 +143,12 @@ func HomeDir() string {
// InitSimple initializes git module with a very simple step, no config changes, no global command arguments. // InitSimple initializes git module with a very simple step, no config changes, no global command arguments.
// This method doesn't change anything to filesystem. At the moment, it is only used by some Gitea sub-commands. // This method doesn't change anything to filesystem. At the moment, it is only used by some Gitea sub-commands.
func InitSimple(ctx context.Context) error { func InitSimple(ctx context.Context) error {
if err := checkInit(); err != nil { if setting.Git.HomePath == "" {
return err return errors.New("unable to init Git's HomeDir, incorrect initialization of the setting and git modules")
}
if DefaultContext != nil && (!setting.IsProd || setting.IsInTesting) {
log.Warn("git module has been initialized already, duplicate init may work but it's better to fix it")
} }
DefaultContext = ctx DefaultContext = ctx
@ -165,7 +158,24 @@ func InitSimple(ctx context.Context) error {
defaultCommandExecutionTimeout = time.Duration(setting.Git.Timeout.Default) * time.Second defaultCommandExecutionTimeout = time.Duration(setting.Git.Timeout.Default) * time.Second
} }
return SetExecutablePath(setting.Git.Path) if err := SetExecutablePath(setting.Git.Path); err != nil {
return err
}
var err error
defaultFeatures, err = loadGitVersionFeatures()
if err != nil {
return err
}
if err = ensureGitVersion(); err != nil {
return err
}
// when git works with gnupg (commit signing), there should be a stable home for gnupg commands
if _, ok := os.LookupEnv("GNUPGHOME"); !ok {
_ = os.Setenv("GNUPGHOME", filepath.Join(HomeDir(), ".gnupg"))
}
return nil
} }
// InitFull initializes git module with version check and change global variables, sync gitconfig. // InitFull initializes git module with version check and change global variables, sync gitconfig.
@ -175,30 +185,18 @@ func InitFull(ctx context.Context) (err error) {
return err return err
} }
// when git works with gnupg (commit signing), there should be a stable home for gnupg commands
if _, ok := os.LookupEnv("GNUPGHOME"); !ok {
_ = os.Setenv("GNUPGHOME", filepath.Join(HomeDir(), ".gnupg"))
}
// Since git wire protocol has been released from git v2.18 // Since git wire protocol has been released from git v2.18
if setting.Git.EnableAutoGitWireProtocol && CheckGitVersionAtLeast("2.18") == nil { if setting.Git.EnableAutoGitWireProtocol && DefaultFeatures().CheckVersionAtLeast("2.18") {
globalCommandArgs = append(globalCommandArgs, "-c", "protocol.version=2") globalCommandArgs = append(globalCommandArgs, "-c", "protocol.version=2")
} }
// Explicitly disable credential helper, otherwise Git credentials might leak // Explicitly disable credential helper, otherwise Git credentials might leak
if CheckGitVersionAtLeast("2.9") == nil { if DefaultFeatures().CheckVersionAtLeast("2.9") {
globalCommandArgs = append(globalCommandArgs, "-c", "credential.helper=") globalCommandArgs = append(globalCommandArgs, "-c", "credential.helper=")
} }
DefaultFeatures.SupportProcReceive = CheckGitVersionAtLeast("2.29") == nil
DefaultFeatures.SupportHashSha256 = CheckGitVersionAtLeast("2.42") == nil && !isGogit
if DefaultFeatures.SupportHashSha256 {
SupportedObjectFormats = append(SupportedObjectFormats, Sha256ObjectFormat)
} else {
log.Warn("sha256 hash support is disabled - requires Git >= 2.42. Gogit is currently unsupported")
}
if setting.LFS.StartServer { if setting.LFS.StartServer {
if CheckGitVersionAtLeast("2.1.2") != nil { if !DefaultFeatures().CheckVersionAtLeast("2.1.2") {
return errors.New("LFS server support requires Git >= 2.1.2") return errors.New("LFS server support requires Git >= 2.1.2")
} }
globalCommandArgs = append(globalCommandArgs, "-c", "filter.lfs.required=", "-c", "filter.lfs.smudge=", "-c", "filter.lfs.clean=") globalCommandArgs = append(globalCommandArgs, "-c", "filter.lfs.required=", "-c", "filter.lfs.smudge=", "-c", "filter.lfs.clean=")
@ -238,13 +236,13 @@ func syncGitConfig() (err error) {
return err return err
} }
if CheckGitVersionAtLeast("2.10") == nil { if DefaultFeatures().CheckVersionAtLeast("2.10") {
if err := configSet("receive.advertisePushOptions", "true"); err != nil { if err := configSet("receive.advertisePushOptions", "true"); err != nil {
return err return err
} }
} }
if CheckGitVersionAtLeast("2.18") == nil { if DefaultFeatures().CheckVersionAtLeast("2.18") {
if err := configSet("core.commitGraph", "true"); err != nil { if err := configSet("core.commitGraph", "true"); err != nil {
return err return err
} }
@ -256,7 +254,7 @@ func syncGitConfig() (err error) {
} }
} }
if DefaultFeatures.SupportProcReceive { if DefaultFeatures().SupportProcReceive {
// set support for AGit flow // set support for AGit flow
if err := configAddNonExist("receive.procReceiveRefs", "refs/for"); err != nil { if err := configAddNonExist("receive.procReceiveRefs", "refs/for"); err != nil {
return err return err
@ -294,7 +292,7 @@ func syncGitConfig() (err error) {
} }
// By default partial clones are disabled, enable them from git v2.22 // By default partial clones are disabled, enable them from git v2.22
if !setting.Git.DisablePartialClone && CheckGitVersionAtLeast("2.22") == nil { if !setting.Git.DisablePartialClone && DefaultFeatures().CheckVersionAtLeast("2.22") {
if err = configSet("uploadpack.allowfilter", "true"); err != nil { if err = configSet("uploadpack.allowfilter", "true"); err != nil {
return err return err
} }
@ -309,21 +307,6 @@ func syncGitConfig() (err error) {
return err return err
} }
// CheckGitVersionAtLeast check git version is at least the constraint version
func CheckGitVersionAtLeast(atLeast string) error {
if DefaultFeatures.GitVersion == nil {
panic("git module is not initialized") // it shouldn't happen
}
atLeastVersion, err := version.NewVersion(atLeast)
if err != nil {
return err
}
if DefaultFeatures.GitVersion.Compare(atLeastVersion) < 0 {
return fmt.Errorf("installed git binary version %s is not at least %s", DefaultFeatures.GitVersion.Original(), atLeast)
}
return nil
}
func checkGitVersionCompatibility(gitVer *version.Version) error { func checkGitVersionCompatibility(gitVer *version.Version) error {
badVersions := []struct { badVersions := []struct {
Version *version.Version Version *version.Version

View File

@ -120,12 +120,8 @@ var (
Sha256ObjectFormat ObjectFormat = Sha256ObjectFormatImpl{} Sha256ObjectFormat ObjectFormat = Sha256ObjectFormatImpl{}
) )
var SupportedObjectFormats = []ObjectFormat{
Sha1ObjectFormat,
}
func ObjectFormatFromName(name string) ObjectFormat { func ObjectFormatFromName(name string) ObjectFormat {
for _, objectFormat := range SupportedObjectFormats { for _, objectFormat := range DefaultFeatures().SupportedObjectFormats {
if name == objectFormat.Name() { if name == objectFormat.Name() {
return objectFormat return objectFormat
} }

View File

@ -54,7 +54,7 @@ func (*Sha256Hash) Type() ObjectFormat { return Sha256ObjectFormat }
func NewIDFromString(hexHash string) (ObjectID, error) { func NewIDFromString(hexHash string) (ObjectID, error) {
var theObjectFormat ObjectFormat var theObjectFormat ObjectFormat
for _, objectFormat := range SupportedObjectFormats { for _, objectFormat := range DefaultFeatures().SupportedObjectFormats {
if len(hexHash) == objectFormat.FullLength() { if len(hexHash) == objectFormat.FullLength() {
theObjectFormat = objectFormat theObjectFormat = objectFormat
break break

View File

@ -12,7 +12,7 @@ import (
// GetRemoteAddress returns remote url of git repository in the repoPath with special remote name // GetRemoteAddress returns remote url of git repository in the repoPath with special remote name
func GetRemoteAddress(ctx context.Context, repoPath, remoteName string) (string, error) { func GetRemoteAddress(ctx context.Context, repoPath, remoteName string) (string, error) {
var cmd *Command var cmd *Command
if CheckGitVersionAtLeast("2.7") == nil { if DefaultFeatures().CheckVersionAtLeast("2.7") {
cmd = NewCommand(ctx, "remote", "get-url").AddDynamicArguments(remoteName) cmd = NewCommand(ctx, "remote", "get-url").AddDynamicArguments(remoteName)
} else { } else {
cmd = NewCommand(ctx, "config", "--get").AddDynamicArguments("remote." + remoteName + ".url") cmd = NewCommand(ctx, "config", "--get").AddDynamicArguments("remote." + remoteName + ".url")

View File

@ -74,7 +74,7 @@ func InitRepository(ctx context.Context, repoPath string, bare bool, objectForma
if !IsValidObjectFormat(objectFormatName) { if !IsValidObjectFormat(objectFormatName) {
return fmt.Errorf("invalid object format: %s", objectFormatName) return fmt.Errorf("invalid object format: %s", objectFormatName)
} }
if DefaultFeatures.SupportHashSha256 { if DefaultFeatures().SupportHashSha256 {
cmd.AddOptionValues("--object-format", objectFormatName) cmd.AddOptionValues("--object-format", objectFormatName)
} }

View File

@ -1,6 +0,0 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package git
var isGogit bool

View File

@ -22,9 +22,7 @@ import (
"github.com/go-git/go-git/v5/storage/filesystem" "github.com/go-git/go-git/v5/storage/filesystem"
) )
func init() { const isGogit = true
isGogit = true
}
// Repository represents a Git repository. // Repository represents a Git repository.
type Repository struct { type Repository struct {

View File

@ -15,9 +15,7 @@ import (
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
) )
func init() { const isGogit = false
isGogit = false
}
// Repository represents a Git repository. // Repository represents a Git repository.
type Repository struct { type Repository struct {

View File

@ -438,7 +438,7 @@ func (repo *Repository) getCommitsBeforeLimit(id ObjectID, num int) ([]*Commit,
} }
func (repo *Repository) getBranches(commit *Commit, limit int) ([]string, error) { func (repo *Repository) getBranches(commit *Commit, limit int) ([]string, error) {
if CheckGitVersionAtLeast("2.7.0") == nil { if DefaultFeatures().CheckVersionAtLeast("2.7.0") {
stdout, _, err := NewCommand(repo.Ctx, "for-each-ref", "--format=%(refname:strip=2)"). stdout, _, err := NewCommand(repo.Ctx, "for-each-ref", "--format=%(refname:strip=2)").
AddOptionFormat("--count=%d", limit). AddOptionFormat("--count=%d", limit).
AddOptionValues("--contains", commit.ID.String(), BranchPrefix). AddOptionValues("--contains", commit.ID.String(), BranchPrefix).

View File

@ -11,7 +11,7 @@ import (
// WriteCommitGraph write commit graph to speed up repo access // WriteCommitGraph write commit graph to speed up repo access
// this requires git v2.18 to be installed // this requires git v2.18 to be installed
func WriteCommitGraph(ctx context.Context, repoPath string) error { func WriteCommitGraph(ctx context.Context, repoPath string) error {
if CheckGitVersionAtLeast("2.18") == nil { if DefaultFeatures().CheckVersionAtLeast("2.18") {
if _, _, err := NewCommand(ctx, "commit-graph", "write").RunStdString(&RunOpts{Dir: repoPath}); err != nil { if _, _, err := NewCommand(ctx, "commit-graph", "write").RunStdString(&RunOpts{Dir: repoPath}); err != nil {
return fmt.Errorf("unable to write commit-graph for '%s' : %w", repoPath, err) return fmt.Errorf("unable to write commit-graph for '%s' : %w", repoPath, err)
} }

View File

@ -41,7 +41,7 @@ func SearchPointerBlobs(ctx context.Context, repo *git.Repository, pointerChan c
go pipeline.BlobsLessThan1024FromCatFileBatchCheck(catFileCheckReader, shasToBatchWriter, &wg) go pipeline.BlobsLessThan1024FromCatFileBatchCheck(catFileCheckReader, shasToBatchWriter, &wg)
// 1. Run batch-check on all objects in the repository // 1. Run batch-check on all objects in the repository
if git.CheckGitVersionAtLeast("2.6.0") != nil { if !git.DefaultFeatures().CheckVersionAtLeast("2.6.0") {
revListReader, revListWriter := io.Pipe() revListReader, revListWriter := io.Pipe()
shasToCheckReader, shasToCheckWriter := io.Pipe() shasToCheckReader, shasToCheckWriter := io.Pipe()
wg.Add(2) wg.Add(2)

View File

@ -25,6 +25,7 @@ import (
"code.gitea.io/gitea/modules/system" "code.gitea.io/gitea/modules/system"
"code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/modules/web/routing" "code.gitea.io/gitea/modules/web/routing"
actions_router "code.gitea.io/gitea/routers/api/actions" actions_router "code.gitea.io/gitea/routers/api/actions"
@ -112,7 +113,10 @@ func InitWebInstallPage(ctx context.Context) {
// InitWebInstalled is for global installed configuration. // InitWebInstalled is for global installed configuration.
func InitWebInstalled(ctx context.Context) { func InitWebInstalled(ctx context.Context) {
mustInitCtx(ctx, git.InitFull) mustInitCtx(ctx, git.InitFull)
log.Info("Git version: %s (home: %s)", git.VersionInfo(), git.HomeDir()) log.Info("Git version: %s (home: %s)", git.DefaultFeatures().VersionInfo(), git.HomeDir())
if !git.DefaultFeatures().SupportHashSha256 {
log.Warn("sha256 hash support is disabled - requires Git >= 2.42." + util.Iif(git.DefaultFeatures().UsingGogit, " Gogit is currently unsupported.", ""))
}
// Setup i18n // Setup i18n
translation.InitLocales(ctx) translation.InitLocales(ctx)

View File

@ -122,7 +122,7 @@ func HookPreReceive(ctx *gitea_context.PrivateContext) {
preReceiveBranch(ourCtx, oldCommitID, newCommitID, refFullName) preReceiveBranch(ourCtx, oldCommitID, newCommitID, refFullName)
case refFullName.IsTag(): case refFullName.IsTag():
preReceiveTag(ourCtx, refFullName) preReceiveTag(ourCtx, refFullName)
case git.DefaultFeatures.SupportProcReceive && refFullName.IsFor(): case git.DefaultFeatures().SupportProcReceive && refFullName.IsFor():
preReceiveFor(ourCtx, refFullName) preReceiveFor(ourCtx, refFullName)
default: default:
ourCtx.AssertCanWriteCode() ourCtx.AssertCanWriteCode()

View File

@ -18,7 +18,7 @@ import (
// HookProcReceive proc-receive hook - only handles agit Proc-Receive requests at present // HookProcReceive proc-receive hook - only handles agit Proc-Receive requests at present
func HookProcReceive(ctx *gitea_context.PrivateContext) { func HookProcReceive(ctx *gitea_context.PrivateContext) {
opts := web.GetForm(ctx).(*private.HookOptions) opts := web.GetForm(ctx).(*private.HookOptions)
if !git.DefaultFeatures.SupportProcReceive { if !git.DefaultFeatures().SupportProcReceive {
ctx.Status(http.StatusNotFound) ctx.Status(http.StatusNotFound)
return return
} }

View File

@ -297,7 +297,7 @@ func ServCommand(ctx *context.PrivateContext) {
} }
} else { } else {
// Because of the special ref "refs/for" we will need to delay write permission check // Because of the special ref "refs/for" we will need to delay write permission check
if git.DefaultFeatures.SupportProcReceive && unitType == unit.TypeCode { if git.DefaultFeatures().SupportProcReceive && unitType == unit.TypeCode {
mode = perm.AccessModeRead mode = perm.AccessModeRead
} }

View File

@ -112,7 +112,7 @@ func Config(ctx *context.Context) {
ctx.Data["OfflineMode"] = setting.OfflineMode ctx.Data["OfflineMode"] = setting.OfflineMode
ctx.Data["RunUser"] = setting.RunUser ctx.Data["RunUser"] = setting.RunUser
ctx.Data["RunMode"] = util.ToTitleCase(setting.RunMode) ctx.Data["RunMode"] = util.ToTitleCase(setting.RunMode)
ctx.Data["GitVersion"] = git.VersionInfo() ctx.Data["GitVersion"] = git.DefaultFeatures().VersionInfo()
ctx.Data["AppDataPath"] = setting.AppDataPath ctx.Data["AppDataPath"] = setting.AppDataPath
ctx.Data["RepoRootPath"] = setting.RepoRootPath ctx.Data["RepoRootPath"] = setting.RepoRootPath

View File

@ -15,7 +15,7 @@ import (
) )
func SSHInfo(rw http.ResponseWriter, req *http.Request) { func SSHInfo(rw http.ResponseWriter, req *http.Request) {
if !git.DefaultFeatures.SupportProcReceive { if !git.DefaultFeatures().SupportProcReceive {
rw.WriteHeader(http.StatusNotFound) rw.WriteHeader(http.StatusNotFound)
return return
} }

View File

@ -183,7 +183,7 @@ func httpBase(ctx *context.Context) *serviceHandler {
if repoExist { if repoExist {
// Because of special ref "refs/for" .. , need delay write permission check // Because of special ref "refs/for" .. , need delay write permission check
if git.DefaultFeatures.SupportProcReceive { if git.DefaultFeatures().SupportProcReceive {
accessMode = perm.AccessModeRead accessMode = perm.AccessModeRead
} }

View File

@ -180,7 +180,7 @@ func Create(ctx *context.Context) {
ctx.Data["CanCreateRepo"] = ctx.Doer.CanCreateRepo() ctx.Data["CanCreateRepo"] = ctx.Doer.CanCreateRepo()
ctx.Data["MaxCreationLimit"] = ctx.Doer.MaxCreationLimit() ctx.Data["MaxCreationLimit"] = ctx.Doer.MaxCreationLimit()
ctx.Data["SupportedObjectFormats"] = git.SupportedObjectFormats ctx.Data["SupportedObjectFormats"] = git.DefaultFeatures().SupportedObjectFormats
ctx.Data["DefaultObjectFormat"] = git.Sha1ObjectFormat ctx.Data["DefaultObjectFormat"] = git.Sha1ObjectFormat
ctx.HTML(http.StatusOK, tplCreate) ctx.HTML(http.StatusOK, tplCreate)

View File

@ -1143,7 +1143,7 @@ func GetDiff(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, fi
// so if we are using at least this version of git we don't have to tell ParsePatch to do // so if we are using at least this version of git we don't have to tell ParsePatch to do
// the skipping for us // the skipping for us
parsePatchSkipToFile := opts.SkipTo parsePatchSkipToFile := opts.SkipTo
if opts.SkipTo != "" && git.CheckGitVersionAtLeast("2.31") == nil { if opts.SkipTo != "" && git.DefaultFeatures().CheckVersionAtLeast("2.31") {
cmdDiff.AddOptionFormat("--skip-to=%s", opts.SkipTo) cmdDiff.AddOptionFormat("--skip-to=%s", opts.SkipTo)
parsePatchSkipToFile = "" parsePatchSkipToFile = ""
} }

View File

@ -383,7 +383,7 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo *
cmdApply.AddArguments("--ignore-whitespace") cmdApply.AddArguments("--ignore-whitespace")
} }
is3way := false is3way := false
if git.CheckGitVersionAtLeast("2.32.0") == nil { if git.DefaultFeatures().CheckVersionAtLeast("2.32.0") {
cmdApply.AddArguments("--3way") cmdApply.AddArguments("--3way")
is3way = true is3way = true
} }

View File

@ -104,7 +104,7 @@ func createTemporaryRepoForPR(ctx context.Context, pr *issues_model.PullRequest)
baseBranch := "base" baseBranch := "base"
fetchArgs := git.TrustedCmdArgs{"--no-tags"} fetchArgs := git.TrustedCmdArgs{"--no-tags"}
if git.CheckGitVersionAtLeast("2.25.0") == nil { if git.DefaultFeatures().CheckVersionAtLeast("2.25.0") {
// Writing the commit graph can be slow and is not needed here // Writing the commit graph can be slow and is not needed here
fetchArgs = append(fetchArgs, "--no-write-commit-graph") fetchArgs = append(fetchArgs, "--no-write-commit-graph")
} }

View File

@ -148,7 +148,7 @@ func ApplyDiffPatch(ctx context.Context, repo *repo_model.Repository, doer *user
stderr := &strings.Builder{} stderr := &strings.Builder{}
cmdApply := git.NewCommand(ctx, "apply", "--index", "--recount", "--cached", "--ignore-whitespace", "--whitespace=fix", "--binary") cmdApply := git.NewCommand(ctx, "apply", "--index", "--recount", "--cached", "--ignore-whitespace", "--whitespace=fix", "--binary")
if git.CheckGitVersionAtLeast("2.32") == nil { if git.DefaultFeatures().CheckVersionAtLeast("2.32") {
cmdApply.AddArguments("-3") cmdApply.AddArguments("-3")
} }

View File

@ -0,0 +1,109 @@
{{template "base/head" .}}
<link rel="stylesheet" href="{{AssetUrlPrefix}}/css/devtest.css?v={{AssetVersion}}">
<div class="page-content devtest ui container">
<div>
<h2>Dropdown</h2>
<div>
<div class="ui dropdown tw-border tw-border-red tw-border-dashed" data-tooltip-content="border for demo purpose only">
<span class="text">search-input &amp; flex-item in menu</span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
<div class="menu flex-items-menu">
<div class="ui icon search input"><i class="icon">{{svg "octicon-search"}}</i><input type="text" value="search input in menu"></div>
<div class="item"><input type="radio">item</div>
<div class="item"><input type="radio">item</div>
</div>
</div>
<div class="ui search selection dropdown">
<span class="text">search ...</span>
<input name="value" class="search">
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
{{svg "octicon-x" 14 "remove icon"}}
<div class="menu">
<div class="item">item</div>
</div>
</div>
<div class="ui multiple selection dropdown">
<input class="hidden" value="1">
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
{{svg "octicon-x" 14 "remove icon"}}
<div class="default text">empty multiple dropdown</div>
<div class="menu">
<div class="item">item</div>
</div>
</div>
<div class="ui multiple clearable search selection dropdown">
<input type="hidden" value="1">
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
{{svg "octicon-x" 14 "remove icon"}}
<div class="default text">clearable search dropdown</div>
<div class="menu">
<div class="item" data-value="1">item</div>
</div>
</div>
<div class="ui buttons">
<button class="ui button">Button with Dropdown</button>
<div class="ui dropdown button icon">
{{svg "octicon-triangle-down"}}
<div class="menu">
<div class="item">item</div>
</div>
</div>
</div>
</div>
<h2>Selection</h2>
<div>
{{/* the "selection" class is optional, it will be added by JS automatically */}}
<select class="ui dropdown selection ellipsis-items-nowrap">
<option>a</option>
<option>abcdefuvwxyz</option>
<option>loooooooooooooooooooooooooooooooooooooooooooooooooooooooooong</option>
</select>
<select class="ui dropdown ellipsis-items-nowrap tw-max-w-[8em]">
<option>loooooooooooooooooooooooooooooooooooooooooooooooooooooooooong</option>
<option>abcdefuvwxyz</option>
<option>a</option>
</select>
</div>
<h2>Dropdown Button (demo only without menu)</h2>
<div>
<div class="ui dropdown mini button">
<span class="text">mini dropdown</span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</div>
<div class="ui dropdown tiny button">
<span class="text">tiny dropdown</span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</div>
<div class="ui button dropdown">
<span class="text">button dropdown</span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</div>
</div>
<div>
<div class="ui dropdown mini compact button">
<span class="text">mini compact</span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</div>
<div class="ui dropdown tiny compact button">
<span class="text">tiny compact</span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</div>
<div class="ui button compact dropdown">
<span class="text">button compact</span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</div>
</div>
<div>
<hr>
<div class="ui tiny button">Other button align with ...</div>
<div class="ui dropdown tiny button">
<span class="text">... Dropdown Button</span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</div>
</div>
</div>
</div>
{{template "base/footer" .}}

View File

@ -180,94 +180,6 @@
<input type="text" placeholder="place holder"> <input type="text" placeholder="place holder">
</div> </div>
</div> </div>
<h2>Dropdown with SVG</h2>
<div>
<div class="ui dropdown tw-border tw-border-red tw-border-dashed" data-tooltip-content="border for demo purpose only">
<span class="text">search-input &amp; flex-item in menu</span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
<div class="menu flex-items-menu">
<div class="ui icon search input"><i class="icon">{{svg "octicon-search"}}</i><input type="text" value="search input in menu"></div>
<div class="item"><input type="radio">item</div>
<div class="item"><input type="radio">item</div>
</div>
</div>
<div class="ui search selection dropdown">
<span class="text">search ...</span>
<input name="value" class="search">
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
{{svg "octicon-x" 14 "remove icon"}}
<div class="menu">
<div class="item">item</div>
</div>
</div>
<div class="ui multiple selection dropdown">
<input class="hidden" value="1">
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
{{svg "octicon-x" 14 "remove icon"}}
<div class="default text">empty multiple dropdown</div>
<div class="menu">
<div class="item">item</div>
</div>
</div>
<div class="ui multiple clearable search selection dropdown">
<input type="hidden" value="1">
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
{{svg "octicon-x" 14 "remove icon"}}
<div class="default text">clearable search dropdown</div>
<div class="menu">
<div class="item" data-value="1">item</div>
</div>
</div>
<div class="ui buttons">
<button class="ui button">Button with Dropdown</button>
<div class="ui dropdown button icon">
{{svg "octicon-triangle-down"}}
<div class="menu">
<div class="item">item</div>
</div>
</div>
</div>
</div>
<div>
<div class="ui dropdown mini button">
<span class="text">mini dropdown</span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</div>
<div class="ui dropdown tiny button">
<span class="text">tiny dropdown</span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</div>
<div class="ui button dropdown">
<span class="text">button dropdown</span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</div>
</div>
<div>
<div class="ui dropdown mini compact button">
<span class="text">mini compact</span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</div>
<div class="ui dropdown tiny compact button">
<span class="text">tiny compact</span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</div>
<div class="ui button compact dropdown">
<span class="text">button compact</span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</div>
</div>
<div>
<hr>
<div class="ui tiny button">Button align with ...</div>
<div class="ui dropdown tiny button">
<span class="text">... Dropdown Button</span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</div>
</div>
</div> </div>
<div> <div>

View File

@ -157,168 +157,171 @@
<!-- Optional Settings --> <!-- Optional Settings -->
<h4 class="ui dividing header">{{ctx.Locale.Tr "install.optional_title"}}</h4> <h4 class="ui dividing header">{{ctx.Locale.Tr "install.optional_title"}}</h4>
<div>
<!-- Email --> <!-- Email -->
<details class="optional field"> <details class="optional field">
<summary class="right-content tw-py-2{{if .Err_SMTP}} text red{{end}}"> <summary class="right-content tw-py-2{{if .Err_SMTP}} text red{{end}}">
{{ctx.Locale.Tr "install.email_title"}} {{ctx.Locale.Tr "install.email_title"}}
</summary> </summary>
<div class="inline field"> <div class="inline field">
<label for="smtp_addr">{{ctx.Locale.Tr "install.smtp_addr"}}</label> <label for="smtp_addr">{{ctx.Locale.Tr "install.smtp_addr"}}</label>
<input id="smtp_addr" name="smtp_addr" value="{{.smtp_addr}}"> <input id="smtp_addr" name="smtp_addr" value="{{.smtp_addr}}">
</div>
<div class="inline field">
<label for="smtp_port">{{ctx.Locale.Tr "install.smtp_port"}}</label>
<input id="smtp_port" name="smtp_port" value="{{.smtp_port}}">
</div>
<div class="inline field {{if .Err_SMTPFrom}}error{{end}}">
<label for="smtp_from">{{ctx.Locale.Tr "install.smtp_from"}}</label>
<input id="smtp_from" name="smtp_from" value="{{.smtp_from}}">
<span class="help">{{ctx.Locale.TrString "install.smtp_from_helper"}}{{/* it contains lt/gt chars*/}}</span>
</div>
<div class="inline field {{if .Err_SMTPUser}}error{{end}}">
<label for="smtp_user">{{ctx.Locale.Tr "install.mailer_user"}}</label>
<input id="smtp_user" name="smtp_user" value="{{.smtp_user}}">
</div>
<div class="inline field">
<label for="smtp_passwd">{{ctx.Locale.Tr "install.mailer_password"}}</label>
<input id="smtp_passwd" name="smtp_passwd" type="password" value="{{.smtp_passwd}}">
</div>
<div class="inline field">
<div class="ui checkbox">
<label>{{ctx.Locale.Tr "install.register_confirm"}}</label>
<input name="register_confirm" type="checkbox" {{if .register_confirm}}checked{{end}}>
</div> </div>
</div> <div class="inline field">
<div class="inline field"> <label for="smtp_port">{{ctx.Locale.Tr "install.smtp_port"}}</label>
<div class="ui checkbox"> <input id="smtp_port" name="smtp_port" value="{{.smtp_port}}">
<label>{{ctx.Locale.Tr "install.mail_notify"}}</label>
<input name="mail_notify" type="checkbox" {{if .mail_notify}}checked{{end}}>
</div> </div>
</div> <div class="inline field {{if .Err_SMTPFrom}}error{{end}}">
</details> <label for="smtp_from">{{ctx.Locale.Tr "install.smtp_from"}}</label>
<input id="smtp_from" name="smtp_from" value="{{.smtp_from}}">
<!-- Server and other services --> <span class="help">{{ctx.Locale.TrString "install.smtp_from_helper"}}{{/* it contains lt/gt chars*/}}</span>
<details class="optional field">
<summary class="right-content tw-py-2{{if .Err_Services}} text red{{end}}">
{{ctx.Locale.Tr "install.server_service_title"}}
</summary>
<div class="inline field">
<div class="ui checkbox" id="offline-mode">
<label data-tooltip-content="{{ctx.Locale.Tr "install.offline_mode_popup"}}">{{ctx.Locale.Tr "install.offline_mode"}}</label>
<input name="offline_mode" type="checkbox" {{if .offline_mode}}checked{{end}}>
</div> </div>
</div> <div class="inline field {{if .Err_SMTPUser}}error{{end}}">
<div class="inline field"> <label for="smtp_user">{{ctx.Locale.Tr "install.mailer_user"}}</label>
<div class="ui checkbox" id="disable-gravatar"> <input id="smtp_user" name="smtp_user" value="{{.smtp_user}}">
<label data-tooltip-content="{{ctx.Locale.Tr "install.disable_gravatar_popup"}}">{{ctx.Locale.Tr "install.disable_gravatar"}}</label>
<input name="disable_gravatar" type="checkbox" {{if .disable_gravatar}}checked{{end}}>
</div> </div>
</div> <div class="inline field">
<div class="inline field"> <label for="smtp_passwd">{{ctx.Locale.Tr "install.mailer_password"}}</label>
<div class="ui checkbox" id="federated-avatar-lookup"> <input id="smtp_passwd" name="smtp_passwd" type="password" value="{{.smtp_passwd}}">
<label data-tooltip-content="{{ctx.Locale.Tr "install.federated_avatar_lookup_popup"}}">{{ctx.Locale.Tr "install.federated_avatar_lookup"}}</label>
<input name="enable_federated_avatar" type="checkbox" {{if .enable_federated_avatar}}checked{{end}}>
</div> </div>
</div> <div class="inline field">
<div class="inline field"> <div class="ui checkbox">
<div class="ui checkbox" id="enable-openid-signin"> <label>{{ctx.Locale.Tr "install.register_confirm"}}</label>
<label data-tooltip-content="{{ctx.Locale.Tr "install.openid_signin_popup"}}">{{ctx.Locale.Tr "install.openid_signin"}}</label> <input name="register_confirm" type="checkbox" {{if .register_confirm}}checked{{end}}>
<input name="enable_open_id_sign_in" type="checkbox" {{if .enable_open_id_sign_in}}checked{{end}}>
</div>
</div>
<div class="inline field">
<div class="ui checkbox" id="disable-registration">
<label data-tooltip-content="{{ctx.Locale.Tr "install.disable_registration_popup"}}">{{ctx.Locale.Tr "install.disable_registration"}}</label>
<input name="disable_registration" type="checkbox" {{if .disable_registration}}checked{{end}}>
</div>
</div>
<div class="inline field">
<div class="ui checkbox" id="allow-only-external-registration">
<label data-tooltip-content="{{ctx.Locale.Tr "install.allow_only_external_registration_popup"}}">{{ctx.Locale.Tr "install.allow_only_external_registration_popup"}}</label>
<input name="allow_only_external_registration" type="checkbox" {{if .allow_only_external_registration}}checked{{end}}>
</div>
</div>
<div class="inline field">
<div class="ui checkbox" id="enable-openid-signup">
<label data-tooltip-content="{{ctx.Locale.Tr "install.openid_signup_popup"}}">{{ctx.Locale.Tr "install.openid_signup"}}</label>
<input name="enable_open_id_sign_up" type="checkbox" {{if .enable_open_id_sign_up}}checked{{end}}>
</div>
</div>
<div class="inline field">
<div class="ui checkbox" id="enable-captcha">
<label data-tooltip-content="{{ctx.Locale.Tr "install.enable_captcha_popup"}}">{{ctx.Locale.Tr "install.enable_captcha"}}</label>
<input name="enable_captcha" type="checkbox" {{if .enable_captcha}}checked{{end}}>
</div>
</div>
<div class="inline field">
<div class="ui checkbox">
<label data-tooltip-content="{{ctx.Locale.Tr "install.require_sign_in_view_popup"}}">{{ctx.Locale.Tr "install.require_sign_in_view"}}</label>
<input name="require_sign_in_view" type="checkbox" {{if .require_sign_in_view}}checked{{end}}>
</div>
</div>
<div class="inline field">
<div class="ui checkbox">
<label data-tooltip-content="{{ctx.Locale.Tr "install.default_keep_email_private_popup"}}">{{ctx.Locale.Tr "install.default_keep_email_private"}}</label>
<input name="default_keep_email_private" type="checkbox" {{if .default_keep_email_private}}checked{{end}}>
</div>
</div>
<div class="inline field">
<div class="ui checkbox">
<label data-tooltip-content="{{ctx.Locale.Tr "install.default_allow_create_organization_popup"}}">{{ctx.Locale.Tr "install.default_allow_create_organization"}}</label>
<input name="default_allow_create_organization" type="checkbox" {{if .default_allow_create_organization}}checked{{end}}>
</div>
</div>
<div class="inline field">
<div class="ui checkbox">
<label data-tooltip-content="{{ctx.Locale.Tr "install.default_enable_timetracking_popup"}}">{{ctx.Locale.Tr "install.default_enable_timetracking"}}</label>
<input name="default_enable_timetracking" type="checkbox" {{if .default_enable_timetracking}}checked{{end}}>
</div>
</div>
<div class="inline field">
<label for="no_reply_address">{{ctx.Locale.Tr "install.no_reply_address"}}</label>
<input id="_no_reply_address" name="no_reply_address" value="{{.no_reply_address}}">
<span class="help">{{ctx.Locale.Tr "install.no_reply_address_helper"}}</span>
</div>
<div class="inline field">
<label for="password_algorithm">{{ctx.Locale.Tr "install.password_algorithm"}}</label>
<div class="ui selection dropdown">
<input id="password_algorithm" type="hidden" name="password_algorithm" value="{{.password_algorithm}}">
<div class="text">{{.password_algorithm}}</div>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
<div class="menu">
{{range .PasswordHashAlgorithms}}
<div class="item" data-value="{{.}}">{{.}}</div>
{{end}}
</div> </div>
</div> </div>
<span class="help">{{ctx.Locale.Tr "install.password_algorithm_helper"}}</span> <div class="inline field">
</div> <div class="ui checkbox">
</details> <label>{{ctx.Locale.Tr "install.mail_notify"}}</label>
<input name="mail_notify" type="checkbox" {{if .mail_notify}}checked{{end}}>
</div>
</div>
</details>
<!-- Admin --> <!-- Server and other services -->
<details class="optional field"> <details class="optional field">
<summary class="right-content tw-py-2{{if .Err_Admin}} text red{{end}}"> <summary class="right-content tw-py-2{{if .Err_Services}} text red{{end}}">
{{ctx.Locale.Tr "install.admin_title"}} {{ctx.Locale.Tr "install.server_service_title"}}
</summary> </summary>
<p class="center">{{ctx.Locale.Tr "install.admin_setting_desc"}}</p> <div class="inline field">
<div class="inline field {{if .Err_AdminName}}error{{end}}"> <div class="ui checkbox" id="offline-mode">
<label for="admin_name">{{ctx.Locale.Tr "install.admin_name"}}</label> <label data-tooltip-content="{{ctx.Locale.Tr "install.offline_mode_popup"}}">{{ctx.Locale.Tr "install.offline_mode"}}</label>
<input id="admin_name" name="admin_name" value="{{.admin_name}}"> <input name="offline_mode" type="checkbox" {{if .offline_mode}}checked{{end}}>
</div> </div>
<div class="inline field {{if .Err_AdminEmail}}error{{end}}"> </div>
<label for="admin_email">{{ctx.Locale.Tr "install.admin_email"}}</label> <div class="inline field">
<input id="admin_email" name="admin_email" type="email" value="{{.admin_email}}"> <div class="ui checkbox" id="disable-gravatar">
</div> <label data-tooltip-content="{{ctx.Locale.Tr "install.disable_gravatar_popup"}}">{{ctx.Locale.Tr "install.disable_gravatar"}}</label>
<div class="inline field {{if .Err_AdminPasswd}}error{{end}}"> <input name="disable_gravatar" type="checkbox" {{if .disable_gravatar}}checked{{end}}>
<label for="admin_passwd">{{ctx.Locale.Tr "install.admin_password"}}</label> </div>
<input id="admin_passwd" name="admin_passwd" type="password" autocomplete="new-password" value="{{.admin_passwd}}"> </div>
</div> <div class="inline field">
<div class="inline field {{if .Err_AdminPasswd}}error{{end}}"> <div class="ui checkbox" id="federated-avatar-lookup">
<label for="admin_confirm_passwd">{{ctx.Locale.Tr "install.confirm_password"}}</label> <label data-tooltip-content="{{ctx.Locale.Tr "install.federated_avatar_lookup_popup"}}">{{ctx.Locale.Tr "install.federated_avatar_lookup"}}</label>
<input id="admin_confirm_passwd" name="admin_confirm_passwd" autocomplete="new-password" type="password" value="{{.admin_confirm_passwd}}"> <input name="enable_federated_avatar" type="checkbox" {{if .enable_federated_avatar}}checked{{end}}>
</div> </div>
</details> </div>
<div class="inline field">
<div class="ui checkbox" id="enable-openid-signin">
<label data-tooltip-content="{{ctx.Locale.Tr "install.openid_signin_popup"}}">{{ctx.Locale.Tr "install.openid_signin"}}</label>
<input name="enable_open_id_sign_in" type="checkbox" {{if .enable_open_id_sign_in}}checked{{end}}>
</div>
</div>
<div class="inline field">
<div class="ui checkbox" id="disable-registration">
<label data-tooltip-content="{{ctx.Locale.Tr "install.disable_registration_popup"}}">{{ctx.Locale.Tr "install.disable_registration"}}</label>
<input name="disable_registration" type="checkbox" {{if .disable_registration}}checked{{end}}>
</div>
</div>
<div class="inline field">
<div class="ui checkbox" id="allow-only-external-registration">
<label data-tooltip-content="{{ctx.Locale.Tr "install.allow_only_external_registration_popup"}}">{{ctx.Locale.Tr "install.allow_only_external_registration_popup"}}</label>
<input name="allow_only_external_registration" type="checkbox" {{if .allow_only_external_registration}}checked{{end}}>
</div>
</div>
<div class="inline field">
<div class="ui checkbox" id="enable-openid-signup">
<label data-tooltip-content="{{ctx.Locale.Tr "install.openid_signup_popup"}}">{{ctx.Locale.Tr "install.openid_signup"}}</label>
<input name="enable_open_id_sign_up" type="checkbox" {{if .enable_open_id_sign_up}}checked{{end}}>
</div>
</div>
<div class="inline field">
<div class="ui checkbox" id="enable-captcha">
<label data-tooltip-content="{{ctx.Locale.Tr "install.enable_captcha_popup"}}">{{ctx.Locale.Tr "install.enable_captcha"}}</label>
<input name="enable_captcha" type="checkbox" {{if .enable_captcha}}checked{{end}}>
</div>
</div>
<div class="inline field">
<div class="ui checkbox">
<label data-tooltip-content="{{ctx.Locale.Tr "install.require_sign_in_view_popup"}}">{{ctx.Locale.Tr "install.require_sign_in_view"}}</label>
<input name="require_sign_in_view" type="checkbox" {{if .require_sign_in_view}}checked{{end}}>
</div>
</div>
<div class="inline field">
<div class="ui checkbox">
<label data-tooltip-content="{{ctx.Locale.Tr "install.default_keep_email_private_popup"}}">{{ctx.Locale.Tr "install.default_keep_email_private"}}</label>
<input name="default_keep_email_private" type="checkbox" {{if .default_keep_email_private}}checked{{end}}>
</div>
</div>
<div class="inline field">
<div class="ui checkbox">
<label data-tooltip-content="{{ctx.Locale.Tr "install.default_allow_create_organization_popup"}}">{{ctx.Locale.Tr "install.default_allow_create_organization"}}</label>
<input name="default_allow_create_organization" type="checkbox" {{if .default_allow_create_organization}}checked{{end}}>
</div>
</div>
<div class="inline field">
<div class="ui checkbox">
<label data-tooltip-content="{{ctx.Locale.Tr "install.default_enable_timetracking_popup"}}">{{ctx.Locale.Tr "install.default_enable_timetracking"}}</label>
<input name="default_enable_timetracking" type="checkbox" {{if .default_enable_timetracking}}checked{{end}}>
</div>
</div>
<div class="inline field">
<label for="no_reply_address">{{ctx.Locale.Tr "install.no_reply_address"}}</label>
<input id="_no_reply_address" name="no_reply_address" value="{{.no_reply_address}}">
<span class="help">{{ctx.Locale.Tr "install.no_reply_address_helper"}}</span>
</div>
<div class="inline field">
<label for="password_algorithm">{{ctx.Locale.Tr "install.password_algorithm"}}</label>
<div class="ui selection dropdown">
<input id="password_algorithm" type="hidden" name="password_algorithm" value="{{.password_algorithm}}">
<div class="text">{{.password_algorithm}}</div>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
<div class="menu">
{{range .PasswordHashAlgorithms}}
<div class="item" data-value="{{.}}">{{.}}</div>
{{end}}
</div>
</div>
<span class="help">{{ctx.Locale.Tr "install.password_algorithm_helper"}}</span>
</div>
</details>
<!-- Admin -->
<details class="optional field">
<summary class="right-content tw-py-2{{if .Err_Admin}} text red{{end}}">
{{ctx.Locale.Tr "install.admin_title"}}
</summary>
<p class="center">{{ctx.Locale.Tr "install.admin_setting_desc"}}</p>
<div class="inline field {{if .Err_AdminName}}error{{end}}">
<label for="admin_name">{{ctx.Locale.Tr "install.admin_name"}}</label>
<input id="admin_name" name="admin_name" value="{{.admin_name}}">
</div>
<div class="inline field {{if .Err_AdminEmail}}error{{end}}">
<label for="admin_email">{{ctx.Locale.Tr "install.admin_email"}}</label>
<input id="admin_email" name="admin_email" type="email" value="{{.admin_email}}">
</div>
<div class="inline field {{if .Err_AdminPasswd}}error{{end}}">
<label for="admin_passwd">{{ctx.Locale.Tr "install.admin_password"}}</label>
<input id="admin_passwd" name="admin_passwd" type="password" autocomplete="new-password" value="{{.admin_passwd}}">
</div>
<div class="inline field {{if .Err_AdminPasswd}}error{{end}}">
<label for="admin_confirm_passwd">{{ctx.Locale.Tr "install.confirm_password"}}</label>
<input id="admin_confirm_passwd" name="admin_confirm_passwd" autocomplete="new-password" type="password" value="{{.admin_confirm_passwd}}">
</div>
</details>
</div>
<div class="divider"></div>
{{if .EnvConfigKeys}} {{if .EnvConfigKeys}}
<!-- Environment Config --> <!-- Environment Config -->
@ -333,12 +336,11 @@
</div> </div>
{{end}} {{end}}
<div class="divider"></div>
<div class="inline field"> <div class="inline field">
<div class="right-content"> <div class="right-content">
These configuration options will be written into: {{.CustomConfFile}} These configuration options will be written into: {{.CustomConfFile}}
</div> </div>
<div class="right-content tw-mt-2"> <div class="tw-mt-4 tw-mb-2 tw-text-center">
<button class="ui primary button">{{ctx.Locale.Tr "install.install_btn_confirm"}}</button> <button class="ui primary button">{{ctx.Locale.Tr "install.install_btn_confirm"}}</button>
</div> </div>
</div> </div>

View File

@ -69,7 +69,7 @@
<div class="js-branch-tag-selector {{if .ContainerClasses}}{{.ContainerClasses}}{{end}}"> <div class="js-branch-tag-selector {{if .ContainerClasses}}{{.ContainerClasses}}{{end}}">
{{/* show dummy elements before Vue componment is mounted, this code must match the code in BranchTagSelector.vue */}} {{/* show dummy elements before Vue componment is mounted, this code must match the code in BranchTagSelector.vue */}}
<div class="ui dropdown custom branch-selector-dropdown"> <div class="ui dropdown custom branch-selector-dropdown ellipsis-items-nowrap">
<div class="ui button branch-dropdown-button"> <div class="ui button branch-dropdown-button">
<span class="flex-text-block gt-ellipsis"> <span class="flex-text-block gt-ellipsis">
{{if .release}} {{if .release}}

View File

@ -128,107 +128,109 @@
{{if .IsGenerated}}<div class="fork-flag">{{ctx.Locale.Tr "repo.generated_from"}} <a href="{{(.TemplateRepo ctx).Link}}">{{(.TemplateRepo ctx).FullName}}</a></div>{{end}} {{if .IsGenerated}}<div class="fork-flag">{{ctx.Locale.Tr "repo.generated_from"}} <a href="{{(.TemplateRepo ctx).Link}}">{{(.TemplateRepo ctx).FullName}}</a></div>{{end}}
</div> </div>
{{end}} {{end}}
<overflow-menu class="ui container secondary pointing tabular top attached borderless menu tw-pt-0 tw-my-0"> <div class="ui container">
{{if not (or .Repository.IsBeingCreated .Repository.IsBroken)}} <overflow-menu class="ui secondary pointing menu">
<div class="overflow-menu-items"> {{if not (or .Repository.IsBeingCreated .Repository.IsBroken)}}
{{if .Permission.CanRead ctx.Consts.RepoUnitTypeCode}} <div class="overflow-menu-items">
<a class="{{if .PageIsViewCode}}active {{end}}item" href="{{.RepoLink}}{{if and (ne .BranchName .Repository.DefaultBranch) (not $.PageIsWiki)}}/src/{{.BranchNameSubURL}}{{end}}"> {{if .Permission.CanRead ctx.Consts.RepoUnitTypeCode}}
{{svg "octicon-code"}} {{ctx.Locale.Tr "repo.code"}} <a class="{{if .PageIsViewCode}}active {{end}}item" href="{{.RepoLink}}{{if and (ne .BranchName .Repository.DefaultBranch) (not $.PageIsWiki)}}/src/{{.BranchNameSubURL}}{{end}}">
</a> {{svg "octicon-code"}} {{ctx.Locale.Tr "repo.code"}}
{{end}}
{{if .Permission.CanRead ctx.Consts.RepoUnitTypeIssues}}
<a class="{{if .PageIsIssueList}}active {{end}}item" href="{{.RepoLink}}/issues">
{{svg "octicon-issue-opened"}} {{ctx.Locale.Tr "repo.issues"}}
{{if .Repository.NumOpenIssues}}
<span class="ui small label">{{CountFmt .Repository.NumOpenIssues}}</span>
{{end}}
</a> </a>
{{end}}
{{if .Permission.CanRead ctx.Consts.RepoUnitTypeExternalTracker}}
<a class="{{if .PageIsIssueList}}active {{end}}item" href="{{.RepoExternalIssuesLink}}" target="_blank" rel="noopener noreferrer">
{{svg "octicon-link-external"}} {{ctx.Locale.Tr "repo.issues"}}
</a>
{{end}}
{{if and .Repository.CanEnablePulls (.Permission.CanRead ctx.Consts.RepoUnitTypePullRequests)}}
<a class="{{if .PageIsPullList}}active {{end}}item" href="{{.RepoLink}}/pulls">
{{svg "octicon-git-pull-request"}} {{ctx.Locale.Tr "repo.pulls"}}
{{if .Repository.NumOpenPulls}}
<span class="ui small label">{{CountFmt .Repository.NumOpenPulls}}</span>
{{end}}
</a>
{{end}}
{{if and .EnableActions (not .UnitActionsGlobalDisabled) (.Permission.CanRead ctx.Consts.RepoUnitTypeActions)}}
<a class="{{if .PageIsActions}}active {{end}}item" href="{{.RepoLink}}/actions">
{{svg "octicon-play"}} {{ctx.Locale.Tr "actions.actions"}}
{{if .Repository.NumOpenActionRuns}}
<span class="ui small label">{{CountFmt .Repository.NumOpenActionRuns}}</span>
{{end}}
</a>
{{end}}
{{if .Permission.CanRead ctx.Consts.RepoUnitTypePackages}}
<a href="{{.RepoLink}}/packages" class="{{if .IsPackagesPage}}active {{end}}item">
{{svg "octicon-package"}} {{ctx.Locale.Tr "packages.title"}}
</a>
{{end}}
{{$projectsUnit := .Repository.MustGetUnit $.Context ctx.Consts.RepoUnitTypeProjects}}
{{if and (not .UnitProjectsGlobalDisabled) (.Permission.CanRead ctx.Consts.RepoUnitTypeProjects) ($projectsUnit.ProjectsConfig.IsProjectsAllowed "repo")}}
<a href="{{.RepoLink}}/projects" class="{{if .IsProjectsPage}}active {{end}}item">
{{svg "octicon-project"}} {{ctx.Locale.Tr "repo.project_board"}}
{{if .Repository.NumOpenProjects}}
<span class="ui small label">{{CountFmt .Repository.NumOpenProjects}}</span>
{{end}}
</a>
{{end}}
{{if and (.Permission.CanRead ctx.Consts.RepoUnitTypeReleases) (not .IsEmptyRepo)}}
<a class="{{if or .PageIsReleaseList .PageIsTagList}}active {{end}}item" href="{{.RepoLink}}/releases">
{{svg "octicon-tag"}} {{ctx.Locale.Tr "repo.releases"}}
{{if .NumReleases}}
<span class="ui small label">{{CountFmt .NumReleases}}</span>
{{end}} {{end}}
</a>
{{end}}
{{if .Permission.CanRead ctx.Consts.RepoUnitTypeWiki}} {{if .Permission.CanRead ctx.Consts.RepoUnitTypeIssues}}
<a class="{{if .PageIsWiki}}active {{end}}item" href="{{.RepoLink}}/wiki"> <a class="{{if .PageIsIssueList}}active {{end}}item" href="{{.RepoLink}}/issues">
{{svg "octicon-book"}} {{ctx.Locale.Tr "repo.wiki"}} {{svg "octicon-issue-opened"}} {{ctx.Locale.Tr "repo.issues"}}
{{if .Repository.NumOpenIssues}}
<span class="ui small label">{{CountFmt .Repository.NumOpenIssues}}</span>
{{end}}
</a>
{{end}}
{{if .Permission.CanRead ctx.Consts.RepoUnitTypeExternalTracker}}
<a class="{{if .PageIsIssueList}}active {{end}}item" href="{{.RepoExternalIssuesLink}}" target="_blank" rel="noopener noreferrer">
{{svg "octicon-link-external"}} {{ctx.Locale.Tr "repo.issues"}}
</a>
{{end}}
{{if and .Repository.CanEnablePulls (.Permission.CanRead ctx.Consts.RepoUnitTypePullRequests)}}
<a class="{{if .PageIsPullList}}active {{end}}item" href="{{.RepoLink}}/pulls">
{{svg "octicon-git-pull-request"}} {{ctx.Locale.Tr "repo.pulls"}}
{{if .Repository.NumOpenPulls}}
<span class="ui small label">{{CountFmt .Repository.NumOpenPulls}}</span>
{{end}}
</a>
{{end}}
{{if and .EnableActions (not .UnitActionsGlobalDisabled) (.Permission.CanRead ctx.Consts.RepoUnitTypeActions)}}
<a class="{{if .PageIsActions}}active {{end}}item" href="{{.RepoLink}}/actions">
{{svg "octicon-play"}} {{ctx.Locale.Tr "actions.actions"}}
{{if .Repository.NumOpenActionRuns}}
<span class="ui small label">{{CountFmt .Repository.NumOpenActionRuns}}</span>
{{end}}
</a>
{{end}}
{{if .Permission.CanRead ctx.Consts.RepoUnitTypePackages}}
<a href="{{.RepoLink}}/packages" class="{{if .IsPackagesPage}}active {{end}}item">
{{svg "octicon-package"}} {{ctx.Locale.Tr "packages.title"}}
</a>
{{end}}
{{$projectsUnit := .Repository.MustGetUnit $.Context ctx.Consts.RepoUnitTypeProjects}}
{{if and (not .UnitProjectsGlobalDisabled) (.Permission.CanRead ctx.Consts.RepoUnitTypeProjects) ($projectsUnit.ProjectsConfig.IsProjectsAllowed "repo")}}
<a href="{{.RepoLink}}/projects" class="{{if .IsProjectsPage}}active {{end}}item">
{{svg "octicon-project"}} {{ctx.Locale.Tr "repo.project_board"}}
{{if .Repository.NumOpenProjects}}
<span class="ui small label">{{CountFmt .Repository.NumOpenProjects}}</span>
{{end}}
</a>
{{end}}
{{if and (.Permission.CanRead ctx.Consts.RepoUnitTypeReleases) (not .IsEmptyRepo)}}
<a class="{{if or .PageIsReleaseList .PageIsTagList}}active {{end}}item" href="{{.RepoLink}}/releases">
{{svg "octicon-tag"}} {{ctx.Locale.Tr "repo.releases"}}
{{if .NumReleases}}
<span class="ui small label">{{CountFmt .NumReleases}}</span>
{{end}}
</a> </a>
{{end}} {{end}}
{{if .Permission.CanRead ctx.Consts.RepoUnitTypeExternalWiki}} {{if .Permission.CanRead ctx.Consts.RepoUnitTypeWiki}}
<a class="item" href="{{(.Repository.MustGetUnit $.Context ctx.Consts.RepoUnitTypeExternalWiki).ExternalWikiConfig.ExternalWikiURL}}" target="_blank" rel="noopener noreferrer"> <a class="{{if .PageIsWiki}}active {{end}}item" href="{{.RepoLink}}/wiki">
{{svg "octicon-link-external"}} {{ctx.Locale.Tr "repo.wiki"}} {{svg "octicon-book"}} {{ctx.Locale.Tr "repo.wiki"}}
</a> </a>
{{end}} {{end}}
{{if and (.Permission.CanReadAny ctx.Consts.RepoUnitTypePullRequests ctx.Consts.RepoUnitTypeIssues ctx.Consts.RepoUnitTypeReleases) (not .IsEmptyRepo)}} {{if .Permission.CanRead ctx.Consts.RepoUnitTypeExternalWiki}}
<a class="{{if .PageIsActivity}}active {{end}}item" href="{{.RepoLink}}/activity"> <a class="item" href="{{(.Repository.MustGetUnit $.Context ctx.Consts.RepoUnitTypeExternalWiki).ExternalWikiConfig.ExternalWikiURL}}" target="_blank" rel="noopener noreferrer">
{{svg "octicon-pulse"}} {{ctx.Locale.Tr "repo.activity"}} {{svg "octicon-link-external"}} {{ctx.Locale.Tr "repo.wiki"}}
</a> </a>
{{end}} {{end}}
{{template "custom/extra_tabs" .}} {{if and (.Permission.CanReadAny ctx.Consts.RepoUnitTypePullRequests ctx.Consts.RepoUnitTypeIssues ctx.Consts.RepoUnitTypeReleases) (not .IsEmptyRepo)}}
<a class="{{if .PageIsActivity}}active {{end}}item" href="{{.RepoLink}}/activity">
{{svg "octicon-pulse"}} {{ctx.Locale.Tr "repo.activity"}}
</a>
{{end}}
{{if .Permission.IsAdmin}} {{template "custom/extra_tabs" .}}
<span class="item-flex-space"></span>
{{if .Permission.IsAdmin}}
<span class="item-flex-space"></span>
<a class="{{if .PageIsRepoSettings}}active {{end}} item" href="{{.RepoLink}}/settings">
{{svg "octicon-tools"}} {{ctx.Locale.Tr "repo.settings"}}
</a>
{{end}}
</div>
{{else if .Permission.IsAdmin}}
<div class="overflow-menu-items">
<a class="{{if .PageIsRepoSettings}}active {{end}} item" href="{{.RepoLink}}/settings"> <a class="{{if .PageIsRepoSettings}}active {{end}} item" href="{{.RepoLink}}/settings">
{{svg "octicon-tools"}} {{ctx.Locale.Tr "repo.settings"}} {{svg "octicon-tools"}} {{ctx.Locale.Tr "repo.settings"}}
</a> </a>
{{end}} </div>
</div> {{end}}
{{else if .Permission.IsAdmin}} </overflow-menu>
<div class="overflow-menu-items"> </div>
<a class="{{if .PageIsRepoSettings}}active {{end}} item" href="{{.RepoLink}}/settings">
{{svg "octicon-tools"}} {{ctx.Locale.Tr "repo.settings"}}
</a>
</div>
{{end}}
</overflow-menu>
<div class="ui tabs divider"></div> <div class="ui tabs divider"></div>
</div> </div>

View File

@ -4,7 +4,7 @@
<form method="post" action="{{$.RepoLink}}/issues/{{.Issue.Index}}/ref" id="update_issueref_form"> <form method="post" action="{{$.RepoLink}}/issues/{{.Issue.Index}}/ref" id="update_issueref_form">
{{$.CsrfTokenHtml}} {{$.CsrfTokenHtml}}
</form> </form>
<div class="ui dropdown select-branch branch-selector-dropdown {{if not .HasIssuesOrPullsWritePermission}}disabled{{end}}" <div class="ui dropdown select-branch branch-selector-dropdown ellipsis-items-nowrap {{if not .HasIssuesOrPullsWritePermission}}disabled{{end}}"
data-no-results="{{ctx.Locale.Tr "no_results_found"}}" data-no-results="{{ctx.Locale.Tr "no_results_found"}}"
{{if not .Issue}}data-for-new-issue="true"{{end}} {{if not .Issue}}data-for-new-issue="true"{{end}}
> >

View File

@ -7,7 +7,7 @@
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<div class="field"> <div class="field">
<label><strong>{{ctx.Locale.Tr "repository"}}</strong></label> <label><strong>{{ctx.Locale.Tr "repository"}}</strong></label>
<div class="ui search selection dropdown issue_reference_repository_search"> <div class="ui search selection dropdown issue_reference_repository_search ellipsis-items-nowrap">
<div class="default text gt-ellipsis">{{.Repository.FullName}}</div> <div class="default text gt-ellipsis">{{.Repository.FullName}}</div>
<div class="menu"></div> <div class="menu"></div>
</div> </div>

View File

@ -345,7 +345,7 @@
<div class="inline field"> <div class="inline field">
{{$unitInternalWiki := .Repository.MustGetUnit ctx ctx.Consts.RepoUnitTypeWiki}} {{$unitInternalWiki := .Repository.MustGetUnit ctx ctx.Consts.RepoUnitTypeWiki}}
<label>{{ctx.Locale.Tr "repo.settings.default_wiki_everyone_access"}}</label> <label>{{ctx.Locale.Tr "repo.settings.default_wiki_everyone_access"}}</label>
<select name="default_wiki_everyone_access" class="ui dropdown"> <select name="default_wiki_everyone_access" class="ui selection dropdown">
{{/* everyone access mode is different from others, none means it is unset and won't be applied */}} {{/* everyone access mode is different from others, none means it is unset and won't be applied */}}
<option value="none" {{Iif (eq $unitInternalWiki.EveryoneAccessMode 0) "selected"}}>{{ctx.Locale.Tr "settings.permission_not_set"}}</option> <option value="none" {{Iif (eq $unitInternalWiki.EveryoneAccessMode 0) "selected"}}>{{ctx.Locale.Tr "settings.permission_not_set"}}</option>
<option value="read" {{Iif (eq $unitInternalWiki.EveryoneAccessMode 1) "selected"}}>{{ctx.Locale.Tr "settings.permission_read"}}</option> <option value="read" {{Iif (eq $unitInternalWiki.EveryoneAccessMode 1) "selected"}}>{{ctx.Locale.Tr "settings.permission_read"}}</option>

View File

@ -695,7 +695,7 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string
defer tests.PrintCurrentTest(t)() defer tests.PrintCurrentTest(t)()
// skip this test if git version is low // skip this test if git version is low
if git.CheckGitVersionAtLeast("2.29") != nil { if !git.DefaultFeatures().SupportProcReceive {
return return
} }

View File

@ -342,8 +342,6 @@ a.label,
.ui.dropdown .menu > .item { .ui.dropdown .menu > .item {
color: var(--color-text); color: var(--color-text);
overflow: hidden;
text-overflow: ellipsis;
} }
.ui.dropdown .menu > .item:hover { .ui.dropdown .menu > .item:hover {
@ -374,7 +372,6 @@ a.label,
.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) {
@ -1342,7 +1339,11 @@ table th[data-sortt-desc] .svg {
align-items: center; align-items: center;
gap: .25rem; gap: .25rem;
vertical-align: middle; vertical-align: middle;
min-width: 0; min-width: 0; /* make ellipsis work */
}
.ui.ui.dropdown.selection {
min-width: 14em; /* match the default min width */
} }
.ui.dropdown .ui.label .svg { .ui.dropdown .ui.label .svg {
@ -1369,3 +1370,16 @@ table th[data-sortt-desc] .svg {
gap: .5rem; gap: .5rem;
min-width: 0; min-width: 0;
} }
.ui.dropdown.ellipsis-items-nowrap > .text {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.ellipsis-items-nowrap > .item,
.ui.dropdown.ellipsis-items-nowrap .menu > .item {
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
}

View File

@ -448,6 +448,10 @@ textarea:focus,
} }
} }
.ui.form .field > .selection.dropdown {
min-width: 14em; /* matches the default min width */
}
.new.webhook form .help { .new.webhook form .help {
margin-left: 25px; margin-left: 25px;
} }

View File

@ -13,8 +13,7 @@
.page-content.install .ui.form .field > .help, .page-content.install .ui.form .field > .help,
.page-content.install .ui.form .field > .ui.checkbox:first-child, .page-content.install .ui.form .field > .ui.checkbox:first-child,
.page-content.install .ui.form .field > .right-content { .page-content.install .ui.form .field > .right-content {
margin-left: 30%; margin-left: calc(30% + 5px);
padding-left: 5px;
width: auto; width: auto;
} }
@ -24,10 +23,11 @@
} }
.page-content.install form.ui.form details.optional.field[open] { .page-content.install form.ui.form details.optional.field[open] {
border-bottom: 1px dashed var(--color-secondary);
padding-bottom: 10px; padding-bottom: 10px;
} }
.page-content.install form.ui.form details.optional.field[open]:not(:last-child) {
border-bottom: 1px dashed var(--color-secondary);
}
.page-content.install form.ui.form details.optional.field[open] summary { .page-content.install form.ui.form details.optional.field[open] summary {
margin-bottom: 10px; margin-bottom: 10px;
} }

View File

@ -41,7 +41,7 @@ input[type="radio"] {
.ui.checkbox label, .ui.checkbox label,
.ui.radio.checkbox label { .ui.radio.checkbox label {
margin-left: 1.85714em; margin-left: 20px;
} }
.ui.checkbox + label { .ui.checkbox + label {

View File

@ -2,26 +2,20 @@
unused rules here after refactoring, please remove them. */ unused rules here after refactoring, please remove them. */
.ui.container { .ui.container {
display: block;
max-width: 100%;
}
.ui.fluid.container {
width: 100%;
}
.ui[class*="center aligned"].container {
text-align: center;
}
/* overwrite width of containers inside the main page content div (div with class "page-content") */
.page-content .ui.ui.ui.container:not(.fluid) {
width: 1280px; width: 1280px;
max-width: calc(100% - calc(2 * var(--page-margin-x))); max-width: calc(100% - calc(2 * var(--page-margin-x)));
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
} }
.ui.fluid.container {
width: 100%;
}
.ui.container.fluid.padded { .ui.container.fluid.padded {
padding: 0 var(--page-margin-x); padding: 0 var(--page-margin-x);
} }
.ui[class*="center aligned"].container {
text-align: center;
}

View File

@ -2860,6 +2860,10 @@ tbody.commit-list {
margin-top: 4px; margin-top: 4px;
} }
.ui.dropdown.branch-selector-dropdown .scrolling.menu {
max-width: min(400px, 90vw);
}
.branch-selector-dropdown .branch-dropdown-button { .branch-selector-dropdown .branch-dropdown-button {
margin: 0; margin: 0;
max-width: 340px; max-width: 340px;
@ -2909,6 +2913,8 @@ tbody.commit-list {
} }
.branch-selector-dropdown .menu .item .rss-icon { .branch-selector-dropdown .menu .item .rss-icon {
position: absolute;
right: 4px;
visibility: hidden; /* only show RSS icon on hover */ visibility: hidden; /* only show RSS icon on hover */
} }

View File

@ -6,13 +6,20 @@
// This file must be imported before any lazy-loading is being attempted. // This file must be imported before any lazy-loading is being attempted.
__webpack_public_path__ = `${window.config?.assetUrlPrefix ?? '/assets'}/`; __webpack_public_path__ = `${window.config?.assetUrlPrefix ?? '/assets'}/`;
export function showGlobalErrorMessage(msg) { function shouldIgnoreError(err) {
const pageContent = document.querySelector('.page-content'); const ignorePatterns = [
if (!pageContent) return; '/assets/js/monaco.', // https://github.com/go-gitea/gitea/issues/30861 , https://github.com/microsoft/monaco-editor/issues/4496
];
for (const pattern of ignorePatterns) {
if (err.stack?.includes(pattern)) return true;
}
return false;
}
// compact the message to a data attribute to avoid too many duplicated messages export function showGlobalErrorMessage(msg) {
const msgCompact = msg.replace(/\W/g, '').trim(); const msgContainer = document.querySelector('.page-content') ?? document.body;
let msgDiv = pageContent.querySelector(`.js-global-error[data-global-error-msg-compact="${msgCompact}"]`); const msgCompact = msg.replace(/\W/g, '').trim(); // compact the message to a data attribute to avoid too many duplicated messages
let msgDiv = msgContainer.querySelector(`.js-global-error[data-global-error-msg-compact="${msgCompact}"]`);
if (!msgDiv) { if (!msgDiv) {
const el = document.createElement('div'); const el = document.createElement('div');
el.innerHTML = `<div class="ui container negative message center aligned js-global-error tw-mt-[15px] tw-whitespace-pre-line"></div>`; el.innerHTML = `<div class="ui container negative message center aligned js-global-error tw-mt-[15px] tw-whitespace-pre-line"></div>`;
@ -23,7 +30,7 @@ export function showGlobalErrorMessage(msg) {
msgDiv.setAttribute(`data-global-error-msg-compact`, msgCompact); msgDiv.setAttribute(`data-global-error-msg-compact`, msgCompact);
msgDiv.setAttribute(`data-global-error-msg-count`, msgCount.toString()); msgDiv.setAttribute(`data-global-error-msg-count`, msgCount.toString());
msgDiv.textContent = msg + (msgCount > 1 ? ` (${msgCount})` : ''); msgDiv.textContent = msg + (msgCount > 1 ? ` (${msgCount})` : '');
pageContent.prepend(msgDiv); msgContainer.prepend(msgDiv);
} }
/** /**
@ -52,10 +59,12 @@ function processWindowErrorEvent({error, reason, message, type, filename, lineno
if (runModeIsProd) return; if (runModeIsProd) return;
} }
// If the error stack trace does not include the base URL of our script assets, it likely came if (err instanceof Error) {
// from a browser extension or inline script. Do not show such errors in production. // If the error stack trace does not include the base URL of our script assets, it likely came
if (err instanceof Error && !err.stack?.includes(assetBaseUrl) && runModeIsProd) { // from a browser extension or inline script. Do not show such errors in production.
return; if (!err.stack?.includes(assetBaseUrl) && runModeIsProd) return;
// Ignore some known errors that are unable to fix
if (shouldIgnoreError(err)) return;
} }
let msg = err?.message ?? message; let msg = err?.message ?? message;

View File

@ -246,7 +246,7 @@ export function initRepoBranchTagSelector(selector) {
export default sfc; // activate IDE's Vue plugin export default sfc; // activate IDE's Vue plugin
</script> </script>
<template> <template>
<div class="ui dropdown custom branch-selector-dropdown"> <div class="ui dropdown custom branch-selector-dropdown ellipsis-items-nowrap">
<div class="ui button branch-dropdown-button" @click="menuVisible = !menuVisible" @keyup.enter="menuVisible = !menuVisible"> <div class="ui button branch-dropdown-button" @click="menuVisible = !menuVisible" @keyup.enter="menuVisible = !menuVisible">
<span class="flex-text-block gt-ellipsis"> <span class="flex-text-block gt-ellipsis">
<template v-if="release">{{ textReleaseCompare }}</template> <template v-if="release">{{ textReleaseCompare }}</template>
@ -280,7 +280,7 @@ export default sfc; // activate IDE's Vue plugin
<div class="ui label" v-if="item.name===repoDefaultBranch && mode === 'branches'"> <div class="ui label" v-if="item.name===repoDefaultBranch && mode === 'branches'">
{{ textDefaultBranchLabel }} {{ textDefaultBranchLabel }}
</div> </div>
<a v-show="enableFeed && mode === 'branches'" role="button" class="rss-icon tw-float-right" :href="rssURLPrefix + item.url" target="_blank" @click.stop> <a v-show="enableFeed && mode === 'branches'" role="button" class="rss-icon" :href="rssURLPrefix + item.url" target="_blank" @click.stop>
<!-- creating a lot of Vue component is pretty slow, so we use a static SVG here --> <!-- creating a lot of Vue component is pretty slow, so we use a static SVG here -->
<svg width="14" height="14" class="svg octicon-rss"><use href="#svg-symbol-octicon-rss"/></svg> <svg width="14" height="14" class="svg octicon-rss"><use href="#svg-symbol-octicon-rss"/></svg>
</a> </a>

View File

@ -67,7 +67,7 @@ export default {
const weekValues = Object.values(this.data); const weekValues = Object.values(this.data);
const start = weekValues[0].week; const start = weekValues[0].week;
const end = firstStartDateAfterDate(new Date()); const end = firstStartDateAfterDate(new Date());
const startDays = startDaysBetween(new Date(start), new Date(end)); const startDays = startDaysBetween(start, end);
this.data = fillEmptyStartDaysWithZeroes(startDays, this.data); this.data = fillEmptyStartDaysWithZeroes(startDays, this.data);
this.errorText = ''; this.errorText = '';
} else { } else {

View File

@ -114,7 +114,7 @@ export default {
const weekValues = Object.values(total.weeks); const weekValues = Object.values(total.weeks);
this.xAxisStart = weekValues[0].week; this.xAxisStart = weekValues[0].week;
this.xAxisEnd = firstStartDateAfterDate(new Date()); this.xAxisEnd = firstStartDateAfterDate(new Date());
const startDays = startDaysBetween(new Date(this.xAxisStart), new Date(this.xAxisEnd)); const startDays = startDaysBetween(this.xAxisStart, this.xAxisEnd);
total.weeks = fillEmptyStartDaysWithZeroes(startDays, total.weeks); total.weeks = fillEmptyStartDaysWithZeroes(startDays, total.weeks);
this.xAxisMin = this.xAxisStart; this.xAxisMin = this.xAxisStart;
this.xAxisMax = this.xAxisEnd; this.xAxisMax = this.xAxisEnd;

View File

@ -62,7 +62,7 @@ export default {
const data = await response.json(); const data = await response.json();
const start = Object.values(data)[0].week; const start = Object.values(data)[0].week;
const end = firstStartDateAfterDate(new Date()); const end = firstStartDateAfterDate(new Date());
const startDays = startDaysBetween(new Date(start), new Date(end)); const startDays = startDaysBetween(start, end);
this.data = fillEmptyStartDaysWithZeroes(startDays, data).slice(-52); this.data = fillEmptyStartDaysWithZeroes(startDays, data).slice(-52);
this.errorText = ''; this.errorText = '';
} else { } else {

View File

@ -124,8 +124,8 @@ export function initRepoIssueSidebarList() {
return; return;
} }
filteredResponse.results.push({ filteredResponse.results.push({
name: `#${issue.number} ${htmlEscape(issue.title) name: `<div class="gt-ellipsis">#${issue.number} ${htmlEscape(issue.title)}</div>
}<div class="text small gt-word-break">${htmlEscape(issue.repository.full_name)}</div>`, <div class="text small gt-word-break">${htmlEscape(issue.repository.full_name)}</div>`,
value: issue.id, value: issue.id,
}); });
}); });
@ -299,23 +299,23 @@ export function initRepoPullRequestMergeInstruction() {
export function initRepoPullRequestAllowMaintainerEdit() { export function initRepoPullRequestAllowMaintainerEdit() {
const wrapper = document.getElementById('allow-edits-from-maintainers'); const wrapper = document.getElementById('allow-edits-from-maintainers');
if (!wrapper) return; if (!wrapper) return;
const checkbox = wrapper.querySelector('input[type="checkbox"]');
wrapper.querySelector('input[type="checkbox"]')?.addEventListener('change', async (e) => { checkbox.addEventListener('input', async () => {
const checked = e.target.checked;
const url = `${wrapper.getAttribute('data-url')}/set_allow_maintainer_edit`; const url = `${wrapper.getAttribute('data-url')}/set_allow_maintainer_edit`;
wrapper.classList.add('is-loading'); wrapper.classList.add('is-loading');
e.target.disabled = true;
try { try {
const response = await POST(url, {data: {allow_maintainer_edit: checked}}); const resp = await POST(url, {data: new URLSearchParams({allow_maintainer_edit: checkbox.checked})});
if (!response.ok) { if (!resp.ok) {
throw new Error('Failed to update maintainer edit permission'); throw new Error('Failed to update maintainer edit permission');
} }
const data = await resp.json();
checkbox.checked = data.allow_maintainer_edit;
} catch (error) { } catch (error) {
checkbox.checked = !checkbox.checked;
console.error(error); console.error(error);
showTemporaryTooltip(wrapper, wrapper.getAttribute('data-prompt-error')); showTemporaryTooltip(wrapper, wrapper.getAttribute('data-prompt-error'));
} finally { } finally {
wrapper.classList.remove('is-loading'); wrapper.classList.remove('is-loading');
e.target.disabled = false;
} }
}); });
} }

View File

@ -1,25 +1,30 @@
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc.js';
import {getCurrentLocale} from '../utils.js'; import {getCurrentLocale} from '../utils.js';
// Returns an array of millisecond-timestamps of start-of-week days (Sundays) dayjs.extend(utc);
export function startDaysBetween(startDate, endDate) {
// Ensure the start date is a Sunday
while (startDate.getDay() !== 0) {
startDate.setDate(startDate.getDate() + 1);
}
const start = dayjs(startDate); /**
const end = dayjs(endDate); * Returns an array of millisecond-timestamps of start-of-week days (Sundays)
const startDays = []; *
* @param startConfig The start date. Can take any type that `Date` accepts.
* @param endConfig The end date. Can take any type that `Date` accepts.
*/
export function startDaysBetween(startDate, endDate) {
const start = dayjs.utc(startDate);
const end = dayjs.utc(endDate);
let current = start; let current = start;
// Ensure the start date is a Sunday
while (current.day() !== 0) {
current = current.add(1, 'day');
}
const startDays = [];
while (current.isBefore(end)) { while (current.isBefore(end)) {
startDays.push(current.valueOf()); startDays.push(current.valueOf());
// we are adding 7 * 24 hours instead of 1 week because we don't want current = current.add(1, 'week');
// date library to use local time zone to calculate 1 week from now.
// local time zone is problematic because of daylight saving time (dst)
// used on some countries
current = current.add(7 * 24, 'hour');
} }
return startDays; return startDays;
@ -29,10 +34,10 @@ export function firstStartDateAfterDate(inputDate) {
if (!(inputDate instanceof Date)) { if (!(inputDate instanceof Date)) {
throw new Error('Invalid date'); throw new Error('Invalid date');
} }
const dayOfWeek = inputDate.getDay(); const dayOfWeek = inputDate.getUTCDay();
const daysUntilSunday = 7 - dayOfWeek; const daysUntilSunday = 7 - dayOfWeek;
const resultDate = new Date(inputDate.getTime()); const resultDate = new Date(inputDate.getTime());
resultDate.setDate(resultDate.getDate() + daysUntilSunday); resultDate.setUTCDate(resultDate.getUTCDate() + daysUntilSunday);
return resultDate.valueOf(); return resultDate.valueOf();
} }