1 // Package config collects together all configuration settings
2 // NOTE: Subject to change, do not rely on this package from outside git-lfs source
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"
19 ShowConfigWarnings = false
20 defaultRemote = "origin"
21 gitConfigWarningPrefix = "lfs."
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.
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
38 // gitConfig can fetch or modify the current Git config and track the Git
40 gitConfig *git.Configuration
47 loading sync.Mutex // guards initialization of gitConfig and remotes
48 loadingGit sync.Mutex // guards initialization of local git and working dirs
50 extensions map[string]Extension
53 func New() *Configuration {
57 func NewIn(workdir, gitdir string) *Configuration {
58 gitConf := git.NewConfig(workdir, gitdir)
60 Os: EnvironmentOf(NewOsFetcher()),
64 if len(gitConf.WorkDir) > 0 {
65 c.gitDir = &gitConf.GitDir
66 c.workDir = gitConf.WorkDir
69 c.Git = &delayedEnvironment{
70 callback: func() Environment {
71 sources, err := gitConf.Sources(filepath.Join(c.LocalWorkingDir(), ".lfsconfig"))
73 fmt.Fprintf(os.Stderr, "Error reading git config: %s\n", err)
75 return c.readGitConfig(sources...)
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 {
89 c.remotes = append(c.remotes, remote)
92 return EnvironmentOf(gf)
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.
99 // Git and Os are the stand-in maps used to provide values for their
100 // respective environments.
101 Git, Os map[string][]string
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.
108 // This method should only be used during testing.
109 func NewFrom(v Values) *Configuration {
111 Os: EnvironmentOf(mapFetcher(v.Os)),
112 gitConfig: git.NewConfig("", ""),
114 c.Git = &delayedEnvironment{
115 callback: func() Environment {
116 source := &git.ConfigurationSource{
117 Lines: make([]string, 0, len(v.Git)),
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))
127 return c.readGitConfig(source)
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)
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)
145 func (c *Configuration) FetchIncludePaths() []string {
146 patterns, _ := c.Git.Get("lfs.fetchinclude")
147 return tools.CleanPaths(patterns, ",")
150 func (c *Configuration) FetchExcludePaths() []string {
151 patterns, _ := c.Git.Get("lfs.fetchexclude")
152 return tools.CleanPaths(patterns, ",")
155 func (c *Configuration) CurrentRef() *git.Ref {
157 defer c.loading.Unlock()
159 r, err := git.CurrentRef()
161 tracerx.Printf("Error loading current ref: %s", err)
170 func (c *Configuration) IsDefaultRemote() bool {
171 return c.Remote() == defaultRemote
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()
183 defer c.loading.Unlock()
185 if c.currentRemote == nil {
186 if len(ref.Name) == 0 {
187 c.currentRemote = &defaultRemote
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]
198 // fall back to default :(
199 c.currentRemote = &defaultRemote
202 return *c.currentRemote
205 func (c *Configuration) PushRemote() string {
206 ref := c.CurrentRef()
208 defer c.loading.Unlock()
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
220 c.pushRemote = &remote
227 func (c *Configuration) SetValidRemote(name string) error {
228 if err := git.ValidateRemote(name); err != nil {
235 func (c *Configuration) SetValidPushRemote(name string) error {
236 if err := git.ValidateRemote(name); err != nil {
239 c.SetPushRemote(name)
243 func (c *Configuration) SetRemote(name string) {
244 c.currentRemote = &name
247 func (c *Configuration) SetPushRemote(name string) {
251 func (c *Configuration) Remotes() []string {
256 func (c *Configuration) Extensions() map[string]Extension {
261 // SortedExtensions gets the list of extensions ordered by Priority
262 func (c *Configuration) SortedExtensions() ([]Extension, error) {
263 return SortExtensions(c.Extensions())
266 func (c *Configuration) SkipDownloadErrors() bool {
267 return c.Os.Bool("GIT_LFS_SKIP_DOWNLOAD_ERRORS", false) || c.Git.Bool("lfs.skipdownloaderrors", false)
270 func (c *Configuration) SetLockableFilesReadOnly() bool {
271 return c.Os.Bool("GIT_LFS_SET_LOCKABLE_READONLY", true) && c.Git.Bool("lfs.setlockablereadonly", true)
274 func (c *Configuration) HookDir() string {
275 if git.IsGitVersionAtLeast("2.9.0") {
276 hp, ok := c.Git.Get("core.hooksPath")
281 return filepath.Join(c.LocalGitDir(), "hooks")
284 func (c *Configuration) InRepo() bool {
285 return len(c.LocalGitDir()) > 0
288 func (c *Configuration) LocalWorkingDir() string {
293 func (c *Configuration) LocalGitDir() string {
298 func (c *Configuration) loadGitDirs() {
300 defer c.loadingGit.Unlock()
306 gitdir, workdir, err := git.GitAndRootDirs()
308 errMsg := err.Error()
309 tracerx.Printf("Error running 'git rev-parse': %s", errMsg)
310 if !strings.Contains(strings.ToLower(errMsg),
311 "not a git repository") {
312 fmt.Fprintf(os.Stderr, "Error: %s\n", errMsg)
317 gitdir = tools.ResolveSymlinks(gitdir)
319 c.workDir = tools.ResolveSymlinks(workdir)
322 func (c *Configuration) LocalGitStorageDir() string {
323 return c.Filesystem().GitStorageDir
326 func (c *Configuration) LocalReferenceDirs() []string {
327 return c.Filesystem().ReferenceDirs
330 func (c *Configuration) LFSStorageDir() string {
331 return c.Filesystem().LFSStorageDir
334 func (c *Configuration) LFSObjectDir() string {
335 return c.Filesystem().LFSObjectDir()
338 func (c *Configuration) LFSObjectExists(oid string, size int64) bool {
339 return c.Filesystem().ObjectExists(oid, size)
342 func (c *Configuration) EachLFSObject(fn func(fs.Object) error) error {
343 return c.Filesystem().EachObject(fn)
346 func (c *Configuration) LocalLogDir() string {
347 return c.Filesystem().LogDir()
350 func (c *Configuration) TempDir() string {
351 return c.Filesystem().TempDir()
354 func (c *Configuration) Filesystem() *fs.Filesystem {
357 defer c.loading.Unlock()
360 lfsdir, _ := c.Git.Get("lfs.storage")
372 func (c *Configuration) Cleanup() error {
374 defer c.loading.Unlock()
375 return c.fs.Cleanup()
378 func (c *Configuration) OSEnv() Environment {
382 func (c *Configuration) GitEnv() Environment {
386 func (c *Configuration) GitConfig() *git.Configuration {
390 func (c *Configuration) FindGitGlobalKey(key string) string {
391 return c.gitConfig.FindGlobal(key)
394 func (c *Configuration) FindGitSystemKey(key string) string {
395 return c.gitConfig.FindSystem(key)
398 func (c *Configuration) FindGitLocalKey(key string) string {
399 return c.gitConfig.FindLocal(key)
402 func (c *Configuration) SetGitGlobalKey(key, val string) (string, error) {
403 return c.gitConfig.SetGlobal(key, val)
406 func (c *Configuration) SetGitSystemKey(key, val string) (string, error) {
407 return c.gitConfig.SetSystem(key, val)
410 func (c *Configuration) SetGitLocalKey(key, val string) (string, error) {
411 return c.gitConfig.SetLocal(key, val)
414 func (c *Configuration) UnsetGitGlobalSection(key string) (string, error) {
415 return c.gitConfig.UnsetGlobalSection(key)
418 func (c *Configuration) UnsetGitSystemSection(key string) (string, error) {
419 return c.gitConfig.UnsetSystemSection(key)
422 func (c *Configuration) UnsetGitLocalSection(key string) (string, error) {
423 return c.gitConfig.UnsetLocalSection(key)
426 func (c *Configuration) UnsetGitLocalKey(key string) (string, error) {
427 return c.gitConfig.UnsetLocalKey(key)
430 // loadGitConfig is a temporary measure to support legacy behavior dependent on
431 // accessing properties set by ReadGitConfig, namely:
436 // Since the *gitEnvironment is responsible for setting these values on the
437 // (*config.Configuration) instance, we must call that method, if it exists.
439 // loadGitConfig returns a bool returning whether or not `loadGitConfig` was
440 // called AND the method did not return early.
441 func (c *Configuration) loadGitConfig() {
442 if g, ok := c.Git.(*delayedEnvironment); ok {
447 // CurrentCommitter returns the name/email that would be used to author a commit
448 // with this configuration. In particular, the "user.name" and "user.email"
449 // configuration values are used
450 func (c *Configuration) CurrentCommitter() (name, email string) {
451 name, _ = c.Git.Get("user.name")
452 email, _ = c.Git.Get("user.email")