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:
Antoine GIRARD 2017-11-28 21:58:37 +01:00 committed by Lauris BH
parent 6ad4990a65
commit d99f4ab003
9 changed files with 638 additions and 16 deletions

View file

@ -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
View 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
}

View file

@ -117,6 +117,7 @@ func init() {
new(TrackedTime),
new(DeletedBranch),
new(RepoIndexerStatus),
new(LFSLock),
)
gonicNames := []string{"SSL", "UID"}