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  }