github.com/2lambda123/git-lfs@v2.5.2+incompatible/locking/api.go (about) 1 package locking 2 3 import ( 4 "fmt" 5 "net/http" 6 "strconv" 7 8 "github.com/git-lfs/git-lfs/git" 9 "github.com/git-lfs/git-lfs/lfsapi" 10 ) 11 12 type lockClient struct { 13 *lfsapi.Client 14 } 15 16 type lockRef struct { 17 Name string `json:"name,omitempty"` 18 } 19 20 // LockRequest encapsulates the payload sent across the API when a client would 21 // like to obtain a lock against a particular path on a given remote. 22 type lockRequest struct { 23 // Path is the path that the client would like to obtain a lock against. 24 Path string `json:"path"` 25 Ref *lockRef `json:"ref,omitempty"` 26 } 27 28 // LockResponse encapsulates the information sent over the API in response to 29 // a `LockRequest`. 30 type lockResponse struct { 31 // Lock is the Lock that was optionally created in response to the 32 // payload that was sent (see above). If the lock already exists, then 33 // the existing lock is sent in this field instead, and the author of 34 // that lock remains the same, meaning that the client failed to obtain 35 // that lock. An HTTP status of "409 - Conflict" is used here. 36 // 37 // If the lock was unable to be created, this field will hold the 38 // zero-value of Lock and the Err field will provide a more detailed set 39 // of information. 40 // 41 // If an error was experienced in creating this lock, then the 42 // zero-value of Lock should be sent here instead. 43 Lock *Lock `json:"lock"` 44 45 // Message is the optional error that was encountered while trying to create 46 // the above lock. 47 Message string `json:"message,omitempty"` 48 DocumentationURL string `json:"documentation_url,omitempty"` 49 RequestID string `json:"request_id,omitempty"` 50 } 51 52 func (c *lockClient) Lock(remote string, lockReq *lockRequest) (*lockResponse, *http.Response, error) { 53 e := c.Endpoints.Endpoint("upload", remote) 54 req, err := c.NewRequest("POST", e, "locks", lockReq) 55 if err != nil { 56 return nil, nil, err 57 } 58 59 req = c.LogRequest(req, "lfs.locks.lock") 60 res, err := c.DoWithAuth(remote, req) 61 if err != nil { 62 return nil, res, err 63 } 64 65 lockRes := &lockResponse{} 66 return lockRes, res, lfsapi.DecodeJSON(res, lockRes) 67 } 68 69 // UnlockRequest encapsulates the data sent in an API request to remove a lock. 70 type unlockRequest struct { 71 // Force determines whether or not the lock should be "forcibly" 72 // unlocked; that is to say whether or not a given individual should be 73 // able to break a different individual's lock. 74 Force bool `json:"force"` 75 Ref *lockRef `json:"ref,omitempty"` 76 } 77 78 // UnlockResponse is the result sent back from the API when asked to remove a 79 // lock. 80 type unlockResponse struct { 81 // Lock is the lock corresponding to the asked-about lock in the 82 // `UnlockPayload` (see above). If no matching lock was found, this 83 // field will take the zero-value of Lock, and Err will be non-nil. 84 Lock *Lock `json:"lock"` 85 86 // Message is an optional field which holds any error that was experienced 87 // while removing the lock. 88 Message string `json:"message,omitempty"` 89 DocumentationURL string `json:"documentation_url,omitempty"` 90 RequestID string `json:"request_id,omitempty"` 91 } 92 93 func (c *lockClient) Unlock(ref *git.Ref, remote, id string, force bool) (*unlockResponse, *http.Response, error) { 94 e := c.Endpoints.Endpoint("upload", remote) 95 suffix := fmt.Sprintf("locks/%s/unlock", id) 96 req, err := c.NewRequest("POST", e, suffix, &unlockRequest{ 97 Force: force, 98 Ref: &lockRef{Name: ref.Refspec()}, 99 }) 100 if err != nil { 101 return nil, nil, err 102 } 103 104 req = c.LogRequest(req, "lfs.locks.unlock") 105 res, err := c.DoWithAuth(remote, req) 106 if err != nil { 107 return nil, res, err 108 } 109 110 unlockRes := &unlockResponse{} 111 err = lfsapi.DecodeJSON(res, unlockRes) 112 return unlockRes, res, err 113 } 114 115 // Filter represents a single qualifier to apply against a set of locks. 116 type lockFilter struct { 117 // Property is the property to search against. 118 // Value is the value that the property must take. 119 Property, Value string 120 } 121 122 // LockSearchRequest encapsulates the request sent to the server when the client 123 // would like a list of locks that match the given criteria. 124 type lockSearchRequest struct { 125 // Filters is the set of filters to query against. If the client wishes 126 // to obtain a list of all locks, an empty array should be passed here. 127 Filters []lockFilter 128 // Cursor is an optional field used to tell the server which lock was 129 // seen last, if scanning through multiple pages of results. 130 // 131 // Servers must return a list of locks sorted in reverse chronological 132 // order, so the Cursor provides a consistent method of viewing all 133 // locks, even if more were created between two requests. 134 Cursor string 135 // Limit is the maximum number of locks to return in a single page. 136 Limit int 137 138 Refspec string 139 } 140 141 func (r *lockSearchRequest) QueryValues() map[string]string { 142 q := make(map[string]string) 143 for _, filter := range r.Filters { 144 q[filter.Property] = filter.Value 145 } 146 147 if len(r.Cursor) > 0 { 148 q["cursor"] = r.Cursor 149 } 150 151 if r.Limit > 0 { 152 q["limit"] = strconv.Itoa(r.Limit) 153 } 154 155 if len(r.Refspec) > 0 { 156 q["refspec"] = r.Refspec 157 } 158 159 return q 160 } 161 162 // LockList encapsulates a set of Locks. 163 type lockList struct { 164 // Locks is the set of locks returned back, typically matching the query 165 // parameters sent in the LockListRequest call. If no locks were matched 166 // from a given query, then `Locks` will be represented as an empty 167 // array. 168 Locks []Lock `json:"locks"` 169 // NextCursor returns the Id of the Lock the client should update its 170 // cursor to, if there are multiple pages of results for a particular 171 // `LockListRequest`. 172 NextCursor string `json:"next_cursor,omitempty"` 173 // Message populates any error that was encountered during the search. If no 174 // error was encountered and the operation was succesful, then a value 175 // of nil will be passed here. 176 Message string `json:"message,omitempty"` 177 DocumentationURL string `json:"documentation_url,omitempty"` 178 RequestID string `json:"request_id,omitempty"` 179 } 180 181 func (c *lockClient) Search(remote string, searchReq *lockSearchRequest) (*lockList, *http.Response, error) { 182 e := c.Endpoints.Endpoint("upload", remote) 183 req, err := c.NewRequest("GET", e, "locks", nil) 184 if err != nil { 185 return nil, nil, err 186 } 187 188 q := req.URL.Query() 189 for key, value := range searchReq.QueryValues() { 190 q.Add(key, value) 191 } 192 req.URL.RawQuery = q.Encode() 193 194 req = c.LogRequest(req, "lfs.locks.search") 195 res, err := c.DoWithAuth(remote, req) 196 if err != nil { 197 return nil, res, err 198 } 199 200 locks := &lockList{} 201 if res.StatusCode == http.StatusOK { 202 err = lfsapi.DecodeJSON(res, locks) 203 } 204 205 return locks, res, err 206 } 207 208 // lockVerifiableRequest encapsulates the request sent to the server when the 209 // client would like a list of locks to verify a Git push. 210 type lockVerifiableRequest struct { 211 Ref *lockRef `json:"ref,omitempty"` 212 213 // Cursor is an optional field used to tell the server which lock was 214 // seen last, if scanning through multiple pages of results. 215 // 216 // Servers must return a list of locks sorted in reverse chronological 217 // order, so the Cursor provides a consistent method of viewing all 218 // locks, even if more were created between two requests. 219 Cursor string `json:"cursor,omitempty"` 220 // Limit is the maximum number of locks to return in a single page. 221 Limit int `json:"limit,omitempty"` 222 } 223 224 // lockVerifiableList encapsulates a set of Locks to verify a Git push. 225 type lockVerifiableList struct { 226 // Ours is the set of locks returned back matching filenames that the user 227 // is allowed to edit. 228 Ours []Lock `json:"ours"` 229 230 // Their is the set of locks returned back matching filenames that the user 231 // is NOT allowed to edit. Any edits matching these files should reject 232 // the Git push. 233 Theirs []Lock `json:"theirs"` 234 235 // NextCursor returns the Id of the Lock the client should update its 236 // cursor to, if there are multiple pages of results for a particular 237 // `LockListRequest`. 238 NextCursor string `json:"next_cursor,omitempty"` 239 // Message populates any error that was encountered during the search. If no 240 // error was encountered and the operation was succesful, then a value 241 // of nil will be passed here. 242 Message string `json:"message,omitempty"` 243 DocumentationURL string `json:"documentation_url,omitempty"` 244 RequestID string `json:"request_id,omitempty"` 245 } 246 247 func (c *lockClient) SearchVerifiable(remote string, vreq *lockVerifiableRequest) (*lockVerifiableList, *http.Response, error) { 248 e := c.Endpoints.Endpoint("upload", remote) 249 req, err := c.NewRequest("POST", e, "locks/verify", vreq) 250 if err != nil { 251 return nil, nil, err 252 } 253 254 req = c.LogRequest(req, "lfs.locks.verify") 255 res, err := c.DoWithAuth(remote, req) 256 if err != nil { 257 return nil, res, err 258 } 259 260 locks := &lockVerifiableList{} 261 if res.StatusCode == http.StatusOK { 262 err = lfsapi.DecodeJSON(res, locks) 263 } 264 265 return locks, res, err 266 } 267 268 // User represents the owner of a lock. 269 type User struct { 270 // Name is the name of the individual who would like to obtain the 271 // lock, for instance: "Rick Sanchez". 272 Name string `json:"name"` 273 } 274 275 func NewUser(name string) *User { 276 return &User{Name: name} 277 } 278 279 // String implements the fmt.Stringer interface. 280 func (u *User) String() string { 281 return u.Name 282 }