@@ -22,42 +22,65 @@ import (
22
22
"github.com/hashicorp/go-version"
23
23
)
24
24
25
- // RequiredVersion is the minimum Git version required
26
- const RequiredVersion = "2.0.0"
25
+ const RequiredVersion = "2.0.0" // the minimum Git version required
26
+
27
+ type Features struct {
28
+ gitVersion * version.Version
29
+
30
+ UsingGogit bool
31
+ SupportProcReceive bool // >= 2.29
32
+ SupportHashSha256 bool // >= 2.42, SHA-256 repositories no longer an ‘experimental curiosity’
33
+ SupportedObjectFormats []ObjectFormat // sha1, sha256
34
+ }
27
35
28
36
var (
29
- // GitExecutable is the command name of git
30
- // Could be updated to an absolute path while initialization
31
- GitExecutable = "git"
37
+ GitExecutable = "git" // the command name of git, will be updated to an absolute path during initialization
38
+ DefaultContext context.Context // the default context to run git commands in, must be initialized by git.InitXxx
39
+ defaultFeatures * Features
40
+ )
32
41
33
- // DefaultContext is the default context to run git commands in, must be initialized by git.InitXxx
34
- DefaultContext context.Context
42
+ func (f * Features ) CheckVersionAtLeast (atLeast string ) bool {
43
+ return f .gitVersion .Compare (version .Must (version .NewVersion (atLeast ))) >= 0
44
+ }
35
45
36
- DefaultFeatures struct {
37
- GitVersion * version.Version
46
+ // VersionInfo returns git version information
47
+ func (f * Features ) VersionInfo () string {
48
+ return f .gitVersion .Original ()
49
+ }
38
50
39
- SupportProcReceive bool // >= 2.29
40
- SupportHashSha256 bool // >= 2.42, SHA-256 repositories no longer an ‘experimental curiosity’
51
+ func DefaultFeatures () * Features {
52
+ if defaultFeatures == nil {
53
+ if ! setting .IsProd || setting .IsInTesting {
54
+ log .Warn ("git.DefaultFeatures is called before git.InitXxx, initializing with default values" )
55
+ }
56
+ if err := InitSimple (context .Background ()); err != nil {
57
+ log .Fatal ("git.InitSimple failed: %v" , err )
58
+ }
41
59
}
42
- )
60
+ return defaultFeatures
61
+ }
43
62
44
63
// loadGitVersion tries to get the current git version and stores it into a global variable
45
- func loadGitVersion () error {
46
- // doesn't need RWMutex because it's executed by Init()
47
- if DefaultFeatures .GitVersion != nil {
48
- return nil
49
- }
50
-
64
+ func loadGitVersionFeatures () (* Features , error ) {
51
65
stdout , _ , runErr := NewCommand (DefaultContext , "version" ).RunStdString (nil )
52
66
if runErr != nil {
53
- return runErr
67
+ return nil , runErr
54
68
}
55
69
56
70
ver , err := parseGitVersionLine (strings .TrimSpace (stdout ))
57
- if err = = nil {
58
- DefaultFeatures . GitVersion = ver
71
+ if err ! = nil {
72
+ return nil , err
59
73
}
60
- return err
74
+
75
+ features := & Features {gitVersion : ver , UsingGogit : isGogit }
76
+ features .SupportProcReceive = features .CheckVersionAtLeast ("2.29" )
77
+ features .SupportHashSha256 = features .CheckVersionAtLeast ("2.42" ) && ! isGogit
78
+
79
+ features .SupportedObjectFormats = append (features .SupportedObjectFormats , Sha1ObjectFormat )
80
+ if features .SupportHashSha256 {
81
+ features .SupportedObjectFormats = append (features .SupportedObjectFormats , Sha256ObjectFormat )
82
+ }
83
+ return features , nil
61
84
}
62
85
63
86
func parseGitVersionLine (s string ) (* version.Version , error ) {
@@ -85,56 +108,24 @@ func SetExecutablePath(path string) error {
85
108
return fmt .Errorf ("git not found: %w" , err )
86
109
}
87
110
GitExecutable = absPath
111
+ return nil
112
+ }
88
113
89
- if err = loadGitVersion (); err != nil {
90
- return fmt .Errorf ("unable to load git version: %w" , err )
91
- }
92
-
93
- versionRequired , err := version .NewVersion (RequiredVersion )
94
- if err != nil {
95
- return err
96
- }
97
-
98
- if DefaultFeatures .GitVersion .LessThan (versionRequired ) {
114
+ func ensureGitVersion () error {
115
+ if ! DefaultFeatures ().CheckVersionAtLeast (RequiredVersion ) {
99
116
moreHint := "get git: https://git-scm.com/download/"
100
117
if runtime .GOOS == "linux" {
101
118
// there are a lot of CentOS/RHEL users using old git, so we add a special hint for them
102
- if _ , err = os .Stat ("/etc/redhat-release" ); err == nil {
119
+ if _ , err : = os .Stat ("/etc/redhat-release" ); err == nil {
103
120
// ius.io is the recommended official(git-scm.com) method to install git
104
121
moreHint = "get git: https://git-scm.com/download/linux and https://ius.io"
105
122
}
106
123
}
107
- return fmt .Errorf ("installed git version %q is not supported, Gitea requires git version >= %q, %s" , DefaultFeatures .GitVersion .Original (), RequiredVersion , moreHint )
108
- }
109
-
110
- if err = checkGitVersionCompatibility (DefaultFeatures .GitVersion ); err != nil {
111
- return fmt .Errorf ("installed git version %s has a known compatibility issue with Gitea: %w, please upgrade (or downgrade) git" , DefaultFeatures .GitVersion .String (), err )
124
+ return fmt .Errorf ("installed git version %q is not supported, Gitea requires git version >= %q, %s" , DefaultFeatures ().gitVersion .Original (), RequiredVersion , moreHint )
112
125
}
113
- return nil
114
- }
115
-
116
- // VersionInfo returns git version information
117
- func VersionInfo () string {
118
- if DefaultFeatures .GitVersion == nil {
119
- return "(git not found)"
120
- }
121
- format := "%s"
122
- args := []any {DefaultFeatures .GitVersion .Original ()}
123
- // Since git wire protocol has been released from git v2.18
124
- if setting .Git .EnableAutoGitWireProtocol && CheckGitVersionAtLeast ("2.18" ) == nil {
125
- format += ", Wire Protocol %s Enabled"
126
- args = append (args , "Version 2" ) // for focus color
127
- }
128
-
129
- return fmt .Sprintf (format , args ... )
130
- }
131
126
132
- func checkInit () error {
133
- if setting .Git .HomePath == "" {
134
- return errors .New ("unable to init Git's HomeDir, incorrect initialization of the setting and git modules" )
135
- }
136
- if DefaultContext != nil {
137
- log .Warn ("git module has been initialized already, duplicate init may work but it's better to fix it" )
127
+ if err := checkGitVersionCompatibility (DefaultFeatures ().gitVersion ); err != nil {
128
+ return fmt .Errorf ("installed git version %s has a known compatibility issue with Gitea: %w, please upgrade (or downgrade) git" , DefaultFeatures ().gitVersion .String (), err )
138
129
}
139
130
return nil
140
131
}
@@ -154,8 +145,12 @@ func HomeDir() string {
154
145
// InitSimple initializes git module with a very simple step, no config changes, no global command arguments.
155
146
// This method doesn't change anything to filesystem. At the moment, it is only used by some Gitea sub-commands.
156
147
func InitSimple (ctx context.Context ) error {
157
- if err := checkInit (); err != nil {
158
- return err
148
+ if setting .Git .HomePath == "" {
149
+ return errors .New ("unable to init Git's HomeDir, incorrect initialization of the setting and git modules" )
150
+ }
151
+
152
+ if DefaultContext != nil && (! setting .IsProd || setting .IsInTesting ) {
153
+ log .Warn ("git module has been initialized already, duplicate init may work but it's better to fix it" )
159
154
}
160
155
161
156
DefaultContext = ctx
@@ -165,40 +160,45 @@ func InitSimple(ctx context.Context) error {
165
160
defaultCommandExecutionTimeout = time .Duration (setting .Git .Timeout .Default ) * time .Second
166
161
}
167
162
168
- return SetExecutablePath (setting .Git .Path )
169
- }
163
+ if err := SetExecutablePath (setting .Git .Path ); err != nil {
164
+ return err
165
+ }
170
166
171
- // InitFull initializes git module with version check and change global variables, sync gitconfig.
172
- // It should only be called once at the beginning of the program initialization (TestMain/GlobalInitInstalled) as this code makes unsynchronized changes to variables.
173
- func InitFull (ctx context.Context ) (err error ) {
174
- if err = InitSimple (ctx ); err != nil {
167
+ var err error
168
+ defaultFeatures , err = loadGitVersionFeatures ()
169
+ if err != nil {
170
+ return err
171
+ }
172
+ if err = ensureGitVersion (); err != nil {
175
173
return err
176
174
}
177
175
178
176
// when git works with gnupg (commit signing), there should be a stable home for gnupg commands
179
177
if _ , ok := os .LookupEnv ("GNUPGHOME" ); ! ok {
180
178
_ = os .Setenv ("GNUPGHOME" , filepath .Join (HomeDir (), ".gnupg" ))
181
179
}
180
+ return nil
181
+ }
182
+
183
+ // InitFull initializes git module with version check and change global variables, sync gitconfig.
184
+ // It should only be called once at the beginning of the program initialization (TestMain/GlobalInitInstalled) as this code makes unsynchronized changes to variables.
185
+ func InitFull (ctx context.Context ) (err error ) {
186
+ if err = InitSimple (ctx ); err != nil {
187
+ return err
188
+ }
182
189
183
190
// Since git wire protocol has been released from git v2.18
184
- if setting .Git .EnableAutoGitWireProtocol && CheckGitVersionAtLeast ( "2.18" ) == nil {
191
+ if setting .Git .EnableAutoGitWireProtocol && DefaultFeatures (). CheckVersionAtLeast ( "2.18" ) {
185
192
globalCommandArgs = append (globalCommandArgs , "-c" , "protocol.version=2" )
186
193
}
187
194
188
195
// Explicitly disable credential helper, otherwise Git credentials might leak
189
- if CheckGitVersionAtLeast ( "2.9" ) == nil {
196
+ if DefaultFeatures (). CheckVersionAtLeast ( "2.9" ) {
190
197
globalCommandArgs = append (globalCommandArgs , "-c" , "credential.helper=" )
191
198
}
192
- DefaultFeatures .SupportProcReceive = CheckGitVersionAtLeast ("2.29" ) == nil
193
- DefaultFeatures .SupportHashSha256 = CheckGitVersionAtLeast ("2.42" ) == nil && ! isGogit
194
- if DefaultFeatures .SupportHashSha256 {
195
- SupportedObjectFormats = append (SupportedObjectFormats , Sha256ObjectFormat )
196
- } else {
197
- log .Warn ("sha256 hash support is disabled - requires Git >= 2.42. Gogit is currently unsupported" )
198
- }
199
199
200
200
if setting .LFS .StartServer {
201
- if CheckGitVersionAtLeast ( "2.1.2" ) != nil {
201
+ if ! DefaultFeatures (). CheckVersionAtLeast ( "2.1.2" ) {
202
202
return errors .New ("LFS server support requires Git >= 2.1.2" )
203
203
}
204
204
globalCommandArgs = append (globalCommandArgs , "-c" , "filter.lfs.required=" , "-c" , "filter.lfs.smudge=" , "-c" , "filter.lfs.clean=" )
@@ -238,13 +238,13 @@ func syncGitConfig() (err error) {
238
238
return err
239
239
}
240
240
241
- if CheckGitVersionAtLeast ( "2.10" ) == nil {
241
+ if DefaultFeatures (). CheckVersionAtLeast ( "2.10" ) {
242
242
if err := configSet ("receive.advertisePushOptions" , "true" ); err != nil {
243
243
return err
244
244
}
245
245
}
246
246
247
- if CheckGitVersionAtLeast ( "2.18" ) == nil {
247
+ if DefaultFeatures (). CheckVersionAtLeast ( "2.18" ) {
248
248
if err := configSet ("core.commitGraph" , "true" ); err != nil {
249
249
return err
250
250
}
@@ -256,7 +256,7 @@ func syncGitConfig() (err error) {
256
256
}
257
257
}
258
258
259
- if DefaultFeatures .SupportProcReceive {
259
+ if DefaultFeatures () .SupportProcReceive {
260
260
// set support for AGit flow
261
261
if err := configAddNonExist ("receive.procReceiveRefs" , "refs/for" ); err != nil {
262
262
return err
@@ -294,7 +294,7 @@ func syncGitConfig() (err error) {
294
294
}
295
295
296
296
// By default partial clones are disabled, enable them from git v2.22
297
- if ! setting .Git .DisablePartialClone && CheckGitVersionAtLeast ( "2.22" ) == nil {
297
+ if ! setting .Git .DisablePartialClone && DefaultFeatures (). CheckVersionAtLeast ( "2.22" ) {
298
298
if err = configSet ("uploadpack.allowfilter" , "true" ); err != nil {
299
299
return err
300
300
}
@@ -309,21 +309,6 @@ func syncGitConfig() (err error) {
309
309
return err
310
310
}
311
311
312
- // CheckGitVersionAtLeast check git version is at least the constraint version
313
- func CheckGitVersionAtLeast (atLeast string ) error {
314
- if DefaultFeatures .GitVersion == nil {
315
- panic ("git module is not initialized" ) // it shouldn't happen
316
- }
317
- atLeastVersion , err := version .NewVersion (atLeast )
318
- if err != nil {
319
- return err
320
- }
321
- if DefaultFeatures .GitVersion .Compare (atLeastVersion ) < 0 {
322
- return fmt .Errorf ("installed git binary version %s is not at least %s" , DefaultFeatures .GitVersion .Original (), atLeast )
323
- }
324
- return nil
325
- }
326
-
327
312
func checkGitVersionCompatibility (gitVer * version.Version ) error {
328
313
badVersions := []struct {
329
314
Version * version.Version
0 commit comments