Permalink
Cannot retrieve contributors at this time
# frozen_string_literal: true | |
class Repository < ApplicationRecord | |
include RepoSearch | |
include Status | |
include RepoManifests | |
include RepositorySourceRank | |
# eager load this module to avoid clashing with Gitlab gem in development | |
RepositoryHost::Gitlab | |
STATUSES = ['Active', 'Deprecated', 'Unmaintained', 'Help Wanted', 'Removed'] | |
API_FIELDS = [:full_name, :description, :fork, :created_at, :updated_at, :pushed_at, :homepage, | |
:size, :stargazers_count, :language, :has_issues, :has_wiki, :has_pages, | |
:forks_count, :mirror_url, :open_issues_count, :default_branch, | |
:subscribers_count, :private, :logo_url, :pull_requests_enabled, :scm, :keywords, :status] | |
has_many :projects | |
has_many :contributions, dependent: :delete_all | |
has_many :contributors, through: :contributions, source: :repository_user | |
has_many :tags, dependent: :delete_all | |
has_many :published_tags, -> { published }, anonymous_class: Tag | |
has_many :manifests, dependent: :destroy | |
has_many :repository_dependencies | |
has_many :repository_maintenance_stats, dependent: :destroy | |
has_many :dependencies, through: :manifests, source: :repository_dependencies | |
has_many :dependency_projects, -> { group('projects.id').order(Arel.sql("COUNT(projects.id) DESC")) }, through: :dependencies, source: :project | |
has_many :dependency_repos, -> { group('repositories.id') }, through: :dependency_projects, source: :repository | |
has_many :repository_subscriptions, dependent: :delete_all | |
has_many :web_hooks, dependent: :delete_all | |
has_many :issues, dependent: :delete_all | |
has_one :readme, dependent: :delete | |
belongs_to :repository_organisation | |
belongs_to :repository_user | |
belongs_to :source, primary_key: :full_name, foreign_key: :source_name, anonymous_class: Repository | |
has_many :forked_repositories, primary_key: :full_name, foreign_key: :source_name, anonymous_class: Repository | |
validates :full_name, uniqueness: {scope: :host_type}, if: lambda { self.full_name_changed? } | |
validates :uuid, uniqueness: {scope: :host_type}, if: lambda { self.uuid_changed? } | |
before_save :normalize_license_and_language | |
after_commit :update_all_info_async, on: :create | |
after_commit :save_projects, on: :update | |
after_commit :update_source_rank_async, on: [:update] | |
scope :without_readme, -> { where("repositories.id NOT IN (SELECT repository_id FROM readmes)") } | |
scope :with_projects, -> { joins(:projects) } | |
scope :without_projects, -> { includes(:projects).where(projects: { repository_id: nil }) } | |
scope :with_tags, -> { joins(:tags) } | |
scope :without_tags, -> { includes(:tags).where(tags: { repository_id: nil }) } | |
scope :host, lambda{ |host_type| where('lower(repositories.host_type) = ?', host_type.try(:downcase)) } | |
scope :fork, -> { where(fork: true) } | |
scope :source, -> { where(fork: false) } | |
scope :open_source, -> { where(private: false) } | |
scope :from_org, lambda{ |org_id| where(repository_organisation_id: org_id) } | |
scope :with_manifests, -> { joins(:manifests) } | |
scope :without_manifests, -> { includes(:manifests).where(manifests: {repository_id: nil}) } | |
scope :with_description, -> { where("repositories.description <> ''") } | |
scope :with_license, -> { where("repositories.license <> ''") } | |
scope :without_license, -> {where("repositories.license IS ? OR repositories.license = ''", nil)} | |
scope :pushed, -> { where.not(pushed_at: nil) } | |
scope :good_quality, -> { maintained.open_source.pushed } | |
scope :with_stars, -> { where('repositories.stargazers_count > 0') } | |
scope :interesting, -> { with_stars.order(Arel.sql('repositories.stargazers_count DESC, repositories.rank DESC NULLS LAST, repositories.pushed_at DESC')) } | |
scope :uninteresting, -> { without_readme.without_manifests.without_license.where('repositories.stargazers_count = 0').where('repositories.forks_count = 0') } | |
scope :recently_created, -> { where('created_at > ?', 7.days.ago)} | |
scope :hacker_news, -> { order(Arel.sql("((stargazers_count-1)/POW((EXTRACT(EPOCH FROM current_timestamp-created_at)/3600)+2,1.8)) DESC")) } | |
scope :trending, -> { good_quality.recently_created.with_stars } | |
scope :maintained, -> { where('repositories."status" not in (?) OR repositories."status" IS NULL', ["Deprecated", "Removed", "Unmaintained", "Hidden"])} | |
scope :deprecated, -> { where('repositories."status" = ?', "Deprecated")} | |
scope :not_removed, -> { where('repositories."status" not in (?) OR repositories."status" IS NULL', ["Removed", "Hidden"])} | |
scope :removed, -> { where('repositories."status" = ?', "Removed")} | |
scope :unmaintained, -> { where('repositories."status" = ?', "Unmaintained")} | |
scope :hidden, -> { where('repositories."status" = ?', "Hidden")} | |
scope :indexable, -> { open_source.source.not_removed } | |
delegate :download_owner, :download_readme, :domain, :watchers_url, :forks_url, | |
:download_fork_source, :download_tags, :download_contributions, :url, | |
:create_webhook, :download_issues, :download_forks, :stargazers_url, | |
:formatted_host, :get_file_list, :get_file_contents, :issues_url, | |
:source_url, :contributors_url, :blob_url, :raw_url, :commits_url, | |
:compare_url, :download_pull_requests, :retrieve_commits, :gather_maintenance_stats, to: :repository_host | |
def self.language(language) | |
where('lower(repositories.language) = ?', language.try(:downcase)) | |
end | |
def meta_tags | |
{ | |
title: "#{full_name} on #{formatted_host}", | |
description: description_with_language, | |
image: avatar_url(200) | |
} | |
end | |
def description_with_language | |
language_text = [language, "repository"].compact.join(' ').with_indefinite_article | |
[description, "#{language_text} on #{formatted_host}"].compact.join(' - ') | |
end | |
def normalize_license_and_language | |
self.language = Linguist::Language[self.language].to_s | |
self.language = nil if self.language.blank? | |
return if license.blank? | |
if license.downcase == 'other' | |
self.license = 'Other' | |
else | |
l = Spdx.find(license).try(:id) | |
l = 'Other' if l.blank? | |
self.license = l | |
end | |
end | |
def deprecate! | |
update_attribute(:status, 'Deprecated') | |
projects.each do |project| | |
project.update_attribute(:status, 'Deprecated') | |
end | |
end | |
def unmaintain! | |
update_attribute(:status, 'Unmaintained') | |
projects.each do |project| | |
project.update_attribute(:status, 'Unmaintained') | |
end | |
end | |
def save_projects | |
projects.find_each(&:forced_save) if previous_changes.any? | |
end | |
def owner | |
repository_organisation_id.present? ? repository_organisation : repository_user | |
end | |
def to_s | |
full_name | |
end | |
def to_param | |
{ | |
host_type: host_type.downcase, | |
owner: owner_name, | |
name: project_name | |
} | |
end | |
def owner_name | |
full_name.split('/')[0] | |
end | |
def project_name | |
full_name.split('/')[1] | |
end | |
def color | |
Linguist::Language[language].try(:color) | |
end | |
def stars | |
stargazers_count || 0 | |
end | |
def forks | |
forks_count || 0 | |
end | |
def avatar_url(size = 60) | |
avatar = repository_host.avatar_url(size) | |
return fallback_avatar_url(size) if avatar.blank? | |
avatar | |
end | |
def fallback_avatar_url(size = 60) | |
hash = Digest::MD5.hexdigest("#{host_type}-#{full_name}") | |
"https://www.gravatar.com/avatar/#{hash}?s=#{size}&f=y&d=retro" | |
end | |
def load_dependencies_tree(date = nil) | |
RepositoryTreeResolver.new(self, date).load_dependencies_tree | |
end | |
def id_or_name | |
if host_type == 'GitHub' | |
uuid || full_name | |
else | |
full_name | |
end | |
end | |
def recently_synced? | |
last_synced_at && last_synced_at > 1.day.ago | |
end | |
def manual_sync(token = nil) | |
update_all_info_async(token) | |
self.last_synced_at = Time.zone.now | |
save | |
end | |
def update_all_info_async(token = nil) | |
RepositoryDownloadWorker.perform_async(self.id, token) | |
end | |
def update_all_info(token = nil) | |
token ||= AuthToken.token if host_type == 'GitHub' | |
check_status | |
return if status == 'Removed' | |
update_from_repository(token) | |
unless last_synced_at && last_synced_at > 2.minutes.ago | |
download_owner | |
download_fork_source(token) | |
download_readme(token) | |
download_tags(token) | |
download_contributions(token) | |
download_manifests(token) | |
update_source_rank(true) | |
end | |
update(last_synced_at: Time.now) | |
end | |
def update_from_repository(token) | |
repository_host.update_from_host(token) | |
end | |
def self.create_from_host(host_type, full_name, token = nil) | |
RepositoryHost.const_get(host_type.capitalize).create(full_name, token) | |
end | |
def self.create_from_hash(repo_hash) | |
return unless repo_hash | |
repo_hash = repo_hash.to_hash.with_indifferent_access | |
ActiveRecord::Base.transaction do | |
g = Repository.where(host_type: (repo_hash[:host_type] || 'GitHub')).find_by(uuid: repo_hash[:id]) | |
g = Repository.host(repo_hash[:host_type] || 'GitHub').find_by('lower(full_name) = ?', repo_hash[:full_name].downcase) if g.nil? | |
g = Repository.new(uuid: repo_hash[:id], full_name: repo_hash[:full_name]) if g.nil? | |
g.host_type = repo_hash[:host_type] || 'GitHub' | |
g.full_name = repo_hash[:full_name] if g.full_name.downcase != repo_hash[:full_name].downcase | |
g.uuid = repo_hash[:id] if g.uuid.nil? | |
g.license = repo_hash[:license][:key] if repo_hash[:license] | |
if repo_hash[:fork] && repo_hash[:parent] | |
g.source_name = repo_hash[:parent][:full_name] | |
else | |
g.source_name = nil | |
end | |
g.assign_attributes repo_hash.slice(*Repository::API_FIELDS) | |
if g.changed? | |
return g.save ? g : nil | |
else | |
return g | |
end | |
end | |
rescue ActiveRecord::RecordNotUnique | |
nil | |
end | |
def check_status(removed = false) | |
Repository.check_status(host_type, full_name, removed) | |
end | |
def self.check_status(host_type, repo_full_name, removed = false) | |
domain = RepositoryHost::Base.domain(host_type) | |
response = Typhoeus.head("#{domain}/#{repo_full_name}") | |
if response.response_code == 404 | |
repo = Repository.includes(:projects).find_by_full_name(repo_full_name) | |
if repo | |
status = removed ? nil : 'Removed' | |
repo.update_attribute(:status, status) if !repo.private? | |
repo.projects.each do |project| | |
next unless ['bower', 'go', 'elm', 'alcatraz', 'julia', 'nimble'].include?(project.platform.downcase) | |
project.update_attribute(:status, status) | |
end | |
end | |
end | |
end | |
def self.update_from_hook(uuid, sender_id) | |
repository = Repository.where(host_type:'GitHub').find_by_uuid(uuid) | |
user = Identity.where('provider ILIKE ?', 'github%').where(uid: sender_id).first.try(:user) | |
if user.present? && repository.present? | |
repository.download_manifests(user.token) | |
repository.update_all_info_async(user.token) | |
end | |
end | |
def self.update_from_star(repo_name) | |
repository = Repository.host('GitHub').find_by_full_name(repo_name) | |
if repository | |
repository.increment!(:stargazers_count) | |
else | |
CreateRepositoryWorker.perform_async('GitHub', repo_name) | |
end | |
end | |
def self.update_from_tag(repo_name) | |
repository = Repository.host('GitHub').find_by_full_name(repo_name) | |
if repository | |
repository.download_tags | |
else | |
CreateRepositoryWorker.perform_async('GitHub', repo_name) | |
end | |
end | |
def github_contributions_count | |
contributions_count # legacy alias | |
end | |
def github_id | |
uuid # legacy alias | |
end | |
def sorted_tags | |
@sorted_tags ||= tags.sort | |
end | |
def repository_host | |
@repository_host ||= RepositoryHost.const_get(host_type.capitalize).new(self) | |
end | |
def hide | |
update!(status: "Hidden") | |
end | |
def gather_maintenance_stats_async | |
RepositoryMaintenanceStatWorker.enqueue(id, priority: :medium) | |
end | |
end |