From cac7723451e575ce39a6930990178450a2a972f0 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 2 Jan 2013 23:35:11 +0200 Subject: [PATCH 1/3] Get rid of roles --- app/models/event.rb | 135 +++++ app/models/project.rb | 516 +++++++++++++++++- app/models/user.rb | 90 ++- app/roles/account.rb | 95 ---- app/roles/authority.rb | 68 --- app/roles/namespaced_project.rb | 60 -- app/roles/note_event.rb | 43 -- app/roles/push_event.rb | 106 ---- app/roles/push_observer.rb | 149 ----- app/roles/repository.rb | 216 -------- app/roles/team.rb | 63 --- {app/roles => lib}/git_host.rb | 0 {app/roles => lib}/issue_commonality.rb | 1 - {app/roles => lib}/static_model.rb | 0 {app/roles => lib}/votes.rb | 0 spec/{roles => lib}/issue_commonality_spec.rb | 0 spec/{roles => lib}/votes_spec.rb | 0 .../project_repository_spec.rb} | 0 spec/models/user_spec.rb | 10 + spec/roles/account_role_spec.rb | 13 - 20 files changed, 744 insertions(+), 821 deletions(-) delete mode 100644 app/roles/account.rb delete mode 100644 app/roles/authority.rb delete mode 100644 app/roles/namespaced_project.rb delete mode 100644 app/roles/note_event.rb delete mode 100644 app/roles/push_event.rb delete mode 100644 app/roles/push_observer.rb delete mode 100644 app/roles/repository.rb delete mode 100644 app/roles/team.rb rename {app/roles => lib}/git_host.rb (100%) rename {app/roles => lib}/issue_commonality.rb (99%) rename {app/roles => lib}/static_model.rb (100%) rename {app/roles => lib}/votes.rb (100%) rename spec/{roles => lib}/issue_commonality_spec.rb (100%) rename spec/{roles => lib}/votes_spec.rb (100%) rename spec/{roles/repository_spec.rb => models/project_repository_spec.rb} (100%) delete mode 100644 spec/roles/account_role_spec.rb diff --git a/app/models/event.rb b/app/models/event.rb index 90376e73..eb88a4ed 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -170,4 +170,139 @@ class Event < ActiveRecord::Base "opened" end end + + def valid_push? + data[:ref] + rescue => ex + false + end + + def tag? + data[:ref]["refs/tags"] + end + + def branch? + data[:ref]["refs/heads"] + end + + def new_branch? + commit_from =~ /^00000/ + end + + def new_ref? + commit_from =~ /^00000/ + end + + def rm_ref? + commit_to =~ /^00000/ + end + + def md_ref? + !(rm_ref? || new_ref?) + end + + def commit_from + data[:before] + end + + def commit_to + data[:after] + end + + def ref_name + if tag? + tag_name + else + branch_name + end + end + + def branch_name + @branch_name ||= data[:ref].gsub("refs/heads/", "") + end + + def tag_name + @tag_name ||= data[:ref].gsub("refs/tags/", "") + end + + # Max 20 commits from push DESC + def commits + @commits ||= data[:commits].map { |commit| project.commit(commit[:id]) }.reverse + end + + def commits_count + data[:total_commits_count] || commits.count || 0 + end + + def ref_type + tag? ? "tag" : "branch" + end + + def push_action_name + if new_ref? + "pushed new" + elsif rm_ref? + "deleted" + else + "pushed to" + end + end + + def parent_commit + project.commit(commit_from) + rescue => ex + nil + end + + def last_commit + project.commit(commit_to) + rescue => ex + nil + end + + def push_with_commits? + md_ref? && commits.any? && parent_commit && last_commit + rescue Grit::NoSuchPathError + false + end + + def last_push_to_non_root? + branch? && project.default_branch != branch_name + end + + def note_commit_id + target.commit_id + end + + def note_short_commit_id + note_commit_id[0..8] + end + + def note_commit? + target.noteable_type == "Commit" + end + + def note_target + target.noteable + end + + def note_target_id + if note_commit? + target.commit_id + else + target.noteable_id.to_s + end + end + + def wall_note? + target.noteable_type.blank? + end + + def note_target_type + if target.noteable_type.present? + target.noteable_type.titleize + else + "Wall" + end.downcase + end end diff --git a/app/models/project.rb b/app/models/project.rb index a697ccf2..a5ef65cd 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -21,11 +21,7 @@ require "grit" class Project < ActiveRecord::Base - include Repository - include PushObserver - include Authority - include Team - include NamespacedProject + include GitHost class TransferError < StandardError; end @@ -277,4 +273,514 @@ class Project < ActiveRecord::Base creator end end + + def team_member_by_name_or_email(name = nil, email = nil) + user = users.where("name like ? or email like ?", name, email).first + users_projects.where(user: user) if user + end + + # Get Team Member record by user id + def team_member_by_id(user_id) + users_projects.find_by_user_id(user_id) + end + + # Add user to project + # with passed access role + def add_user_to_team(user, access_role) + add_user_id_to_team(user.id, access_role) + end + + # Add multiple users to project + # with same access role + def add_users_to_team(users, access_role) + add_users_ids_to_team(users.map(&:id), access_role) + end + + # Add user to project + # with passed access role by user id + def add_user_id_to_team(user_id, access_role) + users_projects.create( + user_id: user_id, + project_access: access_role + ) + end + + # Add multiple users to project + # with same access role by user ids + def add_users_ids_to_team(users_ids, access_role) + UsersProject.bulk_import(self, users_ids, access_role) + end + + # Update multiple project users + # to same access role by user ids + def update_users_ids_to_role(users_ids, access_role) + UsersProject.bulk_update(self, users_ids, access_role) + end + + # Delete multiple users from project by user ids + def delete_users_ids_from_team(users_ids) + UsersProject.bulk_delete(self, users_ids) + end + + # Remove all users from project team + def truncate_team + UsersProject.truncate_team(self) + 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 + repository_members[UsersProject::REPORTER] + end + + def repository_writers + repository_members[UsersProject::DEVELOPER] + end + + def repository_masters + repository_members[UsersProject::MASTER] + end + + def repository_members + keys = Hash.new {|h,k| h[k] = [] } + UsersProject.select("keys.identifier, project_access"). + joins(user: :keys).where(project_id: id). + each {|row| keys[row.project_access] << [row.identifier] } + + keys[UsersProject::REPORTER] += deploy_keys.pluck(:identifier) + keys + 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? + end + + def transfer(new_namespace) + Project.transaction do + old_namespace = namespace + self.namespace = new_namespace + + old_dir = old_namespace.try(:path) || '' + new_dir = new_namespace.try(:path) || '' + + old_repo = if old_dir.present? + File.join(old_dir, self.path) + else + self.path + end + + if Project.where(path: self.path, namespace_id: new_namespace.try(:id)).present? + raise TransferError.new("Project with same path in target namespace already exists") + end + + Gitlab::ProjectMover.new(self, old_dir, new_dir).execute + + git_host.move_repository(old_repo, self) + + save! + end + rescue Gitlab::ProjectMover::ProjectMoveError => ex + raise Project::TransferError.new(ex.message) + end + + def name_with_namespace + @name_with_namespace ||= begin + if namespace + namespace.human_name + " / " + name + else + name + end + end + end + + def namespace_owner + namespace.try(:owner) + end + + def path_with_namespace + if namespace + namespace.path + '/' + path + else + path + end + end + + # This method will be called after each post receive and only if the provided + # user is present in GitLab. + # + # All callbacks for post receive should be placed here. + def trigger_post_receive(oldrev, newrev, ref, user) + data = post_receive_data(oldrev, newrev, ref, user) + + # Create push event + self.observe_push(data) + + if push_to_branch? ref, oldrev + # Close merged MR + self.update_merge_requests(oldrev, newrev, ref, user) + + # Execute web hooks + self.execute_hooks(data.dup) + + # Execute project services + self.execute_services(data.dup) + end + + # Create satellite + self.satellite.create unless self.satellite.exists? + + # Discover the default branch, but only if it hasn't already been set to + # something else + if default_branch.nil? + update_attributes(default_branch: discover_default_branch) + end + end + + def push_to_branch? ref, oldrev + ref_parts = ref.split('/') + + # Return if this is not a push to a branch (e.g. new commits) + !(ref_parts[1] !~ /heads/ || oldrev == "00000000000000000000000000000000") + end + + def observe_push(data) + Event.create( + project: self, + action: Event::Pushed, + data: data, + author_id: data[:user_id] + ) + end + + def execute_hooks(data) + hooks.each { |hook| hook.execute(data) } + end + + def execute_services(data) + services.each do |service| + + # Call service hook only if it is active + service.execute(data) if service.active + end + end + + # Produce a hash of post-receive data + # + # data = { + # before: String, + # after: String, + # ref: String, + # user_id: String, + # user_name: String, + # repository: { + # name: String, + # url: String, + # description: String, + # homepage: String, + # }, + # commits: Array, + # total_commits_count: Fixnum + # } + # + def post_receive_data(oldrev, newrev, ref, user) + + push_commits = commits_between(oldrev, newrev) + + # Total commits count + push_commits_count = push_commits.size + + # Get latest 20 commits ASC + push_commits_limited = push_commits.last(20) + + # Hash to be passed as post_receive_data + data = { + before: oldrev, + after: newrev, + ref: ref, + user_id: user.id, + user_name: user.name, + repository: { + name: name, + url: url_to_repo, + description: description, + homepage: web_url, + }, + commits: [], + total_commits_count: push_commits_count + } + + # For perfomance purposes maximum 20 latest commits + # will be passed as post receive hook data. + # + push_commits_limited.each do |commit| + data[:commits] << { + id: commit.id, + message: commit.safe_message, + timestamp: commit.date.xmlschema, + url: "#{Gitlab.config.gitlab.url}/#{path_with_namespace}/commit/#{commit.id}", + author: { + name: commit.author_name, + email: commit.author_email + } + } + end + + data + end + + def update_merge_requests(oldrev, newrev, ref, user) + return true unless ref =~ /heads/ + branch_name = ref.gsub("refs/heads/", "") + 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; merge_request.mark_as_unchecked } + + # 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 valid_repo? + repo + rescue + errors.add(:path, "Invalid repository path") + false + end + + def empty_repo? + !repo_exists? || !has_commits? + end + + def commit(commit_id = nil) + Commit.find_or_first(repo, commit_id, root_ref) + 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 last_commit_for(ref, path = nil) + commits(ref, path, 1).first + end + + def commits_between(from, to) + Commit.commits_between(repo, from, to) + end + + def satellite + @satellite ||= Gitlab::Satellite::Satellite.new(self) + end + + def has_post_receive_file? + !!hook_file + end + + def valid_post_receive_file? + valid_hook_file == hook_file + end + + def valid_hook_file + @valid_hook_file ||= File.read(Rails.root.join('lib', 'hooks', 'post-receive')) + end + + def hook_file + @hook_file ||= begin + hook_path = File.join(path_to_repo, 'hooks', 'post-receive') + File.read(hook_path) if File.exists?(hook_path) + end + end + + # Returns an Array of branch names + def branch_names + repo.branches.collect(&:name).sort + end + + # Returns an Array of Branches + def branches + repo.branches.sort_by(&:name) + end + + # Returns an Array of tag names + def tag_names + repo.tags.collect(&:name).sort.reverse + end + + # Returns an Array of Tags + def tags + repo.tags.sort_by(&:name).reverse + end + + # Returns an Array of branch and tag names + def ref_names + [branch_names + tag_names].flatten + end + + def repo + @repo ||= Grit::Repo.new(path_to_repo) + end + + def url_to_repo + git_host.url_to_repo(path_with_namespace) + end + + def path_to_repo + File.join(Gitlab.config.gitolite.repos_path, "#{path_with_namespace}.git") + end + + def namespace_dir + namespace.try(:path) || '' + end + + def update_repository + git_host.update_repository(self) + end + + def destroy_repository + git_host.remove_repository(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 + + # Discovers the default branch based on the repository's available branches + # + # - If no branches are present, returns nil + # - If one branch is present, returns its name + # - If two or more branches are present, returns the one that has a name + # matching root_ref (default_branch or 'master' if default_branch is nil) + def discover_default_branch + if branch_names.length == 0 + nil + elsif branch_names.length == 1 + branch_names.first + else + branch_names.select { |v| v == root_ref }.first + end + end + + def has_commits? + !!commit + rescue Grit::NoSuchPathError + false + end + + def root_ref + default_branch || "master" + end + + def root_ref?(branch) + root_ref == branch + end + + # Archive Project to .tar.gz + # + # Already packed repo archives stored at + # app_root/tmp/repositories/project_name/project_name-commit-id.tag.gz + # + def archive_repo(ref) + ref = ref || self.root_ref + commit = self.commit(ref) + return nil unless commit + + # Build file path + file_name = self.path + "-" + commit.id.to_s + ".tar.gz" + storage_path = Rails.root.join("tmp", "repositories", self.path_with_namespace) + file_path = File.join(storage_path, file_name) + + # Put files into a directory before archiving + prefix = self.path + "/" + + # Create file if not exists + unless File.exists?(file_path) + FileUtils.mkdir_p storage_path + file = self.repo.archive_to_file(ref, prefix, file_path) + end + + file_path + end + + def ssh_url_to_repo + url_to_repo + end + + def http_url_to_repo + http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('') + end + + # Check if current branch name is marked as protected in the system + def protected_branch? branch_name + protected_branches.map(&:name).include?(branch_name) + end end diff --git a/app/models/user.rb b/app/models/user.rb index 582eee6d..d166ae4d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -34,8 +34,6 @@ # class User < ActiveRecord::Base - include Account - devise :database_authenticatable, :token_authenticatable, :lockable, :recoverable, :rememberable, :trackable, :validatable, :omniauthable @@ -192,4 +190,92 @@ class User < ActiveRecord::Base def tm_in_personal_projects personal_projects.users_projects.where(user_id: self.id) end + + # Returns a string for use as a Gitolite user identifier + # + # Note that Gitolite 2.x requires the following pattern for users: + # + # ^@?[0-9a-zA-Z][0-9a-zA-Z._\@+-]*$ + def identifier + # Replace non-word chars with underscores, then make sure it starts with + # valid chars + email.gsub(/\W/, '_').gsub(/\A([\W\_])+/, '') + end + + def is_admin? + admin + end + + def require_ssh_key? + keys.count == 0 + end + + def can_create_project? + projects_limit > personal_projects.count + end + + def can_create_group? + is_admin? + end + + def abilities + @abilities ||= begin + abilities = Six.new + abilities << Ability + abilities + end + end + + def can? action, subject + abilities.allowed?(self, action, subject) + end + + def last_activity_project + projects.first + end + + def first_name + name.split.first unless name.blank? + end + + def cared_merge_requests + MergeRequest.where("author_id = :id or assignee_id = :id", id: self.id) + end + + # Remove user from all projects and + # set blocked attribute to true + def block + users_projects.find_each do |membership| + return false unless membership.destroy + end + + self.blocked = true + save + end + + def projects_limit_percent + return 100 if projects_limit.zero? + (personal_projects.count.to_f / projects_limit) * 100 + end + + def recent_push project_id = nil + # Get push events not earlier than 2 hours ago + events = recent_events.code_push.where("created_at > ?", Time.now - 2.hours) + events = events.where(project_id: project_id) if project_id + + # Take only latest one + events = events.recent.limit(1).first + end + + def projects_sorted_by_activity + authorized_projects.sorted_by_activity + end + + def several_namespaces? + namespaces.size > 1 + end + + def namespace_id + namespace.try :id + end end diff --git a/app/roles/account.rb b/app/roles/account.rb deleted file mode 100644 index 7e6d1490..00000000 --- a/app/roles/account.rb +++ /dev/null @@ -1,95 +0,0 @@ -# == Account role -# -# Describe behaviour of User in application -# -# Used by User -# -module Account - # Returns a string for use as a Gitolite user identifier - # - # Note that Gitolite 2.x requires the following pattern for users: - # - # ^@?[0-9a-zA-Z][0-9a-zA-Z._\@+-]*$ - def identifier - # Replace non-word chars with underscores, then make sure it starts with - # valid chars - email.gsub(/\W/, '_').gsub(/\A([\W\_])+/, '') - end - - def is_admin? - admin - end - - def require_ssh_key? - keys.count == 0 - end - - def can_create_project? - projects_limit > personal_projects.count - end - - def can_create_group? - is_admin? - end - - def abilities - @abilities ||= begin - abilities = Six.new - abilities << Ability - abilities - end - end - - def can? action, subject - abilities.allowed?(self, action, subject) - end - - def last_activity_project - projects.first - end - - def first_name - name.split.first unless name.blank? - end - - def cared_merge_requests - MergeRequest.where("author_id = :id or assignee_id = :id", id: self.id) - end - - # Remove user from all projects and - # set blocked attribute to true - def block - users_projects.find_each do |membership| - return false unless membership.destroy - end - - self.blocked = true - save - end - - def projects_limit_percent - return 100 if projects_limit.zero? - (personal_projects.count.to_f / projects_limit) * 100 - end - - def recent_push project_id = nil - # Get push events not earlier than 2 hours ago - events = recent_events.code_push.where("created_at > ?", Time.now - 2.hours) - events = events.where(project_id: project_id) if project_id - - # Take only latest one - events = events.recent.limit(1).first - end - - def projects_sorted_by_activity - authorized_projects.sorted_by_activity - end - - def several_namespaces? - namespaces.size > 1 - end - - def namespace_id - namespace.try :id - end -end diff --git a/app/roles/authority.rb b/app/roles/authority.rb deleted file mode 100644 index 77278489..00000000 --- a/app/roles/authority.rb +++ /dev/null @@ -1,68 +0,0 @@ -# == Authority role -# -# Control access to project repository based on users role in team -# -# Used by Project -# -module Authority - # 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 - repository_members[UsersProject::REPORTER] - end - - def repository_writers - repository_members[UsersProject::DEVELOPER] - end - - def repository_masters - repository_members[UsersProject::MASTER] - end - - def repository_members - keys = Hash.new {|h,k| h[k] = [] } - UsersProject.select("keys.identifier, project_access"). - joins(user: :keys).where(project_id: id). - each {|row| keys[row.project_access] << [row.identifier] } - - keys[UsersProject::REPORTER] += deploy_keys.pluck(:identifier) - keys - 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? - end -end diff --git a/app/roles/namespaced_project.rb b/app/roles/namespaced_project.rb deleted file mode 100644 index 0f9fb051..00000000 --- a/app/roles/namespaced_project.rb +++ /dev/null @@ -1,60 +0,0 @@ -# == NamespacedProject role -# -# Provides extra functionality for Project related to namespaces like: -# - transfer project between namespaces -# - name, path including namespece -# - project owner based on namespace -# -# Used by Project -# -module NamespacedProject - def transfer(new_namespace) - Project.transaction do - old_namespace = namespace - self.namespace = new_namespace - - old_dir = old_namespace.try(:path) || '' - new_dir = new_namespace.try(:path) || '' - - old_repo = if old_dir.present? - File.join(old_dir, self.path) - else - self.path - end - - if Project.where(path: self.path, namespace_id: new_namespace.try(:id)).present? - raise TransferError.new("Project with same path in target namespace already exists") - end - - Gitlab::ProjectMover.new(self, old_dir, new_dir).execute - - git_host.move_repository(old_repo, self) - - save! - end - rescue Gitlab::ProjectMover::ProjectMoveError => ex - raise Project::TransferError.new(ex.message) - end - - def name_with_namespace - @name_with_namespace ||= begin - if namespace - namespace.human_name + " / " + name - else - name - end - end - end - - def namespace_owner - namespace.try(:owner) - end - - def path_with_namespace - if namespace - namespace.path + '/' + path - else - path - end - end -end diff --git a/app/roles/note_event.rb b/app/roles/note_event.rb deleted file mode 100644 index 8e311ea3..00000000 --- a/app/roles/note_event.rb +++ /dev/null @@ -1,43 +0,0 @@ -# == NoteEvent role -# -# Extends Event model functionality by providing extra methods related to comment events -# -# Used by Event -# -module NoteEvent - def note_commit_id - target.commit_id - end - - def note_short_commit_id - note_commit_id[0..8] - end - - def note_commit? - target.noteable_type == "Commit" - end - - def note_target - target.noteable - end - - def note_target_id - if note_commit? - target.commit_id - else - target.noteable_id.to_s - end - end - - def wall_note? - target.noteable_type.blank? - end - - def note_target_type - if target.noteable_type.present? - target.noteable_type.titleize - else - "Wall" - end.downcase - end -end diff --git a/app/roles/push_event.rb b/app/roles/push_event.rb deleted file mode 100644 index ac9c38ce..00000000 --- a/app/roles/push_event.rb +++ /dev/null @@ -1,106 +0,0 @@ -# == PushEvent role -# -# Extends Event model functionality by providing extra methods related to push events -# -# Used by Event -# -module PushEvent - def valid_push? - data[:ref] - rescue => ex - false - end - - def tag? - data[:ref]["refs/tags"] - end - - def branch? - data[:ref]["refs/heads"] - end - - def new_branch? - commit_from =~ /^00000/ - end - - def new_ref? - commit_from =~ /^00000/ - end - - def rm_ref? - commit_to =~ /^00000/ - end - - def md_ref? - !(rm_ref? || new_ref?) - end - - def commit_from - data[:before] - end - - def commit_to - data[:after] - end - - def ref_name - if tag? - tag_name - else - branch_name - end - end - - def branch_name - @branch_name ||= data[:ref].gsub("refs/heads/", "") - end - - def tag_name - @tag_name ||= data[:ref].gsub("refs/tags/", "") - end - - # Max 20 commits from push DESC - def commits - @commits ||= data[:commits].map { |commit| project.commit(commit[:id]) }.reverse - end - - def commits_count - data[:total_commits_count] || commits.count || 0 - end - - def ref_type - tag? ? "tag" : "branch" - end - - def push_action_name - if new_ref? - "pushed new" - elsif rm_ref? - "deleted" - else - "pushed to" - end - end - - def parent_commit - project.commit(commit_from) - rescue => ex - nil - end - - def last_commit - project.commit(commit_to) - rescue => ex - nil - end - - def push_with_commits? - md_ref? && commits.any? && parent_commit && last_commit - rescue Grit::NoSuchPathError - false - end - - def last_push_to_non_root? - branch? && project.default_branch != branch_name - end -end diff --git a/app/roles/push_observer.rb b/app/roles/push_observer.rb deleted file mode 100644 index 42979f4d..00000000 --- a/app/roles/push_observer.rb +++ /dev/null @@ -1,149 +0,0 @@ -# == PushObserver role -# -# Includes methods to be triggered on push to project repository. -# -# -# Used by Project -# Triggered by PostReceive job -# -module PushObserver - # This method will be called after each post receive and only if the provided - # user is present in GitLab. - # - # All callbacks for post receive should be placed here. - def trigger_post_receive(oldrev, newrev, ref, user) - data = post_receive_data(oldrev, newrev, ref, user) - - # Create push event - self.observe_push(data) - - if push_to_branch? ref, oldrev - # Close merged MR - self.update_merge_requests(oldrev, newrev, ref, user) - - # Execute web hooks - self.execute_hooks(data.dup) - - # Execute project services - self.execute_services(data.dup) - end - - # Create satellite - self.satellite.create unless self.satellite.exists? - - # Discover the default branch, but only if it hasn't already been set to - # something else - if default_branch.nil? - update_attributes(default_branch: discover_default_branch) - end - end - - def push_to_branch? ref, oldrev - ref_parts = ref.split('/') - - # Return if this is not a push to a branch (e.g. new commits) - !(ref_parts[1] !~ /heads/ || oldrev == "00000000000000000000000000000000") - end - - def observe_push(data) - Event.create( - project: self, - action: Event::Pushed, - data: data, - author_id: data[:user_id] - ) - end - - def execute_hooks(data) - hooks.each { |hook| hook.execute(data) } - end - - def execute_services(data) - services.each do |service| - - # Call service hook only if it is active - service.execute(data) if service.active - end - end - - # Produce a hash of post-receive data - # - # data = { - # before: String, - # after: String, - # ref: String, - # user_id: String, - # user_name: String, - # repository: { - # name: String, - # url: String, - # description: String, - # homepage: String, - # }, - # commits: Array, - # total_commits_count: Fixnum - # } - # - def post_receive_data(oldrev, newrev, ref, user) - - push_commits = commits_between(oldrev, newrev) - - # Total commits count - push_commits_count = push_commits.size - - # Get latest 20 commits ASC - push_commits_limited = push_commits.last(20) - - # Hash to be passed as post_receive_data - data = { - before: oldrev, - after: newrev, - ref: ref, - user_id: user.id, - user_name: user.name, - repository: { - name: name, - url: url_to_repo, - description: description, - homepage: web_url, - }, - commits: [], - total_commits_count: push_commits_count - } - - # For perfomance purposes maximum 20 latest commits - # will be passed as post receive hook data. - # - push_commits_limited.each do |commit| - data[:commits] << { - id: commit.id, - message: commit.safe_message, - timestamp: commit.date.xmlschema, - url: "#{Gitlab.config.gitlab.url}/#{path_with_namespace}/commit/#{commit.id}", - author: { - name: commit.author_name, - email: commit.author_email - } - } - end - - data - end - - def update_merge_requests(oldrev, newrev, ref, user) - return true unless ref =~ /heads/ - branch_name = ref.gsub("refs/heads/", "") - 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; merge_request.mark_as_unchecked } - - # 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 -end diff --git a/app/roles/repository.rb b/app/roles/repository.rb deleted file mode 100644 index 8896569c..00000000 --- a/app/roles/repository.rb +++ /dev/null @@ -1,216 +0,0 @@ -# == Repository role -# -# Provides access to git repository resources like commits, branches etc.. -# Allows you to manage repository via gitolite interface(git_host) -# -# Used by Project -# -module Repository - include GitHost - - def valid_repo? - repo - rescue - errors.add(:path, "Invalid repository path") - false - end - - def empty_repo? - !repo_exists? || !has_commits? - end - - def commit(commit_id = nil) - Commit.find_or_first(repo, commit_id, root_ref) - 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 last_commit_for(ref, path = nil) - commits(ref, path, 1).first - end - - def commits_between(from, to) - Commit.commits_between(repo, from, to) - end - - def satellite - @satellite ||= Gitlab::Satellite::Satellite.new(self) - end - - def has_post_receive_file? - !!hook_file - end - - def valid_post_receive_file? - valid_hook_file == hook_file - end - - def valid_hook_file - @valid_hook_file ||= File.read(Rails.root.join('lib', 'hooks', 'post-receive')) - end - - def hook_file - @hook_file ||= begin - hook_path = File.join(path_to_repo, 'hooks', 'post-receive') - File.read(hook_path) if File.exists?(hook_path) - end - end - - # Returns an Array of branch names - def branch_names - repo.branches.collect(&:name).sort - end - - # Returns an Array of Branches - def branches - repo.branches.sort_by(&:name) - end - - # Returns an Array of tag names - def tag_names - repo.tags.collect(&:name).sort.reverse - end - - # Returns an Array of Tags - def tags - repo.tags.sort_by(&:name).reverse - end - - # Returns an Array of branch and tag names - def ref_names - [branch_names + tag_names].flatten - end - - def repo - @repo ||= Grit::Repo.new(path_to_repo) - end - - def url_to_repo - git_host.url_to_repo(path_with_namespace) - end - - def path_to_repo - File.join(Gitlab.config.gitolite.repos_path, "#{path_with_namespace}.git") - end - - def namespace_dir - namespace.try(:path) || '' - end - - def update_repository - git_host.update_repository(self) - end - - def destroy_repository - git_host.remove_repository(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 - - # Discovers the default branch based on the repository's available branches - # - # - If no branches are present, returns nil - # - If one branch is present, returns its name - # - If two or more branches are present, returns the one that has a name - # matching root_ref (default_branch or 'master' if default_branch is nil) - def discover_default_branch - if branch_names.length == 0 - nil - elsif branch_names.length == 1 - branch_names.first - else - branch_names.select { |v| v == root_ref }.first - end - end - - def has_commits? - !!commit - rescue Grit::NoSuchPathError - false - end - - def root_ref - default_branch || "master" - end - - def root_ref?(branch) - root_ref == branch - end - - # Archive Project to .tar.gz - # - # Already packed repo archives stored at - # app_root/tmp/repositories/project_name/project_name-commit-id.tag.gz - # - def archive_repo(ref) - ref = ref || self.root_ref - commit = self.commit(ref) - return nil unless commit - - # Build file path - file_name = self.path + "-" + commit.id.to_s + ".tar.gz" - storage_path = Rails.root.join("tmp", "repositories", self.path_with_namespace) - file_path = File.join(storage_path, file_name) - - # Put files into a directory before archiving - prefix = self.path + "/" - - # Create file if not exists - unless File.exists?(file_path) - FileUtils.mkdir_p storage_path - file = self.repo.archive_to_file(ref, prefix, file_path) - end - - file_path - end - - def ssh_url_to_repo - url_to_repo - end - - def http_url_to_repo - http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('') - end - - # Check if current branch name is marked as protected in the system - def protected_branch? branch_name - protected_branches.map(&:name).include?(branch_name) - end -end diff --git a/app/roles/team.rb b/app/roles/team.rb deleted file mode 100644 index 8e431fc1..00000000 --- a/app/roles/team.rb +++ /dev/null @@ -1,63 +0,0 @@ -# == Team role -# -# Provides functionality to manage project team -# - add user/users to project -# - update existing membership -# - remove users from project team -# -# Used by Project -# -module Team - def team_member_by_name_or_email(name = nil, email = nil) - user = users.where("name like ? or email like ?", name, email).first - users_projects.where(user: user) if user - end - - # Get Team Member record by user id - def team_member_by_id(user_id) - users_projects.find_by_user_id(user_id) - end - - # Add user to project - # with passed access role - def add_user_to_team(user, access_role) - add_user_id_to_team(user.id, access_role) - end - - # Add multiple users to project - # with same access role - def add_users_to_team(users, access_role) - add_users_ids_to_team(users.map(&:id), access_role) - end - - # Add user to project - # with passed access role by user id - def add_user_id_to_team(user_id, access_role) - users_projects.create( - user_id: user_id, - project_access: access_role - ) - end - - # Add multiple users to project - # with same access role by user ids - def add_users_ids_to_team(users_ids, access_role) - UsersProject.bulk_import(self, users_ids, access_role) - end - - # Update multiple project users - # to same access role by user ids - def update_users_ids_to_role(users_ids, access_role) - UsersProject.bulk_update(self, users_ids, access_role) - end - - # Delete multiple users from project by user ids - def delete_users_ids_from_team(users_ids) - UsersProject.bulk_delete(self, users_ids) - end - - # Remove all users from project team - def truncate_team - UsersProject.truncate_team(self) - end -end diff --git a/app/roles/git_host.rb b/lib/git_host.rb similarity index 100% rename from app/roles/git_host.rb rename to lib/git_host.rb diff --git a/app/roles/issue_commonality.rb b/lib/issue_commonality.rb similarity index 99% rename from app/roles/issue_commonality.rb rename to lib/issue_commonality.rb index 3948ef14..b755936c 100644 --- a/app/roles/issue_commonality.rb +++ b/lib/issue_commonality.rb @@ -68,5 +68,4 @@ module IssueCommonality def is_being_reopened? closed_changed? && !closed end - end diff --git a/app/roles/static_model.rb b/lib/static_model.rb similarity index 100% rename from app/roles/static_model.rb rename to lib/static_model.rb diff --git a/app/roles/votes.rb b/lib/votes.rb similarity index 100% rename from app/roles/votes.rb rename to lib/votes.rb diff --git a/spec/roles/issue_commonality_spec.rb b/spec/lib/issue_commonality_spec.rb similarity index 100% rename from spec/roles/issue_commonality_spec.rb rename to spec/lib/issue_commonality_spec.rb diff --git a/spec/roles/votes_spec.rb b/spec/lib/votes_spec.rb similarity index 100% rename from spec/roles/votes_spec.rb rename to spec/lib/votes_spec.rb diff --git a/spec/roles/repository_spec.rb b/spec/models/project_repository_spec.rb similarity index 100% rename from spec/roles/repository_spec.rb rename to spec/models/project_repository_spec.rb diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index eb2717e3..5f6244ec 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -185,4 +185,14 @@ describe User do it { User.not_in_project(@project).should == [@user, @project.owner] } end + + describe 'normal user' do + let(:user) { create(:user, name: 'John Smith') } + + it { user.is_admin?.should be_false } + it { user.require_ssh_key?.should be_true } + it { user.can_create_group?.should be_false } + it { user.can_create_project?.should be_true } + it { user.first_name.should == 'John' } + end end diff --git a/spec/roles/account_role_spec.rb b/spec/roles/account_role_spec.rb deleted file mode 100644 index f7a128d0..00000000 --- a/spec/roles/account_role_spec.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'spec_helper' - -describe User, "Account" do - describe 'normal user' do - let(:user) { create(:user, name: 'John Smith') } - - it { user.is_admin?.should be_false } - it { user.require_ssh_key?.should be_true } - it { user.can_create_group?.should be_false } - it { user.can_create_project?.should be_true } - it { user.first_name.should == 'John' } - end -end From 40a956eb6825f2bbca06e9f24c1fb24dc71a1ecd Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 3 Jan 2013 00:01:08 +0200 Subject: [PATCH 2/3] Few more fixes after removing roles --- app/models/event.rb | 3 --- app/models/merge_request.rb | 2 +- spec/models/project_spec.rb | 8 -------- spec/models/user_spec.rb | 4 ---- spec/support/stubbed_repository.rb | 4 +--- 5 files changed, 2 insertions(+), 19 deletions(-) diff --git a/app/models/event.rb b/app/models/event.rb index eb88a4ed..95075ffa 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -15,9 +15,6 @@ # class Event < ActiveRecord::Base - include NoteEvent - include PushEvent - attr_accessible :project, :action, :data, :author_id, :project_id, :target_id, :target_type diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 052e0850..68211acc 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -20,7 +20,7 @@ # require Rails.root.join("app/models/commit") -require Rails.root.join("app/roles/static_model") +require Rails.root.join("lib/static_model") class MergeRequest < ActiveRecord::Base include IssueCommonality diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 27e68ce1..ea1efbbe 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -133,14 +133,6 @@ describe Project do it { should respond_to(:path_with_namespace) } end - describe 'modules' do - it { should include_module(Repository) } - it { should include_module(PushObserver) } - it { should include_module(Authority) } - it { should include_module(Team) } - it { should include_module(NamespacedProject) } - end - it "should return valid url to repo" do project = Project.new(path: "somewhere") project.url_to_repo.should == Gitlab.config.gitolite.ssh_path_prefix + "somewhere.git" diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 5f6244ec..51774e4c 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -65,10 +65,6 @@ describe User do it { should ensure_length_of(:bio).is_within(0..255) } end - describe 'modules' do - it { should include_module(Account) } - end - describe "Respond to" do it { should respond_to(:is_admin?) } it { should respond_to(:identifier) } diff --git a/spec/support/stubbed_repository.rb b/spec/support/stubbed_repository.rb index 5bf3ea46..ad88dd77 100644 --- a/spec/support/stubbed_repository.rb +++ b/spec/support/stubbed_repository.rb @@ -1,6 +1,6 @@ # Stubs out all Git repository access done by models so that specs can run # against fake repositories without Grit complaining that they don't exist. -module StubbedRepository +class Project def path_to_repo if new_record? || path == 'newproject' # There are a couple Project specs and features that expect the Project's @@ -27,5 +27,3 @@ module StubbedRepository end end end - -Project.send(:include, StubbedRepository) From da03a5c7e25601c2bce8375dbbe1cffc58db7bbf Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 3 Jan 2013 09:06:07 +0200 Subject: [PATCH 3/3] more refactoring using models/concerns --- .../models/concerns/issuable.rb | 35 ++++++++++++++++- app/models/issue.rb | 3 +- app/models/merge_request.rb | 3 +- app/models/project.rb | 10 ++--- app/models/protected_branch.rb | 4 +- app/models/users_project.rb | 4 +- app/observers/key_observer.rb | 6 +-- lib/git_host.rb | 11 ------ lib/gitolited.rb | 11 ++++++ lib/votes.rb | 39 ------------------- 10 files changed, 58 insertions(+), 68 deletions(-) rename lib/issue_commonality.rb => app/models/concerns/issuable.rb (72%) delete mode 100644 lib/git_host.rb create mode 100644 lib/gitolited.rb delete mode 100644 lib/votes.rb diff --git a/lib/issue_commonality.rb b/app/models/concerns/issuable.rb similarity index 72% rename from lib/issue_commonality.rb rename to app/models/concerns/issuable.rb index b755936c..f9dd74f9 100644 --- a/lib/issue_commonality.rb +++ b/app/models/concerns/issuable.rb @@ -1,10 +1,10 @@ -# == IssueCommonality role +# == Issuable concern # # Contains common functionality shared between Issues and MergeRequests # # Used by Issue, MergeRequest # -module IssueCommonality +module Issuable extend ActiveSupport::Concern included do @@ -68,4 +68,35 @@ module IssueCommonality def is_being_reopened? closed_changed? && !closed end + + # Return the number of +1 comments (upvotes) + def upvotes + notes.select(&:upvote?).size + end + + def upvotes_in_percent + if votes_count.zero? + 0 + else + 100.0 / votes_count * upvotes + end + end + + # Return the number of -1 comments (downvotes) + def downvotes + notes.select(&:downvote?).size + end + + def downvotes_in_percent + if votes_count.zero? + 0 + else + 100.0 - upvotes_in_percent + end + end + + # Return the total number of votes + def votes_count + upvotes + downvotes + end end diff --git a/app/models/issue.rb b/app/models/issue.rb index 1de9d0f9..7381136c 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -17,8 +17,7 @@ # class Issue < ActiveRecord::Base - include IssueCommonality - include Votes + include Issuable attr_accessible :title, :assignee_id, :closed, :position, :description, :milestone_id, :label_list, :author_id_of_changes diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 68211acc..b6ea85f6 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -23,8 +23,7 @@ require Rails.root.join("app/models/commit") require Rails.root.join("lib/static_model") class MergeRequest < ActiveRecord::Base - include IssueCommonality - include Votes + include Issuable attr_accessible :title, :assignee_id, :closed, :target_branch, :source_branch, :milestone_id, :author_id_of_changes diff --git a/app/models/project.rb b/app/models/project.rb index a5ef65cd..f60c2442 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -21,7 +21,7 @@ require "grit" class Project < ActiveRecord::Base - include GitHost + include Gitolited class TransferError < StandardError; end @@ -408,7 +408,7 @@ class Project < ActiveRecord::Base Gitlab::ProjectMover.new(self, old_dir, new_dir).execute - git_host.move_repository(old_repo, self) + gitolite.move_repository(old_repo, self) save! end @@ -670,7 +670,7 @@ class Project < ActiveRecord::Base end def url_to_repo - git_host.url_to_repo(path_with_namespace) + gitolite.url_to_repo(path_with_namespace) end def path_to_repo @@ -682,11 +682,11 @@ class Project < ActiveRecord::Base end def update_repository - git_host.update_repository(self) + gitolite.update_repository(self) end def destroy_repository - git_host.remove_repository(self) + gitolite.remove_repository(self) end def repo_exists? diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb index c54aa3ce..f405a7bf 100644 --- a/app/models/protected_branch.rb +++ b/app/models/protected_branch.rb @@ -10,7 +10,7 @@ # class ProtectedBranch < ActiveRecord::Base - include GitHost + include Gitolited attr_accessible :name @@ -22,7 +22,7 @@ class ProtectedBranch < ActiveRecord::Base after_destroy :update_repository def update_repository - git_host.update_repository(project) + gitolite.update_repository(project) end def commit diff --git a/app/models/users_project.rb b/app/models/users_project.rb index b8aacb32..ce7ac8ac 100644 --- a/app/models/users_project.rb +++ b/app/models/users_project.rb @@ -11,7 +11,7 @@ # class UsersProject < ActiveRecord::Base - include GitHost + include Gitolited GUEST = 10 REPORTER = 20 @@ -152,7 +152,7 @@ class UsersProject < ActiveRecord::Base end def update_repository - git_host.update_repository(project) + gitolite.update_repository(project) end def project_access_human diff --git a/app/observers/key_observer.rb b/app/observers/key_observer.rb index a3f17bde..bf5fa647 100644 --- a/app/observers/key_observer.rb +++ b/app/observers/key_observer.rb @@ -1,12 +1,12 @@ class KeyObserver < ActiveRecord::Observer - include GitHost + include Gitolited def after_save(key) - git_host.set_key(key.identifier, key.key, key.projects) + gitolite.set_key(key.identifier, key.key, key.projects) end def after_destroy(key) return if key.is_deploy_key && !key.last_deploy? - git_host.remove_key(key.identifier, key.projects) + gitolite.remove_key(key.identifier, key.projects) end end diff --git a/lib/git_host.rb b/lib/git_host.rb deleted file mode 100644 index 2410e0fe..00000000 --- a/lib/git_host.rb +++ /dev/null @@ -1,11 +0,0 @@ -# == GitHost role -# -# Provide a shortcut to Gitlab::Gitolite instance -# -# Used by Project, UsersProject -# -module GitHost - def git_host - Gitlab::Gitolite.new - end -end diff --git a/lib/gitolited.rb b/lib/gitolited.rb new file mode 100644 index 00000000..68b9b625 --- /dev/null +++ b/lib/gitolited.rb @@ -0,0 +1,11 @@ +# == Gitolited mixin +# +# Provide a shortcut to Gitlab::Gitolite instance by gitolite +# +# Used by Project, UsersProject, etc +# +module Gitolited + def gitolite + Gitlab::Gitolite.new + end +end diff --git a/lib/votes.rb b/lib/votes.rb deleted file mode 100644 index dfd751b1..00000000 --- a/lib/votes.rb +++ /dev/null @@ -1,39 +0,0 @@ -# == Votes role -# -# Provides functionality to upvote/downvote entity -# based on +1 and -1 notes -# -# Used for Issue and Merge Request -# -module Votes - # Return the number of +1 comments (upvotes) - def upvotes - notes.select(&:upvote?).size - end - - def upvotes_in_percent - if votes_count.zero? - 0 - else - 100.0 / votes_count * upvotes - end - end - - # Return the number of -1 comments (downvotes) - def downvotes - notes.select(&:downvote?).size - end - - def downvotes_in_percent - if votes_count.zero? - 0 - else - 100.0 - upvotes_in_percent - end - end - - # Return the total number of votes - def votes_count - upvotes + downvotes - end -end