diff --git a/Gemfile b/Gemfile index 53210137f7a..0703b382e38 100644 --- a/Gemfile +++ b/Gemfile @@ -34,6 +34,7 @@ gem "omniauth-ldap" gem 'bootstrap-sass', "1.4.4" gem "colored" gem 'yaml_db', :git => "https://github.com/gitlabhq/yaml_db.git" +gem 'modularity' group :assets do gem "sass-rails", "3.2.3" diff --git a/Gemfile.lock b/Gemfile.lock index dbac6a52bf5..46831d37422 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -149,6 +149,7 @@ GEM treetop (~> 1.4.8) method_source (0.7.0) mime-types (1.17.2) + modularity (0.6.1) multi_json (1.0.4) multi_xml (0.4.1) mysql2 (0.3.11) @@ -326,6 +327,7 @@ DEPENDENCIES kaminari launchy letter_opener + modularity mysql2 omniauth-ldap pry diff --git a/app/models/project.rb b/app/models/project.rb index 8811176170e..e2646ead503 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -3,6 +3,11 @@ require "grit" class Project < ActiveRecord::Base belongs_to :owner, :class_name => "User" + does "project/validations" + does "project/repository" + does "project/permissions" + does "project/hooks" + has_many :users, :through => :users_projects has_many :events, :dependent => :destroy has_many :merge_requests, :dependent => :destroy @@ -15,32 +20,6 @@ class Project < ActiveRecord::Base has_many :wikis, :dependent => :destroy has_many :protected_branches, :dependent => :destroy - validates :name, - :uniqueness => true, - :presence => true, - :length => { :within => 0..255 } - - validates :path, - :uniqueness => true, - :presence => true, - :format => { :with => /^[a-zA-Z0-9_\-\.]*$/, - :message => "only letters, digits & '_' '-' '.' allowed" }, - :length => { :within => 0..255 } - - validates :description, - :length => { :within => 0..2000 } - - validates :code, - :presence => true, - :uniqueness => true, - :format => { :with => /^[a-zA-Z0-9_\-\.]*$/, - :message => "only letters, digits & '_' '-' '.' allowed" }, - :length => { :within => 3..255 } - - validates :owner, :presence => true - validate :check_limit - validate :repo_name - attr_protected :private_flag, :owner_id scope :public_only, where(:private_flag => false) @@ -66,89 +45,6 @@ class Project < ActiveRecord::Base [GIT_HOST['host'], code].join("/") end - def observe_push(oldrev, newrev, ref, author_key_id) - data = web_hook_data(oldrev, newrev, ref, author_key_id) - - Event.create( - :project => self, - :action => Event::Pushed, - :data => data, - :author_id => data[:user_id] - ) - end - - def update_merge_requests(oldrev, newrev, ref, author_key_id) - return true unless ref =~ /heads/ - branch_name = ref.gsub("refs/heads/", "") - user = Key.find_by_identifier(author_key_id).user - c_ids = self.commits_between(oldrev, newrev).map(&:id) - - # Update code for merge requests - mrs = self.merge_requests.opened.find_all_by_branch(branch_name).all - mrs.each { |merge_request| merge_request.reload_code } - - # Close merge requests - mrs = self.merge_requests.opened.where(:target_branch => branch_name).all - mrs = mrs.select(&:last_commit).select { |mr| c_ids.include?(mr.last_commit.id) } - mrs.each { |merge_request| merge_request.merge!(user.id) } - - true - end - - def execute_web_hooks(oldrev, newrev, ref, author_key_id) - ref_parts = ref.split('/') - - # Return if this is not a push to a branch (e.g. new commits) - return if ref_parts[1] !~ /heads/ || oldrev == "00000000000000000000000000000000" - - data = web_hook_data(oldrev, newrev, ref, author_key_id) - - web_hooks.each { |web_hook| web_hook.execute(data) } - end - - def web_hook_data(oldrev, newrev, ref, author_key_id) - key = Key.find_by_identifier(author_key_id) - data = { - before: oldrev, - after: newrev, - ref: ref, - user_id: key.user.id, - user_name: key.user_name, - repository: { - name: name, - url: web_url, - description: description, - homepage: web_url, - private: private? - }, - commits: [] - } - - commits_between(oldrev, newrev).each do |commit| - data[:commits] << { - id: commit.id, - message: commit.safe_message, - timestamp: commit.date.xmlschema, - url: "http://#{GIT_HOST['host']}/#{code}/commits/#{commit.id}", - author: { - name: commit.author_name, - email: commit.author_email - } - } - end - - data - end - - def open_branches - if protected_branches.empty? - self.repo.heads - else - pnames = protected_branches.map(&:name) - self.repo.heads.reject { |h| pnames.include?(h.name) } - end.sort_by(&:name) - end - def team_member_by_name_or_email(email = nil, name = nil) user = users.where("email like ? or name like ?", email, name).first users_projects.find_by_user_id(user.id) if user @@ -174,71 +70,6 @@ class Project < ActiveRecord::Base notes.where(:noteable_id => commit.id, :noteable_type => "Commit").where("line_code is not null") end - def has_commits? - !!commit - end - - # Compatible with all access rights - # Should be rewrited for new access rights - def add_access(user, *access) - access = if access.include?(:admin) - { :project_access => UsersProject::MASTER } - elsif access.include?(:write) - { :project_access => UsersProject::DEVELOPER } - else - { :project_access => UsersProject::REPORTER } - end - opts = { :user => user } - opts.merge!(access) - users_projects.create(opts) - end - - def reset_access(user) - users_projects.where(:project_id => self.id, :user_id => user.id).destroy if self.id - end - - def repository_readers - keys = Key.joins({:user => :users_projects}). - where("users_projects.project_id = ? AND users_projects.project_access = ?", id, UsersProject::REPORTER) - keys.map(&:identifier) + deploy_keys.map(&:identifier) - end - - def repository_writers - keys = Key.joins({:user => :users_projects}). - where("users_projects.project_id = ? AND users_projects.project_access = ?", id, UsersProject::DEVELOPER) - keys.map(&:identifier) - end - - def repository_masters - keys = Key.joins({:user => :users_projects}). - where("users_projects.project_id = ? AND users_projects.project_access = ?", id, UsersProject::MASTER) - keys.map(&:identifier) - end - - def allow_read_for?(user) - !users_projects.where(:user_id => user.id).empty? - end - - def guest_access_for?(user) - !users_projects.where(:user_id => user.id).empty? - end - - def report_access_for?(user) - !users_projects.where(:user_id => user.id, :project_access => [UsersProject::REPORTER, UsersProject::DEVELOPER, UsersProject::MASTER]).empty? - end - - def dev_access_for?(user) - !users_projects.where(:user_id => user.id, :project_access => [UsersProject::DEVELOPER, UsersProject::MASTER]).empty? - end - - def master_access_for?(user) - !users_projects.where(:user_id => user.id, :project_access => [UsersProject::MASTER]).empty? || owner_id == user.id - end - - def root_ref - default_branch || "master" - end - def public? !private_flag end @@ -259,112 +90,9 @@ class Project < ActiveRecord::Base end end - def check_limit - unless owner.can_create_project? - errors[:base] << ("Your own projects limit is #{owner.projects_limit}! Please contact administrator to increase it") - end - rescue - errors[:base] << ("Cant check your ability to create project") - end - - def repo_name - if path == "gitolite-admin" - errors.add(:path, " like 'gitolite-admin' is not allowed") - end - end - - def valid_repo? - repo - rescue - errors.add(:path, "Invalid repository path") - false - end - - def commit(commit_id = nil) - Commit.find_or_first(repo, commit_id) - end - - def fresh_commits(n = 10) - Commit.fresh_commits(repo, n) - end - - def commits_with_refs(n = 20) - Commit.commits_with_refs(repo, n) - end - - def commits_since(date) - Commit.commits_since(repo, date) - end - - def commits(ref, path = nil, limit = nil, offset = nil) - Commit.commits(repo, ref, path, limit, offset) - end - - def commits_between(from, to) - Commit.commits_between(repo, from, to) - end - def project_id self.id end - - def write_hooks - %w(post-receive).each do |hook| - write_hook(hook, File.read(File.join(Rails.root, 'lib', "#{hook}-hook"))) - end - end - - def write_hook(name, content) - hook_file = File.join(path_to_repo, 'hooks', name) - - File.open(hook_file, 'w') do |f| - f.write(content) - end - - File.chmod(0775, hook_file) - end - - def repo - @repo ||= Grit::Repo.new(path_to_repo) - end - - def url_to_repo - Gitlabhq::GitHost.url_to_repo(path) - end - - def path_to_repo - File.join(GIT_HOST["base_path"], "#{path}.git") - end - - def update_repository - Gitlabhq::GitHost.system.update_project(path, self) - - write_hooks if File.exists?(path_to_repo) - end - - def destroy_repository - Gitlabhq::GitHost.system.destroy_project(self) - end - - def repo_exists? - @repo_exists ||= (repo && !repo.branches.empty?) - rescue - @repo_exists = false - end - - def tags - repo.tags.map(&:name).sort.reverse - end - - def heads - @heads ||= repo.heads - end - - def tree(fcommit, path = nil) - fcommit = commit if fcommit == :head - tree = fcommit.tree - path ? (tree / path) : tree - end end # == Schema Information diff --git a/app/models/project/hooks_trait.rb b/app/models/project/hooks_trait.rb new file mode 100644 index 00000000000..fc32cf4c277 --- /dev/null +++ b/app/models/project/hooks_trait.rb @@ -0,0 +1,77 @@ +module Project::HooksTrait + as_trait do + def observe_push(oldrev, newrev, ref, author_key_id) + data = web_hook_data(oldrev, newrev, ref, author_key_id) + + Event.create( + :project => self, + :action => Event::Pushed, + :data => data, + :author_id => data[:user_id] + ) + end + + def update_merge_requests(oldrev, newrev, ref, author_key_id) + return true unless ref =~ /heads/ + branch_name = ref.gsub("refs/heads/", "") + user = Key.find_by_identifier(author_key_id).user + c_ids = self.commits_between(oldrev, newrev).map(&:id) + + # Update code for merge requests + mrs = self.merge_requests.opened.find_all_by_branch(branch_name).all + mrs.each { |merge_request| merge_request.reload_code } + + # Close merge requests + mrs = self.merge_requests.opened.where(:target_branch => branch_name).all + mrs = mrs.select(&:last_commit).select { |mr| c_ids.include?(mr.last_commit.id) } + mrs.each { |merge_request| merge_request.merge!(user.id) } + + true + end + + def execute_web_hooks(oldrev, newrev, ref, author_key_id) + ref_parts = ref.split('/') + + # Return if this is not a push to a branch (e.g. new commits) + return if ref_parts[1] !~ /heads/ || oldrev == "00000000000000000000000000000000" + + data = web_hook_data(oldrev, newrev, ref, author_key_id) + + web_hooks.each { |web_hook| web_hook.execute(data) } + end + + def web_hook_data(oldrev, newrev, ref, author_key_id) + key = Key.find_by_identifier(author_key_id) + data = { + before: oldrev, + after: newrev, + ref: ref, + user_id: key.user.id, + user_name: key.user_name, + repository: { + name: name, + url: web_url, + description: description, + homepage: web_url, + private: private? + }, + commits: [] + } + + commits_between(oldrev, newrev).each do |commit| + data[:commits] << { + id: commit.id, + message: commit.safe_message, + timestamp: commit.date.xmlschema, + url: "http://#{GIT_HOST['host']}/#{code}/commits/#{commit.id}", + author: { + name: commit.author_name, + email: commit.author_email + } + } + end + + data + end + end +end diff --git a/app/models/project/permissions_trait.rb b/app/models/project/permissions_trait.rb new file mode 100644 index 00000000000..3b90c711c2e --- /dev/null +++ b/app/models/project/permissions_trait.rb @@ -0,0 +1,60 @@ +module Project::PermissionsTrait + as_trait do + # Compatible with all access rights + # Should be rewrited for new access rights + def add_access(user, *access) + access = if access.include?(:admin) + { :project_access => UsersProject::MASTER } + elsif access.include?(:write) + { :project_access => UsersProject::DEVELOPER } + else + { :project_access => UsersProject::REPORTER } + end + opts = { :user => user } + opts.merge!(access) + users_projects.create(opts) + end + + def reset_access(user) + users_projects.where(:project_id => self.id, :user_id => user.id).destroy if self.id + end + + def repository_readers + keys = Key.joins({:user => :users_projects}). + where("users_projects.project_id = ? AND users_projects.project_access = ?", id, UsersProject::REPORTER) + keys.map(&:identifier) + deploy_keys.map(&:identifier) + end + + def repository_writers + keys = Key.joins({:user => :users_projects}). + where("users_projects.project_id = ? AND users_projects.project_access = ?", id, UsersProject::DEVELOPER) + keys.map(&:identifier) + end + + def repository_masters + keys = Key.joins({:user => :users_projects}). + where("users_projects.project_id = ? AND users_projects.project_access = ?", id, UsersProject::MASTER) + keys.map(&:identifier) + end + + def allow_read_for?(user) + !users_projects.where(:user_id => user.id).empty? + end + + def guest_access_for?(user) + !users_projects.where(:user_id => user.id).empty? + end + + def report_access_for?(user) + !users_projects.where(:user_id => user.id, :project_access => [UsersProject::REPORTER, UsersProject::DEVELOPER, UsersProject::MASTER]).empty? + end + + def dev_access_for?(user) + !users_projects.where(:user_id => user.id, :project_access => [UsersProject::DEVELOPER, UsersProject::MASTER]).empty? + end + + def master_access_for?(user) + !users_projects.where(:user_id => user.id, :project_access => [UsersProject::MASTER]).empty? || owner_id == user.id + end + end +end diff --git a/app/models/project/repository_trait.rb b/app/models/project/repository_trait.rb new file mode 100644 index 00000000000..28e47a788eb --- /dev/null +++ b/app/models/project/repository_trait.rb @@ -0,0 +1,109 @@ +module Project::RepositoryTrait + as_trait do + def valid_repo? + repo + rescue + errors.add(:path, "Invalid repository path") + false + end + + def commit(commit_id = nil) + Commit.find_or_first(repo, commit_id) + end + + def fresh_commits(n = 10) + Commit.fresh_commits(repo, n) + end + + def commits_with_refs(n = 20) + Commit.commits_with_refs(repo, n) + end + + def commits_since(date) + Commit.commits_since(repo, date) + end + + def commits(ref, path = nil, limit = nil, offset = nil) + Commit.commits(repo, ref, path, limit, offset) + end + + def commits_between(from, to) + Commit.commits_between(repo, from, to) + end + + def write_hooks + %w(post-receive).each do |hook| + write_hook(hook, File.read(File.join(Rails.root, 'lib', "#{hook}-hook"))) + end + end + + def write_hook(name, content) + hook_file = File.join(path_to_repo, 'hooks', name) + + File.open(hook_file, 'w') do |f| + f.write(content) + end + + File.chmod(0775, hook_file) + end + + def tags + repo.tags.map(&:name).sort.reverse + end + + def repo + @repo ||= Grit::Repo.new(path_to_repo) + end + + def url_to_repo + Gitlabhq::GitHost.url_to_repo(path) + end + + def path_to_repo + File.join(GIT_HOST["base_path"], "#{path}.git") + end + + def update_repository + Gitlabhq::GitHost.system.update_project(path, self) + + write_hooks if File.exists?(path_to_repo) + end + + def destroy_repository + Gitlabhq::GitHost.system.destroy_project(self) + end + + def repo_exists? + @repo_exists ||= (repo && !repo.branches.empty?) + rescue + @repo_exists = false + end + + def heads + @heads ||= repo.heads + end + + def tree(fcommit, path = nil) + fcommit = commit if fcommit == :head + tree = fcommit.tree + path ? (tree / path) : tree + end + + def open_branches + if protected_branches.empty? + self.repo.heads + else + pnames = protected_branches.map(&:name) + self.repo.heads.reject { |h| pnames.include?(h.name) } + end.sort_by(&:name) + end + + def has_commits? + !!commit + end + + def root_ref + default_branch || "master" + end + end +end diff --git a/app/models/project/validations_trait.rb b/app/models/project/validations_trait.rb new file mode 100644 index 00000000000..2a9708448c7 --- /dev/null +++ b/app/models/project/validations_trait.rb @@ -0,0 +1,43 @@ +module Project::ValidationsTrait + as_trait do + validates :name, + :uniqueness => true, + :presence => true, + :length => { :within => 0..255 } + + validates :path, + :uniqueness => true, + :presence => true, + :format => { :with => /^[a-zA-Z0-9_\-\.]*$/, + :message => "only letters, digits & '_' '-' '.' allowed" }, + :length => { :within => 0..255 } + + validates :description, + :length => { :within => 0..2000 } + + validates :code, + :presence => true, + :uniqueness => true, + :format => { :with => /^[a-zA-Z0-9_\-\.]*$/, + :message => "only letters, digits & '_' '-' '.' allowed" }, + :length => { :within => 3..255 } + + validates :owner, :presence => true + validate :check_limit + validate :repo_name + + def check_limit + unless owner.can_create_project? + errors[:base] << ("Your own projects limit is #{owner.projects_limit}! Please contact administrator to increase it") + end + rescue + errors[:base] << ("Cant check your ability to create project") + end + + def repo_name + if path == "gitolite-admin" + errors.add(:path, " like 'gitolite-admin' is not allowed") + end + end + end +end