Commit 70c44a0d authored by Hannes Rosenögger's avatar Hannes Rosenögger
Browse files

Fix tests, merge conflicts, some minor issues and make the project avatar feature mergable

parent a7dc2dfd1eca
...@@ -34,7 +34,7 @@ v 7.8.0 ...@@ -34,7 +34,7 @@ v 7.8.0
- -
- -
- -
- - Add Project Avatars (Steven Thonus and Hannes Rosenögger)
- -
- -
- Password reset token validity increased from 2 hours to 2 days since it is also send on account creation. - Password reset token validity increased from 2 hours to 2 days since it is also send on account creation.
......
...@@ -96,6 +96,7 @@ class Dispatcher ...@@ -96,6 +96,7 @@ class Dispatcher
new Profile() new Profile()
when 'projects' when 'projects'
new Project() new Project()
new ProjectAvatar()
switch path[1] switch path[1]
when 'edit' when 'edit'
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
......
...@@ -18,13 +18,3 @@ class @Project ...@@ -18,13 +18,3 @@ class @Project
$.cookie('hide_no_ssh_message', 'false', { path: path }) $.cookie('hide_no_ssh_message', 'false', { path: path })
$(@).parents('.no-ssh-key-message').hide() $(@).parents('.no-ssh-key-message').hide()
e.preventDefault() e.preventDefault()
# avatar
$('.js-choose-project-avatar-button').bind "click", ->
form = $(this).closest("form")
form.find(".js-project-avatar-input").click()
$('.js-project-avatar-input').bind "change", ->
form = $(this).closest("form")
filename = $(this).val().replace(/^.*[\\\/]/, '')
form.find(".js-avatar-filename").text(filename)
class @ProjectAvatar
constructor: ->
$('.js-choose-project-avatar-button').bind 'click', ->
form = $(this).closest('form')
form.find('.js-project-avatar-input').click()
$('.js-project-avatar-input').bind 'change', ->
form = $(this).closest('form')
filename = $(this).val().replace(/^.*[\\\/]/, '')
form.find('.js-avatar-filename').text(filename)
...@@ -14,7 +14,7 @@ def new ...@@ -14,7 +14,7 @@ def new
end end
def edit def edit
render 'edit', layout: "project_settings" render 'edit', layout: 'project_settings'
end end
def create def create
...@@ -36,7 +36,7 @@ def update ...@@ -36,7 +36,7 @@ def update
format.html { redirect_to edit_project_path(@project), notice: 'Project was successfully updated.' } format.html { redirect_to edit_project_path(@project), notice: 'Project was successfully updated.' }
format.js format.js
else else
format.html { render "edit", layout: "project_settings" } format.html { render 'edit', layout: 'project_settings' }
format.js format.js
end end
end end
...@@ -66,17 +66,17 @@ def show ...@@ -66,17 +66,17 @@ def show
format.html do format.html do
if @project.repository_exists? if @project.repository_exists?
if @project.empty_repo? if @project.empty_repo?
render "projects/empty", layout: user_layout render 'projects/empty', layout: user_layout
else else
@last_push = current_user.recent_push(@project.id) if current_user @last_push = current_user.recent_push(@project.id) if current_user
render :show, layout: user_layout render :show, layout: user_layout
end end
else else
render "projects/no_repo", layout: user_layout render 'projects/no_repo', layout: user_layout
end end
end end
format.json { pager_json("events/_events", @events.count) } format.json { pager_json('events/_events', @events.count) }
end end
end end
...@@ -87,9 +87,9 @@ def destroy ...@@ -87,9 +87,9 @@ def destroy
respond_to do |format| respond_to do |format|
format.html do format.html do
flash[:alert] = "Project deleted." flash[:alert] = 'Project deleted.'
if request.referer.include?("/admin") if request.referer.include?('/admin')
redirect_to admin_projects_path redirect_to admin_projects_path
else else
redirect_to projects_dashboard_path redirect_to projects_dashboard_path
...@@ -141,7 +141,7 @@ def upload_image ...@@ -141,7 +141,7 @@ def upload_image
if link_to_image if link_to_image
format.json { render json: { link: link_to_image } } format.json { render json: { link: link_to_image } }
else else
format.json { render json: "Invalid file.", status: :unprocessable_entity } format.json { render json: 'Invalid file.', status: :unprocessable_entity }
end end
end end
end end
...@@ -172,14 +172,14 @@ def set_title ...@@ -172,14 +172,14 @@ def set_title
end end
def user_layout def user_layout
current_user ? "projects" : "public_projects" current_user ? 'projects' : 'public_projects'
end end
def project_params def project_params
params.require(:project).permit( params.require(:project).permit(
:name, :path, :description, :issues_tracker, :tag_list, :name, :path, :description, :issues_tracker, :tag_list,
:issues_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, :default_branch, :issues_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, :default_branch,
:wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id :wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar
) )
end end
......
...@@ -54,10 +54,10 @@ def project_icon(project_id, options = {}) ...@@ -54,10 +54,10 @@ def project_icon(project_id, options = {})
project = Project.find_with_namespace(project_id) project = Project.find_with_namespace(project_id)
if project.avatar.present? if project.avatar.present?
image_tag project.avatar.url, options image_tag project.avatar.url, options
elsif options[:only_uploaded]
image_tag '/assets/no_project_icon.png', options
elsif project.avatar_in_git elsif project.avatar_in_git
image_tag project_avatar_path(project), options image_tag project_avatar_path(project), options
elsif options[:only_uploaded]
image_tag '/assets/no_project_icon.png', options
else # generated icon else # generated icon
project_identicon(project, options) project_identicon(project, options)
end end
...@@ -107,24 +107,24 @@ def last_commit(project) ...@@ -107,24 +107,24 @@ def last_commit(project)
if project.repo_exists? if project.repo_exists?
time_ago_with_tooltip(project.repository.commit.committed_date) time_ago_with_tooltip(project.repository.commit.committed_date)
else else
"Never" 'Never'
end end
rescue rescue
"Never" 'Never'
end end
def grouped_options_refs def grouped_options_refs
repository = @project.repository repository = @project.repository
options = [ options = [
["Branches", repository.branch_names], ['Branches', repository.branch_names],
["Tags", VersionSorter.rsort(repository.tag_names)] ['Tags', VersionSorter.rsort(repository.tag_names)]
] ]
# If reference is commit id - we should add it to branch/tag selectbox # If reference is commit id - we should add it to branch/tag selectbox
if(@ref && !options.flatten.include?(@ref) && if(@ref && !options.flatten.include?(@ref) &&
@ref =~ /^[0-9a-zA-Z]{6,52}$/) @ref =~ /^[0-9a-zA-Z]{6,52}$/)
options << ["Commit", [@ref]] options << ['Commit', [@ref]]
end end
grouped_options_for_select(options, @ref || @project.default_branch) grouped_options_for_select(options, @ref || @project.default_branch)
...@@ -186,7 +186,7 @@ def body_data_page ...@@ -186,7 +186,7 @@ def body_data_page
path = controller.controller_path.split('/') path = controller.controller_path.split('/')
namespace = path.first if path.second namespace = path.first if path.second
[namespace, controller.controller_name, controller.action_name].compact.join(":") [namespace, controller.controller_name, controller.action_name].compact.join(':')
end end
# shortcut for gitlab config # shortcut for gitlab config
...@@ -201,13 +201,13 @@ def extra_config ...@@ -201,13 +201,13 @@ def extra_config
def search_placeholder def search_placeholder
if @project && @project.persisted? if @project && @project.persisted?
"Search in this project" 'Search in this project'
elsif @snippet || @snippets || @show_snippets elsif @snippet || @snippets || @show_snippets
'Search snippets' 'Search snippets'
elsif @group && @group.persisted? elsif @group && @group.persisted?
"Search in this group" 'Search in this group'
else else
"Search" 'Search'
end end
end end
...@@ -218,7 +218,7 @@ def broadcast_message ...@@ -218,7 +218,7 @@ def broadcast_message
def time_ago_with_tooltip(date, placement = 'top', html_class = 'time_ago') def time_ago_with_tooltip(date, placement = 'top', html_class = 'time_ago')
capture_haml do capture_haml do
haml_tag :time, date.to_s, haml_tag :time, date.to_s,
class: html_class, datetime: date.getutc.iso8601, title: date.stamp("Aug 21, 2011 9:23pm"), class: html_class, datetime: date.getutc.iso8601, title: date.stamp('Aug 21, 2011 9:23pm'),
data: { toggle: 'tooltip', placement: placement } data: { toggle: 'tooltip', placement: placement }
haml_tag :script, "$('." + html_class + "').timeago().tooltip()" haml_tag :script, "$('." + html_class + "').timeago().tooltip()"
...@@ -241,8 +241,8 @@ def gitlab_markdown?(filename) ...@@ -241,8 +241,8 @@ def gitlab_markdown?(filename)
end end
def spinner(text = nil, visible = false) def spinner(text = nil, visible = false)
css_class = "loading" css_class = 'loading'
css_class << " hide" unless visible css_class << ' hide' unless visible
content_tag :div, class: css_class do content_tag :div, class: css_class do
content_tag(:i, nil, class: 'fa fa-spinner fa-spin') + text content_tag(:i, nil, class: 'fa fa-spinner fa-spin') + text
...@@ -259,17 +259,17 @@ def link_to(name = nil, options = nil, html_options = nil, &block) ...@@ -259,17 +259,17 @@ def link_to(name = nil, options = nil, html_options = nil, &block)
absolute_uri = nil absolute_uri = nil
end end
# Add "nofollow" only to external links # Add 'nofollow' only to external links
if host && host != Gitlab.config.gitlab.host && absolute_uri if host && host != Gitlab.config.gitlab.host && absolute_uri
if html_options if html_options
if html_options[:rel] if html_options[:rel]
html_options[:rel] << " nofollow" html_options[:rel] << ' nofollow'
else else
html_options.merge!(rel: "nofollow") html_options.merge!(rel: 'nofollow')
end end
else else
html_options = Hash.new html_options = Hash.new
html_options[:rel] = "nofollow" html_options[:rel] = 'nofollow'
end end
end end
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
# merge_requests_enabled :boolean default(TRUE), not null # merge_requests_enabled :boolean default(TRUE), not null
# wiki_enabled :boolean default(TRUE), not null # wiki_enabled :boolean default(TRUE), not null
# namespace_id :integer # namespace_id :integer
# issues_tracker :string(255) default("gitlab"), not null # issues_tracker :string(255) default('gitlab'), not null
# issues_tracker_id :string(255) # issues_tracker_id :string(255)
# snippets_enabled :boolean default(TRUE), not null # snippets_enabled :boolean default(TRUE), not null
# last_activity_at :datetime # last_activity_at :datetime
...@@ -29,6 +29,9 @@ ...@@ -29,6 +29,9 @@
# avatar :string(255) # avatar :string(255)
# #
require 'carrierwave/orm/activerecord'
require 'file_size_validator'
class Project < ActiveRecord::Base class Project < ActiveRecord::Base
include Gitlab::ShellAdapter include Gitlab::ShellAdapter
include Gitlab::VisibilityLevel include Gitlab::VisibilityLevel
...@@ -50,8 +53,8 @@ class Project < ActiveRecord::Base ...@@ -50,8 +53,8 @@ class Project < ActiveRecord::Base
attr_accessor :new_default_branch attr_accessor :new_default_branch
# Relations # Relations
belongs_to :creator, foreign_key: "creator_id", class_name: "User" belongs_to :creator, foreign_key: 'creator_id', class_name: 'User'
belongs_to :group, -> { where(type: Group) }, foreign_key: "namespace_id" belongs_to :group, -> { where(type: Group) }, foreign_key: 'namespace_id'
belongs_to :namespace belongs_to :namespace
has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id' has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id'
...@@ -71,20 +74,20 @@ class Project < ActiveRecord::Base ...@@ -71,20 +74,20 @@ class Project < ActiveRecord::Base
has_one :bamboo_service, dependent: :destroy has_one :bamboo_service, dependent: :destroy
has_one :teamcity_service, dependent: :destroy has_one :teamcity_service, dependent: :destroy
has_one :pushover_service, dependent: :destroy has_one :pushover_service, dependent: :destroy
has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id" has_one :forked_project_link, dependent: :destroy, foreign_key: 'forked_to_project_id'
has_one :forked_from_project, through: :forked_project_link has_one :forked_from_project, through: :forked_project_link
# Merge Requests for target project should be removed with it # Merge Requests for target project should be removed with it
has_many :merge_requests, dependent: :destroy, foreign_key: "target_project_id" has_many :merge_requests, dependent: :destroy, foreign_key: 'target_project_id'
# Merge requests from source project should be kept when source project was removed # Merge requests from source project should be kept when source project was removed
has_many :fork_merge_requests, foreign_key: "source_project_id", class_name: MergeRequest has_many :fork_merge_requests, foreign_key: 'source_project_id', class_name: MergeRequest
has_many :issues, -> { order 'issues.state DESC, issues.created_at DESC' }, dependent: :destroy has_many :issues, -> { order 'issues.state DESC, issues.created_at DESC' }, dependent: :destroy
has_many :labels, dependent: :destroy has_many :labels, dependent: :destroy
has_many :services, dependent: :destroy has_many :services, dependent: :destroy
has_many :events, dependent: :destroy has_many :events, dependent: :destroy
has_many :milestones, dependent: :destroy has_many :milestones, dependent: :destroy
has_many :notes, dependent: :destroy has_many :notes, dependent: :destroy
has_many :snippets, dependent: :destroy, class_name: "ProjectSnippet" has_many :snippets, dependent: :destroy, class_name: 'ProjectSnippet'
has_many :hooks, dependent: :destroy, class_name: "ProjectHook" has_many :hooks, dependent: :destroy, class_name: 'ProjectHook'
has_many :protected_branches, dependent: :destroy has_many :protected_branches, dependent: :destroy
has_many :project_members, dependent: :destroy, as: :source, class_name: 'ProjectMember' has_many :project_members, dependent: :destroy, as: :source, class_name: 'ProjectMember'
has_many :users, through: :project_members has_many :users, through: :project_members
...@@ -116,27 +119,27 @@ class Project < ActiveRecord::Base ...@@ -116,27 +119,27 @@ class Project < ActiveRecord::Base
validates_uniqueness_of :name, scope: :namespace_id validates_uniqueness_of :name, scope: :namespace_id
validates_uniqueness_of :path, scope: :namespace_id validates_uniqueness_of :path, scope: :namespace_id
validates :import_url, validates :import_url,
format: { with: URI::regexp(%w(git http https)), message: "should be a valid url" }, format: { with: URI::regexp(%w(git http https)), message: 'should be a valid url' },
if: :import? if: :import?
validates :star_count, numericality: { greater_than_or_equal_to: 0 } validates :star_count, numericality: { greater_than_or_equal_to: 0 }
validate :check_limit, on: :create validate :check_limit, on: :create
validate :avatar_type, validate :avatar_type,
if: ->(project) { project.avatar && project.avatar_changed? } if: ->(project) { project.avatar && project.avatar_changed? }
validates :avatar, file_size: { maximum: 100.kilobytes.to_i } validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
mount_uploader :avatar, AttachmentUploader mount_uploader :avatar, AttachmentUploader
# Scopes # Scopes
scope :without_user, ->(user) { where("projects.id NOT IN (:ids)", ids: user.authorized_projects.map(&:id) ) } scope :without_user, ->(user) { where('projects.id NOT IN (:ids)', ids: user.authorized_projects.map(&:id) ) }
scope :without_team, ->(team) { team.projects.present? ? where("projects.id NOT IN (:ids)", ids: team.projects.map(&:id)) : scoped } scope :without_team, ->(team) { team.projects.present? ? where('projects.id NOT IN (:ids)', ids: team.projects.map(&:id)) : scoped }
scope :not_in_group, ->(group) { where("projects.id NOT IN (:ids)", ids: group.project_ids ) } scope :not_in_group, ->(group) { where('projects.id NOT IN (:ids)', ids: group.project_ids ) }
scope :in_team, ->(team) { where("projects.id IN (:ids)", ids: team.projects.map(&:id)) } scope :in_team, ->(team) { where('projects.id IN (:ids)', ids: team.projects.map(&:id)) }
scope :in_namespace, ->(namespace) { where(namespace_id: namespace.id) } scope :in_namespace, ->(namespace) { where(namespace_id: namespace.id) }
scope :in_group_namespace, -> { joins(:group) } scope :in_group_namespace, -> { joins(:group) }
scope :sorted_by_activity, -> { reorder("projects.last_activity_at DESC") } scope :sorted_by_activity, -> { reorder('projects.last_activity_at DESC') }
scope :sorted_by_stars, -> { reorder("projects.star_count DESC") } scope :sorted_by_stars, -> { reorder('projects.star_count DESC') }
scope :personal, ->(user) { where(namespace_id: user.namespace_id) } scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
scope :joined, ->(user) { where("namespace_id != ?", user.namespace_id) } scope :joined, ->(user) { where('namespace_id != ?', user.namespace_id) }
scope :public_only, -> { where(visibility_level: Project::PUBLIC) } scope :public_only, -> { where(visibility_level: Project::PUBLIC) }
scope :public_and_internal_only, -> { where(visibility_level: Project.public_and_internal_levels) } scope :public_and_internal_only, -> { where(visibility_level: Project.public_and_internal_levels) }
scope :non_archived, -> { where(archived: false) } scope :non_archived, -> { where(archived: false) }
...@@ -187,26 +190,26 @@ def with_push ...@@ -187,26 +190,26 @@ def with_push
end end
def active def active
joins(:issues, :notes, :merge_requests).order("issues.created_at, notes.created_at, merge_requests.created_at DESC") joins(:issues, :notes, :merge_requests).order('issues.created_at, notes.created_at, merge_requests.created_at DESC')
end end
def search(query) def search(query)
joins(:namespace).where("projects.archived = ?", false). joins(:namespace).where('projects.archived = ?', false).
where("LOWER(projects.name) LIKE :query OR where('LOWER(projects.name) LIKE :query OR
LOWER(projects.path) LIKE :query OR LOWER(projects.path) LIKE :query OR
LOWER(namespaces.name) LIKE :query OR LOWER(namespaces.name) LIKE :query OR
LOWER(projects.description) LIKE :query", LOWER(projects.description) LIKE :query',
query: "%#{query.try(:downcase)}%") query: "%#{query.try(:downcase)}%")
end end
def search_by_title(query) def search_by_title(query)
where("projects.archived = ?", false).where("LOWER(projects.name) LIKE :query", query: "%#{query.downcase}%") where('projects.archived = ?', false).where('LOWER(projects.name) LIKE :query', query: "%#{query.downcase}%")
end end
def find_with_namespace(id) def find_with_namespace(id)
return nil unless id.include?("/") return nil unless id.include?('/')
id = id.split("/") id = id.split('/')
namespace = Namespace.find_by(path: id.first) namespace = Namespace.find_by(path: id.first)
return nil unless namespace return nil unless namespace
...@@ -224,7 +227,7 @@ def sort(method) ...@@ -224,7 +227,7 @@ def sort(method)
when 'recently_updated' then reorder('projects.updated_at DESC') when 'recently_updated' then reorder('projects.updated_at DESC')
when 'last_updated' then reorder('projects.updated_at ASC') when 'last_updated' then reorder('projects.updated_at ASC')
when 'largest_repository' then reorder('projects.repository_size DESC') when 'largest_repository' then reorder('projects.repository_size DESC')
else reorder("namespaces.path, projects.name ASC") else reorder('namespaces.path, projects.name ASC')
end end
end end
end end
...@@ -274,19 +277,19 @@ def check_limit ...@@ -274,19 +277,19 @@ def check_limit
end end
def to_param def to_param
namespace.path + "/" + path namespace.path + '/' + path
end end
def web_url def web_url
[gitlab_config.url, path_with_namespace].join("/") [gitlab_config.url, path_with_namespace].join('/')
end end
def web_url_without_protocol def web_url_without_protocol
web_url.split("://")[1] web_url.split('://')[1]
end end
def build_commit_note(commit) def build_commit_note(commit)
notes.new(commit_id: commit.id, noteable_type: "Commit") notes.new(commit_id: commit.id, noteable_type: 'Commit')
end end
def last_activity def last_activity
...@@ -345,8 +348,8 @@ def ci_service ...@@ -345,8 +348,8 @@ def ci_service
end end
def avatar_type def avatar_type
unless avatar.image? unless self.avatar.image?
errors.add :avatar, 'only images allowed' self.errors.add :avatar, 'only images allowed'
end end
end end
...@@ -384,7 +387,7 @@ def owner ...@@ -384,7 +387,7 @@ def owner
end end
def team_member_by_name_or_email(name = nil, email = nil) def team_member_by_name_or_email(name = nil, email = nil)
user = users.where("name like ? or email like ?", name, email).first user = users.where('name like ? or email like ?', name, email).first
project_members.where(user: user) if user project_members.where(user: user) if user
end end
...@@ -396,7 +399,7 @@ def team_member_by_id(user_id) ...@@ -396,7 +399,7 @@ def team_member_by_id(user_id)
def name_with_namespace def name_with_namespace
@name_with_namespace ||= begin @name_with_namespace ||= begin
if namespace if namespace
namespace.human_name + " / " + name namespace.human_name + ' / ' + name
else else
name name
end end
...@@ -431,7 +434,7 @@ def update_merge_requests(oldrev, newrev, ref, user) ...@@ -431,7 +434,7 @@ def update_merge_requests(oldrev, newrev, ref, user)
def valid_repo? def valid_repo?
repository.exists? repository.exists?
rescue rescue
errors.add(:path, "Invalid repository path") errors.add(:path, 'Invalid repository path')
false false