Skip to content
Snippets Groups Projects
Commit 61485de5 authored by Valery Sizov's avatar Valery Sizov
Browse files

file lock: feature mockup

parent ff0bfe78
No related branches found
No related tags found
No related merge requests found
Loading
Loading
@@ -11,6 +11,15 @@ module BlobHelper
%w(credits changelog news copying copyright license authors)
end
 
def lock_file_link(project = @project, path = @path)
# TODO: implement the condition
if false
button_tag 'Lock', class: 'btn disabled has-tooltip btn-file-option', title: 'You can only edit files when you are on a branch', data: { container: 'body' }
elsif true#can_lock_path?(path, project, ref)
link_to 'Lock', "#", class: 'btn btn-file-option'
end
end
def edit_blob_link(project = @project, ref = @ref, path = @path, options = {})
return unless current_user
 
Loading
Loading
class LockedPath < ActiveRecord::Base
end
class PathLock < ActiveRecord::Base
belongs_to :project
belongs_to :user
end
\ No newline at end of file
Loading
Loading
@@ -128,6 +128,7 @@ class Project < ActiveRecord::Base
has_many :variables, dependent: :destroy, class_name: 'Ci::Variable', foreign_key: :gl_project_id
has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger', foreign_key: :gl_project_id
has_many :remote_mirrors, dependent: :destroy
has_many :path_locks, dependent: :destroy
 
accepts_nested_attributes_for :variables, allow_destroy: true
accepts_nested_attributes_for :remote_mirrors,
Loading
Loading
@@ -1141,6 +1142,18 @@ class Project < ActiveRecord::Base
Dir.exist?(public_pages_path)
end
 
def path_lock_info(path)
unless @path_matcher
@path_matcher = Gitlab::LockedPathMatcher.new(self)
end
@path_matcher.get_lock_info(path)
end
def any_path_locks?
path_locks.any?
end
def schedule_delete!(user_id, params)
# Queue this task for after the commit, so once we mark pending_delete it will run
run_after_commit { ProjectDestroyWorker.perform_async(id, user_id, params) }
Loading
Loading
Loading
Loading
@@ -16,6 +16,7 @@
 
- if current_user
.btn-group{ role: "group" }
= lock_file_link
= edit_blob_link
= replace_blob_link
= delete_blob_link
Loading
Loading
@@ -15,6 +15,8 @@
= link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace"
&ndash;
= truncate(@commit.title, length: 50)
= link_to 'Lock', '#', class: 'pull-right prepend-left-10'
= link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id), class: 'pull-right'
 
- if @path.present?
Loading
Loading
class CreatePathLocksTable < ActiveRecord::Migration
def change
create_table :path_locks do |t|
t.string :path, null: false, index: true
t.references :project, index: true, foreign_key: true
t.references :user, index: true, foreign_key: true
t.timestamps null: false
end
end
end
Loading
Loading
@@ -11,7 +11,8 @@
#
# It's strongly recommended that you check this file into your version control system.
 
ActiveRecord::Schema.define(version: 20160530214349) do
ActiveRecord::Schema.define(version: 20160601102211) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
enable_extension "pg_trgm"
Loading
Loading
@@ -85,6 +86,10 @@ ActiveRecord::Schema.define(version: 20160530214349) do
t.text "disabled_oauth_sign_in_sources"
t.string "health_check_access_token"
t.boolean "send_user_confirmation_email", default: false
t.boolean "es_indexing", default: false, null: false
t.boolean "es_search", default: false, null: false
t.string "es_host", default: "localhost"
t.string "es_port", default: "9200"
t.integer "container_registry_token_expire_delay", default: 5
end
 
Loading
Loading
@@ -822,6 +827,18 @@ ActiveRecord::Schema.define(version: 20160530214349) do
 
add_index "pages_domains", ["domain"], name: "index_pages_domains_on_domain", unique: true, using: :btree
 
create_table "path_locks", force: :cascade do |t|
t.string "path", null: false
t.integer "project_id"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "path_locks", ["path"], name: "index_path_locks_on_path", using: :btree
add_index "path_locks", ["project_id"], name: "index_path_locks_on_project_id", using: :btree
add_index "path_locks", ["user_id"], name: "index_path_locks_on_user_id", using: :btree
create_table "project_group_links", force: :cascade do |t|
t.integer "project_id", null: false
t.integer "group_id", null: false
Loading
Loading
@@ -1193,6 +1210,8 @@ ActiveRecord::Schema.define(version: 20160530214349) do
add_index "web_hooks", ["created_at", "id"], name: "index_web_hooks_on_created_at_and_id", using: :btree
add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree
 
add_foreign_key "path_locks", "projects"
add_foreign_key "path_locks", "users"
add_foreign_key "remote_mirrors", "projects"
add_foreign_key "u2f_registrations", "users"
end
Loading
Loading
@@ -186,13 +186,47 @@ module Gitlab
 
# Return build_status_object(true) if all git hook checks passed successfully
# or build_status_object(false) if any hook fails
git_hook_check(user, project, ref, oldrev, newrev)
result = git_hook_check(user, project, ref, oldrev, newrev)
if result.status
result = file_locks_check(user, project, ref, oldrev, newrev)
end
return result
end
 
def forced_push?(oldrev, newrev)
Gitlab::ForcePushCheck.force_push?(project, oldrev, newrev)
end
 
def file_locks_check(user, project, ref, oldrev, newrev)
unless project.any_path_locks? && newrev && oldrev
return build_status_object(true)
end
# if newrev is blank, the branch was deleted
if Gitlab::Git.blank_ref?(newrev)
return build_status_object(true)
end
# if oldrev is blank, the branch was just created
oldrev = project.default_branch if Gitlab::Git.blank_ref?(oldrev)
commits(newrev, oldrev, project).each do |commit|
next if commit_from_annex_sync?(commit.safe_message) || old_commit?(commit)
commit.diffs.each do |diff|
path = diff.new_path || diff.old_path
if lock_info = project.path_lock_info(path)
return build_status_object(false, "The path '#{lock_info.path}' is locked by #{lock_info.user.name}")
end
end
end
build_status_object(true)
end
def git_hook_check(user, project, ref, oldrev, newrev)
unless project.git_hook && newrev && oldrev
return build_status_object(true)
Loading
Loading
# The database stores locked paths as following:
# 'app/models/project.rb' or 'lib/gitlab'
# To determine that 'lib/gitlab/some_class.rb' is locked we need to generate
# tokens for every requested paths and check every token whether it exist in locked paths or not.
# So for 'lib/gitlab/some_class.rb' path we would need to search next paths:
# 'lib', 'lib/gitlab' and 'lib/gitlab/some_class.rb'
# It's also desirable to use cache or memoization for common paths like 'lib' 'lib/gitlab', 'app', etc.
class Gitlab::LockedPathMatcher
def initialize(project)
@project = project
@non_locked_paths = []
end
def get_lock_info(path)
tokenizer(path).each do |token|
if lock = find_lock(token)
return lock
end
end
false
end
private
# This returns hierarchy tokens for path
# app/models/project.rb => ['app', 'app/models', 'app/models/project.rb']
def tokenizer(path)
tokens = []
path.split('/').each do |fragment|
last_token = tokens.last
if last_token
tokens << "#{last_token}/#{fragment}"
else
tokens << fragment
end
end
tokens
end
# TODO: Fix case insensitiveness
def find_lock(token)
if @non_locked_paths.include? token
return false
end
lock = @project.path_locks.find_by(path: token)
unless lock
@non_locked_paths << token
end
lock
end
end
\ No newline at end of file
FactoryGirl.define do
factory :path_lock do
project
user { create :user }
sequence(:path) { |n| "app/model#{n}" }
end
end
require 'spec_helper'
describe Gitlab::LockedPathMatcher, lib: true do
let(:project) { create :empty_project }
let(:user) { create :user }
let(:matcher) { Gitlab::LockedPathMatcher.new(project) }
it "returns correct lock information" do
lock1 = create :path_lock, project: project, path: 'app'
lock2 = create :path_lock, project: project, path: 'lib/gitlab/repo.rb'
expect(matcher.get_lock_info('app')).to eq(lock1)
expect(matcher.get_lock_info('app/models/project.rb')).to eq(lock1)
expect(matcher.get_lock_info('lib')).to be_falsey
expect(matcher.get_lock_info('lib/gitlab/repo.rb')).to eq(lock2)
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment