2016-08-05 20:16:29 +00:00
package config
import (
"fmt"
"os"
2016-08-05 21:40:17 +00:00
"path/filepath"
2016-08-05 20:16:29 +00:00
"strconv"
"strings"
"sync"
2016-08-05 21:40:17 +00:00
"github.com/github/git-lfs/git"
2016-08-05 20:16:29 +00:00
)
type GitFetcher struct {
vmu sync . RWMutex
vals map [ string ] string
}
type GitConfig struct {
Lines [ ] string
OnlySafeKeys bool
}
2016-08-05 21:40:17 +00:00
func NewGitConfig ( gitconfiglines string , onlysafe bool ) * GitConfig {
return & GitConfig {
Lines : strings . Split ( gitconfiglines , "\n" ) ,
OnlySafeKeys : onlysafe ,
}
}
2016-08-05 21:13:31 +00:00
func ReadGitConfig ( configs ... * GitConfig ) ( gf * GitFetcher , extensions map [ string ] Extension , uniqRemotes map [ string ] bool ) {
2016-08-05 20:16:29 +00:00
vals := make ( map [ string ] string )
extensions = make ( map [ string ] Extension )
uniqRemotes = make ( map [ string ] bool )
for _ , gc := range configs {
uniqKeys := make ( map [ string ] string )
for _ , line := range gc . Lines {
pieces := strings . SplitN ( line , "=" , 2 )
if len ( pieces ) < 2 {
continue
}
allowed := ! gc . OnlySafeKeys
key , val := strings . ToLower ( pieces [ 0 ] ) , pieces [ 1 ]
if origKey , ok := uniqKeys [ key ] ; ok {
if ShowConfigWarnings && vals [ key ] != val && strings . HasPrefix ( key , gitConfigWarningPrefix ) {
fmt . Fprintf ( os . Stderr , "WARNING: These git config values clash:\n" )
fmt . Fprintf ( os . Stderr , " git config %q = %q\n" , origKey , vals [ key ] )
fmt . Fprintf ( os . Stderr , " git config %q = %q\n" , pieces [ 0 ] , val )
}
} else {
uniqKeys [ key ] = pieces [ 0 ]
}
parts := strings . Split ( key , "." )
if len ( parts ) == 4 && parts [ 0 ] == "lfs" && parts [ 1 ] == "extension" {
// prop: lfs.extension.<name>.<prop>
name := parts [ 2 ]
prop := parts [ 3 ]
ext := extensions [ name ]
ext . Name = name
switch prop {
case "clean" :
if gc . OnlySafeKeys {
continue
}
ext . Clean = val
case "smudge" :
if gc . OnlySafeKeys {
continue
}
2016-08-05 21:13:31 +00:00
ext . Smudge = val
2016-08-05 20:16:29 +00:00
case "priority" :
allowed = true
p , err := strconv . Atoi ( val )
if err == nil && p >= 0 {
ext . Priority = p
}
}
extensions [ name ] = ext
} else if len ( parts ) > 1 && parts [ 0 ] == "remote" {
if gc . OnlySafeKeys && ( len ( parts ) == 3 && parts [ 2 ] != "lfsurl" ) {
continue
}
allowed = true
remote := parts [ 1 ]
uniqRemotes [ remote ] = remote == "origin"
} else if len ( parts ) > 2 && parts [ len ( parts ) - 1 ] == "access" {
allowed = true
}
if ! allowed && keyIsUnsafe ( key ) {
continue
}
vals [ key ] = val
}
}
gf = & GitFetcher { vals : vals }
return
}
2016-08-16 05:23:55 +00:00
// Get implements the Fetcher interface, and returns the value associated with
// a given key and true, signaling that the value was present. Otherwise, an
// empty string and false will be returned, signaling that the value was
// absent.
//
// Map lookup by key is case-insensitive, as per the .gitconfig specification.
//
// Get is safe to call across multiple goroutines.
2016-08-05 21:59:57 +00:00
func ( g * GitFetcher ) Get ( key string ) ( val string , ok bool ) {
2016-08-05 20:16:29 +00:00
g . vmu . RLock ( )
defer g . vmu . RUnlock ( )
2016-08-16 05:23:55 +00:00
val , ok = g . vals [ strings . ToLower ( key ) ]
2016-08-05 21:59:57 +00:00
return
2016-08-05 20:16:29 +00:00
}
2016-11-10 00:33:43 +00:00
func ( g * GitFetcher ) All ( ) map [ string ] string {
newmap := make ( map [ string ] string )
g . vmu . RLock ( )
defer g . vmu . RUnlock ( )
for key , value := range g . vals {
newmap [ key ] = value
}
return newmap
}
2016-11-10 00:37:14 +00:00
func ( g * GitFetcher ) Del ( key string ) {
g . vmu . RLock ( )
defer g . vmu . RUnlock ( )
delete ( g . vals , strings . ToLower ( key ) )
}
2016-08-05 21:40:17 +00:00
func getGitConfigs ( ) ( sources [ ] * GitConfig ) {
if lfsconfig := getFileGitConfig ( ".lfsconfig" ) ; lfsconfig != nil {
sources = append ( sources , lfsconfig )
} else {
if gitconfig := getFileGitConfig ( ".gitconfig" ) ; gitconfig != nil {
if ShowConfigWarnings {
fmt . Fprintf ( os . Stderr , "WARNING: Reading LFS config from .gitconfig, not .lfsconfig. Rename to .lfsconfig before Git LFS v2.0 to remove this warning.\n" )
}
sources = append ( sources , gitconfig )
}
}
globalList , err := git . Config . List ( )
if err == nil {
sources = append ( sources , NewGitConfig ( globalList , false ) )
} else {
fmt . Fprintf ( os . Stderr , "Error reading git config: %s\n" , err )
}
return
}
func getFileGitConfig ( basename string ) * GitConfig {
fullname := filepath . Join ( LocalWorkingDir , basename )
if _ , err := os . Stat ( fullname ) ; err != nil {
if ! os . IsNotExist ( err ) {
fmt . Fprintf ( os . Stderr , "Error reading %s: %s\n" , basename , err )
}
return nil
}
lines , err := git . Config . ListFromFile ( fullname )
if err == nil {
return NewGitConfig ( lines , true )
}
fmt . Fprintf ( os . Stderr , "Error reading %s: %s\n" , basename , err )
return nil
}
2016-08-05 20:16:29 +00:00
func keyIsUnsafe ( key string ) bool {
for _ , safe := range safeKeys {
if safe == key {
return false
}
}
return true
}
var safeKeys = [ ] string {
"lfs.fetchexclude" ,
"lfs.fetchinclude" ,
"lfs.gitprotocol" ,
2016-08-26 21:09:55 +00:00
"lfs.pushurl" ,
2016-08-05 20:16:29 +00:00
"lfs.url" ,
}