github.com/psexton/git-lfs@v2.1.1-0.20170517224304-289a18b2bc53+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  	"reflect"
     8  	"regexp"
     9  	"strconv"
    10  	"strings"
    11  	"sync"
    12  
    13  	"github.com/git-lfs/git-lfs/errors"
    14  	"github.com/git-lfs/git-lfs/tools"
    15  )
    16  
    17  var (
    18  	Config                 = New()
    19  	ShowConfigWarnings     = false
    20  	defaultRemote          = "origin"
    21  	gitConfigWarningPrefix = "lfs."
    22  )
    23  
    24  // FetchPruneConfig collects together the config options that control fetching and pruning
    25  type FetchPruneConfig struct {
    26  	// The number of days prior to current date for which (local) refs other than HEAD
    27  	// will be fetched with --recent (default 7, 0 = only fetch HEAD)
    28  	FetchRecentRefsDays int `git:"lfs.fetchrecentrefsdays"`
    29  	// Makes the FetchRecentRefsDays option apply to remote refs from fetch source as well (default true)
    30  	FetchRecentRefsIncludeRemotes bool `git:"lfs.fetchrecentremoterefs"`
    31  	// number of days prior to latest commit on a ref that we'll fetch previous
    32  	// LFS changes too (default 0 = only fetch at ref)
    33  	FetchRecentCommitsDays int `git:"lfs.fetchrecentcommitsdays"`
    34  	// Whether to always fetch recent even without --recent
    35  	FetchRecentAlways bool `git:"lfs.fetchrecentalways"`
    36  	// Number of days added to FetchRecent*; data outside combined window will be
    37  	// deleted when prune is run. (default 3)
    38  	PruneOffsetDays int `git:"lfs.pruneoffsetdays"`
    39  	// Always verify with remote before pruning
    40  	PruneVerifyRemoteAlways bool `git:"lfs.pruneverifyremotealways"`
    41  	// Name of remote to check for unpushed and verify checks
    42  	PruneRemoteName string `git:"lfs.pruneremotetocheck"`
    43  }
    44  
    45  type Configuration struct {
    46  	// Os provides a `*Environment` used to access to the system's
    47  	// environment through os.Getenv. It is the point of entry for all
    48  	// system environment configuration.
    49  	Os Environment
    50  
    51  	// Git provides a `*Environment` used to access to the various levels of
    52  	// `.gitconfig`'s. It is the point of entry for all Git environment
    53  	// configuration.
    54  	Git Environment
    55  
    56  	CurrentRemote string
    57  
    58  	loading    sync.Mutex // guards initialization of gitConfig and remotes
    59  	remotes    []string
    60  	extensions map[string]Extension
    61  }
    62  
    63  func New() *Configuration {
    64  	c := &Configuration{Os: EnvironmentOf(NewOsFetcher())}
    65  	c.Git = &gitEnvironment{config: c}
    66  	initConfig(c)
    67  	return c
    68  }
    69  
    70  // Values is a convenience type used to call the NewFromValues function. It
    71  // specifies `Git` and `Env` maps to use as mock values, instead of calling out
    72  // to real `.gitconfig`s and the `os.Getenv` function.
    73  type Values struct {
    74  	// Git and Os are the stand-in maps used to provide values for their
    75  	// respective environments.
    76  	Git, Os map[string][]string
    77  }
    78  
    79  // NewFrom returns a new `*config.Configuration` that reads both its Git
    80  // and Enviornment-level values from the ones provided instead of the actual
    81  // `.gitconfig` file or `os.Getenv`, respectively.
    82  //
    83  // This method should only be used during testing.
    84  func NewFrom(v Values) *Configuration {
    85  	c := &Configuration{
    86  		Os:  EnvironmentOf(mapFetcher(v.Os)),
    87  		Git: EnvironmentOf(mapFetcher(v.Git)),
    88  	}
    89  	initConfig(c)
    90  	return c
    91  }
    92  
    93  func initConfig(c *Configuration) {
    94  	c.CurrentRemote = defaultRemote
    95  }
    96  
    97  // Unmarshal unmarshals the *Configuration in context into all of `v`'s fields,
    98  // according to the following rules:
    99  //
   100  // Values are marshaled according to the given key and environment, as follows:
   101  //	type T struct {
   102  //		Field string `git:"key"`
   103  //		Other string `os:"key"`
   104  //	}
   105  //
   106  // If an unknown environment is given, an error will be returned. If there is no
   107  // method supporting conversion into a field's type, an error will be returned.
   108  // If no value is associated with the given key and environment, the field will
   109  // // only be modified if there is a config value present matching the given
   110  // key. If the field is already set to a non-zero value of that field's type,
   111  // then it will be left alone.
   112  //
   113  // Otherwise, the field will be set to the value of calling the
   114  // appropriately-typed method on the specified environment.
   115  func (c *Configuration) Unmarshal(v interface{}) error {
   116  	into := reflect.ValueOf(v)
   117  	if into.Kind() != reflect.Ptr {
   118  		return fmt.Errorf("lfs/config: unable to parse non-pointer type of %T", v)
   119  	}
   120  	into = into.Elem()
   121  
   122  	for i := 0; i < into.Type().NumField(); i++ {
   123  		field := into.Field(i)
   124  		sfield := into.Type().Field(i)
   125  
   126  		lookups, err := c.parseTag(sfield.Tag)
   127  		if err != nil {
   128  			return err
   129  		}
   130  
   131  		var val interface{}
   132  		for _, lookup := range lookups {
   133  			if _, ok := lookup.Get(); !ok {
   134  				continue
   135  			}
   136  
   137  			switch sfield.Type.Kind() {
   138  			case reflect.String:
   139  				val, _ = lookup.Get()
   140  			case reflect.Int:
   141  				val = lookup.Int(int(field.Int()))
   142  			case reflect.Bool:
   143  				val = lookup.Bool(field.Bool())
   144  			default:
   145  				return fmt.Errorf("lfs/config: unsupported target type for field %q: %v",
   146  					sfield.Name, sfield.Type.String())
   147  			}
   148  
   149  			if val != nil {
   150  				break
   151  			}
   152  		}
   153  
   154  		if val != nil {
   155  			into.Field(i).Set(reflect.ValueOf(val))
   156  		}
   157  	}
   158  
   159  	return nil
   160  }
   161  
   162  var (
   163  	tagRe    = regexp.MustCompile("((\\w+:\"[^\"]*\")\\b?)+")
   164  	emptyEnv = EnvironmentOf(MapFetcher(nil))
   165  )
   166  
   167  type lookup struct {
   168  	key string
   169  	env Environment
   170  }
   171  
   172  func (l *lookup) Get() (interface{}, bool) { return l.env.Get(l.key) }
   173  func (l *lookup) Int(or int) int           { return l.env.Int(l.key, or) }
   174  func (l *lookup) Bool(or bool) bool        { return l.env.Bool(l.key, or) }
   175  
   176  // parseTag returns the key, environment, and optional error assosciated with a
   177  // given tag. It will return the XOR of either the `git` or `os` tag. That is to
   178  // say, a field tagged with EITHER `git` OR `os` is valid, but pone tagged with
   179  // both is not.
   180  //
   181  // If neither field was found, then a nil environment will be returned.
   182  func (c *Configuration) parseTag(tag reflect.StructTag) ([]*lookup, error) {
   183  	var lookups []*lookup
   184  
   185  	parts := tagRe.FindAllString(string(tag), -1)
   186  	for _, part := range parts {
   187  		sep := strings.SplitN(part, ":", 2)
   188  		if len(sep) != 2 {
   189  			return nil, errors.Errorf("config: invalid struct tag %q", tag)
   190  		}
   191  
   192  		var env Environment
   193  		switch strings.ToLower(sep[0]) {
   194  		case "git":
   195  			env = c.Git
   196  		case "os":
   197  			env = c.Os
   198  		default:
   199  			// ignore other struct tags, like `json:""`, etc.
   200  			env = emptyEnv
   201  		}
   202  
   203  		uq, err := strconv.Unquote(sep[1])
   204  		if err != nil {
   205  			return nil, err
   206  		}
   207  
   208  		lookups = append(lookups, &lookup{
   209  			key: uq,
   210  			env: env,
   211  		})
   212  	}
   213  
   214  	return lookups, nil
   215  }
   216  
   217  // BasicTransfersOnly returns whether to only allow "basic" HTTP transfers.
   218  // Default is false, including if the lfs.basictransfersonly is invalid
   219  func (c *Configuration) BasicTransfersOnly() bool {
   220  	return c.Git.Bool("lfs.basictransfersonly", false)
   221  }
   222  
   223  // TusTransfersAllowed returns whether to only use "tus.io" HTTP transfers.
   224  // Default is false, including if the lfs.tustransfers is invalid
   225  func (c *Configuration) TusTransfersAllowed() bool {
   226  	return c.Git.Bool("lfs.tustransfers", false)
   227  }
   228  
   229  func (c *Configuration) FetchIncludePaths() []string {
   230  	patterns, _ := c.Git.Get("lfs.fetchinclude")
   231  	return tools.CleanPaths(patterns, ",")
   232  }
   233  
   234  func (c *Configuration) FetchExcludePaths() []string {
   235  	patterns, _ := c.Git.Get("lfs.fetchexclude")
   236  	return tools.CleanPaths(patterns, ",")
   237  }
   238  
   239  func (c *Configuration) Remotes() []string {
   240  	c.loadGitConfig()
   241  
   242  	return c.remotes
   243  }
   244  
   245  func (c *Configuration) Extensions() map[string]Extension {
   246  	c.loadGitConfig()
   247  	return c.extensions
   248  }
   249  
   250  // SortedExtensions gets the list of extensions ordered by Priority
   251  func (c *Configuration) SortedExtensions() ([]Extension, error) {
   252  	return SortExtensions(c.Extensions())
   253  }
   254  
   255  func (c *Configuration) FetchPruneConfig() FetchPruneConfig {
   256  	f := &FetchPruneConfig{
   257  		FetchRecentRefsDays:           7,
   258  		FetchRecentRefsIncludeRemotes: true,
   259  		PruneOffsetDays:               3,
   260  		PruneRemoteName:               "origin",
   261  	}
   262  
   263  	if err := c.Unmarshal(f); err != nil {
   264  		panic(err.Error())
   265  	}
   266  	return *f
   267  }
   268  
   269  func (c *Configuration) SkipDownloadErrors() bool {
   270  	return c.Os.Bool("GIT_LFS_SKIP_DOWNLOAD_ERRORS", false) || c.Git.Bool("lfs.skipdownloaderrors", false)
   271  }
   272  
   273  func (c *Configuration) SetLockableFilesReadOnly() bool {
   274  	return c.Os.Bool("GIT_LFS_SET_LOCKABLE_READONLY", true) && c.Git.Bool("lfs.setlockablereadonly", true)
   275  }
   276  
   277  // loadGitConfig is a temporary measure to support legacy behavior dependent on
   278  // accessing properties set by ReadGitConfig, namely:
   279  //  - `c.extensions`
   280  //  - `c.uniqRemotes`
   281  //  - `c.gitConfig`
   282  //
   283  // Since the *gitEnvironment is responsible for setting these values on the
   284  // (*config.Configuration) instance, we must call that method, if it exists.
   285  //
   286  // loadGitConfig returns a bool returning whether or not `loadGitConfig` was
   287  // called AND the method did not return early.
   288  func (c *Configuration) loadGitConfig() bool {
   289  	if g, ok := c.Git.(*gitEnvironment); ok {
   290  		return g.loadGitConfig()
   291  	}
   292  
   293  	return false
   294  }
   295  
   296  // CurrentCommitter returns the name/email that would be used to author a commit
   297  // with this configuration. In particular, the "user.name" and "user.email"
   298  // configuration values are used
   299  func (c *Configuration) CurrentCommitter() (name, email string) {
   300  	name, _ = c.Git.Get("user.name")
   301  	email, _ = c.Git.Get("user.email")
   302  	return
   303  }