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
Unverified Commit 1e98ed2a authored by Ash McKenzie's avatar Ash McKenzie Committed by GitLab
Browse files

Merge branch 'git-lfs-transfer-verify-listlock' into 'main'

parents 61aa3b16 0117f4a2
No related branches found
No related tags found
No related merge requests found
Loading
Loading
@@ -6,9 +6,11 @@ import (
"crypto/sha256"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"io/fs"
"time"
"github.com/charmbracelet/git-lfs-transfer/transfer"
"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
Loading
Loading
@@ -231,7 +233,8 @@ func (b *GitlabBackend) FinishUpload(_ io.Closer, _ transfer.Args) error {
}
func (b *GitlabBackend) Verify(_ string, _ transfer.Args) (transfer.Status, error) {
return nil, newErrUnsupported("verify-object")
// Not needed, all verification is done in upload step.
return transfer.SuccessStatus(), nil
}
func (b *GitlabBackend) Download(oid string, args transfer.Args) (fs.File, error) {
Loading
Loading
@@ -242,12 +245,21 @@ func (b *GitlabBackend) Download(oid string, args transfer.Args) (fs.File, error
return b.client.GetObject(oid, href, headers)
}
func (b *GitlabBackend) LockBackend(_ transfer.Args) transfer.LockBackend {
return &gitlabLockBackend{}
func (b *GitlabBackend) LockBackend(args transfer.Args) transfer.LockBackend {
return &gitlabLockBackend{
auth: b.auth,
client: b.client,
args: args,
}
}
type gitlabLock struct {
*gitlabLockBackend
id string
path string
timestamp time.Time
owner string
ownerid string
}
func (l *gitlabLock) Unlock() error {
Loading
Loading
@@ -258,27 +270,40 @@ func (l *gitlabLock) AsArguments() []string {
return nil
}
func (l *gitlabLock) AsLockSpec(_ bool) ([]string, error) {
return nil, nil
func (l *gitlabLock) AsLockSpec(useOwnerID bool) ([]string, error) {
spec := []string{
fmt.Sprintf("lock %s", l.id),
fmt.Sprintf("path %s %s", l.id, l.path),
fmt.Sprintf("locked-at %s %s", l.id, l.timestamp.Format(time.RFC3339)),
fmt.Sprintf("ownername %s %s", l.id, l.owner),
}
if useOwnerID {
spec = append(spec, fmt.Sprintf("owner %s %s", l.id, l.ownerid))
}
return spec, nil
}
func (l *gitlabLock) FormattedTimestamp() string {
return ""
return l.timestamp.Format("")
}
func (l *gitlabLock) ID() string {
return ""
return l.id
}
func (l *gitlabLock) OwnerName() string {
return ""
return l.owner
}
func (l *gitlabLock) Path() string {
return ""
return l.path
}
type gitlabLockBackend struct{}
type gitlabLockBackend struct {
auth *GitlabAuthentication
client *lfstransfer.Client
args map[string]string
}
func (b *gitlabLockBackend) Create(_ string, _ string) (transfer.Lock, error) {
return nil, newErrUnsupported("lock")
Loading
Loading
@@ -288,14 +313,75 @@ func (b *gitlabLockBackend) Unlock(_ transfer.Lock) error {
return newErrUnsupported("unlock")
}
func (b *gitlabLockBackend) FromPath(_ string) (transfer.Lock, error) {
return &gitlabLock{gitlabLockBackend: b}, nil
func (b *gitlabLockBackend) FromPath(path string) (transfer.Lock, error) {
res, err := b.client.ListLocksVerify(path, "", "", 1, "")
if err != nil {
return nil, err
}
var lock *lfstransfer.Lock
var owner string
switch {
case len(res.Ours) == 1 && len(res.Theirs) == 0:
lock = res.Ours[0]
owner = "ours"
case len(res.Ours) == 0 && len(res.Theirs) == 1:
lock = res.Theirs[0]
owner = "theirs"
case len(res.Ours) == 0 && len(res.Theirs) == 0:
return nil, nil
default:
return nil, errors.New("internal error")
}
return &gitlabLock{
gitlabLockBackend: b,
id: lock.ID,
path: lock.Path,
timestamp: lock.LockedAt,
owner: lock.Owner.Name,
ownerid: owner,
}, nil
}
func (b *gitlabLockBackend) FromID(_ string) (transfer.Lock, error) {
return &gitlabLock{gitlabLockBackend: b}, nil
func (b *gitlabLockBackend) FromID(id string) (transfer.Lock, error) {
return &gitlabLock{
gitlabLockBackend: b,
id: id,
}, nil
}
func (b *gitlabLockBackend) Range(_ string, _ int, _ func(transfer.Lock) error) (string, error) {
return "", newErrUnsupported("list-lock")
func (b *gitlabLockBackend) Range(cursor string, limit int, iter func(transfer.Lock) error) (string, error) {
res, err := b.client.ListLocksVerify(b.args["path"], b.args["id"], cursor, limit, b.args["refname"])
if err != nil {
return "", err
}
for _, lock := range res.Ours {
tlock := &gitlabLock{
gitlabLockBackend: b,
id: lock.ID,
path: lock.Path,
timestamp: lock.LockedAt,
owner: lock.Owner.Name,
ownerid: "ours",
}
err = iter(tlock)
if err != nil {
return "", err
}
}
for _, lock := range res.Theirs {
tlock := &gitlabLock{
gitlabLockBackend: b,
id: lock.ID,
path: lock.Path,
timestamp: lock.LockedAt,
owner: lock.Owner.Name,
ownerid: "theirs",
}
err = iter(tlock)
if err != nil {
return "", err
}
}
return res.NextCursor, nil
}
Loading
Loading
@@ -10,9 +10,11 @@ import (
"fmt"
"io"
"net/http"
"strconv"
"strings"
"sync"
"testing"
"time"
"github.com/git-lfs/pktline"
"github.com/stretchr/testify/require"
Loading
Loading
@@ -94,7 +96,7 @@ func readCapabilities(t *testing.T, pl *pktline.Pktline) {
var caps []string
end := false
for !end {
cap, l, err := pl.ReadPacketTextWithLength()
capability, l, err := pl.ReadPacketTextWithLength()
require.NoError(t, err)
switch l {
case 0:
Loading
Loading
@@ -102,7 +104,7 @@ func readCapabilities(t *testing.T, pl *pktline.Pktline) {
case 1:
require.Fail(t, "Expected text or flush packet, got delim packet")
default:
caps = append(caps, cap)
caps = append(caps, capability)
}
}
require.Equal(t, []string{
Loading
Loading
@@ -742,12 +744,8 @@ func TestLfsTransferVerifyObject(t *testing.T) {
negotiateVersion(t, pl)
writeCommandArgs(t, pl, "verify-object 00000000", []string{"size=0"})
status, args, data := readStatusArgsAndTextData(t, pl)
require.Equal(t, "status 405", status)
require.Empty(t, args)
require.Equal(t, []string{
"error: verify-object is not yet supported by git-lfs-transfer. See https://gitlab.com/groups/gitlab-org/-/epics/11872 to track progress.",
}, data)
status := readStatus(t, pl)
require.Equal(t, "status 200", status)
quit(t, pl)
wg.Wait()
Loading
Loading
@@ -787,24 +785,262 @@ func TestLfsTransferUnlock(t *testing.T) {
wg.Wait()
}
func TestLfsTransferListLock(t *testing.T) {
func TestLfsTransferListLockDownload(t *testing.T) {
_, cmd, pl, _ := setup(t, "rw", "group/repo", "download")
wg := setupWaitGroupForExecute(t, cmd)
negotiateVersion(t, pl)
writeCommand(t, pl, "list-lock")
status, args, data := readStatusArgsAndTextData(t, pl)
require.Equal(t, "status 405", status)
require.Equal(t, "status 200", status)
require.Empty(t, args)
require.Equal(t, []string{
"lock lock1",
"path lock1 /large/file/1",
"locked-at lock1 2023-10-03T13:56:20Z",
"ownername lock1 johndoe",
"lock lock2",
"path lock2 /large/file/2",
"locked-at lock2 1955-11-12T22:04:00Z",
"ownername lock2 marty",
"lock lock3",
"path lock3 /large/file/3",
"locked-at lock3 2023-10-03T13:56:20Z",
"ownername lock3 janedoe",
}, data)
writeCommandArgs(t, pl, "list-lock", []string{"limit=2"})
status, args, data = readStatusArgsAndTextData(t, pl)
require.Equal(t, "status 200", status)
require.Equal(t, []string{
"next-cursor=lock3",
}, args)
require.Equal(t, []string{
"lock lock1",
"path lock1 /large/file/1",
"locked-at lock1 2023-10-03T13:56:20Z",
"ownername lock1 johndoe",
"lock lock2",
"path lock2 /large/file/2",
"locked-at lock2 1955-11-12T22:04:00Z",
"ownername lock2 marty",
}, data)
writeCommandArgs(t, pl, "list-lock", []string{"cursor=lock2"})
status, args, data = readStatusArgsAndTextData(t, pl)
require.Equal(t, "status 200", status)
require.Empty(t, args)
require.Equal(t, []string{
"lock lock2",
"path lock2 /large/file/2",
"locked-at lock2 1955-11-12T22:04:00Z",
"ownername lock2 marty",
"lock lock3",
"path lock3 /large/file/3",
"locked-at lock3 2023-10-03T13:56:20Z",
"ownername lock3 janedoe",
}, data)
writeCommandArgs(t, pl, "list-lock", []string{"id=lock1"})
status, args, data = readStatusArgsAndTextData(t, pl)
require.Equal(t, "status 200", status)
require.Empty(t, args)
require.Equal(t, []string{
"lock lock1",
"path lock1 /large/file/1",
"locked-at lock1 2023-10-03T13:56:20Z",
"ownername lock1 johndoe",
}, data)
writeCommandArgs(t, pl, "list-lock", []string{"path=/large/file/2"})
status, args, data = readStatusArgsAndTextData(t, pl)
require.Equal(t, "status 200", status)
require.Empty(t, args)
require.Equal(t, []string{
"error: list-lock is not yet supported by git-lfs-transfer. See https://gitlab.com/groups/gitlab-org/-/epics/11872 to track progress.",
"lock lock2",
"path lock2 /large/file/2",
"locked-at lock2 1955-11-12T22:04:00Z",
"ownername lock2 marty",
}, data)
quit(t, pl)
wg.Wait()
}
func setup(t *testing.T, keyId string, repo string, op string) (string, *Command, *pktline.Pktline, *io.PipeReader) {
func TestLfsTransferListLockUpload(t *testing.T) {
_, cmd, pl, _ := setup(t, "rw", "group/repo", "upload")
wg := setupWaitGroupForExecute(t, cmd)
negotiateVersion(t, pl)
writeCommand(t, pl, "list-lock")
status, args, data := readStatusArgsAndTextData(t, pl)
require.Equal(t, "status 200", status)
require.Empty(t, args)
require.Equal(t, []string{
"lock lock1",
"path lock1 /large/file/1",
"locked-at lock1 2023-10-03T13:56:20Z",
"ownername lock1 johndoe",
"owner lock1 ours",
"lock lock2",
"path lock2 /large/file/2",
"locked-at lock2 1955-11-12T22:04:00Z",
"ownername lock2 marty",
"owner lock2 theirs",
"lock lock3",
"path lock3 /large/file/3",
"locked-at lock3 2023-10-03T13:56:20Z",
"ownername lock3 janedoe",
"owner lock3 theirs",
}, data)
writeCommandArgs(t, pl, "list-lock", []string{"limit=2"})
status, args, data = readStatusArgsAndTextData(t, pl)
require.Equal(t, "status 200", status)
require.Equal(t, []string{
"next-cursor=lock3",
}, args)
require.Equal(t, []string{
"lock lock1",
"path lock1 /large/file/1",
"locked-at lock1 2023-10-03T13:56:20Z",
"ownername lock1 johndoe",
"owner lock1 ours",
"lock lock2",
"path lock2 /large/file/2",
"locked-at lock2 1955-11-12T22:04:00Z",
"ownername lock2 marty",
"owner lock2 theirs",
}, data)
writeCommandArgs(t, pl, "list-lock", []string{"cursor=lock2"})
status, args, data = readStatusArgsAndTextData(t, pl)
require.Equal(t, "status 200", status)
require.Empty(t, args)
require.Equal(t, []string{
"lock lock2",
"path lock2 /large/file/2",
"locked-at lock2 1955-11-12T22:04:00Z",
"ownername lock2 marty",
"owner lock2 theirs",
"lock lock3",
"path lock3 /large/file/3",
"locked-at lock3 2023-10-03T13:56:20Z",
"ownername lock3 janedoe",
"owner lock3 theirs",
}, data)
writeCommandArgs(t, pl, "list-lock", []string{"id=lock1"})
status, args, data = readStatusArgsAndTextData(t, pl)
require.Equal(t, "status 200", status)
require.Empty(t, args)
require.Equal(t, []string{
"lock lock1",
"path lock1 /large/file/1",
"locked-at lock1 2023-10-03T13:56:20Z",
"ownername lock1 johndoe",
"owner lock1 ours",
}, data)
writeCommandArgs(t, pl, "list-lock", []string{"path=/large/file/2"})
status, args, data = readStatusArgsAndTextData(t, pl)
require.Equal(t, "status 200", status)
require.Empty(t, args)
require.Equal(t, []string{
"lock lock2",
"path lock2 /large/file/2",
"locked-at lock2 1955-11-12T22:04:00Z",
"ownername lock2 marty",
"owner lock2 theirs",
}, data)
quit(t, pl)
wg.Wait()
}
type Owner struct {
Name string `json:"name"`
}
type LockInfo struct {
ID string `json:"id"`
Path string `json:"path"`
LockedAt string `json:"locked_at"`
*Owner `json:"owner"`
}
func listLocks(cursor string, limit int, refspec string, id string, path string) (locks []*LockInfo, nextCursor string) {
allLocks := []struct {
Refspec string
*LockInfo
}{
{
Refspec: "main",
LockInfo: &LockInfo{
ID: "lock1",
Path: "/large/file/1",
LockedAt: time.Date(2023, 10, 3, 13, 56, 20, 0, time.UTC).Format(time.RFC3339),
Owner: &Owner{
Name: "johndoe",
},
},
},
{
Refspec: "my-branch",
LockInfo: &LockInfo{
ID: "lock2",
Path: "/large/file/2",
LockedAt: time.Date(1955, 11, 12, 22, 04, 0, 0, time.UTC).Format(time.RFC3339),
Owner: &Owner{
Name: "marty",
},
},
},
{
Refspec: "",
LockInfo: &LockInfo{
ID: "lock3",
Path: "/large/file/3",
LockedAt: time.Date(2023, 10, 3, 13, 56, 20, 0, time.UTC).Format(time.RFC3339),
Owner: &Owner{
Name: "janedoe",
},
},
},
}
for _, lock := range allLocks {
if cursor != "" && cursor != lock.ID {
continue
}
cursor = ""
if len(locks) >= limit {
nextCursor = lock.ID
break
}
if refspec != "" && refspec != lock.Refspec {
continue
}
if id != "" && id != lock.ID {
continue
}
if path != "" && path != lock.Path {
continue
}
locks = append(locks, lock.LockInfo)
}
return locks, nextCursor
}
func setup(t *testing.T, keyID string, repo string, op string) (string, *Command, *pktline.Pktline, *io.PipeReader) {
var url string
gitalyAddress, _ := testserver.StartGitalyServer(t, "unix")
Loading
Loading
@@ -940,7 +1176,7 @@ func setup(t *testing.T, keyId string, repo string, op string) (string, *Command
},
{
Path: "/evil-url",
Handler: func(_ http.ResponseWriter, r *http.Request) {
Handler: func(_ http.ResponseWriter, _ *http.Request) {
require.Fail(t, "An attacker accessed an evil URL")
},
},
Loading
Loading
@@ -960,6 +1196,58 @@ func setup(t *testing.T, keyId string, repo string, op string) (string, *Command
require.Equal(t, []byte(evenLargerFileContents), body)
},
},
{
Path: "/group/repo/info/lfs/locks/verify",
Handler: func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, http.MethodPost, r.Method)
requestJSON := &struct {
Cursor string `json:"cursor"`
Limit int `json:"limit"`
Ref struct {
Name string `json:"name"`
} `json:"ref"`
}{}
require.NoError(t, json.NewDecoder(r.Body).Decode(requestJSON))
bodyJSON := &struct {
Ours []*LockInfo `json:"ours,omitempty"`
Theirs []*LockInfo `json:"theirs,omitempty"`
NextCursor string `json:"next_cursor,omitempty"`
}{}
var locks []*LockInfo
locks, bodyJSON.NextCursor = listLocks(requestJSON.Cursor, requestJSON.Limit, requestJSON.Ref.Name, r.URL.Query().Get("id"), r.URL.Query().Get("path"))
for _, lock := range locks {
if lock.ID == "lock1" {
bodyJSON.Ours = append(bodyJSON.Ours, lock)
} else {
bodyJSON.Theirs = append(bodyJSON.Theirs, lock)
}
}
require.NoError(t, json.NewEncoder(w).Encode(bodyJSON))
},
},
{
Path: "/group/repo/info/lfs/locks",
Handler: func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
bodyJSON := &struct {
Locks []*LockInfo `json:"locks,omitempty"`
NextCursor string `json:"next_cursor,omitempty"`
}{}
limit := 100
if r.URL.Query().Has("limit") {
l, err := strconv.Atoi(r.URL.Query().Get("limit"))
require.NoError(t, err)
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))
}
},
},
}
url = testserver.StartHttpServer(t, requests)
Loading
Loading
@@ -970,7 +1258,7 @@ func setup(t *testing.T, keyId string, repo string, op string) (string, *Command
cmd := &Command{
Config: &config.Config{GitlabUrl: url, Secret: "very secret"},
Args: &commandargs.Shell{GitlabKeyId: keyId, SshArgs: []string{"git-lfs-transfer", repo, op}},
Args: &commandargs.Shell{GitlabKeyId: keyID, SshArgs: []string{"git-lfs-transfer", repo, op}},
ReadWriter: &readwriter.ReadWriter{ErrOut: errorSink, Out: outputSink, In: inputSource},
}
pl := pktline.NewPktline(outputSource, inputSink)
Loading
Loading
Loading
Loading
@@ -7,9 +7,11 @@ import (
"io"
"io/fs"
"net/http"
"net/url"
"time"
"github.com/charmbracelet/git-lfs-transfer/transfer"
"github.com/hashicorp/go-retryablehttp"
"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
Loading
Loading
@@ -20,6 +22,7 @@ type Client struct {
args *commandargs.Shell
href string
auth string
header string
}
type BatchAction struct {
Loading
Loading
@@ -98,19 +101,61 @@ func (f *downloadedFile) Stat() (fs.FileInfo, error) {
return &f.downloadedFileInfo, nil
}
type listLocksVerifyRequest struct {
Cursor string `json:"cursor,omitempty"`
Limit int `json:"limit"`
Ref *batchRef `json:"ref,omitempty"`
}
type LockOwner struct {
Name string `json:"name"`
}
type Lock struct {
ID string `json:"id"`
Path string `json:"path"`
LockedAt time.Time `json:"locked_at"`
Owner *LockOwner `json:"owner"`
}
type ListLocksResponse struct {
Locks []*Lock `json:"locks,omitempty"`
NextCursor string `json:"next_cursor,omitempty"`
}
type ListLocksVerifyResponse struct {
Ours []*Lock `json:"ours,omitempty"`
Theirs []*Lock `json:"theirs,omitempty"`
NextCursor string `json:"next_cursor,omitempty"`
}
var ClientHeader = "application/vnd.git-lfs+json"
func NewClient(config *config.Config, args *commandargs.Shell, href string, auth string) (*Client, error) {
return &Client{config: config, args: args, href: href, auth: auth}, nil
return &Client{config: config, args: args, href: href, auth: auth, header: ClientHeader}, nil
}
func newHTTPRequest(method string, ref string, reader io.Reader) (*retryablehttp.Request, error) {
req, err := retryablehttp.NewRequest(method, ref, reader)
if err != nil {
return nil, err
}
return req, nil
}
func (c *Client) Batch(operation string, reqObjects []*BatchObject, ref string, reqHashAlgo string) (*BatchResponse, error) {
var bref *batchRef
func newHTTPClient() *retryablehttp.Client {
client := retryablehttp.NewClient()
client.RetryMax = 3
client.Logger = nil
return client
}
func (c *Client) Batch(operation string, reqObjects []*BatchObject, ref string, reqHashAlgo string) (*BatchResponse, error) {
// FIXME: This causes tests to fail
// if ref == "" {
// return nil, errors.New("A ref must be specified.")
// }
bref = &batchRef{Name: ref}
bref := &batchRef{Name: ref}
body := batchRequest{
Operation: operation,
Objects: reqObjects,
Loading
Loading
@@ -125,15 +170,12 @@ func (c *Client) Batch(operation string, reqObjects []*BatchObject, ref string,
jsonReader := bytes.NewReader(jsonData)
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/objects/batch", c.href), jsonReader)
if err != nil {
return nil, err
}
req, _ := newHTTPRequest(http.MethodPost, fmt.Sprintf("%s/objects/batch", c.href), jsonReader)
req.Header.Set("Content-Type", "application/vnd.git-lfs+json")
req.Header.Set("Content-Type", c.header)
req.Header.Set("Authorization", c.auth)
client := newHTTPClient()
client := http.Client{}
res, err := client.Do(req)
if err != nil {
return nil, err
Loading
Loading
@@ -155,15 +197,12 @@ func (c *Client) Batch(operation string, reqObjects []*BatchObject, ref string,
}
func (c *Client) GetObject(oid, href string, headers map[string]string) (fs.File, error) {
req, err := http.NewRequest(http.MethodGet, href, nil)
if err != nil {
return nil, err
}
req, _ := newHTTPRequest(http.MethodGet, href, nil)
for key, value := range headers {
req.Header.Add(key, value)
}
client := http.Client{}
client := newHTTPClient()
// See https://gitlab.com/gitlab-org/gitlab-shell/-/merge_requests/989#note_1891153531 for
// discussion on bypassing the linter
res, err := client.Do(req) // nolint:bodyclose
Loading
Loading
@@ -184,15 +223,12 @@ func (c *Client) GetObject(oid, href string, headers map[string]string) (fs.File
}
func (c *Client) PutObject(oid, href string, headers map[string]string, r io.Reader) error {
req, err := http.NewRequest(http.MethodPut, href, r)
if err != nil {
return err
}
req, _ := newHTTPRequest(http.MethodPut, href, r)
for key, value := range headers {
req.Header.Add(key, value)
}
client := http.Client{}
client := newHTTPClient()
res, err := client.Do(req)
if err != nil {
return err
Loading
Loading
@@ -206,3 +242,51 @@ func (c *Client) PutObject(oid, href string, headers map[string]string, r io.Rea
}
return nil
}
func (c *Client) ListLocksVerify(path, id, cursor string, limit int, ref string) (*ListLocksVerifyResponse, error) {
url, err := url.Parse(c.href)
if err != nil {
return nil, err
}
url = url.JoinPath("locks/verify")
query := url.Query()
if path != "" {
query.Add("path", path)
}
if id != "" {
query.Add("id", id)
}
url.RawQuery = query.Encode()
body := listLocksVerifyRequest{
Cursor: cursor,
Limit: limit,
Ref: &batchRef{
Name: ref,
},
}
jsonData, err := json.Marshal(&body)
if err != nil {
return nil, err
}
jsonReader := bytes.NewReader(jsonData)
req, _ := newHTTPRequest(http.MethodPost, url.String(), jsonReader)
req.Header.Set("Content-Type", c.header)
req.Header.Set("Authorization", c.auth)
client := newHTTPClient()
res, err := client.Do(req)
if err != nil {
return nil, err
}
defer func() { _ = res.Body.Close() }()
response := &ListLocksVerifyResponse{}
if err := gitlabnet.ParseJSON(res, response); err != nil {
return nil, err
}
return response, nil
}
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