Compare commits

..

No commits in common. "f2f0fb43e04c577d001da453f994d232505f3e6b" and "74aa44625bb0cef4430385f9e30e211dc02b55e9" have entirely different histories.

82 changed files with 606 additions and 945 deletions

View File

@ -773,6 +773,109 @@ steps:
- name: deps - name: deps
path: /go path: /go
---
kind: pipeline
name: update_translations
platform:
os: linux
arch: arm64
trigger:
branch:
- main
event:
- cron
cron:
- update_translations
steps:
- name: download
image: jonasfranz/crowdin
pull: always
settings:
download: true
export_dir: options/locale/
ignore_branch: true
project_identifier: gitea
environment:
CROWDIN_KEY:
from_secret: crowdin_key
- name: update
image: alpine:3.17
pull: always
commands:
- ./build/update-locales.sh
- name: push
image: appleboy/drone-git-push
pull: always
settings:
author_email: "teabot@gitea.io"
author_name: GiteaBot
branch: main
commit: true
commit_message: "[skip ci] Updated translations via Crowdin"
remote: "git@github.com:go-gitea/gitea.git"
environment:
DRONE_COMMIT_AUTHOR_EMAIL: "teabot@gitea.io"
DRONE_COMMIT_AUTHOR: GiteaBot
GIT_PUSH_SSH_KEY:
from_secret: git_push_ssh_key
- name: upload_translations
image: jonasfranz/crowdin
pull: always
settings:
files:
locale_en-US.ini: options/locale/locale_en-US.ini
ignore_branch: true
project_identifier: gitea
environment:
CROWDIN_KEY:
from_secret: crowdin_key
---
kind: pipeline
type: docker
name: update_gitignore_and_licenses
platform:
os: linux
arch: arm64
trigger:
branch:
- main
event:
- cron
cron:
- update_gitignore_and_licenses
steps:
- name: download
image: gitea/test_env:linux-1.20-amd64
pull: always
commands:
- timeout -s ABRT 40m make generate-license generate-gitignore
- name: push
image: appleboy/drone-git-push
pull: always
settings:
author_email: "teabot@gitea.io"
author_name: "GiteaBot"
branch: main
commit: true
commit_message: "[skip ci] Updated licenses and gitignores"
remote: "git@github.com:go-gitea/gitea.git"
environment:
DRONE_COMMIT_AUTHOR_EMAIL: "teabot@gitea.io"
DRONE_COMMIT_AUTHOR: "GiteaBot"
GIT_PUSH_SSH_KEY:
from_secret: git_push_ssh_key
--- ---
kind: pipeline kind: pipeline
type: docker type: docker

View File

@ -1,28 +0,0 @@
on:
schedule:
# weekly on Monday at 0:07 UTC
- cron: "7 0 * * 1"
name: Update licenses and gitignores
jobs:
cron:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: '>=1.20.1'
- name: update licenses and gitignores
run: timeout -s ABRT 40m make generate-license generate-gitignore
- name: push translations to repo
uses: appleboy/git-push-action@v0.0.2
with:
author_email: "teabot@gitea.io"
author_name: GiteaBot
branch: main
commit: true
commit_message: "[skip ci] Updated licenses and gitignores"
remote: "git@github.com:go-gitea/gitea.git"
ssh_key: ${{ secrets.DEPLOY_KEY }}

View File

@ -1,47 +0,0 @@
on:
schedule:
- cron: "7 0 * * *" # every day at 0:07 UTC
name: Pull translations from Crowdin
jobs:
crowdin_pull:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: download from crowdin
uses: docker://jonasfranz/crowdin
env:
CROWDIN_KEY: ${{ secrets.CROWDIN_KEY }}
PLUGIN_DOWNLOAD: true
PLUGIN_EXPORT_DIR: options/locale/
PLUGIN_IGNORE_BRANCH: true
PLUGIN_PROJECT_IDENTIFIER: gitea
- name: update locales
run: ./build/update-locales.sh
- name: push translations to repo
uses: appleboy/git-push-action@v0.0.2
with:
author_email: "teabot@gitea.io"
author_name: GiteaBot
branch: main
commit: true
commit_message: "[skip ci] Updated translations via Crowdin"
remote: "git@github.com:go-gitea/gitea.git"
ssh_key: ${{ secrets.DEPLOY_KEY }}
crowdin_push:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: push translations to crowdin
uses: docker://jonasfranz/crowdin
env:
CROWDIN_KEY: ${{ secrets.CROWDIN_KEY }}
PLUGIN_UPLOAD: true
PLUGIN_IGNORE_BRANCH: true
PLUGIN_PROJECT_IDENTIFIER: gitea
PLUGIN_FILES: |
locale_en-US.ini: options/locale/locale_en-US.ini
PLUGIN_BRANCH: main

View File

@ -24,6 +24,7 @@ import (
"github.com/felixge/fgprof" "github.com/felixge/fgprof"
"github.com/urfave/cli" "github.com/urfave/cli"
ini "gopkg.in/ini.v1"
) )
// PIDFile could be set from build tag // PIDFile could be set from build tag
@ -222,10 +223,9 @@ func setPort(port string) error {
defaultLocalURL += ":" + setting.HTTPPort + "/" defaultLocalURL += ":" + setting.HTTPPort + "/"
// Save LOCAL_ROOT_URL if port changed // Save LOCAL_ROOT_URL if port changed
setting.CfgProvider.Section("server").Key("LOCAL_ROOT_URL").SetValue(defaultLocalURL) setting.CreateOrAppendToCustomConf("server.LOCAL_ROOT_URL", func(cfg *ini.File) {
if err := setting.CfgProvider.Save(); err != nil { cfg.Section("server").Key("LOCAL_ROOT_URL").SetValue(defaultLocalURL)
return fmt.Errorf("Failed to save config file: %v", err) })
}
} }
return nil return nil
} }

View File

@ -10,15 +10,6 @@
# upgrade.sh 1.15.10 # upgrade.sh 1.15.10
# giteahome=/opt/gitea giteaconf=$giteahome/app.ini upgrade.sh # giteahome=/opt/gitea giteaconf=$giteahome/app.ini upgrade.sh
# Check if gitea service is running
if ! pidof gitea &> /dev/null; then
echo "Error: gitea is not running."
exit 1
fi
# Continue with rest of the script if gitea is running
echo "Gitea is running. Continuing with rest of script..."
# apply variables from environment # apply variables from environment
: "${giteabin:="/usr/local/bin/gitea"}" : "${giteabin:="/usr/local/bin/gitea"}"
: "${giteahome:="/var/lib/gitea"}" : "${giteahome:="/var/lib/gitea"}"

View File

@ -16,6 +16,7 @@ import (
_ "code.gitea.io/gitea/models" _ "code.gitea.io/gitea/models"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/ini.v1"
) )
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
@ -26,7 +27,7 @@ func TestMain(m *testing.M) {
func TestBleveSearchIssues(t *testing.T) { func TestBleveSearchIssues(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
setting.CfgProvider = setting.NewEmptyConfigProvider() setting.CfgProvider = ini.Empty()
tmpIndexerDir := t.TempDir() tmpIndexerDir := t.TempDir()

View File

@ -18,6 +18,7 @@ import (
_ "code.gitea.io/gitea/models" _ "code.gitea.io/gitea/models"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/ini.v1"
) )
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
@ -28,7 +29,7 @@ func TestMain(m *testing.M) {
func TestRepoStatsIndex(t *testing.T) { func TestRepoStatsIndex(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
setting.CfgProvider = setting.NewEmptyConfigProvider() setting.CfgProvider = ini.Empty()
setting.LoadQueueSettings() setting.LoadQueueSettings()

View File

@ -4,157 +4,20 @@
package setting package setting
import ( import (
"fmt"
"os"
"path/filepath"
"strings"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"
ini "gopkg.in/ini.v1" ini "gopkg.in/ini.v1"
) )
type ConfigSection interface {
Name() string
MapTo(interface{}) error
HasKey(key string) bool
NewKey(name, value string) (*ini.Key, error)
Key(key string) *ini.Key
Keys() []*ini.Key
ChildSections() []*ini.Section
}
// ConfigProvider represents a config provider // ConfigProvider represents a config provider
type ConfigProvider interface { type ConfigProvider interface {
Section(section string) ConfigSection Section(section string) *ini.Section
NewSection(name string) (ConfigSection, error) NewSection(name string) (*ini.Section, error)
GetSection(name string) (ConfigSection, error) GetSection(name string) (*ini.Section, error)
DeleteSection(name string) error
Save() error
}
type iniFileConfigProvider struct {
*ini.File
filepath string // the ini file path
newFile bool // whether the file has not existed previously
allowEmpty bool // whether not finding configuration files is allowed (only true for the tests)
}
// NewEmptyConfigProvider create a new empty config provider
func NewEmptyConfigProvider() ConfigProvider {
cp, _ := newConfigProviderFromData("")
return cp
}
// newConfigProviderFromData this function is only for testing
func newConfigProviderFromData(configContent string) (ConfigProvider, error) {
var cfg *ini.File
var err error
if configContent == "" {
cfg = ini.Empty()
} else {
cfg, err = ini.Load(strings.NewReader(configContent))
if err != nil {
return nil, err
}
}
cfg.NameMapper = ini.SnackCase
return &iniFileConfigProvider{
File: cfg,
newFile: true,
}, nil
}
// newConfigProviderFromFile load configuration from file.
// NOTE: do not print any log except error.
func newConfigProviderFromFile(customConf string, allowEmpty bool, extraConfig string) (*iniFileConfigProvider, error) {
cfg := ini.Empty()
newFile := true
if customConf != "" {
isFile, err := util.IsFile(customConf)
if err != nil {
return nil, fmt.Errorf("unable to check if %s is a file. Error: %v", customConf, err)
}
if isFile {
if err := cfg.Append(customConf); err != nil {
return nil, fmt.Errorf("failed to load custom conf '%s': %v", customConf, err)
}
newFile = false
}
}
if newFile && !allowEmpty {
return nil, fmt.Errorf("unable to find configuration file: %q, please ensure you are running in the correct environment or set the correct configuration file with -c", CustomConf)
}
if extraConfig != "" {
if err := cfg.Append([]byte(extraConfig)); err != nil {
return nil, fmt.Errorf("unable to append more config: %v", err)
}
}
cfg.NameMapper = ini.SnackCase
return &iniFileConfigProvider{
File: cfg,
filepath: customConf,
newFile: newFile,
allowEmpty: allowEmpty,
}, nil
}
func (p *iniFileConfigProvider) Section(section string) ConfigSection {
return p.File.Section(section)
}
func (p *iniFileConfigProvider) NewSection(name string) (ConfigSection, error) {
return p.File.NewSection(name)
}
func (p *iniFileConfigProvider) GetSection(name string) (ConfigSection, error) {
return p.File.GetSection(name)
}
func (p *iniFileConfigProvider) DeleteSection(name string) error {
p.File.DeleteSection(name)
return nil
}
// Save save the content into file
func (p *iniFileConfigProvider) Save() error {
if p.filepath == "" {
if !p.allowEmpty {
return fmt.Errorf("custom config path must not be empty")
}
return nil
}
if p.newFile {
if err := os.MkdirAll(filepath.Dir(CustomConf), os.ModePerm); err != nil {
return fmt.Errorf("failed to create '%s': %v", CustomConf, err)
}
}
if err := p.SaveTo(p.filepath); err != nil {
return fmt.Errorf("failed to save '%s': %v", p.filepath, err)
}
// Change permissions to be more restrictive
fi, err := os.Stat(CustomConf)
if err != nil {
return fmt.Errorf("failed to determine current conf file permissions: %v", err)
}
if fi.Mode().Perm() > 0o600 {
if err = os.Chmod(CustomConf, 0o600); err != nil {
log.Warn("Failed changing conf file permissions to -rw-------. Consider changing them manually.")
}
}
return nil
} }
// a file is an implementation ConfigProvider and other implementations are possible, i.e. from docker, k8s, … // a file is an implementation ConfigProvider and other implementations are possible, i.e. from docker, k8s, …
var _ ConfigProvider = &iniFileConfigProvider{} var _ ConfigProvider = &ini.File{}
func mustMapSetting(rootCfg ConfigProvider, sectionName string, setting interface{}) { func mustMapSetting(rootCfg ConfigProvider, sectionName string, setting interface{}) {
if err := rootCfg.Section(sectionName).MapTo(setting); err != nil { if err := rootCfg.Section(sectionName).MapTo(setting); err != nil {

View File

@ -7,6 +7,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
ini "gopkg.in/ini.v1"
) )
func Test_getCronSettings(t *testing.T) { func Test_getCronSettings(t *testing.T) {
@ -22,11 +23,11 @@ func Test_getCronSettings(t *testing.T) {
iniStr := ` iniStr := `
[cron.test] [cron.test]
BASE = true Base = true
SECOND = white rabbit Second = white rabbit
EXTEND = true Extend = true
` `
cfg, err := newConfigProviderFromData(iniStr) cfg, err := ini.Load([]byte(iniStr))
assert.NoError(t, err) assert.NoError(t, err)
extended := &Extended{ extended := &Extended{

View File

@ -9,6 +9,8 @@ import (
"code.gitea.io/gitea/modules/generate" "code.gitea.io/gitea/modules/generate"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
ini "gopkg.in/ini.v1"
) )
// LFS represents the configuration for Git LFS // LFS represents the configuration for Git LFS
@ -36,7 +38,8 @@ func loadLFSFrom(rootCfg ConfigProvider) {
// DEPRECATED should not be removed because users maybe upgrade from lower version to the latest version // DEPRECATED should not be removed because users maybe upgrade from lower version to the latest version
// if these are removed, the warning will not be shown // if these are removed, the warning will not be shown
deprecatedSetting(rootCfg, "server", "LFS_CONTENT_PATH", "lfs", "PATH", "v1.19.0") deprecatedSetting(rootCfg, "server", "LFS_CONTENT_PATH", "lfs", "PATH", "v1.19.0")
lfsSec.Key("PATH").MustString(sec.Key("LFS_CONTENT_PATH").String()) lfsSec.Key("PATH").MustString(
sec.Key("LFS_CONTENT_PATH").String())
LFS.Storage = getStorage(rootCfg, "lfs", storageType, lfsSec) LFS.Storage = getStorage(rootCfg, "lfs", storageType, lfsSec)
@ -59,11 +62,9 @@ func loadLFSFrom(rootCfg ConfigProvider) {
} }
// Save secret // Save secret
sec.Key("LFS_JWT_SECRET").SetValue(LFS.JWTSecretBase64) CreateOrAppendToCustomConf("server.LFS_JWT_SECRET", func(cfg *ini.File) {
if err := rootCfg.Save(); err != nil { cfg.Section("server").Key("LFS_JWT_SECRET").SetValue(LFS.JWTSecretBase64)
log.Fatal("Error saving JWT Secret for custom config: %v", err) })
return
}
} }
} }
} }

View File

@ -15,6 +15,8 @@ import (
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
ini "gopkg.in/ini.v1"
) )
var ( var (
@ -129,12 +131,12 @@ type LogDescription struct {
SubLogDescriptions []SubLogDescription SubLogDescriptions []SubLogDescription
} }
func getLogLevel(section ConfigSection, key string, defaultValue log.Level) log.Level { func getLogLevel(section *ini.Section, key string, defaultValue log.Level) log.Level {
value := section.Key(key).MustString(defaultValue.String()) value := section.Key(key).MustString(defaultValue.String())
return log.FromString(value) return log.FromString(value)
} }
func getStacktraceLogLevel(section ConfigSection, key, defaultValue string) string { func getStacktraceLogLevel(section *ini.Section, key, defaultValue string) string {
value := section.Key(key).MustString(defaultValue) value := section.Key(key).MustString(defaultValue)
return log.FromString(value).String() return log.FromString(value).String()
} }
@ -163,7 +165,7 @@ func loadLogFrom(rootCfg ConfigProvider) {
Log.EnableXORMLog = rootCfg.Section("log").Key("ENABLE_XORM_LOG").MustBool(true) Log.EnableXORMLog = rootCfg.Section("log").Key("ENABLE_XORM_LOG").MustBool(true)
} }
func generateLogConfig(sec ConfigSection, name string, defaults defaultLogOptions) (mode, jsonConfig, levelName string) { func generateLogConfig(sec *ini.Section, name string, defaults defaultLogOptions) (mode, jsonConfig, levelName string) {
level := getLogLevel(sec, "LEVEL", Log.Level) level := getLogLevel(sec, "LEVEL", Log.Level)
levelName = level.String() levelName = level.String()
stacktraceLevelName := getStacktraceLogLevel(sec, "STACKTRACE_LEVEL", Log.StacktraceLogLevel) stacktraceLevelName := getStacktraceLogLevel(sec, "STACKTRACE_LEVEL", Log.StacktraceLogLevel)

View File

@ -7,10 +7,11 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
ini "gopkg.in/ini.v1"
) )
func Test_loadMailerFrom(t *testing.T) { func Test_loadMailerFrom(t *testing.T) {
iniFile := NewEmptyConfigProvider() iniFile := ini.Empty()
kases := map[string]*Mailer{ kases := map[string]*Mailer{
"smtp.mydomain.com": { "smtp.mydomain.com": {
SMTPAddr: "smtp.mydomain.com", SMTPAddr: "smtp.mydomain.com",

View File

@ -8,6 +8,8 @@ import (
"strings" "strings"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"gopkg.in/ini.v1"
) )
// ExternalMarkupRenderers represents the external markup renderers // ExternalMarkupRenderers represents the external markup renderers
@ -80,7 +82,7 @@ func loadMarkupFrom(rootCfg ConfigProvider) {
} }
} }
func newMarkupSanitizer(name string, sec ConfigSection) { func newMarkupSanitizer(name string, sec *ini.Section) {
rule, ok := createMarkupSanitizerRule(name, sec) rule, ok := createMarkupSanitizerRule(name, sec)
if ok { if ok {
if strings.HasPrefix(name, "sanitizer.") { if strings.HasPrefix(name, "sanitizer.") {
@ -97,7 +99,7 @@ func newMarkupSanitizer(name string, sec ConfigSection) {
} }
} }
func createMarkupSanitizerRule(name string, sec ConfigSection) (MarkupSanitizerRule, bool) { func createMarkupSanitizerRule(name string, sec *ini.Section) (MarkupSanitizerRule, bool) {
var rule MarkupSanitizerRule var rule MarkupSanitizerRule
ok := false ok := false
@ -139,7 +141,7 @@ func createMarkupSanitizerRule(name string, sec ConfigSection) (MarkupSanitizerR
return rule, true return rule, true
} }
func newMarkupRenderer(name string, sec ConfigSection) { func newMarkupRenderer(name string, sec *ini.Section) {
extensionReg := regexp.MustCompile(`\.\w`) extensionReg := regexp.MustCompile(`\.\w`)
extensions := sec.Key("FILE_EXTENSIONS").Strings(",") extensions := sec.Key("FILE_EXTENSIONS").Strings(",")

View File

@ -4,12 +4,12 @@
package setting package setting
import ( import (
"encoding/base64"
"math" "math"
"path/filepath" "path/filepath"
"code.gitea.io/gitea/modules/generate"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"gopkg.in/ini.v1"
) )
// OAuth2UsernameType is enum describing the way gitea 'name' should be generated from oauth2 data // OAuth2UsernameType is enum describing the way gitea 'name' should be generated from oauth2 data
@ -80,7 +80,7 @@ func loadOAuth2ClientFrom(rootCfg ConfigProvider) {
} }
} }
func parseScopes(sec ConfigSection, name string) []string { func parseScopes(sec *ini.Section, name string) []string {
parts := sec.Key(name).Strings(" ") parts := sec.Key(name).Strings(" ")
scopes := make([]string, 0, len(parts)) scopes := make([]string, 0, len(parts))
for _, scope := range parts { for _, scope := range parts {
@ -119,19 +119,4 @@ func loadOAuth2From(rootCfg ConfigProvider) {
if !filepath.IsAbs(OAuth2.JWTSigningPrivateKeyFile) { if !filepath.IsAbs(OAuth2.JWTSigningPrivateKeyFile) {
OAuth2.JWTSigningPrivateKeyFile = filepath.Join(AppDataPath, OAuth2.JWTSigningPrivateKeyFile) OAuth2.JWTSigningPrivateKeyFile = filepath.Join(AppDataPath, OAuth2.JWTSigningPrivateKeyFile)
} }
key := make([]byte, 32)
n, err := base64.RawURLEncoding.Decode(key, []byte(OAuth2.JWTSecretBase64))
if err != nil || n != 32 {
key, err = generate.NewJwtSecret()
if err != nil {
log.Fatal("error generating JWT secret: %v", err)
}
secretBase64 := base64.RawURLEncoding.EncodeToString(key)
rootCfg.Section("oauth2").Key("JWT_SECRET").SetValue(secretBase64)
if err := rootCfg.Save(); err != nil {
log.Fatal("save oauth2.JWT_SECRET failed: %v", err)
}
}
} }

View File

@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"github.com/dustin/go-humanize" "github.com/dustin/go-humanize"
ini "gopkg.in/ini.v1"
) )
// Package registry settings // Package registry settings
@ -85,7 +86,7 @@ func loadPackagesFrom(rootCfg ConfigProvider) {
Packages.LimitSizeVagrant = mustBytes(sec, "LIMIT_SIZE_VAGRANT") Packages.LimitSizeVagrant = mustBytes(sec, "LIMIT_SIZE_VAGRANT")
} }
func mustBytes(section ConfigSection, key string) int64 { func mustBytes(section *ini.Section, key string) int64 {
const noLimit = "-1" const noLimit = "-1"
value := section.Key(key).MustString(noLimit) value := section.Key(key).MustString(noLimit)

View File

@ -10,6 +10,8 @@ import (
"code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
ini "gopkg.in/ini.v1"
) )
// QueueSettings represent the settings for a queue from the ini // QueueSettings represent the settings for a queue from the ini
@ -193,7 +195,7 @@ func handleOldLengthConfiguration(rootCfg ConfigProvider, queueName, oldSection,
// toDirectlySetKeysSet returns a set of keys directly set by this section // toDirectlySetKeysSet returns a set of keys directly set by this section
// Note: we cannot use section.HasKey(...) as that will immediately set the Key if a parent section has the Key // Note: we cannot use section.HasKey(...) as that will immediately set the Key if a parent section has the Key
// but this section does not. // but this section does not.
func toDirectlySetKeysSet(section ConfigSection) container.Set[string] { func toDirectlySetKeysSet(section *ini.Section) container.Set[string] {
sections := make(container.Set[string]) sections := make(container.Set[string])
for _, key := range section.Keys() { for _, key := range section.Keys() {
sections.Add(key.Name()) sections.Add(key.Name())

View File

@ -11,6 +11,8 @@ import (
"code.gitea.io/gitea/modules/auth/password/hash" "code.gitea.io/gitea/modules/auth/password/hash"
"code.gitea.io/gitea/modules/generate" "code.gitea.io/gitea/modules/generate"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
ini "gopkg.in/ini.v1"
) )
var ( var (
@ -41,7 +43,7 @@ var (
// loadSecret load the secret from ini by uriKey or verbatimKey, only one of them could be set // loadSecret load the secret from ini by uriKey or verbatimKey, only one of them could be set
// If the secret is loaded from uriKey (file), the file should be non-empty, to guarantee the behavior stable and clear. // If the secret is loaded from uriKey (file), the file should be non-empty, to guarantee the behavior stable and clear.
func loadSecret(sec ConfigSection, uriKey, verbatimKey string) string { func loadSecret(sec *ini.Section, uriKey, verbatimKey string) string {
// don't allow setting both URI and verbatim string // don't allow setting both URI and verbatim string
uri := sec.Key(uriKey).String() uri := sec.Key(uriKey).String()
verbatim := sec.Key(verbatimKey).String() verbatim := sec.Key(verbatimKey).String()
@ -82,17 +84,16 @@ func loadSecret(sec ConfigSection, uriKey, verbatimKey string) string {
} }
// generateSaveInternalToken generates and saves the internal token to app.ini // generateSaveInternalToken generates and saves the internal token to app.ini
func generateSaveInternalToken(rootCfg ConfigProvider) { func generateSaveInternalToken() {
token, err := generate.NewInternalToken() token, err := generate.NewInternalToken()
if err != nil { if err != nil {
log.Fatal("Error generate internal token: %v", err) log.Fatal("Error generate internal token: %v", err)
} }
InternalToken = token InternalToken = token
rootCfg.Section("security").Key("INTERNAL_TOKEN").SetValue(token) CreateOrAppendToCustomConf("security.INTERNAL_TOKEN", func(cfg *ini.File) {
if err := rootCfg.Save(); err != nil { cfg.Section("security").Key("INTERNAL_TOKEN").SetValue(token)
log.Fatal("Error saving internal token: %v", err) })
}
} }
func loadSecurityFrom(rootCfg ConfigProvider) { func loadSecurityFrom(rootCfg ConfigProvider) {
@ -140,7 +141,7 @@ func loadSecurityFrom(rootCfg ConfigProvider) {
if InstallLock && InternalToken == "" { if InstallLock && InternalToken == "" {
// if Gitea has been installed but the InternalToken hasn't been generated (upgrade from an old release), we should generate // if Gitea has been installed but the InternalToken hasn't been generated (upgrade from an old release), we should generate
// some users do cluster deployment, they still depend on this auto-generating behavior. // some users do cluster deployment, they still depend on this auto-generating behavior.
generateSaveInternalToken(rootCfg) generateSaveInternalToken()
} }
cfgdata := sec.Key("PASSWORD_COMPLEXITY").Strings(",") cfgdata := sec.Key("PASSWORD_COMPLEXITY").Strings(",")

View File

@ -18,6 +18,9 @@ import (
"code.gitea.io/gitea/modules/auth/password/hash" "code.gitea.io/gitea/modules/auth/password/hash"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/user" "code.gitea.io/gitea/modules/user"
"code.gitea.io/gitea/modules/util"
ini "gopkg.in/ini.v1"
) )
// settings // settings
@ -205,29 +208,17 @@ func PrepareAppDataPath() error {
// InitProviderFromExistingFile initializes config provider from an existing config file (app.ini) // InitProviderFromExistingFile initializes config provider from an existing config file (app.ini)
func InitProviderFromExistingFile() { func InitProviderFromExistingFile() {
var err error CfgProvider = newFileProviderFromConf(CustomConf, false, "")
CfgProvider, err = newConfigProviderFromFile(CustomConf, false, "")
if err != nil {
log.Fatal("InitProviderFromExistingFile: %v", err)
}
} }
// InitProviderAllowEmpty initializes config provider from file, it's also fine that if the config file (app.ini) doesn't exist // InitProviderAllowEmpty initializes config provider from file, it's also fine that if the config file (app.ini) doesn't exist
func InitProviderAllowEmpty() { func InitProviderAllowEmpty() {
var err error CfgProvider = newFileProviderFromConf(CustomConf, true, "")
CfgProvider, err = newConfigProviderFromFile(CustomConf, true, "")
if err != nil {
log.Fatal("InitProviderAllowEmpty: %v", err)
}
} }
// InitProviderAndLoadCommonSettingsForTest initializes config provider and load common setttings for tests // InitProviderAndLoadCommonSettingsForTest initializes config provider and load common setttings for tests
func InitProviderAndLoadCommonSettingsForTest(extraConfigs ...string) { func InitProviderAndLoadCommonSettingsForTest(extraConfigs ...string) {
var err error CfgProvider = newFileProviderFromConf(CustomConf, true, strings.Join(extraConfigs, "\n"))
CfgProvider, err = newConfigProviderFromFile(CustomConf, true, strings.Join(extraConfigs, "\n"))
if err != nil {
log.Fatal("InitProviderAndLoadCommonSettingsForTest: %v", err)
}
loadCommonSettingsFrom(CfgProvider) loadCommonSettingsFrom(CfgProvider)
if err := PrepareAppDataPath(); err != nil { if err := PrepareAppDataPath(); err != nil {
log.Fatal("Can not prepare APP_DATA_PATH: %v", err) log.Fatal("Can not prepare APP_DATA_PATH: %v", err)
@ -238,6 +229,33 @@ func InitProviderAndLoadCommonSettingsForTest(extraConfigs ...string) {
PasswordHashAlgo, _ = hash.SetDefaultPasswordHashAlgorithm("dummy") PasswordHashAlgo, _ = hash.SetDefaultPasswordHashAlgorithm("dummy")
} }
// newFileProviderFromConf initializes configuration context.
// NOTE: do not print any log except error.
func newFileProviderFromConf(customConf string, allowEmpty bool, extraConfig string) *ini.File {
cfg := ini.Empty()
isFile, err := util.IsFile(customConf)
if err != nil {
log.Error("Unable to check if %s is a file. Error: %v", customConf, err)
}
if isFile {
if err := cfg.Append(customConf); err != nil {
log.Fatal("Failed to load custom conf '%s': %v", customConf, err)
}
} else if !allowEmpty {
log.Fatal("Unable to find configuration file: %q.\nEnsure you are running in the correct environment or set the correct configuration file with -c.", CustomConf)
} // else: no config file, a config file might be created at CustomConf later (might not)
if extraConfig != "" {
if err = cfg.Append([]byte(extraConfig)); err != nil {
log.Fatal("Unable to append more config: %v", err)
}
}
cfg.NameMapper = ini.SnackCase
return cfg
}
// LoadCommonSettings loads common configurations from a configuration provider. // LoadCommonSettings loads common configurations from a configuration provider.
func LoadCommonSettings() { func LoadCommonSettings() {
loadCommonSettingsFrom(CfgProvider) loadCommonSettingsFrom(CfgProvider)
@ -301,6 +319,51 @@ func loadRunModeFrom(rootCfg ConfigProvider) {
} }
} }
// CreateOrAppendToCustomConf creates or updates the custom config.
// Use the callback to set individual values.
func CreateOrAppendToCustomConf(purpose string, callback func(cfg *ini.File)) {
if CustomConf == "" {
log.Error("Custom config path must not be empty")
return
}
cfg := ini.Empty()
isFile, err := util.IsFile(CustomConf)
if err != nil {
log.Error("Unable to check if %s is a file. Error: %v", CustomConf, err)
}
if isFile {
if err := cfg.Append(CustomConf); err != nil {
log.Error("failed to load custom conf %s: %v", CustomConf, err)
return
}
}
callback(cfg)
if err := os.MkdirAll(filepath.Dir(CustomConf), os.ModePerm); err != nil {
log.Fatal("failed to create '%s': %v", CustomConf, err)
return
}
if err := cfg.SaveTo(CustomConf); err != nil {
log.Fatal("error saving to custom config: %v", err)
}
log.Info("Settings for %s saved to: %q", purpose, CustomConf)
// Change permissions to be more restrictive
fi, err := os.Stat(CustomConf)
if err != nil {
log.Error("Failed to determine current conf file permissions: %v", err)
return
}
if fi.Mode().Perm() > 0o600 {
if err = os.Chmod(CustomConf, 0o600); err != nil {
log.Warn("Failed changing conf file permissions to -rw-------. Consider changing them manually.")
}
}
}
// LoadSettings initializes the settings for normal start up // LoadSettings initializes the settings for normal start up
func LoadSettings() { func LoadSettings() {
loadDBSetting(CfgProvider) loadDBSetting(CfgProvider)

View File

@ -6,13 +6,15 @@ package setting
import ( import (
"path/filepath" "path/filepath"
"reflect" "reflect"
ini "gopkg.in/ini.v1"
) )
// Storage represents configuration of storages // Storage represents configuration of storages
type Storage struct { type Storage struct {
Type string Type string
Path string Path string
Section ConfigSection Section *ini.Section
ServeDirect bool ServeDirect bool
} }
@ -28,7 +30,7 @@ func (s *Storage) MapTo(v interface{}) error {
return nil return nil
} }
func getStorage(rootCfg ConfigProvider, name, typ string, targetSec ConfigSection) Storage { func getStorage(rootCfg ConfigProvider, name, typ string, targetSec *ini.Section) Storage {
const sectionName = "storage" const sectionName = "storage"
sec := rootCfg.Section(sectionName) sec := rootCfg.Section(sectionName)
@ -50,7 +52,7 @@ func getStorage(rootCfg ConfigProvider, name, typ string, targetSec ConfigSectio
storage.Section = targetSec storage.Section = targetSec
storage.Type = typ storage.Type = typ
overrides := make([]ConfigSection, 0, 3) overrides := make([]*ini.Section, 0, 3)
nameSec, err := rootCfg.GetSection(sectionName + "." + name) nameSec, err := rootCfg.GetSection(sectionName + "." + name)
if err == nil { if err == nil {
overrides = append(overrides, nameSec) overrides = append(overrides, nameSec)

View File

@ -7,6 +7,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
ini "gopkg.in/ini.v1"
) )
func Test_getStorageCustomType(t *testing.T) { func Test_getStorageCustomType(t *testing.T) {
@ -19,7 +20,7 @@ MINIO_BUCKET = gitea-attachment
STORAGE_TYPE = minio STORAGE_TYPE = minio
MINIO_ENDPOINT = my_minio:9000 MINIO_ENDPOINT = my_minio:9000
` `
cfg, err := newConfigProviderFromData(iniStr) cfg, err := ini.Load([]byte(iniStr))
assert.NoError(t, err) assert.NoError(t, err)
sec := cfg.Section("attachment") sec := cfg.Section("attachment")
@ -42,7 +43,7 @@ MINIO_BUCKET = gitea-attachment
[storage.minio] [storage.minio]
MINIO_BUCKET = gitea MINIO_BUCKET = gitea
` `
cfg, err := newConfigProviderFromData(iniStr) cfg, err := ini.Load([]byte(iniStr))
assert.NoError(t, err) assert.NoError(t, err)
sec := cfg.Section("attachment") sec := cfg.Section("attachment")
@ -64,7 +65,7 @@ MINIO_BUCKET = gitea-minio
[storage] [storage]
MINIO_BUCKET = gitea MINIO_BUCKET = gitea
` `
cfg, err := newConfigProviderFromData(iniStr) cfg, err := ini.Load([]byte(iniStr))
assert.NoError(t, err) assert.NoError(t, err)
sec := cfg.Section("attachment") sec := cfg.Section("attachment")
@ -87,7 +88,7 @@ MINIO_BUCKET = gitea
[storage] [storage]
STORAGE_TYPE = local STORAGE_TYPE = local
` `
cfg, err := newConfigProviderFromData(iniStr) cfg, err := ini.Load([]byte(iniStr))
assert.NoError(t, err) assert.NoError(t, err)
sec := cfg.Section("attachment") sec := cfg.Section("attachment")
@ -99,7 +100,7 @@ STORAGE_TYPE = local
} }
func Test_getStorageGetDefaults(t *testing.T) { func Test_getStorageGetDefaults(t *testing.T) {
cfg, err := newConfigProviderFromData("") cfg, err := ini.Load([]byte(""))
assert.NoError(t, err) assert.NoError(t, err)
sec := cfg.Section("attachment") sec := cfg.Section("attachment")
@ -120,7 +121,7 @@ MINIO_BUCKET = gitea-attachment
[storage] [storage]
MINIO_BUCKET = gitea-storage MINIO_BUCKET = gitea-storage
` `
cfg, err := newConfigProviderFromData(iniStr) cfg, err := ini.Load([]byte(iniStr))
assert.NoError(t, err) assert.NoError(t, err)
{ {
@ -154,7 +155,7 @@ STORAGE_TYPE = lfs
[storage.lfs] [storage.lfs]
MINIO_BUCKET = gitea-storage MINIO_BUCKET = gitea-storage
` `
cfg, err := newConfigProviderFromData(iniStr) cfg, err := ini.Load([]byte(iniStr))
assert.NoError(t, err) assert.NoError(t, err)
{ {
@ -178,7 +179,7 @@ func Test_getStorageInheritStorageType(t *testing.T) {
[storage] [storage]
STORAGE_TYPE = minio STORAGE_TYPE = minio
` `
cfg, err := newConfigProviderFromData(iniStr) cfg, err := ini.Load([]byte(iniStr))
assert.NoError(t, err) assert.NoError(t, err)
sec := cfg.Section("attachment") sec := cfg.Section("attachment")
@ -193,7 +194,7 @@ func Test_getStorageInheritNameSectionType(t *testing.T) {
[storage.attachments] [storage.attachments]
STORAGE_TYPE = minio STORAGE_TYPE = minio
` `
cfg, err := newConfigProviderFromData(iniStr) cfg, err := ini.Load([]byte(iniStr))
assert.NoError(t, err) assert.NoError(t, err)
sec := cfg.Section("attachment") sec := cfg.Section("attachment")

View File

@ -7,54 +7,19 @@ import (
"fmt" "fmt"
"html" "html"
"html/template" "html/template"
"time"
) )
// DateTime renders an absolute time HTML element by datetime. // DateTime renders an absolute time HTML given a time as a string
func DateTime(format string, datetime any) template.HTML { func DateTime(format, datetime, fallback string) template.HTML {
if p, ok := datetime.(*time.Time); ok { datetimeEscaped := html.EscapeString(datetime)
datetime = *p fallbackEscaped := html.EscapeString(fallback)
}
if p, ok := datetime.(*TimeStamp); ok {
datetime = *p
}
switch v := datetime.(type) {
case TimeStamp:
datetime = v.AsTime()
case int:
datetime = TimeStamp(v).AsTime()
case int64:
datetime = TimeStamp(v).AsTime()
}
var datetimeEscaped, textEscaped string
switch v := datetime.(type) {
case nil:
return "N/A"
case string:
datetimeEscaped = html.EscapeString(v)
textEscaped = datetimeEscaped
case time.Time:
if v.IsZero() || v.Unix() == 0 {
return "N/A"
}
datetimeEscaped = html.EscapeString(v.Format(time.RFC3339))
if format == "full" {
textEscaped = html.EscapeString(v.Format("2006-01-02 15:04:05 -07:00"))
} else {
textEscaped = html.EscapeString(v.Format("2006-01-02"))
}
default:
panic(fmt.Sprintf("Unsupported time type %T", datetime))
}
switch format { switch format {
case "short": case "short":
return template.HTML(fmt.Sprintf(`<relative-time format="datetime" year="numeric" month="short" day="numeric" weekday="" datetime="%s">%s</relative-time>`, datetimeEscaped, textEscaped)) return template.HTML(fmt.Sprintf(`<relative-time format="datetime" year="numeric" month="short" day="numeric" weekday="" datetime="%s">%s</relative-time>`, datetimeEscaped, fallbackEscaped))
case "long": case "long":
return template.HTML(fmt.Sprintf(`<relative-time format="datetime" year="numeric" month="long" day="numeric" weekday="" datetime="%s">%s</relative-time>`, datetimeEscaped, textEscaped)) return template.HTML(fmt.Sprintf(`<relative-time format="datetime" year="numeric" month="long" day="numeric" weekday="" datetime="%s">%s</relative-time>`, datetimeEscaped, fallbackEscaped))
case "full": case "full":
return template.HTML(fmt.Sprintf(`<relative-time format="datetime" weekday="" year="numeric" month="short" day="numeric" hour="numeric" minute="numeric" second="numeric" datetime="%s">%s</relative-time>`, datetimeEscaped, textEscaped)) return template.HTML(fmt.Sprintf(`<relative-time format="datetime" weekday="" year="numeric" month="short" day="numeric" hour="numeric" minute="numeric" second="numeric" datetime="%s">%s</relative-time>`, datetimeEscaped, fallbackEscaped))
} }
panic(fmt.Sprintf("Unsupported format %s", format)) return template.HTML("error in DateTime")
} }

View File

@ -1,45 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package timeutil
import (
"testing"
"time"
"code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert"
)
func TestDateTime(t *testing.T) {
oldTz := setting.DefaultUILocation
setting.DefaultUILocation, _ = time.LoadLocation("America/New_York")
defer func() {
setting.DefaultUILocation = oldTz
}()
refTimeStr := "2018-01-01T00:00:00Z"
refTime, _ := time.Parse(time.RFC3339, refTimeStr)
refTimeStamp := TimeStamp(refTime.Unix())
assert.EqualValues(t, "N/A", DateTime("short", nil))
assert.EqualValues(t, "N/A", DateTime("short", 0))
assert.EqualValues(t, "N/A", DateTime("short", time.Time{}))
assert.EqualValues(t, "N/A", DateTime("short", TimeStamp(0)))
actual := DateTime("short", "invalid")
assert.EqualValues(t, `<relative-time format="datetime" year="numeric" month="short" day="numeric" weekday="" datetime="invalid">invalid</relative-time>`, actual)
actual = DateTime("short", refTimeStr)
assert.EqualValues(t, `<relative-time format="datetime" year="numeric" month="short" day="numeric" weekday="" datetime="2018-01-01T00:00:00Z">2018-01-01T00:00:00Z</relative-time>`, actual)
actual = DateTime("short", refTime)
assert.EqualValues(t, `<relative-time format="datetime" year="numeric" month="short" day="numeric" weekday="" datetime="2018-01-01T00:00:00Z">2018-01-01</relative-time>`, actual)
actual = DateTime("short", refTimeStamp)
assert.EqualValues(t, `<relative-time format="datetime" year="numeric" month="short" day="numeric" weekday="" datetime="2017-12-31T19:00:00-05:00">2017-12-31</relative-time>`, actual)
actual = DateTime("full", refTimeStamp)
assert.EqualValues(t, `<relative-time format="datetime" weekday="" year="numeric" month="short" day="numeric" hour="numeric" minute="numeric" second="numeric" datetime="2017-12-31T19:00:00-05:00">2017-12-31 19:00:00 -05:00</relative-time>`, actual)
}

View File

@ -115,7 +115,7 @@ func timeSincePro(then, now time.Time, lang translation.Locale) string {
} }
func timeSinceUnix(then, now time.Time, lang translation.Locale) template.HTML { func timeSinceUnix(then, now time.Time, lang translation.Locale) template.HTML {
friendlyText := then.Format("2006-01-02 15:04:05 -07:00") friendlyText := then.Format("2006-01-02 15:04:05 +07:00")
// document: https://github.com/github/relative-time-element // document: https://github.com/github/relative-time-element
attrs := `tense="past"` attrs := `tense="past"`

View File

@ -462,7 +462,6 @@ team_invite.text_3=Σημείωση: Αυτή η πρόσκληση προορι
[modal] [modal]
yes=Ναι yes=Ναι
no=Όχι no=Όχι
confirm=Επιβεβαίωση
cancel=Ακύρωση cancel=Ακύρωση
modify=Ενημέρωση modify=Ενημέρωση
@ -1623,10 +1622,7 @@ pulls.tab_files=Αρχεία Με Αλλαγές
pulls.reopen_to_merge=Παρακαλώ ανοίξτε ξανά αυτό το pull request για να εκτελέσετε μια συγχώνευση. pulls.reopen_to_merge=Παρακαλώ ανοίξτε ξανά αυτό το pull request για να εκτελέσετε μια συγχώνευση.
pulls.cant_reopen_deleted_branch=Αυτό το pull request δεν μπορεί να ανοίξει ξανά επειδή ο κλάδος διαγράφηκε. pulls.cant_reopen_deleted_branch=Αυτό το pull request δεν μπορεί να ανοίξει ξανά επειδή ο κλάδος διαγράφηκε.
pulls.merged=Συγχωνευμένο pulls.merged=Συγχωνευμένο
pulls.merged_success=Το pull request συγχωνεύτηκε και έκλεισε επιτυχώς
pulls.closed=Το pull request έκλεισε
pulls.manually_merged=Συγχωνεύτηκαν χειροκίνητα pulls.manually_merged=Συγχωνεύτηκαν χειροκίνητα
pulls.merged_info_text=Ο κλάδος %s μπορεί τώρα να διαγραφεί.
pulls.is_closed=Το pull request έχει κλείσει. pulls.is_closed=Το pull request έχει κλείσει.
pulls.title_wip_desc=`<a href="#">Ξεκινήστε τον τίτλο με <strong>%s</strong></a> για να αποτρέψετε την τυχαία συγχώνευση του pull request.` pulls.title_wip_desc=`<a href="#">Ξεκινήστε τον τίτλο με <strong>%s</strong></a> για να αποτρέψετε την τυχαία συγχώνευση του pull request.`
pulls.cannot_merge_work_in_progress=Αυτό το pull request επισημαίνεται ως μια εργασία σε εξέλιξη. pulls.cannot_merge_work_in_progress=Αυτό το pull request επισημαίνεται ως μια εργασία σε εξέλιξη.
@ -3418,7 +3414,4 @@ runs.no_matching_runner_helper=Δε ταιριάζει εκτελεστής: %s
need_approval_desc=Πρέπει να εγκριθεί η εκτέλεση ροών εργασίας για pull request από fork. need_approval_desc=Πρέπει να εγκριθεί η εκτέλεση ροών εργασίας για pull request από fork.
[projects] [projects]
type-1.display_name=Ατομικό Έργο
type-2.display_name=Έργο Αποθετηρίου
type-3.display_name=Έργο Οργανισμού

View File

@ -284,7 +284,7 @@ show_only_public=只显示公开的
issues.in_your_repos=在您的仓库中 issues.in_your_repos=在您的仓库中
[explore] [explore]
repos=仓库 repos=仓库管理
users=用户 users=用户
organizations=组织管理 organizations=组织管理
search=搜索 search=搜索

View File

@ -966,7 +966,7 @@ func SignInOAuthCallback(ctx *context.Context) {
} }
overwriteDefault := &user_model.CreateUserOverwriteOptions{ overwriteDefault := &user_model.CreateUserOverwriteOptions{
IsActive: util.OptionalBoolOf(!setting.OAuth2Client.RegisterEmailConfirm && !setting.Service.RegisterManualConfirm), IsActive: util.OptionalBoolOf(!setting.OAuth2Client.RegisterEmailConfirm),
} }
source := authSource.Cfg.(*oauth2.Source) source := authSource.Cfg.(*oauth2.Source)

View File

@ -1,50 +0,0 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package feed
import (
"fmt"
"strings"
"time"
"code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/context"
"github.com/gorilla/feeds"
)
// ShowBranchFeed shows tags and/or releases on the repo as RSS / Atom feed
func ShowBranchFeed(ctx *context.Context, repo *repo.Repository, formatType string) {
commits, err := ctx.Repo.Commit.CommitsByRange(0, 10)
if err != nil {
ctx.ServerError("ShowBranchFeed", err)
return
}
title := fmt.Sprintf("Latest commits for branch %s", ctx.Repo.BranchName)
link := &feeds.Link{Href: repo.HTMLURL() + "/" + ctx.Repo.BranchNameSubURL()}
feed := &feeds.Feed{
Title: title,
Link: link,
Description: repo.Description,
Created: time.Now(),
}
for _, commit := range commits {
feed.Items = append(feed.Items, &feeds.Item{
Id: commit.ID.String(),
Title: strings.TrimSpace(strings.Split(commit.Message(), "\n")[0]),
Link: &feeds.Link{Href: repo.HTMLURL() + "/commit/" + commit.ID.String()},
Author: &feeds.Author{
Name: commit.Author.Name,
Email: commit.Author.Email,
},
Description: commit.Message(),
Content: commit.Message(),
})
}
writeFeed(ctx, feed, formatType)
}

View File

@ -1,56 +0,0 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package feed
import (
"fmt"
"strings"
"time"
"code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/util"
"github.com/gorilla/feeds"
)
// ShowFileFeed shows tags and/or releases on the repo as RSS / Atom feed
func ShowFileFeed(ctx *context.Context, repo *repo.Repository, formatType string) {
fileName := ctx.Repo.TreePath
if len(fileName) == 0 {
return
}
commits, err := ctx.Repo.GitRepo.CommitsByFileAndRange(ctx.Repo.RefName, fileName, 1)
if err != nil {
ctx.ServerError("ShowBranchFeed", err)
return
}
title := fmt.Sprintf("Latest commits for file %s", ctx.Repo.TreePath)
link := &feeds.Link{Href: repo.HTMLURL() + "/" + ctx.Repo.BranchNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)}
feed := &feeds.Feed{
Title: title,
Link: link,
Description: repo.Description,
Created: time.Now(),
}
for _, commit := range commits {
feed.Items = append(feed.Items, &feeds.Item{
Id: commit.ID.String(),
Title: strings.TrimSpace(strings.Split(commit.Message(), "\n")[0]),
Link: &feeds.Link{Href: repo.HTMLURL() + "/commit/" + commit.ID.String()},
Author: &feeds.Author{
Name: commit.Author.Name,
Email: commit.Author.Email,
},
Description: commit.Message(),
Content: commit.Message(),
})
}
writeFeed(ctx, feed, formatType)
}

View File

@ -1,18 +0,0 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package feed
import (
"code.gitea.io/gitea/modules/context"
)
// RenderBranchFeed render format for branch or file
func RenderBranchFeed(ctx *context.Context) {
_, _, showFeedType := GetFeedType(ctx.Params(":reponame"), ctx.Req)
if ctx.Repo.TreePath == "" {
ShowBranchFeed(ctx, ctx.Repo.Repository, showFeedType)
} else {
ShowFileFeed(ctx, ctx.Repo.Repository, showFeedType)
}
}

View File

@ -32,31 +32,43 @@ import (
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
repo_service "code.gitea.io/gitea/services/repository" repo_service "code.gitea.io/gitea/services/repository"
"github.com/go-chi/cors"
) )
func HTTPGitEnabledHandler(ctx *context.Context) {
if setting.Repository.DisableHTTPGit {
ctx.Resp.WriteHeader(http.StatusForbidden)
_, _ = ctx.Resp.Write([]byte("Interacting with repositories by HTTP protocol is not allowed"))
}
}
func CorsHandler() func(next http.Handler) http.Handler {
if setting.Repository.AccessControlAllowOrigin != "" {
return cors.Handler(cors.Options{
AllowedOrigins: []string{setting.Repository.AccessControlAllowOrigin},
AllowedHeaders: []string{"Content-Type", "Authorization", "User-Agent"},
})
}
return func(next http.Handler) http.Handler {
return next
}
}
// httpBase implementation git smart HTTP protocol // httpBase implementation git smart HTTP protocol
func httpBase(ctx *context.Context) (h *serviceHandler) { func httpBase(ctx *context.Context) (h *serviceHandler) {
if setting.Repository.DisableHTTPGit {
ctx.Resp.WriteHeader(http.StatusForbidden)
_, err := ctx.Resp.Write([]byte("Interacting with repositories by HTTP protocol is not allowed"))
if err != nil {
log.Error(err.Error())
}
return
}
if len(setting.Repository.AccessControlAllowOrigin) > 0 {
allowedOrigin := setting.Repository.AccessControlAllowOrigin
// Set CORS headers for browser-based git clients
ctx.Resp.Header().Set("Access-Control-Allow-Origin", allowedOrigin)
ctx.Resp.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, User-Agent")
// Handle preflight OPTIONS request
if ctx.Req.Method == "OPTIONS" {
if allowedOrigin == "*" {
ctx.Status(http.StatusOK)
} else if allowedOrigin == "null" {
ctx.Status(http.StatusForbidden)
} else {
origin := ctx.Req.Header.Get("Origin")
if len(origin) > 0 && origin == allowedOrigin {
ctx.Status(http.StatusOK)
} else {
ctx.Status(http.StatusForbidden)
}
}
return
}
}
username := ctx.Params(":username") username := ctx.Params(":username")
reponame := strings.TrimSuffix(ctx.Params(":reponame"), ".git") reponame := strings.TrimSuffix(ctx.Params(":reponame"), ".git")

View File

@ -29,9 +29,8 @@ import (
) )
const ( const (
tplReleasesList base.TplName = "repo/release/list" tplReleases base.TplName = "repo/release/list"
tplReleaseNew base.TplName = "repo/release/new" tplReleaseNew base.TplName = "repo/release/new"
tplTagsList base.TplName = "repo/tag/list"
) )
// calReleaseNumCommitsBehind calculates given release has how many commits behind release target. // calReleaseNumCommitsBehind calculates given release has how many commits behind release target.
@ -59,19 +58,16 @@ func calReleaseNumCommitsBehind(repoCtx *context.Repository, release *repo_model
// Releases render releases list page // Releases render releases list page
func Releases(ctx *context.Context) { func Releases(ctx *context.Context) {
ctx.Data["PageIsReleaseList"] = true
ctx.Data["Title"] = ctx.Tr("repo.release.releases")
releasesOrTags(ctx, false) releasesOrTags(ctx, false)
} }
// TagsList render tags list page // TagsList render tags list page
func TagsList(ctx *context.Context) { func TagsList(ctx *context.Context) {
ctx.Data["PageIsTagList"] = true
ctx.Data["Title"] = ctx.Tr("repo.release.tags")
releasesOrTags(ctx, true) releasesOrTags(ctx, true)
} }
func releasesOrTags(ctx *context.Context, isTagList bool) { func releasesOrTags(ctx *context.Context, isTagList bool) {
ctx.Data["PageIsReleaseList"] = true
ctx.Data["DefaultBranch"] = ctx.Repo.Repository.DefaultBranch ctx.Data["DefaultBranch"] = ctx.Repo.Repository.DefaultBranch
ctx.Data["IsViewBranch"] = false ctx.Data["IsViewBranch"] = false
ctx.Data["IsViewTag"] = true ctx.Data["IsViewTag"] = true
@ -79,6 +75,14 @@ func releasesOrTags(ctx *context.Context, isTagList bool) {
ctx.Data["CanCreateBranch"] = false ctx.Data["CanCreateBranch"] = false
ctx.Data["HideBranchesInDropdown"] = true ctx.Data["HideBranchesInDropdown"] = true
if isTagList {
ctx.Data["Title"] = ctx.Tr("repo.release.tags")
ctx.Data["PageIsTagList"] = true
} else {
ctx.Data["Title"] = ctx.Tr("repo.release.releases")
ctx.Data["PageIsTagList"] = false
}
listOptions := db.ListOptions{ listOptions := db.ListOptions{
Page: ctx.FormInt("page"), Page: ctx.FormInt("page"),
PageSize: ctx.FormInt("limit"), PageSize: ctx.FormInt("limit"),
@ -192,11 +196,7 @@ func releasesOrTags(ctx *context.Context, isTagList bool) {
pager.SetDefaultParams(ctx) pager.SetDefaultParams(ctx)
ctx.Data["Page"] = pager ctx.Data["Page"] = pager
if isTagList { ctx.HTML(http.StatusOK, tplReleases)
ctx.HTML(http.StatusOK, tplTagsList)
} else {
ctx.HTML(http.StatusOK, tplReleasesList)
}
} }
// ReleasesFeedRSS get feeds for releases in RSS format // ReleasesFeedRSS get feeds for releases in RSS format
@ -282,7 +282,7 @@ func SingleRelease(ctx *context.Context) {
} }
ctx.Data["Releases"] = []*repo_model.Release{release} ctx.Data["Releases"] = []*repo_model.Release{release}
ctx.HTML(http.StatusOK, tplReleasesList) ctx.HTML(http.StatusOK, tplReleases)
} }
// LatestRelease redirects to the latest release // LatestRelease redirects to the latest release

View File

@ -710,14 +710,7 @@ func Home(ctx *context.Context) {
if setting.Other.EnableFeed { if setting.Other.EnableFeed {
isFeed, _, showFeedType := feed.GetFeedType(ctx.Params(":reponame"), ctx.Req) isFeed, _, showFeedType := feed.GetFeedType(ctx.Params(":reponame"), ctx.Req)
if isFeed { if isFeed {
switch { feed.ShowRepoFeed(ctx, ctx.Repo.Repository, showFeedType)
case ctx.Link == fmt.Sprintf("%s.%s", ctx.Repo.RepoLink, showFeedType):
feed.ShowRepoFeed(ctx, ctx.Repo.Repository, showFeedType)
case ctx.Repo.TreePath == "":
feed.ShowBranchFeed(ctx, ctx.Repo.Repository, showFeedType)
case ctx.Repo.TreePath != "":
feed.ShowFileFeed(ctx, ctx.Repo.Repository, showFeedType)
}
return return
} }
} }

View File

@ -1198,7 +1198,7 @@ func RegisterRoutes(m *web.Route) {
}, context.RepoMustNotBeArchived(), reqRepoCodeWriter, repo.MustBeNotEmpty) }, context.RepoMustNotBeArchived(), reqRepoCodeWriter, repo.MustBeNotEmpty)
}, reqSignIn, context.RepoAssignment, context.UnitTypes()) }, reqSignIn, context.RepoAssignment, context.UnitTypes())
// Tags // Releases
m.Group("/{username}/{reponame}", func() { m.Group("/{username}/{reponame}", func() {
m.Group("/tags", func() { m.Group("/tags", func() {
m.Get("", repo.TagsList) m.Get("", repo.TagsList)
@ -1207,12 +1207,6 @@ func RegisterRoutes(m *web.Route) {
}, func(ctx *context.Context) { }, func(ctx *context.Context) {
ctx.Data["EnableFeed"] = setting.Other.EnableFeed ctx.Data["EnableFeed"] = setting.Other.EnableFeed
}, repo.MustBeNotEmpty, reqRepoCodeReader, context.RepoRefByType(context.RepoRefTag, true)) }, repo.MustBeNotEmpty, reqRepoCodeReader, context.RepoRefByType(context.RepoRefTag, true))
m.Post("/tags/delete", repo.DeleteTag, reqSignIn,
repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoCodeWriter, context.RepoRef())
}, reqSignIn, context.RepoAssignment, context.UnitTypes())
// Releases
m.Group("/{username}/{reponame}", func() {
m.Group("/releases", func() { m.Group("/releases", func() {
m.Get("/", repo.Releases) m.Get("/", repo.Releases)
m.Get("/tag/*", repo.SingleRelease) m.Get("/tag/*", repo.SingleRelease)
@ -1230,6 +1224,8 @@ func RegisterRoutes(m *web.Route) {
m.Post("/attachments", repo.UploadReleaseAttachment) m.Post("/attachments", repo.UploadReleaseAttachment)
m.Post("/attachments/remove", repo.DeleteAttachment) m.Post("/attachments/remove", repo.DeleteAttachment)
}, reqSignIn, repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoReleaseWriter, context.RepoRef()) }, reqSignIn, repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoReleaseWriter, context.RepoRef())
m.Post("/tags/delete", repo.DeleteTag, reqSignIn,
repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoCodeWriter, context.RepoRef())
m.Group("/releases", func() { m.Group("/releases", func() {
m.Get("/edit/*", repo.EditRelease) m.Get("/edit/*", repo.EditRelease)
m.Post("/edit/*", web.Bind(forms.EditReleaseForm{}), repo.EditReleasePost) m.Post("/edit/*", web.Bind(forms.EditReleaseForm{}), repo.EditReleasePost)
@ -1454,9 +1450,6 @@ func RegisterRoutes(m *web.Route) {
m.Get("/cherry-pick/{sha:([a-f0-9]{7,40})$}", repo.SetEditorconfigIfExists, repo.CherryPick) m.Get("/cherry-pick/{sha:([a-f0-9]{7,40})$}", repo.SetEditorconfigIfExists, repo.CherryPick)
}, repo.MustBeNotEmpty, context.RepoRef(), reqRepoCodeReader) }, repo.MustBeNotEmpty, context.RepoRef(), reqRepoCodeReader)
m.Get("/rss/branch/*", context.RepoRefByType(context.RepoRefBranch), feedEnabled, feed.RenderBranchFeed)
m.Get("/atom/branch/*", context.RepoRefByType(context.RepoRefBranch), feedEnabled, feed.RenderBranchFeed)
m.Group("/src", func() { m.Group("/src", func() {
m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.Home) m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.Home)
m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.Home) m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.Home)
@ -1515,7 +1508,7 @@ func RegisterRoutes(m *web.Route) {
m.GetOptions("/objects/{head:[0-9a-f]{2}}/{hash:[0-9a-f]{38}}", repo.GetLooseObject) m.GetOptions("/objects/{head:[0-9a-f]{2}}/{hash:[0-9a-f]{38}}", repo.GetLooseObject)
m.GetOptions("/objects/pack/pack-{file:[0-9a-f]{40}}.pack", repo.GetPackFile) m.GetOptions("/objects/pack/pack-{file:[0-9a-f]{40}}.pack", repo.GetPackFile)
m.GetOptions("/objects/pack/pack-{file:[0-9a-f]{40}}.idx", repo.GetIdxFile) m.GetOptions("/objects/pack/pack-{file:[0-9a-f]{40}}.idx", repo.GetIdxFile)
}, ignSignInAndCsrf, repo.HTTPGitEnabledHandler, repo.CorsHandler(), context_service.UserAssignmentWeb()) }, ignSignInAndCsrf, context_service.UserAssignmentWeb())
}) })
}) })
// ***** END: Repository ***** // ***** END: Repository *****

View File

@ -18,12 +18,14 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"code.gitea.io/gitea/modules/generate"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"github.com/golang-jwt/jwt/v4" "github.com/golang-jwt/jwt/v4"
"github.com/minio/sha256-simd" "github.com/minio/sha256-simd"
ini "gopkg.in/ini.v1"
) )
// ErrInvalidAlgorithmType represents an invalid algorithm error. // ErrInvalidAlgorithmType represents an invalid algorithm error.
@ -314,7 +316,8 @@ func InitSigningKey() error {
case "HS384": case "HS384":
fallthrough fallthrough
case "HS512": case "HS512":
key, err = loadSymmetricKey() key, err = loadOrCreateSymmetricKey()
case "RS256": case "RS256":
fallthrough fallthrough
case "RS384": case "RS384":
@ -329,6 +332,7 @@ func InitSigningKey() error {
fallthrough fallthrough
case "EdDSA": case "EdDSA":
key, err = loadOrCreateAsymmetricKey() key, err = loadOrCreateAsymmetricKey()
default: default:
return ErrInvalidAlgorithmType{setting.OAuth2.JWTSigningAlgorithm} return ErrInvalidAlgorithmType{setting.OAuth2.JWTSigningAlgorithm}
} }
@ -347,16 +351,22 @@ func InitSigningKey() error {
return nil return nil
} }
// loadSymmetricKey checks if the configured secret is valid. // loadOrCreateSymmetricKey checks if the configured secret is valid.
// If it is not valid, it will return an error. // If it is not valid a new secret is created and saved in the configuration file.
func loadSymmetricKey() (interface{}, error) { func loadOrCreateSymmetricKey() (interface{}, error) {
key := make([]byte, 32) key := make([]byte, 32)
n, err := base64.RawURLEncoding.Decode(key, []byte(setting.OAuth2.JWTSecretBase64)) n, err := base64.RawURLEncoding.Decode(key, []byte(setting.OAuth2.JWTSecretBase64))
if err != nil { if err != nil || n != 32 {
return nil, err key, err = generate.NewJwtSecret()
} if err != nil {
if n != 32 { log.Fatal("error generating JWT secret: %v", err)
return nil, fmt.Errorf("JWT secret must be 32 bytes long") return nil, err
}
setting.CreateOrAppendToCustomConf("oauth2.JWT_SECRET", func(cfg *ini.File) {
secretBase64 := base64.RawURLEncoding.EncodeToString(key)
cfg.Section("oauth2").Key("JWT_SECRET").SetValue(secretBase64)
})
} }
return key, nil return key, nil

View File

@ -26,8 +26,8 @@
<td><a href="{{AppSubUrl}}/admin/auths/{{.ID}}">{{.Name}}</a></td> <td><a href="{{AppSubUrl}}/admin/auths/{{.ID}}">{{.Name}}</a></td>
<td>{{.TypeName}}</td> <td>{{.TypeName}}</td>
<td>{{if .IsActive}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</td> <td>{{if .IsActive}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</td>
<td>{{DateTime "short" .UpdatedUnix}}</td> <td>{{DateTime "short" .UpdatedUnix.FormatLong .UpdatedUnix.FormatShort}}</td>
<td>{{DateTime "short" .CreatedUnix}}</td> <td>{{DateTime "short" .CreatedUnix.FormatLong .CreatedUnix.FormatShort}}</td>
<td><a href="{{AppSubUrl}}/admin/auths/{{.ID}}">{{svg "octicon-pencil"}}</a></td> <td><a href="{{AppSubUrl}}/admin/auths/{{.ID}}">{{svg "octicon-pencil"}}</a></td>
</tr> </tr>
{{end}} {{end}}

View File

@ -21,8 +21,8 @@
<td><button type="submit" class="ui green button" name="op" value="{{.Name}}" title="{{$.locale.Tr "admin.dashboard.operation_run"}}">{{svg "octicon-triangle-right"}}</button></td> <td><button type="submit" class="ui green button" name="op" value="{{.Name}}" title="{{$.locale.Tr "admin.dashboard.operation_run"}}">{{svg "octicon-triangle-right"}}</button></td>
<td>{{$.locale.Tr (printf "admin.dashboard.%s" .Name)}}</td> <td>{{$.locale.Tr (printf "admin.dashboard.%s" .Name)}}</td>
<td>{{.Spec}}</td> <td>{{.Spec}}</td>
<td>{{DateTime "full" (DateFmtLong .Next)}}</td> <td>{{DateTime "full" (DateFmtLong .Next) (DateFmtLong .Next)}}</td>
<td>{{if gt .Prev.Year 1}}{{DateTime "full" .Prev}}{{else}}N/A{{end}}</td> <td>{{if gt .Prev.Year 1}}{{DateTime "full" (DateFmtLong .Prev) (DateFmtLong .Prev)}}{{else}}N/A{{end}}</td>
<td>{{.ExecTimes}}</td> <td>{{.ExecTimes}}</td>
<td {{if ne .Status ""}}data-tooltip-content="{{.FormatLastMessage $.locale}}"{{end}} >{{if eq .Status ""}}{{else if eq .Status "finished"}}{{svg "octicon-check" 16}}{{else}}{{svg "octicon-x" 16}}{{end}}</td> <td {{if ne .Status ""}}data-tooltip-content="{{.FormatLastMessage $.locale}}"{{end}} >{{if eq .Status ""}}{{else if eq .Status "finished"}}{{svg "octicon-check" 16}}{{else}}{{svg "octicon-x" 16}}{{end}}</td>
</tr> </tr>

View File

@ -26,7 +26,7 @@
<td>{{.ID}}</td> <td>{{.ID}}</td>
<td>{{$.locale.Tr .TrStr}}</td> <td>{{$.locale.Tr .TrStr}}</td>
<td class="view-detail"><span class="notice-description text truncate">{{.Description}}</span></td> <td class="view-detail"><span class="notice-description text truncate">{{.Description}}</span></td>
<td>{{DateTime "short" .CreatedUnix}}</td> <td>{{DateTime "short" .CreatedUnix.FormatLong .CreatedUnix.FormatShort}}</td>
<td><a href="#">{{svg "octicon-note" 16 "view-detail"}}</a></td> <td><a href="#">{{svg "octicon-note" 16 "view-detail"}}</a></td>
</tr> </tr>
{{end}} {{end}}

View File

@ -41,7 +41,7 @@
<td>{{.NumTeams}}</td> <td>{{.NumTeams}}</td>
<td>{{.NumMembers}}</td> <td>{{.NumMembers}}</td>
<td>{{.NumRepos}}</td> <td>{{.NumRepos}}</td>
<td>{{DateTime "short" .CreatedUnix}}</td> <td>{{DateTime "short" .CreatedUnix.FormatLong .CreatedUnix.FormatShort}}</td>
<td><a href="{{.OrganisationLink}}/settings">{{svg "octicon-pencil"}}</a></td> <td><a href="{{.OrganisationLink}}/settings">{{svg "octicon-pencil"}}</a></td>
</tr> </tr>
{{end}} {{end}}

View File

@ -65,7 +65,7 @@
{{end}} {{end}}
</td> </td>
<td>{{FileSize .CalculateBlobSize}}</td> <td>{{FileSize .CalculateBlobSize}}</td>
<td>{{DateTime "short" .Version.CreatedUnix}}</td> <td>{{DateTime "short" .Version.CreatedUnix.FormatLong .Version.CreatedUnix.FormatShort}}</td>
<td><a class="delete-button" href="" data-url="{{$.Link}}/delete?page={{$.Page.Paginater.Current}}&sort={{$.SortType}}" data-id="{{.Version.ID}}" data-name="{{.Package.Name}}" data-data-version="{{.Version.Version}}">{{svg "octicon-trash"}}</a></td> <td><a class="delete-button" href="" data-url="{{$.Link}}/delete?page={{$.Page.Paginater.Current}}&sort={{$.SortType}}" data-id="{{.Version.ID}}" data-name="{{.Package.Name}}" data-data-version="{{.Version.Version}}">{{svg "octicon-trash"}}</a></td>
</tr> </tr>
{{end}} {{end}}

View File

@ -155,8 +155,8 @@
{{range .Queue.Workers}} {{range .Queue.Workers}}
<tr> <tr>
<td>{{.Workers}}{{if .IsFlusher}}<span title="{{$.locale.Tr "admin.monitor.queue.flush"}}">{{svg "octicon-sync"}}</span>{{end}}</td> <td>{{.Workers}}{{if .IsFlusher}}<span title="{{$.locale.Tr "admin.monitor.queue.flush"}}">{{svg "octicon-sync"}}</span>{{end}}</td>
<td>{{DateTime "full" .Start}}</td> <td>{{DateTime "full" (DateFmtLong .Start) (DateFmtLong .Start)}}</td>
<td>{{if .HasTimeout}}{{DateTime "full" .Timeout}}{{else}}-{{end}}</td> <td>{{if .HasTimeout}}{{DateTime "full" (DateFmtLong .Timeout) (DateFmtLong .Timeout)}}{{else}}-{{end}}</td>
<td> <td>
<a class="delete-button" href="" data-url="{{$.Link}}/cancel/{{.PID}}" data-id="{{.PID}}" data-name="{{.Workers}}" title="{{$.locale.Tr "remove"}}">{{svg "octicon-trash"}}</a> <a class="delete-button" href="" data-url="{{$.Link}}/cancel/{{.PID}}" data-id="{{.PID}}" data-name="{{.Workers}}" title="{{$.locale.Tr "remove"}}">{{svg "octicon-trash"}}</a>
</td> </td>

View File

@ -80,7 +80,7 @@
<td>{{.NumForks}}</td> <td>{{.NumForks}}</td>
<td>{{.NumIssues}}</td> <td>{{.NumIssues}}</td>
<td>{{FileSize .Size}}</td> <td>{{FileSize .Size}}</td>
<td>{{DateTime "short" .CreatedUnix}}</td> <td>{{DateTime "short" .CreatedUnix.FormatLong .CreatedUnix.FormatShort}}</td>
<td><a class="delete-button" href="" data-url="{{$.Link}}/delete?page={{$.Page.Paginater.Current}}&sort={{$.SortType}}" data-id="{{.ID}}" data-name="{{.Name}}">{{svg "octicon-trash"}}</a></td> <td><a class="delete-button" href="" data-url="{{$.Link}}/delete?page={{$.Page.Paginater.Current}}&sort={{$.SortType}}" data-id="{{.ID}}" data-name="{{.Name}}">{{svg "octicon-trash"}}</a></td>
</tr> </tr>
{{end}} {{end}}

View File

@ -91,9 +91,9 @@
<td>{{if .IsRestricted}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</td> <td>{{if .IsRestricted}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</td>
<td>{{if index $.UsersTwoFaStatus .ID}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</td> <td>{{if index $.UsersTwoFaStatus .ID}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</td>
<td>{{.NumRepos}}</td> <td>{{.NumRepos}}</td>
<td>{{DateTime "short" .CreatedUnix}}</td> <td>{{DateTime "short" .CreatedUnix.FormatLong .CreatedUnix.FormatShort}}</td>
{{if .LastLoginUnix}} {{if .LastLoginUnix}}
<td>{{DateTime "short" .LastLoginUnix}}</td> <td>{{DateTime "short" .LastLoginUnix.FormatLong .LastLoginUnix.FormatShort}}</td>
{{else}} {{else}}
<td><span>{{$.locale.Tr "admin.users.never_login"}}</span></td> <td><span>{{$.locale.Tr "admin.users.never_login"}}</span></td>
{{end}} {{end}}

View File

@ -23,7 +23,7 @@
{{svg "octicon-link"}} {{svg "octicon-link"}}
<a href="{{.Website}}" rel="nofollow">{{.Website}}</a> <a href="{{.Website}}" rel="nofollow">{{.Website}}</a>
{{end}} {{end}}
{{svg "octicon-clock"}} {{$.locale.Tr "user.join_on"}} {{DateTime "short" .CreatedUnix}} {{svg "octicon-clock"}} {{$.locale.Tr "user.join_on"}} {{DateTime "short" .CreatedUnix.FormatLong .CreatedUnix.FormatShort}}
</div> </div>
</div> </div>
</div> </div>

View File

@ -18,7 +18,7 @@
{{svg "octicon-mail"}} {{svg "octicon-mail"}}
<a href="mailto:{{.Email}}" rel="nofollow">{{.Email}}</a> <a href="mailto:{{.Email}}" rel="nofollow">{{.Email}}</a>
{{end}} {{end}}
{{svg "octicon-clock"}} {{$.locale.Tr "user.join_on"}} {{DateTime "short" .CreatedUnix}} {{svg "octicon-clock"}} {{$.locale.Tr "user.join_on"}} {{DateTime "short" .CreatedUnix.FormatLong .CreatedUnix.FormatShort}}
</div> </div>
</div> </div>
</div> </div>

View File

@ -22,7 +22,7 @@
<td><a href="{{.FullWebLink}}">{{.Version.Version}}</a></td> <td><a href="{{.FullWebLink}}">{{.Version.Version}}</a></td>
<td><a href="{{.Creator.HomeLink}}">{{.Creator.Name}}</a></td> <td><a href="{{.Creator.HomeLink}}">{{.Creator.Name}}</a></td>
<td>{{FileSize .CalculateBlobSize}}</td> <td>{{FileSize .CalculateBlobSize}}</td>
<td>{{DateTime "short" .Version.CreatedUnix}}</td> <td>{{DateTime "short" .Version.CreatedUnix.FormatLong .Version.CreatedUnix.FormatShort}}</td>
</tr> </tr>
{{else}} {{else}}
<tr> <tr>

View File

@ -86,7 +86,7 @@
{{range .LatestVersions}} {{range .LatestVersions}}
<div class="item gt-df"> <div class="item gt-df">
<a class="gt-f1" href="{{$.PackageDescriptor.PackageWebLink}}/{{PathEscape .LowerVersion}}">{{.Version}}</a> <a class="gt-f1" href="{{$.PackageDescriptor.PackageWebLink}}/{{PathEscape .LowerVersion}}">{{.Version}}</a>
<span class="text small">{{DateTime "short" .CreatedUnix}}</span> <span class="text small">{{DateTime "short" (.CreatedUnix.FormatDate) (.CreatedUnix.FormatDate)}}</span>
</div> </div>
{{end}} {{end}}
</div> </div>

View File

@ -2,7 +2,7 @@
<div role="main" aria-label="{{.Title}}" class="page-content repository commits"> <div role="main" aria-label="{{.Title}}" class="page-content repository commits">
{{template "repo/header" .}} {{template "repo/header" .}}
<div class="ui container"> <div class="ui container">
<h2 class="ui header">{{DateTime "long" .DateFrom}} - {{DateTime "long" .DateUntil}} <h2 class="ui header">{{DateTime "long" .DateFrom .DateFrom}} - {{DateTime "long" .DateUntil .DateUntil}}
<div class="ui right"> <div class="ui right">
<!-- Period --> <!-- Period -->
<div class="ui floating dropdown jump filter"> <div class="ui floating dropdown jump filter">

View File

@ -26,11 +26,6 @@
{{svg "octicon-git-branch"}} {{svg "octicon-git-branch"}}
</button> </button>
{{end}} {{end}}
{{if .EnableFeed}}
<a role="button" class="ui basic button icon" href="{{$.FeedURL}}/rss/branch/{{PathEscapeSegments .DefaultBranch}}">
{{svg "octicon-rss"}}
</a>
{{end}}
{{if not $.DisableDownloadSourceArchives}} {{if not $.DisableDownloadSourceArchives}}
<button class="ui basic jump dropdown icon button" data-tooltip-content="{{$.locale.Tr "repo.branch.download" ($.DefaultBranch)}}"> <button class="ui basic jump dropdown icon button" data-tooltip-content="{{$.locale.Tr "repo.branch.download" ($.DefaultBranch)}}">
{{svg "octicon-download"}} {{svg "octicon-download"}}
@ -118,11 +113,6 @@
{{svg "octicon-git-branch"}} {{svg "octicon-git-branch"}}
</button> </button>
{{end}} {{end}}
{{if $.EnableFeed}}
<a role="button" class="ui basic button icon" href="{{$.FeedURL}}/rss/branch/{{PathEscapeSegments .Name}}">
{{svg "octicon-rss"}}
</a>
{{end}}
{{if and (not .IsDeleted) (not $.DisableDownloadSourceArchives)}} {{if and (not .IsDeleted) (not $.DisableDownloadSourceArchives)}}
<button class="ui basic jump dropdown icon button" data-tooltip-content="{{$.locale.Tr "repo.branch.download" (.Name)}}"> <button class="ui basic jump dropdown icon button" data-tooltip-content="{{$.locale.Tr "repo.branch.download" (.Name)}}">
{{svg "octicon-download"}} {{svg "octicon-download"}}

View File

@ -42,8 +42,6 @@
'branches': {{.root.Branches}}, 'branches': {{.root.Branches}},
'tags': {{.root.Tags}}, 'tags': {{.root.Tags}},
'defaultBranch': {{$defaultBranch}}, 'defaultBranch': {{$defaultBranch}},
'enableFeed': {{.root.EnableFeed}},
'rssURLPrefix': '{{$.root.RepoLink}}/rss/branch/',
'branchURLPrefix': '{{if .branchURLPrefix}}{{.branchURLPrefix}}{{else}}{{$.root.RepoLink}}/{{if $.root.PageIsCommits}}commits{{else}}src{{end}}/branch/{{end}}', 'branchURLPrefix': '{{if .branchURLPrefix}}{{.branchURLPrefix}}{{else}}{{$.root.RepoLink}}/{{if $.root.PageIsCommits}}commits{{else}}src{{end}}/branch/{{end}}',
'branchURLSuffix': '{{if .branchURLSuffix}}{{.branchURLSuffix}}{{else}}{{if $.root.TreePath}}/{{PathEscapeSegments $.root.TreePath}}{{end}}{{end}}', 'branchURLSuffix': '{{if .branchURLSuffix}}{{.branchURLSuffix}}{{else}}{{if $.root.TreePath}}/{{PathEscapeSegments $.root.TreePath}}{{end}}{{end}}',
'tagURLPrefix': '{{if .tagURLPrefix}}{{.tagURLPrefix}}{{else if .release}}{{$.root.RepoLink}}/compare/{{else}}{{$.root.RepoLink}}/{{if $.root.PageIsCommits}}commits{{else}}src{{end}}/tag/{{end}}', 'tagURLPrefix': '{{if .tagURLPrefix}}{{.tagURLPrefix}}{{else if .release}}{{$.root.RepoLink}}/compare/{{else}}{{$.root.RepoLink}}/{{if $.root.PageIsCommits}}commits{{else}}src{{end}}/tag/{{end}}',

View File

@ -63,13 +63,13 @@ git push -u origin {{.Repository.DefaultBranch}}</code></pre>
git push -u origin {{.Repository.DefaultBranch}}</code></pre> git push -u origin {{.Repository.DefaultBranch}}</code></pre>
</div> </div>
</div> </div>
{{template "repo/clone_script" .}}
{{end}} {{end}}
{{else}} {{else}}
<div class="ui segment center"> <div class="ui segment center">
{{.locale.Tr "repo.empty_message"}} {{.locale.Tr "repo.empty_message"}}
</div> </div>
{{end}} {{end}}
{{template "repo/clone_script" .}}
</div> </div>
</div> </div>
</div> </div>

View File

@ -63,13 +63,12 @@
{{$n := len .TreeNames}} {{$n := len .TreeNames}}
{{$l := Eval $n "-" 1}} {{$l := Eval $n "-" 1}}
<!-- If home page, show new pr. If not, show breadcrumb --> <!-- If home page, show new pr. If not, show breadcrumb -->
{{if and (eq $n 0) .CanCompareOrPull .IsViewBranch (not .Repository.IsArchived)}}
<a id="new-pull-request" role="button" class="ui compact basic button" href="{{CompareLink .BaseRepo .Repository .BranchName}}"
data-tooltip-content="{{if .PullRequestCtx.Allowed}}{{.locale.Tr "repo.pulls.compare_changes"}}{{else}}{{.locale.Tr "action.compare_branch"}}{{end}}">
{{svg "octicon-git-pull-request"}}
</a>
{{end}}
{{if eq $n 0}} {{if eq $n 0}}
{{if and .CanCompareOrPull .IsViewBranch (not .Repository.IsArchived)}}
<a href="{{CompareLink .BaseRepo .Repository .BranchName}}">
<button id="new-pull-request" class="ui compact basic button" data-tooltip-content="{{if .PullRequestCtx.Allowed}}{{.locale.Tr "repo.pulls.compare_changes"}}{{else}}{{.locale.Tr "action.compare_branch"}}{{end}}"><span class="text">{{svg "octicon-git-pull-request"}}</span></button>
</a>
{{end}}
<a href="{{.Repository.Link}}/find/{{.BranchNameSubURL}}" class="ui compact basic button">{{.locale.Tr "repo.find_file.go_to_file"}}</a> <a href="{{.Repository.Link}}/find/{{.BranchNameSubURL}}" class="ui compact basic button">{{.locale.Tr "repo.find_file.go_to_file"}}</a>
{{end}} {{end}}

View File

@ -35,7 +35,7 @@
{{else}} {{else}}
{{svg "octicon-calendar"}} {{svg "octicon-calendar"}}
{{if .Milestone.DeadlineString}} {{if .Milestone.DeadlineString}}
<span {{if .IsOverdue}}class="overdue"{{end}}>{{DateTime "short" .Milestone.DeadlineString}}</span> <span {{if .IsOverdue}}class="overdue"{{end}}>{{DateTime "short" .Milestone.DeadlineString .Milestone.DeadlineString}}</span>
{{else}} {{else}}
{{$.locale.Tr "repo.milestones.no_due_date"}} {{$.locale.Tr "repo.milestones.no_due_date"}}
{{end}} {{end}}

View File

@ -77,7 +77,7 @@
{{else}} {{else}}
{{svg "octicon-calendar"}} {{svg "octicon-calendar"}}
{{if .DeadlineString}} {{if .DeadlineString}}
<span {{if .IsOverdue}}class="overdue"{{end}}>{{DateTime "short" .DeadlineString}}</span> <span {{if .IsOverdue}}class="overdue"{{end}}>{{DateTime "short" .DeadlineString .DeadlineString}}</span>
{{else}} {{else}}
{{$.locale.Tr "repo.milestones.no_due_date"}} {{$.locale.Tr "repo.milestones.no_due_date"}}
{{end}} {{end}}

View File

@ -295,7 +295,7 @@
{{template "shared/user/avatarlink" dict "Context" $.Context "user" .Poster}} {{template "shared/user/avatarlink" dict "Context" $.Context "user" .Poster}}
<span class="text grey muted-links"> <span class="text grey muted-links">
{{template "shared/user/authorlink" .Poster}} {{template "shared/user/authorlink" .Poster}}
{{$.locale.Tr "repo.issues.due_date_added" (DateTime "long" .Content) $createdStr | Safe}} {{$.locale.Tr "repo.issues.due_date_added" (DateTime "long" .Content .Content) $createdStr | Safe}}
</span> </span>
</div> </div>
{{else if eq .Type 17}} {{else if eq .Type 17}}
@ -305,8 +305,8 @@
<span class="text grey muted-links"> <span class="text grey muted-links">
{{template "shared/user/authorlink" .Poster}} {{template "shared/user/authorlink" .Poster}}
{{$parsedDeadline := .Content | ParseDeadline}} {{$parsedDeadline := .Content | ParseDeadline}}
{{$from := DateTime "long" (index $parsedDeadline 1)}} {{$from := DateTime "long" (index $parsedDeadline 1) (index $parsedDeadline 1)}}
{{$to := DateTime "long" (index $parsedDeadline 0)}} {{$to := DateTime "long" (index $parsedDeadline 0) (index $parsedDeadline 0)}}
{{$.locale.Tr "repo.issues.due_date_modified" $to $from $createdStr | Safe}} {{$.locale.Tr "repo.issues.due_date_modified" $to $from $createdStr | Safe}}
</span> </span>
</div> </div>
@ -316,7 +316,7 @@
{{template "shared/user/avatarlink" dict "Context" $.Context "user" .Poster}} {{template "shared/user/avatarlink" dict "Context" $.Context "user" .Poster}}
<span class="text grey muted-links"> <span class="text grey muted-links">
{{template "shared/user/authorlink" .Poster}} {{template "shared/user/authorlink" .Poster}}
{{$.locale.Tr "repo.issues.due_date_remove" (DateTime "long" .Content) $createdStr | Safe}} {{$.locale.Tr "repo.issues.due_date_remove" (DateTime "long" .Content .Content) $createdStr | Safe}}
</span> </span>
</div> </div>
{{else if eq .Type 19}} {{else if eq .Type 19}}

View File

@ -385,7 +385,7 @@
<div class="gt-df gt-sb gt-ac"> <div class="gt-df gt-sb gt-ac">
<div class="due-date {{if .Issue.IsOverdue}}text red{{end}}" {{if .Issue.IsOverdue}}data-tooltip-content="{{.locale.Tr "repo.issues.due_date_overdue"}}"{{end}}> <div class="due-date {{if .Issue.IsOverdue}}text red{{end}}" {{if .Issue.IsOverdue}}data-tooltip-content="{{.locale.Tr "repo.issues.due_date_overdue"}}"{{end}}>
{{svg "octicon-calendar" 16 "gt-mr-3"}} {{svg "octicon-calendar" 16 "gt-mr-3"}}
{{DateTime "long" .Issue.DeadlineUnix}} {{DateTime "long" .Issue.DeadlineUnix.FormatDate .Issue.DeadlineUnix.FormatDate}}
</div> </div>
<div> <div>
{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}} {{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}

View File

@ -1,16 +1,73 @@
{{template "base/head" .}} {{template "base/head" .}}
<div role="main" aria-label="{{.Title}}" class="page-content repository releases"> <div role="main" aria-label="{{.Title}}" class="page-content repository release">
{{template "repo/header" .}} {{template "repo/header" .}}
<div class="ui container"> <div class="ui container">
{{template "base/alert" .}} {{template "base/alert" .}}
{{template "repo/sub_menu_release_tag" .}} <h2 class="ui compact small menu header">
{{if .Permission.CanRead $.UnitTypeReleases}}
{{if .CanCreateRelease}} <a class="{{if (and (not .PageIsSingleTag) (not .PageIsTagList))}}active {{end}}item" href="{{.RepoLink}}/releases">{{.locale.Tr "repo.release.releases"}}</a>
{{end}}
{{if .Permission.CanRead $.UnitTypeCode}}
<a class="{{if (or .PageIsSingleTag .PageIsTagList)}}active {{end}}item" href="{{.RepoLink}}/tags">{{.locale.Tr "repo.release.tags"}}</a>
{{end}}
</h2>
{{if .EnableFeed}}
<a href="{{.RepoLink}}/{{if .PageIsTagList}}tags{{else}}releases{{end}}.rss"><i class="ui grey icon gt-ml-3" data-tooltip-content="{{.locale.Tr "rss_feed"}}">{{svg "octicon-rss" 18}}</i></a>
{{end}}
{{if (and .CanCreateRelease (not .PageIsTagList))}}
<a class="ui right small green button" href="{{$.RepoLink}}/releases/new"> <a class="ui right small green button" href="{{$.RepoLink}}/releases/new">
{{.locale.Tr "repo.release.new_release"}} {{.locale.Tr "repo.release.new_release"}}
</a> </a>
{{end}} {{end}}
{{if .PageIsTagList}}
<div class="ui divider"></div>
{{if gt .ReleasesNum 0}}
<h4 class="ui top attached header">
<div class="five wide column gt-df gt-ac">
{{svg "octicon-tag" 16 "gt-mr-2"}}{{.locale.Tr "repo.release.tags"}}
</div>
</h4>
<div class="ui attached table segment">
<table class="ui very basic striped fixed table single line" id="tags-table">
<thead></thead>
<tbody class="tag-list">
{{range $idx, $release := .Releases}}
<tr>
<td class="tag">
<h3 class="release-tag-name gt-mb-3">
<a class="gt-df gt-ac" href="{{$.RepoLink}}/releases/tag/{{.TagName | PathEscapeSegments}}" rel="nofollow">{{.TagName}}</a>
</h3>
<div class="download gt-df gt-ac">
{{if $.Permission.CanRead $.UnitTypeCode}}
{{if .CreatedUnix}}
<span class="gt-mr-3">{{svg "octicon-clock" 16 "gt-mr-2"}}{{TimeSinceUnix .CreatedUnix $.locale}}</span>
{{end}}
<a class="gt-mr-3 gt-mono muted" href="{{$.RepoLink}}/src/commit/{{.Sha1}}" rel="nofollow">{{svg "octicon-git-commit" 16 "gt-mr-2"}}{{ShortSha .Sha1}}</a>
{{if not $.DisableDownloadSourceArchives}}
<a class="archive-link gt-mr-3 muted" href="{{$.RepoLink}}/archive/{{.TagName | PathEscapeSegments}}.zip" rel="nofollow">{{svg "octicon-file-zip" 16 "gt-mr-2"}}ZIP</a>
<a class="archive-link gt-mr-3 muted" href="{{$.RepoLink}}/archive/{{.TagName | PathEscapeSegments}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip" 16 "gt-mr-2"}}TAR.GZ</a>
{{end}}
{{if (and $.CanCreateRelease $release.IsTag)}}
<a class="gt-mr-3 muted" href="{{$.RepoLink}}/releases/new?tag={{.TagName}}">{{svg "octicon-tag" 16 "gt-mr-2"}}{{$.locale.Tr "repo.release.new_release"}}</a>
{{end}}
{{if (and ($.Permission.CanWrite $.UnitTypeCode) $release.IsTag)}}
<a class="ui delete-button gt-mr-3 muted" data-url="{{$.RepoLink}}/tags/delete" data-id="{{.ID}}">
{{svg "octicon-trash" 16 "gt-mr-2"}}{{$.locale.Tr "repo.release.delete_tag"}}
</a>
{{end}}
{{if (not $release.IsTag)}}
<a class="gt-mr-3 muted" href="{{$.RepoLink}}/releases/tag/{{.TagName | PathEscapeSegments}}">{{svg "octicon-tag" 16 "gt-mr-2"}}{{$.locale.Tr "repo.release.detail"}}</a>
{{end}}
{{end}}
</div>
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
{{end}}
{{else}}
<ul id="release-list"> <ul id="release-list">
{{range $idx, $release := .Releases}} {{range $idx, $release := .Releases}}
<li class="ui grid"> <li class="ui grid">
@ -121,7 +178,7 @@
</li> </li>
{{end}} {{end}}
</ul> </ul>
{{end}}
{{template "base/paginate" .}} {{template "base/paginate" .}}
</div> </div>
</div> </div>

View File

@ -60,7 +60,7 @@
{{.Fingerprint}} {{.Fingerprint}}
</div> </div>
<div class="activity meta"> <div class="activity meta">
<i>{{$.locale.Tr "settings.add_on"}} <span>{{DateTime "short" .CreatedUnix}}</span> — {{svg "octicon-info"}} {{if .HasUsed}}{{$.locale.Tr "settings.last_used"}} <span {{if .HasRecentActivity}}class="green"{{end}}>{{DateTime "short" .UpdatedUnix.FormatLong .UpdatedUnix.FormatShort}}</span>{{else}}{{$.locale.Tr "settings.no_activity"}}{{end}} - <span>{{$.locale.Tr "settings.can_read_info"}}{{if not .IsReadOnly}} / {{$.locale.Tr "settings.can_write_info"}} {{end}}</span></i> <i>{{$.locale.Tr "settings.add_on"}} <span>{{DateTime "short" .CreatedUnix.FormatLong .CreatedUnix.FormatShort}}</span> — {{svg "octicon-info"}} {{if .HasUsed}}{{$.locale.Tr "settings.last_used"}} <span {{if .HasRecentActivity}}class="green"{{end}}>{{DateTime "short" .UpdatedUnix.FormatLong .UpdatedUnix.FormatShort}}</span>{{else}}{{$.locale.Tr "settings.no_activity"}}{{end}} - <span>{{$.locale.Tr "settings.can_read_info"}}{{if not .IsReadOnly}} / {{$.locale.Tr "settings.can_write_info"}} {{end}}</span></i>
</div> </div>
</div> </div>
</div> </div>

View File

@ -89,7 +89,7 @@
<tr> <tr>
<td>{{(MirrorRemoteAddress $.Context .Repository .Mirror.GetRemoteName false).Address}}</td> <td>{{(MirrorRemoteAddress $.Context .Repository .Mirror.GetRemoteName false).Address}}</td>
<td>{{$.locale.Tr "repo.settings.mirror_settings.direction.pull"}}</td> <td>{{$.locale.Tr "repo.settings.mirror_settings.direction.pull"}}</td>
<td>{{DateTime "full" .Mirror.UpdatedUnix}}</td> <td>{{DateTime "full" .Mirror.UpdatedUnix.FormatLong .Mirror.UpdatedUnix.AsTime}}</td>
<td class="right aligned"> <td class="right aligned">
<form method="post" style="display: inline-block"> <form method="post" style="display: inline-block">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
@ -167,7 +167,7 @@
{{$address := MirrorRemoteAddress $.Context $.Repository .GetRemoteName true}} {{$address := MirrorRemoteAddress $.Context $.Repository .GetRemoteName true}}
<td class="gt-word-break">{{$address.Address}}</td> <td class="gt-word-break">{{$address.Address}}</td>
<td>{{$.locale.Tr "repo.settings.mirror_settings.direction.push"}}</td> <td>{{$.locale.Tr "repo.settings.mirror_settings.direction.push"}}</td>
<td>{{if .LastUpdateUnix}}{{DateTime "full" .LastUpdateUnix}}{{else}}{{$.locale.Tr "never"}}{{end}} {{if .LastError}}<div class="ui red label" data-tooltip-content="{{.LastError}}">{{$.locale.Tr "error"}}</div>{{end}}</td> <td>{{if .LastUpdateUnix}}{{DateTime "full" .LastUpdateUnix.FormatLong .LastUpdateUnix.AsTime}}{{else}}{{$.locale.Tr "never"}}{{end}} {{if .LastError}}<div class="ui red label" data-tooltip-content="{{.LastError}}">{{$.locale.Tr "error"}}</div>{{end}}</td>
<td class="right aligned"> <td class="right aligned">
<form method="post" style="display: inline-block"> <form method="post" style="display: inline-block">
{{$.CsrfTokenHtml}} {{$.CsrfTokenHtml}}

View File

@ -10,7 +10,7 @@
<a href="{{.RepoLink}}/branches">{{svg "octicon-git-branch"}} <b>{{.BranchesCount}}</b> {{.locale.TrN .BranchesCount "repo.branch" "repo.branches"}}</a> <a href="{{.RepoLink}}/branches">{{svg "octicon-git-branch"}} <b>{{.BranchesCount}}</b> {{.locale.TrN .BranchesCount "repo.branch" "repo.branches"}}</a>
</div> </div>
{{if $.Permission.CanRead $.UnitTypeCode}} {{if $.Permission.CanRead $.UnitTypeCode}}
<div class="item{{if .PageIsTagList}} active{{end}}"> <div class="item">
<a href="{{.RepoLink}}/tags">{{svg "octicon-tag"}} <b>{{.NumTags}}</b> {{.locale.TrN .NumTags "repo.tag" "repo.tags"}}</a> <a href="{{.RepoLink}}/tags">{{svg "octicon-tag"}} <b>{{.NumTags}}</b> {{.locale.TrN .NumTags "repo.tag" "repo.tags"}}</a>
</div> </div>
{{end}} {{end}}

View File

@ -1,17 +0,0 @@
{{$canReadReleases := $.Permission.CanRead $.UnitTypeReleases}}
{{$canReadCode := $.Permission.CanRead $.UnitTypeCode}}
{{if $canReadReleases}}
<h2 class="ui compact small menu header">
<a class="{{if .PageIsReleaseList}}active {{end}}item" href="{{.RepoLink}}/releases">{{.locale.Tr "repo.release.releases"}}</a>
{{if $canReadCode}}
<a class="{{if .PageIsTagList}}active {{end}}item" href="{{.RepoLink}}/tags">{{.locale.Tr "repo.release.tags"}}</a>
{{end}}
</h2>
{{if .EnableFeed}}
<a href="{{.RepoLink}}/{{if .PageIsTagList}}tags{{else}}releases{{end}}.rss"><i class="ui grey icon gt-ml-3" data-tooltip-content="{{.locale.Tr "rss_feed"}}">{{svg "octicon-rss" 18}}</i></a>
{{end}}
{{else if $canReadCode}}
{{template "repo/sub_menu" .}}
{{end}}

View File

@ -1,85 +0,0 @@
{{template "base/head" .}}
<div role="main" aria-label="{{.Title}}" class="page-content repository tags">
{{template "repo/header" .}}
<div class="ui container">
{{template "base/alert" .}}
{{template "repo/sub_menu_release_tag" .}}
<div class="ui divider"></div>
<h4 class="ui top attached header">
<div class="five wide column gt-df gt-ac">
{{svg "octicon-tag" 16 "gt-mr-2"}}{{.locale.Tr "repo.release.tags"}}
</div>
</h4>
{{$canReadReleases := $.Permission.CanRead $.UnitTypeReleases}}
<div class="ui attached table segment">
<table class="ui very basic striped fixed table single line" id="tags-table">
<tbody class="tag-list">
{{range $idx, $release := .Releases}}
<tr>
<td class="tag">
<h3 class="release-tag-name gt-mb-3">
{{if $canReadReleases}}
<a class="gt-df gt-ac" href="{{$.RepoLink}}/releases/tag/{{.TagName | PathEscapeSegments}}" rel="nofollow">{{.TagName}}</a>
{{else}}
<a class="gt-df gt-ac" href="{{$.RepoLink}}/src/tag/{{.TagName | PathEscapeSegments}}" rel="nofollow">{{.TagName}}</a>
{{end}}
</h3>
<div class="download gt-df gt-ac">
{{if $.Permission.CanRead $.UnitTypeCode}}
{{if .CreatedUnix}}
<span class="gt-mr-3">{{svg "octicon-clock" 16 "gt-mr-2"}}{{TimeSinceUnix .CreatedUnix $.locale}}</span>
{{end}}
<a class="gt-mr-3 gt-mono muted" href="{{$.RepoLink}}/src/commit/{{.Sha1}}" rel="nofollow">{{svg "octicon-git-commit" 16 "gt-mr-2"}}{{ShortSha .Sha1}}</a>
{{if not $.DisableDownloadSourceArchives}}
<a class="archive-link gt-mr-3 muted" href="{{$.RepoLink}}/archive/{{.TagName | PathEscapeSegments}}.zip" rel="nofollow">{{svg "octicon-file-zip" 16 "gt-mr-2"}}ZIP</a>
<a class="archive-link gt-mr-3 muted" href="{{$.RepoLink}}/archive/{{.TagName | PathEscapeSegments}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip" 16 "gt-mr-2"}}TAR.GZ</a>
{{end}}
{{if (and $canReadReleases $.CanCreateRelease $release.IsTag)}}
<a class="gt-mr-3 muted" href="{{$.RepoLink}}/releases/new?tag={{.TagName}}">{{svg "octicon-tag" 16 "gt-mr-2"}}{{$.locale.Tr "repo.release.new_release"}}</a>
{{end}}
{{if (and ($.Permission.CanWrite $.UnitTypeCode) $release.IsTag)}}
<a class="ui delete-button gt-mr-3 muted" data-url="{{$.RepoLink}}/tags/delete" data-id="{{.ID}}">
{{svg "octicon-trash" 16 "gt-mr-2"}}{{$.locale.Tr "repo.release.delete_tag"}}
</a>
{{end}}
{{if and $canReadReleases (not $release.IsTag)}}
<a class="gt-mr-3 muted" href="{{$.RepoLink}}/releases/tag/{{.TagName | PathEscapeSegments}}">{{svg "octicon-tag" 16 "gt-mr-2"}}{{$.locale.Tr "repo.release.detail"}}</a>
{{end}}
{{end}}
</div>
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
{{template "base/paginate" .}}
</div>
</div>
{{if $.Permission.CanWrite $.UnitTypeCode}}
<div class="ui g-modal-confirm delete modal">
<div class="header">
{{svg "octicon-trash"}}
{{.locale.Tr "repo.release.delete_tag"}}
</div>
<div class="content">
<p>{{.locale.Tr "repo.release.deletion_tag_desc"}}</p>
</div>
{{template "base/modal_actions_confirm" .}}
</div>
{{end}}
{{template "base/footer" .}}

View File

@ -18,7 +18,7 @@
{{else if .Location}} {{else if .Location}}
{{svg "octicon-location"}} {{.Location}} {{svg "octicon-location"}} {{.Location}}
{{else}} {{else}}
{{svg "octicon-clock"}} {{$.locale.Tr "user.join_on"}} {{DateTime "short" .CreatedUnix}} {{svg "octicon-clock"}} {{$.locale.Tr "user.join_on"}} {{DateTime "short" .CreatedUnix.FormatLong .CreatedUnix.FormatShort}}
{{end}} {{end}}
</div> </div>
</li> </li>

View File

@ -42,9 +42,6 @@
</div> </div>
<a download href="{{$.RawFileLink}}"><span class="btn-octicon" data-tooltip-content="{{.locale.Tr "repo.download_file"}}">{{svg "octicon-download"}}</span></a> <a download href="{{$.RawFileLink}}"><span class="btn-octicon" data-tooltip-content="{{.locale.Tr "repo.download_file"}}">{{svg "octicon-download"}}</span></a>
<a id="copy-content" class="btn-octicon {{if not .CanCopyContent}} disabled{{end}}"{{if or .IsImageFile (and .HasSourceRenderedToggle (not .IsDisplayingSource))}} data-link="{{$.RawFileLink}}"{{end}} data-tooltip-content="{{if .CanCopyContent}}{{.locale.Tr "copy_content"}}{{else}}{{.locale.Tr "copy_type_unsupported"}}{{end}}">{{svg "octicon-copy" 14}}</a> <a id="copy-content" class="btn-octicon {{if not .CanCopyContent}} disabled{{end}}"{{if or .IsImageFile (and .HasSourceRenderedToggle (not .IsDisplayingSource))}} data-link="{{$.RawFileLink}}"{{end}} data-tooltip-content="{{if .CanCopyContent}}{{.locale.Tr "copy_content"}}{{else}}{{.locale.Tr "copy_type_unsupported"}}{{end}}">{{svg "octicon-copy" 14}}</a>
{{if .EnableFeed}}
<a class="btn-octicon" href="{{$.FeedURL}}/rss/{{$.BranchNameSubURL}}/{{PathEscapeSegments .TreePath}}">{{svg "octicon-rss" 14}}</a>
{{end}}
{{if .Repository.CanEnableEditor}} {{if .Repository.CanEnableEditor}}
{{if .CanEditFile}} {{if .CanEditFile}}
<a href="{{.RepoLink}}/_edit/{{PathEscapeSegments .BranchName}}/{{PathEscapeSegments .TreePath}}"><span class="btn-octicon" data-tooltip-content="{{.EditFileTooltip}}">{{svg "octicon-pencil"}}</span></a> <a href="{{.RepoLink}}/_edit/{{PathEscapeSegments .BranchName}}/{{PathEscapeSegments .TreePath}}"><span class="btn-octicon" data-tooltip-content="{{.EditFileTooltip}}">{{svg "octicon-pencil"}}</span></a>

View File

@ -106,7 +106,7 @@
<span class="due-date" data-tooltip-content="{{$.locale.Tr "repo.issues.due_date"}}"> <span class="due-date" data-tooltip-content="{{$.locale.Tr "repo.issues.due_date"}}">
<span{{if .IsOverdue}} class="overdue"{{end}}> <span{{if .IsOverdue}} class="overdue"{{end}}>
{{svg "octicon-calendar" 14 "gt-mr-2"}} {{svg "octicon-calendar" 14 "gt-mr-2"}}
{{DateTime "short" .DeadlineUnix}} {{DateTime "short" .DeadlineUnix.FormatDate .DeadlineUnix.FormatShort}}
</span> </span>
</span> </span>
{{end}} {{end}}

View File

@ -6,7 +6,7 @@
<div class="ui container gt-mt-5"> <div class="ui container gt-mt-5">
{{if .ErrorMsg}} {{if .ErrorMsg}}
<p>{{.locale.Tr "error.occurred"}}:</p> <p>{{.locale.Tr "error.occurred"}}:</p>
<pre class="gt-whitespace-pre-wrap gt-break-all">{{.ErrorMsg}}</pre> <pre class="gt-whitespace-pre-wrap">{{.ErrorMsg}}</pre>
{{end}} {{end}}
<div class="center gt-mt-5"> <div class="center gt-mt-5">

View File

@ -97,7 +97,7 @@
{{else}} {{else}}
{{svg "octicon-calendar"}} {{svg "octicon-calendar"}}
{{if .DeadlineString}} {{if .DeadlineString}}
<span {{if .IsOverdue}}class="overdue"{{end}}>{{DateTime "short" .DeadlineString}}</span> <span {{if .IsOverdue}}class="overdue"{{end}}>{{DateTime "short" .DeadlineString .DeadlineString}}</span>
{{else}} {{else}}
{{$.locale.Tr "repo.milestones.no_due_date"}} {{$.locale.Tr "repo.milestones.no_due_date"}}
{{end}} {{end}}

View File

@ -44,10 +44,8 @@ data.teamId = {{.Team.ID}};
{{if not .ContextUser.IsOrganization}} {{if not .ContextUser.IsOrganization}}
data.organizations = [{{range .Orgs}}{'name': {{.Name}}, 'num_repos': {{.NumRepos}}},{{end}}]; data.organizations = [{{range .Orgs}}{'name': {{.Name}}, 'num_repos': {{.NumRepos}}},{{end}}];
data.isOrganization = false; data.isOrganization = false;
data.organizationsTotalCount = {{.UserOrgsCount}}; data.organizationsTotalCount = {{.UserOrgsCount}}
data.canCreateOrganization = {{.SignedUser.CanCreateOrganization}}; data.canCreateOrganization = {{.SignedUser.CanCreateOrganization}}
{{else}}
data.organizationId = {{.ContextUser.ID}};
{{end}} {{end}}
window.config.pageData.dashboardRepoList = data; window.config.pageData.dashboardRepoList = data;

View File

@ -73,7 +73,7 @@
</li> </li>
{{end}} {{end}}
{{end}} {{end}}
<li>{{svg "octicon-clock"}} {{.locale.Tr "user.join_on"}} {{DateTime "short" .ContextUser.CreatedUnix}}</li> <li>{{svg "octicon-clock"}} {{.locale.Tr "user.join_on"}} {{DateTime "short" .ContextUser.CreatedUnix.FormatLong .ContextUser.CreatedUnix.FormatShort}}</li>
{{if and .Orgs .HasOrgsVisible}} {{if and .Orgs .HasOrgsVisible}}
<li> <li>
<ul class="user-orgs"> <ul class="user-orgs">

View File

@ -27,7 +27,7 @@
</ul> </ul>
</details> </details>
<div class="activity meta"> <div class="activity meta">
<i>{{$.locale.Tr "settings.add_on"}} <span>{{DateTime "short" .CreatedUnix}}</span> — {{svg "octicon-info"}} {{if .HasUsed}}{{$.locale.Tr "settings.last_used"}} <span {{if .HasRecentActivity}}class="green"{{end}}>{{DateTime "short" .UpdatedUnix.FormatLong .UpdatedUnix.FormatShort}}</span>{{else}}{{$.locale.Tr "settings.no_activity"}}{{end}}</i> <i>{{$.locale.Tr "settings.add_on"}} <span>{{DateTime "short" .CreatedUnix.FormatLong .CreatedUnix.FormatShort}}</span> — {{svg "octicon-info"}} {{if .HasUsed}}{{$.locale.Tr "settings.last_used"}} <span {{if .HasRecentActivity}}class="green"{{end}}>{{DateTime "short" .UpdatedUnix.FormatLong .UpdatedUnix.FormatShort}}</span>{{else}}{{$.locale.Tr "settings.no_activity"}}{{end}}</i>
</div> </div>
</div> </div>
</div> </div>

View File

@ -20,7 +20,7 @@
<div class="content"> <div class="content">
<strong>{{$grant.Application.Name}}</strong> <strong>{{$grant.Application.Name}}</strong>
<div class="activity meta"> <div class="activity meta">
<i>{{$.locale.Tr "settings.add_on"}} <span>{{DateTime "short" $grant.CreatedUnix}}</span></i> <i>{{$.locale.Tr "settings.add_on"}} <span>{{DateTime "short" $grant.CreatedUnix.FormatLong $grant.CreatedUnix.FormatShort}}</span></i>
</div> </div>
</div> </div>
</div> </div>

View File

@ -68,9 +68,9 @@
<b>{{$.locale.Tr "settings.subkeys"}}:</b> {{range .SubsKey}} {{.PaddedKeyID}} {{end}} <b>{{$.locale.Tr "settings.subkeys"}}:</b> {{range .SubsKey}} {{.PaddedKeyID}} {{end}}
</div> </div>
<div class="activity meta"> <div class="activity meta">
<i>{{$.locale.Tr "settings.add_on"}} <span>{{DateTime "short" .AddedUnix}}</span></i> <i>{{$.locale.Tr "settings.add_on"}} <span>{{DateTime "short" .AddedUnix.FormatLong .AddedUnix.FormatShort}}</span></i>
- -
<i>{{if not .ExpiredUnix.IsZero}}{{$.locale.Tr "settings.valid_until"}} <span>{{DateTime "short" .ExpiredUnix}}</span>{{else}}{{$.locale.Tr "settings.valid_forever"}}{{end}}</i> <i>{{if not .ExpiredUnix.IsZero}}{{$.locale.Tr "settings.valid_until"}} <span>{{DateTime "short" .ExpiredUnix.FormatLong .ExpiredUnix.FormatShort}}</span>{{else}}{{$.locale.Tr "settings.valid_forever"}}{{end}}</i>
</div> </div>
</div> </div>
</div> </div>

View File

@ -25,7 +25,7 @@
<div class="content"> <div class="content">
<strong>{{.Name}}</strong> <strong>{{.Name}}</strong>
<div class="activity meta"> <div class="activity meta">
<i>{{$.locale.Tr "settings.add_on"}} <span>{{DateTime "short" .CreatedUnix}}</span> — {{svg "octicon-info" 16}} {{if .HasUsed}}{{$.locale.Tr "settings.last_used"}} <span {{if .HasRecentActivity}}class="green"{{end}}>{{DateTime "short" .UpdatedUnix.FormatLong .UpdatedUnix.FormatShort}}</span>{{else}}{{$.locale.Tr "settings.no_activity"}}{{end}}</i> <i>{{$.locale.Tr "settings.add_on"}} <span>{{DateTime "short" .CreatedUnix.FormatLong .CreatedUnix.FormatShort}}</span> — {{svg "octicon-info" 16}} {{if .HasUsed}}{{$.locale.Tr "settings.last_used"}} <span {{if .HasRecentActivity}}class="green"{{end}}>{{DateTime "short" .UpdatedUnix.FormatLong .UpdatedUnix.FormatShort}}</span>{{else}}{{$.locale.Tr "settings.no_activity"}}{{end}}</i>
</div> </div>
</div> </div>
</div> </div>

View File

@ -59,7 +59,7 @@
{{.Fingerprint}} {{.Fingerprint}}
</div> </div>
<div class="activity meta"> <div class="activity meta">
<i>{{$.locale.Tr "settings.add_on"}} <span>{{DateTime "short" .CreatedUnix}}</span> — {{svg "octicon-info"}} {{if .HasUsed}}{{$.locale.Tr "settings.last_used"}} <span {{if .HasRecentActivity}}class="green"{{end}}>{{DateTime "short" .UpdatedUnix.FormatLong .UpdatedUnix.FormatShort}}</span>{{else}}{{$.locale.Tr "settings.no_activity"}}{{end}}</i> <i>{{$.locale.Tr "settings.add_on"}} <span>{{DateTime "short" .CreatedUnix.FormatLong .CreatedUnix.FormatShort}}</span> — {{svg "octicon-info"}} {{if .HasUsed}}{{$.locale.Tr "settings.last_used"}} <span {{if .HasRecentActivity}}class="green"{{end}}>{{DateTime "short" .UpdatedUnix.FormatLong .UpdatedUnix.FormatShort}}</span>{{else}}{{$.locale.Tr "settings.no_activity"}}{{end}}</i>
</div> </div>
</div> </div>
</div> </div>

View File

@ -22,7 +22,7 @@ func testPullCreate(t *testing.T, session *TestSession, user, repo, branch, titl
// Click the PR button to create a pull // Click the PR button to create a pull
htmlDoc := NewHTMLParser(t, resp.Body) htmlDoc := NewHTMLParser(t, resp.Body)
link, exists := htmlDoc.doc.Find("#new-pull-request").Attr("href") link, exists := htmlDoc.doc.Find("#new-pull-request").Parent().Attr("href")
assert.True(t, exists, "The template has changed") assert.True(t, exists, "The template has changed")
if branch != "master" { if branch != "master" {
link = strings.Replace(link, ":master", ":"+branch, 1) link = strings.Replace(link, ":master", ":"+branch, 1)

View File

@ -864,9 +864,6 @@ a.label,
margin-top: -0.25rem; margin-top: -0.25rem;
margin-bottom: -0.25rem; margin-bottom: -0.25rem;
} }
.ui.dropdown .menu > .item > svg {
margin-right: .78rem; /* use the same margin as for <img> */
}
.ui.selection.dropdown .menu > .item { .ui.selection.dropdown .menu > .item {
border-color: var(--color-secondary); border-color: var(--color-secondary);

View File

@ -23,6 +23,15 @@ Gitea's private styles use `g-` prefix.
.gt-h-100 { height: 100% !important; } .gt-h-100 { height: 100% !important; }
.gt-br-0 { border-radius: 0 !important; } .gt-br-0 { border-radius: 0 !important; }
/* below class names match Tailwind CSS */
.gt-pointer-events-none { pointer-events: none !important; }
.gt-relative { position: relative !important; }
.gt-overflow-x-scroll { overflow-x: scroll !important; }
.gt-cursor-default { cursor: default !important; }
.gt-items-start { align-items: flex-start !important; }
.gt-whitespace-pre { white-space: pre !important; }
.gt-invisible { visibility: hidden !important; }
.gt-mono { .gt-mono {
font-family: var(--fonts-monospace) !important; font-family: var(--fonts-monospace) !important;
font-size: .95em !important; /* compensate for monospace fonts being usually slightly larger */ font-size: .95em !important; /* compensate for monospace fonts being usually slightly larger */
@ -42,19 +51,6 @@ Gitea's private styles use `g-` prefix.
text-overflow: ellipsis !important; text-overflow: ellipsis !important;
} }
/* below class names match Tailwind CSS */
.gt-break-all { word-break: break-all !important; }
.gt-content-center { align-content: center !important; }
.gt-cursor-default { cursor: default !important; }
.gt-invisible { visibility: hidden !important; }
.gt-items-start { align-items: flex-start !important; }
.gt-overflow-x-scroll { overflow-x: scroll !important; }
.gt-pointer-events-none { pointer-events: none !important; }
.gt-relative { position: relative !important; }
.gt-whitespace-nowrap { white-space: nowrap !important; }
.gt-whitespace-pre { white-space: pre !important; }
.gt-whitespace-pre-wrap { white-space: pre-wrap !important; }
.gt-w-screen { width: 100vw !important; } .gt-w-screen { width: 100vw !important; }
.gt-h-screen { height: 100vh !important; } .gt-h-screen { height: 100vh !important; }
@ -207,7 +203,11 @@ Gitea's private styles use `g-` prefix.
.gt-gap-y-4 { row-gap: 1rem !important; } .gt-gap-y-4 { row-gap: 1rem !important; }
.gt-gap-y-5 { row-gap: 2rem !important; } .gt-gap-y-5 { row-gap: 2rem !important; }
.gt-content-center { align-content: center !important; }
.gt-shrink-0 { flex-shrink: 0 !important; } .gt-shrink-0 { flex-shrink: 0 !important; }
.gt-whitespace-nowrap { white-space: nowrap !important; }
.gt-whitespace-pre-wrap { white-space: pre-wrap !important; }
@media (max-width: 767px) { @media (max-width: 767px) {
.gt-db-small { display: block !important; } .gt-db-small { display: block !important; }

View File

@ -30,7 +30,6 @@
@import "./install.css"; @import "./install.css";
@import "./form.css"; @import "./form.css";
@import "./repository.css"; @import "./repository.css";
@import "./repository-release-tag.css";
@import "./editor.css"; @import "./editor.css";
@import "./editor-markdown.css"; @import "./editor-markdown.css";
@import "./organization.css"; @import "./organization.css";

View File

@ -1,151 +0,0 @@
.repository.releases #release-list {
border-top: 1px solid var(--color-secondary);
margin-top: 20px;
padding-top: 15px;
padding-left: 0;
}
.repository.releases #release-list .release-list-title {
font-size: 2rem;
font-weight: normal;
margin-top: -4px;
margin-bottom: 0;
}
.repository.releases #release-list > li {
list-style: none;
}
.repository.releases #release-list > li .meta,
.repository.releases #release-list > li .detail {
padding-top: 30px;
padding-bottom: 40px;
}
.repository.releases #release-list > li .meta {
text-align: right;
position: relative;
}
.repository.releases #release-list > li .meta .label {
margin-right: 0;
}
.repository.releases #release-list > li .meta .commit {
display: block;
margin-top: 10px;
}
.repository.releases #release-list > li .meta .choose {
margin-top: 15px;
}
.repository.releases #release-list > li .meta .choose .button {
margin-right: 0;
}
.repository.releases #release-list > li .detail {
border-left: 2px solid var(--color-secondary);
}
.repository.releases #release-list > li .detail .author img {
margin-bottom: 3px;
}
.repository.releases #release-list > li .detail .download > a .svg {
margin-left: 5px;
margin-right: 5px;
}
.repository.releases #release-list > li .detail .download .list {
padding-left: 0;
}
.repository.releases #release-list > li .detail .download .list li {
list-style: none;
display: block;
padding: 8px;
border: 1px solid var(--color-secondary);
background: var(--color-light);
}
.repository.releases #release-list > li .detail .download .list li a > .text.right {
margin-right: 5px;
}
.repository.releases #release-list > li .detail .download .list li + li {
border-top: 0;
}
.repository.releases #release-list > li .detail .download .list li:first-of-type {
border-radius: var(--border-radius) 0 0 var(--border-radius);
}
.repository.releases #release-list > li .detail .download .list li:last-of-type {
border-radius: 0 var(--border-radius) var(--border-radius) 0;
}
.repository.releases #release-list > li .detail .dot {
width: 10px;
height: 10px;
background-color: var(--color-secondary-dark-3);
z-index: 9;
position: absolute;
display: block;
left: -6px;
top: 40px;
border-radius: 100%;
border: 2.5px solid var(--color-body);
}
.repository.tags #tags-table .tag {
padding: 8px 12px;
}
.repository.tags #tags-table .release-tag-name {
font-size: 18px;
font-weight: normal;
}
.repository.new.release .target {
min-width: 500px;
}
.repository.new.release .target #tag-name {
margin-top: -4px;
}
.repository.new.release .target .at {
margin-left: -5px;
margin-right: 5px;
}
.repository.new.release .target .selection.dropdown {
padding-top: 10px;
padding-bottom: 10px;
}
.repository.new.release .prerelease.field {
margin-bottom: 0;
}
@media (max-width: 438px) {
.repository.new.release .field button,
.repository.new.release .field input {
width: 100%;
}
}
@media (max-width: 767px) {
.repository.new.release .field button {
margin-bottom: 1em;
}
}
.repository.new.release .field .wrap_remove {
height: 38px;
}
.repository.new.release .field .attachment_edit {
width: 450px !important;
}

View File

@ -1923,6 +1923,157 @@
flex: 1 flex: 1
} }
.repository.release #release-list {
border-top: 1px solid var(--color-secondary);
margin-top: 20px;
padding-top: 15px;
padding-left: 0;
}
.repository.release #release-list .release-list-title {
font-size: 2rem;
font-weight: normal;
margin-top: -4px;
margin-bottom: 0;
}
.repository.release #release-list > li {
list-style: none;
}
.repository.release #release-list > li .meta,
.repository.release #release-list > li .detail {
padding-top: 30px;
padding-bottom: 40px;
}
.repository.release #release-list > li .meta {
text-align: right;
position: relative;
}
.repository.release #release-list > li .meta .label {
margin-right: 0;
}
.repository.release #release-list > li .meta .commit {
display: block;
margin-top: 10px;
}
.repository.release #release-list > li .meta .choose {
margin-top: 15px;
}
.repository.release #release-list > li .meta .choose .button {
margin-right: 0;
}
.repository.release #release-list > li .detail {
border-left: 2px solid var(--color-secondary);
}
.repository.release #release-list > li .detail .author img {
margin-bottom: 3px;
}
.repository.release #release-list > li .detail .download > a .svg {
margin-left: 5px;
margin-right: 5px;
}
.repository.release #release-list > li .detail .download .list {
padding-left: 0;
}
.repository.release #release-list > li .detail .download .list li {
list-style: none;
display: block;
padding: 8px;
border: 1px solid var(--color-secondary);
background: var(--color-light);
}
.repository.release #release-list > li .detail .download .list li a > .text.right {
margin-right: 5px;
}
.repository.release #release-list > li .detail .download .list li + li {
border-top: 0;
}
.repository.release #release-list > li .detail .download .list li:first-of-type {
border-radius: var(--border-radius) 0 0 var(--border-radius);
}
.repository.release #release-list > li .detail .download .list li:last-of-type {
border-radius: 0 var(--border-radius) var(--border-radius) 0;
}
.repository.release #release-list > li .detail .dot {
width: 10px;
height: 10px;
background-color: var(--color-secondary-dark-3);
z-index: 9;
position: absolute;
display: block;
left: -6px;
top: 40px;
border-radius: 100%;
border: 2.5px solid var(--color-body);
}
.repository.release #tags-table .tag {
padding: 8px 12px;
}
.repository.release #tags-table .release-tag-name {
font-size: 18px;
font-weight: normal;
}
.repository.new.release .target {
min-width: 500px;
}
.repository.new.release .target #tag-name {
margin-top: -4px;
}
.repository.new.release .target .at {
margin-left: -5px;
margin-right: 5px;
}
.repository.new.release .target .selection.dropdown {
padding-top: 10px;
padding-bottom: 10px;
}
.repository.new.release .prerelease.field {
margin-bottom: 0;
}
@media (max-width: 438px) {
.repository.new.release .field button,
.repository.new.release .field input {
width: 100%;
}
}
@media (max-width: 767px) {
.repository.new.release .field button {
margin-bottom: 1em;
}
}
.repository.new.release .field .wrap_remove {
height: 38px;
}
.repository.new.release .field .attachment_edit {
width: 450px !important;
}
.repository.packages .empty { .repository.packages .empty {
padding-top: 70px; padding-top: 70px;

View File

@ -10,7 +10,7 @@
{{ textMyRepos }} {{ textMyRepos }}
<span class="ui grey label gt-ml-3">{{ reposTotalCount }}</span> <span class="ui grey label gt-ml-3">{{ reposTotalCount }}</span>
</div> </div>
<a :href="subUrl + '/repo/create' + (isOrganization ? '?org=' + organizationId : '')" :data-tooltip-content="textNewRepo"> <a :href="subUrl + '/repo/create'" :data-tooltip-content="textNewRepo">
<svg-icon name="octicon-plus"/> <svg-icon name="octicon-plus"/>
<span class="sr-only">{{ textNewRepo }}</span> <span class="sr-only">{{ textNewRepo }}</span>
</a> </a>
@ -74,7 +74,7 @@
<a class="repo-list-link gt-df gt-ac gt-sb" :href="repo.link"> <a class="repo-list-link gt-df gt-ac gt-sb" :href="repo.link">
<div class="item-name gt-df gt-ac gt-f1"> <div class="item-name gt-df gt-ac gt-f1">
<svg-icon :name="repoIcon(repo)" :size="16" class-name="gt-mr-2"/> <svg-icon :name="repoIcon(repo)" :size="16" class-name="gt-mr-2"/>
<div class="text gt-bold truncate gt-ml-1">{{ repo.full_name }}</div> <div class="text truncate gt-ml-1">{{ repo.full_name }}</div>
<span v-if="repo.archived"> <span v-if="repo.archived">
<svg-icon name="octicon-archive" :size="16" class-name="gt-ml-2"/> <svg-icon name="octicon-archive" :size="16" class-name="gt-ml-2"/>
</span> </span>
@ -199,7 +199,6 @@ const sfc = {
isOrganization: true, isOrganization: true,
canCreateOrganization: false, canCreateOrganization: false,
organizationsTotalCount: 0, organizationsTotalCount: 0,
organizationId: 0,
subUrl: appSubUrl, subUrl: appSubUrl,
...pageData.dashboardRepoList, ...pageData.dashboardRepoList,

View File

@ -39,9 +39,6 @@
<div class="scrolling menu" ref="scrollContainer"> <div class="scrolling menu" ref="scrollContainer">
<div v-for="(item, index) in filteredItems" :key="item.name" class="item" :class="{selected: item.selected, active: active === index}" @click="selectItem(item)" :ref="'listItem' + index"> <div v-for="(item, index) in filteredItems" :key="item.name" class="item" :class="{selected: item.selected, active: active === index}" @click="selectItem(item)" :ref="'listItem' + index">
{{ item.name }} {{ item.name }}
<a v-if="enableFeed && mode === 'branches'" role="button" class="ui compact muted right" :href="rssURLPrefix + item.url" target="_blank" @click.stop>
<svg-icon name="octicon-rss" :size="14"/>
</a>
</div> </div>
<div class="item" v-if="showCreateNewBranch" :class="{active: active === filteredItems.length}" :ref="'listItem' + filteredItems.length"> <div class="item" v-if="showCreateNewBranch" :class="{active: active === filteredItems.length}" :ref="'listItem' + filteredItems.length">
<a href="#" @click="createNewBranch()"> <a href="#" @click="createNewBranch()">
@ -294,12 +291,3 @@ export function initRepoBranchTagSelector(selector) {
export default sfc; // activate IDE's Vue plugin export default sfc; // activate IDE's Vue plugin
</script> </script>
<style scoped>
.menu .item a {
display: none; /* only show RSS icon on hover */
}
.menu .item:hover a {
display: inline-block;
}
</style>

View File

@ -43,7 +43,6 @@ import octiconChevronLeft from '../../public/img/svg/octicon-chevron-left.svg';
import octiconOrganization from '../../public/img/svg/octicon-organization.svg'; import octiconOrganization from '../../public/img/svg/octicon-organization.svg';
import octiconTag from '../../public/img/svg/octicon-tag.svg'; import octiconTag from '../../public/img/svg/octicon-tag.svg';
import octiconGitBranch from '../../public/img/svg/octicon-git-branch.svg'; import octiconGitBranch from '../../public/img/svg/octicon-git-branch.svg';
import octiconRss from '../../public/img/svg/octicon-rss.svg';
const svgs = { const svgs = {
'octicon-blocked': octiconBlocked, 'octicon-blocked': octiconBlocked,
@ -90,7 +89,6 @@ const svgs = {
'octicon-organization': octiconOrganization, 'octicon-organization': octiconOrganization,
'octicon-tag': octiconTag, 'octicon-tag': octiconTag,
'octicon-git-branch': octiconGitBranch, 'octicon-git-branch': octiconGitBranch,
'octicon-rss': octiconRss,
}; };
// TODO: use a more general approach to access SVG icons. // TODO: use a more general approach to access SVG icons.