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 }