github.com/git-lfs/git-lfs@v2.5.2+incompatible/config/config.go (about) 1 // Package config collects together all configuration settings 2 // NOTE: Subject to change, do not rely on this package from outside git-lfs source 3 package config 4 5 import ( 6 "fmt" 7 "os" 8 "path/filepath" 9 "strings" 10 "sync" 11 12 "github.com/git-lfs/git-lfs/fs" 13 "github.com/git-lfs/git-lfs/git" 14 "github.com/git-lfs/git-lfs/tools" 15 "github.com/rubyist/tracerx" 16 ) 17 18 var ( 19 ShowConfigWarnings = false 20 defaultRemote = "origin" 21 gitConfigWarningPrefix = "lfs." 22 ) 23 24 type Configuration struct { 25 // Os provides a `*Environment` used to access to the system's 26 // environment through os.Getenv. It is the point of entry for all 27 // system environment configuration. 28 Os Environment 29 30 // Git provides a `*Environment` used to access to the various levels of 31 // `.gitconfig`'s. It is the point of entry for all Git environment 32 // configuration. 33 Git Environment 34 35 currentRemote *string 36 pushRemote *string 37 38 // gitConfig can fetch or modify the current Git config and track the Git 39 // version. 40 gitConfig *git.Configuration 41 42 ref *git.Ref 43 remoteRef *git.Ref 44 fs *fs.Filesystem 45 gitDir *string 46 workDir string 47 loading sync.Mutex // guards initialization of gitConfig and remotes 48 loadingGit sync.Mutex // guards initialization of local git and working dirs 49 remotes []string 50 extensions map[string]Extension 51 } 52 53 func New() *Configuration { 54 return NewIn("", "") 55 } 56 57 func NewIn(workdir, gitdir string) *Configuration { 58 gitConf := git.NewConfig(workdir, gitdir) 59 c := &Configuration{ 60 Os: EnvironmentOf(NewOsFetcher()), 61 gitConfig: gitConf, 62 } 63 64 if len(gitConf.WorkDir) > 0 { 65 c.gitDir = &gitConf.GitDir 66 c.workDir = gitConf.WorkDir 67 } 68 69 c.Git = &delayedEnvironment{ 70 callback: func() Environment { 71 sources, err := gitConf.Sources(filepath.Join(c.LocalWorkingDir(), ".lfsconfig")) 72 if err != nil { 73 fmt.Fprintf(os.Stderr, "Error reading git config: %s\n", err) 74 } 75 return c.readGitConfig(sources...) 76 }, 77 } 78 return c 79 } 80 81 func (c *Configuration) readGitConfig(gitconfigs ...*git.ConfigurationSource) Environment { 82 gf, extensions, uniqRemotes := readGitConfig(gitconfigs...) 83 c.extensions = extensions 84 c.remotes = make([]string, 0, len(uniqRemotes)) 85 for remote := range uniqRemotes { 86 c.remotes = append(c.remotes, remote) 87 } 88 89 return EnvironmentOf(gf) 90 } 91 92 // Values is a convenience type used to call the NewFromValues function. It 93 // specifies `Git` and `Env` maps to use as mock values, instead of calling out 94 // to real `.gitconfig`s and the `os.Getenv` function. 95 type Values struct { 96 // Git and Os are the stand-in maps used to provide values for their 97 // respective environments. 98 Git, Os map[string][]string 99 } 100 101 // NewFrom returns a new `*config.Configuration` that reads both its Git 102 // and Enviornment-level values from the ones provided instead of the actual 103 // `.gitconfig` file or `os.Getenv`, respectively. 104 // 105 // This method should only be used during testing. 106 func NewFrom(v Values) *Configuration { 107 c := &Configuration{ 108 Os: EnvironmentOf(mapFetcher(v.Os)), 109 gitConfig: git.NewConfig("", ""), 110 } 111 c.Git = &delayedEnvironment{ 112 callback: func() Environment { 113 source := &git.ConfigurationSource{ 114 Lines: make([]string, 0, len(v.Git)), 115 } 116 117 for key, values := range v.Git { 118 for _, value := range values { 119 fmt.Printf("Config: %s=%s\n", key, value) 120 source.Lines = append(source.Lines, fmt.Sprintf("%s=%s", key, value)) 121 } 122 } 123 124 return c.readGitConfig(source) 125 }, 126 } 127 return c 128 } 129 130 // BasicTransfersOnly returns whether to only allow "basic" HTTP transfers. 131 // Default is false, including if the lfs.basictransfersonly is invalid 132 func (c *Configuration) BasicTransfersOnly() bool { 133 return c.Git.Bool("lfs.basictransfersonly", false) 134 } 135 136 // TusTransfersAllowed returns whether to only use "tus.io" HTTP transfers. 137 // Default is false, including if the lfs.tustransfers is invalid 138 func (c *Configuration) TusTransfersAllowed() bool { 139 return c.Git.Bool("lfs.tustransfers", false) 140 } 141 142 func (c *Configuration) FetchIncludePaths() []string { 143 patterns, _ := c.Git.Get("lfs.fetchinclude") 144 return tools.CleanPaths(patterns, ",") 145 } 146 147 func (c *Configuration) FetchExcludePaths() []string { 148 patterns, _ := c.Git.Get("lfs.fetchexclude") 149 return tools.CleanPaths(patterns, ",") 150 } 151 152 func (c *Configuration) CurrentRef() *git.Ref { 153 c.loading.Lock() 154 defer c.loading.Unlock() 155 if c.ref == nil { 156 r, err := git.CurrentRef() 157 if err != nil { 158 tracerx.Printf("Error loading current ref: %s", err) 159 c.ref = &git.Ref{} 160 } else { 161 c.ref = r 162 } 163 } 164 return c.ref 165 } 166 167 func (c *Configuration) IsDefaultRemote() bool { 168 return c.Remote() == defaultRemote 169 } 170 171 // Remote returns the default remote based on: 172 // 1. The currently tracked remote branch, if present 173 // 2. Any other SINGLE remote defined in .git/config 174 // 3. Use "origin" as a fallback. 175 // Results are cached after the first hit. 176 func (c *Configuration) Remote() string { 177 ref := c.CurrentRef() 178 179 c.loading.Lock() 180 defer c.loading.Unlock() 181 182 if c.currentRemote == nil { 183 if len(ref.Name) == 0 { 184 c.currentRemote = &defaultRemote 185 return defaultRemote 186 } 187 188 if remote, ok := c.Git.Get(fmt.Sprintf("branch.%s.remote", ref.Name)); ok { 189 // try tracking remote 190 c.currentRemote = &remote 191 } else if remotes := c.Remotes(); len(remotes) == 1 { 192 // use only remote if there is only 1 193 c.currentRemote = &remotes[0] 194 } else { 195 // fall back to default :( 196 c.currentRemote = &defaultRemote 197 } 198 } 199 return *c.currentRemote 200 } 201 202 func (c *Configuration) PushRemote() string { 203 ref := c.CurrentRef() 204 c.loading.Lock() 205 defer c.loading.Unlock() 206 207 if c.pushRemote == nil { 208 if remote, ok := c.Git.Get(fmt.Sprintf("branch.%s.pushRemote", ref.Name)); ok { 209 c.pushRemote = &remote 210 } else if remote, ok := c.Git.Get("remote.pushDefault"); ok { 211 c.pushRemote = &remote 212 } else { 213 c.loading.Unlock() 214 remote := c.Remote() 215 c.loading.Lock() 216 217 c.pushRemote = &remote 218 } 219 } 220 221 return *c.pushRemote 222 } 223 224 func (c *Configuration) SetValidRemote(name string) error { 225 if err := git.ValidateRemote(name); err != nil { 226 return err 227 } 228 c.SetRemote(name) 229 return nil 230 } 231 232 func (c *Configuration) SetValidPushRemote(name string) error { 233 if err := git.ValidateRemote(name); err != nil { 234 return err 235 } 236 c.SetPushRemote(name) 237 return nil 238 } 239 240 func (c *Configuration) SetRemote(name string) { 241 c.currentRemote = &name 242 } 243 244 func (c *Configuration) SetPushRemote(name string) { 245 c.pushRemote = &name 246 } 247 248 func (c *Configuration) Remotes() []string { 249 c.loadGitConfig() 250 return c.remotes 251 } 252 253 func (c *Configuration) Extensions() map[string]Extension { 254 c.loadGitConfig() 255 return c.extensions 256 } 257 258 // SortedExtensions gets the list of extensions ordered by Priority 259 func (c *Configuration) SortedExtensions() ([]Extension, error) { 260 return SortExtensions(c.Extensions()) 261 } 262 263 func (c *Configuration) SkipDownloadErrors() bool { 264 return c.Os.Bool("GIT_LFS_SKIP_DOWNLOAD_ERRORS", false) || c.Git.Bool("lfs.skipdownloaderrors", false) 265 } 266 267 func (c *Configuration) SetLockableFilesReadOnly() bool { 268 return c.Os.Bool("GIT_LFS_SET_LOCKABLE_READONLY", true) && c.Git.Bool("lfs.setlockablereadonly", true) 269 } 270 271 func (c *Configuration) HookDir() string { 272 if git.IsGitVersionAtLeast("2.9.0") { 273 hp, ok := c.Git.Get("core.hooksPath") 274 if ok { 275 return hp 276 } 277 } 278 return filepath.Join(c.LocalGitDir(), "hooks") 279 } 280 281 func (c *Configuration) InRepo() bool { 282 return len(c.LocalGitDir()) > 0 283 } 284 285 func (c *Configuration) LocalWorkingDir() string { 286 c.loadGitDirs() 287 return c.workDir 288 } 289 290 func (c *Configuration) LocalGitDir() string { 291 c.loadGitDirs() 292 return *c.gitDir 293 } 294 295 func (c *Configuration) loadGitDirs() { 296 c.loadingGit.Lock() 297 defer c.loadingGit.Unlock() 298 299 if c.gitDir != nil { 300 return 301 } 302 303 gitdir, workdir, err := git.GitAndRootDirs() 304 if err != nil { 305 errMsg := err.Error() 306 tracerx.Printf("Error running 'git rev-parse': %s", errMsg) 307 if !strings.Contains(strings.ToLower(errMsg), 308 "not a git repository") { 309 fmt.Fprintf(os.Stderr, "Error: %s\n", errMsg) 310 } 311 c.gitDir = &gitdir 312 } 313 314 gitdir = tools.ResolveSymlinks(gitdir) 315 c.gitDir = &gitdir 316 c.workDir = tools.ResolveSymlinks(workdir) 317 } 318 319 func (c *Configuration) LocalGitStorageDir() string { 320 return c.Filesystem().GitStorageDir 321 } 322 323 func (c *Configuration) LocalReferenceDirs() []string { 324 return c.Filesystem().ReferenceDirs 325 } 326 327 func (c *Configuration) LFSStorageDir() string { 328 return c.Filesystem().LFSStorageDir 329 } 330 331 func (c *Configuration) LFSObjectDir() string { 332 return c.Filesystem().LFSObjectDir() 333 } 334 335 func (c *Configuration) LFSObjectExists(oid string, size int64) bool { 336 return c.Filesystem().ObjectExists(oid, size) 337 } 338 339 func (c *Configuration) EachLFSObject(fn func(fs.Object) error) error { 340 return c.Filesystem().EachObject(fn) 341 } 342 343 func (c *Configuration) LocalLogDir() string { 344 return c.Filesystem().LogDir() 345 } 346 347 func (c *Configuration) TempDir() string { 348 return c.Filesystem().TempDir() 349 } 350 351 func (c *Configuration) Filesystem() *fs.Filesystem { 352 c.loadGitDirs() 353 c.loading.Lock() 354 defer c.loading.Unlock() 355 356 if c.fs == nil { 357 lfsdir, _ := c.Git.Get("lfs.storage") 358 c.fs = fs.New( 359 c.Os, 360 c.LocalGitDir(), 361 c.LocalWorkingDir(), 362 lfsdir, 363 ) 364 } 365 366 return c.fs 367 } 368 369 func (c *Configuration) Cleanup() error { 370 c.loading.Lock() 371 defer c.loading.Unlock() 372 return c.fs.Cleanup() 373 } 374 375 func (c *Configuration) OSEnv() Environment { 376 return c.Os 377 } 378 379 func (c *Configuration) GitEnv() Environment { 380 return c.Git 381 } 382 383 func (c *Configuration) GitConfig() *git.Configuration { 384 return c.gitConfig 385 } 386 387 func (c *Configuration) FindGitGlobalKey(key string) string { 388 return c.gitConfig.FindGlobal(key) 389 } 390 391 func (c *Configuration) FindGitSystemKey(key string) string { 392 return c.gitConfig.FindSystem(key) 393 } 394 395 func (c *Configuration) FindGitLocalKey(key string) string { 396 return c.gitConfig.FindLocal(key) 397 } 398 399 func (c *Configuration) SetGitGlobalKey(key, val string) (string, error) { 400 return c.gitConfig.SetGlobal(key, val) 401 } 402 403 func (c *Configuration) SetGitSystemKey(key, val string) (string, error) { 404 return c.gitConfig.SetSystem(key, val) 405 } 406 407 func (c *Configuration) SetGitLocalKey(key, val string) (string, error) { 408 return c.gitConfig.SetLocal(key, val) 409 } 410 411 func (c *Configuration) UnsetGitGlobalSection(key string) (string, error) { 412 return c.gitConfig.UnsetGlobalSection(key) 413 } 414 415 func (c *Configuration) UnsetGitSystemSection(key string) (string, error) { 416 return c.gitConfig.UnsetSystemSection(key) 417 } 418 419 func (c *Configuration) UnsetGitLocalSection(key string) (string, error) { 420 return c.gitConfig.UnsetLocalSection(key) 421 } 422 423 func (c *Configuration) UnsetGitLocalKey(key string) (string, error) { 424 return c.gitConfig.UnsetLocalKey(key) 425 } 426 427 // loadGitConfig is a temporary measure to support legacy behavior dependent on 428 // accessing properties set by ReadGitConfig, namely: 429 // - `c.extensions` 430 // - `c.uniqRemotes` 431 // - `c.gitConfig` 432 // 433 // Since the *gitEnvironment is responsible for setting these values on the 434 // (*config.Configuration) instance, we must call that method, if it exists. 435 // 436 // loadGitConfig returns a bool returning whether or not `loadGitConfig` was 437 // called AND the method did not return early. 438 func (c *Configuration) loadGitConfig() { 439 if g, ok := c.Git.(*delayedEnvironment); ok { 440 g.Load() 441 } 442 } 443 444 // CurrentCommitter returns the name/email that would be used to author a commit 445 // with this configuration. In particular, the "user.name" and "user.email" 446 // configuration values are used 447 func (c *Configuration) CurrentCommitter() (name, email string) { 448 name, _ = c.Git.Get("user.name") 449 email, _ = c.Git.Get("user.email") 450 return 451 }