"bytes"
"context"
"errors"
- "fmt"
"io"
"os/exec"
"strconv"
Signature *CommitSignature
Parents []ObjectID // ID strings
- submoduleCache *ObjectCache
+ submoduleCache *ObjectCache[*SubModule]
}
// CommitSignature represents a git commit signature part.
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")
type CommitInfo struct {
Entry *TreeEntry
Commit *Commit
- SubModuleFile *SubModuleFile
+ SubModuleFile *CommitSubModuleFile
}
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
} 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
}
}
} 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
}
}
--- /dev/null
+// 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
+}
--- /dev/null
+// 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
+}
--- /dev/null
+// 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))
+ }
+}
committer KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100
encoding ISO-8859-1
gpgsig -----BEGIN PGP SIGNATURE-----
-
+<SPACE>
iQGzBAABCgAdFiEE9HRrbqvYxPT8PXbefPSEkrowAa8FAmYGg7IACgkQfPSEkrow
Aa9olwv+P0HhtCM6CRvlUmPaqswRsDPNR4i66xyXGiSxdI9V5oJL7HLiQIM7KrFR
gizKa2COiGtugv8fE+TKqXKaJx6uJUJEjaBd8E9Af9PrAzjWj+A84lU6/PgPS8hq
-----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)
--- /dev/null
+// 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)
+}
--- /dev/null
+// 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
+}
--- /dev/null
+// 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)
+}
--- /dev/null
+// 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"))
+}
--- /dev/null
+// 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})
+}
"os"
"os/exec"
"path/filepath"
- "regexp"
"runtime"
"strings"
"time"
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
}
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 == "" {
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})
-}
"context"
"fmt"
"os"
- "strings"
"testing"
"code.gitea.io/gitea/modules/setting"
}
}
-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)
type Repository struct {
Path string
- tagCache *ObjectCache
+ tagCache *ObjectCache[*Tag]
gogitRepo *gogit.Repository
gogitStorage *filesystem.Storage
Path: repoPath,
gogitRepo: gogitRepo,
gogitStorage: storage,
- tagCache: newObjectCache(),
+ tagCache: newObjectCache[*Tag](),
Ctx: ctx,
objectFormat: ParseGogitHash(plumbing.ZeroHash).Type(),
}, nil
type Repository struct {
Path string
- tagCache *ObjectCache
+ tagCache *ObjectCache[*Tag]
gpgSettings *GPGSettings
return &Repository{
Path: repoPath,
- tagCache: newObjectCache(),
+ tagCache: newObjectCache[*Tag](),
Ctx: ctx,
}, nil
}
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
}
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
}
+++ /dev/null
-// 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
-}
+++ /dev/null
-// 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))
- }
-}
)
// 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()