Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Git LFS lock api #2938

Merged
merged 30 commits into from
Nov 28, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
55f5424
Implement routes
sapk Nov 18, 2017
80b3ce0
move to api/sdk and create model
sapk Nov 19, 2017
144ab60
Implement add + list
sapk Nov 19, 2017
3ef7315
List return 200 empty list no 404
sapk Nov 20, 2017
8ac2219
Add verify lfs lock api
sapk Nov 20, 2017
b3ad366
Add delete and start implementing auth control
sapk Nov 20, 2017
bcd3b39
Revert to code.gitea.io/sdk/gitea vendor
sapk Nov 21, 2017
74a04bc
Apply needed check for all lfs locks route
sapk Nov 21, 2017
82bab1b
Add simple tests
sapk Nov 21, 2017
cc66370
fix lint
sapk Nov 21, 2017
18eed10
Improve tests
sapk Nov 22, 2017
61606fc
Add delete test + fix
sapk Nov 22, 2017
3015595
Add lfs ascii header
sapk Nov 22, 2017
5b3b301
Various fixes from review + remove useless code + add more corner cas…
sapk Nov 24, 2017
b37282e
Remove repo link since only id is needed.
sapk Nov 24, 2017
3289ddb
Improve tests
sapk Nov 25, 2017
d529d02
Use TEXT column format for path + test
sapk Nov 25, 2017
51befcf
fix mispell
sapk Nov 25, 2017
ed57927
Use NewRequestWithJSON for POST tests
sapk Nov 25, 2017
bd3e9e7
Clean path
sapk Nov 26, 2017
caf92f9
Improve DB format
sapk Nov 26, 2017
2988c10
Revert uniquess repoid+path
sapk Nov 26, 2017
b6acea8
(Re)-setup uniqueness + max path length
sapk Nov 27, 2017
40b3cbb
Fixed TEXT in place of VARCHAR
sapk Nov 27, 2017
ca344b5
Settle back to maximum VARCHAR(3072)
sapk Nov 27, 2017
ab0a3eb
Let place for repoid in key
sapk Nov 27, 2017
d13c73e
Let place for repoid in key
sapk Nov 27, 2017
59565f4
Let place for repoid in key
sapk Nov 27, 2017
0180efa
Revert back
sapk Nov 27, 2017
a987dda
Merge branch 'master' into git-lfs-lock-api
sapk Nov 28, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 176 additions & 0 deletions integrations/api_repo_lfs_locks_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
3 changes: 1 addition & 2 deletions integrations/mysql.ini.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ HTTP_PORT = 3001
ROOT_URL = http://localhost:3001/
DISABLE_SSH = false
SSH_PORT = 22
LFS_START_SERVER = false
LFS_START_SERVER = true
OFFLINE_MODE = false

[mailer]
Expand Down Expand Up @@ -65,4 +65,3 @@ LEVEL = Debug
INSTALL_LOCK = true
SECRET_KEY = 9pCviYTWSb
INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0OTU1NTE2MTh9.hhSVGOANkaKk3vfCd2jDOIww4pUk0xtg9JRde5UogyQ

2 changes: 1 addition & 1 deletion integrations/pgsql.ini.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ HTTP_PORT = 3002
ROOT_URL = http://localhost:3002/
DISABLE_SSH = false
SSH_PORT = 22
LFS_START_SERVER = false
LFS_START_SERVER = true
OFFLINE_MODE = false

[mailer]
Expand Down
27 changes: 14 additions & 13 deletions integrations/sqlite.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ APP_NAME = Gitea: Git with a cup of tea
RUN_MODE = prod

[database]
DB_TYPE = sqlite3
PATH = :memory:
DB_TYPE = sqlite3
PATH = :memory:

[indexer]
ISSUE_INDEXER_PATH = integrations/indexers-sqlite/issues.bleve
ISSUE_INDEXER_PATH = integrations/indexers-sqlite/issues.bleve
REPO_INDEXER_ENABLED = true
REPO_INDEXER_PATH = integrations/indexers-sqlite/repos.bleve
REPO_INDEXER_PATH = integrations/indexers-sqlite/repos.bleve

[repository]
ROOT = integrations/gitea-integration-sqlite/gitea-repositories
Expand All @@ -22,21 +22,22 @@ HTTP_PORT = 3003
ROOT_URL = http://localhost:3003/
DISABLE_SSH = false
SSH_PORT = 22
LFS_START_SERVER = false
LFS_START_SERVER = true
OFFLINE_MODE = false
LFS_JWT_SECRET = Tv_MjmZuHqpIY6GFl12ebgkRAMt4RlWt0v4EHKSXO0w

[mailer]
ENABLED = false

[service]
REGISTER_EMAIL_CONFIRM = false
ENABLE_NOTIFY_MAIL = false
DISABLE_REGISTRATION = false
ENABLE_CAPTCHA = false
REQUIRE_SIGNIN_VIEW = false
DEFAULT_KEEP_EMAIL_PRIVATE = false
REGISTER_EMAIL_CONFIRM = false
ENABLE_NOTIFY_MAIL = false
DISABLE_REGISTRATION = false
ENABLE_CAPTCHA = false
REQUIRE_SIGNIN_VIEW = false
DEFAULT_KEEP_EMAIL_PRIVATE = false
DEFAULT_ALLOW_CREATE_ORGANIZATION = true
NO_REPLY_ADDRESS = noreply.example.org
NO_REPLY_ADDRESS = noreply.example.org

[picture]
DISABLE_GRAVATAR = false
Expand All @@ -46,7 +47,7 @@ ENABLE_FEDERATED_AVATAR = false
PROVIDER = file

[log]
MODE = console,file
MODE = console,file
ROOT_PATH = sqlite-log

[log.console]
Expand Down
57 changes: 57 additions & 0 deletions models/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

// __________ .__ __
// \______ \ ____ ______ ____ _____|__|/ |_ ___________ ___.__.
// | _// __ \\____ \ / _ \/ ___/ \ __\/ _ \_ __ < | |
Expand Down
Loading