Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix some mirror bugs #18649

Merged
merged 17 commits into from
Jun 11, 2022
6 changes: 0 additions & 6 deletions models/repo/mirror.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,6 @@ import (
// ErrMirrorNotExist mirror does not exist error
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.
type Mirror struct {
ID int64 `xorm:"pk autoincr"`
Expand Down
20 changes: 15 additions & 5 deletions modules/git/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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 changes: 90 additions & 0 deletions modules/git/url/url.go
Original file line number Diff line number Diff line change
@@ -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
lunny marked this conversation as resolved.
Show resolved Hide resolved
}

// 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, ":") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SCP target path parsing is found here: https://github.com/openssh/openssh-portable/blob/16ea8b85838dd7a4dbeba4e51ac4f43fd68b1e5b/misc.c#L767-L834

  1. If there is a '[' then we can split by the first ':' after ']'
    • note this ':' has to be immediately after the ] otherwise the whole parse fails and it's a path.
  2. Otherwise split by the first ':'
  3. We split by this ':' into <host_user> ':' <path>
    • if <path> is empty set to ".". If no ':' set all to <path>
  4. Split <host_user> by '@' into <user> '@' <host-to-clean>.
    • if no '@' set all to <host-to-clean>
  5. <host-to-clean> - strip off '[' and ']' if present to form the <host>

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 changes: 167 additions & 0 deletions modules/git/url/url_test.go
Original file line number Diff line number Diff line change
@@ -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)
})
}
}
30 changes: 23 additions & 7 deletions modules/templates/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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{}
if !m.IsMirror {
return a
}
Comment on lines +977 to +979
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lunny I saw an issue here, the push mirrors URL are no longer displayed on the UI

image

I think we should return empty remoteAddress when the repo is a mirror not the opposite

	if m.IsMirror {
		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 := git.GetRemoteAddress(ctx, m.GetRepository().RepoPath(), m.GetRemoteName())
u, err := giturl.Parse(remoteURL)
if err != nil {
log.Error("GetRemoteAddress %v", err)
log.Error("giturl.Parse %v", err)
return a
}

if u.User != nil {
a.Username = u.User.Username()
a.Password, _ = u.User.Password()
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
Expand Down
14 changes: 8 additions & 6 deletions routers/web/repo/setting.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,22 +215,24 @@ func SettingsPost(ctx *context.Context) {
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() {
form.MirrorPassword, _ = u.User.Password()
}

address, err := forms.ParseRemoteAddr(form.MirrorAddress, form.MirrorUsername, form.MirrorPassword)
if err == nil {
err = migrations.IsMigrateURLAllowed(address, ctx.Doer)
}
err = migrations.IsMigrateURLAllowed(u.String(), ctx.Doer)
if err != nil {
ctx.Data["Err_MirrorAddress"] = true
handleSettingRemoteAddrError(ctx, err, form)
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)
return
}
Expand Down
Loading