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 back
This commit is contained in:
parent
6ad4990a65
commit
d99f4ab003
9 changed files with 638 additions and 16 deletions
|
@ -506,6 +506,63 @@ func (err ErrLastOrgOwner) Error() string {
|
|||
return fmt.Sprintf("user is the last member of owner team [uid: %d]", err.UID)
|
||||
}
|
||||
|
||||
//.____ ____________________
|
||||
//| | \_ _____/ _____/
|
||||
//| | | __) \_____ \
|
||||
//| |___| \ / \
|
||||
//|_______ \___ / /_______ /
|
||||
// \/ \/ \/
|
||||
|
||||
// ErrLFSLockNotExist represents a "LFSLockNotExist" kind of error.
|
||||
type ErrLFSLockNotExist struct {
|
||||
ID int64
|
||||
RepoID int64
|
||||
Path string
|
||||
}
|
||||
|
||||
// IsErrLFSLockNotExist checks if an error is a ErrLFSLockNotExist.
|
||||
func IsErrLFSLockNotExist(err error) bool {
|
||||
_, ok := err.(ErrLFSLockNotExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrLFSLockNotExist) Error() string {
|
||||
return fmt.Sprintf("lfs lock does not exist [id: %d, rid: %d, path: %s]", err.ID, err.RepoID, err.Path)
|
||||
}
|
||||
|
||||
// ErrLFSLockUnauthorizedAction represents a "LFSLockUnauthorizedAction" kind of error.
|
||||
type ErrLFSLockUnauthorizedAction struct {
|
||||
RepoID int64
|
||||
UserName string
|
||||
Action string
|
||||
}
|
||||
|
||||
// IsErrLFSLockUnauthorizedAction checks if an error is a ErrLFSLockUnauthorizedAction.
|
||||
func IsErrLFSLockUnauthorizedAction(err error) bool {
|
||||
_, ok := err.(ErrLFSLockUnauthorizedAction)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrLFSLockUnauthorizedAction) Error() string {
|
||||
return fmt.Sprintf("User %s doesn't have rigth to %s for lfs lock [rid: %d]", err.UserName, err.Action, err.RepoID)
|
||||
}
|
||||
|
||||
// ErrLFSLockAlreadyExist represents a "LFSLockAlreadyExist" kind of error.
|
||||
type ErrLFSLockAlreadyExist struct {
|
||||
RepoID int64
|
||||
Path string
|
||||
}
|
||||
|
||||
// IsErrLFSLockAlreadyExist checks if an error is a ErrLFSLockAlreadyExist.
|
||||
func IsErrLFSLockAlreadyExist(err error) bool {
|
||||
_, ok := err.(ErrLFSLockAlreadyExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrLFSLockAlreadyExist) Error() string {
|
||||
return fmt.Sprintf("lfs lock already exists [rid: %d, path: %s]", err.RepoID, err.Path)
|
||||
}
|
||||
|
||||
// __________ .__ __
|
||||
// \______ \ ____ ______ ____ _____|__|/ |_ ___________ ___.__.
|
||||
// | _// __ \\____ \ / _ \/ ___/ \ __\/ _ \_ __ < | |
|
||||
|
|
146
models/lfs_lock.go
Normal file
146
models/lfs_lock.go
Normal file
|
@ -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
|
||||
}
|
|
@ -117,6 +117,7 @@ func init() {
|
|||
new(TrackedTime),
|
||||
new(DeletedBranch),
|
||||
new(RepoIndexerStatus),
|
||||
new(LFSLock),
|
||||
)
|
||||
|
||||
gonicNames := []string{"SSL", "UID"}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue