github.com/psexton/git-lfs@v2.1.1-0.20170517224304-289a18b2bc53+incompatible/config/git_fetcher.go (about)

     1  package config
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"strconv"
     8  	"strings"
     9  	"sync"
    10  
    11  	"github.com/git-lfs/git-lfs/git"
    12  )
    13  
    14  type GitFetcher struct {
    15  	vmu  sync.RWMutex
    16  	vals map[string][]string
    17  }
    18  
    19  type GitConfig struct {
    20  	Lines        []string
    21  	OnlySafeKeys bool
    22  }
    23  
    24  func NewGitConfig(gitconfiglines string, onlysafe bool) *GitConfig {
    25  	return &GitConfig{
    26  		Lines:        strings.Split(gitconfiglines, "\n"),
    27  		OnlySafeKeys: onlysafe,
    28  	}
    29  }
    30  
    31  func ReadGitConfig(configs ...*GitConfig) (gf *GitFetcher, extensions map[string]Extension, uniqRemotes map[string]bool) {
    32  	vals := make(map[string][]string)
    33  
    34  	extensions = make(map[string]Extension)
    35  	uniqRemotes = make(map[string]bool)
    36  
    37  	for _, gc := range configs {
    38  		uniqKeys := make(map[string]string)
    39  
    40  		for _, line := range gc.Lines {
    41  			pieces := strings.SplitN(line, "=", 2)
    42  			if len(pieces) < 2 {
    43  				continue
    44  			}
    45  
    46  			allowed := !gc.OnlySafeKeys
    47  			key, val := strings.ToLower(pieces[0]), pieces[1]
    48  
    49  			if origKey, ok := uniqKeys[key]; ok {
    50  				if ShowConfigWarnings && len(vals[key]) > 0 && vals[key][len(vals[key])-1] != val && strings.HasPrefix(key, gitConfigWarningPrefix) {
    51  					fmt.Fprintf(os.Stderr, "WARNING: These git config values clash:\n")
    52  					fmt.Fprintf(os.Stderr, "  git config %q = %q\n", origKey, vals[key])
    53  					fmt.Fprintf(os.Stderr, "  git config %q = %q\n", pieces[0], val)
    54  				}
    55  			} else {
    56  				uniqKeys[key] = pieces[0]
    57  			}
    58  
    59  			parts := strings.Split(key, ".")
    60  			if len(parts) == 4 && parts[0] == "lfs" && parts[1] == "extension" {
    61  				// prop: lfs.extension.<name>.<prop>
    62  				name := parts[2]
    63  				prop := parts[3]
    64  
    65  				ext := extensions[name]
    66  				ext.Name = name
    67  
    68  				switch prop {
    69  				case "clean":
    70  					if gc.OnlySafeKeys {
    71  						continue
    72  					}
    73  					ext.Clean = val
    74  				case "smudge":
    75  					if gc.OnlySafeKeys {
    76  						continue
    77  					}
    78  					ext.Smudge = val
    79  				case "priority":
    80  					allowed = true
    81  					p, err := strconv.Atoi(val)
    82  					if err == nil && p >= 0 {
    83  						ext.Priority = p
    84  					}
    85  				}
    86  
    87  				extensions[name] = ext
    88  			} else if len(parts) > 1 && parts[0] == "remote" {
    89  				if gc.OnlySafeKeys && (len(parts) == 3 && parts[2] != "lfsurl") {
    90  					continue
    91  				}
    92  
    93  				allowed = true
    94  				remote := parts[1]
    95  				uniqRemotes[remote] = remote == "origin"
    96  			} else if len(parts) > 2 && parts[len(parts)-1] == "access" {
    97  				allowed = true
    98  			}
    99  
   100  			if !allowed && keyIsUnsafe(key) {
   101  				continue
   102  			}
   103  
   104  			vals[key] = append(vals[key], val)
   105  		}
   106  	}
   107  
   108  	gf = &GitFetcher{vals: vals}
   109  
   110  	return
   111  }
   112  
   113  // Get implements the Fetcher interface, and returns the value associated with
   114  // a given key and true, signaling that the value was present. Otherwise, an
   115  // empty string and false will be returned, signaling that the value was
   116  // absent.
   117  //
   118  // Map lookup by key is case-insensitive, as per the .gitconfig specification.
   119  //
   120  // Get is safe to call across multiple goroutines.
   121  func (g *GitFetcher) Get(key string) (val string, ok bool) {
   122  	all := g.GetAll(key)
   123  
   124  	if len(all) == 0 {
   125  		return "", false
   126  	}
   127  	return all[len(all)-1], true
   128  }
   129  
   130  func (g *GitFetcher) GetAll(key string) []string {
   131  	g.vmu.RLock()
   132  	defer g.vmu.RUnlock()
   133  
   134  	return g.vals[strings.ToLower(key)]
   135  }
   136  
   137  func (g *GitFetcher) All() map[string][]string {
   138  	newmap := make(map[string][]string)
   139  
   140  	g.vmu.RLock()
   141  	defer g.vmu.RUnlock()
   142  
   143  	for key, values := range g.vals {
   144  		for _, value := range values {
   145  			newmap[key] = append(newmap[key], value)
   146  		}
   147  	}
   148  
   149  	return newmap
   150  }
   151  
   152  func getGitConfigs() (sources []*GitConfig) {
   153  	if lfsconfig := getFileGitConfig(".lfsconfig"); lfsconfig != nil {
   154  		sources = append(sources, lfsconfig)
   155  	}
   156  
   157  	globalList, err := git.Config.List()
   158  	if err == nil {
   159  		sources = append(sources, NewGitConfig(globalList, false))
   160  	} else {
   161  		fmt.Fprintf(os.Stderr, "Error reading git config: %s\n", err)
   162  	}
   163  
   164  	return
   165  }
   166  
   167  func getFileGitConfig(basename string) *GitConfig {
   168  	fullname := filepath.Join(LocalWorkingDir, basename)
   169  	if _, err := os.Stat(fullname); err != nil {
   170  		if !os.IsNotExist(err) {
   171  			fmt.Fprintf(os.Stderr, "Error reading %s: %s\n", basename, err)
   172  		}
   173  		return nil
   174  	}
   175  
   176  	lines, err := git.Config.ListFromFile(fullname)
   177  	if err == nil {
   178  		return NewGitConfig(lines, true)
   179  	}
   180  
   181  	fmt.Fprintf(os.Stderr, "Error reading %s: %s\n", basename, err)
   182  	return nil
   183  }
   184  
   185  func keyIsUnsafe(key string) bool {
   186  	for _, safe := range safeKeys {
   187  		if safe == key {
   188  			return false
   189  		}
   190  	}
   191  	return true
   192  }
   193  
   194  var safeKeys = []string{
   195  	"lfs.fetchexclude",
   196  	"lfs.fetchinclude",
   197  	"lfs.gitprotocol",
   198  	"lfs.pushurl",
   199  	"lfs.url",
   200  }