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 }