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