]> source.dussan.org Git - gitea.git/commitdiff
Fix submodule parsing (#32571)
authorLunny Xiao <xiaolunwen@gmail.com>
Wed, 20 Nov 2024 19:26:12 +0000 (11:26 -0800)
committerGitHub <noreply@github.com>
Wed, 20 Nov 2024 19:26:12 +0000 (19:26 +0000)
Fix #32568, parse `.gitmodules` correctly

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
22 files changed:
modules/git/commit.go
modules/git/commit_info.go
modules/git/commit_info_gogit.go
modules/git/commit_info_nogogit.go
modules/git/commit_submodule.go [new file with mode: 0644]
modules/git/commit_submodule_file.go [new file with mode: 0644]
modules/git/commit_submodule_file_test.go [new file with mode: 0644]
modules/git/commit_test.go
modules/git/config.go [new file with mode: 0644]
modules/git/config_submodule.go [new file with mode: 0644]
modules/git/config_submodule_test.go [new file with mode: 0644]
modules/git/config_test.go [new file with mode: 0644]
modules/git/fsck.go [new file with mode: 0644]
modules/git/git.go
modules/git/git_test.go
modules/git/repo_base_gogit.go
modules/git/repo_base_nogogit.go
modules/git/repo_tag_gogit.go
modules/git/repo_tag_nogogit.go
modules/git/submodule.go [deleted file]
modules/git/submodule_test.go [deleted file]
modules/git/utils.go

index 86adaa79a667ccb318555456469afc3374c5b026..010b56948ef60778d2ce1053c6bb05a6f139dc3b 100644 (file)
@@ -9,7 +9,6 @@ import (
        "bytes"
        "context"
        "errors"
-       "fmt"
        "io"
        "os/exec"
        "strconv"
@@ -29,7 +28,7 @@ type Commit struct {
        Signature     *CommitSignature
 
        Parents        []ObjectID // ID strings
-       submoduleCache *ObjectCache
+       submoduleCache *ObjectCache[*SubModule]
 }
 
 // CommitSignature represents a git commit signature part.
@@ -357,69 +356,6 @@ func (c *Commit) GetFileContent(filename string, limit int) (string, error) {
        return string(bytes), nil
 }
 
-// GetSubModules get all the sub modules of current revision git tree
-func (c *Commit) GetSubModules() (*ObjectCache, error) {
-       if c.submoduleCache != nil {
-               return c.submoduleCache, nil
-       }
-
-       entry, err := c.GetTreeEntryByPath(".gitmodules")
-       if err != nil {
-               if _, ok := err.(ErrNotExist); ok {
-                       return nil, nil
-               }
-               return nil, err
-       }
-
-       rd, err := entry.Blob().DataAsync()
-       if err != nil {
-               return nil, err
-       }
-
-       defer rd.Close()
-       scanner := bufio.NewScanner(rd)
-       c.submoduleCache = newObjectCache()
-       var ismodule bool
-       var path string
-       for scanner.Scan() {
-               if strings.HasPrefix(scanner.Text(), "[submodule") {
-                       ismodule = true
-                       continue
-               }
-               if ismodule {
-                       fields := strings.Split(scanner.Text(), "=")
-                       k := strings.TrimSpace(fields[0])
-                       if k == "path" {
-                               path = strings.TrimSpace(fields[1])
-                       } else if k == "url" {
-                               c.submoduleCache.Set(path, &SubModule{path, strings.TrimSpace(fields[1])})
-                               ismodule = false
-                       }
-               }
-       }
-       if err = scanner.Err(); err != nil {
-               return nil, fmt.Errorf("GetSubModules scan: %w", err)
-       }
-
-       return c.submoduleCache, nil
-}
-
-// GetSubModule get the sub module according entryname
-func (c *Commit) GetSubModule(entryname string) (*SubModule, error) {
-       modules, err := c.GetSubModules()
-       if err != nil {
-               return nil, err
-       }
-
-       if modules != nil {
-               module, has := modules.Get(entryname)
-               if has {
-                       return module.(*SubModule), nil
-               }
-       }
-       return nil, nil
-}
-
 // GetBranchName gets the closest branch name (as returned by 'git name-rev --name-only')
 func (c *Commit) GetBranchName() (string, error) {
        cmd := NewCommand(c.repo.Ctx, "name-rev")
index c740a4e13eb94cb52858a89f3a54c5fbc6fa07a1..545081275b7a7baa916447576f257ae2ace8823b 100644 (file)
@@ -7,5 +7,5 @@ package git
 type CommitInfo struct {
        Entry         *TreeEntry
        Commit        *Commit
-       SubModuleFile *SubModuleFile
+       SubModuleFile *CommitSubModuleFile
 }
index 31ffc9aec1c3850c650b0be6b635ef78714d3c5e..11b44f7c356a46237672f95ff898e3fcf7afd2b3 100644 (file)
@@ -71,7 +71,7 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
                        commitsInfo[i].Commit = entryCommit
                }
 
-               // If the entry if a submodule add a submodule file for this
+               // If the entry is a submodule add a submodule file for this
                if entry.IsSubModule() {
                        subModuleURL := ""
                        var fullPath string
@@ -85,7 +85,7 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
                        } else if subModule != nil {
                                subModuleURL = subModule.URL
                        }
-                       subModuleFile := NewSubModuleFile(commitsInfo[i].Commit, subModuleURL, entry.ID.String())
+                       subModuleFile := NewCommitSubModuleFile(subModuleURL, entry.ID.String())
                        commitsInfo[i].SubModuleFile = subModuleFile
                }
        }
index cfde64a033932e6f0640f77f3f93b85237d8e90f..20d586f0ff58c71e21c49ab8993bb340b9dc1166 100644 (file)
@@ -79,7 +79,7 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
                        } else if subModule != nil {
                                subModuleURL = subModule.URL
                        }
-                       subModuleFile := NewSubModuleFile(commitsInfo[i].Commit, subModuleURL, entry.ID.String())
+                       subModuleFile := NewCommitSubModuleFile(subModuleURL, entry.ID.String())
                        commitsInfo[i].SubModuleFile = subModuleFile
                }
        }
diff --git a/modules/git/commit_submodule.go b/modules/git/commit_submodule.go
new file mode 100644 (file)
index 0000000..6603061
--- /dev/null
@@ -0,0 +1,47 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package git
+
+// GetSubModules get all the submodules of current revision git tree
+func (c *Commit) GetSubModules() (*ObjectCache[*SubModule], error) {
+       if c.submoduleCache != nil {
+               return c.submoduleCache, nil
+       }
+
+       entry, err := c.GetTreeEntryByPath(".gitmodules")
+       if err != nil {
+               if _, ok := err.(ErrNotExist); ok {
+                       return nil, nil
+               }
+               return nil, err
+       }
+
+       rd, err := entry.Blob().DataAsync()
+       if err != nil {
+               return nil, err
+       }
+       defer rd.Close()
+
+       // at the moment we do not strictly limit the size of the .gitmodules file because some users would have huge .gitmodules files (>1MB)
+       c.submoduleCache, err = configParseSubModules(rd)
+       if err != nil {
+               return nil, err
+       }
+       return c.submoduleCache, nil
+}
+
+// GetSubModule get the submodule according entry name
+func (c *Commit) GetSubModule(entryName string) (*SubModule, error) {
+       modules, err := c.GetSubModules()
+       if err != nil {
+               return nil, err
+       }
+
+       if modules != nil {
+               if module, has := modules.Get(entryName); has {
+                       return module, nil
+               }
+       }
+       return nil, nil
+}
diff --git a/modules/git/commit_submodule_file.go b/modules/git/commit_submodule_file.go
new file mode 100644 (file)
index 0000000..bdec35f
--- /dev/null
@@ -0,0 +1,111 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// Copyright 2015 The Gogs Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package git
+
+import (
+       "fmt"
+       "net"
+       "net/url"
+       "path"
+       "regexp"
+       "strings"
+)
+
+var scpSyntax = regexp.MustCompile(`^([a-zA-Z0-9_]+@)?([a-zA-Z0-9._-]+):(.*)$`)
+
+// CommitSubModuleFile represents a file with submodule type.
+type CommitSubModuleFile struct {
+       refURL string
+       refID  string
+}
+
+// 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: git@try.gitea.io: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 ""
+       }
+
+       refHostname, _, err := net.SplitHostPort(ref.Host)
+       if err != nil {
+               refHostname = ref.Host
+       }
+
+       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
+               }
+       }
+
+       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
+}
diff --git a/modules/git/commit_submodule_file_test.go b/modules/git/commit_submodule_file_test.go
new file mode 100644 (file)
index 0000000..473b996
--- /dev/null
@@ -0,0 +1,42 @@
+// Copyright 2018 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package git
+
+import (
+       "testing"
+
+       "github.com/stretchr/testify/assert"
+)
+
+func TestCommitSubModuleFileGetRefURL(t *testing.T) {
+       kases := []struct {
+               refURL     string
+               prefixURL  string
+               parentPath string
+               SSHDomain  string
+               expect     string
+       }{
+               {"git://github.com/user1/repo1", "/", "user1/repo2", "", "http://github.com/user1/repo1"},
+               {"https://localhost/user1/repo1.git", "/", "user1/repo2", "", "https://localhost/user1/repo1"},
+               {"http://localhost/user1/repo1.git", "/", "owner/reponame", "", "http://localhost/user1/repo1"},
+               {"git@github.com:user1/repo1.git", "/", "owner/reponame", "", "http://github.com/user1/repo1"},
+               {"ssh://git@git.zefie.net:2222/zefie/lge_g6_kernel_scripts.git", "/", "zefie/lge_g6_kernel", "", "http://git.zefie.net/zefie/lge_g6_kernel_scripts"},
+               {"git@git.zefie.net:2222/zefie/lge_g6_kernel_scripts.git", "/", "zefie/lge_g6_kernel", "", "http://git.zefie.net/2222/zefie/lge_g6_kernel_scripts"},
+               {"git@try.gitea.io:go-gitea/gitea", "https://try.gitea.io/", "go-gitea/sdk", "", "https://try.gitea.io/go-gitea/gitea"},
+               {"ssh://git@try.gitea.io:9999/go-gitea/gitea", "https://try.gitea.io/", "go-gitea/sdk", "", "https://try.gitea.io/go-gitea/gitea"},
+               {"git://git@try.gitea.io:9999/go-gitea/gitea", "https://try.gitea.io/", "go-gitea/sdk", "", "https://try.gitea.io/go-gitea/gitea"},
+               {"ssh://git@127.0.0.1:9999/go-gitea/gitea", "https://127.0.0.1:3000/", "go-gitea/sdk", "", "https://127.0.0.1:3000/go-gitea/gitea"},
+               {"https://gitea.com:3000/user1/repo1.git", "https://127.0.0.1:3000/", "user/repo2", "", "https://gitea.com:3000/user1/repo1"},
+               {"https://example.gitea.com/gitea/user1/repo1.git", "https://example.gitea.com/gitea/", "", "user/repo2", "https://example.gitea.com/gitea/user1/repo1"},
+               {"https://username:password@github.com/username/repository.git", "/", "username/repository2", "", "https://username:password@github.com/username/repository"},
+               {"somethingbad", "https://127.0.0.1:3000/go-gitea/gitea", "/", "", ""},
+               {"git@localhost:user/repo", "https://localhost/", "user2/repo1", "", "https://localhost/user/repo"},
+               {"../path/to/repo.git/", "https://localhost/", "user/repo2", "", "https://localhost/user/path/to/repo.git"},
+               {"ssh://git@ssh.gitea.io:2222/go-gitea/gitea", "https://try.gitea.io/", "go-gitea/sdk", "ssh.gitea.io", "https://try.gitea.io/go-gitea/gitea"},
+       }
+
+       for _, kase := range kases {
+               assert.EqualValues(t, kase.expect, getRefURL(kase.refURL, kase.prefixURL, kase.parentPath, kase.SSHDomain))
+       }
+}
index 0ddeb182ef83985580942f6c8d217acad5ffcac3..bf381a53501c58be6e541bf305001697ffcfd05d 100644 (file)
@@ -135,7 +135,7 @@ author KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100
 committer KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100
 encoding ISO-8859-1
 gpgsig -----BEGIN PGP SIGNATURE-----
+<SPACE>
  iQGzBAABCgAdFiEE9HRrbqvYxPT8PXbefPSEkrowAa8FAmYGg7IACgkQfPSEkrow
  Aa9olwv+P0HhtCM6CRvlUmPaqswRsDPNR4i66xyXGiSxdI9V5oJL7HLiQIM7KrFR
  gizKa2COiGtugv8fE+TKqXKaJx6uJUJEjaBd8E9Af9PrAzjWj+A84lU6/PgPS8hq
@@ -150,7 +150,7 @@ gpgsig -----BEGIN PGP SIGNATURE-----
  -----END PGP SIGNATURE-----
 
 ISO-8859-1`
-
+       commitString = strings.ReplaceAll(commitString, "<SPACE>", " ")
        sha := &Sha1Hash{0xfe, 0xaf, 0x4b, 0xa6, 0xbc, 0x63, 0x5f, 0xec, 0x44, 0x2f, 0x46, 0xdd, 0xd4, 0x51, 0x24, 0x16, 0xec, 0x43, 0xc2, 0xc2}
        gitRepo, err := openRepositoryWithDefaultContext(filepath.Join(testReposDir, "repo1_bare"))
        assert.NoError(t, err)
diff --git a/modules/git/config.go b/modules/git/config.go
new file mode 100644 (file)
index 0000000..9c36cf1
--- /dev/null
@@ -0,0 +1,187 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package git
+
+import (
+       "fmt"
+       "os"
+       "regexp"
+       "runtime"
+       "strings"
+
+       "code.gitea.io/gitea/modules/setting"
+)
+
+// 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 prepare git home directory %s, err: %w", HomeDir(), err)
+       }
+
+       // first, write user's git config options to git config file
+       // user config options could be overwritten by builtin values later, because if a value is builtin, it must have some special purposes
+       for k, v := range setting.GitConfig.Options {
+               if err = configSet(strings.ToLower(k), v); err != nil {
+                       return 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"
+       // 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.
+       for configKey, defaultValue := range map[string]string{
+               "user.name":  "Gitea",
+               "user.email": "gitea@fake.local",
+       } {
+               if err := configSetNonExist(configKey, defaultValue); err != nil {
+                       return err
+               }
+       }
+
+       // Set git some configurations - these must be set to these values for gitea to work correctly
+       if err := configSet("core.quotePath", "false"); err != nil {
+               return err
+       }
+
+       if DefaultFeatures().CheckVersionAtLeast("2.10") {
+               if err := configSet("receive.advertisePushOptions", "true"); err != nil {
+                       return err
+               }
+       }
+
+       if DefaultFeatures().CheckVersionAtLeast("2.18") {
+               if err := configSet("core.commitGraph", "true"); err != nil {
+                       return err
+               }
+               if err := configSet("gc.writeCommitGraph", "true"); err != nil {
+                       return err
+               }
+               if err := configSet("fetch.writeCommitGraph", "true"); err != nil {
+                       return err
+               }
+       }
+
+       if DefaultFeatures().SupportProcReceive {
+               // set support for AGit flow
+               if err := configAddNonExist("receive.procReceiveRefs", "refs/for"); err != nil {
+                       return err
+               }
+       } else {
+               if err := configUnsetAll("receive.procReceiveRefs", "refs/for"); err != nil {
+                       return err
+               }
+       }
+
+       // Due to CVE-2022-24765, git now denies access to git directories which are not owned by current user.
+       // However, some docker users and samba users find it difficult to configure their systems correctly,
+       // so that Gitea's git repositories are owned by the Gitea user.
+       // (Possibly Windows Service users - but ownership in this case should really be set correctly on the filesystem.)
+       // See issue: https://github.com/go-gitea/gitea/issues/19455
+       // As Gitea now always use its internal git config file, and access to the git repositories is managed through Gitea,
+       // it is now safe to set "safe.directory=*" for internal usage only.
+       // Although this setting is only supported by some new git versions, it is also tolerated by earlier versions
+       if err := configAddNonExist("safe.directory", "*"); err != nil {
+               return err
+       }
+
+       if runtime.GOOS == "windows" {
+               if err := configSet("core.longpaths", "true"); err != nil {
+                       return err
+               }
+               if setting.Git.DisableCoreProtectNTFS {
+                       err = configSet("core.protectNTFS", "false")
+               } else {
+                       err = configUnsetAll("core.protectNTFS", "false")
+               }
+               if err != nil {
+                       return err
+               }
+       }
+
+       // By default partial clones are disabled, enable them from git v2.22
+       if !setting.Git.DisablePartialClone && DefaultFeatures().CheckVersionAtLeast("2.22") {
+               if err = configSet("uploadpack.allowfilter", "true"); err != nil {
+                       return err
+               }
+               err = configSet("uploadpack.allowAnySHA1InWant", "true")
+       } else {
+               if err = configUnsetAll("uploadpack.allowfilter", "true"); err != nil {
+                       return err
+               }
+               err = configUnsetAll("uploadpack.allowAnySHA1InWant", "true")
+       }
+
+       return err
+}
+
+func configSet(key, value string) error {
+       stdout, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
+       if err != nil && !IsErrorExitCode(err, 1) {
+               return fmt.Errorf("failed to get git config %s, err: %w", key, err)
+       }
+
+       currValue := strings.TrimSpace(stdout)
+       if currValue == value {
+               return nil
+       }
+
+       _, _, err = NewCommand(DefaultContext, "config", "--global").AddDynamicArguments(key, value).RunStdString(nil)
+       if err != nil {
+               return fmt.Errorf("failed to set git global config %s, err: %w", key, err)
+       }
+
+       return nil
+}
+
+func configSetNonExist(key, value string) error {
+       _, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
+       if err == nil {
+               // already exist
+               return nil
+       }
+       if IsErrorExitCode(err, 1) {
+               // not exist, set new config
+               _, _, err = NewCommand(DefaultContext, "config", "--global").AddDynamicArguments(key, value).RunStdString(nil)
+               if err != nil {
+                       return fmt.Errorf("failed to set git global config %s, err: %w", key, err)
+               }
+               return nil
+       }
+
+       return fmt.Errorf("failed to get git config %s, err: %w", key, err)
+}
+
+func configAddNonExist(key, value string) error {
+       _, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(nil)
+       if err == nil {
+               // already exist
+               return nil
+       }
+       if IsErrorExitCode(err, 1) {
+               // not exist, add new config
+               _, _, err = NewCommand(DefaultContext, "config", "--global", "--add").AddDynamicArguments(key, value).RunStdString(nil)
+               if err != nil {
+                       return fmt.Errorf("failed to add git global config %s, err: %w", key, err)
+               }
+               return nil
+       }
+       return fmt.Errorf("failed to get git config %s, err: %w", key, err)
+}
+
+func configUnsetAll(key, value string) error {
+       _, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
+       if err == nil {
+               // exist, need to remove
+               _, _, err = NewCommand(DefaultContext, "config", "--global", "--unset-all").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(nil)
+               if err != nil {
+                       return fmt.Errorf("failed to unset git global config %s, err: %w", key, err)
+               }
+               return nil
+       }
+       if IsErrorExitCode(err, 1) {
+               // not exist
+               return nil
+       }
+       return fmt.Errorf("failed to get git config %s, err: %w", key, err)
+}
diff --git a/modules/git/config_submodule.go b/modules/git/config_submodule.go
new file mode 100644 (file)
index 0000000..fe33208
--- /dev/null
@@ -0,0 +1,75 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package git
+
+import (
+       "bufio"
+       "fmt"
+       "io"
+       "strings"
+)
+
+// SubModule is a reference on git repository
+type SubModule struct {
+       Path   string
+       URL    string
+       Branch string // this field is newly added but not really used
+}
+
+// configParseSubModules this is not a complete parse for gitmodules file, it only
+// parses the url and path of submodules. At the moment it only parses well-formed gitmodules files.
+// In the future, there should be a complete implementation of https://git-scm.com/docs/git-config#_syntax
+func configParseSubModules(r io.Reader) (*ObjectCache[*SubModule], error) {
+       var subModule *SubModule
+       subModules := newObjectCache[*SubModule]()
+       scanner := bufio.NewScanner(r)
+       for scanner.Scan() {
+               line := strings.TrimSpace(scanner.Text())
+
+               // Skip empty lines and comments
+               if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, ";") {
+                       continue
+               }
+
+               // Section header [section]
+               if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
+                       if subModule != nil {
+                               subModules.Set(subModule.Path, subModule)
+                       }
+                       if strings.HasPrefix(line, "[submodule") {
+                               subModule = &SubModule{}
+                       } else {
+                               subModule = nil
+                       }
+                       continue
+               }
+
+               if subModule == nil {
+                       continue
+               }
+
+               parts := strings.SplitN(line, "=", 2)
+               if len(parts) != 2 {
+                       continue
+               }
+               key := strings.TrimSpace(parts[0])
+               value := strings.TrimSpace(parts[1])
+               switch key {
+               case "path":
+                       subModule.Path = value
+               case "url":
+                       subModule.URL = value
+               case "branch":
+                       subModule.Branch = value
+               }
+       }
+
+       if err := scanner.Err(); err != nil {
+               return nil, fmt.Errorf("error reading file: %w", err)
+       }
+       if subModule != nil {
+               subModules.Set(subModule.Path, subModule)
+       }
+       return subModules, nil
+}
diff --git a/modules/git/config_submodule_test.go b/modules/git/config_submodule_test.go
new file mode 100644 (file)
index 0000000..f0846c7
--- /dev/null
@@ -0,0 +1,49 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package git
+
+import (
+       "strings"
+       "testing"
+
+       "github.com/stretchr/testify/assert"
+)
+
+func TestConfigSubmodule(t *testing.T) {
+       input := `
+[core]
+path = test
+
+[submodule "submodule1"]
+       path = path1
+       url =   https://gitea.io/foo/foo
+       #branch = b1
+
+[other1]
+branch = master
+
+[submodule "submodule2"]
+       path = path2
+       url =   https://gitea.io/bar/bar
+       branch = b2
+
+[other2]
+branch = main
+
+[submodule "submodule3"]
+       path = path3
+       url =   https://gitea.io/xxx/xxx
+`
+
+       subModules, err := configParseSubModules(strings.NewReader(input))
+       assert.NoError(t, err)
+       assert.Len(t, subModules.cache, 3)
+
+       sm1, _ := subModules.Get("path1")
+       assert.Equal(t, &SubModule{Path: "path1", URL: "https://gitea.io/foo/foo", Branch: ""}, sm1)
+       sm2, _ := subModules.Get("path2")
+       assert.Equal(t, &SubModule{Path: "path2", URL: "https://gitea.io/bar/bar", Branch: "b2"}, sm2)
+       sm3, _ := subModules.Get("path3")
+       assert.Equal(t, &SubModule{Path: "path3", URL: "https://gitea.io/xxx/xxx", Branch: ""}, sm3)
+}
diff --git a/modules/git/config_test.go b/modules/git/config_test.go
new file mode 100644 (file)
index 0000000..59f70c9
--- /dev/null
@@ -0,0 +1,66 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package git
+
+import (
+       "os"
+       "strings"
+       "testing"
+
+       "code.gitea.io/gitea/modules/setting"
+
+       "github.com/stretchr/testify/assert"
+)
+
+func gitConfigContains(sub string) bool {
+       if b, err := os.ReadFile(HomeDir() + "/.gitconfig"); err == nil {
+               return strings.Contains(string(b), sub)
+       }
+       return false
+}
+
+func TestGitConfig(t *testing.T) {
+       assert.False(t, gitConfigContains("key-a"))
+
+       assert.NoError(t, configSetNonExist("test.key-a", "val-a"))
+       assert.True(t, gitConfigContains("key-a = val-a"))
+
+       assert.NoError(t, configSetNonExist("test.key-a", "val-a-changed"))
+       assert.False(t, gitConfigContains("key-a = val-a-changed"))
+
+       assert.NoError(t, configSet("test.key-a", "val-a-changed"))
+       assert.True(t, gitConfigContains("key-a = val-a-changed"))
+
+       assert.NoError(t, configAddNonExist("test.key-b", "val-b"))
+       assert.True(t, gitConfigContains("key-b = val-b"))
+
+       assert.NoError(t, configAddNonExist("test.key-b", "val-2b"))
+       assert.True(t, gitConfigContains("key-b = val-b"))
+       assert.True(t, gitConfigContains("key-b = val-2b"))
+
+       assert.NoError(t, configUnsetAll("test.key-b", "val-b"))
+       assert.False(t, gitConfigContains("key-b = val-b"))
+       assert.True(t, gitConfigContains("key-b = val-2b"))
+
+       assert.NoError(t, configUnsetAll("test.key-b", "val-2b"))
+       assert.False(t, gitConfigContains("key-b = val-2b"))
+
+       assert.NoError(t, configSet("test.key-x", "*"))
+       assert.True(t, gitConfigContains("key-x = *"))
+       assert.NoError(t, configSetNonExist("test.key-x", "*"))
+       assert.NoError(t, configUnsetAll("test.key-x", "*"))
+       assert.False(t, gitConfigContains("key-x = *"))
+}
+
+func TestSyncConfig(t *testing.T) {
+       oldGitConfig := setting.GitConfig
+       defer func() {
+               setting.GitConfig = oldGitConfig
+       }()
+
+       setting.GitConfig.Options["sync-test.cfg-key-a"] = "CfgValA"
+       assert.NoError(t, syncGitConfig())
+       assert.True(t, gitConfigContains("[sync-test]"))
+       assert.True(t, gitConfigContains("cfg-key-a = CfgValA"))
+}
diff --git a/modules/git/fsck.go b/modules/git/fsck.go
new file mode 100644 (file)
index 0000000..cec27f1
--- /dev/null
@@ -0,0 +1,14 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package git
+
+import (
+       "context"
+       "time"
+)
+
+// Fsck verifies the connectivity and validity of the objects in the database
+func Fsck(ctx context.Context, repoPath string, timeout time.Duration, args TrustedCmdArgs) error {
+       return NewCommand(ctx, "fsck").AddArguments(args...).Run(&RunOpts{Timeout: timeout, Dir: repoPath})
+}
index a19dd7771ba0fd8198744cb3de452cace0396a25..e3e5b832747cdaa3f89be295bab38adca85a872d 100644 (file)
@@ -11,7 +11,6 @@ import (
        "os"
        "os/exec"
        "path/filepath"
-       "regexp"
        "runtime"
        "strings"
        "time"
@@ -95,17 +94,18 @@ func parseGitVersionLine(s string) (*version.Version, error) {
        return version.NewVersion(versionString)
 }
 
-// SetExecutablePath changes the path of git executable and checks the file permission and version.
-func SetExecutablePath(path string) error {
-       // If path is empty, we use the default value of GitExecutable "git" to search for the location of git.
-       if path != "" {
-               GitExecutable = path
+func checkGitVersionCompatibility(gitVer *version.Version) error {
+       badVersions := []struct {
+               Version *version.Version
+               Reason  string
+       }{
+               {version.Must(version.NewVersion("2.43.1")), "regression bug of GIT_FLUSH"},
        }
-       absPath, err := exec.LookPath(GitExecutable)
-       if err != nil {
-               return fmt.Errorf("git not found: %w", err)
+       for _, bad := range badVersions {
+               if gitVer.Equal(bad.Version) {
+                       return errors.New(bad.Reason)
+               }
        }
-       GitExecutable = absPath
        return nil
 }
 
@@ -128,6 +128,20 @@ func ensureGitVersion() error {
        return nil
 }
 
+// SetExecutablePath changes the path of git executable and checks the file permission and version.
+func SetExecutablePath(path string) error {
+       // If path is empty, we use the default value of GitExecutable "git" to search for the location of git.
+       if path != "" {
+               GitExecutable = path
+       }
+       absPath, err := exec.LookPath(GitExecutable)
+       if err != nil {
+               return fmt.Errorf("git not found: %w", err)
+       }
+       GitExecutable = absPath
+       return nil
+}
+
 // HomeDir is the home dir for git to store the global config file used by Gitea internally
 func HomeDir() string {
        if setting.Git.HomePath == "" {
@@ -204,196 +218,3 @@ func InitFull(ctx context.Context) (err error) {
 
        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 prepare git home directory %s, err: %w", HomeDir(), err)
-       }
-
-       // first, write user's git config options to git config file
-       // user config options could be overwritten by builtin values later, because if a value is builtin, it must have some special purposes
-       for k, v := range setting.GitConfig.Options {
-               if err = configSet(strings.ToLower(k), v); err != nil {
-                       return 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"
-       // 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.
-       for configKey, defaultValue := range map[string]string{
-               "user.name":  "Gitea",
-               "user.email": "gitea@fake.local",
-       } {
-               if err := configSetNonExist(configKey, defaultValue); err != nil {
-                       return err
-               }
-       }
-
-       // Set git some configurations - these must be set to these values for gitea to work correctly
-       if err := configSet("core.quotePath", "false"); err != nil {
-               return err
-       }
-
-       if DefaultFeatures().CheckVersionAtLeast("2.10") {
-               if err := configSet("receive.advertisePushOptions", "true"); err != nil {
-                       return err
-               }
-       }
-
-       if DefaultFeatures().CheckVersionAtLeast("2.18") {
-               if err := configSet("core.commitGraph", "true"); err != nil {
-                       return err
-               }
-               if err := configSet("gc.writeCommitGraph", "true"); err != nil {
-                       return err
-               }
-               if err := configSet("fetch.writeCommitGraph", "true"); err != nil {
-                       return err
-               }
-       }
-
-       if DefaultFeatures().SupportProcReceive {
-               // set support for AGit flow
-               if err := configAddNonExist("receive.procReceiveRefs", "refs/for"); err != nil {
-                       return err
-               }
-       } else {
-               if err := configUnsetAll("receive.procReceiveRefs", "refs/for"); err != nil {
-                       return err
-               }
-       }
-
-       // Due to CVE-2022-24765, git now denies access to git directories which are not owned by current user.
-       // However, some docker users and samba users find it difficult to configure their systems correctly,
-       // so that Gitea's git repositories are owned by the Gitea user.
-       // (Possibly Windows Service users - but ownership in this case should really be set correctly on the filesystem.)
-       // See issue: https://github.com/go-gitea/gitea/issues/19455
-       // As Gitea now always use its internal git config file, and access to the git repositories is managed through Gitea,
-       // it is now safe to set "safe.directory=*" for internal usage only.
-       // Although this setting is only supported by some new git versions, it is also tolerated by earlier versions
-       if err := configAddNonExist("safe.directory", "*"); err != nil {
-               return err
-       }
-
-       if runtime.GOOS == "windows" {
-               if err := configSet("core.longpaths", "true"); err != nil {
-                       return err
-               }
-               if setting.Git.DisableCoreProtectNTFS {
-                       err = configSet("core.protectNTFS", "false")
-               } else {
-                       err = configUnsetAll("core.protectNTFS", "false")
-               }
-               if err != nil {
-                       return err
-               }
-       }
-
-       // By default partial clones are disabled, enable them from git v2.22
-       if !setting.Git.DisablePartialClone && DefaultFeatures().CheckVersionAtLeast("2.22") {
-               if err = configSet("uploadpack.allowfilter", "true"); err != nil {
-                       return err
-               }
-               err = configSet("uploadpack.allowAnySHA1InWant", "true")
-       } else {
-               if err = configUnsetAll("uploadpack.allowfilter", "true"); err != nil {
-                       return err
-               }
-               err = configUnsetAll("uploadpack.allowAnySHA1InWant", "true")
-       }
-
-       return err
-}
-
-func checkGitVersionCompatibility(gitVer *version.Version) error {
-       badVersions := []struct {
-               Version *version.Version
-               Reason  string
-       }{
-               {version.Must(version.NewVersion("2.43.1")), "regression bug of GIT_FLUSH"},
-       }
-       for _, bad := range badVersions {
-               if gitVer.Equal(bad.Version) {
-                       return errors.New(bad.Reason)
-               }
-       }
-       return nil
-}
-
-func configSet(key, value string) error {
-       stdout, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
-       if err != nil && !IsErrorExitCode(err, 1) {
-               return fmt.Errorf("failed to get git config %s, err: %w", key, err)
-       }
-
-       currValue := strings.TrimSpace(stdout)
-       if currValue == value {
-               return nil
-       }
-
-       _, _, err = NewCommand(DefaultContext, "config", "--global").AddDynamicArguments(key, value).RunStdString(nil)
-       if err != nil {
-               return fmt.Errorf("failed to set git global config %s, err: %w", key, err)
-       }
-
-       return nil
-}
-
-func configSetNonExist(key, value string) error {
-       _, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
-       if err == nil {
-               // already exist
-               return nil
-       }
-       if IsErrorExitCode(err, 1) {
-               // not exist, set new config
-               _, _, err = NewCommand(DefaultContext, "config", "--global").AddDynamicArguments(key, value).RunStdString(nil)
-               if err != nil {
-                       return fmt.Errorf("failed to set git global config %s, err: %w", key, err)
-               }
-               return nil
-       }
-
-       return fmt.Errorf("failed to get git config %s, err: %w", key, err)
-}
-
-func configAddNonExist(key, value string) error {
-       _, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(nil)
-       if err == nil {
-               // already exist
-               return nil
-       }
-       if IsErrorExitCode(err, 1) {
-               // not exist, add new config
-               _, _, err = NewCommand(DefaultContext, "config", "--global", "--add").AddDynamicArguments(key, value).RunStdString(nil)
-               if err != nil {
-                       return fmt.Errorf("failed to add git global config %s, err: %w", key, err)
-               }
-               return nil
-       }
-       return fmt.Errorf("failed to get git config %s, err: %w", key, err)
-}
-
-func configUnsetAll(key, value string) error {
-       _, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
-       if err == nil {
-               // exist, need to remove
-               _, _, err = NewCommand(DefaultContext, "config", "--global", "--unset-all").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(nil)
-               if err != nil {
-                       return fmt.Errorf("failed to unset git global config %s, err: %w", key, err)
-               }
-               return nil
-       }
-       if IsErrorExitCode(err, 1) {
-               // not exist
-               return nil
-       }
-       return fmt.Errorf("failed to get git config %s, err: %w", key, err)
-}
-
-// Fsck verifies the connectivity and validity of the objects in the database
-func Fsck(ctx context.Context, repoPath string, timeout time.Duration, args TrustedCmdArgs) error {
-       return NewCommand(ctx, "fsck").AddArguments(args...).Run(&RunOpts{Timeout: timeout, Dir: repoPath})
-}
index fc92bebe04755196c10413c795bcc344dcf857aa..5472842b76e58fbcfdef72a828ae9c2dffa100bf 100644 (file)
@@ -7,7 +7,6 @@ import (
        "context"
        "fmt"
        "os"
-       "strings"
        "testing"
 
        "code.gitea.io/gitea/modules/setting"
@@ -43,58 +42,6 @@ func TestMain(m *testing.M) {
        }
 }
 
-func gitConfigContains(sub string) bool {
-       if b, err := os.ReadFile(HomeDir() + "/.gitconfig"); err == nil {
-               return strings.Contains(string(b), sub)
-       }
-       return false
-}
-
-func TestGitConfig(t *testing.T) {
-       assert.False(t, gitConfigContains("key-a"))
-
-       assert.NoError(t, configSetNonExist("test.key-a", "val-a"))
-       assert.True(t, gitConfigContains("key-a = val-a"))
-
-       assert.NoError(t, configSetNonExist("test.key-a", "val-a-changed"))
-       assert.False(t, gitConfigContains("key-a = val-a-changed"))
-
-       assert.NoError(t, configSet("test.key-a", "val-a-changed"))
-       assert.True(t, gitConfigContains("key-a = val-a-changed"))
-
-       assert.NoError(t, configAddNonExist("test.key-b", "val-b"))
-       assert.True(t, gitConfigContains("key-b = val-b"))
-
-       assert.NoError(t, configAddNonExist("test.key-b", "val-2b"))
-       assert.True(t, gitConfigContains("key-b = val-b"))
-       assert.True(t, gitConfigContains("key-b = val-2b"))
-
-       assert.NoError(t, configUnsetAll("test.key-b", "val-b"))
-       assert.False(t, gitConfigContains("key-b = val-b"))
-       assert.True(t, gitConfigContains("key-b = val-2b"))
-
-       assert.NoError(t, configUnsetAll("test.key-b", "val-2b"))
-       assert.False(t, gitConfigContains("key-b = val-2b"))
-
-       assert.NoError(t, configSet("test.key-x", "*"))
-       assert.True(t, gitConfigContains("key-x = *"))
-       assert.NoError(t, configSetNonExist("test.key-x", "*"))
-       assert.NoError(t, configUnsetAll("test.key-x", "*"))
-       assert.False(t, gitConfigContains("key-x = *"))
-}
-
-func TestSyncConfig(t *testing.T) {
-       oldGitConfig := setting.GitConfig
-       defer func() {
-               setting.GitConfig = oldGitConfig
-       }()
-
-       setting.GitConfig.Options["sync-test.cfg-key-a"] = "CfgValA"
-       assert.NoError(t, syncGitConfig())
-       assert.True(t, gitConfigContains("[sync-test]"))
-       assert.True(t, gitConfigContains("cfg-key-a = CfgValA"))
-}
-
 func TestParseGitVersion(t *testing.T) {
        v, err := parseGitVersionLine("git version 2.29.3")
        assert.NoError(t, err)
index a1127f4e6c5b9cf47d8302942f3c93af60b685da..0ca1ea79c21aaf263dfc77fede615001dbe12914 100644 (file)
@@ -28,7 +28,7 @@ const isGogit = true
 type Repository struct {
        Path string
 
-       tagCache *ObjectCache
+       tagCache *ObjectCache[*Tag]
 
        gogitRepo    *gogit.Repository
        gogitStorage *filesystem.Storage
@@ -79,7 +79,7 @@ func OpenRepository(ctx context.Context, repoPath string) (*Repository, error) {
                Path:         repoPath,
                gogitRepo:    gogitRepo,
                gogitStorage: storage,
-               tagCache:     newObjectCache(),
+               tagCache:     newObjectCache[*Tag](),
                Ctx:          ctx,
                objectFormat: ParseGogitHash(plumbing.ZeroHash).Type(),
        }, nil
index 3eb2e2ee6b2f0dd1dbe406f880740ec836ca62a1..477e3b8742976a833fdad1a0a6b17a95c679e49d 100644 (file)
@@ -21,7 +21,7 @@ const isGogit = false
 type Repository struct {
        Path string
 
-       tagCache *ObjectCache
+       tagCache *ObjectCache[*Tag]
 
        gpgSettings *GPGSettings
 
@@ -53,7 +53,7 @@ func OpenRepository(ctx context.Context, repoPath string) (*Repository, error) {
 
        return &Repository{
                Path:     repoPath,
-               tagCache: newObjectCache(),
+               tagCache: newObjectCache[*Tag](),
                Ctx:      ctx,
        }, nil
 }
index 4a7a06e9bdaf1c450d8277a7c008b5af472d5d3d..3e1b4e89ad6c7884e2dc205e897c99c1baac77f9 100644 (file)
@@ -72,7 +72,7 @@ func (repo *Repository) getTag(tagID ObjectID, name string) (*Tag, error) {
        t, ok := repo.tagCache.Get(tagID.String())
        if ok {
                log.Debug("Hit cache: %s", tagID)
-               tagClone := *t.(*Tag)
+               tagClone := *t
                tagClone.Name = name // This is necessary because lightweight tags may have same id
                return &tagClone, nil
        }
index 8b06a6a1c3c4e7a85d1b5ff923c0bbe5ec294ca4..e0a3104249ebc1924a73d0acefea1dfc3b274ae2 100644 (file)
@@ -51,7 +51,7 @@ func (repo *Repository) getTag(tagID ObjectID, name string) (*Tag, error) {
        t, ok := repo.tagCache.Get(tagID.String())
        if ok {
                log.Debug("Hit cache: %s", tagID)
-               tagClone := *t.(*Tag)
+               tagClone := *t
                tagClone.Name = name // This is necessary because lightweight tags may have same id
                return &tagClone, nil
        }
diff --git a/modules/git/submodule.go b/modules/git/submodule.go
deleted file mode 100644 (file)
index b99c815..0000000
+++ /dev/null
@@ -1,119 +0,0 @@
-// Copyright 2019 The Gitea Authors. All rights reserved.
-// Copyright 2015 The Gogs Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package git
-
-import (
-       "fmt"
-       "net"
-       "net/url"
-       "path"
-       "regexp"
-       "strings"
-)
-
-var scpSyntax = regexp.MustCompile(`^([a-zA-Z0-9_]+@)?([a-zA-Z0-9._-]+):(.*)$`)
-
-// SubModule submodule is a reference on git repository
-type SubModule struct {
-       Name string
-       URL  string
-}
-
-// SubModuleFile represents a file with submodule type.
-type SubModuleFile struct {
-       *Commit
-
-       refURL string
-       refID  string
-}
-
-// NewSubModuleFile create a new submodule file
-func NewSubModuleFile(c *Commit, refURL, refID string) *SubModuleFile {
-       return &SubModuleFile{
-               Commit: c,
-               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: git@try.gitea.io: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 ""
-       }
-
-       refHostname, _, err := net.SplitHostPort(ref.Host)
-       if err != nil {
-               refHostname = ref.Host
-       }
-
-       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
-               }
-       }
-
-       return ""
-}
-
-// RefURL guesses and returns reference URL.
-func (sf *SubModuleFile) RefURL(urlPrefix, repoFullName, sshDomain string) string {
-       return getRefURL(sf.refURL, urlPrefix, repoFullName, sshDomain)
-}
-
-// RefID returns reference ID.
-func (sf *SubModuleFile) RefID() string {
-       return sf.refID
-}
diff --git a/modules/git/submodule_test.go b/modules/git/submodule_test.go
deleted file mode 100644 (file)
index e05f251..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2018 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package git
-
-import (
-       "testing"
-
-       "github.com/stretchr/testify/assert"
-)
-
-func TestGetRefURL(t *testing.T) {
-       kases := []struct {
-               refURL     string
-               prefixURL  string
-               parentPath string
-               SSHDomain  string
-               expect     string
-       }{
-               {"git://github.com/user1/repo1", "/", "user1/repo2", "", "http://github.com/user1/repo1"},
-               {"https://localhost/user1/repo1.git", "/", "user1/repo2", "", "https://localhost/user1/repo1"},
-               {"http://localhost/user1/repo1.git", "/", "owner/reponame", "", "http://localhost/user1/repo1"},
-               {"git@github.com:user1/repo1.git", "/", "owner/reponame", "", "http://github.com/user1/repo1"},
-               {"ssh://git@git.zefie.net:2222/zefie/lge_g6_kernel_scripts.git", "/", "zefie/lge_g6_kernel", "", "http://git.zefie.net/zefie/lge_g6_kernel_scripts"},
-               {"git@git.zefie.net:2222/zefie/lge_g6_kernel_scripts.git", "/", "zefie/lge_g6_kernel", "", "http://git.zefie.net/2222/zefie/lge_g6_kernel_scripts"},
-               {"git@try.gitea.io:go-gitea/gitea", "https://try.gitea.io/", "go-gitea/sdk", "", "https://try.gitea.io/go-gitea/gitea"},
-               {"ssh://git@try.gitea.io:9999/go-gitea/gitea", "https://try.gitea.io/", "go-gitea/sdk", "", "https://try.gitea.io/go-gitea/gitea"},
-               {"git://git@try.gitea.io:9999/go-gitea/gitea", "https://try.gitea.io/", "go-gitea/sdk", "", "https://try.gitea.io/go-gitea/gitea"},
-               {"ssh://git@127.0.0.1:9999/go-gitea/gitea", "https://127.0.0.1:3000/", "go-gitea/sdk", "", "https://127.0.0.1:3000/go-gitea/gitea"},
-               {"https://gitea.com:3000/user1/repo1.git", "https://127.0.0.1:3000/", "user/repo2", "", "https://gitea.com:3000/user1/repo1"},
-               {"https://example.gitea.com/gitea/user1/repo1.git", "https://example.gitea.com/gitea/", "", "user/repo2", "https://example.gitea.com/gitea/user1/repo1"},
-               {"https://username:password@github.com/username/repository.git", "/", "username/repository2", "", "https://username:password@github.com/username/repository"},
-               {"somethingbad", "https://127.0.0.1:3000/go-gitea/gitea", "/", "", ""},
-               {"git@localhost:user/repo", "https://localhost/", "user2/repo1", "", "https://localhost/user/repo"},
-               {"../path/to/repo.git/", "https://localhost/", "user/repo2", "", "https://localhost/user/path/to/repo.git"},
-               {"ssh://git@ssh.gitea.io:2222/go-gitea/gitea", "https://try.gitea.io/", "go-gitea/sdk", "ssh.gitea.io", "https://try.gitea.io/go-gitea/gitea"},
-       }
-
-       for _, kase := range kases {
-               assert.EqualValues(t, kase.expect, getRefURL(kase.refURL, kase.prefixURL, kase.parentPath, kase.SSHDomain))
-       }
-}
index 53211c64518667f87932938b7cc21804b2cfeef2..56cba9087a35aed69bc3a8637c2eedd5def72dbd 100644 (file)
@@ -15,27 +15,25 @@ import (
 )
 
 // ObjectCache provides thread-safe cache operations.
-type ObjectCache struct {
+type ObjectCache[T any] struct {
        lock  sync.RWMutex
-       cache map[string]any
+       cache map[string]T
 }
 
-func newObjectCache() *ObjectCache {
-       return &ObjectCache{
-               cache: make(map[string]any, 10),
-       }
+func newObjectCache[T any]() *ObjectCache[T] {
+       return &ObjectCache[T]{cache: make(map[string]T, 10)}
 }
 
-// Set add obj to cache
-func (oc *ObjectCache) Set(id string, obj any) {
+// Set adds obj to cache
+func (oc *ObjectCache[T]) Set(id string, obj T) {
        oc.lock.Lock()
        defer oc.lock.Unlock()
 
        oc.cache[id] = obj
 }
 
-// Get get cached obj by id
-func (oc *ObjectCache) Get(id string) (any, bool) {
+// Get gets cached obj by id
+func (oc *ObjectCache[T]) Get(id string) (T, bool) {
        oc.lock.RLock()
        defer oc.lock.RUnlock()