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