Skip to content

add submodule diff links #33097

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

Merged
merged 17 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 2 additions & 52 deletions models/repo/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -784,60 +784,10 @@ func GetRepositoryByName(ctx context.Context, ownerID int64, name string) (*Repo
return &repo, err
}

func parseRepositoryURL(ctx context.Context, repoURL string) (ret struct {
OwnerName, RepoName, RemainingPath string
},
) {
// possible urls for git:
// https://my.domain/sub-path/<owner>/<repo>[.git]
// git+ssh://[email protected]/<owner>/<repo>[.git]
// ssh://[email protected]/<owner>/<repo>[.git]
// [email protected]:<owner>/<repo>[.git]

fillPathParts := func(s string) {
s = strings.TrimPrefix(s, "/")
fields := strings.SplitN(s, "/", 3)
if len(fields) >= 2 {
ret.OwnerName = fields[0]
ret.RepoName = strings.TrimSuffix(fields[1], ".git")
if len(fields) == 3 {
ret.RemainingPath = "/" + fields[2]
}
}
}

parsed, err := giturl.ParseGitURL(repoURL)
if err != nil {
return ret
}
if parsed.URL.Scheme == "http" || parsed.URL.Scheme == "https" {
if !httplib.IsCurrentGiteaSiteURL(ctx, repoURL) {
return ret
}
fillPathParts(strings.TrimPrefix(parsed.URL.Path, setting.AppSubURL))
} else if parsed.URL.Scheme == "ssh" || parsed.URL.Scheme == "git+ssh" {
domainSSH := setting.SSH.Domain
domainCur := httplib.GuessCurrentHostDomain(ctx)
urlDomain, _, _ := net.SplitHostPort(parsed.URL.Host)
urlDomain = util.IfZero(urlDomain, parsed.URL.Host)
if urlDomain == "" {
return ret
}
// check whether URL domain is the App domain
domainMatches := domainSSH == urlDomain
// check whether URL domain is current domain from context
domainMatches = domainMatches || (domainCur != "" && domainCur == urlDomain)
if domainMatches {
fillPathParts(parsed.URL.Path)
}
}
return ret
}

// GetRepositoryByURL returns the repository by given url
func GetRepositoryByURL(ctx context.Context, repoURL string) (*Repository, error) {
ret := parseRepositoryURL(ctx, repoURL)
if ret.OwnerName == "" {
ret, err := giturl.ParseRepositoryURL(ctx, repoURL)
if err != nil || ret.OwnerName == "" {
return nil, fmt.Errorf("unknown or malformed repository URL")
}
return GetRepositoryByOwnerAndName(ctx, ret.OwnerName, ret.RepoName)
Expand Down
77 changes: 0 additions & 77 deletions models/repo/repo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,12 @@
package repo

import (
"context"
"net/http"
"net/url"
"testing"

"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
Expand Down Expand Up @@ -132,79 +128,6 @@ func TestMetas(t *testing.T) {
assert.Equal(t, ",owners,team1,", metas["teams"])
}

func TestParseRepositoryURLPathSegments(t *testing.T) {
defer test.MockVariableValue(&setting.AppURL, "https://localhost:3000")()

ctxURL, _ := url.Parse("https://gitea")
ctxReq := &http.Request{URL: ctxURL, Header: http.Header{}}
ctxReq.Host = ctxURL.Host
ctxReq.Header.Add("X-Forwarded-Proto", ctxURL.Scheme)
ctx := context.WithValue(context.Background(), httplib.RequestContextKey, ctxReq)
cases := []struct {
input string
ownerName, repoName, remaining string
}{
{input: "/user/repo"},

{input: "https://localhost:3000/user/repo", ownerName: "user", repoName: "repo"},
{input: "https://external:3000/user/repo"},

{input: "https://localhost:3000/user/repo.git/other", ownerName: "user", repoName: "repo", remaining: "/other"},

{input: "https://gitea/user/repo", ownerName: "user", repoName: "repo"},
{input: "https://gitea:3333/user/repo"},

{input: "ssh://try.gitea.io:2222/user/repo", ownerName: "user", repoName: "repo"},
{input: "ssh://external:2222/user/repo"},

{input: "git+ssh://[email protected]/user/repo.git", ownerName: "user", repoName: "repo"},
{input: "git+ssh://user@external/user/repo.git"},

{input: "[email protected]:user/repo.git", ownerName: "user", repoName: "repo"},
{input: "root@gitea:user/repo.git", ownerName: "user", repoName: "repo"},
{input: "root@external:user/repo.git"},
}

for _, c := range cases {
t.Run(c.input, func(t *testing.T) {
ret := parseRepositoryURL(ctx, c.input)
assert.Equal(t, c.ownerName, ret.OwnerName)
assert.Equal(t, c.repoName, ret.RepoName)
assert.Equal(t, c.remaining, ret.RemainingPath)
})
}

t.Run("WithSubpath", func(t *testing.T) {
defer test.MockVariableValue(&setting.AppURL, "https://localhost:3000/subpath")()
defer test.MockVariableValue(&setting.AppSubURL, "/subpath")()
cases = []struct {
input string
ownerName, repoName, remaining string
}{
{input: "https://localhost:3000/user/repo"},
{input: "https://localhost:3000/subpath/user/repo.git/other", ownerName: "user", repoName: "repo", remaining: "/other"},

{input: "ssh://try.gitea.io:2222/user/repo", ownerName: "user", repoName: "repo"},
{input: "ssh://external:2222/user/repo"},

{input: "git+ssh://[email protected]/user/repo.git", ownerName: "user", repoName: "repo"},
{input: "git+ssh://user@external/user/repo.git"},

{input: "[email protected]:user/repo.git", ownerName: "user", repoName: "repo"},
{input: "root@external:user/repo.git"},
}

for _, c := range cases {
t.Run(c.input, func(t *testing.T) {
ret := parseRepositoryURL(ctx, c.input)
assert.Equal(t, c.ownerName, ret.OwnerName)
assert.Equal(t, c.repoName, ret.RepoName)
assert.Equal(t, c.remaining, ret.RemainingPath)
})
}
})
}

func TestGetRepositoryByURL(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())

Expand Down
2 changes: 1 addition & 1 deletion modules/git/commit_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ package git
type CommitInfo struct {
Entry *TreeEntry
Commit *Commit
SubModuleFile *CommitSubModuleFile
SubmoduleFile *CommitSubmoduleFile
}
4 changes: 2 additions & 2 deletions modules/git/commit_info_gogit.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
} else if subModule != nil {
subModuleURL = subModule.URL
}
subModuleFile := NewCommitSubModuleFile(subModuleURL, entry.ID.String())
commitsInfo[i].SubModuleFile = subModuleFile
subModuleFile := NewCommitSubmoduleFile(subModuleURL, entry.ID.String())
commitsInfo[i].SubmoduleFile = subModuleFile
}
}

Expand Down
4 changes: 2 additions & 2 deletions modules/git/commit_info_nogogit.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
} else if subModule != nil {
subModuleURL = subModule.URL
}
subModuleFile := NewCommitSubModuleFile(subModuleURL, entry.ID.String())
commitsInfo[i].SubModuleFile = subModuleFile
subModuleFile := NewCommitSubmoduleFile(subModuleURL, entry.ID.String())
commitsInfo[i].SubmoduleFile = subModuleFile
}
}

Expand Down
4 changes: 4 additions & 0 deletions modules/git/commit_submodule.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@

package git

type SubmoduleWebLink struct {
RepoWebLink, CommitWebLink string
}

// GetSubModules get all the submodules of current revision git tree
func (c *Commit) GetSubModules() (*ObjectCache[*SubModule], error) {
if c.submoduleCache != nil {
Expand Down
131 changes: 37 additions & 94 deletions modules/git/commit_submodule_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,107 +5,50 @@
package git

import (
"fmt"
"net"
"net/url"
"path"
"regexp"
"strings"
)
"context"

var scpSyntax = regexp.MustCompile(`^([a-zA-Z0-9_]+@)?([a-zA-Z0-9._-]+):(.*)$`)
giturl "code.gitea.io/gitea/modules/git/url"
)

// CommitSubModuleFile represents a file with submodule type.
type CommitSubModuleFile struct {
refURL string
refID string
// CommitSubmoduleFile represents a file with submodule type.
type CommitSubmoduleFile struct {
refURL string
parsedURL *giturl.RepositoryURL
parsed bool
refID string
repoLink string
}

// NewCommitSubModuleFile create a new submodule file
func NewCommitSubModuleFile(refURL, refID string) *CommitSubModuleFile {
return &CommitSubModuleFile{
refURL: refURL,
refID: refID,
}
// NewCommitSubmoduleFile create a new submodule file
func NewCommitSubmoduleFile(refURL, refID string) *CommitSubmoduleFile {
return &CommitSubmoduleFile{refURL: refURL, refID: refID}
}

func getRefURL(refURL, urlPrefix, repoFullName, sshDomain string) string {
if refURL == "" {
return ""
}

refURI := strings.TrimSuffix(refURL, ".git")

prefixURL, _ := url.Parse(urlPrefix)
urlPrefixHostname, _, err := net.SplitHostPort(prefixURL.Host)
if err != nil {
urlPrefixHostname = prefixURL.Host
}

urlPrefix = strings.TrimSuffix(urlPrefix, "/")

// FIXME: Need to consider branch - which will require changes in modules/git/commit.go:GetSubModules
// Relative url prefix check (according to git submodule documentation)
if strings.HasPrefix(refURI, "./") || strings.HasPrefix(refURI, "../") {
return urlPrefix + path.Clean(path.Join("/", repoFullName, refURI))
}

if !strings.Contains(refURI, "://") {
// scp style syntax which contains *no* port number after the : (and is not parsed by net/url)
// ex: [email protected]:go-gitea/gitea
match := scpSyntax.FindAllStringSubmatch(refURI, -1)
if len(match) > 0 {
m := match[0]
refHostname := m[2]
pth := m[3]

if !strings.HasPrefix(pth, "/") {
pth = "/" + pth
}

if urlPrefixHostname == refHostname || refHostname == sshDomain {
return urlPrefix + path.Clean(path.Join("/", pth))
}
return "http://" + refHostname + pth
}
}

ref, err := url.Parse(refURI)
if err != nil {
return ""
}
func (sf *CommitSubmoduleFile) RefID() string {
return sf.refID // this function is only used in templates
}

refHostname, _, err := net.SplitHostPort(ref.Host)
if err != nil {
refHostname = ref.Host
// SubmoduleWebLink tries to make some web links for a submodule, it also works on "nil" receiver
func (sf *CommitSubmoduleFile) SubmoduleWebLink(ctx context.Context, optCommitID ...string) *SubmoduleWebLink {
if sf == nil {
return nil
}

supportedSchemes := []string{"http", "https", "git", "ssh", "git+ssh"}

for _, scheme := range supportedSchemes {
if ref.Scheme == scheme {
if ref.Scheme == "http" || ref.Scheme == "https" {
if len(ref.User.Username()) > 0 {
return ref.Scheme + "://" + fmt.Sprintf("%v", ref.User) + "@" + ref.Host + ref.Path
}
return ref.Scheme + "://" + ref.Host + ref.Path
} else if urlPrefixHostname == refHostname || refHostname == sshDomain {
return urlPrefix + path.Clean(path.Join("/", ref.Path))
}
return "http://" + refHostname + ref.Path
if !sf.parsed {
sf.parsed = true
parsedURL, err := giturl.ParseRepositoryURL(ctx, sf.refURL)
if err != nil {
return nil
}
}

return ""
}

// RefURL guesses and returns reference URL.
// FIXME: template passes AppURL as urlPrefix, it needs to figure out the correct approach (no hard-coded AppURL anymore)
func (sf *CommitSubModuleFile) RefURL(urlPrefix, repoFullName, sshDomain string) string {
return getRefURL(sf.refURL, urlPrefix, repoFullName, sshDomain)
}

// RefID returns reference ID.
func (sf *CommitSubModuleFile) RefID() string {
return sf.refID
sf.parsedURL = parsedURL
sf.repoLink = giturl.MakeRepositoryWebLink(sf.parsedURL)
}
var commitLink string
if len(optCommitID) == 2 {
commitLink = sf.repoLink + "/compare/" + optCommitID[0] + "..." + optCommitID[1]
} else if len(optCommitID) == 1 {
commitLink = sf.repoLink + "/commit/" + optCommitID[0]
} else {
commitLink = sf.repoLink + "/commit/" + sf.refID
}
return &SubmoduleWebLink{RepoWebLink: sf.repoLink, CommitWebLink: commitLink}
}
Loading
Loading