Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Léo-Paul Géneau
gitlab-ce
Commits
bed94832
Commit
bed94832
authored
Feb 07, 2018
by
Rubén Dávila
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Backport of LFS File Locking API
parent
ead97c55
Changes
33
Hide whitespace changes
Inline
Side-by-side
Showing
33 changed files
with
1002 additions
and
6 deletions
+1002
-6
app/controllers/concerns/lfs_request.rb
app/controllers/concerns/lfs_request.rb
+4
-2
app/controllers/projects/lfs_api_controller.rb
app/controllers/projects/lfs_api_controller.rb
+1
-1
app/controllers/projects/lfs_locks_api_controller.rb
app/controllers/projects/lfs_locks_api_controller.rb
+70
-0
app/models/lfs_file_lock.rb
app/models/lfs_file_lock.rb
+12
-0
app/models/project.rb
app/models/project.rb
+1
-0
app/models/repository.rb
app/models/repository.rb
+7
-0
app/serializers/lfs_file_lock_entity.rb
app/serializers/lfs_file_lock_entity.rb
+11
-0
app/serializers/lfs_file_lock_serializer.rb
app/serializers/lfs_file_lock_serializer.rb
+3
-0
app/services/lfs/lock_file_service.rb
app/services/lfs/lock_file_service.rb
+39
-0
app/services/lfs/locks_finder_service.rb
app/services/lfs/locks_finder_service.rb
+17
-0
app/services/lfs/unlock_file_service.rb
app/services/lfs/unlock_file_service.rb
+43
-0
changelogs/unreleased/35856-implement-file-locking-api.yml
changelogs/unreleased/35856-implement-file-locking-api.yml
+5
-0
config/initializers/mime_types.rb
config/initializers/mime_types.rb
+1
-1
config/routes/git_http.rb
config/routes/git_http.rb
+7
-0
db/migrate/20180116193854_create_lfs_file_locks.rb
db/migrate/20180116193854_create_lfs_file_locks.rb
+30
-0
db/schema.rb
db/schema.rb
+12
-0
doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
+66
-0
lib/gitlab/checks/change_access.rb
lib/gitlab/checks/change_access.rb
+28
-1
lib/gitlab/checks/commit_check.rb
lib/gitlab/checks/commit_check.rb
+61
-0
lib/gitlab/import_export/import_export.yml
lib/gitlab/import_export/import_export.yml
+2
-0
spec/factories/lfs_file_locks.rb
spec/factories/lfs_file_locks.rb
+7
-0
spec/lib/gitlab/checks/change_access_spec.rb
spec/lib/gitlab/checks/change_access_spec.rb
+39
-0
spec/lib/gitlab/import_export/all_models.yml
spec/lib/gitlab/import_export/all_models.yml
+3
-0
spec/lib/gitlab/import_export/safe_model_attributes.yml
spec/lib/gitlab/import_export/safe_model_attributes.yml
+6
-0
spec/models/lfs_file_lock_spec.rb
spec/models/lfs_file_lock_spec.rb
+57
-0
spec/models/project_spec.rb
spec/models/project_spec.rb
+1
-0
spec/models/repository_spec.rb
spec/models/repository_spec.rb
+22
-0
spec/requests/lfs_http_spec.rb
spec/requests/lfs_http_spec.rb
+1
-1
spec/requests/lfs_locks_api_spec.rb
spec/requests/lfs_locks_api_spec.rb
+159
-0
spec/serializers/lfs_file_lock_entity_spec.rb
spec/serializers/lfs_file_lock_entity_spec.rb
+19
-0
spec/services/lfs/lock_file_service_spec.rb
spec/services/lfs/lock_file_service_spec.rb
+62
-0
spec/services/lfs/locks_finder_service_spec.rb
spec/services/lfs/locks_finder_service_spec.rb
+101
-0
spec/services/lfs/unlock_file_service_spec.rb
spec/services/lfs/unlock_file_service_spec.rb
+105
-0
No files found.
app/controllers/concerns/lfs_request.rb
View file @
bed94832
...
@@ -10,6 +10,8 @@
...
@@ -10,6 +10,8 @@
module
LfsRequest
module
LfsRequest
extend
ActiveSupport
::
Concern
extend
ActiveSupport
::
Concern
CONTENT_TYPE
=
'application/vnd.git-lfs+json'
.
freeze
included
do
included
do
before_action
:require_lfs_enabled!
before_action
:require_lfs_enabled!
before_action
:lfs_check_access!
before_action
:lfs_check_access!
...
@@ -50,7 +52,7 @@ module LfsRequest
...
@@ -50,7 +52,7 @@ module LfsRequest
message:
'Access forbidden. Check your access level.'
,
message:
'Access forbidden. Check your access level.'
,
documentation_url:
help_url
documentation_url:
help_url
},
},
content_type:
"application/vnd.git-lfs+json"
,
content_type:
CONTENT_TYPE
,
status:
403
status:
403
)
)
end
end
...
@@ -61,7 +63,7 @@ module LfsRequest
...
@@ -61,7 +63,7 @@ module LfsRequest
message:
'Not found.'
,
message:
'Not found.'
,
documentation_url:
help_url
documentation_url:
help_url
},
},
content_type:
"application/vnd.git-lfs+json"
,
content_type:
CONTENT_TYPE
,
status:
404
status:
404
)
)
end
end
...
...
app/controllers/projects/lfs_api_controller.rb
View file @
bed94832
...
@@ -98,7 +98,7 @@ class Projects::LfsApiController < Projects::GitHttpClientController
...
@@ -98,7 +98,7 @@ class Projects::LfsApiController < Projects::GitHttpClientController
json:
{
json:
{
message:
lfs_read_only_message
message:
lfs_read_only_message
},
},
content_type:
'application/vnd.git-lfs+json'
,
content_type:
LfsRequest
::
CONTENT_TYPE
,
status:
403
status:
403
)
)
end
end
...
...
app/controllers/projects/lfs_locks_api_controller.rb
0 → 100644
View file @
bed94832
class
Projects::LfsLocksApiController
<
Projects
::
GitHttpClientController
include
LfsRequest
def
create
@result
=
Lfs
::
LockFileService
.
new
(
project
,
user
,
params
).
execute
render_json
(
@result
[
:lock
])
end
def
unlock
@result
=
Lfs
::
UnlockFileService
.
new
(
project
,
user
,
params
).
execute
render_json
(
@result
[
:lock
])
end
def
index
@result
=
Lfs
::
LocksFinderService
.
new
(
project
,
user
,
params
).
execute
render_json
(
@result
[
:locks
])
end
def
verify
@result
=
Lfs
::
LocksFinderService
.
new
(
project
,
user
,
{}).
execute
ours
,
theirs
=
split_by_owner
(
@result
[
:locks
])
render_json
({
ours:
ours
,
theirs:
theirs
},
false
)
end
private
def
render_json
(
data
,
process
=
true
)
render
json:
build_payload
(
data
,
process
),
content_type:
LfsRequest
::
CONTENT_TYPE
,
status:
@result
[
:http_status
]
end
def
build_payload
(
data
,
process
)
data
=
LfsFileLockSerializer
.
new
.
represent
(
data
)
if
process
return
data
if
@result
[
:status
]
==
:success
# When the locking failed due to an existent Lock, the existent record
# is returned in `@result[:lock]`
error_payload
(
@result
[
:message
],
@result
[
:lock
]
?
data
:
{})
end
def
error_payload
(
message
,
custom_attrs
=
{})
custom_attrs
.
merge
({
message:
message
,
documentation_url:
help_url
})
end
def
split_by_owner
(
locks
)
groups
=
locks
.
partition
{
|
lock
|
lock
.
user_id
==
user
.
id
}
groups
.
map!
do
|
records
|
LfsFileLockSerializer
.
new
.
represent
(
records
,
root:
false
)
end
end
def
download_request?
params
[
:action
]
==
'index'
end
def
upload_request?
%w(create unlock verify)
.
include?
(
params
[
:action
])
end
end
app/models/lfs_file_lock.rb
0 → 100644
View file @
bed94832
class
LfsFileLock
<
ActiveRecord
::
Base
belongs_to
:project
belongs_to
:user
validates
:project_id
,
:user_id
,
:path
,
presence:
true
def
can_be_unlocked_by?
(
current_user
,
forced
=
false
)
return
true
if
current_user
.
id
==
user_id
forced
&&
current_user
.
can?
(
:admin_project
,
project
)
end
end
app/models/project.rb
View file @
bed94832
...
@@ -179,6 +179,7 @@ class Project < ActiveRecord::Base
...
@@ -179,6 +179,7 @@ class Project < ActiveRecord::Base
has_many
:releases
has_many
:releases
has_many
:lfs_objects_projects
,
dependent: :destroy
# rubocop:disable Cop/ActiveRecordDependent
has_many
:lfs_objects_projects
,
dependent: :destroy
# rubocop:disable Cop/ActiveRecordDependent
has_many
:lfs_objects
,
through: :lfs_objects_projects
has_many
:lfs_objects
,
through: :lfs_objects_projects
has_many
:lfs_file_locks
has_many
:project_group_links
has_many
:project_group_links
has_many
:invited_groups
,
through: :project_group_links
,
source: :group
has_many
:invited_groups
,
through: :project_group_links
,
source: :group
has_many
:pages_domains
has_many
:pages_domains
...
...
app/models/repository.rb
View file @
bed94832
...
@@ -164,6 +164,13 @@ class Repository
...
@@ -164,6 +164,13 @@ class Repository
commits
commits
end
end
# Returns a list of commits that are not present in any reference
def
new_commits
(
newrev
)
refs
=
::
Gitlab
::
Git
::
RevList
.
new
(
raw
,
newrev:
newrev
).
new_refs
refs
.
map
{
|
sha
|
commit
(
sha
.
strip
)
}
end
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/384
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/384
def
find_commits_by_message
(
query
,
ref
=
nil
,
path
=
nil
,
limit
=
1000
,
offset
=
0
)
def
find_commits_by_message
(
query
,
ref
=
nil
,
path
=
nil
,
limit
=
1000
,
offset
=
0
)
unless
exists?
&&
has_visible_content?
&&
query
.
present?
unless
exists?
&&
has_visible_content?
&&
query
.
present?
...
...
app/serializers/lfs_file_lock_entity.rb
0 → 100644
View file @
bed94832
class
LfsFileLockEntity
<
Grape
::
Entity
root
'locks'
,
'lock'
expose
:path
expose
(
:id
)
{
|
entity
|
entity
.
id
.
to_s
}
expose
(
:created_at
,
as: :locked_at
)
{
|
entity
|
entity
.
created_at
.
to_s
(
:iso8601
)
}
expose
:owner
do
expose
(
:name
)
{
|
entity
|
entity
.
user
&
.
name
}
end
end
app/serializers/lfs_file_lock_serializer.rb
0 → 100644
View file @
bed94832
class
LfsFileLockSerializer
<
BaseSerializer
entity
LfsFileLockEntity
end
app/services/lfs/lock_file_service.rb
0 → 100644
View file @
bed94832
module
Lfs
class
LockFileService
<
BaseService
def
execute
unless
can?
(
current_user
,
:push_code
,
project
)
raise
Gitlab
::
GitAccess
::
UnauthorizedError
,
'You have no permissions'
end
create_lock!
rescue
ActiveRecord
::
RecordNotUnique
error
(
'already locked'
,
409
,
current_lock
)
rescue
Gitlab
::
GitAccess
::
UnauthorizedError
=>
ex
error
(
ex
.
message
,
403
)
rescue
=>
ex
error
(
ex
.
message
,
500
)
end
private
def
current_lock
project
.
lfs_file_locks
.
find_by
(
path:
params
[
:path
])
end
def
create_lock!
lock
=
project
.
lfs_file_locks
.
create!
(
user:
current_user
,
path:
params
[
:path
])
success
(
http_status:
201
,
lock:
lock
)
end
def
error
(
message
,
http_status
,
lock
=
nil
)
{
status: :error
,
message:
message
,
http_status:
http_status
,
lock:
lock
}
end
end
end
app/services/lfs/locks_finder_service.rb
0 → 100644
View file @
bed94832
module
Lfs
class
LocksFinderService
<
BaseService
def
execute
success
(
locks:
find_locks
)
rescue
=>
ex
error
(
ex
.
message
,
500
)
end
private
def
find_locks
options
=
params
.
slice
(
:id
,
:path
).
compact
.
symbolize_keys
project
.
lfs_file_locks
.
where
(
options
)
end
end
end
app/services/lfs/unlock_file_service.rb
0 → 100644
View file @
bed94832
module
Lfs
class
UnlockFileService
<
BaseService
def
execute
unless
can?
(
current_user
,
:push_code
,
project
)
raise
Gitlab
::
GitAccess
::
UnauthorizedError
,
'You have no permissions'
end
unlock_file
rescue
Gitlab
::
GitAccess
::
UnauthorizedError
=>
ex
error
(
ex
.
message
,
403
)
rescue
ActiveRecord
::
RecordNotFound
error
(
'Lock not found'
,
404
)
rescue
=>
ex
error
(
ex
.
message
,
500
)
end
private
def
unlock_file
forced
=
params
[
:force
]
==
true
if
lock
.
can_be_unlocked_by?
(
current_user
,
forced
)
lock
.
destroy!
success
(
lock:
lock
,
http_status: :ok
)
elsif
forced
error
(
'You must have master access to force delete a lock'
,
403
)
else
error
(
"
#{
lock
.
path
}
is locked by GitLab User
#{
lock
.
user_id
}
"
,
403
)
end
end
def
lock
return
@lock
if
defined?
(
@lock
)
@lock
=
if
params
[
:id
].
present?
project
.
lfs_file_locks
.
find
(
params
[
:id
])
elsif
params
[
:path
].
present?
project
.
lfs_file_locks
.
find_by!
(
path:
params
[
:path
])
end
end
end
end
changelogs/unreleased/35856-implement-file-locking-api.yml
0 → 100644
View file @
bed94832
---
title
:
Backport of LFS File Locking API
merge_request
:
16935
author
:
type
:
added
config/initializers/mime_types.rb
View file @
bed94832
...
@@ -14,4 +14,4 @@ Mime::Type.register "video/webm", :webm
...
@@ -14,4 +14,4 @@ Mime::Type.register "video/webm", :webm
Mime
::
Type
.
register
"video/ogg"
,
:ogv
Mime
::
Type
.
register
"video/ogg"
,
:ogv
Mime
::
Type
.
unregister
:json
Mime
::
Type
.
unregister
:json
Mime
::
Type
.
register
'application/json'
,
:json
,
%w(application/vnd.git-lfs+json application/json)
Mime
::
Type
.
register
'application/json'
,
:json
,
[
LfsRequest
::
CONTENT_TYPE
,
'application/json'
]
config/routes/git_http.rb
View file @
bed94832
...
@@ -16,6 +16,13 @@ scope(path: '*namespace_id/:project_id',
...
@@ -16,6 +16,13 @@ scope(path: '*namespace_id/:project_id',
get
'/*oid'
,
action: :deprecated
get
'/*oid'
,
action: :deprecated
end
end
scope
(
path:
'info/lfs'
)
do
resources
:lfs_locks
,
controller: :lfs_locks_api
,
path:
'locks'
do
post
:unlock
,
on: :member
post
:verify
,
on: :collection
end
end
# GitLab LFS object storage
# GitLab LFS object storage
scope
(
path:
'gitlab-lfs/objects/*oid'
,
controller: :lfs_storage
,
constraints:
{
oid:
/[a-f0-9]{64}/
})
do
scope
(
path:
'gitlab-lfs/objects/*oid'
,
controller: :lfs_storage
,
constraints:
{
oid:
/[a-f0-9]{64}/
})
do
get
'/'
,
action: :download
get
'/'
,
action: :download
...
...
db/migrate/20180116193854_create_lfs_file_locks.rb
0 → 100644
View file @
bed94832
class
CreateLfsFileLocks
<
ActiveRecord
::
Migration
include
Gitlab
::
Database
::
MigrationHelpers
DOWNTIME
=
false
disable_ddl_transaction!
def
up
create_table
:lfs_file_locks
do
|
t
|
t
.
references
:project
,
null:
false
,
foreign_key:
{
on_delete: :cascade
}
t
.
references
:user
,
null:
false
,
index:
true
,
foreign_key:
{
on_delete: :cascade
}
t
.
datetime
:created_at
,
null:
false
t
.
string
:path
,
limit:
511
end
add_index
:lfs_file_locks
,
[
:project_id
,
:path
],
unique:
true
end
def
down
if
foreign_keys_for
(
:lfs_file_locks
,
:project_id
).
any?
remove_foreign_key
:lfs_file_locks
,
column: :project_id
end
if
index_exists?
(
:lfs_file_locks
,
[
:project_id
,
:path
])
remove_concurrent_index
:lfs_file_locks
,
[
:project_id
,
:path
]
end
drop_table
:lfs_file_locks
end
end
db/schema.rb
View file @
bed94832
...
@@ -947,6 +947,16 @@ ActiveRecord::Schema.define(version: 20180206200543) do
...
@@ -947,6 +947,16 @@ ActiveRecord::Schema.define(version: 20180206200543) do
add_index
"labels"
,
[
"title"
],
name:
"index_labels_on_title"
,
using: :btree
add_index
"labels"
,
[
"title"
],
name:
"index_labels_on_title"
,
using: :btree
add_index
"labels"
,
[
"type"
,
"project_id"
],
name:
"index_labels_on_type_and_project_id"
,
using: :btree
add_index
"labels"
,
[
"type"
,
"project_id"
],
name:
"index_labels_on_type_and_project_id"
,
using: :btree
create_table
"lfs_file_locks"
,
force: :cascade
do
|
t
|
t
.
integer
"project_id"
,
null:
false
t
.
integer
"user_id"
,
null:
false
t
.
datetime
"created_at"
,
null:
false
t
.
string
"path"
,
limit:
511
end
add_index
"lfs_file_locks"
,
[
"project_id"
,
"path"
],
name:
"index_lfs_file_locks_on_project_id_and_path"
,
unique:
true
,
using: :btree
add_index
"lfs_file_locks"
,
[
"user_id"
],
name:
"index_lfs_file_locks_on_user_id"
,
using: :btree
create_table
"lfs_objects"
,
force: :cascade
do
|
t
|
create_table
"lfs_objects"
,
force: :cascade
do
|
t
|
t
.
string
"oid"
,
null:
false
t
.
string
"oid"
,
null:
false
t
.
integer
"size"
,
limit:
8
,
null:
false
t
.
integer
"size"
,
limit:
8
,
null:
false
...
@@ -1998,6 +2008,8 @@ ActiveRecord::Schema.define(version: 20180206200543) do
...
@@ -1998,6 +2008,8 @@ ActiveRecord::Schema.define(version: 20180206200543) do
add_foreign_key
"label_priorities"
,
"projects"
,
on_delete: :cascade
add_foreign_key
"label_priorities"
,
"projects"
,
on_delete: :cascade
add_foreign_key
"labels"
,
"namespaces"
,
column:
"group_id"
,
on_delete: :cascade
add_foreign_key
"labels"
,
"namespaces"
,
column:
"group_id"
,
on_delete: :cascade
add_foreign_key
"labels"
,
"projects"
,
name:
"fk_7de4989a69"
,
on_delete: :cascade
add_foreign_key
"labels"
,
"projects"
,
name:
"fk_7de4989a69"
,
on_delete: :cascade
add_foreign_key
"lfs_file_locks"
,
"projects"
,
on_delete: :cascade
add_foreign_key
"lfs_file_locks"
,
"users"
,
on_delete: :cascade
add_foreign_key
"lists"
,
"boards"
,
name:
"fk_0d3f677137"
,
on_delete: :cascade
add_foreign_key
"lists"
,
"boards"
,
name:
"fk_0d3f677137"
,
on_delete: :cascade
add_foreign_key
"lists"
,
"labels"
,
name:
"fk_7a5553d60f"
,
on_delete: :cascade
add_foreign_key
"lists"
,
"labels"
,
name:
"fk_7a5553d60f"
,
on_delete: :cascade
add_foreign_key
"members"
,
"users"
,
name:
"fk_2e88fb7ce9"
,
on_delete: :cascade
add_foreign_key
"members"
,
"users"
,
name:
"fk_2e88fb7ce9"
,
on_delete: :cascade
...
...
doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
View file @
bed94832
...
@@ -83,6 +83,72 @@ that are on the remote repository, eg. from branch `master`:
...
@@ -83,6 +83,72 @@ that are on the remote repository, eg. from branch `master`:
git lfs fetch master
git lfs fetch master
```
```
## File Locking
The first thing to do before using File Locking is to tell Git LFS which
kind of files are lockable. The following command will store PNG files
in LFS and flag them as lockable:
```
bash
git lfs track
"*.png"
--lockable
```
After executing the above command a file named
`.gitattributes`
will be
created or updated with the following content:
```
bash
*
.png
filter
=
lfs
diff
=
lfs
merge
=
lfs
-text
lockable
```
You can also register a file type as lockable without using LFS
(In order to be able to lock/unlock a file you need a remote server that implements the LFS File Locking API),
in order to do that you can edit the
`.gitattributes`
file manually:
```
bash
*
.pdf lockable
```
After a file type has been registered as lockable, Git LFS will make
them readonly on the file system automatically. This means you will
need to lock the file before editing it.
### Managing Locked Files
Once you're ready to edit your file you need to lock it first:
```
bash
git lfs lock images/banner.png
Locked images/banner.png
```
This will register the file as locked in your name on the server:
```
bash
git lfs locks
images/banner.png joe ID:123
```
Once you have pushed your changes, you can unlock the file so others can
also edit it:
```
bash
git lfs unlock images/banner.png
```
You can also unlock by id:
```
bash
git lfs unlock
--id
=
123
```
If for some reason you need to unlock a file that was not locked by you,
you can use the
`--force`
flag as long as you have a
`master`
access on
the project:
```
bash
git lfs unlock
--id
=
123
--force
```
## Troubleshooting
## Troubleshooting
### error: Repository or object not found
### error: Repository or object not found
...
...
lib/gitlab/checks/change_access.rb
View file @
bed94832
...
@@ -31,13 +31,14 @@ module Gitlab
...
@@ -31,13 +31,14 @@ module Gitlab
@protocol
=
protocol
@protocol
=
protocol
end
end
def
exec
def
exec
(
skip_commits_check:
false
)
return
true
if
skip_authorization
return
true
if
skip_authorization
push_checks
push_checks
branch_checks
branch_checks
tag_checks
tag_checks
lfs_objects_exist_check
lfs_objects_exist_check
commits_check
unless
skip_commits_check
true
true
end
end
...
@@ -117,6 +118,24 @@ module Gitlab
...
@@ -117,6 +118,24 @@ module Gitlab
end
end
end
end
def
commits_check
return
if
deletion?
||
newrev
.
nil?
# n+1: https://gitlab.com/gitlab-org/gitlab-ee/issues/3593
::
Gitlab
::
GitalyClient
.
allow_n_plus_1_calls
do
commits
.
each
do
|
commit
|
commit_check
.
validate
(
commit
,
validations_for_commit
(
commit
))
end
end
commit_check
.
validate_file_paths
end
# Method overwritten in EE to inject custom validations
def
validations_for_commit
(
_
)
[]
end
private
private
def
updated_from_web?
def
updated_from_web?
...
@@ -150,6 +169,14 @@ module Gitlab
...
@@ -150,6 +169,14 @@ module Gitlab
raise
GitAccess
::
UnauthorizedError
,
ERROR_MESSAGES
[
:lfs_objects_missing
]
raise
GitAccess
::
UnauthorizedError
,
ERROR_MESSAGES
[
:lfs_objects_missing
]
end
end
end
end
def
commit_check
@commit_check
||=
Gitlab
::
Checks
::
CommitCheck
.
new
(
project
,
user_access
.
user
,
newrev
,
oldrev
)
end
def
commits
project
.
repository
.
new_commits
(
newrev
)
end
end
end
end
end
end
end
lib/gitlab/checks/commit_check.rb
0 → 100644
View file @
bed94832
module
Gitlab
module
Checks
class
CommitCheck
include
Gitlab
::
Utils
::
StrongMemoize
attr_reader
:project
,
:user
,
:newrev
,
:oldrev
def
initialize
(
project
,
user
,
newrev
,
oldrev
)
@project
=
project
@user
=
user
@newrev
=
user
@oldrev
=
user
@file_paths
=
[]
end
def
validate
(
commit
,
validations
)
return
if
validations
.
empty?
&&
path_validations
.
empty?
commit
.
raw_deltas
.
each
do
|
diff
|
@file_paths
<<
(
diff
.
new_path
||
diff
.
old_path
)
validations
.
each
do
|
validation
|
if
error
=
validation
.
call
(
diff
)
raise
::
Gitlab
::
GitAccess
::
UnauthorizedError
,
error
end
end
end
end
def
validate_file_paths
path_validations
.
each
do
|
validation
|
if
error
=
validation
.
call
(
@file_paths
)
raise
::
Gitlab
::
GitAccess
::
UnauthorizedError
,
error
end
end
end
private
def
validate_lfs_file_locks?
strong_memoize
(
:validate_lfs_file_locks
)
do
project
.
lfs_enabled?
&&
project
.
lfs_file_locks
.
any?
&&
newrev
&&
oldrev
end
end
def
lfs_file_locks_validation
lambda
do
|
paths
|
lfs_lock
=
project
.
lfs_file_locks
.
where
(
path:
paths
).
where
.
not
(
user_id:
user
.
id
).
first
if
lfs_lock
return
"The path '
#{
lfs_lock
.
path
}
' is locked in Git LFS by
#{
lfs_lock
.
user
.
name
}
"
end
end
end
def
path_validations
validate_lfs_file_locks?
?
[
lfs_file_locks_validation
]
:
[]
end
end
end
end
lib/gitlab/import_export/import_export.yml
View file @
bed94832
...
@@ -27,6 +27,8 @@ project_tree:
...
@@ -27,6 +27,8 @@ project_tree:
-
:releases
-
:releases
-
project_members
:
-
project_members
:
-
:user
-
:user
-
lfs_file_locks
:
-
:user
-
merge_requests
:
-
merge_requests
:
-
notes
:
-
notes
:
-
:author
-
:author
...
...
spec/factories/lfs_file_locks.rb
0 → 100644
View file @
bed94832
FactoryBot
.
define
do
factory
:lfs_file_lock
do
user
project
path
'README.md'
end
end
spec/lib/gitlab/checks/change_access_spec.rb
View file @
bed94832
...
@@ -177,5 +177,44 @@ describe Gitlab::Checks::ChangeAccess do
...
@@ -177,5 +177,44 @@ describe Gitlab::Checks::ChangeAccess do
expect
{
subject
.
exec
}.
not_to
raise_error
expect
{
subject
.
exec
}.
not_to
raise_error
end
end
end
end
context
'LFS file lock check'
do
let
(
:owner
)
{
create
(
:user
)
}
let!
(
:lock
)
{
create
(
:lfs_file_lock
,
user:
owner
,
project:
project
,
path:
'README'
)
}
before
do
allow
(
project
.
repository
).
to
receive
(
:new_commits
).
and_return
(
project
.
repository
.
commits_between
(
'be93687618e4b132087f430a4d8fc3a609c9b77c'
,
'54fcc214b94e78d7a41a9a8fe6d87a5e59500e51'
)
)
end
context
'with LFS not enabled'
do
it
'skips the validation'
do
expect_any_instance_of
(
described_class
).
not_to
receive
(
:lfs_file_locks_validation
)
subject
.
exec
end
end
context
'with LFS enabled'
do
before
do
allow
(
project
).
to
receive
(
:lfs_enabled?
).
and_return
(
true
)
end
context
'when change is sent by a different user'
do
it
'raises an error if the user is not allowed to update the file'
do
expect
{
subject
.
exec
}.
to
raise_error
(
Gitlab
::
GitAccess
::
UnauthorizedError
,
"The path 'README' is locked in Git LFS by
#{
lock
.
user
.
name
}
"
)
end
end
context
'when change is sent by the author od the lock'
do
let
(
:user
)
{
owner
}
it
"doesn't raise any error"
do
expect
{
subject
.
exec
}.
not_to
raise_error
end
end
end
end
end
end
end
end
spec/lib/gitlab/import_export/all_models.yml
View file @
bed94832
...
@@ -276,6 +276,7 @@ project:
...
@@ -276,6 +276,7 @@ project:
-
fork_network_member
-
fork_network_member
-
fork_network
-
fork_network
-
custom_attributes
-
custom_attributes
-
lfs_file_locks
award_emoji
:
award_emoji
:
-
awardable
-
awardable
-
user
-
user
...
@@ -290,3 +291,5 @@ push_event_payload:
...
@@ -290,3 +291,5 @@ push_event_payload:
issue_assignees
:
issue_assignees
:
-
issue
-
issue
-
assignee
-
assignee
lfs_file_locks
:
-
user
spec/lib/gitlab/import_export/safe_model_attributes.yml
View file @
bed94832
...
@@ -530,3 +530,9 @@ ProjectCustomAttribute:
...
@@ -530,3 +530,9 @@ ProjectCustomAttribute:
-
project_id
-
project_id
-
key
-
key
-
value
-
value
LfsFileLock
:
-
id
-
path
-
user_id
-
project_id
-
created_at
spec/models/lfs_file_lock_spec.rb
0 → 100644
View file @
bed94832
require
'rails_helper'
describe
LfsFileLock
do
set
(
:lfs_file_lock
)
{
create
(
:lfs_file_lock
)
}
subject
{
lfs_file_lock
}
it
{
is_expected
.
to
belong_to
(
:project
)
}
it
{
is_expected
.
to
belong_to
(
:user
)
}
it
{
is_expected
.
to
validate_presence_of
(
:project_id
)
}
it
{
is_expected
.
to
validate_presence_of
(
:user_id
)
}
it
{
is_expected
.
to
validate_presence_of
(
:path
)
}
describe
'#can_be_unlocked_by?'
do
let
(
:developer
)
{
create
(
:user
)
}
let
(
:master
)
{
create
(
:user
)
}
before
do
project
=
lfs_file_lock
.
project
project
.
add_developer
(
developer
)
project
.
add_master
(
master
)
end
context
"when it's forced"
do
it
'can be unlocked by the author'
do
user
=
lfs_file_lock
.
user
expect
(
lfs_file_lock
.
can_be_unlocked_by?
(
user
,
true
)).
to
eq
(
true
)
end
it
'can be unlocked by a master'
do
expect
(
lfs_file_lock
.
can_be_unlocked_by?
(
master
,
true
)).
to
eq
(
true
)
end
it
"can't be unlocked by other user"
do
expect
(
lfs_file_lock
.
can_be_unlocked_by?
(
developer
,
true
)).
to
eq
(
false
)
end
end
context
"when it isn't forced"
do
it
'can be unlocked by the author'
do
user
=
lfs_file_lock
.
user
expect
(
lfs_file_lock
.
can_be_unlocked_by?
(
user
)).
to
eq
(
true
)
end
it
"can't be unlocked by a master"
do
expect
(
lfs_file_lock
.
can_be_unlocked_by?
(
master
)).
to
eq
(
false
)
end
it
"can't be unlocked by other user"
do
expect
(
lfs_file_lock
.
can_be_unlocked_by?
(
developer
)).
to
eq
(
false
)
end
end
end
end
spec/models/project_spec.rb
View file @
bed94832
...
@@ -80,6 +80,7 @@ describe Project do
...
@@ -80,6 +80,7 @@ describe Project do
it
{
is_expected
.
to
have_many
(
:members_and_requesters
)
}
it
{
is_expected
.
to
have_many
(
:members_and_requesters
)
}
it
{
is_expected
.
to
have_many
(
:clusters
)
}
it
{
is_expected
.
to
have_many
(
:clusters
)
}
it
{
is_expected
.
to
have_many
(
:custom_attributes
).
class_name
(
'ProjectCustomAttribute'
)
}
it
{
is_expected
.
to
have_many
(
:custom_attributes
).
class_name
(
'ProjectCustomAttribute'
)
}
it
{
is_expected
.
to
have_many
(
:lfs_file_locks
)
}
context
'after initialized'
do
context
'after initialized'
do
it
"has a project_feature"
do
it
"has a project_feature"
do
...
...
spec/models/repository_spec.rb
View file @
bed94832
...
@@ -262,6 +262,28 @@ describe Repository do
...
@@ -262,6 +262,28 @@ describe Repository do
end
end
end
end
describe
'#new_commits'
do
let
(
:new_refs
)
do
double
(
:git_rev_list
,
new_refs:
%w[
c1acaa58bbcbc3eafe538cb8274ba387047b69f8
5937ac0a7beb003549fc5fd26fc247adbce4a52e
]
)
end
it
'delegates to Gitlab::Git::RevList'
do
expect
(
Gitlab
::
Git
::
RevList
).
to
receive
(
:new
).
with
(
repository
.
raw
,
newrev:
'aaaabbbbccccddddeeeeffffgggghhhhiiiijjjj'
).
and_return
(
new_refs
)
commits
=
repository
.
new_commits
(
'aaaabbbbccccddddeeeeffffgggghhhhiiiijjjj'
)
expect
(
commits
).
to
eq
([
repository
.
commit
(
'c1acaa58bbcbc3eafe538cb8274ba387047b69f8'
),
repository
.
commit
(
'5937ac0a7beb003549fc5fd26fc247adbce4a52e'
)
])
end
end
describe
'#commits_by'
do
describe
'#commits_by'
do
set
(
:project
)
{
create
(
:project
,
:repository
)
}
set
(
:project
)
{
create
(
:project
,
:repository
)
}
...
...
spec/requests/lfs_http_spec.rb
View file @
bed94832
...
@@ -1208,7 +1208,7 @@ describe 'Git LFS API and storage' do
...
@@ -1208,7 +1208,7 @@ describe 'Git LFS API and storage' do
end
end
def
post_lfs_json
(
url
,
body
=
nil
,
headers
=
nil
)
def
post_lfs_json
(
url
,
body
=
nil
,
headers
=
nil
)
post
(
url
,
body
.
try
(
:to_json
),
(
headers
||
{}).
merge
(
'Content-Type'
=>
'application/vnd.git-lfs+json'
))
post
(
url
,
body
.
try
(
:to_json
),
(
headers
||
{}).
merge
(
'Content-Type'
=>
LfsRequest
::
CONTENT_TYPE
))
end
end
def
json_response
def
json_response
...
...
spec/requests/lfs_locks_api_spec.rb
0 → 100644
View file @
bed94832
require
'spec_helper'
describe
'Git LFS File Locking API'
do
include
WorkhorseHelpers
let
(
:project
)
{
create
(
:project
)
}
let
(
:master
)
{
create
(
:user
)
}
let
(
:developer
)
{
create
(
:user
)
}
let
(
:guest
)
{
create
(
:user
)
}
let
(
:path
)
{
'README.md'
}
let
(
:headers
)
do
{
'Authorization'
=>
authorization
}.
compact
end
shared_examples
'unauthorized request'
do
context
'when user is not authorized'
do
let
(
:authorization
)
{
authorize_user
(
guest
)
}
it
'returns a forbidden 403 response'
do
post_lfs_json
url
,
body
,
headers
expect
(
response
).
to
have_gitlab_http_status
(
403
)
end
end
end
before
do
allow
(
Gitlab
.
config
.
lfs
).
to
receive
(
:enabled
).
and_return
(
true
)
project
.
add_developer
(
master
)
project
.
add_developer
(
developer
)
project
.
add_guest
(
guest
)
end
describe
'Create File Lock endpoint'
do
let
(
:url
)
{
"
#{
project
.
http_url_to_repo
}
/info/lfs/locks"
}
let
(
:authorization
)
{
authorize_user
(
developer
)
}
let
(
:body
)
{
{
path:
path
}
}
include_examples
'unauthorized request'
context
'with an existent lock'
do
before
do
lock_file
(
'README.md'
,
developer
)
end
it
'return an error message'
do
post_lfs_json
url
,
body
,
headers
expect
(
response
).
to
have_gitlab_http_status
(
409
)
expect
(
json_response
.
keys
).
to
match_array
(
%w(lock message documentation_url)
)
expect
(
json_response
[
'message'
]).
to
match
(
/already locked/
)
end
it
'returns the existen lock'
do
post_lfs_json
url
,
body
,
headers
expect
(
json_response
[
'lock'
][
'path'
]).
to
eq
(
'README.md'
)
end
end
context
'without an existent lock'
do
it
'creates the lock'
do
post_lfs_json
url
,
body
,
headers
expect
(
response
).
to
have_gitlab_http_status
(
201
)
expect
(
json_response
[
'lock'
].
keys
).
to
match_array
(
%w(id path locked_at owner)
)
end
end
end
describe
'Listing File Locks endpoint'
do
let
(
:url
)
{
"
#{
project
.
http_url_to_repo
}
/info/lfs/locks"
}
let
(
:authorization
)
{
authorize_user
(
developer
)
}
include_examples
'unauthorized request'
it
'returns the list of locked files'
do
lock_file
(
'README.md'
,
developer
)
lock_file
(
'README'
,
developer
)
do_get
url
,
nil
,
headers
expect
(
response
).
to
have_gitlab_http_status
(
200
)
expect
(
json_response
[
'locks'
].
size
).
to
eq
(
2
)
expect
(
json_response
[
'locks'
].
first
.
keys
).
to
match_array
(
%w(id path locked_at owner)
)
end
end
describe
'List File Locks for verification endpoint'
do
let
(
:url
)
{
"
#{
project
.
http_url_to_repo
}
/info/lfs/locks/verify"
}
let
(
:authorization
)
{
authorize_user
(
developer
)
}
include_examples
'unauthorized request'
it
'returns the list of locked files grouped by owner'
do
lock_file
(
'README.md'
,
master
)
lock_file
(
'README'
,
developer
)
post_lfs_json
url
,
nil
,
headers
expect
(
response
).
to
have_gitlab_http_status
(
200
)
expect
(
json_response
[
'ours'
].
size
).
to
eq
(
1
)
expect
(
json_response
[
'ours'
].
first
[
'path'
]).
to
eq
(
'README'
)
expect
(
json_response
[
'theirs'
].
size
).
to
eq
(
1
)
expect
(
json_response
[
'theirs'
].
first
[
'path'
]).
to
eq
(
'README.md'
)
end
end
describe
'Delete File Lock endpoint'
do
let!
(
:lock
)
{
lock_file
(
'README.md'
,
developer
)
}
let
(
:url
)
{
"
#{
project
.
http_url_to_repo
}
/info/lfs/locks/
#{
lock
[
:id
]
}
/unlock"
}
let
(
:authorization
)
{
authorize_user
(
developer
)
}
include_examples
'unauthorized request'
context
'with an existent lock'
do
it
'deletes the lock'
do
post_lfs_json
url
,
nil
,
headers
expect
(
response
).
to
have_gitlab_http_status
(
200
)
end
it
'returns the deleted lock'
do
post_lfs_json
url
,
nil
,
headers
expect
(
json_response
[
'lock'
].
keys
).
to
match_array
(
%w(id path locked_at owner)
)
end
end
end
def
lock_file
(
path
,
author
)
result
=
Lfs
::
LockFileService
.
new
(
project
,
author
,
{
path:
path
}).
execute
result
[
:lock
]
end
def
authorize_user
(
user
)
ActionController
::
HttpAuthentication
::
Basic
.
encode_credentials
(
user
.
username
,
user
.
password
)
end
def
post_lfs_json
(
url
,
body
=
nil
,
headers
=
nil
)
post
(
url
,
body
.
try
(
:to_json
),
(
headers
||
{}).
merge
(
'Content-Type'
=>
LfsRequest
::
CONTENT_TYPE
))
end
def
do_get
(
url
,
params
=
nil
,
headers
=
nil
)
get
(
url
,
(
params
||
{}),
(
headers
||
{}).
merge
(
'Content-Type'
=>
LfsRequest
::
CONTENT_TYPE
))
end
def
json_response
@json_response
||=
JSON
.
parse
(
response
.
body
)
end
end
spec/serializers/lfs_file_lock_entity_spec.rb
0 → 100644
View file @
bed94832
require
'spec_helper'
describe
LfsFileLockEntity
do
let
(
:user
)
{
create
(
:user
)
}
let
(
:resource
)
{
create
(
:lfs_file_lock
,
user:
user
)
}
let
(
:request
)
{
double
(
'request'
,
current_user:
user
)
}
subject
{
described_class
.
new
(
resource
,
request:
request
).
as_json
}
it
'exposes basic attrs of the lock'
do
expect
(
subject
).
to
include
(
:id
,
:path
,
:locked_at
)
end
it
'exposes the owner info'
do
expect
(
subject
).
to
include
(
:owner
)
expect
(
subject
[
:owner
][
:name
]).
to
eq
(
user
.
name
)
end
end
spec/services/lfs/lock_file_service_spec.rb
0 → 100644
View file @
bed94832
require
'spec_helper'
describe
Lfs
::
LockFileService
do
let
(
:project
)
{
create
(
:project
)
}
let
(
:current_user
)
{
create
(
:user
)
}
subject
{
described_class
.
new
(
project
,
current_user
,
params
)
}
describe
'#execute'
do
let
(
:params
)
{
{
path:
'README.md'
}
}
context
'when not authorized'
do
it
"doesn't succeed"
do
result
=
subject
.
execute
expect
(
result
[
:status
]).
to
eq
(
:error
)
expect
(
result
[
:http_status
]).
to
eq
(
403
)
expect
(
result
[
:message
]).
to
eq
(
'You have no permissions'
)
end
end
context
'when authorized'
do
before
do
project
.
add_developer
(
current_user
)
end
context
'with an existent lock'
do
let!
(
:lock
)
{
create
(
:lfs_file_lock
,
project:
project
)
}
it
"doesn't succeed"
do
expect
(
subject
.
execute
[
:status
]).
to
eq
(
:error
)
end
it
"doesn't create the Lock"
do
expect
do
subject
.
execute
end
.
not_to
change
{
LfsFileLock
.
count
}
end
end
context
'without an existent lock'
do
it
"succeeds"
do
expect
(
subject
.
execute
[
:status
]).
to
eq
(
:success
)
end
it
"creates the Lock"
do
expect
do
subject
.
execute
end
.
to
change
{
LfsFileLock
.
count
}.
by
(
1
)
end
end
context
'when an error is raised'
do
it
"doesn't succeed"
do
allow_any_instance_of
(
described_class
).
to
receive
(
:create_lock!
).
and_raise
(
StandardError
)
expect
(
subject
.
execute
[
:status
]).
to
eq
(
:error
)
end
end
end
end
end
spec/services/lfs/locks_finder_service_spec.rb
0 → 100644
View file @
bed94832
require
'spec_helper'
describe
Lfs
::
LocksFinderService
do
let
(
:project
)
{
create
(
:project
)
}
let
(
:user
)
{
create
(
:user
)
}
let
(
:params
)
{
{}
}
subject
{
described_class
.
new
(
project
,
user
,
params
)
}
shared_examples
'no results'
do
it
'returns an empty list'
do
result
=
subject
.
execute
expect
(
result
[
:status
]).
to
eq
(
:success
)
expect
(
result
[
:locks
]).
to
be_blank
end
end
describe
'#execute'
do
let!
(
:lock_1
)
{
create
(
:lfs_file_lock
,
project:
project
)
}
let!
(
:lock_2
)
{
create
(
:lfs_file_lock
,
project:
project
,
path:
'README'
)
}
context
'find by id'
do
context
'with results'
do
let
(
:params
)
do
{
id:
lock_1
.
id
}
end
it
'returns the record'
do
result
=
subject
.
execute
expect
(
result
[
:status
]).
to
eq
(
:success
)
expect
(
result
[
:locks
].
size
).
to
eq
(
1
)
expect
(
result
[
:locks
].
first
).
to
eq
(
lock_1
)
end
end
context
'without results'
do
let
(
:params
)
do
{
id:
123
}
end
include_examples
'no results'
end
end
context
'find by path'
do
context
'with results'
do
let
(
:params
)
do
{
path:
lock_1
.
path
}
end
it
'returns the record'
do
result
=
subject
.
execute
expect
(
result
[
:status
]).
to
eq
(
:success
)
expect
(
result
[
:locks
].
size
).
to
eq
(
1
)
expect
(
result
[
:locks
].
first
).
to
eq
(
lock_1
)
end
end
context
'without results'
do
let
(
:params
)
do
{
path:
'not-found'
}
end
include_examples
'no results'
end
end
context
'find all'
do
context
'with results'
do
it
'returns all the records'
do
result
=
subject
.
execute
expect
(
result
[
:status
]).
to
eq
(
:success
)
expect
(
result
[
:locks
].
size
).
to
eq
(
2
)
end
end
context
'without results'
do
before
do
LfsFileLock
.
delete_all
end
include_examples
'no results'
end
end
context
'when an error is raised'
do
it
"doesn't succeed"
do
allow_any_instance_of
(
described_class
).
to
receive
(
:find_locks
).
and_raise
(
StandardError
)
result
=
subject
.
execute
expect
(
result
[
:status
]).
to
eq
(
:error
)
expect
(
result
[
:locks
]).
to
be_blank
end
end
end
end
spec/services/lfs/unlock_file_service_spec.rb
0 → 100644
View file @
bed94832
require
'spec_helper'
describe
Lfs
::
UnlockFileService
do
let
(
:project
)
{
create
(
:project
)
}
let
(
:current_user
)
{
create
(
:user
)
}
let
(
:lock_author
)
{
create
(
:user
)
}
let!
(
:lock
)
{
create
(
:lfs_file_lock
,
user:
lock_author
,
project:
project
)
}
let
(
:params
)
{
{}
}
subject
{
described_class
.
new
(
project
,
current_user
,
params
)
}
describe
'#execute'
do
context
'when not authorized'
do
it
"doesn't succeed"
do
result
=
subject
.
execute
expect
(
result
[
:status
]).
to
eq
(
:error
)
expect
(
result
[
:http_status
]).
to
eq
(
403
)
expect
(
result
[
:message
]).
to
eq
(
'You have no permissions'
)
end
end
context
'when authorized'
do
before
do
project
.
add_developer
(
current_user
)
end
context
'when lock does not exists'
do
let
(
:params
)
{
{
id:
123
}
}
it
"doesn't succeed"
do
result
=
subject
.
execute
expect
(
result
[
:status
]).
to
eq
(
:error
)
expect
(
result
[
:http_status
]).
to
eq
(
404
)
end
end
context
'when unlocked by the author'
do
let
(
:current_user
)
{
lock_author
}
let
(
:params
)
{
{
id:
lock
.
id
}
}
it
"succeeds"
do
result
=
subject
.
execute
expect
(
result
[
:status
]).
to
eq
(
:success
)
expect
(
result
[
:lock
]).
to
be_present
end
end
context
'when unlocked by a different user'
do
let
(
:current_user
)
{
create
(
:user
)
}
let
(
:params
)
{
{
id:
lock
.
id
}
}
it
"doesn't succeed"
do
result
=
subject
.
execute
expect
(
result
[
:status
]).
to
eq
(
:error
)
expect
(
result
[
:message
]).
to
match
(
/is locked by GitLab User
#{
lock_author
.
id
}
/
)
expect
(
result
[
:http_status
]).
to
eq
(
403
)
end
end
context
'when forced'
do
let
(
:developer
)
{
create
(
:user
)
}
let
(
:master
)
{
create
(
:user
)
}
before
do
project
.
add_developer
(
developer
)
project
.
add_master
(
master
)
end
context
'by a regular user'
do
let
(
:current_user
)
{
developer
}
let
(
:params
)
do
{
id:
lock
.
id
,
force:
true
}
end
it
"doesn't succeed"
do
result
=
subject
.
execute
expect
(
result
[
:status
]).
to
eq
(
:error
)
expect
(
result
[
:message
]).
to
match
(
/You must have master access/
)
expect
(
result
[
:http_status
]).
to
eq
(
403
)
end
end
context
'by a master user'
do
let
(
:current_user
)
{
master
}
let
(
:params
)
do
{
id:
lock
.
id
,
force:
true
}
end
it
"succeeds"
do
result
=
subject
.
execute
expect
(
result
[
:status
]).
to
eq
(
:success
)
expect
(
result
[
:lock
]).
to
be_present
end
end
end
end
end
end
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment