mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-27 00:23:41 +09:00 
			
		
		
		
	Support changing git config through app.ini, use diff.algorithm=histogram by default (#24860)
				
					
				
			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>
This commit is contained in:
		| @@ -682,6 +682,28 @@ LEVEL = Info | |||||||
| ;; Disable the usage of using partial clones for git. | ;; Disable the usage of using partial clones for git. | ||||||
| ;DISABLE_PARTIAL_CLONE = false | ;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] | [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.) | ;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 | ;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] | ;[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_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. | - `DISABLE_PARTIAL_CLONE`: **false** Disable the usage of using partial clones for git. | ||||||
|  |  | ||||||
| ## Git - Reflog settings (`git.reflog`) | ### Git - Timeout settings (`git.timeout`) | ||||||
|  |  | ||||||
| - `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`) |  | ||||||
|  |  | ||||||
| - `DEFAULT`: **360**: Git operations default timeout seconds. | - `DEFAULT`: **360**: Git operations default timeout seconds. | ||||||
| - `MIGRATE`: **600**: Migrate external repositories 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. | - `PULL`: **300**: Git pull from internal repositories timeout seconds. | ||||||
| - `GC`: **60**: Git repository GC 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`) | ## Metrics (`metrics`) | ||||||
|  |  | ||||||
| - `ENABLED`: **false**: Enables /metrics endpoint for prometheus. | - `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` | 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 | ### 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`: | 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) | 		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" | 	// 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. | 	// 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. | 	// 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) { | func gitConfigContains(sub string) bool { | ||||||
| 	gitConfigContains := func(sub string) bool { | 	if b, err := os.ReadFile(HomeDir() + "/.gitconfig"); err == nil { | ||||||
| 		if b, err := os.ReadFile(HomeDir() + "/.gitconfig"); err == nil { | 		return strings.Contains(string(b), sub) | ||||||
| 			return strings.Contains(string(b), sub) |  | ||||||
| 		} |  | ||||||
| 		return false |  | ||||||
| 	} | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestGitConfig(t *testing.T) { | ||||||
| 	assert.False(t, gitConfigContains("key-a")) | 	assert.False(t, gitConfigContains("key-a")) | ||||||
|  |  | ||||||
| 	assert.NoError(t, configSetNonExist("test.key-a", "val-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.NoError(t, configUnsetAll("test.key-x", "*")) | ||||||
| 	assert.False(t, gitConfigContains("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 ( | import ( | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
|  | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"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) { | func loadGitFrom(rootCfg ConfigProvider) { | ||||||
| 	sec := rootCfg.Section("git") | 	sec := rootCfg.Section("git") | ||||||
| 	if err := sec.MapTo(&Git); err != nil { | 	if err := sec.MapTo(&Git); err != nil { | ||||||
| 		log.Fatal("Failed to map Git settings: %v", err) | 		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") | 	Git.HomePath = sec.Key("HOME_PATH").MustString("home") | ||||||
| 	if !filepath.IsAbs(Git.HomePath) { | 	if !filepath.IsAbs(Git.HomePath) { | ||||||
| 		Git.HomePath = filepath.Join(AppDataPath, Git.HomePath) | 		Git.HomePath = filepath.Join(AppDataPath, Git.HomePath) | ||||||
|   | |||||||
							
								
								
									
										40
									
								
								modules/setting/git_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								modules/setting/git_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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"]) | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user