github.com/stffabi/git-lfs@v2.3.5-0.20180214015214-8eeaa8d88902+incompatible/commands/lockverifier.go (about) 1 package commands 2 3 import ( 4 "fmt" 5 "sort" 6 "strconv" 7 "strings" 8 9 "github.com/git-lfs/git-lfs/config" 10 "github.com/git-lfs/git-lfs/errors" 11 "github.com/git-lfs/git-lfs/git" 12 "github.com/git-lfs/git-lfs/lfsapi" 13 "github.com/git-lfs/git-lfs/locking" 14 "github.com/git-lfs/git-lfs/tq" 15 ) 16 17 type verifyState byte 18 19 const ( 20 verifyStateUnknown verifyState = iota 21 verifyStateEnabled 22 verifyStateDisabled 23 ) 24 25 func verifyLocksForUpdates(lv *lockVerifier, updates []*git.RefUpdate) { 26 for _, update := range updates { 27 lv.Verify(update.Right()) 28 } 29 } 30 31 // lockVerifier verifies locked files before updating one or more refs. 32 type lockVerifier struct { 33 endpoint lfsapi.Endpoint 34 verifyState verifyState 35 verifiedRefs map[string]bool 36 37 // all existing locks 38 ourLocks map[string]*refLock 39 theirLocks map[string]*refLock 40 41 // locks from ourLocks that have been modified 42 ownedLocks []*refLock 43 44 // locks from theirLocks that have been modified 45 unownedLocks []*refLock 46 } 47 48 func (lv *lockVerifier) Verify(ref *git.Ref) { 49 if lv.verifyState == verifyStateDisabled || lv.verifiedRefs[ref.Refspec()] { 50 return 51 } 52 53 lockClient := newLockClient() 54 ours, theirs, err := lockClient.VerifiableLocks(ref, 0) 55 if err != nil { 56 if errors.IsNotImplementedError(err) { 57 disableFor(lv.endpoint.Url) 58 } else if lv.verifyState == verifyStateUnknown || lv.verifyState == verifyStateEnabled { 59 if errors.IsAuthError(err) { 60 if lv.verifyState == verifyStateUnknown { 61 Error("WARNING: Authentication error: %s", err) 62 } else if lv.verifyState == verifyStateEnabled { 63 Exit("ERROR: Authentication error: %s", err) 64 } 65 } else { 66 Print("Remote %q does not support the LFS locking API. Consider disabling it with:", cfg.PushRemote()) 67 Print(" $ git config lfs.%s.locksverify false", lv.endpoint.Url) 68 if lv.verifyState == verifyStateEnabled { 69 ExitWithError(err) 70 } 71 } 72 } 73 } else if lv.verifyState == verifyStateUnknown { 74 Print("Locking support detected on remote %q. Consider enabling it with:", cfg.PushRemote()) 75 Print(" $ git config lfs.%s.locksverify true", lv.endpoint.Url) 76 } 77 78 lv.addLocks(ref, ours, lv.ourLocks) 79 lv.addLocks(ref, theirs, lv.theirLocks) 80 lv.verifiedRefs[ref.Refspec()] = true 81 } 82 83 func (lv *lockVerifier) addLocks(ref *git.Ref, locks []locking.Lock, set map[string]*refLock) { 84 for _, l := range locks { 85 if rl, ok := set[l.Path]; ok { 86 if err := rl.Add(ref, l); err != nil { 87 Error("WARNING: error adding %q lock for ref %q: %+v", l.Path, ref, err) 88 } 89 } else { 90 set[l.Path] = lv.newRefLocks(ref, l) 91 } 92 } 93 } 94 95 // Determines if a filename is lockable. Implements lfs.GitScannerSet 96 func (lv *lockVerifier) Contains(name string) bool { 97 if lv == nil { 98 return false 99 } 100 _, ok := lv.theirLocks[name] 101 return ok 102 } 103 104 func (lv *lockVerifier) LockedByThem(name string) bool { 105 if lock, ok := lv.theirLocks[name]; ok { 106 lv.unownedLocks = append(lv.unownedLocks, lock) 107 return true 108 } 109 return false 110 } 111 112 func (lv *lockVerifier) LockedByUs(name string) bool { 113 if lock, ok := lv.ourLocks[name]; ok { 114 lv.ownedLocks = append(lv.ownedLocks, lock) 115 return true 116 } 117 return false 118 } 119 120 func (lv *lockVerifier) UnownedLocks() []*refLock { 121 return lv.unownedLocks 122 } 123 124 func (lv *lockVerifier) HasUnownedLocks() bool { 125 return len(lv.unownedLocks) > 0 126 } 127 128 func (lv *lockVerifier) OwnedLocks() []*refLock { 129 return lv.ownedLocks 130 } 131 132 func (lv *lockVerifier) HasOwnedLocks() bool { 133 return len(lv.ownedLocks) > 0 134 } 135 136 func (lv *lockVerifier) Enabled() bool { 137 return lv.verifyState == verifyStateEnabled 138 } 139 140 func (lv *lockVerifier) newRefLocks(ref *git.Ref, l locking.Lock) *refLock { 141 return &refLock{ 142 allRefs: lv.verifiedRefs, 143 path: l.Path, 144 refs: map[*git.Ref]locking.Lock{ref: l}, 145 } 146 } 147 148 func newLockVerifier(m *tq.Manifest) *lockVerifier { 149 lv := &lockVerifier{ 150 endpoint: getAPIClient().Endpoints.Endpoint("upload", cfg.PushRemote()), 151 verifiedRefs: make(map[string]bool), 152 ourLocks: make(map[string]*refLock), 153 theirLocks: make(map[string]*refLock), 154 } 155 156 // Do not check locks for standalone transfer, because there is no LFS 157 // server to ask. 158 if m.IsStandaloneTransfer() { 159 lv.verifyState = verifyStateDisabled 160 } else { 161 lv.verifyState = getVerifyStateFor(lv.endpoint.Url) 162 } 163 164 return lv 165 } 166 167 // refLock represents a unique locked file path, potentially across multiple 168 // refs. It tracks each individual lock in case different users locked the 169 // same path across multiple refs. 170 type refLock struct { 171 path string 172 allRefs map[string]bool 173 refs map[*git.Ref]locking.Lock 174 } 175 176 // Path returns the locked path. 177 func (r *refLock) Path() string { 178 return r.path 179 } 180 181 // Owners returns the list of owners that locked this file, including what 182 // specific refs the files were locked in. If a user locked a file on all refs, 183 // don't bother listing them. 184 // 185 // Example: technoweenie, bob (refs: foo) 186 func (r *refLock) Owners() string { 187 users := make(map[string][]string, len(r.refs)) 188 for ref, lock := range r.refs { 189 u := lock.Owner.Name 190 if _, ok := users[u]; !ok { 191 users[u] = make([]string, 0, len(r.refs)) 192 } 193 users[u] = append(users[u], ref.Name) 194 } 195 196 owners := make([]string, 0, len(users)) 197 for name, refs := range users { 198 seenRefCount := 0 199 for _, ref := range refs { 200 if r.allRefs[ref] { 201 seenRefCount++ 202 } 203 } 204 if seenRefCount == len(r.allRefs) { // lock is included in all refs, so don't list them 205 owners = append(owners, name) 206 continue 207 } 208 209 sort.Strings(refs) 210 owners = append(owners, fmt.Sprintf("%s (refs: %s)", name, strings.Join(refs, ", "))) 211 } 212 sort.Strings(owners) 213 return strings.Join(owners, ", ") 214 } 215 216 func (r *refLock) Add(ref *git.Ref, l locking.Lock) error { 217 r.refs[ref] = l 218 return nil 219 } 220 221 // getVerifyStateFor returns whether or not lock verification is enabled for the 222 // given url. If no state has been explicitly set, an "unknown" state will be 223 // returned instead. 224 func getVerifyStateFor(rawurl string) verifyState { 225 uc := config.NewURLConfig(cfg.Git) 226 227 v, ok := uc.Get("lfs", rawurl, "locksverify") 228 if !ok { 229 if supportsLockingAPI(rawurl) { 230 return verifyStateEnabled 231 } 232 return verifyStateUnknown 233 } 234 235 if enabled, _ := strconv.ParseBool(v); enabled { 236 return verifyStateEnabled 237 } 238 return verifyStateDisabled 239 }