11 "github.com/git-lfs/git-lfs/git"
14 type GitFetcher struct {
16 vals map[string][]string
19 type GitConfig struct {
24 func NewGitConfig(gitconfiglines string, onlysafe bool) *GitConfig {
26 Lines: strings.Split(gitconfiglines, "\n"),
27 OnlySafeKeys: onlysafe,
31 func ReadGitConfig(configs ...*GitConfig) (gf *GitFetcher, extensions map[string]Extension, uniqRemotes map[string]bool) {
32 vals := make(map[string][]string)
33 ignored := make([]string, 0)
35 extensions = make(map[string]Extension)
36 uniqRemotes = make(map[string]bool)
38 for _, gc := range configs {
39 uniqKeys := make(map[string]string)
41 for _, line := range gc.Lines {
42 pieces := strings.SplitN(line, "=", 2)
47 allowed := !gc.OnlySafeKeys
48 key, val := strings.ToLower(pieces[0]), pieces[1]
50 if origKey, ok := uniqKeys[key]; ok {
51 if ShowConfigWarnings && len(vals[key]) > 0 && vals[key][len(vals[key])-1] != val && strings.HasPrefix(key, gitConfigWarningPrefix) {
52 fmt.Fprintf(os.Stderr, "WARNING: These git config values clash:\n")
53 fmt.Fprintf(os.Stderr, " git config %q = %q\n", origKey, vals[key])
54 fmt.Fprintf(os.Stderr, " git config %q = %q\n", pieces[0], val)
57 uniqKeys[key] = pieces[0]
60 parts := strings.Split(key, ".")
61 if len(parts) == 4 && parts[0] == "lfs" && parts[1] == "extension" {
62 // prop: lfs.extension.<name>.<prop>
66 ext := extensions[name]
72 ignored = append(ignored, key)
78 ignored = append(ignored, key)
84 p, err := strconv.Atoi(val)
85 if err == nil && p >= 0 {
90 extensions[name] = ext
91 } else if len(parts) > 1 && parts[0] == "remote" {
92 if gc.OnlySafeKeys && (len(parts) == 3 && parts[2] != "lfsurl") {
93 ignored = append(ignored, key)
99 uniqRemotes[remote] = remote == "origin"
100 } else if len(parts) > 2 && parts[len(parts)-1] == "access" {
104 if !allowed && keyIsUnsafe(key) {
105 ignored = append(ignored, key)
109 vals[key] = append(vals[key], val)
113 if len(ignored) > 0 {
114 fmt.Fprintf(os.Stderr, "WARNING: These unsafe lfsconfig keys were ignored:\n\n")
115 for _, key := range ignored {
116 fmt.Fprintf(os.Stderr, " %s\n", key)
120 gf = &GitFetcher{vals: vals}
125 // Get implements the Fetcher interface, and returns the value associated with
126 // a given key and true, signaling that the value was present. Otherwise, an
127 // empty string and false will be returned, signaling that the value was
130 // Map lookup by key is case-insensitive, as per the .gitconfig specification.
132 // Get is safe to call across multiple goroutines.
133 func (g *GitFetcher) Get(key string) (val string, ok bool) {
139 return all[len(all)-1], true
142 func (g *GitFetcher) GetAll(key string) []string {
144 defer g.vmu.RUnlock()
146 return g.vals[strings.ToLower(key)]
149 func (g *GitFetcher) All() map[string][]string {
150 newmap := make(map[string][]string)
153 defer g.vmu.RUnlock()
155 for key, values := range g.vals {
156 for _, value := range values {
157 newmap[key] = append(newmap[key], value)
164 func getGitConfigs() (sources []*GitConfig) {
165 if lfsconfig := getFileGitConfig(".lfsconfig"); lfsconfig != nil {
166 sources = append(sources, lfsconfig)
169 globalList, err := git.Config.List()
171 sources = append(sources, NewGitConfig(globalList, false))
173 fmt.Fprintf(os.Stderr, "Error reading git config: %s\n", err)
179 func getFileGitConfig(basename string) *GitConfig {
180 fullname := filepath.Join(LocalWorkingDir, basename)
181 if _, err := os.Stat(fullname); err != nil {
182 if !os.IsNotExist(err) {
183 fmt.Fprintf(os.Stderr, "Error reading %s: %s\n", basename, err)
188 lines, err := git.Config.ListFromFile(fullname)
190 return NewGitConfig(lines, true)
193 fmt.Fprintf(os.Stderr, "Error reading %s: %s\n", basename, err)
197 func keyIsUnsafe(key string) bool {
198 for _, safe := range safeKeys {
206 var safeKeys = []string{