As we reevaluate how to best support and maintain Staging Ref in the future, we encourage development teams using this environment to highlight their use cases in the following issue: https://gitlab.com/gitlab-com/gl-infra/software-delivery/framework/software-delivery-framework-issue-tracker/-/issues/36.

Skip to content
Snippets Groups Projects
Commit 4f2a5d78 authored by Kyle Edwards's avatar Kyle Edwards Committed by Joseph Snyder
Browse files

git-lfs-transfer: Add support for lock

parent 0152fb7d
No related branches found
No related tags found
No related merge requests found
Loading
Loading
@@ -267,7 +267,12 @@ func (l *gitlabLock) Unlock() error {
}
func (l *gitlabLock) AsArguments() []string {
return nil
return []string{
fmt.Sprintf("id=%s", l.id),
fmt.Sprintf("path=%s", l.path),
fmt.Sprintf("locked-at=%s", l.timestamp.Format(time.RFC3339)),
fmt.Sprintf("ownername=%s", l.owner),
}
}
func (l *gitlabLock) AsLockSpec(useOwnerID bool) ([]string, error) {
Loading
Loading
@@ -305,8 +310,19 @@ type gitlabLockBackend struct {
args map[string]string
}
func (b *gitlabLockBackend) Create(_ string, _ string) (transfer.Lock, error) {
return nil, newErrUnsupported("lock")
func (b *gitlabLockBackend) Create(path string, refname string) (transfer.Lock, error) {
l, err := b.client.Lock(path, refname)
var lock *gitlabLock
if l != nil {
lock = &gitlabLock{
gitlabLockBackend: b,
id: l.ID,
path: l.Path,
timestamp: l.LockedAt,
owner: l.Owner.Name,
}
}
return lock, err
}
func (b *gitlabLockBackend) Unlock(_ transfer.Lock) error {
Loading
Loading
Loading
Loading
@@ -756,14 +756,55 @@ func TestLfsTransferLock(t *testing.T) {
wg := setupWaitGroupForExecute(t, cmd)
negotiateVersion(t, pl)
writeCommandArgs(t, pl, "lock", []string{"path=large/file"})
writeCommandArgs(t, pl, "lock", []string{"path=/large/file/1"})
status, args, data := readStatusArgsAndTextData(t, pl)
require.Equal(t, "status 405", status)
require.Equal(t, "status 409", status)
require.Equal(t, []string{
"id=lock1",
"path=/large/file/1",
"locked-at=2023-10-03T13:56:20Z",
"ownername=johndoe",
}, args)
require.Equal(t, []string{
"conflict",
}, data)
writeCommandArgs(t, pl, "lock", []string{"path=/large/file/2"})
status, args, data = readStatusArgsAndTextData(t, pl)
require.Equal(t, "status 403", status)
require.Empty(t, args)
require.Equal(t, []string{
"error: lock is not yet supported by git-lfs-transfer. See https://gitlab.com/groups/gitlab-org/-/epics/11872 to track progress.",
"error: forbidden",
}, data)
writeCommandArgs(t, pl, "lock", []string{"path=/large/file/3"})
status, args, data = readStatusArgsAndTextData(t, pl)
require.Equal(t, "status 500", status)
require.Empty(t, args)
require.Equal(t, []string{
"internal error",
}, data)
writeCommandArgs(t, pl, "lock", []string{"path=/large/file/4"})
status, args = readStatusArgs(t, pl)
require.Equal(t, "status 201", status)
require.Equal(t, []string{
"id=lock4",
"path=/large/file/4",
"locked-at=2023-10-03T13:56:20Z",
"ownername=johndoe",
}, args)
writeCommandArgs(t, pl, "lock", []string{"path=/large/file/5", "refname=refs/heads/main"})
status, args = readStatusArgs(t, pl)
require.Equal(t, "status 201", status)
require.Equal(t, []string{
"id=lock5",
"path=/large/file/5",
"locked-at=2023-10-03T13:56:20Z",
"ownername=johndoe",
}, args)
quit(t, pl)
wg.Wait()
}
Loading
Loading
@@ -1243,8 +1284,66 @@ func setup(t *testing.T, keyID string, repo string, op string) (string, *Command
limit = l
}
bodyJSON.Locks, bodyJSON.NextCursor = listLocks(r.URL.Query().Get("cursor"), limit, r.URL.Query().Get("refspec"), r.URL.Query().Get("id"), r.URL.Query().Get("path"))
require.NoError(t, json.NewEncoder(w).Encode(bodyJSON))
case http.MethodPost:
var body map[string]interface{}
reader := json.NewDecoder(r.Body)
reader.Decode(&body)
var response map[string]interface{}
switch body["path"] {
case "/large/file/1":
response = map[string]interface{}{
"lock": map[string]interface{}{
"id": "lock1",
"path": "/large/file/1",
"locked_at": time.Date(2023, 10, 3, 13, 56, 20, 0, time.UTC).Format(time.RFC3339),
"owner": map[string]interface{}{
"name": "johndoe",
},
},
"message": "already created lock",
}
w.WriteHeader(http.StatusConflict)
case "/large/file/2":
response = map[string]interface{}{
"message": "no permission",
}
w.WriteHeader(http.StatusForbidden)
case "/large/file/4":
response = map[string]interface{}{
"lock": map[string]interface{}{
"id": "lock4",
"path": "/large/file/4",
"locked_at": time.Date(2023, 10, 3, 13, 56, 20, 0, time.UTC).Format(time.RFC3339),
"owner": map[string]interface{}{
"name": "johndoe",
},
},
}
w.WriteHeader(http.StatusCreated)
case "/large/file/5":
ref := body["ref"].(map[string]interface{})
require.Equal(t, "refs/heads/main", ref["name"])
response = map[string]interface{}{
"lock": map[string]interface{}{
"id": "lock5",
"path": "/large/file/5",
"locked_at": time.Date(2023, 10, 3, 13, 56, 20, 0, time.UTC).Format(time.RFC3339),
"owner": map[string]interface{}{
"name": "johndoe",
},
},
}
w.WriteHeader(http.StatusCreated)
default:
response = map[string]interface{}{
"message": "internal error",
}
w.WriteHeader(http.StatusInternalServerError)
}
writer := json.NewEncoder(w)
writer.Encode(response)
}
},
},
Loading
Loading
Loading
Loading
@@ -101,6 +101,15 @@ func (f *downloadedFile) Stat() (fs.FileInfo, error) {
return &f.downloadedFileInfo, nil
}
type lockRequest struct {
Path string `json:"path"`
Ref *batchRef `json:"ref,omitempty"`
}
type lockResponse struct {
Lock *Lock `json:"lock"`
}
type listLocksVerifyRequest struct {
Cursor string `json:"cursor,omitempty"`
Limit int `json:"limit"`
Loading
Loading
@@ -243,6 +252,63 @@ func (c *Client) PutObject(oid, href string, headers map[string]string, r io.Rea
return nil
}
func (c *Client) Lock(path, refname string) (*Lock, error) {
var ref *batchRef
if refname != "" {
ref = &batchRef{
Name: refname,
}
}
body := &lockRequest{
Path: path,
Ref: ref,
}
jsonData, err := json.Marshal(body)
if err != nil {
return nil, err
}
jsonReader := bytes.NewReader(jsonData)
req, err := http.NewRequest(http.MethodPost, c.href+"/locks", jsonReader)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/vnd.git-lfs+json")
req.Header.Set("Authorization", c.auth)
client := http.Client{}
res, err := client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
switch {
case res.StatusCode >= 200 && res.StatusCode <= 299:
response := &lockResponse{}
if err := gitlabnet.ParseJSON(res, response); err != nil {
return nil, err
}
return response.Lock, nil
case res.StatusCode == http.StatusForbidden:
return nil, transfer.ErrForbidden
case res.StatusCode == http.StatusNotFound:
return nil, transfer.ErrNotFound
case res.StatusCode == http.StatusConflict:
response := &lockResponse{}
if err := gitlabnet.ParseJSON(res, response); err != nil {
return nil, err
}
return response.Lock, transfer.ErrConflict
default:
return nil, fmt.Errorf("internal error")
}
}
func (c *Client) ListLocksVerify(path, id, cursor string, limit int, ref string) (*ListLocksVerifyResponse, error) {
url, err := url.Parse(c.href)
if err != nil {
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment