[#345] move lib/modules/models to app/models/concerns; small refactoring
This commit is contained in:
parent
a5f56bb835
commit
0a7d5d22da
|
@ -1,6 +1,6 @@
|
|||
class BuildList < ActiveRecord::Base
|
||||
include Modules::Models::CommitAndVersion
|
||||
include Modules::Models::FileStoreClean
|
||||
include CommitAndVersion
|
||||
include FileStoreClean
|
||||
include AbfWorker::ModelHelper
|
||||
include Feed::BuildList
|
||||
include BuildListObserver
|
||||
|
@ -164,7 +164,7 @@ class BuildList < ActiveRecord::Base
|
|||
|
||||
after_transition on: [:published, :fail_publish, :build_error, :tests_failed], do: :notify_users
|
||||
after_transition on: :build_success, do: :notify_users,
|
||||
unless: -> { |build_list| build_list.auto_publish? || build_list.auto_publish_into_testing? }
|
||||
unless: ->(build_list) { build_list.auto_publish? || build_list.auto_publish_into_testing? }
|
||||
|
||||
event :place_build do
|
||||
transition waiting_for_response: :build_pending
|
||||
|
|
|
@ -17,7 +17,7 @@ class Comment < ActiveRecord::Base
|
|||
scope :for_commit, ->(c) { where(commentable_id: c.id.hex, commentable_type: c.class) }
|
||||
default_scope { order(:created_at) }
|
||||
|
||||
after_create :subscribe_on_reply, unless: -> {|c| c.commit_comment?}
|
||||
after_create :subscribe_on_reply, unless: ->(c) { c.commit_comment? }
|
||||
after_create :subscribe_users
|
||||
|
||||
attr_accessible :body, :data
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
module ActsLikeMember
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
scope :not_member_of, ->(item) {
|
||||
where("
|
||||
#{table_name}.id NOT IN (
|
||||
SELECT relations.actor_id
|
||||
FROM relations
|
||||
WHERE (
|
||||
relations.actor_type = '#{self.to_s}'
|
||||
AND relations.target_type = '#{item.class.to_s}'
|
||||
AND relations.target_id = #{item.id}
|
||||
)
|
||||
)
|
||||
")
|
||||
}
|
||||
scope :search_order, -> { order('CHAR_LENGTH(#{table_name}.uname) ASC') }
|
||||
scope :without, ->(a) { where("#{table_name}.id NOT IN (?)", a) }
|
||||
scope :by_uname, ->(n) { where("#{table_name}.uname ILIKE ?", n) }
|
||||
scope :search, ->(q) { by_uname("%#{q.to_s.strip}%") }
|
||||
end
|
||||
|
||||
def to_param
|
||||
uname
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def find_by_insensitive_uname(uname)
|
||||
find_by_uname(uname) || by_uname(uname).first
|
||||
end
|
||||
|
||||
def find_by_insensitive_uname!(uname)
|
||||
find_by_insensitive_uname(uname) or raise ActiveRecord::RecordNotFound
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,31 @@
|
|||
module Autostart
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
ONCE_A_12_HOURS = 0
|
||||
ONCE_A_DAY = 1
|
||||
ONCE_A_WEEK = 2
|
||||
|
||||
AUTOSTART_STATUSES = [ONCE_A_12_HOURS, ONCE_A_DAY, ONCE_A_WEEK]
|
||||
HUMAN_AUTOSTART_STATUSES = {
|
||||
ONCE_A_12_HOURS => :once_a_12_hours,
|
||||
ONCE_A_DAY => :once_a_day,
|
||||
ONCE_A_WEEK => :once_a_week
|
||||
}
|
||||
|
||||
included do
|
||||
validates :autostart_status, numericality: true,
|
||||
inclusion: {in: AUTOSTART_STATUSES}, allow_blank: true
|
||||
|
||||
attr_accessible :autostart_status
|
||||
end
|
||||
|
||||
def human_autostart_status
|
||||
self.class.human_autostart_status(autostart_status)
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def human_autostart_status(autostart_status)
|
||||
I18n.t("layout.products.autostart_statuses.#{HUMAN_AUTOSTART_STATUSES[autostart_status]}")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -32,5 +32,4 @@ module BuildListObserver
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
module CommitAndVersion
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
|
||||
validate -> {
|
||||
if project && (commit_hash.blank? || project.repo.commit(commit_hash).blank?)
|
||||
errors.add :commit_hash, I18n.t('flash.build_list.wrong_commit_hash', commit_hash: commit_hash)
|
||||
end
|
||||
}
|
||||
|
||||
before_validation :set_commit_and_version
|
||||
before_create :set_last_published_commit
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def set_commit_and_version
|
||||
if project && project_version.present? && commit_hash.blank?
|
||||
self.commit_hash = project.repo.commits(project_version).try(:first).try(:id)
|
||||
elsif project_version.blank? && commit_hash.present?
|
||||
self.project_version = commit_hash
|
||||
end
|
||||
end
|
||||
|
||||
def set_last_published_commit
|
||||
return unless self.respond_to? :last_published_commit_hash # product?
|
||||
last_commit = self.last_published.first.try :commit_hash
|
||||
if last_commit && self.project.repo.commit(last_commit).present? # commit(nil) is not nil!
|
||||
self.last_published_commit_hash = last_commit
|
||||
end
|
||||
end
|
||||
end
|
|
@ -4,11 +4,11 @@ module Feed::Issue
|
|||
included do
|
||||
after_commit :new_issue_notifications, on: :create
|
||||
|
||||
after_commit :send_assign_notifications, on: :create, if: Proc.new { |i| i.assignee }
|
||||
after_commit :send_assign_notifications, on: :create, if: -> { |i| i.assignee }
|
||||
after_commit -> { send_assign_notifications(:update) }, on: :update
|
||||
|
||||
after_commit :send_hooks, on: :create
|
||||
after_commit -> { send_hooks(:update) }, on: :update, if: Proc.new { |i| i.previous_changes['status'].present? }
|
||||
after_commit -> { send_hooks(:update) }, on: :update, if: -> { |i| i.previous_changes['status'].present? }
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
module FileStoreClean
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
def destroy
|
||||
destroy_files_from_file_store if Rails.env.production?
|
||||
super
|
||||
end
|
||||
later :destroy, queue: :clone_build
|
||||
|
||||
def sha1_of_file_store_files
|
||||
raise NotImplementedError, "You should implement this method"
|
||||
end
|
||||
|
||||
def destroy_files_from_file_store(args = sha1_of_file_store_files)
|
||||
files = *args
|
||||
token = User.find_by_uname('file_store').authentication_token
|
||||
uri = URI APP_CONFIG['file_store_url']
|
||||
Net::HTTP.start(uri.host, uri.port) do |http|
|
||||
files.each do |sha1|
|
||||
begin
|
||||
req = Net::HTTP::Delete.new("/api/v1/file_stores/#{sha1}.json")
|
||||
req.basic_auth token, ''
|
||||
http.request(req)
|
||||
rescue # Dont care about it
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def later_destroy_files_from_file_store(args)
|
||||
destroy_files_from_file_store(args)
|
||||
end
|
||||
later :later_destroy_files_from_file_store, queue: :clone_build
|
||||
end
|
||||
|
||||
def self.file_exist_on_file_store?(sha1)
|
||||
begin
|
||||
resp = JSON(RestClient.get "#{APP_CONFIG['file_store_url']}/api/v1/file_stores.json", params: {hash: sha1})
|
||||
rescue # Dont care about it
|
||||
resp = []
|
||||
end
|
||||
if resp[0].respond_to?('[]') && resp[0]['file_name'] && resp[0]['sha1_hash']
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,241 @@
|
|||
require 'nokogiri'
|
||||
require 'open-uri'
|
||||
|
||||
module Git
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
has_attached_file :srpm
|
||||
|
||||
validates_attachment_size :srpm, less_than_or_equal_to: 500.megabytes
|
||||
validates_attachment_content_type :srpm, content_type: ['application/octet-stream', "application/x-rpm", "application/x-redhat-package-manager"], message: I18n.t('layout.invalid_content_type')
|
||||
|
||||
after_create :create_git_repo
|
||||
after_commit(on: :create) {|p| p.fork_git_repo unless p.is_root?} # later with resque
|
||||
after_commit(on: :create) {|p| p.import_attached_srpm if p.srpm?} # later with resque # should be after create_git_repo
|
||||
after_destroy :destroy_git_repo
|
||||
# after_rollback -> { destroy_git_repo rescue true if new_record? }
|
||||
|
||||
later :import_attached_srpm, queue: :fork_import
|
||||
later :fork_git_repo, queue: :fork_import
|
||||
end
|
||||
|
||||
def repo
|
||||
@repo ||= Grit::Repo.new(path) rescue Grit::Repo.new(GAP_REPO_PATH)
|
||||
end
|
||||
|
||||
def path
|
||||
build_path(name_with_owner)
|
||||
end
|
||||
|
||||
def versions
|
||||
repo.tags.map(&:name) + repo.branches.map(&:name)
|
||||
end
|
||||
|
||||
def create_branch(new_ref, from_ref, user)
|
||||
return false if new_ref.blank? || from_ref.blank? || !(from_commit = repo.commit(from_ref))
|
||||
status, out, err = repo.git.native(:branch, {process_info: true}, new_ref, from_commit.id)
|
||||
if status == 0
|
||||
Resque.enqueue(GitHook, owner.uname, name, from_commit.id, GitHook::ZERO, "refs/heads/#{new_ref}", 'commit', "user-#{user.id}", nil)
|
||||
return true
|
||||
end
|
||||
return false
|
||||
|
||||
end
|
||||
|
||||
def delete_branch(branch, user)
|
||||
return false if default_branch == branch.name
|
||||
message = repo.git.native(:branch, {}, '-D', branch.name)
|
||||
if message.present?
|
||||
Resque.enqueue(GitHook, owner.uname, name, GitHook::ZERO, branch.commit.id, "refs/heads/#{branch.name}", 'commit', "user-#{user.id}", message)
|
||||
end
|
||||
return message.present?
|
||||
end
|
||||
|
||||
def update_file(path, data, options = {})
|
||||
head = options[:head].to_s || default_branch
|
||||
actor = get_actor(options[:actor])
|
||||
filename = File.split(path).last
|
||||
message = options[:message]
|
||||
message = "Updated file #{filename}" if message.nil? or message.empty?
|
||||
|
||||
# can not write to unexisted branch
|
||||
return false if repo.branches.select{|b| b.name == head}.size != 1
|
||||
|
||||
parent = repo.commits(head).first
|
||||
|
||||
index = repo.index
|
||||
index.read_tree(parent.tree.id)
|
||||
|
||||
# can not create new file
|
||||
return false if (index.current_tree / path).nil?
|
||||
|
||||
system "sudo chown -R rosa:rosa #{repo.path}" #FIXME Permission denied - /mnt/gitstore/git_projects/...
|
||||
index.add(path, data)
|
||||
if sha1 = index.commit(message, parents: [parent], actor: actor, last_tree: parent.tree.id, head: head)
|
||||
Resque.enqueue(GitHook, owner.uname, name, sha1, sha1, "refs/heads/#{head}", 'commit', "user-#{options[:actor].id}", message)
|
||||
end
|
||||
sha1
|
||||
end
|
||||
|
||||
def paginate_commits(treeish, options = {})
|
||||
options[:page] = options[:page].try(:to_i) || 1
|
||||
options[:per_page] = options[:per_page].try(:to_i) || 20
|
||||
|
||||
skip = options[:per_page] * (options[:page] - 1)
|
||||
last_page = (skip + options[:per_page]) >= repo.commit_count(treeish)
|
||||
|
||||
[repo.commits(treeish, options[:per_page], skip), options[:page], last_page]
|
||||
end
|
||||
|
||||
def tree_info(tree, treeish = nil, path = nil)
|
||||
return [] unless tree
|
||||
grouped = tree.contents.sort_by{|c| c.name.downcase}.group_by(&:class)
|
||||
[
|
||||
grouped[Grit::Tree],
|
||||
grouped[Grit::Blob],
|
||||
grouped[Grit::Submodule]
|
||||
].compact.flatten.map do |node|
|
||||
node_path = File.join([path.present? ? path : nil, node.name].compact)
|
||||
[
|
||||
node,
|
||||
node_path,
|
||||
repo.log(treeish, node_path, max_count: 1).first
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
def import_srpm(srpm_path = srpm.path, branch_name = 'import')
|
||||
token = User.find_by_uname('rosa_system').authentication_token
|
||||
opts = [srpm_path, path, branch_name, Rails.root.join('bin', 'file-store.rb'), token, APP_CONFIG['file_store_url']].join(' ')
|
||||
system("#{Rails.root.join('bin', 'import_srpm.sh')} #{opts} >> /dev/null 2>&1")
|
||||
end
|
||||
|
||||
def is_empty?
|
||||
repo.branches.count == 0
|
||||
end
|
||||
|
||||
def total_commits_count
|
||||
return 0 if is_empty?
|
||||
%x(cd #{path} && git rev-list --all | wc -l).to_i
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def build_path(dir)
|
||||
File.join(APP_CONFIG['git_path'], 'git_projects', "#{dir}.git")
|
||||
end
|
||||
|
||||
def import_attached_srpm
|
||||
if srpm?
|
||||
import_srpm # srpm.path
|
||||
self.srpm = nil; save # clear srpm
|
||||
end
|
||||
end
|
||||
|
||||
def create_git_repo
|
||||
if is_root?
|
||||
Grit::Repo.init_bare(path)
|
||||
write_hook
|
||||
end
|
||||
end
|
||||
|
||||
def fork_git_repo
|
||||
dummy = Grit::Repo.new(path) rescue parent.repo.fork_bare(path, shared: false)
|
||||
write_hook
|
||||
end
|
||||
|
||||
def destroy_git_repo
|
||||
FileUtils.rm_rf path
|
||||
end
|
||||
|
||||
def write_hook
|
||||
hook = "/home/#{APP_CONFIG['shell_user']}/gitlab-shell/hooks/post-receive"
|
||||
hook_file = File.join(path, 'hooks', 'post-receive')
|
||||
FileUtils.ln_sf hook, hook_file
|
||||
end
|
||||
|
||||
def get_actor(actor = nil)
|
||||
@last_actor = case actor.class.to_s
|
||||
when 'Grit::Actor' then options[:actor]
|
||||
when 'Hash' then Grit::Actor.new(actor[:name], actor[:email])
|
||||
when 'String' then Grit::Actor.from_stirng(actor)
|
||||
else begin
|
||||
if actor.respond_to?(:name) and actor.respond_to?(:email)
|
||||
Grit::Actor.new(actor.name, actor.email)
|
||||
else
|
||||
config = Grit::Config.new(repo)
|
||||
Grit::Actor.new(config['user.name'], config['user.email'])
|
||||
end
|
||||
end
|
||||
end
|
||||
@last_actor
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
MAX_SRC_SIZE = 1024*1024*256
|
||||
|
||||
def process_hook(owner_uname, repo, newrev, oldrev, ref, newrev_type, user = nil, message = nil)
|
||||
rec = GitHook.new(owner_uname, repo, newrev, oldrev, ref, newrev_type, user, message)
|
||||
Modules::Observers::ActivityFeed::Git.create_notifications rec
|
||||
end
|
||||
|
||||
def run_mass_import(url, srpms_list, visibility, owner, add_to_repository_id)
|
||||
doc = Nokogiri::HTML(open(url))
|
||||
links = doc.css("a[href$='.src.rpm']")
|
||||
return if links.count == 0
|
||||
filter = srpms_list.lines.map(&:chomp).map(&:strip).select(&:present?)
|
||||
|
||||
repository = Repository.find add_to_repository_id
|
||||
platform = repository.platform
|
||||
dir = Dir.mktmpdir 'mass-import-', APP_CONFIG['tmpfs_path']
|
||||
links.each do |link|
|
||||
begin
|
||||
package = link.attributes['href'].value
|
||||
package.chomp!; package.strip!
|
||||
|
||||
next if package.size == 0 || package !~ /^[\w\.\-]+$/
|
||||
next if filter.present? && !filter.include?(package)
|
||||
|
||||
uri = URI "#{url}/#{package}"
|
||||
srpm_file = "#{dir}/#{package}"
|
||||
Net::HTTP.start(uri.host) do |http|
|
||||
if http.request_head(uri.path)['content-length'].to_i < MAX_SRC_SIZE
|
||||
f = open(srpm_file, 'wb')
|
||||
http.request_get(uri.path) do |resp|
|
||||
resp.read_body{ |segment| f.write(segment) }
|
||||
end
|
||||
f.close
|
||||
end
|
||||
end
|
||||
if name = `rpm -q --qf '[%{Name}]' -p #{srpm_file}` and $?.success? and name.present?
|
||||
next if owner.projects.exists?(name: name)
|
||||
description = `rpm -q --qf '[%{Description}]' -p #{srpm_file}`.scrub('')
|
||||
|
||||
project = owner.projects.build(
|
||||
name: name,
|
||||
description: description,
|
||||
visibility: visibility,
|
||||
is_package: false # See: Hook for #attach_to_personal_repository
|
||||
)
|
||||
project.owner = owner
|
||||
if project.save
|
||||
repository.projects << project rescue nil
|
||||
project.update_attributes(is_package: true)
|
||||
project.import_srpm srpm_file, platform.name
|
||||
end
|
||||
end
|
||||
rescue => e
|
||||
f.close if defined?(f)
|
||||
Airbrake.notify_or_ignore(e, link: link.to_s, url: url, owner: owner)
|
||||
ensure
|
||||
File.delete srpm_file if srpm_file
|
||||
end
|
||||
end
|
||||
rescue => e
|
||||
Airbrake.notify_or_ignore(e, url: url, owner: owner)
|
||||
ensure
|
||||
FileUtils.remove_entry_secure dir if dir
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,190 @@
|
|||
# This module is based on
|
||||
# https://github.com/gitlabhq/gitlabhq/blob/397c3da9758c03a215a308c011f94261d9c61cfa/lib/gitlab/markdown.rb
|
||||
|
||||
# Custom parser for GitLab-flavored Markdown
|
||||
#
|
||||
# It replaces references in the text with links to the appropriate items in
|
||||
# GitLab.
|
||||
#
|
||||
# Supported reference formats are:
|
||||
# * @foo for team members
|
||||
# * for issues & pull requests:
|
||||
# * #123
|
||||
# * abf#123
|
||||
# * abf/rosa-build#123
|
||||
# * 123456 for commits
|
||||
#
|
||||
# It also parses Emoji codes to insert images. See
|
||||
# http://www.emoji-cheat-sheet.com/ for a list of the supported icons.
|
||||
#
|
||||
# Examples
|
||||
#
|
||||
# >> gfm("Hey @david, can you fix this?")
|
||||
# => "Hey <a href="/users/david">@david</a>, can you fix this?"
|
||||
#
|
||||
# >> gfm("Commit 35d5f7c closes #1234")
|
||||
# => "Commit <a href="/gitlab/commits/35d5f7c">35d5f7c</a> closes <a href="/gitlab/issues/1234">#1234</a>"
|
||||
#
|
||||
# >> gfm(":trollface:")
|
||||
# => "<img alt=\":trollface:\" class=\"emoji\" src=\"/images/trollface.png" title=\":trollface:\" />
|
||||
module Markdown
|
||||
include IssuesHelper
|
||||
|
||||
attr_reader :html_options
|
||||
|
||||
# Public: Parse the provided text with GitLab-Flavored Markdown
|
||||
#
|
||||
# text - the source text
|
||||
# html_options - extra options for the reference links as given to link_to
|
||||
#
|
||||
# Note: reference links will only be generated if @project is set
|
||||
def gfm(text, html_options = {})
|
||||
return text if text.nil?
|
||||
|
||||
# Duplicate the string so we don't alter the original, then call to_str
|
||||
# to cast it back to a String instead of a SafeBuffer. This is required
|
||||
# for gsub calls to work as we need them to.
|
||||
text = text.dup.to_str
|
||||
|
||||
@html_options = html_options
|
||||
|
||||
# Extract pre blocks so they are not altered
|
||||
# from http://github.github.com/github-flavored-markdown/
|
||||
text.gsub!(%r{<pre>.*?</pre>|<code>.*?</code>}m) { |match| extract_piece(match) }
|
||||
# Extract links with probably parsable hrefs
|
||||
text.gsub!(%r{<a.*?>.*?</a>}m) { |match| extract_piece(match) }
|
||||
# Extract images with probably parsable src
|
||||
text.gsub!(%r{<img.*?>}m) { |match| extract_piece(match) }
|
||||
|
||||
# TODO: add popups with additional information
|
||||
|
||||
text = parse(text)
|
||||
|
||||
# Insert pre block extractions
|
||||
text.gsub!(/\{gfm-extraction-(\h{32})\}/) do
|
||||
insert_piece($1)
|
||||
end
|
||||
|
||||
sanitize text.html_safe, attributes: ActionView::Base.sanitized_allowed_attributes + %w(id class)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def extract_piece(text)
|
||||
@extractions ||= {}
|
||||
|
||||
md5 = Digest::MD5.hexdigest(text)
|
||||
@extractions[md5] = text
|
||||
"{gfm-extraction-#{md5}}"
|
||||
end
|
||||
|
||||
def insert_piece(id)
|
||||
@extractions[id]
|
||||
end
|
||||
|
||||
# Private: Parses text for references and emoji
|
||||
#
|
||||
# text - Text to parse
|
||||
#
|
||||
# Note: reference links will only be generated if @project is set
|
||||
#
|
||||
# Returns parsed text
|
||||
def parse(text)
|
||||
parse_references(text) if @project
|
||||
parse_emoji(text)
|
||||
|
||||
text
|
||||
end
|
||||
|
||||
REFERENCE_PATTERN = %r{
|
||||
(?<prefix>[\W\/])? # Prefix
|
||||
( # Reference
|
||||
@(?<user>[a-zA-Z][a-zA-Z0-9_\-\.]*) # User/Group uname
|
||||
|(?<issue>(?:[a-zA-Z0-9\-_]*\/)?(?:[a-zA-Z0-9\-_]*)?\#[0-9]+) # Issue ID
|
||||
|(?<commit>[\h]{6,40}) # Commit ID
|
||||
)
|
||||
(?<suffix>\W)? # Suffix
|
||||
}x.freeze
|
||||
|
||||
TYPES = [:user, :issue, :commit].freeze
|
||||
|
||||
def parse_references(text)
|
||||
# parse reference links
|
||||
text.gsub!(REFERENCE_PATTERN) do |match|
|
||||
prefix = $~[:prefix]
|
||||
suffix = $~[:suffix]
|
||||
type = TYPES.select{|t| !$~[t].nil?}.first
|
||||
identifier = $~[type]
|
||||
|
||||
# Avoid HTML entities
|
||||
if prefix && suffix && prefix[0] == '&' && suffix[-1] == ';'
|
||||
match
|
||||
elsif ref_link = reference_link(type, identifier)
|
||||
"#{prefix}#{ref_link}#{suffix}"
|
||||
else
|
||||
match
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
EMOJI_PATTERN = %r{(:(\S+):)}.freeze
|
||||
|
||||
def parse_emoji(text)
|
||||
# parse emoji
|
||||
text.gsub!(EMOJI_PATTERN) do |match|
|
||||
if valid_emoji?($2)
|
||||
image_tag(image_path("emoji/#{$2}.png"), class: 'emoji', title: $1, alt: $1, size: "20x20")
|
||||
else
|
||||
match
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Private: Checks if an emoji icon exists in the image asset directory
|
||||
#
|
||||
# emoji - Identifier of the emoji as a string (e.g., "+1", "heart")
|
||||
#
|
||||
# Returns boolean
|
||||
def valid_emoji?(emoji)
|
||||
Emoji.names.include? emoji
|
||||
end
|
||||
|
||||
# Private: Dispatches to a dedicated processing method based on reference
|
||||
#
|
||||
# reference - Object reference ("@1234", "!567", etc.)
|
||||
# identifier - Object identifier (Issue ID, SHA hash, etc.)
|
||||
#
|
||||
# Returns string rendered by the processing method
|
||||
def reference_link(type, identifier)
|
||||
send("reference_#{type}", identifier)
|
||||
end
|
||||
|
||||
def reference_user(identifier)
|
||||
member = User.where(uname: identifier).first || Group.where(uname: identifier).first
|
||||
if member
|
||||
link_to("@#{identifier}", "/#{identifier}", html_options.merge(title: member.fullname, class: "gfm gfm-member #{html_options[:class]}"))
|
||||
end
|
||||
end
|
||||
|
||||
def reference_issue(identifier)
|
||||
if issue = Issue.find_by_hash_tag(identifier, current_ability, @project)
|
||||
if issue.pull_request
|
||||
title = "#{PullRequest.model_name.human}: #{issue.title}"
|
||||
url = project_pull_request_path(issue.project, issue.pull_request)
|
||||
else
|
||||
title = "#{Issue.model_name.human}: #{issue.title}"
|
||||
url = project_issue_path(issue.project.owner, issue.project.name, issue.serial_id)
|
||||
end
|
||||
link_to(identifier, url, html_options.merge(title: title, class: "gfm gfm-issue #{html_options[:class]}"))
|
||||
end
|
||||
end
|
||||
|
||||
def reference_commit(identifier)
|
||||
if commit = @project.repo.commit(identifier)
|
||||
link_to shortest_hash_id(commit.id), commit_path(@project, commit.id)
|
||||
title = GitPresenters::CommitAsMessagePresenter.present(commit, project: @project) do |presenter|
|
||||
link_to(identifier, commit_path(@project, commit), html_options.merge(title: presenter.caption, class: "gfm gfm-commit #{html_options[:class]}"))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,8 @@
|
|||
module Owner
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
validates :owner, presence: true
|
||||
after_create -> { relations.create actor_id: owner.id, actor_type: owner.class.to_s, role: 'admin' }
|
||||
end
|
||||
end
|
|
@ -0,0 +1,37 @@
|
|||
module PersonalRepository
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
after_create :create_personal_repository, unless: :system?
|
||||
end
|
||||
|
||||
def create_personal_repository
|
||||
begin
|
||||
pl = own_platforms.build
|
||||
pl.owner = self
|
||||
pl.name = "#{self.uname}_personal"
|
||||
pl.description = "#{self.uname}_personal"
|
||||
pl.platform_type = 'personal'
|
||||
pl.distrib_type = APP_CONFIG['distr_types'].first
|
||||
pl.visibility = 'open'
|
||||
pl.save!
|
||||
|
||||
rep = pl.repositories.build
|
||||
rep.name = 'main'
|
||||
rep.description = 'main'
|
||||
rep.save!
|
||||
rescue Exception => e
|
||||
pl.now_destroy rescue false
|
||||
raise e
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
def personal_platform
|
||||
own_platforms.personal.first
|
||||
end
|
||||
|
||||
def personal_repository
|
||||
personal_platform.repositories.first
|
||||
end
|
||||
end
|
|
@ -0,0 +1,44 @@
|
|||
module RegenerationStatus
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
READY = 0
|
||||
WAITING_FOR_REGENERATION = 100
|
||||
REGENERATING = 200
|
||||
|
||||
HUMAN_STATUSES = {
|
||||
READY => :ready,
|
||||
WAITING_FOR_REGENERATION => :waiting_for_regeneration,
|
||||
REGENERATING => :regenerating
|
||||
}
|
||||
|
||||
HUMAN_REGENERATION_STATUSES = {
|
||||
AbfWorker::BaseObserver::COMPLETED => :completed,
|
||||
AbfWorker::BaseObserver::FAILED => :failed,
|
||||
AbfWorker::BaseObserver::CANCELED => :canceled
|
||||
}.freeze
|
||||
|
||||
included do
|
||||
after_update :cleanup_file_store
|
||||
|
||||
def sha1_of_file_store_files
|
||||
files = []
|
||||
files << last_regenerated_log_sha1 if last_regenerated_log_sha1.present?
|
||||
files
|
||||
end
|
||||
|
||||
def human_regeneration_status
|
||||
self.class::HUMAN_REGENERATION_STATUSES[last_regenerated_status] || :no_data
|
||||
end
|
||||
|
||||
def human_status
|
||||
self.class::HUMAN_STATUSES[status] || :no_data
|
||||
end
|
||||
|
||||
def cleanup_file_store
|
||||
old_log_sha1 = last_regenerated_log_sha1_was
|
||||
if old_log_sha1.present? && old_log_sha1 != last_regenerated_log_sha1
|
||||
later_destroy_files_from_file_store([old_log_sha1])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,29 @@
|
|||
module TimeLiving
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
|
||||
validates :time_living, numericality: { only_integer: true }, presence: true
|
||||
|
||||
validate -> {
|
||||
# MIN_TIME_LIVING <= time_living <= MAX_TIME_LIVING or
|
||||
# 2 min <= time_living <= 12 hours
|
||||
# time_living in seconds
|
||||
min = self.class.const_defined?(:MIN_TIME_LIVING) ? self.class::MIN_TIME_LIVING : 120
|
||||
max = self.class.const_defined?(:MAX_TIME_LIVING) ? self.class::MAX_TIME_LIVING : 43200
|
||||
if min > time_living.to_i || time_living.to_i > max
|
||||
errors.add :time_living,
|
||||
I18n.t('flash.time_living.numericality_error', min: (min / 60), max: (max / 60))
|
||||
end
|
||||
}
|
||||
|
||||
before_validation :convert_time_living
|
||||
attr_accessible :time_living
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def convert_time_living
|
||||
self.time_living = time_living.to_i * 60 if time_living_was.to_i != time_living.to_i
|
||||
end
|
||||
end
|
|
@ -1,7 +1,7 @@
|
|||
module Modules::Models::UrlHelper
|
||||
module UrlHelper
|
||||
def default_url_options
|
||||
host ||= EventLog.current_controller.request.host_with_port rescue ::Rosa::Application.config.action_mailer.default_url_options[:host]
|
||||
protocol ||= APP_CONFIG['mailer_https_url'] ? 'https' : 'http' rescue 'http'
|
||||
{host: host, protocol: protocol}
|
||||
{ host: host, protocol: protocol }
|
||||
end
|
||||
end
|
|
@ -1,4 +1,4 @@
|
|||
module Modules::Models::WebHooks
|
||||
module WebHooks
|
||||
|
||||
class << self
|
||||
protected
|
||||
|
@ -45,10 +45,11 @@ module Modules::Models::WebHooks
|
|||
password :password
|
||||
boolean :ssl, :message_without_join, :no_colors, :long_url, :notice
|
||||
end
|
||||
|
||||
add_hook :jabber do
|
||||
string :user
|
||||
end
|
||||
|
||||
SCHEMA.freeze
|
||||
NAMES.freeze
|
||||
|
||||
end
|
|
@ -0,0 +1,31 @@
|
|||
module Wiki
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
after_save :create_wiki
|
||||
after_destroy :destroy_wiki
|
||||
end
|
||||
|
||||
def wiki_path
|
||||
build_path(wiki_repo_name)
|
||||
end
|
||||
|
||||
def wiki_repo_name
|
||||
File.join owner.uname, "#{name}.wiki"
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def create_wiki
|
||||
if has_wiki && !FileTest.exist?(wiki_path)
|
||||
Grit::Repo.init_bare(wiki_path)
|
||||
wiki = Gollum::Wiki.new(wiki_path, {base_path: Rails.application.routes.url_helpers.project_wiki_index_path(owner, self)})
|
||||
wiki.write_page('Home', :markdown, I18n.t("wiki.seed.welcome_content"),
|
||||
{name: owner.name, email: owner.email, message: 'Initial commit'})
|
||||
end
|
||||
end
|
||||
|
||||
def destroy_wiki
|
||||
FileUtils.rm_rf wiki_path
|
||||
end
|
||||
end
|
|
@ -1,6 +1,6 @@
|
|||
class Group < Avatar
|
||||
include Modules::Models::ActsLikeMember
|
||||
include Modules::Models::PersonalRepository
|
||||
include ActsLikeMember
|
||||
include PersonalRepository
|
||||
|
||||
belongs_to :owner, class_name: 'User'
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
class Hook < ActiveRecord::Base
|
||||
include Modules::Models::WebHooks
|
||||
include Modules::Models::UrlHelper
|
||||
include WebHooks
|
||||
include UrlHelper
|
||||
include Rails.application.routes.url_helpers
|
||||
belongs_to :project
|
||||
|
||||
|
@ -121,7 +121,7 @@ class Hook < ActiveRecord::Base
|
|||
def cleanup_data
|
||||
if self.name.present? && fields = SCHEMA[self.name.to_sym]
|
||||
new_data = {}
|
||||
fields.each{ |type, field| new_data[field] = self.data[field] }
|
||||
fields.each { |type, field| new_data[field] = self.data[field] }
|
||||
self.data = new_data
|
||||
end
|
||||
end
|
||||
|
@ -140,7 +140,7 @@ class Hook < ActiveRecord::Base
|
|||
modified << diff.a_path
|
||||
end
|
||||
end
|
||||
{removed: removed, added: added, modified: modified}
|
||||
{ removed: removed, added: added, modified: modified }
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -2,9 +2,9 @@ class Platform < ActiveRecord::Base
|
|||
extend FriendlyId
|
||||
friendly_id :name
|
||||
|
||||
include Modules::Models::FileStoreClean
|
||||
include Modules::Models::RegenerationStatus
|
||||
include Modules::Models::Owner
|
||||
include FileStoreClean
|
||||
include RegenerationStatus
|
||||
include Owner
|
||||
include EventLoggable
|
||||
|
||||
AUTOMATIC_METADATA_REGENERATIONS = %w(day week)
|
||||
|
@ -32,10 +32,10 @@ class Platform < ActiveRecord::Base
|
|||
has_many :mass_builds, foreign_key: :save_to_platform_id
|
||||
|
||||
validates :description, presence: true
|
||||
validates :visibility, presence: true, inclusion: {in: VISIBILITIES}
|
||||
validates :automatic_metadata_regeneration, inclusion: {in: AUTOMATIC_METADATA_REGENERATIONS}, allow_blank: true
|
||||
validates :visibility, presence: true, inclusion: { in: VISIBILITIES }
|
||||
validates :automatic_metadata_regeneration, inclusion: { in: AUTOMATIC_METADATA_REGENERATIONS }, allow_blank: true
|
||||
validates :name, uniqueness: {case_sensitive: false}, presence: true, format: { with: /\A#{NAME_PATTERN}\z/ }
|
||||
validates :distrib_type, presence: true, inclusion: {in: APP_CONFIG['distr_types']}
|
||||
validates :distrib_type, presence: true, inclusion: { in: APP_CONFIG['distr_types'] }
|
||||
validate -> {
|
||||
if released_was && !released
|
||||
errors.add(:released, I18n.t('flash.platform.released_status_can_not_be_changed'))
|
||||
|
@ -79,7 +79,7 @@ class Platform < ActiveRecord::Base
|
|||
end
|
||||
|
||||
event :regenerate do
|
||||
transition ready: :waiting_for_regeneration, if: -> { |p| p.main? }
|
||||
transition ready: :waiting_for_regeneration, if: ->(p) { p.main? }
|
||||
end
|
||||
|
||||
event :start_regeneration do
|
||||
|
|
|
@ -2,7 +2,7 @@ class PlatformArchSetting < ActiveRecord::Base
|
|||
DEFAULT_TIME_LIVING = 43200 # seconds, 12 hours
|
||||
MIN_TIME_LIVING = 600 # seconds, 10 minutes
|
||||
MAX_TIME_LIVING = 360000 # seconds, 100 hours, 4 day and 4 hours
|
||||
include Modules::Models::TimeLiving
|
||||
include TimeLiving
|
||||
|
||||
belongs_to :arch
|
||||
belongs_to :platform
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
class Product < ActiveRecord::Base
|
||||
include Modules::Models::TimeLiving
|
||||
include Modules::Models::Autostart
|
||||
include TimeLiving
|
||||
include Autostart
|
||||
include EventLoggable
|
||||
|
||||
belongs_to :platform
|
||||
|
@ -25,7 +25,7 @@ class Product < ActiveRecord::Base
|
|||
def full_clone(attrs = {})
|
||||
dup.tap do |c|
|
||||
attrs.each {|k,v| c.send("#{k}=", v)}
|
||||
c.time_living = c.time_living.to_i / 60 # see: Modules::Models::TimeLiving#convert_time_living
|
||||
c.time_living = c.time_living.to_i / 60 # see: TimeLiving#convert_time_living
|
||||
c.platform_id = nil
|
||||
c.product_build_lists = []
|
||||
c.updated_at = nil; c.created_at = nil
|
||||
|
@ -33,7 +33,7 @@ class Product < ActiveRecord::Base
|
|||
end
|
||||
|
||||
class << self
|
||||
Modules::Models::Autostart::HUMAN_AUTOSTART_STATUSES.each do |autostart_status, human_autostart_status|
|
||||
Autostart::HUMAN_AUTOSTART_STATUSES.each do |autostart_status, human_autostart_status|
|
||||
define_method "autostart_iso_builds_#{human_autostart_status}" do
|
||||
autostart_iso_builds autostart_status
|
||||
end
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
class ProductBuildList < ActiveRecord::Base
|
||||
include Modules::Models::CommitAndVersion
|
||||
include Modules::Models::TimeLiving
|
||||
include Modules::Models::FileStoreClean
|
||||
include Modules::Models::UrlHelper
|
||||
include CommitAndVersion
|
||||
include TimeLiving
|
||||
include FileStoreClean
|
||||
include UrlHelper
|
||||
include AbfWorker::ModelHelper
|
||||
include EventLoggable
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
class Project < ActiveRecord::Base
|
||||
include Modules::Models::Autostart
|
||||
include Modules::Models::Owner
|
||||
include Modules::Models::Git
|
||||
include Modules::Models::Wiki
|
||||
include Modules::Models::UrlHelper
|
||||
include Autostart
|
||||
include Owner
|
||||
include Git
|
||||
include Wiki
|
||||
include UrlHelper
|
||||
include EventLoggable
|
||||
|
||||
VISIBILITIES = ['open', 'hidden']
|
||||
|
@ -238,11 +238,11 @@ class Project < ActiveRecord::Base
|
|||
format_id = ProjectTag::FORMATS["#{tag_file_format(format)}"]
|
||||
project_tag = project_tags.where(tag_name: tag.name, format_id: format_id).first
|
||||
|
||||
return project_tag.sha1 if project_tag && project_tag.commit_id == tag.commit.id && Modules::Models::FileStoreClean.file_exist_on_file_store?(project_tag.sha1)
|
||||
return project_tag.sha1 if project_tag && project_tag.commit_id == tag.commit.id && FileStoreClean.file_exist_on_file_store?(project_tag.sha1)
|
||||
|
||||
archive = archive_by_treeish_and_format tag.name, format
|
||||
sha1 = Digest::SHA1.file(archive[:path]).hexdigest
|
||||
unless Modules::Models::FileStoreClean.file_exist_on_file_store? sha1
|
||||
unless FileStoreClean.file_exist_on_file_store? sha1
|
||||
token = User.find_by_uname('rosa_system').authentication_token
|
||||
begin
|
||||
resp = JSON `curl --user #{token}: -POST -F 'file_store[file]=@#{archive[:path]};filename=#{name}-#{tag.name}.#{tag_file_format(format)}' #{APP_CONFIG['file_store_url']}/api/v1/upload`
|
||||
|
@ -297,7 +297,7 @@ class Project < ActiveRecord::Base
|
|||
end
|
||||
|
||||
class << self
|
||||
Modules::Models::Autostart::HUMAN_AUTOSTART_STATUSES.each do |autostart_status, human_autostart_status|
|
||||
Autostart::HUMAN_AUTOSTART_STATUSES.each do |autostart_status, human_autostart_status|
|
||||
define_method "autostart_build_lists_#{human_autostart_status}" do
|
||||
autostart_build_lists autostart_status
|
||||
end
|
||||
|
|
|
@ -7,5 +7,5 @@ class ProjectImport < ActiveRecord::Base
|
|||
|
||||
scope :by_name, ->(name) { where("#{table_name}.name ILIKE ?", name) }
|
||||
|
||||
after_initialize -> {|r| r.file_mtime ||= Time.current - 10.years } # default
|
||||
after_initialize ->(r) { r.file_mtime ||= Time.current - 10.years } # default
|
||||
end
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
class ProjectTag < ActiveRecord::Base
|
||||
include Modules::Models::FileStoreClean
|
||||
include FileStoreClean
|
||||
|
||||
FORMATS = {
|
||||
'zip' => 0,
|
||||
|
|
|
@ -7,7 +7,7 @@ class PullRequest < ActiveRecord::Base
|
|||
:created_at, :updated_at, :comments, :status=, to: :issue, allow_nil: true
|
||||
|
||||
validates :from_project, :to_project, presence: true
|
||||
validate :uniq_merge, if: -> { |pull| pull.to_project.present? }
|
||||
validate :uniq_merge, if: ->(pull) { pull.to_project.present? }
|
||||
validates_each :from_ref, :to_ref do |record, attr, value|
|
||||
check_ref record, attr, value
|
||||
end
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
class RepositoryStatus < ActiveRecord::Base
|
||||
include Modules::Models::FileStoreClean
|
||||
include Modules::Models::RegenerationStatus
|
||||
include FileStoreClean
|
||||
include RegenerationStatus
|
||||
|
||||
WAITING_FOR_RESIGN = 300
|
||||
PUBLISH = 400
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
class User < Avatar
|
||||
include Modules::Models::PersonalRepository
|
||||
include Modules::Models::ActsLikeMember
|
||||
include PersonalRepository
|
||||
include ActsLikeMember
|
||||
include Feed::User
|
||||
include EventLoggable
|
||||
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
module Modules
|
||||
module Models
|
||||
module ActsLikeMember
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
scope :not_member_of, -> {|item|
|
||||
where("
|
||||
#{table_name}.id NOT IN (
|
||||
SELECT relations.actor_id
|
||||
FROM relations
|
||||
WHERE (
|
||||
relations.actor_type = '#{self.to_s}'
|
||||
AND relations.target_type = '#{item.class.to_s}'
|
||||
AND relations.target_id = #{item.id}
|
||||
)
|
||||
)
|
||||
")
|
||||
}
|
||||
scope :search_order, { order('CHAR_LENGTH(#{table_name}.uname) ASC') }
|
||||
scope :without, ->(a) { where("#{table_name}.id NOT IN (?)", a) }
|
||||
scope :by_uname, ->(n) { where("#{table_name}.uname ILIKE ?", n) }
|
||||
scope :search, ->(q) { by_uname("%#{q.to_s.strip}%") }
|
||||
end
|
||||
|
||||
def to_param
|
||||
uname
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def find_by_insensitive_uname(uname)
|
||||
find_by_uname(uname) || by_uname(uname).first
|
||||
end
|
||||
|
||||
def find_by_insensitive_uname!(uname)
|
||||
find_by_insensitive_uname(uname) or raise ActiveRecord::RecordNotFound
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
module Modules
|
||||
module Models
|
||||
module Autostart
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
ONCE_A_12_HOURS = 0
|
||||
ONCE_A_DAY = 1
|
||||
ONCE_A_WEEK = 2
|
||||
|
||||
AUTOSTART_STATUSES = [ONCE_A_12_HOURS, ONCE_A_DAY, ONCE_A_WEEK]
|
||||
HUMAN_AUTOSTART_STATUSES = {
|
||||
ONCE_A_12_HOURS => :once_a_12_hours,
|
||||
ONCE_A_DAY => :once_a_day,
|
||||
ONCE_A_WEEK => :once_a_week
|
||||
}
|
||||
|
||||
included do
|
||||
validates :autostart_status, numericality: true,
|
||||
inclusion: {in: AUTOSTART_STATUSES}, allow_blank: true
|
||||
|
||||
attr_accessible :autostart_status
|
||||
end
|
||||
|
||||
def human_autostart_status
|
||||
self.class.human_autostart_status(autostart_status)
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def human_autostart_status(autostart_status)
|
||||
I18n.t("layout.products.autostart_statuses.#{HUMAN_AUTOSTART_STATUSES[autostart_status]}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,37 +0,0 @@
|
|||
module Modules
|
||||
module Models
|
||||
module CommitAndVersion
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
|
||||
validate -> {
|
||||
if project && (commit_hash.blank? || project.repo.commit(commit_hash).blank?)
|
||||
errors.add :commit_hash, I18n.t('flash.build_list.wrong_commit_hash', commit_hash: commit_hash)
|
||||
end
|
||||
}
|
||||
|
||||
before_validation :set_commit_and_version
|
||||
before_create :set_last_published_commit
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def set_commit_and_version
|
||||
if project && project_version.present? && commit_hash.blank?
|
||||
self.commit_hash = project.repo.commits(project_version).try(:first).try(:id)
|
||||
elsif project_version.blank? && commit_hash.present?
|
||||
self.project_version = commit_hash
|
||||
end
|
||||
end
|
||||
|
||||
def set_last_published_commit
|
||||
return unless self.respond_to? :last_published_commit_hash # product?
|
||||
last_commit = self.last_published.first.try :commit_hash
|
||||
if last_commit && self.project.repo.commit(last_commit).present? # commit(nil) is not nil!
|
||||
self.last_published_commit_hash = last_commit
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,54 +0,0 @@
|
|||
module Modules
|
||||
module Models
|
||||
module FileStoreClean
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
def destroy
|
||||
destroy_files_from_file_store if Rails.env.production?
|
||||
super
|
||||
end
|
||||
later :destroy, queue: :clone_build
|
||||
|
||||
def sha1_of_file_store_files
|
||||
raise NotImplementedError, "You should implement this method"
|
||||
end
|
||||
|
||||
def destroy_files_from_file_store(args = sha1_of_file_store_files)
|
||||
files = *args
|
||||
token = User.find_by_uname('file_store').authentication_token
|
||||
uri = URI APP_CONFIG['file_store_url']
|
||||
Net::HTTP.start(uri.host, uri.port) do |http|
|
||||
files.each do |sha1|
|
||||
begin
|
||||
req = Net::HTTP::Delete.new("/api/v1/file_stores/#{sha1}.json")
|
||||
req.basic_auth token, ''
|
||||
http.request(req)
|
||||
rescue # Dont care about it
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def later_destroy_files_from_file_store(args)
|
||||
destroy_files_from_file_store(args)
|
||||
end
|
||||
later :later_destroy_files_from_file_store, queue: :clone_build
|
||||
end
|
||||
|
||||
def self.file_exist_on_file_store?(sha1)
|
||||
begin
|
||||
resp = JSON(RestClient.get "#{APP_CONFIG['file_store_url']}/api/v1/file_stores.json", params: {hash: sha1})
|
||||
rescue # Dont care about it
|
||||
resp = []
|
||||
end
|
||||
if resp[0].respond_to?('[]') && resp[0]['file_name'] && resp[0]['sha1_hash']
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,246 +0,0 @@
|
|||
require 'nokogiri'
|
||||
require 'open-uri'
|
||||
|
||||
module Modules
|
||||
module Models
|
||||
module Git
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
has_attached_file :srpm
|
||||
|
||||
validates_attachment_size :srpm, less_than_or_equal_to: 500.megabytes
|
||||
validates_attachment_content_type :srpm, content_type: ['application/octet-stream', "application/x-rpm", "application/x-redhat-package-manager"], message: I18n.t('layout.invalid_content_type')
|
||||
|
||||
after_create :create_git_repo
|
||||
after_commit(on: :create) {|p| p.fork_git_repo unless p.is_root?} # later with resque
|
||||
after_commit(on: :create) {|p| p.import_attached_srpm if p.srpm?} # later with resque # should be after create_git_repo
|
||||
after_destroy :destroy_git_repo
|
||||
# after_rollback -> { destroy_git_repo rescue true if new_record? }
|
||||
|
||||
later :import_attached_srpm, queue: :fork_import
|
||||
later :fork_git_repo, queue: :fork_import
|
||||
end
|
||||
|
||||
def repo
|
||||
@repo ||= Grit::Repo.new(path) rescue Grit::Repo.new(GAP_REPO_PATH)
|
||||
end
|
||||
|
||||
def path
|
||||
build_path(name_with_owner)
|
||||
end
|
||||
|
||||
def versions
|
||||
repo.tags.map(&:name) + repo.branches.map(&:name)
|
||||
end
|
||||
|
||||
def create_branch(new_ref, from_ref, user)
|
||||
return false if new_ref.blank? || from_ref.blank? || !(from_commit = repo.commit(from_ref))
|
||||
status, out, err = repo.git.native(:branch, {process_info: true}, new_ref, from_commit.id)
|
||||
if status == 0
|
||||
Resque.enqueue(GitHook, owner.uname, name, from_commit.id, GitHook::ZERO, "refs/heads/#{new_ref}", 'commit', "user-#{user.id}", nil)
|
||||
return true
|
||||
end
|
||||
return false
|
||||
|
||||
end
|
||||
|
||||
def delete_branch(branch, user)
|
||||
return false if default_branch == branch.name
|
||||
message = repo.git.native(:branch, {}, '-D', branch.name)
|
||||
if message.present?
|
||||
Resque.enqueue(GitHook, owner.uname, name, GitHook::ZERO, branch.commit.id, "refs/heads/#{branch.name}", 'commit', "user-#{user.id}", message)
|
||||
end
|
||||
return message.present?
|
||||
end
|
||||
|
||||
def update_file(path, data, options = {})
|
||||
head = options[:head].to_s || default_branch
|
||||
actor = get_actor(options[:actor])
|
||||
filename = File.split(path).last
|
||||
message = options[:message]
|
||||
message = "Updated file #{filename}" if message.nil? or message.empty?
|
||||
|
||||
# can not write to unexisted branch
|
||||
return false if repo.branches.select{|b| b.name == head}.size != 1
|
||||
|
||||
parent = repo.commits(head).first
|
||||
|
||||
index = repo.index
|
||||
index.read_tree(parent.tree.id)
|
||||
|
||||
# can not create new file
|
||||
return false if (index.current_tree / path).nil?
|
||||
|
||||
system "sudo chown -R rosa:rosa #{repo.path}" #FIXME Permission denied - /mnt/gitstore/git_projects/...
|
||||
index.add(path, data)
|
||||
if sha1 = index.commit(message, parents: [parent], actor: actor, last_tree: parent.tree.id, head: head)
|
||||
Resque.enqueue(GitHook, owner.uname, name, sha1, sha1, "refs/heads/#{head}", 'commit', "user-#{options[:actor].id}", message)
|
||||
end
|
||||
sha1
|
||||
end
|
||||
|
||||
def paginate_commits(treeish, options = {})
|
||||
options[:page] = options[:page].try(:to_i) || 1
|
||||
options[:per_page] = options[:per_page].try(:to_i) || 20
|
||||
|
||||
skip = options[:per_page] * (options[:page] - 1)
|
||||
last_page = (skip + options[:per_page]) >= repo.commit_count(treeish)
|
||||
|
||||
[repo.commits(treeish, options[:per_page], skip), options[:page], last_page]
|
||||
end
|
||||
|
||||
def tree_info(tree, treeish = nil, path = nil)
|
||||
return [] unless tree
|
||||
grouped = tree.contents.sort_by{|c| c.name.downcase}.group_by(&:class)
|
||||
[
|
||||
grouped[Grit::Tree],
|
||||
grouped[Grit::Blob],
|
||||
grouped[Grit::Submodule]
|
||||
].compact.flatten.map do |node|
|
||||
node_path = File.join([path.present? ? path : nil, node.name].compact)
|
||||
[
|
||||
node,
|
||||
node_path,
|
||||
repo.log(treeish, node_path, max_count: 1).first
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
def import_srpm(srpm_path = srpm.path, branch_name = 'import')
|
||||
token = User.find_by_uname('rosa_system').authentication_token
|
||||
opts = [srpm_path, path, branch_name, Rails.root.join('bin', 'file-store.rb'), token, APP_CONFIG['file_store_url']].join(' ')
|
||||
system("#{Rails.root.join('bin', 'import_srpm.sh')} #{opts} >> /dev/null 2>&1")
|
||||
end
|
||||
|
||||
def is_empty?
|
||||
repo.branches.count == 0
|
||||
end
|
||||
|
||||
def total_commits_count
|
||||
return 0 if is_empty?
|
||||
%x(cd #{path} && git rev-list --all | wc -l).to_i
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def build_path(dir)
|
||||
File.join(APP_CONFIG['git_path'], 'git_projects', "#{dir}.git")
|
||||
end
|
||||
|
||||
def import_attached_srpm
|
||||
if srpm?
|
||||
import_srpm # srpm.path
|
||||
self.srpm = nil; save # clear srpm
|
||||
end
|
||||
end
|
||||
|
||||
def create_git_repo
|
||||
if is_root?
|
||||
Grit::Repo.init_bare(path)
|
||||
write_hook
|
||||
end
|
||||
end
|
||||
|
||||
def fork_git_repo
|
||||
dummy = Grit::Repo.new(path) rescue parent.repo.fork_bare(path, shared: false)
|
||||
write_hook
|
||||
end
|
||||
|
||||
def destroy_git_repo
|
||||
FileUtils.rm_rf path
|
||||
end
|
||||
|
||||
def write_hook
|
||||
hook = "/home/#{APP_CONFIG['shell_user']}/gitlab-shell/hooks/post-receive"
|
||||
hook_file = File.join(path, 'hooks', 'post-receive')
|
||||
FileUtils.ln_sf hook, hook_file
|
||||
end
|
||||
|
||||
def get_actor(actor = nil)
|
||||
@last_actor = case actor.class.to_s
|
||||
when 'Grit::Actor' then options[:actor]
|
||||
when 'Hash' then Grit::Actor.new(actor[:name], actor[:email])
|
||||
when 'String' then Grit::Actor.from_stirng(actor)
|
||||
else begin
|
||||
if actor.respond_to?(:name) and actor.respond_to?(:email)
|
||||
Grit::Actor.new(actor.name, actor.email)
|
||||
else
|
||||
config = Grit::Config.new(repo)
|
||||
Grit::Actor.new(config['user.name'], config['user.email'])
|
||||
end
|
||||
end
|
||||
end
|
||||
@last_actor
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
MAX_SRC_SIZE = 1024*1024*256
|
||||
|
||||
def process_hook(owner_uname, repo, newrev, oldrev, ref, newrev_type, user = nil, message = nil)
|
||||
rec = GitHook.new(owner_uname, repo, newrev, oldrev, ref, newrev_type, user, message)
|
||||
Modules::Observers::ActivityFeed::Git.create_notifications rec
|
||||
end
|
||||
|
||||
def run_mass_import(url, srpms_list, visibility, owner, add_to_repository_id)
|
||||
doc = Nokogiri::HTML(open(url))
|
||||
links = doc.css("a[href$='.src.rpm']")
|
||||
return if links.count == 0
|
||||
filter = srpms_list.lines.map(&:chomp).map(&:strip).select(&:present?)
|
||||
|
||||
repository = Repository.find add_to_repository_id
|
||||
platform = repository.platform
|
||||
dir = Dir.mktmpdir 'mass-import-', APP_CONFIG['tmpfs_path']
|
||||
links.each do |link|
|
||||
begin
|
||||
package = link.attributes['href'].value
|
||||
package.chomp!; package.strip!
|
||||
|
||||
next if package.size == 0 || package !~ /^[\w\.\-]+$/
|
||||
next if filter.present? && !filter.include?(package)
|
||||
|
||||
uri = URI "#{url}/#{package}"
|
||||
srpm_file = "#{dir}/#{package}"
|
||||
Net::HTTP.start(uri.host) do |http|
|
||||
if http.request_head(uri.path)['content-length'].to_i < MAX_SRC_SIZE
|
||||
f = open(srpm_file, 'wb')
|
||||
http.request_get(uri.path) do |resp|
|
||||
resp.read_body{ |segment| f.write(segment) }
|
||||
end
|
||||
f.close
|
||||
end
|
||||
end
|
||||
if name = `rpm -q --qf '[%{Name}]' -p #{srpm_file}` and $?.success? and name.present?
|
||||
next if owner.projects.exists?(name: name)
|
||||
description = `rpm -q --qf '[%{Description}]' -p #{srpm_file}`.scrub('')
|
||||
|
||||
project = owner.projects.build(
|
||||
name: name,
|
||||
description: description,
|
||||
visibility: visibility,
|
||||
is_package: false # See: Hook for #attach_to_personal_repository
|
||||
)
|
||||
project.owner = owner
|
||||
if project.save
|
||||
repository.projects << project rescue nil
|
||||
project.update_attributes(is_package: true)
|
||||
project.import_srpm srpm_file, platform.name
|
||||
end
|
||||
end
|
||||
rescue => e
|
||||
f.close if defined?(f)
|
||||
Airbrake.notify_or_ignore(e, link: link.to_s, url: url, owner: owner)
|
||||
ensure
|
||||
File.delete srpm_file if srpm_file
|
||||
end
|
||||
end
|
||||
rescue => e
|
||||
Airbrake.notify_or_ignore(e, url: url, owner: owner)
|
||||
ensure
|
||||
FileUtils.remove_entry_secure dir if dir
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,193 +0,0 @@
|
|||
# This module is based on
|
||||
# https://github.com/gitlabhq/gitlabhq/blob/397c3da9758c03a215a308c011f94261d9c61cfa/lib/gitlab/markdown.rb
|
||||
module Modules
|
||||
module Models
|
||||
# Custom parser for GitLab-flavored Markdown
|
||||
#
|
||||
# It replaces references in the text with links to the appropriate items in
|
||||
# GitLab.
|
||||
#
|
||||
# Supported reference formats are:
|
||||
# * @foo for team members
|
||||
# * for issues & pull requests:
|
||||
# * #123
|
||||
# * abf#123
|
||||
# * abf/rosa-build#123
|
||||
# * 123456 for commits
|
||||
#
|
||||
# It also parses Emoji codes to insert images. See
|
||||
# http://www.emoji-cheat-sheet.com/ for a list of the supported icons.
|
||||
#
|
||||
# Examples
|
||||
#
|
||||
# >> gfm("Hey @david, can you fix this?")
|
||||
# => "Hey <a href="/users/david">@david</a>, can you fix this?"
|
||||
#
|
||||
# >> gfm("Commit 35d5f7c closes #1234")
|
||||
# => "Commit <a href="/gitlab/commits/35d5f7c">35d5f7c</a> closes <a href="/gitlab/issues/1234">#1234</a>"
|
||||
#
|
||||
# >> gfm(":trollface:")
|
||||
# => "<img alt=\":trollface:\" class=\"emoji\" src=\"/images/trollface.png" title=\":trollface:\" />
|
||||
module Markdown
|
||||
include IssuesHelper
|
||||
|
||||
attr_reader :html_options
|
||||
|
||||
# Public: Parse the provided text with GitLab-Flavored Markdown
|
||||
#
|
||||
# text - the source text
|
||||
# html_options - extra options for the reference links as given to link_to
|
||||
#
|
||||
# Note: reference links will only be generated if @project is set
|
||||
def gfm(text, html_options = {})
|
||||
return text if text.nil?
|
||||
|
||||
# Duplicate the string so we don't alter the original, then call to_str
|
||||
# to cast it back to a String instead of a SafeBuffer. This is required
|
||||
# for gsub calls to work as we need them to.
|
||||
text = text.dup.to_str
|
||||
|
||||
@html_options = html_options
|
||||
|
||||
# Extract pre blocks so they are not altered
|
||||
# from http://github.github.com/github-flavored-markdown/
|
||||
text.gsub!(%r{<pre>.*?</pre>|<code>.*?</code>}m) { |match| extract_piece(match) }
|
||||
# Extract links with probably parsable hrefs
|
||||
text.gsub!(%r{<a.*?>.*?</a>}m) { |match| extract_piece(match) }
|
||||
# Extract images with probably parsable src
|
||||
text.gsub!(%r{<img.*?>}m) { |match| extract_piece(match) }
|
||||
|
||||
# TODO: add popups with additional information
|
||||
|
||||
text = parse(text)
|
||||
|
||||
# Insert pre block extractions
|
||||
text.gsub!(/\{gfm-extraction-(\h{32})\}/) do
|
||||
insert_piece($1)
|
||||
end
|
||||
|
||||
sanitize text.html_safe, attributes: ActionView::Base.sanitized_allowed_attributes + %w(id class)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def extract_piece(text)
|
||||
@extractions ||= {}
|
||||
|
||||
md5 = Digest::MD5.hexdigest(text)
|
||||
@extractions[md5] = text
|
||||
"{gfm-extraction-#{md5}}"
|
||||
end
|
||||
|
||||
def insert_piece(id)
|
||||
@extractions[id]
|
||||
end
|
||||
|
||||
# Private: Parses text for references and emoji
|
||||
#
|
||||
# text - Text to parse
|
||||
#
|
||||
# Note: reference links will only be generated if @project is set
|
||||
#
|
||||
# Returns parsed text
|
||||
def parse(text)
|
||||
parse_references(text) if @project
|
||||
parse_emoji(text)
|
||||
|
||||
text
|
||||
end
|
||||
|
||||
REFERENCE_PATTERN = %r{
|
||||
(?<prefix>[\W\/])? # Prefix
|
||||
( # Reference
|
||||
@(?<user>[a-zA-Z][a-zA-Z0-9_\-\.]*) # User/Group uname
|
||||
|(?<issue>(?:[a-zA-Z0-9\-_]*\/)?(?:[a-zA-Z0-9\-_]*)?\#[0-9]+) # Issue ID
|
||||
|(?<commit>[\h]{6,40}) # Commit ID
|
||||
)
|
||||
(?<suffix>\W)? # Suffix
|
||||
}x.freeze
|
||||
|
||||
TYPES = [:user, :issue, :commit].freeze
|
||||
|
||||
def parse_references(text)
|
||||
# parse reference links
|
||||
text.gsub!(REFERENCE_PATTERN) do |match|
|
||||
prefix = $~[:prefix]
|
||||
suffix = $~[:suffix]
|
||||
type = TYPES.select{|t| !$~[t].nil?}.first
|
||||
identifier = $~[type]
|
||||
|
||||
# Avoid HTML entities
|
||||
if prefix && suffix && prefix[0] == '&' && suffix[-1] == ';'
|
||||
match
|
||||
elsif ref_link = reference_link(type, identifier)
|
||||
"#{prefix}#{ref_link}#{suffix}"
|
||||
else
|
||||
match
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
EMOJI_PATTERN = %r{(:(\S+):)}.freeze
|
||||
|
||||
def parse_emoji(text)
|
||||
# parse emoji
|
||||
text.gsub!(EMOJI_PATTERN) do |match|
|
||||
if valid_emoji?($2)
|
||||
image_tag(image_path("emoji/#{$2}.png"), class: 'emoji', title: $1, alt: $1, size: "20x20")
|
||||
else
|
||||
match
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Private: Checks if an emoji icon exists in the image asset directory
|
||||
#
|
||||
# emoji - Identifier of the emoji as a string (e.g., "+1", "heart")
|
||||
#
|
||||
# Returns boolean
|
||||
def valid_emoji?(emoji)
|
||||
Emoji.names.include? emoji
|
||||
end
|
||||
|
||||
# Private: Dispatches to a dedicated processing method based on reference
|
||||
#
|
||||
# reference - Object reference ("@1234", "!567", etc.)
|
||||
# identifier - Object identifier (Issue ID, SHA hash, etc.)
|
||||
#
|
||||
# Returns string rendered by the processing method
|
||||
def reference_link(type, identifier)
|
||||
send("reference_#{type}", identifier)
|
||||
end
|
||||
|
||||
def reference_user(identifier)
|
||||
member = User.where(uname: identifier).first || Group.where(uname: identifier).first
|
||||
if member
|
||||
link_to("@#{identifier}", "/#{identifier}", html_options.merge(title: member.fullname, class: "gfm gfm-member #{html_options[:class]}"))
|
||||
end
|
||||
end
|
||||
|
||||
def reference_issue(identifier)
|
||||
if issue = Issue.find_by_hash_tag(identifier, current_ability, @project)
|
||||
if issue.pull_request
|
||||
title = "#{PullRequest.model_name.human}: #{issue.title}"
|
||||
url = project_pull_request_path(issue.project, issue.pull_request)
|
||||
else
|
||||
title = "#{Issue.model_name.human}: #{issue.title}"
|
||||
url = project_issue_path(issue.project.owner, issue.project.name, issue.serial_id)
|
||||
end
|
||||
link_to(identifier, url, html_options.merge(title: title, class: "gfm gfm-issue #{html_options[:class]}"))
|
||||
end
|
||||
end
|
||||
|
||||
def reference_commit(identifier)
|
||||
if commit = @project.repo.commit(identifier)
|
||||
link_to shortest_hash_id(commit.id), commit_path(@project, commit.id)
|
||||
title = GitPresenters::CommitAsMessagePresenter.present(commit, project: @project) do |presenter|
|
||||
link_to(identifier, commit_path(@project, commit), html_options.merge(title: presenter.caption, class: "gfm gfm-commit #{html_options[:class]}"))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,13 +0,0 @@
|
|||
module Modules
|
||||
module Models
|
||||
module Owner
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
validates :owner, presence: true
|
||||
after_create -> { relations.create actor_id: owner.id, actor_type: owner.class.to_s, role: 'admin' }
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,44 +0,0 @@
|
|||
module Modules
|
||||
module Models
|
||||
module PersonalRepository
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
after_create :create_personal_repository, unless: :system?
|
||||
end
|
||||
|
||||
def create_personal_repository
|
||||
begin
|
||||
pl = own_platforms.build
|
||||
pl.owner = self
|
||||
pl.name = "#{self.uname}_personal"
|
||||
pl.description = "#{self.uname}_personal"
|
||||
pl.platform_type = 'personal'
|
||||
pl.distrib_type = APP_CONFIG['distr_types'].first
|
||||
pl.visibility = 'open'
|
||||
pl.save!
|
||||
|
||||
rep = pl.repositories.build
|
||||
rep.name = 'main'
|
||||
rep.description = 'main'
|
||||
rep.save!
|
||||
rescue Exception => e
|
||||
pl.now_destroy rescue false
|
||||
raise e
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
def personal_platform
|
||||
own_platforms.personal.first
|
||||
end
|
||||
|
||||
def personal_repository
|
||||
personal_platform.repositories.first
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,48 +0,0 @@
|
|||
module Modules
|
||||
module Models
|
||||
module RegenerationStatus
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
READY = 0
|
||||
WAITING_FOR_REGENERATION = 100
|
||||
REGENERATING = 200
|
||||
|
||||
HUMAN_STATUSES = {
|
||||
READY => :ready,
|
||||
WAITING_FOR_REGENERATION => :waiting_for_regeneration,
|
||||
REGENERATING => :regenerating
|
||||
}
|
||||
|
||||
HUMAN_REGENERATION_STATUSES = {
|
||||
AbfWorker::BaseObserver::COMPLETED => :completed,
|
||||
AbfWorker::BaseObserver::FAILED => :failed,
|
||||
AbfWorker::BaseObserver::CANCELED => :canceled
|
||||
}.freeze
|
||||
|
||||
included do
|
||||
after_update :cleanup_file_store
|
||||
|
||||
def sha1_of_file_store_files
|
||||
files = []
|
||||
files << last_regenerated_log_sha1 if last_regenerated_log_sha1.present?
|
||||
files
|
||||
end
|
||||
|
||||
def human_regeneration_status
|
||||
self.class::HUMAN_REGENERATION_STATUSES[last_regenerated_status] || :no_data
|
||||
end
|
||||
|
||||
def human_status
|
||||
self.class::HUMAN_STATUSES[status] || :no_data
|
||||
end
|
||||
|
||||
def cleanup_file_store
|
||||
old_log_sha1 = last_regenerated_log_sha1_was
|
||||
if old_log_sha1.present? && old_log_sha1 != last_regenerated_log_sha1
|
||||
later_destroy_files_from_file_store([old_log_sha1])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,35 +0,0 @@
|
|||
module Modules
|
||||
module Models
|
||||
module TimeLiving
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
|
||||
validates :time_living, numericality: {
|
||||
only_integer: true
|
||||
}, presence: true
|
||||
|
||||
validate -> {
|
||||
# MIN_TIME_LIVING <= time_living <= MAX_TIME_LIVING or
|
||||
# 2 min <= time_living <= 12 hours
|
||||
# time_living in seconds
|
||||
min = self.class.const_defined?(:MIN_TIME_LIVING) ? self.class::MIN_TIME_LIVING : 120
|
||||
max = self.class.const_defined?(:MAX_TIME_LIVING) ? self.class::MAX_TIME_LIVING : 43200
|
||||
if min > time_living.to_i || time_living.to_i > max
|
||||
errors.add :time_living,
|
||||
I18n.t('flash.time_living.numericality_error', min: (min / 60), max: (max / 60))
|
||||
end
|
||||
}
|
||||
|
||||
before_validation :convert_time_living
|
||||
attr_accessible :time_living
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def convert_time_living
|
||||
self.time_living = time_living.to_i * 60 if time_living_was.to_i != time_living.to_i
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,38 +0,0 @@
|
|||
module Modules
|
||||
module Models
|
||||
module Wiki
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
after_save :create_wiki
|
||||
after_destroy :destroy_wiki
|
||||
end
|
||||
|
||||
def wiki_path
|
||||
build_path(wiki_repo_name)
|
||||
end
|
||||
|
||||
def wiki_repo_name
|
||||
File.join owner.uname, "#{name}.wiki"
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def create_wiki
|
||||
if has_wiki && !FileTest.exist?(wiki_path)
|
||||
Grit::Repo.init_bare(wiki_path)
|
||||
wiki = Gollum::Wiki.new(wiki_path, {base_path: Rails.application.routes.url_helpers.project_wiki_index_path(owner, self)})
|
||||
wiki.write_page('Home', :markdown, I18n.t("wiki.seed.welcome_content"),
|
||||
{name: owner.name, email: owner.email, message: 'Initial commit'})
|
||||
end
|
||||
end
|
||||
|
||||
def destroy_wiki
|
||||
FileUtils.rm_rf wiki_path
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue