forked from kevadesu/forgejo
Merge branch 'main' into feature-activitypub
This commit is contained in:
commit
f7da251c5d
73 changed files with 1027 additions and 328 deletions
|
@ -34,15 +34,12 @@ var (
|
|||
GitExecutable = "git"
|
||||
|
||||
// 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()
|
||||
|
||||
// SupportProcReceive version >= 2.29.0
|
||||
SupportProcReceive bool
|
||||
|
||||
// initMutex is used to avoid Golang's data race error. see the comments below.
|
||||
initMutex sync.Mutex
|
||||
|
||||
gitVersion *version.Version
|
||||
)
|
||||
|
||||
|
@ -131,15 +128,6 @@ func VersionInfo() string {
|
|||
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
|
||||
func HomeDir() string {
|
||||
if setting.RepoRootPath == "" {
|
||||
|
@ -153,11 +141,9 @@ func HomeDir() string {
|
|||
return setting.RepoRootPath
|
||||
}
|
||||
|
||||
func initSimpleInternal(ctx context.Context) error {
|
||||
// at the moment, when running integration tests, the git.InitXxx would be called twice.
|
||||
// one is called by the GlobalInitInstalled, one is called by TestMain.
|
||||
// so the init functions should be protected by a mutex to avoid Golang's data race error.
|
||||
|
||||
// InitSimple initializes git module with a very simple step, no config changes, no global command arguments.
|
||||
// This method doesn't change anything to filesystem. At the moment, it is only used by "git serv" sub-command, no data-race
|
||||
func InitSimple(ctx context.Context) error {
|
||||
DefaultContext = ctx
|
||||
|
||||
if setting.Git.Timeout.Default > 0 {
|
||||
|
@ -174,35 +160,47 @@ func initSimpleInternal(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// InitWithConfigSync initializes git module. This method may create directories or write files into filesystem
|
||||
func InitWithConfigSync(ctx context.Context) error {
|
||||
initMutex.Lock()
|
||||
defer initMutex.Unlock()
|
||||
var initOnce sync.Once
|
||||
|
||||
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 {
|
||||
return
|
||||
}
|
||||
|
||||
// Since git wire protocol has been released from git v2.18
|
||||
if setting.Git.EnableAutoGitWireProtocol && CheckGitVersionAtLeast("2.18") == nil {
|
||||
globalCommandArgs = append(globalCommandArgs, "-c", "protocol.version=2")
|
||||
}
|
||||
|
||||
// By default partial clones are disabled, enable them from git v2.22
|
||||
if !setting.Git.DisablePartialClone && CheckGitVersionAtLeast("2.22") == nil {
|
||||
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()
|
||||
}
|
||||
|
||||
if err = os.MkdirAll(setting.RepoRootPath, os.ModePerm); err != nil {
|
||||
// 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)
|
||||
}
|
||||
|
||||
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
|
||||
if setting.Git.EnableAutoGitWireProtocol && CheckGitVersionAtLeast("2.18") == nil {
|
||||
globalCommandArgs = append(globalCommandArgs, "-c", "protocol.version=2")
|
||||
}
|
||||
|
||||
// By default partial clones are disabled, enable them from git v2.22
|
||||
if !setting.Git.DisablePartialClone && CheckGitVersionAtLeast("2.22") == nil {
|
||||
globalCommandArgs = append(globalCommandArgs, "-c", "uploadpack.allowfilter=true", "-c", "uploadpack.allowAnySHA1InWant=true")
|
||||
}
|
||||
|
||||
// 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.
|
||||
// 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
|
||||
if err := configAddNonExist("receive.procReceiveRefs", "refs/for"); err != nil {
|
||||
return err
|
||||
}
|
||||
SupportProcReceive = true
|
||||
} else {
|
||||
if err := configUnsetAll("receive.procReceiveRefs", "refs/for"); err != nil {
|
||||
return err
|
||||
}
|
||||
SupportProcReceive = false
|
||||
}
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
|
|
|
@ -28,7 +28,7 @@ func testRun(m *testing.M) error {
|
|||
defer util.RemoveAll(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)
|
||||
}
|
||||
|
||||
|
|
|
@ -6,11 +6,12 @@ package git
|
|||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
|
||||
giturl "code.gitea.io/gitea/modules/git/url"
|
||||
)
|
||||
|
||||
// GetRemoteAddress returns the url of a specific remote of the repository.
|
||||
func GetRemoteAddress(ctx context.Context, repoPath, remoteName string) (*url.URL, error) {
|
||||
// GetRemoteAddress returns remote url of git repository in the repoPath with special remote name
|
||||
func GetRemoteAddress(ctx context.Context, repoPath, remoteName string) (string, error) {
|
||||
var cmd *Command
|
||||
if CheckGitVersionAtLeast("2.7") == nil {
|
||||
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})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(result) > 0 {
|
||||
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"
|
||||
} else if content == `</span><span class="w">` {
|
||||
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.TrimPrefix(content, `</span>`)
|
||||
|
|
|
@ -5,11 +5,13 @@
|
|||
package highlight
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
|
@ -20,83 +22,83 @@ func TestFile(t *testing.T) {
|
|||
numLines int
|
||||
fileName string
|
||||
code string
|
||||
want []string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: ".drone.yml",
|
||||
numLines: 12,
|
||||
fileName: ".drone.yml",
|
||||
code: `kind: pipeline
|
||||
name: default
|
||||
code: util.Dedent(`
|
||||
kind: pipeline
|
||||
name: default
|
||||
|
||||
steps:
|
||||
- name: test
|
||||
image: golang:1.13
|
||||
environment:
|
||||
GOPROXY: https://goproxy.cn
|
||||
commands:
|
||||
- go get -u
|
||||
- go build -v
|
||||
- go test -v -race -coverprofile=coverage.txt -covermode=atomic
|
||||
`,
|
||||
want: []string{
|
||||
`<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></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">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="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="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 test -v -race -coverprofile=coverage.txt -covermode=atomic</span><span class="w">
|
||||
</span></span></span>`,
|
||||
`<span class="w">
|
||||
</span>`,
|
||||
},
|
||||
steps:
|
||||
- name: test
|
||||
image: golang:1.13
|
||||
environment:
|
||||
GOPROXY: https://goproxy.cn
|
||||
commands:
|
||||
- go get -u
|
||||
- go build -v
|
||||
- go test -v -race -coverprofile=coverage.txt -covermode=atomic
|
||||
`),
|
||||
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></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 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">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="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="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 test -v -race -coverprofile=coverage.txt -covermode=atomic</span></span></span>
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: ".drone.yml - trailing space",
|
||||
numLines: 13,
|
||||
fileName: ".drone.yml",
|
||||
code: `kind: pipeline
|
||||
name: default ` + `
|
||||
code: strings.Replace(util.Dedent(`
|
||||
kind: pipeline
|
||||
name: default
|
||||
|
||||
steps:
|
||||
- name: test
|
||||
image: golang:1.13
|
||||
environment:
|
||||
GOPROXY: https://goproxy.cn
|
||||
commands:
|
||||
- go get -u
|
||||
- go build -v
|
||||
- go test -v -race -coverprofile=coverage.txt -covermode=atomic
|
||||
`,
|
||||
want: []string{
|
||||
`<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></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">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="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="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 test -v -race -coverprofile=coverage.txt -covermode=atomic</span>`,
|
||||
`</span></span><span class="line"><span class="cl"><span class="w"> </span></span></span>`,
|
||||
},
|
||||
steps:
|
||||
- name: test
|
||||
image: golang:1.13
|
||||
environment:
|
||||
GOPROXY: https://goproxy.cn
|
||||
commands:
|
||||
- go get -u
|
||||
- go build -v
|
||||
- go test -v -race -coverprofile=coverage.txt -covermode=atomic
|
||||
`)+"\n", "name: default", "name: default ", 1),
|
||||
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></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 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">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="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="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 test -v -race -coverprofile=coverage.txt -covermode=atomic</span>
|
||||
</span></span>
|
||||
<span class="w">
|
||||
</span>
|
||||
`),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := File(tt.numLines, tt.fileName, "", []byte(tt.code)); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("File() = %v, want %v", got, tt.want)
|
||||
}
|
||||
got := strings.Join(File(tt.numLines, tt.fileName, "", []byte(tt.code)), "\n")
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@ import (
|
|||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/queue"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
|
@ -30,10 +29,6 @@ func TestMain(m *testing.M) {
|
|||
}
|
||||
|
||||
func TestRepoStatsIndex(t *testing.T) {
|
||||
if err := git.InitWithConfigSync(context.Background()); !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
setting.Cfg = ini.Empty()
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/markup/common"
|
||||
"code.gitea.io/gitea/modules/references"
|
||||
"code.gitea.io/gitea/modules/regexplru"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/templates/vars"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
@ -33,6 +34,7 @@ import (
|
|||
const (
|
||||
IssueNameStyleNumeric = "numeric"
|
||||
IssueNameStyleAlphanumeric = "alphanumeric"
|
||||
IssueNameStyleRegexp = "regexp"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -815,19 +817,35 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
|
|||
)
|
||||
|
||||
next := node.NextSibling
|
||||
|
||||
for node != nil && node != next {
|
||||
_, exttrack := ctx.Metas["format"]
|
||||
alphanum := ctx.Metas["style"] == IssueNameStyleAlphanumeric
|
||||
_, hasExtTrackFormat := ctx.Metas["format"]
|
||||
|
||||
// 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
|
||||
found, ref = references.FindRenderizableReferenceNumeric(node.Data, exttrack && alphanum)
|
||||
if exttrack && alphanum {
|
||||
if found2, ref2 := references.FindRenderizableReferenceAlphanumeric(node.Data); found2 {
|
||||
if !found || ref2.RefLocation.Start < ref.RefLocation.Start {
|
||||
found = true
|
||||
ref = ref2
|
||||
}
|
||||
isNumericStyle := ctx.Metas["style"] == "" || ctx.Metas["style"] == IssueNameStyleNumeric
|
||||
foundNumeric, refNumeric := references.FindRenderizableReferenceNumeric(node.Data, hasExtTrackFormat && !isNumericStyle)
|
||||
|
||||
switch ctx.Metas["style"] {
|
||||
case "", IssueNameStyleNumeric:
|
||||
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 {
|
||||
|
@ -836,7 +854,7 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
|
|||
|
||||
var link *html.Node
|
||||
reftext := node.Data[ref.RefLocation.Start:ref.RefLocation.End]
|
||||
if exttrack && !ref.IsPull {
|
||||
if hasExtTrackFormat && !ref.IsPull {
|
||||
ctx.Metas["index"] = ref.Issue
|
||||
|
||||
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
|
||||
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])
|
||||
} else {
|
||||
keyword = &html.Node{
|
||||
|
|
|
@ -21,8 +21,8 @@ const (
|
|||
TestRepoURL = TestAppURL + TestOrgRepo + "/"
|
||||
)
|
||||
|
||||
// alphanumLink an HTML link to an alphanumeric-style issue
|
||||
func alphanumIssueLink(baseURL, class, name string) string {
|
||||
// externalIssueLink an HTML link to an alphanumeric-style issue
|
||||
func externalIssueLink(baseURL, class, name string) string {
|
||||
return link(util.URLJoin(baseURL, name), class, name)
|
||||
}
|
||||
|
||||
|
@ -54,6 +54,13 @@ var alphanumericMetas = map[string]string{
|
|||
"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
|
||||
var localMetas = map[string]string{
|
||||
"user": "gogits",
|
||||
|
@ -184,7 +191,7 @@ func TestRender_IssueIndexPattern4(t *testing.T) {
|
|||
test := func(s, expectedFmt string, names ...string) {
|
||||
links := make([]interface{}, len(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...)
|
||||
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")
|
||||
}
|
||||
|
||||
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) {
|
||||
if ctx.URLPrefix == "" {
|
||||
ctx.URLPrefix = TestAppURL
|
||||
|
@ -202,7 +246,7 @@ func testRenderIssueIndexPattern(t *testing.T, input, expected string, ctx *Rend
|
|||
var buf strings.Builder
|
||||
err := postProcess(ctx, []processor{issueIndexPatternProcessor}, strings.NewReader(input), &buf)
|
||||
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) {
|
||||
|
|
|
@ -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.
|
||||
func FindRenderizableReferenceAlphanumeric(content string) (bool, *RenderizableReference) {
|
||||
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)
|
||||
func IsXrefActionable(ref *RenderizableReference, extTracker, alphaNum bool) bool {
|
||||
func IsXrefActionable(ref *RenderizableReference, extTracker bool) bool {
|
||||
if extTracker {
|
||||
// External issues cannot be automatically closed
|
||||
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/emoji"
|
||||
"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/log"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
|
@ -971,20 +972,35 @@ type remoteAddress struct {
|
|||
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{}
|
||||
|
||||
u, err := git.GetRemoteAddress(ctx, m.GetRepository().RepoPath(), m.GetRemoteName())
|
||||
if err != nil {
|
||||
log.Error("GetRemoteAddress %v", err)
|
||||
if !m.IsMirror {
|
||||
return a
|
||||
}
|
||||
|
||||
if u.User != nil {
|
||||
a.Username = u.User.Username()
|
||||
a.Password, _ = u.User.Password()
|
||||
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 {
|
||||
a.Username = u.User.Username()
|
||||
a.Password, _ = u.User.Password()
|
||||
}
|
||||
u.User = nil
|
||||
}
|
||||
u.User = nil
|
||||
a.Address = u.String()
|
||||
|
||||
return a
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"crypto/rand"
|
||||
"errors"
|
||||
"math/big"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
|
@ -191,3 +192,35 @@ var titleCaser = cases.Title(language.English)
|
|||
func ToTitleCase(s string) string {
|
||||
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`)
|
||||
}
|
||||
|
||||
func TestDedent(t *testing.T) {
|
||||
assert.Equal(t, Dedent(`
|
||||
foo
|
||||
bar
|
||||
`), "foo\n\tbar")
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue