@@ -22,42 +22,63 @@ import (
2222 "github.com/hashicorp/go-version"
2323)
2424
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
2726
28- var (
29- // GitExecutable is the command name of git
30- // Could be updated to an absolute path while initialization
31- GitExecutable = "git"
32-
33- // DefaultContext is the default context to run git commands in, must be initialized by git.InitXxx
34- DefaultContext context.Context
27+ type Features struct {
28+ gitVersion * version.Version
3529
36- DefaultFeatures struct {
37- GitVersion * version.Version
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+ }
3835
39- SupportProcReceive bool // >= 2.29
40- SupportHashSha256 bool // >= 2.42, SHA-256 repositories no longer an ‘experimental curiosity’
41- }
36+ var (
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
4240)
4341
44- // 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
42+ func (f * Features ) CheckVersionAtLeast (atLeast string ) bool {
43+ return f .gitVersion .Compare (version .Must (version .NewVersion (atLeast ))) >= 0
44+ }
45+
46+ // VersionInfo returns git version information
47+ func (f * Features ) VersionInfo () string {
48+ return f .gitVersion .Original ()
49+ }
50+
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+ }
4959 }
60+ return defaultFeatures
61+ }
5062
63+ func loadGitVersionFeatures () (* Features , error ) {
5164 stdout , _ , runErr := NewCommand (DefaultContext , "version" ).RunStdString (nil )
5265 if runErr != nil {
53- return runErr
66+ return nil , runErr
5467 }
5568
5669 ver , err := parseGitVersionLine (strings .TrimSpace (stdout ))
57- if err = = nil {
58- DefaultFeatures . GitVersion = ver
70+ if err ! = nil {
71+ return nil , err
5972 }
60- return err
73+
74+ features := & Features {gitVersion : ver , UsingGogit : isGogit }
75+ features .SupportProcReceive = features .CheckVersionAtLeast ("2.29" )
76+ features .SupportHashSha256 = features .CheckVersionAtLeast ("2.42" ) && ! isGogit
77+ features .SupportedObjectFormats = []ObjectFormat {Sha1ObjectFormat }
78+ if features .SupportHashSha256 {
79+ features .SupportedObjectFormats = append (features .SupportedObjectFormats , Sha256ObjectFormat )
80+ }
81+ return features , nil
6182}
6283
6384func parseGitVersionLine (s string ) (* version.Version , error ) {
@@ -85,56 +106,24 @@ func SetExecutablePath(path string) error {
85106 return fmt .Errorf ("git not found: %w" , err )
86107 }
87108 GitExecutable = absPath
109+ return nil
110+ }
88111
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 ) {
112+ func ensureGitVersion () error {
113+ if ! DefaultFeatures ().CheckVersionAtLeast (RequiredVersion ) {
99114 moreHint := "get git: https://git-scm.com/download/"
100115 if runtime .GOOS == "linux" {
101116 // 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 {
117+ if _ , err : = os .Stat ("/etc/redhat-release" ); err == nil {
103118 // ius.io is the recommended official(git-scm.com) method to install git
104119 moreHint = "get git: https://git-scm.com/download/linux and https://ius.io"
105120 }
106121 }
107- return fmt .Errorf ("installed git version %q is not supported, Gitea requires git version >= %q, %s" , DefaultFeatures . GitVersion .Original (), RequiredVersion , moreHint )
122+ return fmt .Errorf ("installed git version %q is not supported, Gitea requires git version >= %q, %s" , DefaultFeatures (). gitVersion .Original (), RequiredVersion , moreHint )
108123 }
109124
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 )
112- }
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-
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" )
125+ if err := checkGitVersionCompatibility (DefaultFeatures ().gitVersion ); err != nil {
126+ return fmt .Errorf ("installed git version %s has a known compatibility issue with Gitea: %w, please upgrade (or downgrade) git" , DefaultFeatures ().gitVersion .String (), err )
138127 }
139128 return nil
140129}
@@ -154,8 +143,12 @@ func HomeDir() string {
154143// InitSimple initializes git module with a very simple step, no config changes, no global command arguments.
155144// This method doesn't change anything to filesystem. At the moment, it is only used by some Gitea sub-commands.
156145func InitSimple (ctx context.Context ) error {
157- if err := checkInit (); err != nil {
158- return err
146+ if setting .Git .HomePath == "" {
147+ return errors .New ("unable to init Git's HomeDir, incorrect initialization of the setting and git modules" )
148+ }
149+
150+ if DefaultContext != nil && (! setting .IsProd || setting .IsInTesting ) {
151+ log .Warn ("git module has been initialized already, duplicate init may work but it's better to fix it" )
159152 }
160153
161154 DefaultContext = ctx
@@ -165,40 +158,45 @@ func InitSimple(ctx context.Context) error {
165158 defaultCommandExecutionTimeout = time .Duration (setting .Git .Timeout .Default ) * time .Second
166159 }
167160
168- return SetExecutablePath (setting .Git .Path )
169- }
161+ if err := SetExecutablePath (setting .Git .Path ); err != nil {
162+ return err
163+ }
170164
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 {
165+ var err error
166+ defaultFeatures , err = loadGitVersionFeatures ()
167+ if err != nil {
168+ return err
169+ }
170+ if err = ensureGitVersion (); err != nil {
175171 return err
176172 }
177173
178174 // when git works with gnupg (commit signing), there should be a stable home for gnupg commands
179175 if _ , ok := os .LookupEnv ("GNUPGHOME" ); ! ok {
180176 _ = os .Setenv ("GNUPGHOME" , filepath .Join (HomeDir (), ".gnupg" ))
181177 }
178+ return nil
179+ }
180+
181+ // InitFull initializes git module with version check and change global variables, sync gitconfig.
182+ // It should only be called once at the beginning of the program initialization (TestMain/GlobalInitInstalled) as this code makes unsynchronized changes to variables.
183+ func InitFull (ctx context.Context ) (err error ) {
184+ if err = InitSimple (ctx ); err != nil {
185+ return err
186+ }
182187
183188 // Since git wire protocol has been released from git v2.18
184- if setting .Git .EnableAutoGitWireProtocol && CheckGitVersionAtLeast ( "2.18" ) == nil {
189+ if setting .Git .EnableAutoGitWireProtocol && DefaultFeatures (). CheckVersionAtLeast ( "2.18" ) {
185190 globalCommandArgs = append (globalCommandArgs , "-c" , "protocol.version=2" )
186191 }
187192
188193 // Explicitly disable credential helper, otherwise Git credentials might leak
189- if CheckGitVersionAtLeast ( "2.9" ) == nil {
194+ if DefaultFeatures (). CheckVersionAtLeast ( "2.9" ) {
190195 globalCommandArgs = append (globalCommandArgs , "-c" , "credential.helper=" )
191196 }
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- }
199197
200198 if setting .LFS .StartServer {
201- if CheckGitVersionAtLeast ( "2.1.2" ) != nil {
199+ if ! DefaultFeatures (). CheckVersionAtLeast ( "2.1.2" ) {
202200 return errors .New ("LFS server support requires Git >= 2.1.2" )
203201 }
204202 globalCommandArgs = append (globalCommandArgs , "-c" , "filter.lfs.required=" , "-c" , "filter.lfs.smudge=" , "-c" , "filter.lfs.clean=" )
@@ -238,13 +236,13 @@ func syncGitConfig() (err error) {
238236 return err
239237 }
240238
241- if CheckGitVersionAtLeast ( "2.10" ) == nil {
239+ if DefaultFeatures (). CheckVersionAtLeast ( "2.10" ) {
242240 if err := configSet ("receive.advertisePushOptions" , "true" ); err != nil {
243241 return err
244242 }
245243 }
246244
247- if CheckGitVersionAtLeast ( "2.18" ) == nil {
245+ if DefaultFeatures (). CheckVersionAtLeast ( "2.18" ) {
248246 if err := configSet ("core.commitGraph" , "true" ); err != nil {
249247 return err
250248 }
@@ -256,7 +254,7 @@ func syncGitConfig() (err error) {
256254 }
257255 }
258256
259- if DefaultFeatures .SupportProcReceive {
257+ if DefaultFeatures () .SupportProcReceive {
260258 // set support for AGit flow
261259 if err := configAddNonExist ("receive.procReceiveRefs" , "refs/for" ); err != nil {
262260 return err
@@ -294,7 +292,7 @@ func syncGitConfig() (err error) {
294292 }
295293
296294 // By default partial clones are disabled, enable them from git v2.22
297- if ! setting .Git .DisablePartialClone && CheckGitVersionAtLeast ( "2.22" ) == nil {
295+ if ! setting .Git .DisablePartialClone && DefaultFeatures (). CheckVersionAtLeast ( "2.22" ) {
298296 if err = configSet ("uploadpack.allowfilter" , "true" ); err != nil {
299297 return err
300298 }
@@ -309,21 +307,6 @@ func syncGitConfig() (err error) {
309307 return err
310308}
311309
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-
327310func checkGitVersionCompatibility (gitVer * version.Version ) error {
328311 badVersions := []struct {
329312 Version * version.Version
0 commit comments