mirror of
https://github.com/go-gitea/gitea
synced 2024-11-18 16:14:26 +00:00
Merge branch 'main' into feature-activitypub
This commit is contained in:
commit
f7da251c5d
1
Makefile
1
Makefile
@ -704,6 +704,7 @@ fomantic:
|
|||||||
cp -f $(FOMANTIC_WORK_DIR)/theme.config.less $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/theme.config
|
cp -f $(FOMANTIC_WORK_DIR)/theme.config.less $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/theme.config
|
||||||
cp -rf $(FOMANTIC_WORK_DIR)/_site $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/
|
cp -rf $(FOMANTIC_WORK_DIR)/_site $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/
|
||||||
cd $(FOMANTIC_WORK_DIR) && npx gulp -f node_modules/fomantic-ui/gulpfile.js build
|
cd $(FOMANTIC_WORK_DIR) && npx gulp -f node_modules/fomantic-ui/gulpfile.js build
|
||||||
|
$(SED_INPLACE) -e 's/\r//g' $(FOMANTIC_WORK_DIR)/build/semantic.css $(FOMANTIC_WORK_DIR)/build/semantic.js
|
||||||
rm -f $(FOMANTIC_WORK_DIR)/build/*.min.*
|
rm -f $(FOMANTIC_WORK_DIR)/build/*.min.*
|
||||||
|
|
||||||
.PHONY: webpack
|
.PHONY: webpack
|
||||||
|
@ -275,7 +275,7 @@ func prepareTestEnv(t testing.TB, skip ...int) func() {
|
|||||||
assert.NoError(t, unittest.LoadFixtures())
|
assert.NoError(t, unittest.LoadFixtures())
|
||||||
assert.NoError(t, util.RemoveAll(setting.RepoRootPath))
|
assert.NoError(t, util.RemoveAll(setting.RepoRootPath))
|
||||||
assert.NoError(t, unittest.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"), setting.RepoRootPath))
|
assert.NoError(t, unittest.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"), setting.RepoRootPath))
|
||||||
assert.NoError(t, git.InitWithConfigSync(context.Background()))
|
assert.NoError(t, git.InitOnceWithSync(context.Background()))
|
||||||
ownerDirs, err := os.ReadDir(setting.RepoRootPath)
|
ownerDirs, err := os.ReadDir(setting.RepoRootPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
assert.NoError(t, err, "unable to read the new repo root: %v\n", err)
|
assert.NoError(t, err, "unable to read the new repo root: %v\n", err)
|
||||||
@ -576,7 +576,7 @@ func resetFixtures(t *testing.T) {
|
|||||||
assert.NoError(t, unittest.LoadFixtures())
|
assert.NoError(t, unittest.LoadFixtures())
|
||||||
assert.NoError(t, util.RemoveAll(setting.RepoRootPath))
|
assert.NoError(t, util.RemoveAll(setting.RepoRootPath))
|
||||||
assert.NoError(t, unittest.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"), setting.RepoRootPath))
|
assert.NoError(t, unittest.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"), setting.RepoRootPath))
|
||||||
assert.NoError(t, git.InitWithConfigSync(context.Background()))
|
assert.NoError(t, git.InitOnceWithSync(context.Background()))
|
||||||
ownerDirs, err := os.ReadDir(setting.RepoRootPath)
|
ownerDirs, err := os.ReadDir(setting.RepoRootPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
assert.NoError(t, err, "unable to read the new repo root: %v\n", err)
|
assert.NoError(t, err, "unable to read the new repo root: %v\n", err)
|
||||||
|
@ -62,7 +62,7 @@ func initMigrationTest(t *testing.T) func() {
|
|||||||
assert.True(t, len(setting.RepoRootPath) != 0)
|
assert.True(t, len(setting.RepoRootPath) != 0)
|
||||||
assert.NoError(t, util.RemoveAll(setting.RepoRootPath))
|
assert.NoError(t, util.RemoveAll(setting.RepoRootPath))
|
||||||
assert.NoError(t, unittest.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"), setting.RepoRootPath))
|
assert.NoError(t, unittest.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"), setting.RepoRootPath))
|
||||||
assert.NoError(t, git.InitWithConfigSync(context.Background()))
|
assert.NoError(t, git.InitOnceWithSync(context.Background()))
|
||||||
ownerDirs, err := os.ReadDir(setting.RepoRootPath)
|
ownerDirs, err := os.ReadDir(setting.RepoRootPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
assert.NoError(t, err, "unable to read the new repo root: %v\n", err)
|
assert.NoError(t, err, "unable to read the new repo root: %v\n", err)
|
||||||
|
@ -203,7 +203,7 @@ func prepareTestEnv(t *testing.T, skip int, syncModels ...interface{}) (*xorm.En
|
|||||||
deferFn := PrintCurrentTest(t, ourSkip)
|
deferFn := PrintCurrentTest(t, ourSkip)
|
||||||
assert.NoError(t, os.RemoveAll(setting.RepoRootPath))
|
assert.NoError(t, os.RemoveAll(setting.RepoRootPath))
|
||||||
assert.NoError(t, unittest.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"), setting.RepoRootPath))
|
assert.NoError(t, unittest.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"), setting.RepoRootPath))
|
||||||
assert.NoError(t, git.InitWithConfigSync(context.Background()))
|
assert.NoError(t, git.InitOnceWithSync(context.Background()))
|
||||||
ownerDirs, err := os.ReadDir(setting.RepoRootPath)
|
ownerDirs, err := os.ReadDir(setting.RepoRootPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
assert.NoError(t, err, "unable to read the new repo root: %v\n", err)
|
assert.NoError(t, err, "unable to read the new repo root: %v\n", err)
|
||||||
|
@ -19,12 +19,6 @@ import (
|
|||||||
// ErrMirrorNotExist mirror does not exist error
|
// ErrMirrorNotExist mirror does not exist error
|
||||||
var ErrMirrorNotExist = errors.New("Mirror does not exist")
|
var ErrMirrorNotExist = errors.New("Mirror does not exist")
|
||||||
|
|
||||||
// RemoteMirrorer defines base methods for pull/push mirrors.
|
|
||||||
type RemoteMirrorer interface {
|
|
||||||
GetRepository() *Repository
|
|
||||||
GetRemoteName() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mirror represents mirror information of a repository.
|
// Mirror represents mirror information of a repository.
|
||||||
type Mirror struct {
|
type Mirror struct {
|
||||||
ID int64 `xorm:"pk autoincr"`
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
@ -414,6 +414,9 @@ func (repo *Repository) ComposeMetas() map[string]string {
|
|||||||
switch unit.ExternalTrackerConfig().ExternalTrackerStyle {
|
switch unit.ExternalTrackerConfig().ExternalTrackerStyle {
|
||||||
case markup.IssueNameStyleAlphanumeric:
|
case markup.IssueNameStyleAlphanumeric:
|
||||||
metas["style"] = markup.IssueNameStyleAlphanumeric
|
metas["style"] = markup.IssueNameStyleAlphanumeric
|
||||||
|
case markup.IssueNameStyleRegexp:
|
||||||
|
metas["style"] = markup.IssueNameStyleRegexp
|
||||||
|
metas["regexp"] = unit.ExternalTrackerConfig().ExternalTrackerRegexpPattern
|
||||||
default:
|
default:
|
||||||
metas["style"] = markup.IssueNameStyleNumeric
|
metas["style"] = markup.IssueNameStyleNumeric
|
||||||
}
|
}
|
||||||
|
@ -79,6 +79,7 @@ type ExternalTrackerConfig struct {
|
|||||||
ExternalTrackerURL string
|
ExternalTrackerURL string
|
||||||
ExternalTrackerFormat string
|
ExternalTrackerFormat string
|
||||||
ExternalTrackerStyle string
|
ExternalTrackerStyle string
|
||||||
|
ExternalTrackerRegexpPattern string
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromDB fills up a ExternalTrackerConfig from serialized format.
|
// FromDB fills up a ExternalTrackerConfig from serialized format.
|
||||||
|
@ -74,6 +74,9 @@ func TestMetas(t *testing.T) {
|
|||||||
externalTracker.ExternalTrackerConfig().ExternalTrackerStyle = markup.IssueNameStyleNumeric
|
externalTracker.ExternalTrackerConfig().ExternalTrackerStyle = markup.IssueNameStyleNumeric
|
||||||
testSuccess(markup.IssueNameStyleNumeric)
|
testSuccess(markup.IssueNameStyleNumeric)
|
||||||
|
|
||||||
|
externalTracker.ExternalTrackerConfig().ExternalTrackerStyle = markup.IssueNameStyleRegexp
|
||||||
|
testSuccess(markup.IssueNameStyleRegexp)
|
||||||
|
|
||||||
repo, err := repo_model.GetRepositoryByID(3)
|
repo, err := repo_model.GetRepositoryByID(3)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
@ -117,7 +117,7 @@ func MainTest(m *testing.M, testOpts *TestOptions) {
|
|||||||
if err = CopyDir(filepath.Join(testOpts.GiteaRootPath, "integrations", "gitea-repositories-meta"), setting.RepoRootPath); err != nil {
|
if err = CopyDir(filepath.Join(testOpts.GiteaRootPath, "integrations", "gitea-repositories-meta"), setting.RepoRootPath); err != nil {
|
||||||
fatalTestError("util.CopyDir: %v\n", err)
|
fatalTestError("util.CopyDir: %v\n", err)
|
||||||
}
|
}
|
||||||
if err = git.InitWithConfigSync(context.Background()); err != nil {
|
if err = git.InitOnceWithSync(context.Background()); err != nil {
|
||||||
fatalTestError("git.Init: %v\n", err)
|
fatalTestError("git.Init: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,7 +202,7 @@ func PrepareTestEnv(t testing.TB) {
|
|||||||
assert.NoError(t, util.RemoveAll(setting.RepoRootPath))
|
assert.NoError(t, util.RemoveAll(setting.RepoRootPath))
|
||||||
metaPath := filepath.Join(giteaRoot, "integrations", "gitea-repositories-meta")
|
metaPath := filepath.Join(giteaRoot, "integrations", "gitea-repositories-meta")
|
||||||
assert.NoError(t, CopyDir(metaPath, setting.RepoRootPath))
|
assert.NoError(t, CopyDir(metaPath, setting.RepoRootPath))
|
||||||
assert.NoError(t, git.InitWithConfigSync(context.Background()))
|
assert.NoError(t, git.InitOnceWithSync(context.Background()))
|
||||||
|
|
||||||
ownerDirs, err := os.ReadDir(setting.RepoRootPath)
|
ownerDirs, err := os.ReadDir(setting.RepoRootPath)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -34,15 +34,12 @@ var (
|
|||||||
GitExecutable = "git"
|
GitExecutable = "git"
|
||||||
|
|
||||||
// DefaultContext is the default context to run git commands in
|
// DefaultContext is the default context to run git commands in
|
||||||
// will be overwritten by InitWithConfigSync with HammerContext
|
// will be overwritten by InitXxx with HammerContext
|
||||||
DefaultContext = context.Background()
|
DefaultContext = context.Background()
|
||||||
|
|
||||||
// SupportProcReceive version >= 2.29.0
|
// SupportProcReceive version >= 2.29.0
|
||||||
SupportProcReceive bool
|
SupportProcReceive bool
|
||||||
|
|
||||||
// initMutex is used to avoid Golang's data race error. see the comments below.
|
|
||||||
initMutex sync.Mutex
|
|
||||||
|
|
||||||
gitVersion *version.Version
|
gitVersion *version.Version
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -131,15 +128,6 @@ func VersionInfo() string {
|
|||||||
return fmt.Sprintf(format, args...)
|
return fmt.Sprintf(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitSimple initializes git module with a very simple step, no config changes, no global command arguments.
|
|
||||||
// This method doesn't change anything to filesystem
|
|
||||||
func InitSimple(ctx context.Context) error {
|
|
||||||
initMutex.Lock()
|
|
||||||
defer initMutex.Unlock()
|
|
||||||
|
|
||||||
return initSimpleInternal(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HomeDir is the home dir for git to store the global config file used by Gitea internally
|
// HomeDir is the home dir for git to store the global config file used by Gitea internally
|
||||||
func HomeDir() string {
|
func HomeDir() string {
|
||||||
if setting.RepoRootPath == "" {
|
if setting.RepoRootPath == "" {
|
||||||
@ -153,11 +141,9 @@ func HomeDir() string {
|
|||||||
return setting.RepoRootPath
|
return setting.RepoRootPath
|
||||||
}
|
}
|
||||||
|
|
||||||
func initSimpleInternal(ctx context.Context) error {
|
// InitSimple initializes git module with a very simple step, no config changes, no global command arguments.
|
||||||
// at the moment, when running integration tests, the git.InitXxx would be called twice.
|
// This method doesn't change anything to filesystem. At the moment, it is only used by "git serv" sub-command, no data-race
|
||||||
// one is called by the GlobalInitInstalled, one is called by TestMain.
|
func InitSimple(ctx context.Context) error {
|
||||||
// so the init functions should be protected by a mutex to avoid Golang's data race error.
|
|
||||||
|
|
||||||
DefaultContext = ctx
|
DefaultContext = ctx
|
||||||
|
|
||||||
if setting.Git.Timeout.Default > 0 {
|
if setting.Git.Timeout.Default > 0 {
|
||||||
@ -174,23 +160,16 @@ func initSimpleInternal(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitWithConfigSync initializes git module. This method may create directories or write files into filesystem
|
var initOnce sync.Once
|
||||||
func InitWithConfigSync(ctx context.Context) error {
|
|
||||||
initMutex.Lock()
|
|
||||||
defer initMutex.Unlock()
|
|
||||||
|
|
||||||
err := initSimpleInternal(ctx)
|
// InitOnceWithSync initializes git module with version check and change global variables, sync gitconfig.
|
||||||
|
// This method will update the global variables ONLY ONCE (just like git.CheckLFSVersion -- which is not ideal too),
|
||||||
|
// otherwise there will be data-race problem at the moment.
|
||||||
|
func InitOnceWithSync(ctx context.Context) (err error) {
|
||||||
|
initOnce.Do(func() {
|
||||||
|
err = InitSimple(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return
|
||||||
}
|
|
||||||
|
|
||||||
if err = os.MkdirAll(setting.RepoRootPath, os.ModePerm); err != nil {
|
|
||||||
return fmt.Errorf("unable to create directory %s, err: %w", setting.RepoRootPath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if CheckGitVersionAtLeast("2.9") == nil {
|
|
||||||
// Explicitly disable credential helper, otherwise Git credentials might leak
|
|
||||||
globalCommandArgs = append(globalCommandArgs, "-c", "credential.helper=")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since git wire protocol has been released from git v2.18
|
// Since git wire protocol has been released from git v2.18
|
||||||
@ -203,6 +182,25 @@ func InitWithConfigSync(ctx context.Context) error {
|
|||||||
globalCommandArgs = append(globalCommandArgs, "-c", "uploadpack.allowfilter=true", "-c", "uploadpack.allowAnySHA1InWant=true")
|
globalCommandArgs = append(globalCommandArgs, "-c", "uploadpack.allowfilter=true", "-c", "uploadpack.allowAnySHA1InWant=true")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Explicitly disable credential helper, otherwise Git credentials might leak
|
||||||
|
if CheckGitVersionAtLeast("2.9") == nil {
|
||||||
|
globalCommandArgs = append(globalCommandArgs, "-c", "credential.helper=")
|
||||||
|
}
|
||||||
|
|
||||||
|
SupportProcReceive = CheckGitVersionAtLeast("2.29") == nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return syncGitConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
// syncGitConfig only modifies gitconfig, won't change global variables (otherwise there will be data-race problem)
|
||||||
|
func syncGitConfig() (err error) {
|
||||||
|
if err = os.MkdirAll(HomeDir(), os.ModePerm); err != nil {
|
||||||
|
return fmt.Errorf("unable to create directory %s, err: %w", setting.RepoRootPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
// Git requires setting user.name and user.email in order to commit changes - old comment: "if they're not set just add some defaults"
|
// Git requires setting user.name and user.email in order to commit changes - old comment: "if they're not set just add some defaults"
|
||||||
// TODO: need to confirm whether users really need to change these values manually. It seems that these values are dummy only and not really used.
|
// TODO: need to confirm whether users really need to change these values manually. It seems that these values are dummy only and not really used.
|
||||||
// If these values are not really used, then they can be set (overwritten) directly without considering about existence.
|
// If these values are not really used, then they can be set (overwritten) directly without considering about existence.
|
||||||
@ -235,17 +233,15 @@ func InitWithConfigSync(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if CheckGitVersionAtLeast("2.29") == nil {
|
if SupportProcReceive {
|
||||||
// set support for AGit flow
|
// set support for AGit flow
|
||||||
if err := configAddNonExist("receive.procReceiveRefs", "refs/for"); err != nil {
|
if err := configAddNonExist("receive.procReceiveRefs", "refs/for"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
SupportProcReceive = true
|
|
||||||
} else {
|
} else {
|
||||||
if err := configUnsetAll("receive.procReceiveRefs", "refs/for"); err != nil {
|
if err := configUnsetAll("receive.procReceiveRefs", "refs/for"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
SupportProcReceive = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
|
@ -28,7 +28,7 @@ func testRun(m *testing.M) error {
|
|||||||
defer util.RemoveAll(repoRootPath)
|
defer util.RemoveAll(repoRootPath)
|
||||||
setting.RepoRootPath = repoRootPath
|
setting.RepoRootPath = repoRootPath
|
||||||
|
|
||||||
if err = InitWithConfigSync(context.Background()); err != nil {
|
if err = InitOnceWithSync(context.Background()); err != nil {
|
||||||
return fmt.Errorf("failed to call Init: %w", err)
|
return fmt.Errorf("failed to call Init: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,11 +6,12 @@ package git
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/url"
|
|
||||||
|
giturl "code.gitea.io/gitea/modules/git/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetRemoteAddress returns the url of a specific remote of the repository.
|
// GetRemoteAddress returns remote url of git repository in the repoPath with special remote name
|
||||||
func GetRemoteAddress(ctx context.Context, repoPath, remoteName string) (*url.URL, error) {
|
func GetRemoteAddress(ctx context.Context, repoPath, remoteName string) (string, error) {
|
||||||
var cmd *Command
|
var cmd *Command
|
||||||
if CheckGitVersionAtLeast("2.7") == nil {
|
if CheckGitVersionAtLeast("2.7") == nil {
|
||||||
cmd = NewCommand(ctx, "remote", "get-url", remoteName)
|
cmd = NewCommand(ctx, "remote", "get-url", remoteName)
|
||||||
@ -20,11 +21,20 @@ func GetRemoteAddress(ctx context.Context, repoPath, remoteName string) (*url.UR
|
|||||||
|
|
||||||
result, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath})
|
result, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(result) > 0 {
|
if len(result) > 0 {
|
||||||
result = result[:len(result)-1]
|
result = result[:len(result)-1]
|
||||||
}
|
}
|
||||||
return url.Parse(result)
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRemoteURL returns the url of a specific remote of the repository.
|
||||||
|
func GetRemoteURL(ctx context.Context, repoPath, remoteName string) (*giturl.GitURL, error) {
|
||||||
|
addr, err := GetRemoteAddress(ctx, repoPath, remoteName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return giturl.Parse(addr)
|
||||||
}
|
}
|
||||||
|
90
modules/git/url/url.go
Normal file
90
modules/git/url/url.go
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package url
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
stdurl "net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrWrongURLFormat represents an error with wrong url format
|
||||||
|
type ErrWrongURLFormat struct {
|
||||||
|
URL string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrWrongURLFormat) Error() string {
|
||||||
|
return fmt.Sprintf("git URL %s format is wrong", err.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GitURL represents a git URL
|
||||||
|
type GitURL struct {
|
||||||
|
*stdurl.URL
|
||||||
|
extraMark int // 0 no extra 1 scp 2 file path with no prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the URL's string
|
||||||
|
func (u *GitURL) String() string {
|
||||||
|
switch u.extraMark {
|
||||||
|
case 0:
|
||||||
|
return u.URL.String()
|
||||||
|
case 1:
|
||||||
|
return fmt.Sprintf("%s@%s:%s", u.User.Username(), u.Host, u.Path)
|
||||||
|
case 2:
|
||||||
|
return u.Path
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parse all kinds of git URL
|
||||||
|
func Parse(remote string) (*GitURL, error) {
|
||||||
|
if strings.Contains(remote, "://") {
|
||||||
|
u, err := stdurl.Parse(remote)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &GitURL{URL: u}, nil
|
||||||
|
} else if strings.Contains(remote, "@") && strings.Contains(remote, ":") {
|
||||||
|
url := stdurl.URL{
|
||||||
|
Scheme: "ssh",
|
||||||
|
}
|
||||||
|
squareBrackets := false
|
||||||
|
lastIndex := -1
|
||||||
|
FOR:
|
||||||
|
for i := 0; i < len(remote); i++ {
|
||||||
|
switch remote[i] {
|
||||||
|
case '@':
|
||||||
|
url.User = stdurl.User(remote[:i])
|
||||||
|
lastIndex = i + 1
|
||||||
|
case ':':
|
||||||
|
if !squareBrackets {
|
||||||
|
url.Host = strings.ReplaceAll(remote[lastIndex:i], "%25", "%")
|
||||||
|
if len(remote) <= i+1 {
|
||||||
|
return nil, ErrWrongURLFormat{URL: remote}
|
||||||
|
}
|
||||||
|
url.Path = remote[i+1:]
|
||||||
|
break FOR
|
||||||
|
}
|
||||||
|
case '[':
|
||||||
|
squareBrackets = true
|
||||||
|
case ']':
|
||||||
|
squareBrackets = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &GitURL{
|
||||||
|
URL: &url,
|
||||||
|
extraMark: 1,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &GitURL{
|
||||||
|
URL: &stdurl.URL{
|
||||||
|
Scheme: "file",
|
||||||
|
Path: remote,
|
||||||
|
},
|
||||||
|
extraMark: 2,
|
||||||
|
}, nil
|
||||||
|
}
|
167
modules/git/url/url_test.go
Normal file
167
modules/git/url/url_test.go
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package url
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseGitURLs(t *testing.T) {
|
||||||
|
kases := []struct {
|
||||||
|
kase string
|
||||||
|
expected *GitURL
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
kase: "git@127.0.0.1:go-gitea/gitea.git",
|
||||||
|
expected: &GitURL{
|
||||||
|
URL: &url.URL{
|
||||||
|
Scheme: "ssh",
|
||||||
|
User: url.User("git"),
|
||||||
|
Host: "127.0.0.1",
|
||||||
|
Path: "go-gitea/gitea.git",
|
||||||
|
},
|
||||||
|
extraMark: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kase: "git@[fe80:14fc:cec5:c174:d88%2510]:go-gitea/gitea.git",
|
||||||
|
expected: &GitURL{
|
||||||
|
URL: &url.URL{
|
||||||
|
Scheme: "ssh",
|
||||||
|
User: url.User("git"),
|
||||||
|
Host: "[fe80:14fc:cec5:c174:d88%10]",
|
||||||
|
Path: "go-gitea/gitea.git",
|
||||||
|
},
|
||||||
|
extraMark: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kase: "git@[::1]:go-gitea/gitea.git",
|
||||||
|
expected: &GitURL{
|
||||||
|
URL: &url.URL{
|
||||||
|
Scheme: "ssh",
|
||||||
|
User: url.User("git"),
|
||||||
|
Host: "[::1]",
|
||||||
|
Path: "go-gitea/gitea.git",
|
||||||
|
},
|
||||||
|
extraMark: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kase: "git@github.com:go-gitea/gitea.git",
|
||||||
|
expected: &GitURL{
|
||||||
|
URL: &url.URL{
|
||||||
|
Scheme: "ssh",
|
||||||
|
User: url.User("git"),
|
||||||
|
Host: "github.com",
|
||||||
|
Path: "go-gitea/gitea.git",
|
||||||
|
},
|
||||||
|
extraMark: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kase: "ssh://git@github.com/go-gitea/gitea.git",
|
||||||
|
expected: &GitURL{
|
||||||
|
URL: &url.URL{
|
||||||
|
Scheme: "ssh",
|
||||||
|
User: url.User("git"),
|
||||||
|
Host: "github.com",
|
||||||
|
Path: "/go-gitea/gitea.git",
|
||||||
|
},
|
||||||
|
extraMark: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kase: "ssh://git@[::1]/go-gitea/gitea.git",
|
||||||
|
expected: &GitURL{
|
||||||
|
URL: &url.URL{
|
||||||
|
Scheme: "ssh",
|
||||||
|
User: url.User("git"),
|
||||||
|
Host: "[::1]",
|
||||||
|
Path: "/go-gitea/gitea.git",
|
||||||
|
},
|
||||||
|
extraMark: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kase: "/repositories/go-gitea/gitea.git",
|
||||||
|
expected: &GitURL{
|
||||||
|
URL: &url.URL{
|
||||||
|
Scheme: "file",
|
||||||
|
Path: "/repositories/go-gitea/gitea.git",
|
||||||
|
},
|
||||||
|
extraMark: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kase: "file:///repositories/go-gitea/gitea.git",
|
||||||
|
expected: &GitURL{
|
||||||
|
URL: &url.URL{
|
||||||
|
Scheme: "file",
|
||||||
|
Path: "/repositories/go-gitea/gitea.git",
|
||||||
|
},
|
||||||
|
extraMark: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kase: "https://github.com/go-gitea/gitea.git",
|
||||||
|
expected: &GitURL{
|
||||||
|
URL: &url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: "github.com",
|
||||||
|
Path: "/go-gitea/gitea.git",
|
||||||
|
},
|
||||||
|
extraMark: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kase: "https://git:git@github.com/go-gitea/gitea.git",
|
||||||
|
expected: &GitURL{
|
||||||
|
URL: &url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: "github.com",
|
||||||
|
User: url.UserPassword("git", "git"),
|
||||||
|
Path: "/go-gitea/gitea.git",
|
||||||
|
},
|
||||||
|
extraMark: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kase: "https://[fe80:14fc:cec5:c174:d88%2510]:20/go-gitea/gitea.git",
|
||||||
|
expected: &GitURL{
|
||||||
|
URL: &url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: "[fe80:14fc:cec5:c174:d88%10]:20",
|
||||||
|
Path: "/go-gitea/gitea.git",
|
||||||
|
},
|
||||||
|
extraMark: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
kase: "git://github.com/go-gitea/gitea.git",
|
||||||
|
expected: &GitURL{
|
||||||
|
URL: &url.URL{
|
||||||
|
Scheme: "git",
|
||||||
|
Host: "github.com",
|
||||||
|
Path: "/go-gitea/gitea.git",
|
||||||
|
},
|
||||||
|
extraMark: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, kase := range kases {
|
||||||
|
t.Run(kase.kase, func(t *testing.T) {
|
||||||
|
u, err := Parse(kase.kase)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, kase.expected.extraMark, u.extraMark)
|
||||||
|
assert.EqualValues(t, *kase.expected, *u)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -203,6 +203,8 @@ func File(numLines int, fileName, language string, code []byte) []string {
|
|||||||
content = "\n"
|
content = "\n"
|
||||||
} else if content == `</span><span class="w">` {
|
} else if content == `</span><span class="w">` {
|
||||||
content += "\n</span>"
|
content += "\n</span>"
|
||||||
|
} else if content == `</span></span><span class="line"><span class="cl">` {
|
||||||
|
content += "\n"
|
||||||
}
|
}
|
||||||
content = strings.TrimSuffix(content, `<span class="w">`)
|
content = strings.TrimSuffix(content, `<span class="w">`)
|
||||||
content = strings.TrimPrefix(content, `</span>`)
|
content = strings.TrimPrefix(content, `</span>`)
|
||||||
|
@ -5,11 +5,13 @@
|
|||||||
package highlight
|
package highlight
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"gopkg.in/ini.v1"
|
"gopkg.in/ini.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -20,13 +22,14 @@ func TestFile(t *testing.T) {
|
|||||||
numLines int
|
numLines int
|
||||||
fileName string
|
fileName string
|
||||||
code string
|
code string
|
||||||
want []string
|
want string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: ".drone.yml",
|
name: ".drone.yml",
|
||||||
numLines: 12,
|
numLines: 12,
|
||||||
fileName: ".drone.yml",
|
fileName: ".drone.yml",
|
||||||
code: `kind: pipeline
|
code: util.Dedent(`
|
||||||
|
kind: pipeline
|
||||||
name: default
|
name: default
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@ -38,31 +41,29 @@ steps:
|
|||||||
- go get -u
|
- go get -u
|
||||||
- go build -v
|
- go build -v
|
||||||
- go test -v -race -coverprofile=coverage.txt -covermode=atomic
|
- go test -v -race -coverprofile=coverage.txt -covermode=atomic
|
||||||
`,
|
`),
|
||||||
want: []string{
|
want: util.Dedent(`
|
||||||
`<span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">pipeline</span>`,
|
<span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">pipeline</span>
|
||||||
`</span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">default</span>`,
|
</span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">default</span>
|
||||||
`</span></span><span class="line"><span class="cl">`,
|
</span></span><span class="line"><span class="cl">
|
||||||
`</span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">steps</span><span class="p">:</span>`,
|
</span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">steps</span><span class="p">:</span>
|
||||||
`</span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">test</span>`,
|
</span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">test</span>
|
||||||
`</span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">golang:1.13</span>`,
|
</span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">golang:1.13</span>
|
||||||
`</span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">environment</span><span class="p">:</span>`,
|
</span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">environment</span><span class="p">:</span>
|
||||||
`</span></span><span class="line"><span class="cl"><span class="w"></span><span class="w"> </span><span class="nt">GOPROXY</span><span class="p">:</span><span class="w"> </span><span class="l">https://goproxy.cn</span>`,
|
</span></span><span class="line"><span class="cl"><span class="w"></span><span class="w"> </span><span class="nt">GOPROXY</span><span class="p">:</span><span class="w"> </span><span class="l">https://goproxy.cn</span>
|
||||||
`</span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">commands</span><span class="p">:</span>`,
|
</span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">commands</span><span class="p">:</span>
|
||||||
`</span></span><span class="line"><span class="cl"><span class="w"></span><span class="w"> </span>- <span class="l">go get -u</span>`,
|
</span></span><span class="line"><span class="cl"><span class="w"></span><span class="w"> </span>- <span class="l">go get -u</span>
|
||||||
`</span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="l">go build -v</span>`,
|
</span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="l">go build -v</span>
|
||||||
`</span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="l">go test -v -race -coverprofile=coverage.txt -covermode=atomic</span><span class="w">
|
</span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="l">go test -v -race -coverprofile=coverage.txt -covermode=atomic</span></span></span>
|
||||||
</span></span></span>`,
|
`),
|
||||||
`<span class="w">
|
|
||||||
</span>`,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: ".drone.yml - trailing space",
|
name: ".drone.yml - trailing space",
|
||||||
numLines: 13,
|
numLines: 13,
|
||||||
fileName: ".drone.yml",
|
fileName: ".drone.yml",
|
||||||
code: `kind: pipeline
|
code: strings.Replace(util.Dedent(`
|
||||||
name: default ` + `
|
kind: pipeline
|
||||||
|
name: default
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: test
|
- name: test
|
||||||
@ -73,30 +74,31 @@ steps:
|
|||||||
- go get -u
|
- go get -u
|
||||||
- go build -v
|
- go build -v
|
||||||
- go test -v -race -coverprofile=coverage.txt -covermode=atomic
|
- go test -v -race -coverprofile=coverage.txt -covermode=atomic
|
||||||
`,
|
`)+"\n", "name: default", "name: default ", 1),
|
||||||
want: []string{
|
want: util.Dedent(`
|
||||||
`<span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">pipeline</span>`,
|
<span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">pipeline</span>
|
||||||
`</span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">default </span>`,
|
</span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">default </span>
|
||||||
`</span></span><span class="line"><span class="cl">`,
|
</span></span><span class="line"><span class="cl">
|
||||||
`</span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">steps</span><span class="p">:</span>`,
|
</span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">steps</span><span class="p">:</span>
|
||||||
`</span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">test</span>`,
|
</span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">test</span>
|
||||||
`</span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">golang:1.13</span>`,
|
</span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">golang:1.13</span>
|
||||||
`</span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">environment</span><span class="p">:</span>`,
|
</span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">environment</span><span class="p">:</span>
|
||||||
`</span></span><span class="line"><span class="cl"><span class="w"></span><span class="w"> </span><span class="nt">GOPROXY</span><span class="p">:</span><span class="w"> </span><span class="l">https://goproxy.cn</span>`,
|
</span></span><span class="line"><span class="cl"><span class="w"></span><span class="w"> </span><span class="nt">GOPROXY</span><span class="p">:</span><span class="w"> </span><span class="l">https://goproxy.cn</span>
|
||||||
`</span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">commands</span><span class="p">:</span>`,
|
</span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">commands</span><span class="p">:</span>
|
||||||
`</span></span><span class="line"><span class="cl"><span class="w"></span><span class="w"> </span>- <span class="l">go get -u</span>`,
|
</span></span><span class="line"><span class="cl"><span class="w"></span><span class="w"> </span>- <span class="l">go get -u</span>
|
||||||
`</span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="l">go build -v</span>`,
|
</span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="l">go build -v</span>
|
||||||
`</span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="l">go test -v -race -coverprofile=coverage.txt -covermode=atomic</span>`,
|
</span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="l">go test -v -race -coverprofile=coverage.txt -covermode=atomic</span>
|
||||||
`</span></span><span class="line"><span class="cl"><span class="w"> </span></span></span>`,
|
</span></span>
|
||||||
},
|
<span class="w">
|
||||||
|
</span>
|
||||||
|
`),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
if got := File(tt.numLines, tt.fileName, "", []byte(tt.code)); !reflect.DeepEqual(got, tt.want) {
|
got := strings.Join(File(tt.numLines, tt.fileName, "", []byte(tt.code)), "\n")
|
||||||
t.Errorf("File() = %v, want %v", got, tt.want)
|
assert.Equal(t, tt.want, got)
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,6 @@ import (
|
|||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
"code.gitea.io/gitea/modules/git"
|
|
||||||
"code.gitea.io/gitea/modules/queue"
|
"code.gitea.io/gitea/modules/queue"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
@ -30,10 +29,6 @@ func TestMain(m *testing.M) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRepoStatsIndex(t *testing.T) {
|
func TestRepoStatsIndex(t *testing.T) {
|
||||||
if err := git.InitWithConfigSync(context.Background()); !assert.NoError(t, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
setting.Cfg = ini.Empty()
|
setting.Cfg = ini.Empty()
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/markup/common"
|
"code.gitea.io/gitea/modules/markup/common"
|
||||||
"code.gitea.io/gitea/modules/references"
|
"code.gitea.io/gitea/modules/references"
|
||||||
|
"code.gitea.io/gitea/modules/regexplru"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/templates/vars"
|
"code.gitea.io/gitea/modules/templates/vars"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
@ -33,6 +34,7 @@ import (
|
|||||||
const (
|
const (
|
||||||
IssueNameStyleNumeric = "numeric"
|
IssueNameStyleNumeric = "numeric"
|
||||||
IssueNameStyleAlphanumeric = "alphanumeric"
|
IssueNameStyleAlphanumeric = "alphanumeric"
|
||||||
|
IssueNameStyleRegexp = "regexp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -815,19 +817,35 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
next := node.NextSibling
|
next := node.NextSibling
|
||||||
|
|
||||||
for node != nil && node != next {
|
for node != nil && node != next {
|
||||||
_, exttrack := ctx.Metas["format"]
|
_, hasExtTrackFormat := ctx.Metas["format"]
|
||||||
alphanum := ctx.Metas["style"] == IssueNameStyleAlphanumeric
|
|
||||||
|
|
||||||
// Repos with external issue trackers might still need to reference local PRs
|
// Repos with external issue trackers might still need to reference local PRs
|
||||||
// We need to concern with the first one that shows up in the text, whichever it is
|
// We need to concern with the first one that shows up in the text, whichever it is
|
||||||
found, ref = references.FindRenderizableReferenceNumeric(node.Data, exttrack && alphanum)
|
isNumericStyle := ctx.Metas["style"] == "" || ctx.Metas["style"] == IssueNameStyleNumeric
|
||||||
if exttrack && alphanum {
|
foundNumeric, refNumeric := references.FindRenderizableReferenceNumeric(node.Data, hasExtTrackFormat && !isNumericStyle)
|
||||||
if found2, ref2 := references.FindRenderizableReferenceAlphanumeric(node.Data); found2 {
|
|
||||||
if !found || ref2.RefLocation.Start < ref.RefLocation.Start {
|
switch ctx.Metas["style"] {
|
||||||
found = true
|
case "", IssueNameStyleNumeric:
|
||||||
ref = ref2
|
found, ref = foundNumeric, refNumeric
|
||||||
|
case IssueNameStyleAlphanumeric:
|
||||||
|
found, ref = references.FindRenderizableReferenceAlphanumeric(node.Data)
|
||||||
|
case IssueNameStyleRegexp:
|
||||||
|
pattern, err := regexplru.GetCompiled(ctx.Metas["regexp"])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
found, ref = references.FindRenderizableReferenceRegexp(node.Data, pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Repos with external issue trackers might still need to reference local PRs
|
||||||
|
// We need to concern with the first one that shows up in the text, whichever it is
|
||||||
|
if hasExtTrackFormat && !isNumericStyle {
|
||||||
|
// If numeric (PR) was found, and it was BEFORE the non-numeric pattern, use that
|
||||||
|
if foundNumeric && refNumeric.RefLocation.Start < ref.RefLocation.Start {
|
||||||
|
found = foundNumeric
|
||||||
|
ref = refNumeric
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !found {
|
if !found {
|
||||||
@ -836,7 +854,7 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
|
|||||||
|
|
||||||
var link *html.Node
|
var link *html.Node
|
||||||
reftext := node.Data[ref.RefLocation.Start:ref.RefLocation.End]
|
reftext := node.Data[ref.RefLocation.Start:ref.RefLocation.End]
|
||||||
if exttrack && !ref.IsPull {
|
if hasExtTrackFormat && !ref.IsPull {
|
||||||
ctx.Metas["index"] = ref.Issue
|
ctx.Metas["index"] = ref.Issue
|
||||||
|
|
||||||
res, err := vars.Expand(ctx.Metas["format"], ctx.Metas)
|
res, err := vars.Expand(ctx.Metas["format"], ctx.Metas)
|
||||||
@ -869,7 +887,7 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
|
|||||||
|
|
||||||
// Decorate action keywords if actionable
|
// Decorate action keywords if actionable
|
||||||
var keyword *html.Node
|
var keyword *html.Node
|
||||||
if references.IsXrefActionable(ref, exttrack, alphanum) {
|
if references.IsXrefActionable(ref, hasExtTrackFormat) {
|
||||||
keyword = createKeyword(node.Data[ref.ActionLocation.Start:ref.ActionLocation.End])
|
keyword = createKeyword(node.Data[ref.ActionLocation.Start:ref.ActionLocation.End])
|
||||||
} else {
|
} else {
|
||||||
keyword = &html.Node{
|
keyword = &html.Node{
|
||||||
|
@ -21,8 +21,8 @@ const (
|
|||||||
TestRepoURL = TestAppURL + TestOrgRepo + "/"
|
TestRepoURL = TestAppURL + TestOrgRepo + "/"
|
||||||
)
|
)
|
||||||
|
|
||||||
// alphanumLink an HTML link to an alphanumeric-style issue
|
// externalIssueLink an HTML link to an alphanumeric-style issue
|
||||||
func alphanumIssueLink(baseURL, class, name string) string {
|
func externalIssueLink(baseURL, class, name string) string {
|
||||||
return link(util.URLJoin(baseURL, name), class, name)
|
return link(util.URLJoin(baseURL, name), class, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,6 +54,13 @@ var alphanumericMetas = map[string]string{
|
|||||||
"style": IssueNameStyleAlphanumeric,
|
"style": IssueNameStyleAlphanumeric,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var regexpMetas = map[string]string{
|
||||||
|
"format": "https://someurl.com/{user}/{repo}/{index}",
|
||||||
|
"user": "someUser",
|
||||||
|
"repo": "someRepo",
|
||||||
|
"style": IssueNameStyleRegexp,
|
||||||
|
}
|
||||||
|
|
||||||
// these values should match the TestOrgRepo const above
|
// these values should match the TestOrgRepo const above
|
||||||
var localMetas = map[string]string{
|
var localMetas = map[string]string{
|
||||||
"user": "gogits",
|
"user": "gogits",
|
||||||
@ -184,7 +191,7 @@ func TestRender_IssueIndexPattern4(t *testing.T) {
|
|||||||
test := func(s, expectedFmt string, names ...string) {
|
test := func(s, expectedFmt string, names ...string) {
|
||||||
links := make([]interface{}, len(names))
|
links := make([]interface{}, len(names))
|
||||||
for i, name := range names {
|
for i, name := range names {
|
||||||
links[i] = alphanumIssueLink("https://someurl.com/someUser/someRepo/", "ref-issue ref-external-issue", name)
|
links[i] = externalIssueLink("https://someurl.com/someUser/someRepo/", "ref-issue ref-external-issue", name)
|
||||||
}
|
}
|
||||||
expected := fmt.Sprintf(expectedFmt, links...)
|
expected := fmt.Sprintf(expectedFmt, links...)
|
||||||
testRenderIssueIndexPattern(t, s, expected, &RenderContext{Metas: alphanumericMetas})
|
testRenderIssueIndexPattern(t, s, expected, &RenderContext{Metas: alphanumericMetas})
|
||||||
@ -194,6 +201,43 @@ func TestRender_IssueIndexPattern4(t *testing.T) {
|
|||||||
test("test issue ABCDEFGHIJ-1234567890", "test issue %s", "ABCDEFGHIJ-1234567890")
|
test("test issue ABCDEFGHIJ-1234567890", "test issue %s", "ABCDEFGHIJ-1234567890")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRender_IssueIndexPattern5(t *testing.T) {
|
||||||
|
setting.AppURL = TestAppURL
|
||||||
|
|
||||||
|
// regexp: render inputs without valid mentions
|
||||||
|
test := func(s, expectedFmt, pattern string, ids, names []string) {
|
||||||
|
metas := regexpMetas
|
||||||
|
metas["regexp"] = pattern
|
||||||
|
links := make([]interface{}, len(ids))
|
||||||
|
for i, id := range ids {
|
||||||
|
links[i] = link(util.URLJoin("https://someurl.com/someUser/someRepo/", id), "ref-issue ref-external-issue", names[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := fmt.Sprintf(expectedFmt, links...)
|
||||||
|
testRenderIssueIndexPattern(t, s, expected, &RenderContext{Metas: metas})
|
||||||
|
}
|
||||||
|
|
||||||
|
test("abc ISSUE-123 def", "abc %s def",
|
||||||
|
"ISSUE-(\\d+)",
|
||||||
|
[]string{"123"},
|
||||||
|
[]string{"ISSUE-123"},
|
||||||
|
)
|
||||||
|
|
||||||
|
test("abc (ISSUE 123) def", "abc %s def",
|
||||||
|
"\\(ISSUE (\\d+)\\)",
|
||||||
|
[]string{"123"},
|
||||||
|
[]string{"(ISSUE 123)"},
|
||||||
|
)
|
||||||
|
|
||||||
|
test("abc ISSUE-123 def", "abc %s def",
|
||||||
|
"(ISSUE-(\\d+))",
|
||||||
|
[]string{"ISSUE-123"},
|
||||||
|
[]string{"ISSUE-123"},
|
||||||
|
)
|
||||||
|
|
||||||
|
testRenderIssueIndexPattern(t, "will not match", "will not match", &RenderContext{Metas: regexpMetas})
|
||||||
|
}
|
||||||
|
|
||||||
func testRenderIssueIndexPattern(t *testing.T, input, expected string, ctx *RenderContext) {
|
func testRenderIssueIndexPattern(t *testing.T, input, expected string, ctx *RenderContext) {
|
||||||
if ctx.URLPrefix == "" {
|
if ctx.URLPrefix == "" {
|
||||||
ctx.URLPrefix = TestAppURL
|
ctx.URLPrefix = TestAppURL
|
||||||
@ -202,7 +246,7 @@ func testRenderIssueIndexPattern(t *testing.T, input, expected string, ctx *Rend
|
|||||||
var buf strings.Builder
|
var buf strings.Builder
|
||||||
err := postProcess(ctx, []processor{issueIndexPatternProcessor}, strings.NewReader(input), &buf)
|
err := postProcess(ctx, []processor{issueIndexPatternProcessor}, strings.NewReader(input), &buf)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, expected, buf.String())
|
assert.Equal(t, expected, buf.String(), "input=%q", input)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRender_AutoLink(t *testing.T) {
|
func TestRender_AutoLink(t *testing.T) {
|
||||||
|
@ -351,6 +351,24 @@ func FindRenderizableReferenceNumeric(content string, prOnly bool) (bool, *Rende
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindRenderizableReferenceRegexp returns the first regexp unvalidated references found in a string.
|
||||||
|
func FindRenderizableReferenceRegexp(content string, pattern *regexp.Regexp) (bool, *RenderizableReference) {
|
||||||
|
match := pattern.FindStringSubmatchIndex(content)
|
||||||
|
if len(match) < 4 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
action, location := findActionKeywords([]byte(content), match[2])
|
||||||
|
|
||||||
|
return true, &RenderizableReference{
|
||||||
|
Issue: content[match[2]:match[3]],
|
||||||
|
RefLocation: &RefSpan{Start: match[0], End: match[1]},
|
||||||
|
Action: action,
|
||||||
|
ActionLocation: location,
|
||||||
|
IsPull: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// FindRenderizableReferenceAlphanumeric returns the first alphanumeric unvalidated references found in a string.
|
// FindRenderizableReferenceAlphanumeric returns the first alphanumeric unvalidated references found in a string.
|
||||||
func FindRenderizableReferenceAlphanumeric(content string) (bool, *RenderizableReference) {
|
func FindRenderizableReferenceAlphanumeric(content string) (bool, *RenderizableReference) {
|
||||||
match := issueAlphanumericPattern.FindStringSubmatchIndex(content)
|
match := issueAlphanumericPattern.FindStringSubmatchIndex(content)
|
||||||
@ -547,7 +565,7 @@ func findActionKeywords(content []byte, start int) (XRefAction, *RefSpan) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IsXrefActionable returns true if the xref action is actionable (i.e. produces a result when resolved)
|
// IsXrefActionable returns true if the xref action is actionable (i.e. produces a result when resolved)
|
||||||
func IsXrefActionable(ref *RenderizableReference, extTracker, alphaNum bool) bool {
|
func IsXrefActionable(ref *RenderizableReference, extTracker bool) bool {
|
||||||
if extTracker {
|
if extTracker {
|
||||||
// External issues cannot be automatically closed
|
// External issues cannot be automatically closed
|
||||||
return false
|
return false
|
||||||
|
45
modules/regexplru/regexplru.go
Normal file
45
modules/regexplru/regexplru.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package regexplru
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
|
||||||
|
lru "github.com/hashicorp/golang-lru"
|
||||||
|
)
|
||||||
|
|
||||||
|
var lruCache *lru.Cache
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var err error
|
||||||
|
lruCache, err = lru.New(1000)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("failed to new LRU cache, err: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCompiled works like regexp.Compile, the compiled expr or error is stored in LRU cache
|
||||||
|
func GetCompiled(expr string) (r *regexp.Regexp, err error) {
|
||||||
|
v, ok := lruCache.Get(expr)
|
||||||
|
if !ok {
|
||||||
|
r, err = regexp.Compile(expr)
|
||||||
|
if err != nil {
|
||||||
|
lruCache.Add(expr, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
lruCache.Add(expr, r)
|
||||||
|
} else {
|
||||||
|
r, ok = v.(*regexp.Regexp)
|
||||||
|
if !ok {
|
||||||
|
if err, ok = v.(error); ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
panic("impossible")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
}
|
27
modules/regexplru/regexplru_test.go
Normal file
27
modules/regexplru/regexplru_test.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package regexplru
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRegexpLru(t *testing.T) {
|
||||||
|
r, err := GetCompiled("a")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, r.MatchString("a"))
|
||||||
|
|
||||||
|
r, err = GetCompiled("a")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, r.MatchString("a"))
|
||||||
|
|
||||||
|
assert.EqualValues(t, 1, lruCache.Len())
|
||||||
|
|
||||||
|
_, err = GetCompiled("(")
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.EqualValues(t, 2, lruCache.Len())
|
||||||
|
}
|
@ -32,6 +32,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/modules/emoji"
|
"code.gitea.io/gitea/modules/emoji"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
giturl "code.gitea.io/gitea/modules/git/url"
|
||||||
"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/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
@ -971,20 +972,35 @@ type remoteAddress struct {
|
|||||||
Password string
|
Password string
|
||||||
}
|
}
|
||||||
|
|
||||||
func mirrorRemoteAddress(ctx context.Context, m repo_model.RemoteMirrorer) remoteAddress {
|
func mirrorRemoteAddress(ctx context.Context, m *repo_model.Repository, remoteName string) remoteAddress {
|
||||||
a := remoteAddress{}
|
a := remoteAddress{}
|
||||||
|
if !m.IsMirror {
|
||||||
u, err := git.GetRemoteAddress(ctx, m.GetRepository().RepoPath(), m.GetRemoteName())
|
|
||||||
if err != nil {
|
|
||||||
log.Error("GetRemoteAddress %v", err)
|
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
remoteURL := m.OriginalURL
|
||||||
|
if remoteURL == "" {
|
||||||
|
var err error
|
||||||
|
remoteURL, err = git.GetRemoteAddress(ctx, m.RepoPath(), remoteName)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("GetRemoteURL %v", err)
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := giturl.Parse(remoteURL)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("giturl.Parse %v", err)
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Scheme != "ssh" && u.Scheme != "file" {
|
||||||
if u.User != nil {
|
if u.User != nil {
|
||||||
a.Username = u.User.Username()
|
a.Username = u.User.Username()
|
||||||
a.Password, _ = u.User.Password()
|
a.Password, _ = u.User.Password()
|
||||||
}
|
}
|
||||||
u.User = nil
|
u.User = nil
|
||||||
|
}
|
||||||
a.Address = u.String()
|
a.Address = u.String()
|
||||||
|
|
||||||
return a
|
return a
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"errors"
|
"errors"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -191,3 +192,35 @@ var titleCaser = cases.Title(language.English)
|
|||||||
func ToTitleCase(s string) string {
|
func ToTitleCase(s string) string {
|
||||||
return titleCaser.String(s)
|
return titleCaser.String(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
whitespaceOnly = regexp.MustCompile("(?m)^[ \t]+$")
|
||||||
|
leadingWhitespace = regexp.MustCompile("(?m)(^[ \t]*)(?:[^ \t\n])")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Dedent removes common indentation of a multi-line string along with whitespace around it
|
||||||
|
// Based on https://github.com/lithammer/dedent
|
||||||
|
func Dedent(s string) string {
|
||||||
|
var margin string
|
||||||
|
|
||||||
|
s = whitespaceOnly.ReplaceAllString(s, "")
|
||||||
|
indents := leadingWhitespace.FindAllStringSubmatch(s, -1)
|
||||||
|
|
||||||
|
for i, indent := range indents {
|
||||||
|
if i == 0 {
|
||||||
|
margin = indent[1]
|
||||||
|
} else if strings.HasPrefix(indent[1], margin) {
|
||||||
|
continue
|
||||||
|
} else if strings.HasPrefix(margin, indent[1]) {
|
||||||
|
margin = indent[1]
|
||||||
|
} else {
|
||||||
|
margin = ""
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if margin != "" {
|
||||||
|
s = regexp.MustCompile("(?m)^"+margin).ReplaceAllString(s, "")
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(s)
|
||||||
|
}
|
||||||
|
@ -225,3 +225,10 @@ func TestToTitleCase(t *testing.T) {
|
|||||||
assert.Equal(t, ToTitleCase(`foo bar baz`), `Foo Bar Baz`)
|
assert.Equal(t, ToTitleCase(`foo bar baz`), `Foo Bar Baz`)
|
||||||
assert.Equal(t, ToTitleCase(`FOO BAR BAZ`), `Foo Bar Baz`)
|
assert.Equal(t, ToTitleCase(`FOO BAR BAZ`), `Foo Bar Baz`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDedent(t *testing.T) {
|
||||||
|
assert.Equal(t, Dedent(`
|
||||||
|
foo
|
||||||
|
bar
|
||||||
|
`), "foo\n\tbar")
|
||||||
|
}
|
||||||
|
@ -955,6 +955,7 @@ branch.included=Включено
|
|||||||
topic.done=Готово
|
topic.done=Готово
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[org]
|
[org]
|
||||||
org_name_holder=Име на организацията
|
org_name_holder=Име на организацията
|
||||||
org_full_name_holder=Пълно име на организацията
|
org_full_name_holder=Пълно име на организацията
|
||||||
|
@ -2183,6 +2183,7 @@ topic.done=Hotovo
|
|||||||
topic.count_prompt=Nelze vybrat více než 25 témat
|
topic.count_prompt=Nelze vybrat více než 25 témat
|
||||||
topic.format_prompt=Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
|
topic.format_prompt=Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
|
||||||
|
|
||||||
|
|
||||||
error.csv.too_large=Tento soubor nelze vykreslit, protože je příliš velký.
|
error.csv.too_large=Tento soubor nelze vykreslit, protože je příliš velký.
|
||||||
error.csv.unexpected=Tento soubor nelze vykreslit, protože obsahuje neočekávaný znak na řádku %d ve sloupci %d.
|
error.csv.unexpected=Tento soubor nelze vykreslit, protože obsahuje neočekávaný znak na řádku %d ve sloupci %d.
|
||||||
error.csv.invalid_field_count=Soubor nelze vykreslit, protože má nesprávný počet polí na řádku %d.
|
error.csv.invalid_field_count=Soubor nelze vykreslit, protože má nesprávný počet polí na řádku %d.
|
||||||
|
@ -2218,6 +2218,7 @@ topic.done=Fertig
|
|||||||
topic.count_prompt=Du kannst nicht mehr als 25 Themen auswählen
|
topic.count_prompt=Du kannst nicht mehr als 25 Themen auswählen
|
||||||
topic.format_prompt=Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
|
topic.format_prompt=Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
|
||||||
|
|
||||||
|
|
||||||
error.csv.too_large=Diese Datei kann nicht gerendert werden, da sie zu groß ist.
|
error.csv.too_large=Diese Datei kann nicht gerendert werden, da sie zu groß ist.
|
||||||
error.csv.unexpected=Diese Datei kann nicht gerendert werden, da sie ein unerwartetes Zeichen in Zeile %d und Spalte %d enthält.
|
error.csv.unexpected=Diese Datei kann nicht gerendert werden, da sie ein unerwartetes Zeichen in Zeile %d und Spalte %d enthält.
|
||||||
error.csv.invalid_field_count=Diese Datei kann nicht gerendert werden, da sie eine falsche Anzahl an Feldern in Zeile %d hat.
|
error.csv.invalid_field_count=Diese Datei kann nicht gerendert werden, da sie eine falsche Anzahl an Feldern in Zeile %d hat.
|
||||||
|
@ -2,6 +2,7 @@ home=Αρχική
|
|||||||
dashboard=Κεντρικός Πίνακας
|
dashboard=Κεντρικός Πίνακας
|
||||||
explore=Εξερεύνηση
|
explore=Εξερεύνηση
|
||||||
help=Βοήθεια
|
help=Βοήθεια
|
||||||
|
logo=Λογότυπο
|
||||||
sign_in=Είσοδος
|
sign_in=Είσοδος
|
||||||
sign_in_with=Είσοδος με
|
sign_in_with=Είσοδος με
|
||||||
sign_out=Έξοδος
|
sign_out=Έξοδος
|
||||||
@ -716,6 +717,9 @@ generate_token_success=Το νέο διακριτικό σας έχει δημι
|
|||||||
generate_token_name_duplicate=Το <strong>%s</strong> έχει ήδη χρησιμοποιηθεί ως όνομα εφαρμογής. Παρακαλούμε χρησιμοποιήστε ένα νέο.
|
generate_token_name_duplicate=Το <strong>%s</strong> έχει ήδη χρησιμοποιηθεί ως όνομα εφαρμογής. Παρακαλούμε χρησιμοποιήστε ένα νέο.
|
||||||
delete_token=Διαγραφή
|
delete_token=Διαγραφή
|
||||||
access_token_deletion=Διαγραφή Διακριτικού Πρόσβασης
|
access_token_deletion=Διαγραφή Διακριτικού Πρόσβασης
|
||||||
|
access_token_deletion_cancel_action=Άκυρο
|
||||||
|
access_token_deletion_confirm_action=Διαγραφή
|
||||||
|
access_token_deletion_desc=Η διαγραφή ενός διακριτικού θα ανακαλέσει οριστικά την πρόσβαση στο λογαριασμό σας για εφαρμογές που το χρησιμοποιούν. Συνέχεια;
|
||||||
delete_token_success=Το διακριτικό έχει διαγραφεί. Οι εφαρμογές που το χρησιμοποιούν δεν έχουν πλέον πρόσβαση στο λογαριασμό σας.
|
delete_token_success=Το διακριτικό έχει διαγραφεί. Οι εφαρμογές που το χρησιμοποιούν δεν έχουν πλέον πρόσβαση στο λογαριασμό σας.
|
||||||
|
|
||||||
manage_oauth2_applications=Διαχείριση Εφαρμογών Oauth2
|
manage_oauth2_applications=Διαχείριση Εφαρμογών Oauth2
|
||||||
@ -858,6 +862,7 @@ default_branch=Προεπιλεγμένος Κλάδος
|
|||||||
default_branch_helper=Ο προεπιλεγμένος κλάδος είναι ο βασικός κλάδος για pull requests και υποβολές κώδικα.
|
default_branch_helper=Ο προεπιλεγμένος κλάδος είναι ο βασικός κλάδος για pull requests και υποβολές κώδικα.
|
||||||
mirror_prune=Καθαρισμός
|
mirror_prune=Καθαρισμός
|
||||||
mirror_prune_desc=Αφαίρεση παρωχημένων αναφορών απομακρυσμένης-παρακολούθησης
|
mirror_prune_desc=Αφαίρεση παρωχημένων αναφορών απομακρυσμένης-παρακολούθησης
|
||||||
|
mirror_interval=Διάστημα ανανέωσης ειδώλου (έγκυρες μονάδες ώρας είναι 'h', 'm', 's'). 0 για απενεργοποίηση του αυτόματου συγχρονισμού. (Ελάχιστο διάστημα: %s)
|
||||||
mirror_interval_invalid=Το χρονικό διάστημα του ειδώλου δεν είναι έγκυρο.
|
mirror_interval_invalid=Το χρονικό διάστημα του ειδώλου δεν είναι έγκυρο.
|
||||||
mirror_address=Κλωνοποίηση Από Το URL
|
mirror_address=Κλωνοποίηση Από Το URL
|
||||||
mirror_address_desc=Τοποθετήστε όλα τα απαιτούμενα διαπιστευτήρια στην ενότητα Εξουσιοδότηση.
|
mirror_address_desc=Τοποθετήστε όλα τα απαιτούμενα διαπιστευτήρια στην ενότητα Εξουσιοδότηση.
|
||||||
@ -1688,7 +1693,7 @@ activity.period.filter_label=Περίοδος:
|
|||||||
activity.period.daily=1 ημέρα
|
activity.period.daily=1 ημέρα
|
||||||
activity.period.halfweekly=3 ημέρες
|
activity.period.halfweekly=3 ημέρες
|
||||||
activity.period.weekly=1 εβδομάδα
|
activity.period.weekly=1 εβδομάδα
|
||||||
activity.period.monthly=1 μήνας
|
activity.period.monthly=1 μήνα
|
||||||
activity.period.quarterly=3 μήνες
|
activity.period.quarterly=3 μήνες
|
||||||
activity.period.semiyearly=6 μήνες
|
activity.period.semiyearly=6 μήνες
|
||||||
activity.period.yearly=1 έτος
|
activity.period.yearly=1 έτος
|
||||||
@ -2281,6 +2286,9 @@ topic.done=Ολοκληρώθηκε
|
|||||||
topic.count_prompt=Δεν μπορείτε να επιλέξετε περισσότερα από 25 θέματα
|
topic.count_prompt=Δεν μπορείτε να επιλέξετε περισσότερα από 25 θέματα
|
||||||
topic.format_prompt=Τα θέματα πρέπει να ξεκινούν με γράμμα ή αριθμό, μπορούν να περιλαμβάνουν παύλες ('-') και μπορεί να είναι έως 35 χαρακτήρες.
|
topic.format_prompt=Τα θέματα πρέπει να ξεκινούν με γράμμα ή αριθμό, μπορούν να περιλαμβάνουν παύλες ('-') και μπορεί να είναι έως 35 χαρακτήρες.
|
||||||
|
|
||||||
|
find_file.go_to_file=Μετάβαση στο αρχείο
|
||||||
|
find_file.no_matching=Δεν ταιριάζει κανένα αρχείο
|
||||||
|
|
||||||
error.csv.too_large=Δεν είναι δυνατή η απόδοση αυτού του αρχείου επειδή είναι πολύ μεγάλο.
|
error.csv.too_large=Δεν είναι δυνατή η απόδοση αυτού του αρχείου επειδή είναι πολύ μεγάλο.
|
||||||
error.csv.unexpected=Δεν είναι δυνατή η απόδοση αυτού του αρχείου, επειδή περιέχει έναν μη αναμενόμενο χαρακτήρα στη γραμμή %d και στη στήλη %d.
|
error.csv.unexpected=Δεν είναι δυνατή η απόδοση αυτού του αρχείου, επειδή περιέχει έναν μη αναμενόμενο χαρακτήρα στη γραμμή %d και στη στήλη %d.
|
||||||
error.csv.invalid_field_count=Δεν είναι δυνατή η απόδοση αυτού του αρχείου, επειδή έχει λάθος αριθμό πεδίων στη γραμμή %d.
|
error.csv.invalid_field_count=Δεν είναι δυνατή η απόδοση αυτού του αρχείου, επειδή έχει λάθος αριθμό πεδίων στη γραμμή %d.
|
||||||
|
@ -1568,14 +1568,7 @@ pulls.squash_merge_pull_request = Create squash commit
|
|||||||
pulls.merge_manually = Manually merged
|
pulls.merge_manually = Manually merged
|
||||||
pulls.merge_commit_id = The merge commit ID
|
pulls.merge_commit_id = The merge commit ID
|
||||||
pulls.require_signed_wont_sign = The branch requires signed commits but this merge will not be signed
|
pulls.require_signed_wont_sign = The branch requires signed commits but this merge will not be signed
|
||||||
pulls.merge_pull_request_now = Merge Pull Request Now
|
|
||||||
pulls.rebase_merge_pull_request_now = Rebase and Merge Now
|
|
||||||
pulls.rebase_merge_commit_pull_request_now = Rebase and Merge Now (--no-ff)
|
|
||||||
pulls.squash_merge_pull_request_now = Squash and Merge Now
|
|
||||||
pulls.merge_pull_request_on_status_success = Merge Pull Request When All Checks Succeed
|
|
||||||
pulls.rebase_merge_pull_request_on_status_success = Rebase and Merge When All Checks Succeed
|
|
||||||
pulls.rebase_merge_commit_pull_request_on_status_success = Rebase and Merge (--no-ff) When All Checks Succeed
|
|
||||||
pulls.squash_merge_pull_request_on_status_success = Squash and Merge When All Checks Succeed
|
|
||||||
pulls.invalid_merge_option = You cannot use this merge option for this pull request.
|
pulls.invalid_merge_option = You cannot use this merge option for this pull request.
|
||||||
pulls.merge_conflict = Merge Failed: There was a conflict whilst merging. Hint: Try a different strategy
|
pulls.merge_conflict = Merge Failed: There was a conflict whilst merging. Hint: Try a different strategy
|
||||||
pulls.merge_conflict_summary = Error Message
|
pulls.merge_conflict_summary = Error Message
|
||||||
@ -1606,14 +1599,18 @@ pulls.reopened_at = `reopened this pull request <a id="%[1]s" href="#%[1]s">%[2]
|
|||||||
pulls.merge_instruction_hint = `You can also view <a class="show-instruction">command line instructions</a>.`
|
pulls.merge_instruction_hint = `You can also view <a class="show-instruction">command line instructions</a>.`
|
||||||
pulls.merge_instruction_step1_desc = From your project repository, check out a new branch and test the changes.
|
pulls.merge_instruction_step1_desc = From your project repository, check out a new branch and test the changes.
|
||||||
pulls.merge_instruction_step2_desc = Merge the changes and update on Gitea.
|
pulls.merge_instruction_step2_desc = Merge the changes and update on Gitea.
|
||||||
pulls.merge_on_status_success = The pull request was scheduled to merge when all checks succeed.
|
|
||||||
pulls.merge_on_status_success_already_scheduled = This pull request is already scheduled to merge when all checks succeed.
|
pulls.auto_merge_button_when_succeed = (When checks succeed)
|
||||||
pulls.pr_has_pending_merge_on_success = %[1]s scheduled this pull request to auto merge when all checks succeed %[2]s.
|
pulls.auto_merge_when_succeed = Auto merge when all checks succeed
|
||||||
pulls.merge_pull_on_success_cancel = Cancel auto merge
|
pulls.auto_merge_newly_scheduled = The pull request was scheduled to merge when all checks succeed.
|
||||||
pulls.pull_request_not_scheduled = This pull request is not scheduled to auto merge.
|
pulls.auto_merge_has_pending_schedule = %[1]s scheduled this pull request to auto merge when all checks succeed %[2]s.
|
||||||
pulls.pull_request_schedule_canceled = The auto merge was canceled for this pull request.
|
|
||||||
pulls.pull_request_scheduled_auto_merge = `scheduled this pull request to auto merge when all checks succeed %[1]s`
|
pulls.auto_merge_cancel_schedule = Cancel auto merge
|
||||||
pulls.pull_request_canceled_scheduled_auto_merge = `canceled auto merging this pull request when all checks succeed %[1]s`
|
pulls.auto_merge_not_scheduled = This pull request is not scheduled to auto merge.
|
||||||
|
pulls.auto_merge_canceled_schedule = The auto merge was canceled for this pull request.
|
||||||
|
|
||||||
|
pulls.auto_merge_newly_scheduled_comment = `scheduled this pull request to auto merge when all checks succeed %[1]s`
|
||||||
|
pulls.auto_merge_canceled_schedule_comment = `canceled auto merging this pull request when all checks succeed %[1]s`
|
||||||
|
|
||||||
milestones.new = New Milestone
|
milestones.new = New Milestone
|
||||||
milestones.open_tab = %d Open
|
milestones.open_tab = %d Open
|
||||||
@ -1811,6 +1808,9 @@ settings.tracker_url_format_error = The external issue tracker URL format is not
|
|||||||
settings.tracker_issue_style = External Issue Tracker Number Format
|
settings.tracker_issue_style = External Issue Tracker Number Format
|
||||||
settings.tracker_issue_style.numeric = Numeric
|
settings.tracker_issue_style.numeric = Numeric
|
||||||
settings.tracker_issue_style.alphanumeric = Alphanumeric
|
settings.tracker_issue_style.alphanumeric = Alphanumeric
|
||||||
|
settings.tracker_issue_style.regexp = Regular Expression
|
||||||
|
settings.tracker_issue_style.regexp_pattern = Regular Expression Pattern
|
||||||
|
settings.tracker_issue_style.regexp_pattern_desc = The first captured group will be used in place of <code>{index}</code>.
|
||||||
settings.tracker_url_format_desc = Use the placeholders <code>{user}</code>, <code>{repo}</code> and <code>{index}</code> for the username, repository name and issue index.
|
settings.tracker_url_format_desc = Use the placeholders <code>{user}</code>, <code>{repo}</code> and <code>{index}</code> for the username, repository name and issue index.
|
||||||
settings.enable_timetracker = Enable Time Tracking
|
settings.enable_timetracker = Enable Time Tracking
|
||||||
settings.allow_only_contributors_to_track_time = Let Only Contributors Track Time
|
settings.allow_only_contributors_to_track_time = Let Only Contributors Track Time
|
||||||
|
@ -2198,6 +2198,7 @@ topic.done=Hecho
|
|||||||
topic.count_prompt=No puede seleccionar más de 25 temas
|
topic.count_prompt=No puede seleccionar más de 25 temas
|
||||||
topic.format_prompt=Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.
|
topic.format_prompt=Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.
|
||||||
|
|
||||||
|
|
||||||
error.csv.too_large=No se puede renderizar este archivo porque es demasiado grande.
|
error.csv.too_large=No se puede renderizar este archivo porque es demasiado grande.
|
||||||
error.csv.unexpected=No se puede procesar este archivo porque contiene un carácter inesperado en la línea %d y la columna %d.
|
error.csv.unexpected=No se puede procesar este archivo porque contiene un carácter inesperado en la línea %d y la columna %d.
|
||||||
error.csv.invalid_field_count=No se puede procesar este archivo porque tiene un número incorrecto de campos en la línea %d.
|
error.csv.invalid_field_count=No se puede procesar este archivo porque tiene un número incorrecto de campos en la línea %d.
|
||||||
|
@ -2110,6 +2110,7 @@ topic.done=انجام شد
|
|||||||
topic.count_prompt=شما نمی توانید بیش از 25 موضوع انتخاب کنید
|
topic.count_prompt=شما نمی توانید بیش از 25 موضوع انتخاب کنید
|
||||||
topic.format_prompt=موضوع میبایستی با حروف یا شماره ها شروع شود. و میتواند شامل دَش ('-') باشد و طول آن تا 35 کارکتر نیز امکانپذیر است.
|
topic.format_prompt=موضوع میبایستی با حروف یا شماره ها شروع شود. و میتواند شامل دَش ('-') باشد و طول آن تا 35 کارکتر نیز امکانپذیر است.
|
||||||
|
|
||||||
|
|
||||||
error.csv.too_large=نمی توان این فایل را رندر کرد زیرا بسیار بزرگ است.
|
error.csv.too_large=نمی توان این فایل را رندر کرد زیرا بسیار بزرگ است.
|
||||||
error.csv.unexpected=نمی توان این فایل را رندر کرد زیرا حاوی یک کاراکتر غیرمنتظره در خط %d و ستون %d است.
|
error.csv.unexpected=نمی توان این فایل را رندر کرد زیرا حاوی یک کاراکتر غیرمنتظره در خط %d و ستون %d است.
|
||||||
error.csv.invalid_field_count=نمی توان این فایل را رندر کرد زیرا تعداد فیلدهای آن در خط %d اشتباه است.
|
error.csv.invalid_field_count=نمی توان این فایل را رندر کرد زیرا تعداد فیلدهای آن در خط %d اشتباه است.
|
||||||
|
@ -971,6 +971,7 @@ topic.manage_topics=Hallitse aiheita
|
|||||||
topic.done=Valmis
|
topic.done=Valmis
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[org]
|
[org]
|
||||||
org_name_holder=Organisaatio
|
org_name_holder=Organisaatio
|
||||||
org_full_name_holder=Organisaation täydellinen nimi
|
org_full_name_holder=Organisaation täydellinen nimi
|
||||||
|
@ -1973,6 +1973,7 @@ topic.count_prompt=Vous ne pouvez pas sélectionner plus de 25 sujets
|
|||||||
topic.format_prompt=Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
|
topic.format_prompt=Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[org]
|
[org]
|
||||||
org_name_holder=Nom de l'organisation
|
org_name_holder=Nom de l'organisation
|
||||||
org_full_name_holder=Nom complet de l'organisation
|
org_full_name_holder=Nom complet de l'organisation
|
||||||
|
@ -1305,6 +1305,7 @@ topic.manage_topics=Témák kezelése
|
|||||||
topic.done=Kész
|
topic.done=Kész
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[org]
|
[org]
|
||||||
org_name_holder=Szervezet neve
|
org_name_holder=Szervezet neve
|
||||||
org_full_name_holder=Szervezet teljes neve
|
org_full_name_holder=Szervezet teljes neve
|
||||||
|
@ -1019,6 +1019,7 @@ branch.deleted_by=Dihapus oleh %s
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[org]
|
[org]
|
||||||
org_name_holder=Nama Organisasi
|
org_name_holder=Nama Organisasi
|
||||||
org_full_name_holder=Organisasi Nama Lengkap
|
org_full_name_holder=Organisasi Nama Lengkap
|
||||||
|
@ -1131,6 +1131,7 @@ tag.confirm_create_tag=Skapa merki
|
|||||||
topic.done=Í lagi
|
topic.done=Í lagi
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[org]
|
[org]
|
||||||
repo_updated=Uppfært
|
repo_updated=Uppfært
|
||||||
people=Fólk
|
people=Fólk
|
||||||
|
@ -1781,6 +1781,7 @@ topic.count_prompt=Non puoi selezionare più di 25 argomenti
|
|||||||
topic.format_prompt=Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
|
topic.format_prompt=Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[org]
|
[org]
|
||||||
org_name_holder=Nome dell'Organizzazione
|
org_name_holder=Nome dell'Organizzazione
|
||||||
org_full_name_holder=Nome completo dell'organizzazione
|
org_full_name_holder=Nome completo dell'organizzazione
|
||||||
|
@ -2281,6 +2281,7 @@ topic.done=完了
|
|||||||
topic.count_prompt=選択できるのは25トピックまでです。
|
topic.count_prompt=選択できるのは25トピックまでです。
|
||||||
topic.format_prompt=トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
|
topic.format_prompt=トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
|
||||||
|
|
||||||
|
|
||||||
error.csv.too_large=このファイルは大きすぎるため表示できません。
|
error.csv.too_large=このファイルは大きすぎるため表示できません。
|
||||||
error.csv.unexpected=このファイルは %d 行目の %d 文字目に予期しない文字が含まれているため表示できません。
|
error.csv.unexpected=このファイルは %d 行目の %d 文字目に予期しない文字が含まれているため表示できません。
|
||||||
error.csv.invalid_field_count=このファイルは %d 行目のフィールドの数が正しくないため表示できません。
|
error.csv.invalid_field_count=このファイルは %d 行目のフィールドの数が正しくないため表示できません。
|
||||||
|
@ -1173,6 +1173,7 @@ topic.done=완료
|
|||||||
topic.count_prompt=25개 이상의 토픽을 선택하실 수 없습니다.
|
topic.count_prompt=25개 이상의 토픽을 선택하실 수 없습니다.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[org]
|
[org]
|
||||||
org_name_holder=조직 이름
|
org_name_holder=조직 이름
|
||||||
org_full_name_holder=조직 전체 이름
|
org_full_name_holder=조직 전체 이름
|
||||||
|
@ -2186,6 +2186,7 @@ topic.done=Gatavs
|
|||||||
topic.count_prompt=Nevar pievienot vairāk kā 25 tēmas
|
topic.count_prompt=Nevar pievienot vairāk kā 25 tēmas
|
||||||
topic.format_prompt=Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.
|
topic.format_prompt=Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.
|
||||||
|
|
||||||
|
|
||||||
error.csv.too_large=Nevar attēlot šo failu, jo tas ir pārāk liels.
|
error.csv.too_large=Nevar attēlot šo failu, jo tas ir pārāk liels.
|
||||||
error.csv.unexpected=Nevar attēlot šo failu, jo tas satur neparedzētu simbolu %d. līnijas %d. kolonnā.
|
error.csv.unexpected=Nevar attēlot šo failu, jo tas satur neparedzētu simbolu %d. līnijas %d. kolonnā.
|
||||||
error.csv.invalid_field_count=Nevar attēlot šo failu, jo tas satur nepareizu skaitu ar laukiem %d. līnijā.
|
error.csv.invalid_field_count=Nevar attēlot šo failu, jo tas satur nepareizu skaitu ar laukiem %d. līnijā.
|
||||||
|
@ -746,6 +746,7 @@ settings.event_issues=ഇഷ്യൂകള്
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[org]
|
[org]
|
||||||
|
|
||||||
|
|
||||||
|
@ -1833,6 +1833,7 @@ topic.count_prompt=Je kunt niet meer dan 25 onderwerpen selecteren
|
|||||||
topic.format_prompt=Onderwerpen moeten beginnen met een letter of nummer, kunnen streepjes bevatten ('-') en kunnen maximaal 35 tekens lang zijn.
|
topic.format_prompt=Onderwerpen moeten beginnen met een letter of nummer, kunnen streepjes bevatten ('-') en kunnen maximaal 35 tekens lang zijn.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[org]
|
[org]
|
||||||
org_name_holder=Organisatienaam
|
org_name_holder=Organisatienaam
|
||||||
org_full_name_holder=Volledige naam organisatie
|
org_full_name_holder=Volledige naam organisatie
|
||||||
|
@ -2056,6 +2056,7 @@ topic.done=Gotowe
|
|||||||
topic.count_prompt=Nie możesz wybrać więcej, niż 25 tematów
|
topic.count_prompt=Nie możesz wybrać więcej, niż 25 tematów
|
||||||
topic.format_prompt=Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
|
topic.format_prompt=Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
|
||||||
|
|
||||||
|
|
||||||
error.csv.too_large=Nie można wyświetlić tego pliku, ponieważ jest on zbyt duży.
|
error.csv.too_large=Nie można wyświetlić tego pliku, ponieważ jest on zbyt duży.
|
||||||
error.csv.unexpected=Nie można renderować tego pliku, ponieważ zawiera nieoczekiwany znak w wierszu %d i kolumnie %d.
|
error.csv.unexpected=Nie można renderować tego pliku, ponieważ zawiera nieoczekiwany znak w wierszu %d i kolumnie %d.
|
||||||
error.csv.invalid_field_count=Nie można renderować tego pliku, ponieważ ma nieprawidłową liczbę pól w wierszu %d.
|
error.csv.invalid_field_count=Nie można renderować tego pliku, ponieważ ma nieprawidłową liczbę pól w wierszu %d.
|
||||||
|
@ -2268,6 +2268,7 @@ topic.done=Feito
|
|||||||
topic.count_prompt=Você não pode selecionar mais de 25 tópicos
|
topic.count_prompt=Você não pode selecionar mais de 25 tópicos
|
||||||
topic.format_prompt=Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
|
topic.format_prompt=Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
|
||||||
|
|
||||||
|
|
||||||
error.csv.too_large=Não é possível renderizar este arquivo porque ele é muito grande.
|
error.csv.too_large=Não é possível renderizar este arquivo porque ele é muito grande.
|
||||||
error.csv.unexpected=Não é possível renderizar este arquivo porque ele contém um caractere inesperado na linha %d e coluna %d.
|
error.csv.unexpected=Não é possível renderizar este arquivo porque ele contém um caractere inesperado na linha %d e coluna %d.
|
||||||
error.csv.invalid_field_count=Não é possível renderizar este arquivo porque ele tem um número errado de campos na linha %d.
|
error.csv.invalid_field_count=Não é possível renderizar este arquivo porque ele tem um número errado de campos na linha %d.
|
||||||
|
@ -2285,6 +2285,9 @@ topic.done=Concluído
|
|||||||
topic.count_prompt=Não pode escolher mais do que 25 tópicos
|
topic.count_prompt=Não pode escolher mais do que 25 tópicos
|
||||||
topic.format_prompt=Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
|
topic.format_prompt=Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
|
||||||
|
|
||||||
|
find_file.go_to_file=Ir para o ficheiro
|
||||||
|
find_file.no_matching=Não foi encontrado qualquer ficheiro correspondente
|
||||||
|
|
||||||
error.csv.too_large=Não é possível apresentar este ficheiro por ser demasiado grande.
|
error.csv.too_large=Não é possível apresentar este ficheiro por ser demasiado grande.
|
||||||
error.csv.unexpected=Não é possível apresentar este ficheiro porque contém um caractere inesperado na linha %d e coluna %d.
|
error.csv.unexpected=Não é possível apresentar este ficheiro porque contém um caractere inesperado na linha %d e coluna %d.
|
||||||
error.csv.invalid_field_count=Não é possível apresentar este ficheiro porque tem um número errado de campos na linha %d.
|
error.csv.invalid_field_count=Não é possível apresentar este ficheiro porque tem um número errado de campos na linha %d.
|
||||||
|
@ -2203,6 +2203,7 @@ topic.done=Сохранить
|
|||||||
topic.count_prompt=Вы не можете выбрать более 25 тем
|
topic.count_prompt=Вы не можете выбрать более 25 тем
|
||||||
topic.format_prompt=Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
|
topic.format_prompt=Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
|
||||||
|
|
||||||
|
|
||||||
error.csv.too_large=Не удается отобразить этот файл, потому что он слишком большой.
|
error.csv.too_large=Не удается отобразить этот файл, потому что он слишком большой.
|
||||||
error.csv.unexpected=Не удается отобразить этот файл, потому что он содержит неожиданный символ в строке %d и столбце %d.
|
error.csv.unexpected=Не удается отобразить этот файл, потому что он содержит неожиданный символ в строке %d и столбце %d.
|
||||||
error.csv.invalid_field_count=Не удается отобразить этот файл, потому что он имеет неправильное количество полей в строке %d.
|
error.csv.invalid_field_count=Не удается отобразить этот файл, потому что он имеет неправильное количество полей в строке %d.
|
||||||
|
@ -2046,6 +2046,7 @@ topic.done=සිදු
|
|||||||
topic.count_prompt=ඔබට 25 මාතෘකා වලට වඩා තෝරා ගත නොහැක
|
topic.count_prompt=ඔබට 25 මාතෘකා වලට වඩා තෝරා ගත නොහැක
|
||||||
topic.format_prompt=මාතෘකා අකුරකින් හෝ අංකයකින් ආරම්භ කළ යුතුය, දෂ්ට කිරීම් ඇතුළත් කළ හැකිය ('-') සහ අක්ෂර 35 ක් දිගු විය හැකිය.
|
topic.format_prompt=මාතෘකා අකුරකින් හෝ අංකයකින් ආරම්භ කළ යුතුය, දෂ්ට කිරීම් ඇතුළත් කළ හැකිය ('-') සහ අක්ෂර 35 ක් දිගු විය හැකිය.
|
||||||
|
|
||||||
|
|
||||||
error.csv.too_large=එය ඉතා විශාල නිසා මෙම ගොනුව විදැහුම්කරණය කළ නොහැක.
|
error.csv.too_large=එය ඉතා විශාල නිසා මෙම ගොනුව විදැහුම්කරණය කළ නොහැක.
|
||||||
error.csv.unexpected=%d පේළියේ සහ %dතීරුවේ අනපේක්ෂිත චරිතයක් අඩංගු බැවින් මෙම ගොනුව විදැහුම්කරණය කළ නොහැක.
|
error.csv.unexpected=%d පේළියේ සහ %dතීරුවේ අනපේක්ෂිත චරිතයක් අඩංගු බැවින් මෙම ගොනුව විදැහුම්කරණය කළ නොහැක.
|
||||||
error.csv.invalid_field_count=මෙම ගොනුව රේඛාවේ වැරදි ක්ෂේත්ර සංඛ්යාවක් ඇති බැවින් එය විදැහුම්කරණය කළ නොහැක %d.
|
error.csv.invalid_field_count=මෙම ගොනුව රේඛාවේ වැරදි ක්ෂේත්ර සංඛ්යාවක් ඇති බැවින් එය විදැහුම්කරණය කළ නොහැක %d.
|
||||||
|
@ -1619,6 +1619,7 @@ topic.count_prompt=Du kan inte välja fler än 25 ämnen
|
|||||||
topic.format_prompt=Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.
|
topic.format_prompt=Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[org]
|
[org]
|
||||||
org_name_holder=Organisationsnamn
|
org_name_holder=Organisationsnamn
|
||||||
org_full_name_holder=Organisationens Fullständiga Namn
|
org_full_name_holder=Organisationens Fullständiga Namn
|
||||||
|
@ -2057,6 +2057,7 @@ topic.done=Bitti
|
|||||||
topic.count_prompt=25'ten fazla konu seçemezsiniz
|
topic.count_prompt=25'ten fazla konu seçemezsiniz
|
||||||
topic.format_prompt=Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.
|
topic.format_prompt=Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.
|
||||||
|
|
||||||
|
|
||||||
error.csv.too_large=Bu dosya çok büyük olduğu için işlenemiyor.
|
error.csv.too_large=Bu dosya çok büyük olduğu için işlenemiyor.
|
||||||
error.csv.unexpected=%d satırı ve %d sütununda beklenmeyen bir karakter içerdiğinden bu dosya işlenemiyor.
|
error.csv.unexpected=%d satırı ve %d sütununda beklenmeyen bir karakter içerdiğinden bu dosya işlenemiyor.
|
||||||
error.csv.invalid_field_count=%d satırında yanlış sayıda alan olduğundan bu dosya işlenemiyor.
|
error.csv.invalid_field_count=%d satırında yanlış sayıda alan olduğundan bu dosya işlenemiyor.
|
||||||
|
@ -2118,6 +2118,7 @@ topic.done=Готово
|
|||||||
topic.count_prompt=Ви не можете вибрати більше 25 тем
|
topic.count_prompt=Ви не можете вибрати більше 25 тем
|
||||||
topic.format_prompt=Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
|
topic.format_prompt=Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
|
||||||
|
|
||||||
|
|
||||||
error.csv.too_large=Не вдається відобразити цей файл, тому що він завеликий.
|
error.csv.too_large=Не вдається відобразити цей файл, тому що він завеликий.
|
||||||
error.csv.unexpected=Не вдається відобразити цей файл, тому що він містить неочікуваний символ в рядку %d і стовпці %d.
|
error.csv.unexpected=Не вдається відобразити цей файл, тому що він містить неочікуваний символ в рядку %d і стовпці %d.
|
||||||
error.csv.invalid_field_count=Не вдається відобразити цей файл, тому що він має неправильну кількість полів у рядку %d.
|
error.csv.invalid_field_count=Не вдається відобразити цей файл, тому що він має неправильну кількість полів у рядку %d.
|
||||||
|
@ -2283,6 +2283,7 @@ topic.done=保存
|
|||||||
topic.count_prompt=您最多选择25个主题
|
topic.count_prompt=您最多选择25个主题
|
||||||
topic.format_prompt=主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
|
topic.format_prompt=主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
|
||||||
|
|
||||||
|
|
||||||
error.csv.too_large=无法渲染此文件,因为它太大了。
|
error.csv.too_large=无法渲染此文件,因为它太大了。
|
||||||
error.csv.unexpected=无法渲染此文件,因为它包含了意外字符,其位于第 %d 行和第 %d 列。
|
error.csv.unexpected=无法渲染此文件,因为它包含了意外字符,其位于第 %d 行和第 %d 列。
|
||||||
error.csv.invalid_field_count=无法渲染此文件,因为它在第 %d 行中的字段数有误。
|
error.csv.invalid_field_count=无法渲染此文件,因为它在第 %d 行中的字段数有误。
|
||||||
|
@ -559,6 +559,7 @@ release.downloads=下載附件
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[org]
|
[org]
|
||||||
org_name_holder=組織名稱
|
org_name_holder=組織名稱
|
||||||
org_full_name_holder=組織全名
|
org_full_name_holder=組織全名
|
||||||
|
@ -715,6 +715,9 @@ generate_token_success=已經產生新的 Token。請立刻複製它,因為他
|
|||||||
generate_token_name_duplicate=應用程式名稱 <strong>%s</strong> 已被使用,請換一個試試。
|
generate_token_name_duplicate=應用程式名稱 <strong>%s</strong> 已被使用,請換一個試試。
|
||||||
delete_token=刪除
|
delete_token=刪除
|
||||||
access_token_deletion=刪除 Access Token
|
access_token_deletion=刪除 Access Token
|
||||||
|
access_token_deletion_cancel_action=取消
|
||||||
|
access_token_deletion_confirm_action=刪除
|
||||||
|
access_token_deletion_desc=刪除 Token 後,使用此 Token 的應用程式將無法再存取您的帳戶,此動作不可還原。是否繼續?
|
||||||
delete_token_success=已刪除 Token。使用此 Token 的應用程式無法再存取您的帳戶。
|
delete_token_success=已刪除 Token。使用此 Token 的應用程式無法再存取您的帳戶。
|
||||||
|
|
||||||
manage_oauth2_applications=管理 OAuth2 應用程式
|
manage_oauth2_applications=管理 OAuth2 應用程式
|
||||||
@ -857,6 +860,7 @@ default_branch=預設分支
|
|||||||
default_branch_helper=預設分支是合併請求和提交程式碼的基礎分支。
|
default_branch_helper=預設分支是合併請求和提交程式碼的基礎分支。
|
||||||
mirror_prune=裁減
|
mirror_prune=裁減
|
||||||
mirror_prune_desc=刪除過時的遠端追蹤參考
|
mirror_prune_desc=刪除過時的遠端追蹤參考
|
||||||
|
mirror_interval=鏡像間隔 (有效時間單位為 'h'、'm'、's'),設為 0 以停用自動同步。(最小間隔: %s)
|
||||||
mirror_interval_invalid=鏡像週期無效
|
mirror_interval_invalid=鏡像週期無效
|
||||||
mirror_address=從 URL Clone
|
mirror_address=從 URL Clone
|
||||||
mirror_address_desc=在授權資訊中填入必要的資料。
|
mirror_address_desc=在授權資訊中填入必要的資料。
|
||||||
@ -1856,7 +1860,7 @@ settings.confirm_wiki_delete=刪除 Wiki 資料
|
|||||||
settings.wiki_deletion_success=已刪除儲存庫的 Wiki 資料。
|
settings.wiki_deletion_success=已刪除儲存庫的 Wiki 資料。
|
||||||
settings.delete=刪除本儲存庫
|
settings.delete=刪除本儲存庫
|
||||||
settings.delete_desc=刪除儲存庫是永久的且不可還原。
|
settings.delete_desc=刪除儲存庫是永久的且不可還原。
|
||||||
settings.delete_notices_1=- 此操作<strong>不可</strong>還原。
|
settings.delete_notices_1=- 此動作<strong>不可</strong>還原。
|
||||||
settings.delete_notices_2=- 此操作將永久刪除 <strong>%s</strong> 儲存庫,包括程式碼、問題、留言、Wiki 資料和協作者設定。
|
settings.delete_notices_2=- 此操作將永久刪除 <strong>%s</strong> 儲存庫,包括程式碼、問題、留言、Wiki 資料和協作者設定。
|
||||||
settings.delete_notices_fork_1=- 在此儲存庫刪除後,它的 fork 將會變成獨立儲存庫。
|
settings.delete_notices_fork_1=- 在此儲存庫刪除後,它的 fork 將會變成獨立儲存庫。
|
||||||
settings.deletion_success=這個儲存庫已被刪除。
|
settings.deletion_success=這個儲存庫已被刪除。
|
||||||
@ -2257,6 +2261,9 @@ topic.done=完成
|
|||||||
topic.count_prompt=您最多能選擇 25 個主題
|
topic.count_prompt=您最多能選擇 25 個主題
|
||||||
topic.format_prompt=主題必須以字母或數字為開頭,可包含連接號「-」且最長為 35 個字元。
|
topic.format_prompt=主題必須以字母或數字為開頭,可包含連接號「-」且最長為 35 個字元。
|
||||||
|
|
||||||
|
find_file.go_to_file=移至檔案
|
||||||
|
find_file.no_matching=找不到符合的檔案
|
||||||
|
|
||||||
error.csv.too_large=無法渲染此檔案,因為它太大了。
|
error.csv.too_large=無法渲染此檔案,因為它太大了。
|
||||||
error.csv.unexpected=無法渲染此檔案,因為它包含了未預期的字元,於第 %d 行第 %d 列。
|
error.csv.unexpected=無法渲染此檔案,因為它包含了未預期的字元,於第 %d 行第 %d 列。
|
||||||
error.csv.invalid_field_count=無法渲染此檔案,因為它第 %d 行的欄位數量有誤。
|
error.csv.invalid_field_count=無法渲染此檔案,因為它第 %d 行的欄位數量有誤。
|
||||||
|
@ -102,7 +102,7 @@ func GlobalInitInstalled(ctx context.Context) {
|
|||||||
log.Fatal("Gitea is not installed")
|
log.Fatal("Gitea is not installed")
|
||||||
}
|
}
|
||||||
|
|
||||||
mustInitCtx(ctx, git.InitWithConfigSync)
|
mustInitCtx(ctx, git.InitOnceWithSync)
|
||||||
log.Info("Git Version: %s (home: %s)", git.VersionInfo(), git.HomeDir())
|
log.Info("Git Version: %s (home: %s)", git.VersionInfo(), git.HomeDir())
|
||||||
|
|
||||||
git.CheckLFSVersion()
|
git.CheckLFSVersion()
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/models/organization"
|
"code.gitea.io/gitea/models/organization"
|
||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
|
pull_model "code.gitea.io/gitea/models/pull"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
@ -36,6 +37,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/web/middleware"
|
"code.gitea.io/gitea/modules/web/middleware"
|
||||||
"code.gitea.io/gitea/routers/utils"
|
"code.gitea.io/gitea/routers/utils"
|
||||||
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
||||||
|
"code.gitea.io/gitea/services/automerge"
|
||||||
"code.gitea.io/gitea/services/forms"
|
"code.gitea.io/gitea/services/forms"
|
||||||
"code.gitea.io/gitea/services/gitdiff"
|
"code.gitea.io/gitea/services/gitdiff"
|
||||||
pull_service "code.gitea.io/gitea/services/pull"
|
pull_service "code.gitea.io/gitea/services/pull"
|
||||||
@ -966,6 +968,22 @@ func MergePullRequest(ctx *context.Context) {
|
|||||||
message += "\n\n" + form.MergeMessageField
|
message += "\n\n" + form.MergeMessageField
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if form.MergeWhenChecksSucceed {
|
||||||
|
// delete all scheduled auto merges
|
||||||
|
_ = pull_model.DeleteScheduledAutoMerge(ctx, pr.ID)
|
||||||
|
// schedule auto merge
|
||||||
|
scheduled, err := automerge.ScheduleAutoMerge(ctx, ctx.Doer, pr, repo_model.MergeStyle(form.Do), message)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("ScheduleAutoMerge", err)
|
||||||
|
return
|
||||||
|
} else if scheduled {
|
||||||
|
// nothing more to do ...
|
||||||
|
ctx.Flash.Success(ctx.Tr("repo.pulls.auto_merge_newly_scheduled"))
|
||||||
|
ctx.Redirect(fmt.Sprintf("%s/pulls/%d", ctx.Repo.RepoLink, pr.Index))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := pull_service.Merge(ctx, pr, ctx.Doer, ctx.Repo.GitRepo, repo_model.MergeStyle(form.Do), form.HeadCommitID, message); err != nil {
|
if err := pull_service.Merge(ctx, pr, ctx.Doer, ctx.Repo.GitRepo, repo_model.MergeStyle(form.Do), form.HeadCommitID, message); err != nil {
|
||||||
if models.IsErrInvalidMergeStyle(err) {
|
if models.IsErrInvalidMergeStyle(err) {
|
||||||
ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option"))
|
ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option"))
|
||||||
@ -1070,6 +1088,26 @@ func MergePullRequest(ctx *context.Context) {
|
|||||||
ctx.Redirect(issue.Link())
|
ctx.Redirect(issue.Link())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CancelAutoMergePullRequest cancels a scheduled pr
|
||||||
|
func CancelAutoMergePullRequest(ctx *context.Context) {
|
||||||
|
issue := checkPullInfo(ctx)
|
||||||
|
if ctx.Written() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := automerge.RemoveScheduledAutoMerge(ctx, ctx.Doer, issue.PullRequest); err != nil {
|
||||||
|
if db.IsErrNotExist(err) {
|
||||||
|
ctx.Flash.Error(ctx.Tr("repo.pulls.auto_merge_not_scheduled"))
|
||||||
|
ctx.Redirect(fmt.Sprintf("%s/pulls/%d", ctx.Repo.RepoLink, issue.Index))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.ServerError("RemoveScheduledAutoMerge", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Flash.Success(ctx.Tr("repo.pulls.auto_merge_canceled_schedule"))
|
||||||
|
ctx.Redirect(fmt.Sprintf("%s/pulls/%d", ctx.Repo.RepoLink, issue.Index))
|
||||||
|
}
|
||||||
|
|
||||||
func stopTimerIfAvailable(user *user_model.User, issue *models.Issue) error {
|
func stopTimerIfAvailable(user *user_model.User, issue *models.Issue) error {
|
||||||
if models.StopwatchExists(user.ID, issue.ID) {
|
if models.StopwatchExists(user.ID, issue.ID) {
|
||||||
if err := models.CreateOrStopIssueStopwatch(user, issue); err != nil {
|
if err := models.CreateOrStopIssueStopwatch(user, issue); err != nil {
|
||||||
|
@ -215,22 +215,24 @@ func SettingsPost(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
u, _ := git.GetRemoteAddress(ctx, ctx.Repo.Repository.RepoPath(), ctx.Repo.Mirror.GetRemoteName())
|
u, err := git.GetRemoteURL(ctx, ctx.Repo.Repository.RepoPath(), ctx.Repo.Mirror.GetRemoteName())
|
||||||
|
if err != nil {
|
||||||
|
ctx.Data["Err_MirrorAddress"] = true
|
||||||
|
handleSettingRemoteAddrError(ctx, err, form)
|
||||||
|
return
|
||||||
|
}
|
||||||
if u.User != nil && form.MirrorPassword == "" && form.MirrorUsername == u.User.Username() {
|
if u.User != nil && form.MirrorPassword == "" && form.MirrorUsername == u.User.Username() {
|
||||||
form.MirrorPassword, _ = u.User.Password()
|
form.MirrorPassword, _ = u.User.Password()
|
||||||
}
|
}
|
||||||
|
|
||||||
address, err := forms.ParseRemoteAddr(form.MirrorAddress, form.MirrorUsername, form.MirrorPassword)
|
err = migrations.IsMigrateURLAllowed(u.String(), ctx.Doer)
|
||||||
if err == nil {
|
|
||||||
err = migrations.IsMigrateURLAllowed(address, ctx.Doer)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Data["Err_MirrorAddress"] = true
|
ctx.Data["Err_MirrorAddress"] = true
|
||||||
handleSettingRemoteAddrError(ctx, err, form)
|
handleSettingRemoteAddrError(ctx, err, form)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := mirror_service.UpdateAddress(ctx, ctx.Repo.Mirror, address); err != nil {
|
if err := mirror_service.UpdateAddress(ctx, ctx.Repo.Mirror, u.String()); err != nil {
|
||||||
ctx.ServerError("UpdateAddress", err)
|
ctx.ServerError("UpdateAddress", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -437,6 +439,7 @@ func SettingsPost(ctx *context.Context) {
|
|||||||
ExternalTrackerURL: form.ExternalTrackerURL,
|
ExternalTrackerURL: form.ExternalTrackerURL,
|
||||||
ExternalTrackerFormat: form.TrackerURLFormat,
|
ExternalTrackerFormat: form.TrackerURLFormat,
|
||||||
ExternalTrackerStyle: form.TrackerIssueStyle,
|
ExternalTrackerStyle: form.TrackerIssueStyle,
|
||||||
|
ExternalTrackerRegexpPattern: form.ExternalTrackerRegexpPattern,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeIssues)
|
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeIssues)
|
||||||
|
@ -1127,6 +1127,7 @@ func RegisterRoutes(m *web.Route) {
|
|||||||
m.Get(".patch", repo.DownloadPullPatch)
|
m.Get(".patch", repo.DownloadPullPatch)
|
||||||
m.Get("/commits", context.RepoRef(), repo.ViewPullCommits)
|
m.Get("/commits", context.RepoRef(), repo.ViewPullCommits)
|
||||||
m.Post("/merge", context.RepoMustNotBeArchived(), bindIgnErr(forms.MergePullRequestForm{}), repo.MergePullRequest)
|
m.Post("/merge", context.RepoMustNotBeArchived(), bindIgnErr(forms.MergePullRequestForm{}), repo.MergePullRequest)
|
||||||
|
m.Post("/cancel_auto_merge", context.RepoMustNotBeArchived(), repo.CancelAutoMergePullRequest)
|
||||||
m.Post("/update", repo.UpdatePullRequest)
|
m.Post("/update", repo.UpdatePullRequest)
|
||||||
m.Post("/set_allow_maintainer_edit", bindIgnErr(forms.UpdateAllowEditsForm{}), repo.SetAllowEdits)
|
m.Post("/set_allow_maintainer_edit", bindIgnErr(forms.UpdateAllowEditsForm{}), repo.SetAllowEdits)
|
||||||
m.Post("/cleanup", context.RepoMustNotBeArchived(), context.RepoRef(), repo.CleanUpPullRequest)
|
m.Post("/cleanup", context.RepoMustNotBeArchived(), context.RepoRef(), repo.CleanUpPullRequest)
|
||||||
|
@ -141,6 +141,7 @@ type RepoSettingForm struct {
|
|||||||
ExternalTrackerURL string
|
ExternalTrackerURL string
|
||||||
TrackerURLFormat string
|
TrackerURLFormat string
|
||||||
TrackerIssueStyle string
|
TrackerIssueStyle string
|
||||||
|
ExternalTrackerRegexpPattern string
|
||||||
EnableCloseIssuesViaCommitInAnyBranch bool
|
EnableCloseIssuesViaCommitInAnyBranch bool
|
||||||
EnableProjects bool
|
EnableProjects bool
|
||||||
EnablePackages bool
|
EnablePackages bool
|
||||||
|
@ -210,9 +210,10 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo
|
|||||||
}
|
}
|
||||||
gitArgs = append(gitArgs, m.GetRemoteName())
|
gitArgs = append(gitArgs, m.GetRemoteName())
|
||||||
|
|
||||||
remoteAddr, remoteErr := git.GetRemoteAddress(ctx, repoPath, m.GetRemoteName())
|
remoteURL, remoteErr := git.GetRemoteURL(ctx, repoPath, m.GetRemoteName())
|
||||||
if remoteErr != nil {
|
if remoteErr != nil {
|
||||||
log.Error("SyncMirrors [repo: %-v]: GetRemoteAddress Error %v", m.Repo, remoteErr)
|
log.Error("SyncMirrors [repo: %-v]: GetRemoteAddress Error %v", m.Repo, remoteErr)
|
||||||
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
stdoutBuilder := strings.Builder{}
|
stdoutBuilder := strings.Builder{}
|
||||||
@ -291,7 +292,7 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo
|
|||||||
|
|
||||||
if m.LFS && setting.LFS.StartServer {
|
if m.LFS && setting.LFS.StartServer {
|
||||||
log.Trace("SyncMirrors [repo: %-v]: syncing LFS objects...", m.Repo)
|
log.Trace("SyncMirrors [repo: %-v]: syncing LFS objects...", m.Repo)
|
||||||
endpoint := lfs.DetermineEndpoint(remoteAddr.String(), m.LFSEndpoint)
|
endpoint := lfs.DetermineEndpoint(remoteURL.String(), m.LFSEndpoint)
|
||||||
lfsClient := lfs.NewClient(endpoint, nil)
|
lfsClient := lfs.NewClient(endpoint, nil)
|
||||||
if err = repo_module.StoreMissingLfsObjectsInRepository(ctx, m.Repo, gitRepo, lfsClient); err != nil {
|
if err = repo_module.StoreMissingLfsObjectsInRepository(ctx, m.Repo, gitRepo, lfsClient); err != nil {
|
||||||
log.Error("SyncMirrors [repo: %-v]: failed to synchronize LFS objects for repository: %v", m.Repo, err)
|
log.Error("SyncMirrors [repo: %-v]: failed to synchronize LFS objects for repository: %v", m.Repo, err)
|
||||||
|
@ -131,7 +131,7 @@ func runPushSync(ctx context.Context, m *repo_model.PushMirror) error {
|
|||||||
timeout := time.Duration(setting.Git.Timeout.Mirror) * time.Second
|
timeout := time.Duration(setting.Git.Timeout.Mirror) * time.Second
|
||||||
|
|
||||||
performPush := func(path string) error {
|
performPush := func(path string) error {
|
||||||
remoteAddr, err := git.GetRemoteAddress(ctx, path, m.RemoteName)
|
remoteURL, err := git.GetRemoteURL(ctx, path, m.RemoteName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetRemoteAddress(%s) Error %v", path, err)
|
log.Error("GetRemoteAddress(%s) Error %v", path, err)
|
||||||
return errors.New("Unexpected error")
|
return errors.New("Unexpected error")
|
||||||
@ -147,7 +147,7 @@ func runPushSync(ctx context.Context, m *repo_model.PushMirror) error {
|
|||||||
}
|
}
|
||||||
defer gitRepo.Close()
|
defer gitRepo.Close()
|
||||||
|
|
||||||
endpoint := lfs.DetermineEndpoint(remoteAddr.String(), "")
|
endpoint := lfs.DetermineEndpoint(remoteURL.String(), "")
|
||||||
lfsClient := lfs.NewClient(endpoint, nil)
|
lfsClient := lfs.NewClient(endpoint, nil)
|
||||||
if err := pushAllLFSObjects(ctx, gitRepo, lfsClient); err != nil {
|
if err := pushAllLFSObjects(ctx, gitRepo, lfsClient); err != nil {
|
||||||
return util.SanitizeErrorCredentialURLs(err)
|
return util.SanitizeErrorCredentialURLs(err)
|
||||||
|
@ -37,7 +37,9 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{if .IsMirror}}<div class="fork-flag">{{$.i18n.Tr "repo.mirror_from"}} <a target="_blank" rel="noopener noreferrer" href="{{if .SanitizedOriginalURL}}{{.SanitizedOriginalURL}}{{else}}{{(MirrorRemoteAddress $.Context $.Mirror).Address}}{{end}}">{{if .SanitizedOriginalURL}}{{.SanitizedOriginalURL}}{{else}}{{(MirrorRemoteAddress $.Context $.Mirror).Address}}{{end}}</a></div>{{end}}
|
{{if .IsMirror}}
|
||||||
|
{{$address := MirrorRemoteAddress $.Context . $.Mirror.GetRemoteName}}
|
||||||
|
<div class="fork-flag">{{$.i18n.Tr "repo.mirror_from"}} <a target="_blank" rel="noopener noreferrer" href="{{$address.Address}}">{{$address.Address}}</a></div>{{end}}
|
||||||
{{if .IsFork}}<div class="fork-flag">{{$.i18n.Tr "repo.forked_from"}} <a href="{{.BaseRepo.Link}}">{{.BaseRepo.FullName}}</a></div>{{end}}
|
{{if .IsFork}}<div class="fork-flag">{{$.i18n.Tr "repo.forked_from"}} <a href="{{.BaseRepo.Link}}">{{.BaseRepo.FullName}}</a></div>{{end}}
|
||||||
{{if .IsGenerated}}<div class="fork-flag">{{$.i18n.Tr "repo.generated_from"}} <a href="{{.TemplateRepo.Link}}">{{.TemplateRepo.FullName}}</a></div>{{end}}
|
{{if .IsGenerated}}<div class="fork-flag">{{$.i18n.Tr "repo.generated_from"}} <a href="{{.TemplateRepo.Link}}">{{.TemplateRepo.FullName}}</a></div>{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
@ -843,8 +843,8 @@
|
|||||||
<span class="badge">{{svg "octicon-git-merge" 16}}</span>
|
<span class="badge">{{svg "octicon-git-merge" 16}}</span>
|
||||||
<span class="text grey">
|
<span class="text grey">
|
||||||
<a class="author" href="{{.Poster.HomeLink}}">{{.Poster.GetDisplayName}}</a>
|
<a class="author" href="{{.Poster.HomeLink}}">{{.Poster.GetDisplayName}}</a>
|
||||||
{{if eq .Type 34}}{{$.i18n.Tr "repo.pulls.pull_request_scheduled_auto_merge" $createdStr | Safe}}
|
{{if eq .Type 34}}{{$.i18n.Tr "repo.pulls.auto_merge_newly_scheduled_comment" $createdStr | Safe}}
|
||||||
{{else}}{{$.i18n.Tr "repo.pulls.pull_request_canceled_scheduled_auto_merge" $createdStr | Safe}}{{end}}
|
{{else}}{{$.i18n.Tr "repo.pulls.auto_merge_canceled_schedule_comment" $createdStr | Safe}}{{end}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -251,8 +251,14 @@
|
|||||||
{{$.i18n.Tr (printf "repo.signing.wont_sign.%s" .WontSignReason) }}
|
{{$.i18n.Tr (printf "repo.signing.wont_sign.%s" .WontSignReason) }}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{$notAllOverridableChecksOk := or .IsBlockedByApprovals .IsBlockedByRejection .IsBlockedByOfficialReviewRequests .IsBlockedByOutdatedBranch .IsBlockedByChangedProtectedFiles (and .EnableStatusCheck (not .RequiredStatusCheckState.IsSuccess))}}
|
{{$notAllOverridableChecksOk := or .IsBlockedByApprovals .IsBlockedByRejection .IsBlockedByOfficialReviewRequests .IsBlockedByOutdatedBranch .IsBlockedByChangedProtectedFiles (and .EnableStatusCheck (not .RequiredStatusCheckState.IsSuccess))}}
|
||||||
{{if and (or $.IsRepoAdmin (not $notAllOverridableChecksOk)) (or (not .AllowMerge) (not .RequireSigned) .WillSign)}}
|
|
||||||
|
{{/* admin can merge without checks, writer can merge when checkes succeed */}}
|
||||||
|
{{$canMergeNow := and (or $.IsRepoAdmin (not $notAllOverridableChecksOk)) (or (not .AllowMerge) (not .RequireSigned) .WillSign)}}
|
||||||
|
{{/* admin and writer both can make an auto merge schedule */}}
|
||||||
|
|
||||||
|
{{if $canMergeNow}}
|
||||||
{{if $notAllOverridableChecksOk}}
|
{{if $notAllOverridableChecksOk}}
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<i class="icon icon-octicon">{{svg "octicon-dot-fill"}}</i>
|
<i class="icon icon-octicon">{{svg "octicon-dot-fill"}}</i>
|
||||||
@ -277,7 +283,6 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{$canAutoMerge = true}}
|
|
||||||
{{if (gt .Issue.PullRequest.CommitsBehind 0)}}
|
{{if (gt .Issue.PullRequest.CommitsBehind 0)}}
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
<div class="item item-section">
|
<div class="item item-section">
|
||||||
@ -317,14 +322,16 @@
|
|||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{if and (or $.IsRepoAdmin (not $notAllOverridableChecksOk)) (or (not .AllowMerge) (not .RequireSigned) .WillSign)}}
|
{{if .AllowMerge}} {{/* user is allowed to merge */}}
|
||||||
{{if .AllowMerge}}
|
|
||||||
{{$prUnit := .Repository.MustGetUnit $.UnitTypePullRequests}}
|
{{$prUnit := .Repository.MustGetUnit $.UnitTypePullRequests}}
|
||||||
{{$approvers := .Issue.PullRequest.GetApprovers}}
|
{{$approvers := .Issue.PullRequest.GetApprovers}}
|
||||||
{{if or $prUnit.PullRequestsConfig.AllowMerge $prUnit.PullRequestsConfig.AllowRebase $prUnit.PullRequestsConfig.AllowRebaseMerge $prUnit.PullRequestsConfig.AllowSquash}}
|
{{if or $prUnit.PullRequestsConfig.AllowMerge $prUnit.PullRequestsConfig.AllowRebase $prUnit.PullRequestsConfig.AllowRebaseMerge $prUnit.PullRequestsConfig.AllowSquash}}
|
||||||
|
{{$hasPendingPullRequestMergeTip := ""}}
|
||||||
|
{{if .HasPendingPullRequestMerge}}
|
||||||
|
{{$createdPRMergeStr := TimeSinceUnix .PendingPullRequestMerge.CreatedUnix $.i18n.Lang}}
|
||||||
|
{{$hasPendingPullRequestMergeTip = $.i18n.Tr "repo.pulls.auto_merge_has_pending_schedule" .PendingPullRequestMerge.Doer.Name $createdPRMergeStr}}
|
||||||
|
{{end}}
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
<!-- /* eslint-disable */ -->
|
<!-- /* eslint-disable */ -->
|
||||||
(() => {
|
(() => {
|
||||||
@ -335,13 +342,22 @@
|
|||||||
'baseLink': {{.Link}},
|
'baseLink': {{.Link}},
|
||||||
'textCancel': {{$.i18n.Tr "cancel"}},
|
'textCancel': {{$.i18n.Tr "cancel"}},
|
||||||
'textDeleteBranch': {{$.i18n.Tr "repo.branch.delete" .HeadTarget}},
|
'textDeleteBranch': {{$.i18n.Tr "repo.branch.delete" .HeadTarget}},
|
||||||
|
'textAutoMergeButtonWhenSucceed': {{$.i18n.Tr "repo.pulls.auto_merge_button_when_succeed"}},
|
||||||
|
'textAutoMergeWhenSucceed': {{$.i18n.Tr "repo.pulls.auto_merge_when_succeed"}},
|
||||||
|
'textAutoMergeCancelSchedule': {{$.i18n.Tr "repo.pulls.auto_merge_cancel_schedule"}},
|
||||||
|
|
||||||
|
'canMergeNow': {{$canMergeNow}},
|
||||||
'allOverridableChecksOk': {{not $notAllOverridableChecksOk}},
|
'allOverridableChecksOk': {{not $notAllOverridableChecksOk}},
|
||||||
'pullHeadCommitID': {{.PullHeadCommitID}},
|
'pullHeadCommitID': {{.PullHeadCommitID}},
|
||||||
'isPullBranchDeletable': {{.IsPullBranchDeletable}},
|
'isPullBranchDeletable': {{.IsPullBranchDeletable}},
|
||||||
'defaultDeleteBranchAfterMerge': {{$prUnit.PullRequestsConfig.DefaultDeleteBranchAfterMerge}},
|
'defaultDeleteBranchAfterMerge': {{$prUnit.PullRequestsConfig.DefaultDeleteBranchAfterMerge}},
|
||||||
'mergeMessageFieldPlaceHolder': {{$.i18n.Tr "repo.editor.commit_message_desc"}},
|
'mergeMessageFieldPlaceHolder': {{$.i18n.Tr "repo.editor.commit_message_desc"}},
|
||||||
|
|
||||||
|
'hasPendingPullRequestMerge': {{.HasPendingPullRequestMerge}},
|
||||||
|
'hasPendingPullRequestMergeTip': {{$hasPendingPullRequestMergeTip}},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const generalHideAutoMerge = mergeForm.canMergeNow && mergeForm.allOverridableChecksOk; // if this PR can be merged now, then hide the auto merge
|
||||||
mergeForm['mergeStyles'] = [
|
mergeForm['mergeStyles'] = [
|
||||||
{
|
{
|
||||||
'name': 'merge',
|
'name': 'merge',
|
||||||
@ -349,12 +365,14 @@
|
|||||||
'textDoMerge': {{$.i18n.Tr "repo.pulls.merge_pull_request"}},
|
'textDoMerge': {{$.i18n.Tr "repo.pulls.merge_pull_request"}},
|
||||||
'mergeTitleFieldText': defaultMergeTitle,
|
'mergeTitleFieldText': defaultMergeTitle,
|
||||||
'mergeMessageFieldText': defaultMergeMessage,
|
'mergeMessageFieldText': defaultMergeMessage,
|
||||||
|
'hideAutoMerge': generalHideAutoMerge,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'rebase',
|
'name': 'rebase',
|
||||||
'allowed': {{$prUnit.PullRequestsConfig.AllowRebase}},
|
'allowed': {{$prUnit.PullRequestsConfig.AllowRebase}},
|
||||||
'textDoMerge': {{$.i18n.Tr "repo.pulls.rebase_merge_pull_request"}},
|
'textDoMerge': {{$.i18n.Tr "repo.pulls.rebase_merge_pull_request"}},
|
||||||
'hideMergeMessageTexts': true,
|
'hideMergeMessageTexts': true,
|
||||||
|
'hideAutoMerge': generalHideAutoMerge,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'rebase-merge',
|
'name': 'rebase-merge',
|
||||||
@ -362,6 +380,7 @@
|
|||||||
'textDoMerge': {{$.i18n.Tr "repo.pulls.rebase_merge_commit_pull_request"}},
|
'textDoMerge': {{$.i18n.Tr "repo.pulls.rebase_merge_commit_pull_request"}},
|
||||||
'mergeTitleFieldText': defaultMergeTitle,
|
'mergeTitleFieldText': defaultMergeTitle,
|
||||||
'mergeMessageFieldText': defaultMergeMessage,
|
'mergeMessageFieldText': defaultMergeMessage,
|
||||||
|
'hideAutoMerge': generalHideAutoMerge,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'squash',
|
'name': 'squash',
|
||||||
@ -369,12 +388,14 @@
|
|||||||
'textDoMerge': {{$.i18n.Tr "repo.pulls.squash_merge_pull_request"}},
|
'textDoMerge': {{$.i18n.Tr "repo.pulls.squash_merge_pull_request"}},
|
||||||
'mergeTitleFieldText': defaultSquashMergeTitle,
|
'mergeTitleFieldText': defaultSquashMergeTitle,
|
||||||
'mergeMessageFieldText': defaultMergeMessage,
|
'mergeMessageFieldText': defaultMergeMessage,
|
||||||
|
'hideAutoMerge': generalHideAutoMerge,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'manually-merged',
|
'name': 'manually-merged',
|
||||||
'allowed': {{and $prUnit.PullRequestsConfig.AllowManualMerge $.IsRepoAdmin}},
|
'allowed': {{and $prUnit.PullRequestsConfig.AllowManualMerge $.IsRepoAdmin}},
|
||||||
'textDoMerge': {{$.i18n.Tr "repo.pulls.merge_manually"}},
|
'textDoMerge': {{$.i18n.Tr "repo.pulls.merge_manually"}},
|
||||||
'hideMergeMessageTexts': true,
|
'hideMergeMessageTexts': true,
|
||||||
|
'hideAutoMerge': true,
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
window.config.pageData.pullRequestMergeForm = mergeForm;
|
window.config.pageData.pullRequestMergeForm = mergeForm;
|
||||||
@ -384,27 +405,10 @@
|
|||||||
<div id="pull-request-merge-form"></div>
|
<div id="pull-request-merge-form"></div>
|
||||||
|
|
||||||
{{if .ShowMergeInstructions}}
|
{{if .ShowMergeInstructions}}
|
||||||
<div class="instruct-toggle mt-3"> {{$.i18n.Tr "repo.pulls.merge_instruction_hint" | Safe}} </div>
|
{{template "repo/issue/view_content/pull_merge_instruction" (dict "i18n" .i18n "Issue" .Issue)}}
|
||||||
<div class="instruct-content" style="display:none">
|
|
||||||
<div class="ui divider"></div>
|
|
||||||
<div><h3 class="di">{{$.i18n.Tr "step1"}} </h3>{{$.i18n.Tr "repo.pulls.merge_instruction_step1_desc"}}</div>
|
|
||||||
<div class="ui secondary segment">
|
|
||||||
{{if eq .Issue.PullRequest.Flow 0}}
|
|
||||||
<div>git checkout -b {{if ne .Issue.PullRequest.HeadRepo.ID .Issue.PullRequest.BaseRepo.ID}}{{.Issue.PullRequest.HeadRepo.OwnerName}}-{{end}}{{.Issue.PullRequest.HeadBranch}} {{.Issue.PullRequest.BaseBranch}}</div>
|
|
||||||
<div>git pull {{if ne .Issue.PullRequest.HeadRepo.ID .Issue.PullRequest.BaseRepo.ID}}{{.Issue.PullRequest.HeadRepo.HTMLURL}}{{else}}origin{{end}} {{.Issue.PullRequest.HeadBranch}}</div>
|
|
||||||
{{else}}
|
|
||||||
<div>git fetch origin {{.Issue.PullRequest.GetGitRefName}}:{{.Issue.PullRequest.HeadBranch}}</div>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
<div><h3 class="di">{{$.i18n.Tr "step2"}} </h3>{{$.i18n.Tr "repo.pulls.merge_instruction_step2_desc"}}</div>
|
|
||||||
<div class="ui secondary segment">
|
|
||||||
<div>git checkout {{.Issue.PullRequest.BaseBranch}}</div>
|
|
||||||
<div>git merge --no-ff {{if ne .Issue.PullRequest.HeadRepo.ID .Issue.PullRequest.BaseRepo.ID}}{{.Issue.PullRequest.HeadRepo.OwnerName}}-{{end}}{{.Issue.PullRequest.HeadBranch}}</div>
|
|
||||||
<div>git push origin {{.Issue.PullRequest.BaseBranch}}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
{{end}}
|
||||||
{{else}}
|
{{else}}
|
||||||
|
{{/* no merge style was set in repo setting: not or ($prUnit.PullRequestsConfig.AllowMerge ...) */}}
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
<div class="item text red">
|
<div class="item text red">
|
||||||
{{svg "octicon-x"}}
|
{{svg "octicon-x"}}
|
||||||
@ -414,15 +418,15 @@
|
|||||||
{{svg "octicon-info"}}
|
{{svg "octicon-info"}}
|
||||||
{{$.i18n.Tr "repo.pulls.no_merge_helper"}}
|
{{$.i18n.Tr "repo.pulls.no_merge_helper"}}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}} {{/* end if the repo was set to use any merge style */}}
|
||||||
{{else}}
|
{{else}}
|
||||||
|
{{/* user is not allowed to merge */}}
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
<div class="item">
|
<div class="item">
|
||||||
{{svg "octicon-info"}}
|
{{svg "octicon-info"}}
|
||||||
{{$.i18n.Tr "repo.pulls.no_merge_access"}}
|
{{$.i18n.Tr "repo.pulls.no_merge_access"}}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}} {{/* end if user is allowed to merge or not */}}
|
||||||
{{end}}
|
|
||||||
{{else}}
|
{{else}}
|
||||||
{{/* Merge conflict without specific file. Suggest manual merge, only if all reviews and status checks OK. */}}
|
{{/* Merge conflict without specific file. Suggest manual merge, only if all reviews and status checks OK. */}}
|
||||||
{{if .IsBlockedByApprovals}}
|
{{if .IsBlockedByApprovals}}
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
<div class="instruct-toggle mt-3"> {{$.i18n.Tr "repo.pulls.merge_instruction_hint" | Safe}} </div>
|
||||||
|
<div class="instruct-content" style="display:none">
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
<div><h3 class="di">{{$.i18n.Tr "step1"}} </h3>{{$.i18n.Tr "repo.pulls.merge_instruction_step1_desc"}}</div>
|
||||||
|
<div class="ui secondary segment">
|
||||||
|
{{if eq $.Issue.PullRequest.Flow 0}}
|
||||||
|
<div>git checkout -b {{if ne $.Issue.PullRequest.HeadRepo.ID $.Issue.PullRequest.BaseRepo.ID}}{{$.Issue.PullRequest.HeadRepo.OwnerName}}-{{end}}{{$.Issue.PullRequest.HeadBranch}} {{$.Issue.PullRequest.BaseBranch}}</div>
|
||||||
|
<div>git pull {{if ne $.Issue.PullRequest.HeadRepo.ID $.Issue.PullRequest.BaseRepo.ID}}{{$.Issue.PullRequest.HeadRepo.HTMLURL}}{{else}}origin{{end}} {{$.Issue.PullRequest.HeadBranch}}</div>
|
||||||
|
{{else}}
|
||||||
|
<div>git fetch origin {{$.Issue.PullRequest.GetGitRefName}}:{{$.Issue.PullRequest.HeadBranch}}</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
<div><h3 class="di">{{$.i18n.Tr "step2"}} </h3>{{$.i18n.Tr "repo.pulls.merge_instruction_step2_desc"}}</div>
|
||||||
|
<div class="ui secondary segment">
|
||||||
|
<div>git checkout {{$.Issue.PullRequest.BaseBranch}}</div>
|
||||||
|
<div>git merge --no-ff {{if ne $.Issue.PullRequest.HeadRepo.ID $.Issue.PullRequest.BaseRepo.ID}}{{$.Issue.PullRequest.HeadRepo.OwnerName}}-{{end}}{{$.Issue.PullRequest.HeadBranch}}</div>
|
||||||
|
<div>git push origin {{$.Issue.PullRequest.BaseBranch}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -91,7 +91,7 @@
|
|||||||
{{if .Repository.IsMirror}}
|
{{if .Repository.IsMirror}}
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{(MirrorRemoteAddress $.Context .Mirror).Address}}</td>
|
<td>{{(MirrorRemoteAddress $.Context .Repository .Mirror.GetRemoteName).Address}}</td>
|
||||||
<td>{{$.i18n.Tr "repo.settings.mirror_settings.direction.pull"}}</td>
|
<td>{{$.i18n.Tr "repo.settings.mirror_settings.direction.pull"}}</td>
|
||||||
<td>{{.Mirror.UpdatedUnix.AsTime}}</td>
|
<td>{{.Mirror.UpdatedUnix.AsTime}}</td>
|
||||||
<td class="right aligned">
|
<td class="right aligned">
|
||||||
@ -119,7 +119,7 @@
|
|||||||
<label for="interval">{{.i18n.Tr "repo.mirror_interval" .MinimumMirrorInterval}}</label>
|
<label for="interval">{{.i18n.Tr "repo.mirror_interval" .MinimumMirrorInterval}}</label>
|
||||||
<input id="interval" name="interval" value="{{.MirrorInterval}}">
|
<input id="interval" name="interval" value="{{.MirrorInterval}}">
|
||||||
</div>
|
</div>
|
||||||
{{$address := MirrorRemoteAddress $.Context .Mirror}}
|
{{$address := MirrorRemoteAddress $.Context .Repository .Mirror.GetRemoteName}}
|
||||||
<div class="field {{if .Err_MirrorAddress}}error{{end}}">
|
<div class="field {{if .Err_MirrorAddress}}error{{end}}">
|
||||||
<label for="mirror_address">{{.i18n.Tr "repo.mirror_address"}}</label>
|
<label for="mirror_address">{{.i18n.Tr "repo.mirror_address"}}</label>
|
||||||
<input id="mirror_address" name="mirror_address" value="{{$address.Address}}" required>
|
<input id="mirror_address" name="mirror_address" value="{{$address.Address}}" required>
|
||||||
@ -168,7 +168,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{{range .PushMirrors}}
|
{{range .PushMirrors}}
|
||||||
<tr>
|
<tr>
|
||||||
{{$address := MirrorRemoteAddress $.Context .}}
|
{{$address := MirrorRemoteAddress $.Context $.Repository .GetRemoteName}}
|
||||||
<td>{{$address.Address}}</td>
|
<td>{{$address.Address}}</td>
|
||||||
<td>{{$.i18n.Tr "repo.settings.mirror_settings.direction.push"}}</td>
|
<td>{{$.i18n.Tr "repo.settings.mirror_settings.direction.push"}}</td>
|
||||||
<td>{{if .LastUpdateUnix}}{{.LastUpdateUnix.AsTime}}{{else}}{{$.i18n.Tr "never"}}{{end}} {{if .LastError}}<div class="ui red label tooltip" data-content="{{.LastError}}">{{$.i18n.Tr "error"}}</div>{{end}}</td>
|
<td>{{if .LastUpdateUnix}}{{.LastUpdateUnix.AsTime}}{{else}}{{$.i18n.Tr "never"}}{{end}} {{if .LastError}}<div class="ui red label tooltip" data-content="{{.LastError}}">{{$.i18n.Tr "error"}}</div>{{end}}</td>
|
||||||
@ -361,16 +361,27 @@
|
|||||||
<div class="ui radio checkbox">
|
<div class="ui radio checkbox">
|
||||||
{{$externalTracker := (.Repository.MustGetUnit $.UnitTypeExternalTracker)}}
|
{{$externalTracker := (.Repository.MustGetUnit $.UnitTypeExternalTracker)}}
|
||||||
{{$externalTrackerStyle := $externalTracker.ExternalTrackerConfig.ExternalTrackerStyle}}
|
{{$externalTrackerStyle := $externalTracker.ExternalTrackerConfig.ExternalTrackerStyle}}
|
||||||
<input class="hidden" tabindex="0" name="tracker_issue_style" type="radio" value="numeric" {{if $externalTrackerStyle}}{{if eq $externalTrackerStyle "numeric"}}checked=""{{end}}{{end}}/>
|
<input class="js-tracker-issue-style" name="tracker_issue_style" type="radio" value="numeric" {{if eq $externalTrackerStyle "numeric"}}checked{{end}}>
|
||||||
<label>{{.i18n.Tr "repo.settings.tracker_issue_style.numeric"}} <span class="ui light grey text">(#1234)</span></label>
|
<label>{{.i18n.Tr "repo.settings.tracker_issue_style.numeric"}} <span class="ui light grey text">#1234</span></label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui radio checkbox">
|
<div class="ui radio checkbox">
|
||||||
<input class="hidden" tabindex="0" name="tracker_issue_style" type="radio" value="alphanumeric" {{if $externalTrackerStyle}}{{if eq $externalTracker.ExternalTrackerConfig.ExternalTrackerStyle "alphanumeric"}}checked=""{{end}}{{end}} />
|
<input class="js-tracker-issue-style" name="tracker_issue_style" type="radio" value="alphanumeric" {{if eq $externalTrackerStyle "alphanumeric"}}checked{{end}}>
|
||||||
<label>{{.i18n.Tr "repo.settings.tracker_issue_style.alphanumeric"}} <span class="ui light grey text">(ABC-123, DEFG-234)</span></label>
|
<label>{{.i18n.Tr "repo.settings.tracker_issue_style.alphanumeric"}} <span class="ui light grey text">ABC-123 , DEFG-234</span></label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<div class="ui radio checkbox">
|
||||||
|
<input class="js-tracker-issue-style" name="tracker_issue_style" type="radio" value="regexp" {{if eq $externalTrackerStyle "regexp"}}checked{{end}}>
|
||||||
|
<label>{{.i18n.Tr "repo.settings.tracker_issue_style.regexp"}} <span class="ui light grey text">(ISSUE-\d+) , ISSUE-(\d+)</span></label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field {{if ne $externalTrackerStyle "regexp"}}disabled{{end}}" id="tracker-issue-style-regex-box">
|
||||||
|
<label for="external_tracker_regexp_pattern">{{.i18n.Tr "repo.settings.tracker_issue_style.regexp_pattern"}}</label>
|
||||||
|
<input id="external_tracker_regexp_pattern" name="external_tracker_regexp_pattern" value="{{(.Repository.MustGetUnit $.UnitTypeExternalTracker).ExternalTrackerConfig.ExternalTrackerRegexpPattern}}">
|
||||||
|
<p class="help">{{.i18n.Tr "repo.settings.tracker_issue_style.regexp_pattern_desc" | Str2html}}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,15 +4,17 @@
|
|||||||
<div class="ui stackable grid">
|
<div class="ui stackable grid">
|
||||||
<div class="ui five wide column">
|
<div class="ui five wide column">
|
||||||
<div class="ui card">
|
<div class="ui card">
|
||||||
|
<div id="profile-avatar" class="content df"/>
|
||||||
{{if eq .SignedUserName .Owner.Name}}
|
{{if eq .SignedUserName .Owner.Name}}
|
||||||
<a class="image tooltip" href="{{AppSubUrl}}/user/settings" id="profile-avatar" data-content="{{.i18n.Tr "user.change_avatar"}}" data-position="bottom center">
|
<a class="image tooltip" href="{{AppSubUrl}}/user/settings" data-content="{{.i18n.Tr "user.change_avatar"}}" data-position="bottom center">
|
||||||
{{avatar .Owner 290}}
|
{{avatar .Owner 290}}
|
||||||
</a>
|
</a>
|
||||||
{{else}}
|
{{else}}
|
||||||
<span class="image" id="profile-avatar">
|
<span class="image">
|
||||||
{{avatar .Owner 290}}
|
{{avatar .Owner 290}}
|
||||||
</span>
|
</span>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
</div>
|
||||||
<div class="content word-break profile-avatar-name">
|
<div class="content word-break profile-avatar-name">
|
||||||
{{if .Owner.FullName}}<span class="header text center">{{.Owner.FullName}}</span>{{end}}
|
{{if .Owner.FullName}}<span class="header text center">{{.Owner.FullName}}</span>{{end}}
|
||||||
<span class="username text center">{{.Owner.Name}}</span>
|
<span class="username text center">{{.Owner.Name}}</span>
|
||||||
|
@ -1,9 +1,23 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<!--
|
||||||
|
if this component is shown, either the user is admin (can do merge without checks), or they is a writer who has the permission to do merge
|
||||||
|
if the user is a writer and can't do merge now (canMergeNow==false), then only show the Auto Merge for them
|
||||||
|
How to test the UI manually:
|
||||||
|
* Method 1: manually set some variables in pull.tmpl, eg: {{$notAllOverridableChecksOk = true}} {{$canMergeNow = false}}
|
||||||
|
* Method 2: make a protected branch, then set state=pending/success :
|
||||||
|
curl -X POST ${root_url}/api/v1/repos/${owner}/${repo}/statuses/${sha} \
|
||||||
|
-H "accept: application/json" -H "authorization: Basic $base64_auth" -H "Content-Type: application/json" \
|
||||||
|
-d '{"context": "test/context", "description": "description", "state": "${state}", "target_url": "http://localhost"}'
|
||||||
|
-->
|
||||||
<div>
|
<div>
|
||||||
|
<!-- eslint-disable -->
|
||||||
|
<div v-if="mergeForm.hasPendingPullRequestMerge" v-html="mergeForm.hasPendingPullRequestMergeTip" class="ui info message"></div>
|
||||||
|
|
||||||
<div class="ui form" v-if="showActionForm">
|
<div class="ui form" v-if="showActionForm">
|
||||||
<form :action="mergeForm.baseLink+'/merge'" method="post">
|
<form :action="mergeForm.baseLink+'/merge'" method="post">
|
||||||
<input type="hidden" name="_csrf" :value="csrfToken">
|
<input type="hidden" name="_csrf" :value="csrfToken">
|
||||||
<input type="hidden" name="head_commit_id" v-model="mergeForm.pullHeadCommitID">
|
<input type="hidden" name="head_commit_id" v-model="mergeForm.pullHeadCommitID">
|
||||||
|
<input type="hidden" name="merge_when_checks_succeed" v-model="autoMergeWhenSucceed">
|
||||||
|
|
||||||
<template v-if="!mergeStyleDetail.hideMergeMessageTexts">
|
<template v-if="!mergeStyleDetail.hideMergeMessageTexts">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
@ -14,39 +28,72 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<button class="ui button" :class="[mergeForm.allOverridableChecksOk?'green':'red']" type="submit" name="do" :value="mergeStyle">
|
<button class="ui button" :class="mergeButtonStyleClass" type="submit" name="do" :value="mergeStyle">
|
||||||
{{ mergeStyleDetail.textDoMerge }}
|
{{ mergeStyleDetail.textDoMerge }}
|
||||||
|
<template v-if="autoMergeWhenSucceed">
|
||||||
|
{{ mergeForm.textAutoMergeButtonWhenSucceed }}
|
||||||
|
</template>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="ui button merge-cancel" @click="toggleActionForm(false)">
|
<button class="ui button merge-cancel" @click="toggleActionForm(false)">
|
||||||
{{ mergeForm.textCancel }}
|
{{ mergeForm.textCancel }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="ui checkbox ml-2" v-if="mergeForm.isPullBranchDeletable">
|
<div class="ui checkbox ml-2" v-if="mergeForm.isPullBranchDeletable && !autoMergeWhenSucceed">
|
||||||
<input name="delete_branch_after_merge" type="checkbox" v-model="deleteBranchAfterMerge" id="delete-branch-after-merge">
|
<input name="delete_branch_after_merge" type="checkbox" v-model="deleteBranchAfterMerge" id="delete-branch-after-merge">
|
||||||
<label for="delete-branch-after-merge">{{ mergeForm.textDeleteBranch }}</label>
|
<label for="delete-branch-after-merge">{{ mergeForm.textDeleteBranch }}</label>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-if="!showActionForm">
|
<div v-if="!showActionForm" class="df">
|
||||||
<div class="ui buttons merge-button" :class="[mergeForm.allOverridableChecksOk?'green':'red']" @click="toggleActionForm(true)">
|
<!-- the merge button -->
|
||||||
|
<div class="ui buttons merge-button" :class="mergeButtonStyleClass" @click="toggleActionForm(true)" >
|
||||||
<button class="ui button">
|
<button class="ui button">
|
||||||
<svg-icon name="octicon-git-merge"/>
|
<svg-icon name="octicon-git-merge"/>
|
||||||
<span class="button-text">{{ mergeStyleDetail.textDoMerge }}</span>
|
<span class="button-text">
|
||||||
|
{{ mergeStyleDetail.textDoMerge }}
|
||||||
|
<template v-if="autoMergeWhenSucceed">
|
||||||
|
{{ mergeForm.textAutoMergeButtonWhenSucceed }}
|
||||||
|
</template>
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<div class="ui dropdown icon button no-text" @click.stop="showMergeStyleMenu = !showMergeStyleMenu" v-if="mergeStyleAllowedCount>1">
|
<div class="ui dropdown icon button no-text" @click.stop="showMergeStyleMenu = !showMergeStyleMenu" v-if="mergeStyleAllowedCount>1">
|
||||||
<svg-icon name="octicon-triangle-down" :size="14"/>
|
<svg-icon name="octicon-triangle-down" :size="14"/>
|
||||||
<div class="menu" :class="{'show':showMergeStyleMenu}">
|
<div class="menu" :class="{'show':showMergeStyleMenu}">
|
||||||
<template v-for="msd in mergeForm.mergeStyles">
|
<template v-for="msd in mergeForm.mergeStyles">
|
||||||
<div class="item" v-if="msd.allowed" :key="msd.name" @click.stop="mergeStyle=msd.name">
|
<!-- if can merge now, show one action "merge now", and an action "auto merge when succeed" -->
|
||||||
|
<div class="item" v-if="msd.allowed && mergeForm.canMergeNow" :key="msd.name" @click.stop="switchMergeStyle(msd.name)">
|
||||||
|
<div class="action-text">
|
||||||
{{ msd.textDoMerge }}
|
{{ msd.textDoMerge }}
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="!msd.hideAutoMerge" class="auto-merge-small" @click.stop="switchMergeStyle(msd.name, true)">
|
||||||
|
<svg-icon name="octicon-clock" :size="14"/>
|
||||||
|
<div class="auto-merge-tip">
|
||||||
|
{{ mergeForm.textAutoMergeWhenSucceed }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- if can NOT merge now, only show one action "auto merge when succeed" -->
|
||||||
|
<div class="item" v-if="msd.allowed && !mergeForm.canMergeNow && !msd.hideAutoMerge" :key="msd.name" @click.stop="switchMergeStyle(msd.name, true)">
|
||||||
|
<div class="action-text">
|
||||||
|
{{ msd.textDoMerge }} {{ mergeForm.textAutoMergeButtonWhenSucceed }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
|
||||||
|
<!-- the cancel auto merge button -->
|
||||||
|
<form v-if="mergeForm.hasPendingPullRequestMerge" :action="mergeForm.baseLink+'/cancel_auto_merge'" method="post" class="ml-4">
|
||||||
|
<input type="hidden" name="_csrf" :value="csrfToken">
|
||||||
|
<button class="ui button">
|
||||||
|
{{ mergeForm.textAutoMergeCancelSchedule }}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -68,6 +115,7 @@ export default {
|
|||||||
mergeTitleFieldValue: '',
|
mergeTitleFieldValue: '',
|
||||||
mergeMessageFieldValue: '',
|
mergeMessageFieldValue: '',
|
||||||
deleteBranchAfterMerge: false,
|
deleteBranchAfterMerge: false,
|
||||||
|
autoMergeWhenSucceed: false,
|
||||||
|
|
||||||
mergeStyle: '',
|
mergeStyle: '',
|
||||||
mergeStyleDetail: { // dummy only, these values will come from one of the mergeForm.mergeStyles
|
mergeStyleDetail: { // dummy only, these values will come from one of the mergeForm.mergeStyles
|
||||||
@ -82,6 +130,13 @@ export default {
|
|||||||
showActionForm: false,
|
showActionForm: false,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
mergeButtonStyleClass() {
|
||||||
|
if (this.mergeForm.allOverridableChecksOk) return 'green';
|
||||||
|
return this.autoMergeWhenSucceed ? 'blue' : 'red';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
mergeStyle(val) {
|
mergeStyle(val) {
|
||||||
this.mergeStyleDetail = this.mergeForm.mergeStyles.find((e) => e.name === val);
|
this.mergeStyleDetail = this.mergeForm.mergeStyles.find((e) => e.name === val);
|
||||||
@ -90,7 +145,7 @@ export default {
|
|||||||
|
|
||||||
created() {
|
created() {
|
||||||
this.mergeStyleAllowedCount = this.mergeForm.mergeStyles.reduce((v, msd) => v + (msd.allowed ? 1 : 0), 0);
|
this.mergeStyleAllowedCount = this.mergeForm.mergeStyles.reduce((v, msd) => v + (msd.allowed ? 1 : 0), 0);
|
||||||
this.mergeStyle = this.mergeForm.mergeStyles.find((e) => e.allowed)?.name;
|
this.switchMergeStyle(this.mergeForm.mergeStyles.find((e) => e.allowed)?.name, !this.mergeForm.canMergeNow);
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
@ -111,7 +166,11 @@ export default {
|
|||||||
this.deleteBranchAfterMerge = this.mergeForm.defaultDeleteBranchAfterMerge;
|
this.deleteBranchAfterMerge = this.mergeForm.defaultDeleteBranchAfterMerge;
|
||||||
this.mergeTitleFieldValue = this.mergeStyleDetail.mergeTitleFieldText;
|
this.mergeTitleFieldValue = this.mergeStyleDetail.mergeTitleFieldText;
|
||||||
this.mergeMessageFieldValue = this.mergeStyleDetail.mergeMessageFieldText;
|
this.mergeMessageFieldValue = this.mergeStyleDetail.mergeMessageFieldText;
|
||||||
}
|
},
|
||||||
|
switchMergeStyle(name, autoMerge = false) {
|
||||||
|
this.mergeStyle = name;
|
||||||
|
this.autoMergeWhenSucceed = autoMerge;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@ -124,4 +183,59 @@ export default {
|
|||||||
.ui.checkbox label {
|
.ui.checkbox label {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* make the dropdown list left-aligned */
|
||||||
|
.ui.merge-button {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.ui.merge-button .ui.dropdown {
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
.ui.merge-button > .ui.dropdown:last-child > .menu:not(.left) {
|
||||||
|
left: 0;
|
||||||
|
right: auto;
|
||||||
|
}
|
||||||
|
.ui.merge-button .ui.dropdown .menu > .item {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
padding: 0 !important; /* polluted by semantic.css: .ui.dropdown .menu > .item { !important } */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* merge style list item */
|
||||||
|
.action-text {
|
||||||
|
padding: 0.8rem;
|
||||||
|
flex: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
.auto-merge-small {
|
||||||
|
width: 40px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.auto-merge-small .auto-merge-tip {
|
||||||
|
display: none;
|
||||||
|
left: 38px;
|
||||||
|
top: -1px;
|
||||||
|
bottom: -1px;
|
||||||
|
position: absolute;
|
||||||
|
align-items: center;
|
||||||
|
color: var(--color-info-text);
|
||||||
|
background-color: var(--color-info-bg);
|
||||||
|
border: 1px solid var(--color-info-border);
|
||||||
|
border-left: none;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auto-merge-small:hover {
|
||||||
|
color: var(--color-info-text);
|
||||||
|
background-color: var(--color-info-bg);
|
||||||
|
border: 1px solid var(--color-info-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auto-merge-small:hover .auto-merge-tip {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -462,6 +462,11 @@ export function initRepository() {
|
|||||||
if (typeof $(this).data('context') !== 'undefined') $($(this).data('context')).addClass('disabled');
|
if (typeof $(this).data('context') !== 'undefined') $($(this).data('context')).addClass('disabled');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const $trackerIssueStyleRadios = $('.js-tracker-issue-style');
|
||||||
|
$trackerIssueStyleRadios.on('change input', () => {
|
||||||
|
const checkedVal = $trackerIssueStyleRadios.filter(':checked').val();
|
||||||
|
$('#tracker-issue-style-regex-box').toggleClass('disabled', checkedVal !== 'regexp');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Labels
|
// Labels
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import octiconChevronDown from '../../public/img/svg/octicon-chevron-down.svg';
|
import octiconChevronDown from '../../public/img/svg/octicon-chevron-down.svg';
|
||||||
import octiconChevronRight from '../../public/img/svg/octicon-chevron-right.svg';
|
import octiconChevronRight from '../../public/img/svg/octicon-chevron-right.svg';
|
||||||
import octiconCopy from '../../public/img/svg/octicon-copy.svg';
|
import octiconCopy from '../../public/img/svg/octicon-copy.svg';
|
||||||
|
import octiconClock from '../../public/img/svg/octicon-clock.svg';
|
||||||
import octiconGitMerge from '../../public/img/svg/octicon-git-merge.svg';
|
import octiconGitMerge from '../../public/img/svg/octicon-git-merge.svg';
|
||||||
import octiconGitPullRequest from '../../public/img/svg/octicon-git-pull-request.svg';
|
import octiconGitPullRequest from '../../public/img/svg/octicon-git-pull-request.svg';
|
||||||
import octiconIssueClosed from '../../public/img/svg/octicon-issue-closed.svg';
|
import octiconIssueClosed from '../../public/img/svg/octicon-issue-closed.svg';
|
||||||
@ -23,6 +24,7 @@ export const svgs = {
|
|||||||
'octicon-chevron-down': octiconChevronDown,
|
'octicon-chevron-down': octiconChevronDown,
|
||||||
'octicon-chevron-right': octiconChevronRight,
|
'octicon-chevron-right': octiconChevronRight,
|
||||||
'octicon-copy': octiconCopy,
|
'octicon-copy': octiconCopy,
|
||||||
|
'octicon-clock': octiconClock,
|
||||||
'octicon-git-merge': octiconGitMerge,
|
'octicon-git-merge': octiconGitMerge,
|
||||||
'octicon-git-pull-request': octiconGitPullRequest,
|
'octicon-git-pull-request': octiconGitPullRequest,
|
||||||
'octicon-issue-closed': octiconIssueClosed,
|
'octicon-issue-closed': octiconIssueClosed,
|
||||||
|
@ -2003,14 +2003,6 @@ table th[data-sortt-desc] {
|
|||||||
margin-right: 0 !important;
|
margin-right: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* limit width of all direct dropdown menu children */
|
|
||||||
/* https://github.com/go-gitea/gitea/pull/10835 */
|
|
||||||
.dropdown:not(.selection) > .menu:not(.review-box) > *:not(.header) {
|
|
||||||
max-width: 300px;
|
|
||||||
overflow-x: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui.dropdown .menu .item {
|
.ui.dropdown .menu .item {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
@ -1055,10 +1055,6 @@
|
|||||||
.merge-section {
|
.merge-section {
|
||||||
background-color: var(--color-box-body);
|
background-color: var(--color-box-body);
|
||||||
|
|
||||||
.item {
|
|
||||||
padding: .25rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-section {
|
.item-section {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -44,27 +44,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#profile-avatar {
|
#profile-avatar {
|
||||||
background: none;
|
background: none;
|
||||||
padding: 1rem 1rem .25rem;
|
padding: 1rem 1rem .25rem;
|
||||||
|
justify-content: center;
|
||||||
img {
|
img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
|
||||||
|
|
||||||
@media @mediaSm {
|
@media @mediaSm {
|
||||||
height: 250px;
|
width: 30vw;
|
||||||
overflow: hidden;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
img {
|
|
||||||
max-height: 767px;
|
|
||||||
max-width: 767px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media @mediaSm {
|
@media @mediaSm {
|
||||||
|
Loading…
Reference in New Issue
Block a user