diff --git a/app/models/event.rb b/app/models/event.rb index 90376e7..eb88a4e 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 a697ccf..a5ef65c 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 582eee6..d166ae4 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 7e6d149..0000000 --- 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 7727848..0000000 --- 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/git_host.rb b/app/roles/git_host.rb deleted file mode 100644 index 2410e0f..0000000 --- a/app/roles/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/app/roles/issue_commonality.rb b/app/roles/issue_commonality.rb deleted file mode 100644 index 3948ef1..0000000 --- a/app/roles/issue_commonality.rb +++ /dev/null @@ -1,72 +0,0 @@ -# == IssueCommonality role -# -# Contains common functionality shared between Issues and MergeRequests -# -# Used by Issue, MergeRequest -# -module IssueCommonality - extend ActiveSupport::Concern - - included do - belongs_to :project - belongs_to :author, class_name: "User" - belongs_to :assignee, class_name: "User" - belongs_to :milestone - has_many :notes, as: :noteable, dependent: :destroy - - validates :project, presence: true - validates :author, presence: true - validates :title, presence: true, length: { within: 0..255 } - validates :closed, inclusion: { in: [true, false] } - - scope :opened, where(closed: false) - scope :closed, where(closed: true) - scope :of_group, ->(group) { where(project_id: group.project_ids) } - scope :assigned, ->(u) { where(assignee_id: u.id)} - scope :recent, order("created_at DESC") - - delegate :name, - :email, - to: :author, - prefix: true - - delegate :name, - :email, - to: :assignee, - allow_nil: true, - prefix: true - - attr_accessor :author_id_of_changes - end - - module ClassMethods - def search(query) - where("title like :query", query: "%#{query}%") - end - end - - def today? - Date.today == created_at.to_date - end - - def new? - today? && created_at == updated_at - end - - def is_assigned? - !!assignee_id - end - - def is_being_reassigned? - assignee_id_changed? - end - - def is_being_closed? - closed_changed? && closed - end - - def is_being_reopened? - closed_changed? && !closed - end - -end diff --git a/app/roles/namespaced_project.rb b/app/roles/namespaced_project.rb deleted file mode 100644 index 0f9fb05..0000000 --- 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 8e311ea..0000000 --- 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 ac9c38c..0000000 --- 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 42979f4..0000000 --- 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 8896569..0000000 --- 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/static_model.rb b/app/roles/static_model.rb deleted file mode 100644 index 5b64be1..0000000 --- a/app/roles/static_model.rb +++ /dev/null @@ -1,47 +0,0 @@ -# Provides an ActiveRecord-like interface to a model whose data is not persisted to a database. -module StaticModel - extend ActiveSupport::Concern - - module ClassMethods - # Used by ActiveRecord's polymorphic association to set object_id - def primary_key - 'id' - end - - # Used by ActiveRecord's polymorphic association to set object_type - def base_class - self - end - end - - # Used by AR for fetching attributes - # - # Pass it along if we respond to it. - def [](key) - send(key) if respond_to?(key) - end - - def to_param - id - end - - def new_record? - false - end - - def persisted? - false - end - - def destroyed? - false - end - - def ==(other) - if other.is_a? StaticModel - id == other.id - else - super - end - end -end diff --git a/app/roles/team.rb b/app/roles/team.rb deleted file mode 100644 index 8e431fc..0000000 --- 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/votes.rb b/app/roles/votes.rb deleted file mode 100644 index dfd751b..0000000 --- a/app/roles/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 diff --git a/lib/git_host.rb b/lib/git_host.rb new file mode 100644 index 0000000..2410e0f --- /dev/null +++ b/lib/git_host.rb @@ -0,0 +1,11 @@ +# == 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/issue_commonality.rb b/lib/issue_commonality.rb new file mode 100644 index 0000000..b755936 --- /dev/null +++ b/lib/issue_commonality.rb @@ -0,0 +1,71 @@ +# == IssueCommonality role +# +# Contains common functionality shared between Issues and MergeRequests +# +# Used by Issue, MergeRequest +# +module IssueCommonality + extend ActiveSupport::Concern + + included do + belongs_to :project + belongs_to :author, class_name: "User" + belongs_to :assignee, class_name: "User" + belongs_to :milestone + has_many :notes, as: :noteable, dependent: :destroy + + validates :project, presence: true + validates :author, presence: true + validates :title, presence: true, length: { within: 0..255 } + validates :closed, inclusion: { in: [true, false] } + + scope :opened, where(closed: false) + scope :closed, where(closed: true) + scope :of_group, ->(group) { where(project_id: group.project_ids) } + scope :assigned, ->(u) { where(assignee_id: u.id)} + scope :recent, order("created_at DESC") + + delegate :name, + :email, + to: :author, + prefix: true + + delegate :name, + :email, + to: :assignee, + allow_nil: true, + prefix: true + + attr_accessor :author_id_of_changes + end + + module ClassMethods + def search(query) + where("title like :query", query: "%#{query}%") + end + end + + def today? + Date.today == created_at.to_date + end + + def new? + today? && created_at == updated_at + end + + def is_assigned? + !!assignee_id + end + + def is_being_reassigned? + assignee_id_changed? + end + + def is_being_closed? + closed_changed? && closed + end + + def is_being_reopened? + closed_changed? && !closed + end +end diff --git a/lib/static_model.rb b/lib/static_model.rb new file mode 100644 index 0000000..5b64be1 --- /dev/null +++ b/lib/static_model.rb @@ -0,0 +1,47 @@ +# Provides an ActiveRecord-like interface to a model whose data is not persisted to a database. +module StaticModel + extend ActiveSupport::Concern + + module ClassMethods + # Used by ActiveRecord's polymorphic association to set object_id + def primary_key + 'id' + end + + # Used by ActiveRecord's polymorphic association to set object_type + def base_class + self + end + end + + # Used by AR for fetching attributes + # + # Pass it along if we respond to it. + def [](key) + send(key) if respond_to?(key) + end + + def to_param + id + end + + def new_record? + false + end + + def persisted? + false + end + + def destroyed? + false + end + + def ==(other) + if other.is_a? StaticModel + id == other.id + else + super + end + end +end diff --git a/lib/votes.rb b/lib/votes.rb new file mode 100644 index 0000000..dfd751b --- /dev/null +++ b/lib/votes.rb @@ -0,0 +1,39 @@ +# == 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 diff --git a/spec/lib/issue_commonality_spec.rb b/spec/lib/issue_commonality_spec.rb new file mode 100644 index 0000000..11f278d --- /dev/null +++ b/spec/lib/issue_commonality_spec.rb @@ -0,0 +1,70 @@ +require 'spec_helper' + +describe Issue, "IssueCommonality" do + let(:issue) { create(:issue) } + + describe "Associations" do + it { should belong_to(:project) } + it { should belong_to(:author) } + it { should belong_to(:assignee) } + it { should have_many(:notes).dependent(:destroy) } + end + + describe "Validation" do + it { should validate_presence_of(:project) } + it { should validate_presence_of(:author) } + it { should validate_presence_of(:title) } + it { should ensure_length_of(:title).is_at_least(0).is_at_most(255) } + it { should ensure_inclusion_of(:closed).in_array([true, false]) } + end + + describe "Scope" do + it { described_class.should respond_to(:opened) } + it { described_class.should respond_to(:closed) } + it { described_class.should respond_to(:assigned) } + end + + it "has an :author_id_of_changes accessor" do + issue.should respond_to(:author_id_of_changes) + issue.should respond_to(:author_id_of_changes=) + end + + describe ".search" do + let!(:searchable_issue) { create(:issue, title: "Searchable issue") } + + it "matches by title" do + described_class.search('able').all.should == [searchable_issue] + end + end + + describe "#today?" do + it "returns true when created today" do + # Avoid timezone differences and just return exactly what we want + Date.stub(:today).and_return(issue.created_at.to_date) + issue.today?.should be_true + end + + it "returns false when not created today" do + Date.stub(:today).and_return(Date.yesterday) + issue.today?.should be_false + end + end + + describe "#new?" do + it "returns true when created today and record hasn't been updated" do + issue.stub(:today?).and_return(true) + issue.new?.should be_true + end + + it "returns false when not created today" do + issue.stub(:today?).and_return(false) + issue.new?.should be_false + end + + it "returns false when record has been updated" do + issue.stub(:today?).and_return(true) + issue.touch + issue.new?.should be_false + end + end +end diff --git a/spec/lib/votes_spec.rb b/spec/lib/votes_spec.rb new file mode 100644 index 0000000..9866602 --- /dev/null +++ b/spec/lib/votes_spec.rb @@ -0,0 +1,132 @@ +require 'spec_helper' + +describe Issue do + let(:issue) { create(:issue) } + + describe "#upvotes" do + it "with no notes has a 0/0 score" do + issue.upvotes.should == 0 + end + + it "should recognize non-+1 notes" do + issue.notes << create(:note, note: "No +1 here") + issue.should have(1).note + issue.notes.first.upvote?.should be_false + issue.upvotes.should == 0 + end + + it "should recognize a single +1 note" do + issue.notes << create(:note, note: "+1 This is awesome") + issue.upvotes.should == 1 + end + + it "should recognize multiple +1 notes" do + issue.notes << create(:note, note: "+1 This is awesome") + issue.notes << create(:note, note: "+1 I want this") + issue.upvotes.should == 2 + end + end + + describe "#downvotes" do + it "with no notes has a 0/0 score" do + issue.downvotes.should == 0 + end + + it "should recognize non--1 notes" do + issue.notes << create(:note, note: "Almost got a -1") + issue.should have(1).note + issue.notes.first.downvote?.should be_false + issue.downvotes.should == 0 + end + + it "should recognize a single -1 note" do + issue.notes << create(:note, note: "-1 This is bad") + issue.downvotes.should == 1 + end + + it "should recognize multiple -1 notes" do + issue.notes << create(:note, note: "-1 This is bad") + issue.notes << create(:note, note: "-1 Away with this") + issue.downvotes.should == 2 + end + end + + describe "#votes_count" do + it "with no notes has a 0/0 score" do + issue.votes_count.should == 0 + end + + it "should recognize non notes" do + issue.notes << create(:note, note: "No +1 here") + issue.should have(1).note + issue.votes_count.should == 0 + end + + it "should recognize a single +1 note" do + issue.notes << create(:note, note: "+1 This is awesome") + issue.votes_count.should == 1 + end + + it "should recognize a single -1 note" do + issue.notes << create(:note, note: "-1 This is bad") + issue.votes_count.should == 1 + end + + it "should recognize multiple notes" do + issue.notes << create(:note, note: "+1 This is awesome") + issue.notes << create(:note, note: "-1 This is bad") + issue.notes << create(:note, note: "+1 I want this") + issue.votes_count.should == 3 + end + end + + describe "#upvotes_in_percent" do + it "with no notes has a 0% score" do + issue.upvotes_in_percent.should == 0 + end + + it "should count a single 1 note as 100%" do + issue.notes << create(:note, note: "+1 This is awesome") + issue.upvotes_in_percent.should == 100 + end + + it "should count multiple +1 notes as 100%" do + issue.notes << create(:note, note: "+1 This is awesome") + issue.notes << create(:note, note: "+1 I want this") + issue.upvotes_in_percent.should == 100 + end + + it "should count fractions for multiple +1 and -1 notes correctly" do + issue.notes << create(:note, note: "+1 This is awesome") + issue.notes << create(:note, note: "+1 I want this") + issue.notes << create(:note, note: "-1 This is bad") + issue.notes << create(:note, note: "+1 me too") + issue.upvotes_in_percent.should == 75 + end + end + + describe "#downvotes_in_percent" do + it "with no notes has a 0% score" do + issue.downvotes_in_percent.should == 0 + end + + it "should count a single -1 note as 100%" do + issue.notes << create(:note, note: "-1 This is bad") + issue.downvotes_in_percent.should == 100 + end + + it "should count multiple -1 notes as 100%" do + issue.notes << create(:note, note: "-1 This is bad") + issue.notes << create(:note, note: "-1 Away with this") + issue.downvotes_in_percent.should == 100 + end + + it "should count fractions for multiple +1 and -1 notes correctly" do + issue.notes << create(:note, note: "+1 This is awesome") + issue.notes << create(:note, note: "+1 I want this") + issue.notes << create(:note, note: "-1 This is bad") + issue.notes << create(:note, note: "+1 me too") + issue.downvotes_in_percent.should == 25 + end + end +end diff --git a/spec/models/project_repository_spec.rb b/spec/models/project_repository_spec.rb new file mode 100644 index 0000000..e1d01cb --- /dev/null +++ b/spec/models/project_repository_spec.rb @@ -0,0 +1,159 @@ +require 'spec_helper' + +describe Project, "Repository" do + let(:project) { create(:project) } + + describe "#empty_repo?" do + it "should return true if the repo doesn't exist" do + project.stub(repo_exists?: false, has_commits?: true) + project.should be_empty_repo + end + + it "should return true if the repo has commits" do + project.stub(repo_exists?: true, has_commits?: false) + project.should be_empty_repo + end + + it "should return false if the repo exists and has commits" do + project.stub(repo_exists?: true, has_commits?: true) + project.should_not be_empty_repo + end + end + + describe "#discover_default_branch" do + let(:master) { 'master' } + let(:stable) { 'stable' } + + it "returns 'master' when master exists" do + project.should_receive(:branch_names).at_least(:once).and_return([stable, master]) + project.discover_default_branch.should == 'master' + end + + it "returns non-master when master exists but default branch is set to something else" do + project.default_branch = 'stable' + project.should_receive(:branch_names).at_least(:once).and_return([stable, master]) + project.discover_default_branch.should == 'stable' + end + + it "returns a non-master branch when only one exists" do + project.should_receive(:branch_names).at_least(:once).and_return([stable]) + project.discover_default_branch.should == 'stable' + end + + it "returns nil when no branch exists" do + project.should_receive(:branch_names).at_least(:once).and_return([]) + project.discover_default_branch.should be_nil + end + end + + describe "#root_ref" do + it "returns default_branch when set" do + project.default_branch = 'stable' + project.root_ref.should == 'stable' + end + + it "returns 'master' when default_branch is nil" do + project.default_branch = nil + project.root_ref.should == 'master' + end + end + + describe "#root_ref?" do + it "returns true when branch is root_ref" do + project.default_branch = 'stable' + project.root_ref?('stable').should be_true + end + + it "returns false when branch is not root_ref" do + project.default_branch = nil + project.root_ref?('stable').should be_false + end + end + + describe :repo do + it "should return valid repo" do + project.repo.should be_kind_of(Grit::Repo) + end + + it "should return nil" do + lambda { Project.new(path: "invalid").repo }.should raise_error(Grit::NoSuchPathError) + end + + it "should return nil" do + lambda { Project.new.repo }.should raise_error(TypeError) + end + end + + describe :commit do + it "should return first head commit if without params" do + project.commit.id.should == project.repo.commits.first.id + end + + it "should return valid commit" do + project.commit(ValidCommit::ID).should be_valid_commit + end + + it "should return nil" do + project.commit("+123_4532530XYZ").should be_nil + end + end + + describe :tree do + before do + @commit = project.commit(ValidCommit::ID) + end + + it "should raise error w/o arguments" do + lambda { project.tree }.should raise_error + end + + it "should return root tree for commit" do + tree = project.tree(@commit) + tree.contents.size.should == ValidCommit::FILES_COUNT + tree.contents.map(&:name).should == ValidCommit::FILES + end + + it "should return root tree for commit with correct path" do + tree = project.tree(@commit, ValidCommit::C_FILE_PATH) + tree.contents.map(&:name).should == ValidCommit::C_FILES + end + + it "should return root tree for commit with incorrect path" do + project.tree(@commit, "invalid_path").should be_nil + end + end + + describe "fresh commits" do + let(:project) { create(:project) } + + it { project.fresh_commits(3).count.should == 3 } + it { project.fresh_commits.first.id.should == "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a" } + it { project.fresh_commits.last.id.should == "f403da73f5e62794a0447aca879360494b08f678" } + end + + describe "commits_between" do + let(:project) { create(:project) } + + subject do + commits = project.commits_between("3a4b4fb4cde7809f033822a171b9feae19d41fff", + "8470d70da67355c9c009e4401746b1d5410af2e3") + commits.map { |c| c.id } + end + + it { should have(3).elements } + it { should include("f0f14c8eaba69ebddd766498a9d0b0e79becd633") } + it { should_not include("bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a") } + end + + describe :valid_repo? do + it "should be valid repo" do + project = create(:project) + project.valid_repo?.should be_true + end + + it "should be invalid repo" do + project = Project.new(name: "ok_name", path: "/INVALID_PATH/", path: "NEOK") + project.valid_repo?.should be_false + end + end +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index eb2717e..5f6244e 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 f7a128d..0000000 --- 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 diff --git a/spec/roles/issue_commonality_spec.rb b/spec/roles/issue_commonality_spec.rb deleted file mode 100644 index 11f278d..0000000 --- a/spec/roles/issue_commonality_spec.rb +++ /dev/null @@ -1,70 +0,0 @@ -require 'spec_helper' - -describe Issue, "IssueCommonality" do - let(:issue) { create(:issue) } - - describe "Associations" do - it { should belong_to(:project) } - it { should belong_to(:author) } - it { should belong_to(:assignee) } - it { should have_many(:notes).dependent(:destroy) } - end - - describe "Validation" do - it { should validate_presence_of(:project) } - it { should validate_presence_of(:author) } - it { should validate_presence_of(:title) } - it { should ensure_length_of(:title).is_at_least(0).is_at_most(255) } - it { should ensure_inclusion_of(:closed).in_array([true, false]) } - end - - describe "Scope" do - it { described_class.should respond_to(:opened) } - it { described_class.should respond_to(:closed) } - it { described_class.should respond_to(:assigned) } - end - - it "has an :author_id_of_changes accessor" do - issue.should respond_to(:author_id_of_changes) - issue.should respond_to(:author_id_of_changes=) - end - - describe ".search" do - let!(:searchable_issue) { create(:issue, title: "Searchable issue") } - - it "matches by title" do - described_class.search('able').all.should == [searchable_issue] - end - end - - describe "#today?" do - it "returns true when created today" do - # Avoid timezone differences and just return exactly what we want - Date.stub(:today).and_return(issue.created_at.to_date) - issue.today?.should be_true - end - - it "returns false when not created today" do - Date.stub(:today).and_return(Date.yesterday) - issue.today?.should be_false - end - end - - describe "#new?" do - it "returns true when created today and record hasn't been updated" do - issue.stub(:today?).and_return(true) - issue.new?.should be_true - end - - it "returns false when not created today" do - issue.stub(:today?).and_return(false) - issue.new?.should be_false - end - - it "returns false when record has been updated" do - issue.stub(:today?).and_return(true) - issue.touch - issue.new?.should be_false - end - end -end diff --git a/spec/roles/repository_spec.rb b/spec/roles/repository_spec.rb deleted file mode 100644 index e1d01cb..0000000 --- a/spec/roles/repository_spec.rb +++ /dev/null @@ -1,159 +0,0 @@ -require 'spec_helper' - -describe Project, "Repository" do - let(:project) { create(:project) } - - describe "#empty_repo?" do - it "should return true if the repo doesn't exist" do - project.stub(repo_exists?: false, has_commits?: true) - project.should be_empty_repo - end - - it "should return true if the repo has commits" do - project.stub(repo_exists?: true, has_commits?: false) - project.should be_empty_repo - end - - it "should return false if the repo exists and has commits" do - project.stub(repo_exists?: true, has_commits?: true) - project.should_not be_empty_repo - end - end - - describe "#discover_default_branch" do - let(:master) { 'master' } - let(:stable) { 'stable' } - - it "returns 'master' when master exists" do - project.should_receive(:branch_names).at_least(:once).and_return([stable, master]) - project.discover_default_branch.should == 'master' - end - - it "returns non-master when master exists but default branch is set to something else" do - project.default_branch = 'stable' - project.should_receive(:branch_names).at_least(:once).and_return([stable, master]) - project.discover_default_branch.should == 'stable' - end - - it "returns a non-master branch when only one exists" do - project.should_receive(:branch_names).at_least(:once).and_return([stable]) - project.discover_default_branch.should == 'stable' - end - - it "returns nil when no branch exists" do - project.should_receive(:branch_names).at_least(:once).and_return([]) - project.discover_default_branch.should be_nil - end - end - - describe "#root_ref" do - it "returns default_branch when set" do - project.default_branch = 'stable' - project.root_ref.should == 'stable' - end - - it "returns 'master' when default_branch is nil" do - project.default_branch = nil - project.root_ref.should == 'master' - end - end - - describe "#root_ref?" do - it "returns true when branch is root_ref" do - project.default_branch = 'stable' - project.root_ref?('stable').should be_true - end - - it "returns false when branch is not root_ref" do - project.default_branch = nil - project.root_ref?('stable').should be_false - end - end - - describe :repo do - it "should return valid repo" do - project.repo.should be_kind_of(Grit::Repo) - end - - it "should return nil" do - lambda { Project.new(path: "invalid").repo }.should raise_error(Grit::NoSuchPathError) - end - - it "should return nil" do - lambda { Project.new.repo }.should raise_error(TypeError) - end - end - - describe :commit do - it "should return first head commit if without params" do - project.commit.id.should == project.repo.commits.first.id - end - - it "should return valid commit" do - project.commit(ValidCommit::ID).should be_valid_commit - end - - it "should return nil" do - project.commit("+123_4532530XYZ").should be_nil - end - end - - describe :tree do - before do - @commit = project.commit(ValidCommit::ID) - end - - it "should raise error w/o arguments" do - lambda { project.tree }.should raise_error - end - - it "should return root tree for commit" do - tree = project.tree(@commit) - tree.contents.size.should == ValidCommit::FILES_COUNT - tree.contents.map(&:name).should == ValidCommit::FILES - end - - it "should return root tree for commit with correct path" do - tree = project.tree(@commit, ValidCommit::C_FILE_PATH) - tree.contents.map(&:name).should == ValidCommit::C_FILES - end - - it "should return root tree for commit with incorrect path" do - project.tree(@commit, "invalid_path").should be_nil - end - end - - describe "fresh commits" do - let(:project) { create(:project) } - - it { project.fresh_commits(3).count.should == 3 } - it { project.fresh_commits.first.id.should == "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a" } - it { project.fresh_commits.last.id.should == "f403da73f5e62794a0447aca879360494b08f678" } - end - - describe "commits_between" do - let(:project) { create(:project) } - - subject do - commits = project.commits_between("3a4b4fb4cde7809f033822a171b9feae19d41fff", - "8470d70da67355c9c009e4401746b1d5410af2e3") - commits.map { |c| c.id } - end - - it { should have(3).elements } - it { should include("f0f14c8eaba69ebddd766498a9d0b0e79becd633") } - it { should_not include("bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a") } - end - - describe :valid_repo? do - it "should be valid repo" do - project = create(:project) - project.valid_repo?.should be_true - end - - it "should be invalid repo" do - project = Project.new(name: "ok_name", path: "/INVALID_PATH/", path: "NEOK") - project.valid_repo?.should be_false - end - end -end diff --git a/spec/roles/votes_spec.rb b/spec/roles/votes_spec.rb deleted file mode 100644 index 9866602..0000000 --- a/spec/roles/votes_spec.rb +++ /dev/null @@ -1,132 +0,0 @@ -require 'spec_helper' - -describe Issue do - let(:issue) { create(:issue) } - - describe "#upvotes" do - it "with no notes has a 0/0 score" do - issue.upvotes.should == 0 - end - - it "should recognize non-+1 notes" do - issue.notes << create(:note, note: "No +1 here") - issue.should have(1).note - issue.notes.first.upvote?.should be_false - issue.upvotes.should == 0 - end - - it "should recognize a single +1 note" do - issue.notes << create(:note, note: "+1 This is awesome") - issue.upvotes.should == 1 - end - - it "should recognize multiple +1 notes" do - issue.notes << create(:note, note: "+1 This is awesome") - issue.notes << create(:note, note: "+1 I want this") - issue.upvotes.should == 2 - end - end - - describe "#downvotes" do - it "with no notes has a 0/0 score" do - issue.downvotes.should == 0 - end - - it "should recognize non--1 notes" do - issue.notes << create(:note, note: "Almost got a -1") - issue.should have(1).note - issue.notes.first.downvote?.should be_false - issue.downvotes.should == 0 - end - - it "should recognize a single -1 note" do - issue.notes << create(:note, note: "-1 This is bad") - issue.downvotes.should == 1 - end - - it "should recognize multiple -1 notes" do - issue.notes << create(:note, note: "-1 This is bad") - issue.notes << create(:note, note: "-1 Away with this") - issue.downvotes.should == 2 - end - end - - describe "#votes_count" do - it "with no notes has a 0/0 score" do - issue.votes_count.should == 0 - end - - it "should recognize non notes" do - issue.notes << create(:note, note: "No +1 here") - issue.should have(1).note - issue.votes_count.should == 0 - end - - it "should recognize a single +1 note" do - issue.notes << create(:note, note: "+1 This is awesome") - issue.votes_count.should == 1 - end - - it "should recognize a single -1 note" do - issue.notes << create(:note, note: "-1 This is bad") - issue.votes_count.should == 1 - end - - it "should recognize multiple notes" do - issue.notes << create(:note, note: "+1 This is awesome") - issue.notes << create(:note, note: "-1 This is bad") - issue.notes << create(:note, note: "+1 I want this") - issue.votes_count.should == 3 - end - end - - describe "#upvotes_in_percent" do - it "with no notes has a 0% score" do - issue.upvotes_in_percent.should == 0 - end - - it "should count a single 1 note as 100%" do - issue.notes << create(:note, note: "+1 This is awesome") - issue.upvotes_in_percent.should == 100 - end - - it "should count multiple +1 notes as 100%" do - issue.notes << create(:note, note: "+1 This is awesome") - issue.notes << create(:note, note: "+1 I want this") - issue.upvotes_in_percent.should == 100 - end - - it "should count fractions for multiple +1 and -1 notes correctly" do - issue.notes << create(:note, note: "+1 This is awesome") - issue.notes << create(:note, note: "+1 I want this") - issue.notes << create(:note, note: "-1 This is bad") - issue.notes << create(:note, note: "+1 me too") - issue.upvotes_in_percent.should == 75 - end - end - - describe "#downvotes_in_percent" do - it "with no notes has a 0% score" do - issue.downvotes_in_percent.should == 0 - end - - it "should count a single -1 note as 100%" do - issue.notes << create(:note, note: "-1 This is bad") - issue.downvotes_in_percent.should == 100 - end - - it "should count multiple -1 notes as 100%" do - issue.notes << create(:note, note: "-1 This is bad") - issue.notes << create(:note, note: "-1 Away with this") - issue.downvotes_in_percent.should == 100 - end - - it "should count fractions for multiple +1 and -1 notes correctly" do - issue.notes << create(:note, note: "+1 This is awesome") - issue.notes << create(:note, note: "+1 I want this") - issue.notes << create(:note, note: "-1 This is bad") - issue.notes << create(:note, note: "+1 me too") - issue.downvotes_in_percent.should == 25 - end - end -end -- libgit2 0.21.2