Git LFS lock api (#2938)
* Implement routes * move to api/sdk and create model * Implement add + list * List return 200 empty list no 404 * Add verify lfs lock api * Add delete and start implementing auth control * Revert to code.gitea.io/sdk/gitea vendor * Apply needed check for all lfs locks route * Add simple tests * fix lint * Improve tests * Add delete test + fix * Add lfs ascii header * Various fixes from review + remove useless code + add more corner case testing * Remove repo link since only id is needed. Save a little of memory and cpu time. * Improve tests * Use TEXT column format for path + test * fix mispell * Use NewRequestWithJSON for POST tests * Clean path * Improve DB format * Revert uniquess repoid+path * (Re)-setup uniqueness + max path length * Fixed TEXT in place of VARCHAR * Settle back to maximum VARCHAR(3072) * Let place for repoid in key * Let place for repoid in key * Let place for repoid in key * Revert backtokarchuk/v1.17
parent
6ad4990a65
commit
d99f4ab003
@ -0,0 +1,176 @@ |
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package integrations |
||||
|
||||
import ( |
||||
"fmt" |
||||
"net/http" |
||||
"testing" |
||||
"time" |
||||
|
||||
"code.gitea.io/gitea/models" |
||||
"code.gitea.io/gitea/modules/setting" |
||||
api "code.gitea.io/sdk/gitea" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
) |
||||
|
||||
func TestAPILFSLocksNotStarted(t *testing.T) { |
||||
prepareTestEnv(t) |
||||
setting.LFS.StartServer = false |
||||
user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) |
||||
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) |
||||
|
||||
req := NewRequestf(t, "GET", "/%s/%s/info/lfs/locks", user.Name, repo.Name) |
||||
MakeRequest(t, req, http.StatusNotFound) |
||||
req = NewRequestf(t, "POST", "/%s/%s/info/lfs/locks", user.Name, repo.Name) |
||||
MakeRequest(t, req, http.StatusNotFound) |
||||
req = NewRequestf(t, "GET", "/%s/%s/info/lfs/locks/verify", user.Name, repo.Name) |
||||
MakeRequest(t, req, http.StatusNotFound) |
||||
req = NewRequestf(t, "GET", "/%s/%s/info/lfs/locks/10/unlock", user.Name, repo.Name) |
||||
MakeRequest(t, req, http.StatusNotFound) |
||||
} |
||||
|
||||
func TestAPILFSLocksNotLogin(t *testing.T) { |
||||
prepareTestEnv(t) |
||||
setting.LFS.StartServer = true |
||||
user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) |
||||
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) |
||||
|
||||
req := NewRequestf(t, "GET", "/%s/%s/info/lfs/locks", user.Name, repo.Name) |
||||
req.Header.Set("Accept", "application/vnd.git-lfs+json") |
||||
req.Header.Set("Content-Type", "application/vnd.git-lfs+json") |
||||
resp := MakeRequest(t, req, http.StatusForbidden) |
||||
var lfsLockError api.LFSLockError |
||||
DecodeJSON(t, resp, &lfsLockError) |
||||
assert.Equal(t, "You must have pull access to list locks : User undefined doesn't have rigth to list for lfs lock [rid: 1]", lfsLockError.Message) |
||||
} |
||||
|
||||
func TestAPILFSLocksLogged(t *testing.T) { |
||||
prepareTestEnv(t) |
||||
setting.LFS.StartServer = true |
||||
user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) //in org 3
|
||||
user4 := models.AssertExistsAndLoadBean(t, &models.User{ID: 4}).(*models.User) //in org 3
|
||||
|
||||
repo1 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) |
||||
repo3 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) // own by org 3
|
||||
|
||||
tests := []struct { |
||||
user *models.User |
||||
repo *models.Repository |
||||
path string |
||||
httpResult int |
||||
addTime []int |
||||
}{ |
||||
{user: user2, repo: repo1, path: "foo/bar.zip", httpResult: http.StatusCreated, addTime: []int{0}}, |
||||
{user: user2, repo: repo1, path: "path/test", httpResult: http.StatusCreated, addTime: []int{0}}, |
||||
{user: user2, repo: repo1, path: "path/test", httpResult: http.StatusConflict}, |
||||
{user: user2, repo: repo1, path: "Foo/BaR.zip", httpResult: http.StatusConflict}, |
||||
{user: user2, repo: repo1, path: "Foo/Test/../subFOlder/../Relative/../BaR.zip", httpResult: http.StatusConflict}, |
||||
{user: user4, repo: repo1, path: "FoO/BaR.zip", httpResult: http.StatusForbidden}, |
||||
{user: user4, repo: repo1, path: "path/test-user4", httpResult: http.StatusForbidden}, |
||||
{user: user2, repo: repo1, path: "patH/Test-user4", httpResult: http.StatusCreated, addTime: []int{0}}, |
||||
{user: user2, repo: repo1, path: "some/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/path", httpResult: http.StatusCreated, addTime: []int{0}}, |
||||
|
||||
{user: user2, repo: repo3, path: "test/foo/bar.zip", httpResult: http.StatusCreated, addTime: []int{1, 2}}, |
||||
{user: user4, repo: repo3, path: "test/foo/bar.zip", httpResult: http.StatusConflict}, |
||||
{user: user4, repo: repo3, path: "test/foo/bar.bin", httpResult: http.StatusCreated, addTime: []int{1, 2}}, |
||||
} |
||||
|
||||
resultsTests := []struct { |
||||
user *models.User |
||||
repo *models.Repository |
||||
totalCount int |
||||
oursCount int |
||||
theirsCount int |
||||
locksOwners []*models.User |
||||
locksTimes []time.Time |
||||
}{ |
||||
{user: user2, repo: repo1, totalCount: 4, oursCount: 4, theirsCount: 0, locksOwners: []*models.User{user2, user2, user2, user2}, locksTimes: []time.Time{}}, |
||||
{user: user2, repo: repo3, totalCount: 2, oursCount: 1, theirsCount: 1, locksOwners: []*models.User{user2, user4}, locksTimes: []time.Time{}}, |
||||
{user: user4, repo: repo3, totalCount: 2, oursCount: 1, theirsCount: 1, locksOwners: []*models.User{user2, user4}, locksTimes: []time.Time{}}, |
||||
} |
||||
|
||||
deleteTests := []struct { |
||||
user *models.User |
||||
repo *models.Repository |
||||
lockID string |
||||
}{} |
||||
|
||||
//create locks
|
||||
for _, test := range tests { |
||||
session := loginUser(t, test.user.Name) |
||||
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s/info/lfs/locks", test.repo.FullName()), map[string]string{"path": test.path}) |
||||
req.Header.Set("Accept", "application/vnd.git-lfs+json") |
||||
req.Header.Set("Content-Type", "application/vnd.git-lfs+json") |
||||
session.MakeRequest(t, req, test.httpResult) |
||||
if len(test.addTime) > 0 { |
||||
for _, id := range test.addTime { |
||||
resultsTests[id].locksTimes = append(resultsTests[id].locksTimes, time.Now()) |
||||
} |
||||
} |
||||
} |
||||
|
||||
//check creation
|
||||
for _, test := range resultsTests { |
||||
session := loginUser(t, test.user.Name) |
||||
req := NewRequestf(t, "GET", "/%s/info/lfs/locks", test.repo.FullName()) |
||||
req.Header.Set("Accept", "application/vnd.git-lfs+json") |
||||
req.Header.Set("Content-Type", "application/vnd.git-lfs+json") |
||||
resp := session.MakeRequest(t, req, http.StatusOK) |
||||
var lfsLocks api.LFSLockList |
||||
DecodeJSON(t, resp, &lfsLocks) |
||||
assert.Len(t, lfsLocks.Locks, test.totalCount) |
||||
for i, lock := range lfsLocks.Locks { |
||||
assert.EqualValues(t, test.locksOwners[i].DisplayName(), lock.Owner.Name) |
||||
assert.WithinDuration(t, test.locksTimes[i], lock.LockedAt, 1*time.Second) |
||||
} |
||||
|
||||
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s/info/lfs/locks/verify", test.repo.FullName()), map[string]string{}) |
||||
req.Header.Set("Accept", "application/vnd.git-lfs+json") |
||||
req.Header.Set("Content-Type", "application/vnd.git-lfs+json") |
||||
resp = session.MakeRequest(t, req, http.StatusOK) |
||||
var lfsLocksVerify api.LFSLockListVerify |
||||
DecodeJSON(t, resp, &lfsLocksVerify) |
||||
assert.Len(t, lfsLocksVerify.Ours, test.oursCount) |
||||
assert.Len(t, lfsLocksVerify.Theirs, test.theirsCount) |
||||
for _, lock := range lfsLocksVerify.Ours { |
||||
assert.EqualValues(t, test.user.DisplayName(), lock.Owner.Name) |
||||
deleteTests = append(deleteTests, struct { |
||||
user *models.User |
||||
repo *models.Repository |
||||
lockID string |
||||
}{test.user, test.repo, lock.ID}) |
||||
} |
||||
for _, lock := range lfsLocksVerify.Theirs { |
||||
assert.NotEqual(t, test.user.DisplayName(), lock.Owner.Name) |
||||
} |
||||
} |
||||
|
||||
//remove all locks
|
||||
for _, test := range deleteTests { |
||||
session := loginUser(t, test.user.Name) |
||||
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s/info/lfs/locks/%s/unlock", test.repo.FullName(), test.lockID), map[string]string{}) |
||||
req.Header.Set("Accept", "application/vnd.git-lfs+json") |
||||
req.Header.Set("Content-Type", "application/vnd.git-lfs+json") |
||||
resp := session.MakeRequest(t, req, http.StatusOK) |
||||
var lfsLockRep api.LFSLockResponse |
||||
DecodeJSON(t, resp, &lfsLockRep) |
||||
assert.Equal(t, test.lockID, lfsLockRep.Lock.ID) |
||||
assert.Equal(t, test.user.DisplayName(), lfsLockRep.Lock.Owner.Name) |
||||
} |
||||
|
||||
// check that we don't have any lock
|
||||
for _, test := range resultsTests { |
||||
session := loginUser(t, test.user.Name) |
||||
req := NewRequestf(t, "GET", "/%s/info/lfs/locks", test.repo.FullName()) |
||||
req.Header.Set("Accept", "application/vnd.git-lfs+json") |
||||
req.Header.Set("Content-Type", "application/vnd.git-lfs+json") |
||||
resp := session.MakeRequest(t, req, http.StatusOK) |
||||
var lfsLocks api.LFSLockList |
||||
DecodeJSON(t, resp, &lfsLocks) |
||||
assert.Len(t, lfsLocks.Locks, 0) |
||||
} |
||||
} |
@ -0,0 +1,146 @@ |
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package models |
||||
|
||||
import ( |
||||
"fmt" |
||||
"path" |
||||
"strconv" |
||||
"strings" |
||||
"time" |
||||
|
||||
api "code.gitea.io/sdk/gitea" |
||||
) |
||||
|
||||
// LFSLock represents a git lfs lock of repository.
|
||||
type LFSLock struct { |
||||
ID int64 `xorm:"pk autoincr"` |
||||
RepoID int64 `xorm:"INDEX NOT NULL"` |
||||
Owner *User `xorm:"-"` |
||||
OwnerID int64 `xorm:"INDEX NOT NULL"` |
||||
Path string `xorm:"TEXT"` |
||||
Created time.Time `xorm:"created"` |
||||
} |
||||
|
||||
// BeforeInsert is invoked from XORM before inserting an object of this type.
|
||||
func (l *LFSLock) BeforeInsert() { |
||||
l.OwnerID = l.Owner.ID |
||||
l.Path = cleanPath(l.Path) |
||||
} |
||||
|
||||
// AfterLoad is invoked from XORM after setting the values of all fields of this object.
|
||||
func (l *LFSLock) AfterLoad() { |
||||
l.Owner, _ = GetUserByID(l.OwnerID) |
||||
} |
||||
|
||||
func cleanPath(p string) string { |
||||
return strings.ToLower(path.Clean(p)) |
||||
} |
||||
|
||||
// APIFormat convert a Release to lfs.LFSLock
|
||||
func (l *LFSLock) APIFormat() *api.LFSLock { |
||||
return &api.LFSLock{ |
||||
ID: strconv.FormatInt(l.ID, 10), |
||||
Path: l.Path, |
||||
LockedAt: l.Created, |
||||
Owner: &api.LFSLockOwner{ |
||||
Name: l.Owner.DisplayName(), |
||||
}, |
||||
} |
||||
} |
||||
|
||||
// CreateLFSLock creates a new lock.
|
||||
func CreateLFSLock(lock *LFSLock) (*LFSLock, error) { |
||||
err := CheckLFSAccessForRepo(lock.Owner, lock.RepoID, "create") |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
l, err := GetLFSLock(lock.RepoID, lock.Path) |
||||
if err == nil { |
||||
return l, ErrLFSLockAlreadyExist{lock.RepoID, lock.Path} |
||||
} |
||||
if !IsErrLFSLockNotExist(err) { |
||||
return nil, err |
||||
} |
||||
|
||||
_, err = x.InsertOne(lock) |
||||
return lock, err |
||||
} |
||||
|
||||
// GetLFSLock returns release by given path.
|
||||
func GetLFSLock(repoID int64, path string) (*LFSLock, error) { |
||||
path = cleanPath(path) |
||||
rel := &LFSLock{RepoID: repoID, Path: path} |
||||
has, err := x.Get(rel) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if !has { |
||||
return nil, ErrLFSLockNotExist{0, repoID, path} |
||||
} |
||||
return rel, nil |
||||
} |
||||
|
||||
// GetLFSLockByID returns release by given id.
|
||||
func GetLFSLockByID(id int64) (*LFSLock, error) { |
||||
lock := new(LFSLock) |
||||
has, err := x.ID(id).Get(lock) |
||||
if err != nil { |
||||
return nil, err |
||||
} else if !has { |
||||
return nil, ErrLFSLockNotExist{id, 0, ""} |
||||
} |
||||
return lock, nil |
||||
} |
||||
|
||||
// GetLFSLockByRepoID returns a list of locks of repository.
|
||||
func GetLFSLockByRepoID(repoID int64) (locks []*LFSLock, err error) { |
||||
err = x.Where("repo_id = ?", repoID).Find(&locks) |
||||
return |
||||
} |
||||
|
||||
// DeleteLFSLockByID deletes a lock by given ID.
|
||||
func DeleteLFSLockByID(id int64, u *User, force bool) (*LFSLock, error) { |
||||
lock, err := GetLFSLockByID(id) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
err = CheckLFSAccessForRepo(u, lock.RepoID, "delete") |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
if !force && u.ID != lock.OwnerID { |
||||
return nil, fmt.Errorf("user doesn't own lock and force flag is not set") |
||||
} |
||||
|
||||
_, err = x.ID(id).Delete(new(LFSLock)) |
||||
return lock, err |
||||
} |
||||
|
||||
//CheckLFSAccessForRepo check needed access mode base on action
|
||||
func CheckLFSAccessForRepo(u *User, repoID int64, action string) error { |
||||
if u == nil { |
||||
return ErrLFSLockUnauthorizedAction{repoID, "undefined", action} |
||||
} |
||||
mode := AccessModeRead |
||||
if action == "create" || action == "delete" || action == "verify" { |
||||
mode = AccessModeWrite |
||||
} |
||||
|
||||
repo, err := GetRepositoryByID(repoID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
has, err := HasAccess(u.ID, repo, mode) |
||||
if err != nil { |
||||
return err |
||||
} else if !has { |
||||
return ErrLFSLockUnauthorizedAction{repo.ID, u.DisplayName(), action} |
||||
} |
||||
return nil |
||||
} |
@ -0,0 +1,236 @@ |
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package lfs |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"strconv" |
||||
|
||||
"code.gitea.io/gitea/models" |
||||
"code.gitea.io/gitea/modules/context" |
||||
"code.gitea.io/gitea/modules/setting" |
||||
api "code.gitea.io/sdk/gitea" |
||||
|
||||
"gopkg.in/macaron.v1" |
||||
) |
||||
|
||||
func checkRequest(req macaron.Request) int { |
||||
if !setting.LFS.StartServer { |
||||
return 404 |
||||
} |
||||
if !MetaMatcher(req) || req.Header.Get("Content-Type") != metaMediaType { |
||||
return 400 |
||||
} |
||||
return 200 |
||||
} |
||||
|
||||
func handleLockListOut(ctx *context.Context, lock *models.LFSLock, err error) { |
||||
if err != nil { |
||||
if models.IsErrLFSLockNotExist(err) { |
||||
ctx.JSON(200, api.LFSLockList{ |
||||
Locks: []*api.LFSLock{}, |
||||
}) |
||||
return |
||||
} |
||||
ctx.JSON(500, api.LFSLockError{ |
||||
Message: "unable to list locks : " + err.Error(), |
||||
}) |
||||
return |
||||
} |
||||
if ctx.Repo.Repository.ID != lock.RepoID { |
||||
ctx.JSON(200, api.LFSLockList{ |
||||
Locks: []*api.LFSLock{}, |
||||
}) |
||||
return |
||||
} |
||||
ctx.JSON(200, api.LFSLockList{ |
||||
Locks: []*api.LFSLock{lock.APIFormat()}, |
||||
}) |
||||
} |
||||
|
||||
// GetListLockHandler list locks
|
||||
func GetListLockHandler(ctx *context.Context) { |
||||
status := checkRequest(ctx.Req) |
||||
if status != 200 { |
||||
writeStatus(ctx, status) |
||||
return |
||||
} |
||||
ctx.Resp.Header().Set("Content-Type", metaMediaType) |
||||
|
||||
err := models.CheckLFSAccessForRepo(ctx.User, ctx.Repo.Repository.ID, "list") |
||||
if err != nil { |
||||
if models.IsErrLFSLockUnauthorizedAction(err) { |
||||
ctx.JSON(403, api.LFSLockError{ |
||||
Message: "You must have pull access to list locks : " + err.Error(), |
||||
}) |
||||
return |
||||
} |
||||
ctx.JSON(500, api.LFSLockError{ |
||||
Message: "unable to list lock : " + err.Error(), |
||||
}) |
||||
return |
||||
} |
||||
//TODO handle query cursor and limit
|
||||
id := ctx.Query("id") |
||||
if id != "" { //Case where we request a specific id
|
||||
v, err := strconv.ParseInt(id, 10, 64) |
||||
if err != nil { |
||||
ctx.JSON(400, api.LFSLockError{ |
||||
Message: "bad request : " + err.Error(), |
||||
}) |
||||
return |
||||
} |
||||
lock, err := models.GetLFSLockByID(int64(v)) |
||||
handleLockListOut(ctx, lock, err) |
||||
return |
||||
} |
||||
|
||||
path := ctx.Query("path") |
||||
if path != "" { //Case where we request a specific id
|
||||
lock, err := models.GetLFSLock(ctx.Repo.Repository.ID, path) |
||||
handleLockListOut(ctx, lock, err) |
||||
return |
||||
} |
||||
|
||||
//If no query params path or id
|
||||
lockList, err := models.GetLFSLockByRepoID(ctx.Repo.Repository.ID) |
||||
if err != nil { |
||||
ctx.JSON(500, api.LFSLockError{ |
||||
Message: "unable to list locks : " + err.Error(), |
||||
}) |
||||
return |
||||
} |
||||
lockListAPI := make([]*api.LFSLock, len(lockList)) |
||||
for i, l := range lockList { |
||||
lockListAPI[i] = l.APIFormat() |
||||
} |
||||
ctx.JSON(200, api.LFSLockList{ |
||||
Locks: lockListAPI, |
||||
}) |
||||
} |
||||
|
||||
// PostLockHandler create lock
|
||||
func PostLockHandler(ctx *context.Context) { |
||||
status := checkRequest(ctx.Req) |
||||
if status != 200 { |
||||
writeStatus(ctx, status) |
||||
return |
||||
} |
||||
ctx.Resp.Header().Set("Content-Type", metaMediaType) |
||||
|
||||
var req api.LFSLockRequest |
||||
dec := json.NewDecoder(ctx.Req.Body().ReadCloser()) |
||||
err := dec.Decode(&req) |
||||
if err != nil { |
||||
writeStatus(ctx, 400) |
||||
return |
||||
} |
||||
|
||||
lock, err := models.CreateLFSLock(&models.LFSLock{ |
||||
RepoID: ctx.Repo.Repository.ID, |
||||
Path: req.Path, |
||||
Owner: ctx.User, |
||||
}) |
||||
if err != nil { |
||||
if models.IsErrLFSLockAlreadyExist(err) { |
||||
ctx.JSON(409, api.LFSLockError{ |
||||
Lock: lock.APIFormat(), |
||||
Message: "already created lock", |
||||
}) |
||||
return |
||||
} |
||||
if models.IsErrLFSLockUnauthorizedAction(err) { |
||||
ctx.JSON(403, api.LFSLockError{ |
||||
Message: "You must have push access to create locks : " + err.Error(), |
||||
}) |
||||
return |
||||
} |
||||
ctx.JSON(500, api.LFSLockError{ |
||||
Message: "internal server error : " + err.Error(), |
||||
}) |
||||
return |
||||
} |
||||
ctx.JSON(201, api.LFSLockResponse{Lock: lock.APIFormat()}) |
||||
} |
||||
|
||||
// VerifyLockHandler list locks for verification
|
||||
func VerifyLockHandler(ctx *context.Context) { |
||||
status := checkRequest(ctx.Req) |
||||
if status != 200 { |
||||
writeStatus(ctx, status) |
||||
return |
||||
} |
||||
|
||||
ctx.Resp.Header().Set("Content-Type", metaMediaType) |
||||
|
||||
err := models.CheckLFSAccessForRepo(ctx.User, ctx.Repo.Repository.ID, "verify") |
||||
if err != nil { |
||||
if models.IsErrLFSLockUnauthorizedAction(err) { |
||||
ctx.JSON(403, api.LFSLockError{ |
||||
Message: "You must have push access to verify locks : " + err.Error(), |
||||
}) |
||||
return |
||||
} |
||||
ctx.JSON(500, api.LFSLockError{ |
||||
Message: "unable to verify lock : " + err.Error(), |
||||
}) |
||||
return |
||||
} |
||||
|
||||
//TODO handle body json cursor and limit
|
||||
lockList, err := models.GetLFSLockByRepoID(ctx.Repo.Repository.ID) |
||||
if err != nil { |
||||
ctx.JSON(500, api.LFSLockError{ |
||||
Message: "unable to list locks : " + err.Error(), |
||||
}) |
||||
return |
||||
} |
||||
lockOursListAPI := make([]*api.LFSLock, 0, len(lockList)) |
||||
lockTheirsListAPI := make([]*api.LFSLock, 0, len(lockList)) |
||||
for _, l := range lockList { |
||||
if l.Owner.ID == ctx.User.ID { |
||||
lockOursListAPI = append(lockOursListAPI, l.APIFormat()) |
||||
} else { |
||||
lockTheirsListAPI = append(lockTheirsListAPI, l.APIFormat()) |
||||
} |
||||
} |
||||
ctx.JSON(200, api.LFSLockListVerify{ |
||||
Ours: lockOursListAPI, |
||||
Theirs: lockTheirsListAPI, |
||||
}) |
||||
} |
||||
|
||||
// UnLockHandler delete locks
|
||||
func UnLockHandler(ctx *context.Context) { |
||||
status := checkRequest(ctx.Req) |
||||
if status != 200 { |
||||
writeStatus(ctx, status) |
||||
return |
||||
} |
||||
ctx.Resp.Header().Set("Content-Type", metaMediaType) |
||||
|
||||
var req api.LFSLockDeleteRequest |
||||
dec := json.NewDecoder(ctx.Req.Body().ReadCloser()) |
||||
err := dec.Decode(&req) |
||||
if err != nil { |
||||
writeStatus(ctx, 400) |
||||
return |
||||
} |
||||
|
||||
lock, err := models.DeleteLFSLockByID(ctx.ParamsInt64("lid"), ctx.User, req.Force) |
||||
if err != nil { |
||||
if models.IsErrLFSLockUnauthorizedAction(err) { |
||||
ctx.JSON(403, api.LFSLockError{ |
||||
Message: "You must have push access to delete locks : " + err.Error(), |
||||
}) |
||||
return |
||||
} |
||||
ctx.JSON(500, api.LFSLockError{ |
||||
Message: "unable to delete lock : " + err.Error(), |
||||
}) |
||||
return |
||||
} |
||||
ctx.JSON(200, api.LFSLockResponse{Lock: lock.APIFormat()}) |
||||
} |
Loading…
Reference in new issue