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  }