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