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