Imported Upstream version 2.3.3
[scm/test.git] / config / git_fetcher.go
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         ignored := make([]string, 0)
34
35         extensions = make(map[string]Extension)
36         uniqRemotes = make(map[string]bool)
37
38         for _, gc := range configs {
39                 uniqKeys := make(map[string]string)
40
41                 for _, line := range gc.Lines {
42                         pieces := strings.SplitN(line, "=", 2)
43                         if len(pieces) < 2 {
44                                 continue
45                         }
46
47                         allowed := !gc.OnlySafeKeys
48                         key, val := strings.ToLower(pieces[0]), pieces[1]
49
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)
55                                 }
56                         } else {
57                                 uniqKeys[key] = pieces[0]
58                         }
59
60                         parts := strings.Split(key, ".")
61                         if len(parts) == 4 && parts[0] == "lfs" && parts[1] == "extension" {
62                                 // prop: lfs.extension.<name>.<prop>
63                                 name := parts[2]
64                                 prop := parts[3]
65
66                                 ext := extensions[name]
67                                 ext.Name = name
68
69                                 switch prop {
70                                 case "clean":
71                                         if gc.OnlySafeKeys {
72                                                 ignored = append(ignored, key)
73                                                 continue
74                                         }
75                                         ext.Clean = val
76                                 case "smudge":
77                                         if gc.OnlySafeKeys {
78                                                 ignored = append(ignored, key)
79                                                 continue
80                                         }
81                                         ext.Smudge = val
82                                 case "priority":
83                                         allowed = true
84                                         p, err := strconv.Atoi(val)
85                                         if err == nil && p >= 0 {
86                                                 ext.Priority = p
87                                         }
88                                 }
89
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)
94                                         continue
95                                 }
96
97                                 allowed = true
98                                 remote := parts[1]
99                                 uniqRemotes[remote] = remote == "origin"
100                         } else if len(parts) > 2 && parts[len(parts)-1] == "access" {
101                                 allowed = true
102                         }
103
104                         if !allowed && keyIsUnsafe(key) {
105                                 ignored = append(ignored, key)
106                                 continue
107                         }
108
109                         vals[key] = append(vals[key], val)
110                 }
111         }
112
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)
117                 }
118         }
119
120         gf = &GitFetcher{vals: vals}
121
122         return
123 }
124
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
128 // absent.
129 //
130 // Map lookup by key is case-insensitive, as per the .gitconfig specification.
131 //
132 // Get is safe to call across multiple goroutines.
133 func (g *GitFetcher) Get(key string) (val string, ok bool) {
134         all := g.GetAll(key)
135
136         if len(all) == 0 {
137                 return "", false
138         }
139         return all[len(all)-1], true
140 }
141
142 func (g *GitFetcher) GetAll(key string) []string {
143         g.vmu.RLock()
144         defer g.vmu.RUnlock()
145
146         return g.vals[strings.ToLower(key)]
147 }
148
149 func (g *GitFetcher) All() map[string][]string {
150         newmap := make(map[string][]string)
151
152         g.vmu.RLock()
153         defer g.vmu.RUnlock()
154
155         for key, values := range g.vals {
156                 for _, value := range values {
157                         newmap[key] = append(newmap[key], value)
158                 }
159         }
160
161         return newmap
162 }
163
164 func getGitConfigs() (sources []*GitConfig) {
165         if lfsconfig := getFileGitConfig(".lfsconfig"); lfsconfig != nil {
166                 sources = append(sources, lfsconfig)
167         }
168
169         globalList, err := git.Config.List()
170         if err == nil {
171                 sources = append(sources, NewGitConfig(globalList, false))
172         } else {
173                 fmt.Fprintf(os.Stderr, "Error reading git config: %s\n", err)
174         }
175
176         return
177 }
178
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)
184                 }
185                 return nil
186         }
187
188         lines, err := git.Config.ListFromFile(fullname)
189         if err == nil {
190                 return NewGitConfig(lines, true)
191         }
192
193         fmt.Fprintf(os.Stderr, "Error reading %s: %s\n", basename, err)
194         return nil
195 }
196
197 func keyIsUnsafe(key string) bool {
198         for _, safe := range safeKeys {
199                 if safe == key {
200                         return false
201                 }
202         }
203         return true
204 }
205
206 var safeKeys = []string{
207         "lfs.fetchexclude",
208         "lfs.fetchinclude",
209         "lfs.gitprotocol",
210         "lfs.pushurl",
211         "lfs.url",
212 }