Close #13454 , Close #23255, Close #14697 (and maybe more related issues) Many users have the requirement to customize the git config. This PR introduces an easy way: put the options in Gitea's app.ini `[git.config]`, then the config options will be applied to git config. And it can support more flexible default config values, eg: now `diff.algorithm=histogram` by default. According to: https://stackoverflow.com/a/32367597/4754037 , `histogram diff` is efficient and doesn't like to cause server-side problems. --------- Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: KN4CK3R <admin@oldschoolhack.me> Co-authored-by: Giteabot <teabot@gitea.io>tags/v1.20.0-rc0
@@ -682,6 +682,28 @@ LEVEL = Info | |||
;; Disable the usage of using partial clones for git. | |||
;DISABLE_PARTIAL_CLONE = false | |||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |||
;; Git Operation timeout in seconds | |||
;[git.timeout] | |||
;DEFAULT = 360 | |||
;MIGRATE = 600 | |||
;MIRROR = 300 | |||
;CLONE = 300 | |||
;PULL = 300 | |||
;GC = 60 | |||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |||
;; Git Reflog timeout in days | |||
;[git.reflog] | |||
;ENABLED = true | |||
;EXPIRATION = 90 | |||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |||
;; Git config options | |||
;; This section only does "set" config, a removed config key from this section won't be removed from git config automatically. The format is `some.configKey = value`. | |||
;[git.config] | |||
;diff.algorithm = histogram | |||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |||
[service] | |||
@@ -2176,32 +2198,6 @@ LEVEL = Info | |||
;Check at least this proportion of LFSMetaObjects per repo. (This may cause all stale LFSMetaObjects to be checked.) | |||
;PROPORTION_TO_CHECK_PER_REPO = 0.6 | |||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |||
;; Git Operation timeout in seconds | |||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |||
;[git.timeout] | |||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |||
;DEFAULT = 360 | |||
;MIGRATE = 600 | |||
;MIRROR = 300 | |||
;CLONE = 300 | |||
;PULL = 300 | |||
;GC = 60 | |||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |||
;; Git Reflog timeout in days | |||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |||
;[git.reflog] | |||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |||
;ENABLED = true | |||
;EXPIRATION = 90 | |||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |||
;[mirror] |
@@ -1054,12 +1054,7 @@ Default templates for project boards: | |||
- `DISABLE_CORE_PROTECT_NTFS`: **false** Set to true to forcibly set `core.protectNTFS` to false. | |||
- `DISABLE_PARTIAL_CLONE`: **false** Disable the usage of using partial clones for git. | |||
## Git - Reflog settings (`git.reflog`) | |||
- `ENABLED`: **true** Set to true to enable Git to write changes to reflogs in each repo. | |||
- `EXPIRATION`: **90** Reflog entry lifetime, in days. Entries are removed opportunistically by Git. | |||
## Git - Timeout settings (`git.timeout`) | |||
### Git - Timeout settings (`git.timeout`) | |||
- `DEFAULT`: **360**: Git operations default timeout seconds. | |||
- `MIGRATE`: **600**: Migrate external repositories timeout seconds. | |||
@@ -1068,6 +1063,18 @@ Default templates for project boards: | |||
- `PULL`: **300**: Git pull from internal repositories timeout seconds. | |||
- `GC`: **60**: Git repository GC timeout seconds. | |||
### Git - Reflog settings (`git.reflog`) | |||
- `ENABLED`: **true** Set to true to enable Git to write changes to reflogs in each repo. | |||
- `EXPIRATION`: **90** Reflog entry lifetime, in days. Entries are removed opportunistically by Git. | |||
### Git - Config options (`git.config`) | |||
The key/value pairs in this section will be used as git config. | |||
This section only does "set" config, a removed config key from this section won't be removed from git config automatically. The format is `some.configKey = value`. | |||
- `diff.algorithm`: **histogram** | |||
## Metrics (`metrics`) | |||
- `ENABLED`: **false**: Enables /metrics endpoint for prometheus. |
@@ -282,6 +282,22 @@ Place custom files in corresponding sub-folder under `custom/options`. | |||
To add custom .gitignore, add a file with existing [.gitignore rules](https://git-scm.com/docs/gitignore) in it to `$GITEA_CUSTOM/options/gitignore` | |||
## Customizing the git configuration | |||
Starting with Gitea 1.20, you can customize the git configuration via the `git.config` section. | |||
### Enabling signed git pushes | |||
To enable signed git pushes, set these two options: | |||
```ini | |||
[git.config] | |||
receive.advertisePushOptions = true | |||
receive.certNonceSeed = <randomstring> | |||
``` | |||
`certNonceSeed` should be set to a random string and be kept secret. | |||
### Labels | |||
Starting with Gitea 1.19, you can add a file that follows the [YAML label format](https://github.com/go-gitea/gitea/blob/main/options/label/Advanced.yaml) to `$GITEA_CUSTOM/options/label`: |
@@ -224,6 +224,14 @@ func syncGitConfig() (err error) { | |||
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. |
@@ -42,14 +42,14 @@ func TestMain(m *testing.M) { | |||
} | |||
} | |||
func TestGitConfig(t *testing.T) { | |||
gitConfigContains := func(sub string) bool { | |||
if b, err := os.ReadFile(HomeDir() + "/.gitconfig"); err == nil { | |||
return strings.Contains(string(b), sub) | |||
} | |||
return false | |||
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")) | |||
@@ -81,3 +81,15 @@ func TestGitConfig(t *testing.T) { | |||
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")) | |||
} |
@@ -5,6 +5,7 @@ package setting | |||
import ( | |||
"path/filepath" | |||
"strings" | |||
"time" | |||
"code.gitea.io/gitea/modules/log" | |||
@@ -78,12 +79,28 @@ var Git = struct { | |||
}, | |||
} | |||
var GitConfig = struct { | |||
Options map[string]string | |||
}{ | |||
Options: make(map[string]string), | |||
} | |||
func loadGitFrom(rootCfg ConfigProvider) { | |||
sec := rootCfg.Section("git") | |||
if err := sec.MapTo(&Git); err != nil { | |||
log.Fatal("Failed to map Git settings: %v", err) | |||
} | |||
secGitConfig := rootCfg.Section("git.config") | |||
GitConfig.Options = make(map[string]string) | |||
for _, key := range secGitConfig.Keys() { | |||
// git config key is case-insensitive, so always use lower-case | |||
GitConfig.Options[strings.ToLower(key.Name())] = key.String() | |||
} | |||
if _, ok := GitConfig.Options["diff.algorithm"]; !ok { | |||
GitConfig.Options["diff.algorithm"] = "histogram" | |||
} | |||
Git.HomePath = sec.Key("HOME_PATH").MustString("home") | |||
if !filepath.IsAbs(Git.HomePath) { | |||
Git.HomePath = filepath.Join(AppDataPath, Git.HomePath) |
@@ -0,0 +1,40 @@ | |||
// Copyright 2019 The Gitea Authors. All rights reserved. | |||
// SPDX-License-Identifier: MIT | |||
package setting | |||
import ( | |||
"testing" | |||
"github.com/stretchr/testify/assert" | |||
) | |||
func TestGitConfig(t *testing.T) { | |||
oldGit := Git | |||
oldGitConfig := GitConfig | |||
defer func() { | |||
Git = oldGit | |||
GitConfig = oldGitConfig | |||
}() | |||
cfg, err := NewConfigProviderFromData(` | |||
[git.config] | |||
a.b = 1 | |||
`) | |||
assert.NoError(t, err) | |||
loadGitFrom(cfg) | |||
assert.Len(t, GitConfig.Options, 2) | |||
assert.EqualValues(t, "1", GitConfig.Options["a.b"]) | |||
assert.EqualValues(t, "histogram", GitConfig.Options["diff.algorithm"]) | |||
cfg, err = NewConfigProviderFromData(` | |||
[git.config] | |||
diff.algorithm = other | |||
`) | |||
assert.NoError(t, err) | |||
loadGitFrom(cfg) | |||
assert.Len(t, GitConfig.Options, 1) | |||
assert.EqualValues(t, "other", GitConfig.Options["diff.algorithm"]) | |||
} |