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  }