mirror of https://github.com/go-gitea/gitea
Fix submodule parsing (#32571)
Fix #32568, parse `.gitmodules` correctly --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>pull/32576/head^2
parent
407b6e6dfc
commit
33850a83fe
@ -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 |
||||
} |
@ -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) |
||||
} |
@ -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 |
||||
} |
@ -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) |
||||
} |
@ -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")) |
||||
} |
@ -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}) |
||||
} |
Loading…
Reference in new issue