summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG4
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/copy_to_clipboard.js.coffee57
-rw-r--r--app/assets/javascripts/dropzone_input.js.coffee80
-rw-r--r--app/assets/javascripts/markdown_preview.js.coffee87
-rw-r--r--app/assets/javascripts/project.js.coffee24
-rw-r--r--app/assets/stylesheets/pages/projects.scss12
-rw-r--r--app/controllers/projects/issues_controller.rb4
-rw-r--r--app/controllers/projects/merge_requests_controller.rb4
-rw-r--r--app/controllers/snippets_controller.rb2
-rw-r--r--app/helpers/application_helper.rb8
-rw-r--r--app/helpers/button_helper.rb58
-rw-r--r--app/helpers/clipboard_helper.rb8
-rw-r--r--app/helpers/emails_helper.rb2
-rw-r--r--app/helpers/projects_helper.rb3
-rw-r--r--app/models/application_setting.rb12
-rw-r--r--app/models/ci/application_setting.rb12
-rw-r--r--app/models/concerns/issuable.rb2
-rw-r--r--app/models/concerns/strip_attribute.rb34
-rw-r--r--app/models/milestone.rb3
-rw-r--r--app/models/user.rb4
-rw-r--r--app/services/gravatar_service.rb4
-rw-r--r--app/views/admin/users/_projects.html.haml13
-rw-r--r--app/views/admin/users/projects.html.haml2
-rw-r--r--app/views/projects/_home_panel.html.haml10
-rw-r--r--app/views/projects/_md_preview.html.haml2
-rw-r--r--app/views/projects/commits/_commit.html.haml4
-rw-r--r--app/views/projects/issues/_discussion.html.haml4
-rw-r--r--app/views/projects/merge_requests/_discussion.html.haml4
-rw-r--r--app/views/shared/_clone_panel.html.haml24
-rw-r--r--config/initializers/1_settings.rb17
-rw-r--r--doc/public_access/public_access.md51
-rw-r--r--doc/raketasks/backup_restore.md3
-rw-r--r--lib/support/nginx/gitlab18
-rw-r--r--lib/support/nginx/gitlab-ssl18
-rwxr-xr-xscripts/prepare_build.sh4
-rw-r--r--spec/controllers/snippets_controller_spec.rb115
-rw-r--r--spec/helpers/application_helper_spec.rb16
-rw-r--r--spec/mailers/notify_spec.rb65
-rw-r--r--spec/models/concerns/strip_attribute_spec.rb20
41 files changed, 599 insertions, 221 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 18381984177..090b54f41a4 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,8 +1,12 @@
Please view this file on the master branch, on stable branches it's out of date.
v 8.3.0 (unreleased)
+ - Fix Error 500 when viewing user's personal projects from admin page (Stan Hu)
+ - Ensure cached application settings are refreshed at startup (Stan Hu)
- Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera)
- Fix 500 error when update group member permission
+ - Fix: Raw private snippets access workflow
+ - Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera)
v 8.2.1
- Forcefully update builds that didn't want to update with state machine
diff --git a/Gemfile b/Gemfile
index 91c909a2e7d..67640bb9ae0 100644
--- a/Gemfile
+++ b/Gemfile
@@ -188,7 +188,7 @@ gem "sass-rails", '~> 4.0.5'
gem "coffee-rails", '~> 4.1.0'
gem "uglifier", '~> 2.7.2'
gem 'turbolinks', '~> 2.5.0'
-gem 'jquery-turbolinks', '~> 2.0.1'
+gem 'jquery-turbolinks', '~> 2.1.0'
gem 'addressable', '~> 2.3.8'
gem 'bootstrap-sass', '~> 3.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index f06c4a4165f..dcb4a74e239 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -394,7 +394,7 @@ GEM
thor (>= 0.14, < 2.0)
jquery-scrollto-rails (1.4.3)
railties (> 3.1, < 5.0)
- jquery-turbolinks (2.0.2)
+ jquery-turbolinks (2.1.0)
railties (>= 3.1.0)
turbolinks
jquery-ui-rails (4.2.1)
@@ -894,7 +894,7 @@ DEPENDENCIES
jquery-atwho-rails (~> 1.3.2)
jquery-rails (~> 3.1.3)
jquery-scrollto-rails (~> 1.4.3)
- jquery-turbolinks (~> 2.0.1)
+ jquery-turbolinks (~> 2.1.0)
jquery-ui-rails (~> 4.2.1)
kaminari (~> 0.16.3)
letter_opener (~> 1.1.2)
diff --git a/app/assets/javascripts/copy_to_clipboard.js.coffee b/app/assets/javascripts/copy_to_clipboard.js.coffee
index 9c68c5cc1bc..24301e01b10 100644
--- a/app/assets/javascripts/copy_to_clipboard.js.coffee
+++ b/app/assets/javascripts/copy_to_clipboard.js.coffee
@@ -1,32 +1,37 @@
#= require clipboard
-$ ->
- clipboard = new Clipboard '.js-clipboard-trigger',
- text: (trigger) ->
- $target = $(trigger.nextElementSibling || trigger.previousElementSibling)
- $target.data('clipboard-text') || $target.text().trim()
+genericSuccess = (e) ->
+ showTooltip(e.trigger, 'Copied!')
+
+ # Clear the selection and blur the trigger so it loses its border
+ e.clearSelection()
+ $(e.trigger).blur()
- clipboard.on 'success', (e) ->
- $(e.trigger).
- tooltip(trigger: 'manual', placement: 'auto bottom', title: 'Copied!').
- tooltip('show').
- one('mouseleave', -> $(this).tooltip('hide'))
+# Safari doesn't support `execCommand`, so instead we inform the user to
+# copy manually.
+#
+# See http://clipboardjs.com/#browser-support
+genericError = (e) ->
+ if /Mac/i.test(navigator.userAgent)
+ key = '&#8984;' # Command
+ else
+ key = 'Ctrl'
- # Clear the selection and blur the trigger so it loses its border
- e.clearSelection()
- $(e.trigger).blur()
+ showTooltip(e.trigger, "Press #{key}-C to copy")
- # Safari doesn't support `execCommand`, so instead we inform the user to
- # copy manually.
- #
- # See http://clipboardjs.com/#browser-support
- clipboard.on 'error', (e) ->
- if /Mac/i.test(navigator.userAgent)
- title = "Press &#8984;-C to copy"
- else
- title = "Press Ctrl-C to copy"
+showTooltip = (target, title) ->
+ $(target).
+ tooltip(
+ container: 'body'
+ html: 'true'
+ placement: 'auto bottom'
+ title: title
+ trigger: 'manual'
+ ).
+ tooltip('show').
+ one('mouseleave', -> $(this).tooltip('hide'))
- $(e.trigger).
- tooltip(trigger: 'manual', placement: 'auto bottom', html: true, title: title).
- tooltip('show').
- one('mouseleave', -> $(this).tooltip('hide'))
+$ ->
+ clipboard = new Clipboard '[data-clipboard-target], [data-clipboard-text]'
+ clipboard.on 'success', genericSuccess
+ clipboard.on 'error', genericError
diff --git a/app/assets/javascripts/dropzone_input.js.coffee b/app/assets/javascripts/dropzone_input.js.coffee
index 6f789e668af..30a35a04339 100644
--- a/app/assets/javascripts/dropzone_input.js.coffee
+++ b/app/assets/javascripts/dropzone_input.js.coffee
@@ -1,3 +1,5 @@
+#= require markdown_preview
+
class @DropzoneInput
constructor: (form) ->
Dropzone.autoDiscover = false
@@ -11,17 +13,14 @@ class @DropzoneInput
uploadProgress = $("<div class=\"div-dropzone-progress\"></div>")
btnAlert = "<button type=\"button\"" + alertAttr + ">&times;</button>"
project_uploads_path = window.project_uploads_path or null
- markdown_preview_path = window.markdown_preview_path or null
max_file_size = gon.max_file_size or 10
form_textarea = $(form).find("textarea.markdown-area")
form_textarea.wrap "<div class=\"div-dropzone\"></div>"
form_textarea.on 'paste', (event) =>
handlePaste(event)
- form_textarea.on "input", ->
- hideReferencedUsers()
- form_textarea.on "blur", ->
- renderMarkdown()
+
+ $(form).setupMarkdownPreview()
form_dropzone = $(form).find('.div-dropzone')
form_dropzone.parent().addClass "div-dropzone-wrapper"
@@ -34,42 +33,6 @@ class @DropzoneInput
"opacity": 0
"display": "none"
- # Preview button
- $(document).off "click", ".js-md-preview-button"
- $(document).on "click", ".js-md-preview-button", (e) ->
- ###
- Shows the Markdown preview.
-
- Lets the server render GFM into Html and displays it.
- ###
- e.preventDefault()
- form = $(this).closest("form")
- # toggle tabs
- form.find(".js-md-write-button").parent().removeClass "active"
- form.find(".js-md-preview-button").parent().addClass "active"
-
- # toggle content
- form.find(".md-write-holder").hide()
- form.find(".md-preview-holder").show()
-
- renderMarkdown()
-
- # Write button
- $(document).off "click", ".js-md-write-button"
- $(document).on "click", ".js-md-write-button", (e) ->
- ###
- Shows the Markdown textarea.
- ###
- e.preventDefault()
- form = $(this).closest("form")
- # toggle tabs
- form.find(".js-md-write-button").parent().addClass "active"
- form.find(".js-md-preview-button").parent().removeClass "active"
-
- # toggle content
- form.find(".md-write-holder").show()
- form.find(".md-preview-holder").hide()
-
dropzone = form_dropzone.dropzone(
url: project_uploads_path
dictDefaultMessage: ""
@@ -136,41 +99,6 @@ class @DropzoneInput
child = $(dropzone[0]).children("textarea")
- hideReferencedUsers = ->
- referencedUsers = form.find(".referenced-users")
- referencedUsers.hide()
-
- renderReferencedUsers = (users) ->
- referencedUsers = form.find(".referenced-users")
-
- if referencedUsers.length
- if users.length >= 10
- referencedUsers.show()
- referencedUsers.find(".js-referenced-users-count").text users.length
- else
- referencedUsers.hide()
-
- renderMarkdown = ->
- preview = form.find(".js-md-preview")
- mdText = form.find(".markdown-area").val()
- if mdText.trim().length is 0
- preview.text "Nothing to preview."
- hideReferencedUsers()
- else
- preview.text "Loading..."
- $.ajax(
- type: "POST",
- url: markdown_preview_path,
- data: {
- text: mdText
- },
- dataType: "json"
- ).success (data) ->
- preview.html data.body
- preview.syntaxHighlight()
-
- renderReferencedUsers data.references.users
-
formatLink = (link) ->
text = "[#{link.alt}](#{link.url})"
text = "!#{text}" if link.is_image
diff --git a/app/assets/javascripts/markdown_preview.js.coffee b/app/assets/javascripts/markdown_preview.js.coffee
new file mode 100644
index 00000000000..98fc8f17340
--- /dev/null
+++ b/app/assets/javascripts/markdown_preview.js.coffee
@@ -0,0 +1,87 @@
+# MarkdownPreview
+#
+# Handles toggling the "Write" and "Preview" tab clicks, rendering the preview,
+# and showing a warning when more than `x` users are referenced.
+#
+class @MarkdownPreview
+ # Minimum number of users referenced before triggering a warning
+ referenceThreshold: 10
+
+ showPreview: (form) ->
+ preview = form.find('.js-md-preview')
+ mdText = form.find('textarea.markdown-area').val()
+
+ if mdText.trim().length == 0
+ preview.text('Nothing to preview.')
+ @hideReferencedUsers(form)
+ else
+ preview.text('Loading...')
+ @renderMarkdown mdText, (response) =>
+ preview.html(response.body)
+ preview.syntaxHighlight()
+ @renderReferencedUsers(response.references.users, form)
+
+ renderMarkdown: (text, success) ->
+ return unless window.markdown_preview_path
+
+ $.ajax
+ type: 'POST'
+ url: window.markdown_preview_path
+ data: { text: text }
+ dataType: 'json'
+ success: success
+
+ hideReferencedUsers: (form) ->
+ referencedUsers = form.find('.referenced-users')
+ referencedUsers.hide()
+
+ renderReferencedUsers: (users, form) ->
+ referencedUsers = form.find('.referenced-users')
+
+ if referencedUsers.length
+ if users.length >= @referenceThreshold
+ referencedUsers.show()
+ referencedUsers.find('.js-referenced-users-count').text(users.length)
+ else
+ referencedUsers.hide()
+
+markdownPreview = new MarkdownPreview()
+
+previewButtonSelector = '.js-md-preview-button'
+writeButtonSelector = '.js-md-write-button'
+
+$.fn.setupMarkdownPreview = ->
+ $form = $(this)
+
+ form_textarea = $form.find('textarea.markdown-area')
+
+ form_textarea.on 'input', -> markdownPreview.hideReferencedUsers($form)
+ form_textarea.on 'blur', -> markdownPreview.showPreview($form)
+
+$(document).on 'click', previewButtonSelector, (e) ->
+ e.preventDefault()
+
+ $form = $(this).closest('form')
+
+ # toggle tabs
+ $form.find(writeButtonSelector).parent().removeClass('active')
+ $form.find(previewButtonSelector).parent().addClass('active')
+
+ # toggle content
+ $form.find('.md-write-holder').hide()
+ $form.find('.md-preview-holder').show()
+
+ markdownPreview.showPreview($form)
+
+$(document).on 'click', writeButtonSelector, (e) ->
+ e.preventDefault()
+
+ $form = $(this).closest('form')
+
+ # toggle tabs
+ $form.find(writeButtonSelector).parent().addClass('active')
+ $form.find(previewButtonSelector).parent().removeClass('active')
+
+ # toggle content
+ $form.find('.md-write-holder').show()
+ $form.find('.md-preview-holder').hide()
diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee
index 0ea8fffce07..ec919f0cd67 100644
--- a/app/assets/javascripts/project.js.coffee
+++ b/app/assets/javascripts/project.js.coffee
@@ -1,13 +1,19 @@
class @Project
constructor: ->
- # Git clone panel switcher
- cloneHolder = $('.git-clone-holder')
- if cloneHolder.length
- $('a, button', cloneHolder).click ->
- $('a, button', cloneHolder).removeClass 'active'
- $(@).addClass 'active'
- $('#project_clone', cloneHolder).val $(@).data 'clone'
- $(".clone").text("").append $(@).data 'clone'
+ # Git protocol switcher
+ $('.js-protocol-switch').click ->
+ return if $(@).hasClass('active')
+
+ # Toggle 'active' for both buttons
+ $('.js-protocol-switch').toggleClass('active')
+
+ url = $(@).data('clone')
+
+ # Update the input field
+ $('#project_clone').val(url)
+
+ # Update the command line instructions
+ $('.clone').text(url)
# Ref switcher
$('.project-refs-select').on 'change', ->
@@ -39,4 +45,4 @@ class @Project
when 4 then label = ' On Mention '
$('#notifications-button').empty().append("<i class='fa fa-bell'></i>" + label + "<i class='fa fa-angle-down'></i>")
$(@).parents('ul').find('li.active').removeClass 'active'
- $(@).parent().addClass 'active' \ No newline at end of file
+ $(@).parent().addClass 'active'
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index d3b10040022..4a0fe546844 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -90,7 +90,12 @@
}
.visibility-level-label {
+ @extend .btn;
+ @extend .btn-gray;
+
color: $gray;
+ cursor: auto;
+
i {
color: inherit;
}
@@ -178,6 +183,11 @@
&:active {
outline: none;
}
+
+ &.btn-clipboard {
+ padding-left: 15px;
+ padding-right: 15px;
+ }
}
.active {
@@ -552,4 +562,4 @@ pre.light-well {
z-index: 100;
position: relative;
}
-} \ No newline at end of file
+}
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 5250a0f5e67..ae474cf8d68 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -158,12 +158,10 @@ class Projects::IssuesController < Projects::ApplicationController
end
def issue_params
- permitted = params.require(:issue).permit(
+ params.require(:issue).permit(
:title, :assignee_id, :position, :description,
:milestone_id, :state_event, :task_num, label_ids: []
)
- params[:issue][:title].strip! if params[:issue][:title]
- permitted
end
def bulk_update_params
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 6378a1f56b0..3f47f2ddb2c 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -276,13 +276,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def merge_request_params
- permitted = params.require(:merge_request).permit(
+ params.require(:merge_request).permit(
:title, :assignee_id, :source_project_id, :source_branch,
:target_project_id, :target_branch, :milestone_id,
:state_event, :description, :task_num, label_ids: []
)
- params[:merge_request][:title].strip! if params[:merge_request][:title]
- permitted
end
# Make sure merge requests created before 8.0
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index 08f2483af33..c72df73af46 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -2,7 +2,7 @@ class SnippetsController < ApplicationController
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
# Allow read snippet
- before_action :authorize_read_snippet!, only: [:show]
+ before_action :authorize_read_snippet!, only: [:show, :raw]
# Allow modify snippet
before_action :authorize_update_snippet!, only: [:edit, :update]
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 8ecdeaf8e76..3230ff1b004 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -68,7 +68,7 @@ module ApplicationHelper
end
end
- def avatar_icon(user_or_email = nil, size = nil)
+ def avatar_icon(user_or_email = nil, size = nil, scale = 2)
if user_or_email.is_a?(User)
user = user_or_email
else
@@ -78,12 +78,12 @@ module ApplicationHelper
if user
user.avatar_url(size) || default_avatar
else
- gravatar_icon(user_or_email, size)
+ gravatar_icon(user_or_email, size, scale)
end
end
- def gravatar_icon(user_email = '', size = nil)
- GravatarService.new.execute(user_email, size) ||
+ def gravatar_icon(user_email = '', size = nil, scale = 2)
+ GravatarService.new.execute(user_email, size, scale) ||
default_avatar
end
diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb
new file mode 100644
index 00000000000..313b6dde910
--- /dev/null
+++ b/app/helpers/button_helper.rb
@@ -0,0 +1,58 @@
+module ButtonHelper
+ # Output a "Copy to Clipboard" button
+ #
+ # data - Data attributes passed to `content_tag`
+ #
+ # Examples:
+ #
+ # # Define the clipboard's text
+ # clipboard_button(clipboard_text: "Foo")
+ # # => "<button class='...' data-clipboard-text='Foo'>...</button>"
+ #
+ # # Define the target element
+ # clipboard_button(clipboard_target: "#foo")
+ # # => "<button class='...' data-clipboard-target='#foo'>...</button>"
+ #
+ # See http://clipboardjs.com/#usage
+ def clipboard_button(data = {})
+ content_tag :button,
+ icon('clipboard'),
+ class: 'btn btn-xs btn-clipboard',
+ data: data,
+ type: :button
+ end
+
+ def http_clone_button(project)
+ klass = 'btn js-protocol-switch'
+ klass << ' active' if default_clone_protocol == 'http'
+ klass << ' has_tooltip' if current_user.try(:require_password?)
+
+ protocol = gitlab_config.protocol.upcase
+
+ content_tag :button, protocol,
+ class: klass,
+ data: {
+ clone: project.http_url_to_repo,
+ container: 'body',
+ html: 'true',
+ title: "Set a password on your account<br>to pull or push via #{protocol}"
+ },
+ type: :button
+ end
+
+ def ssh_clone_button(project)
+ klass = 'btn js-protocol-switch'
+ klass << ' active' if default_clone_protocol == 'ssh'
+ klass << ' has_tooltip' if current_user.try(:require_ssh_key?)
+
+ content_tag :button, 'SSH',
+ class: klass,
+ data: {
+ clone: project.ssh_url_to_repo,
+ container: 'body',
+ html: 'true',
+ title: 'Add an SSH key to your profile<br>to pull or push via SSH.'
+ },
+ type: :button
+ end
+end
diff --git a/app/helpers/clipboard_helper.rb b/app/helpers/clipboard_helper.rb
deleted file mode 100644
index 3c1d7569fac..00000000000
--- a/app/helpers/clipboard_helper.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-module ClipboardHelper
- def clipboard_button
- content_tag :button,
- icon('clipboard'),
- class: 'btn btn-xs btn-clipboard js-clipboard-trigger',
- type: :button
- end
-end
diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb
index 45788ba95ac..41b5bd7be90 100644
--- a/app/helpers/emails_helper.rb
+++ b/app/helpers/emails_helper.rb
@@ -28,6 +28,8 @@ module EmailsHelper
return "View #{action.humanize.singularize}"
end
end
+
+ nil
end
def color_email_diff(diffcontent)
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index c9cd4a0d54c..c0c51aae039 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -173,8 +173,7 @@ module ProjectsHelper
'unknown'
end
- def default_url_to_repo(project = nil)
- project = project || @project
+ def default_url_to_repo(project = @project)
current_user ? project.url_to_repo : project.http_url_to_repo
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index b2d5fe1558f..3df8135acf1 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -73,15 +73,23 @@ class ApplicationSetting < ActiveRecord::Base
end
after_commit do
- Rails.cache.write('application_setting.last', self)
+ Rails.cache.write(cache_key, self)
end
def self.current
- Rails.cache.fetch('application_setting.last') do
+ Rails.cache.fetch(cache_key) do
ApplicationSetting.last
end
end
+ def self.expire
+ Rails.cache.delete(cache_key)
+ end
+
+ def self.cache_key
+ 'application_setting.last'
+ end
+
def self.create_from_defaults
create(
default_projects_limit: Settings.gitlab['default_projects_limit'],
diff --git a/app/models/ci/application_setting.rb b/app/models/ci/application_setting.rb
index 1307fa0b472..4e512d290ee 100644
--- a/app/models/ci/application_setting.rb
+++ b/app/models/ci/application_setting.rb
@@ -14,11 +14,15 @@ module Ci
extend Ci::Model
after_commit do
- Rails.cache.write('ci_application_setting.last', self)
+ Rails.cache.write(cache_key, self)
+ end
+
+ def self.expire
+ Rails.cache.delete(cache_key)
end
def self.current
- Rails.cache.fetch('ci_application_setting.last') do
+ Rails.cache.fetch(cache_key) do
Ci::ApplicationSetting.last
end
end
@@ -29,5 +33,9 @@ module Ci
add_pusher: Settings.gitlab_ci['add_pusher'],
)
end
+
+ def self.cache_key
+ 'ci_application_setting.last'
+ end
end
end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 68138688aab..badeadfa418 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -8,6 +8,7 @@ module Issuable
extend ActiveSupport::Concern
include Participable
include Mentionable
+ include StripAttribute
included do
belongs_to :author, class_name: "User"
@@ -51,6 +52,7 @@ module Issuable
attr_mentionable :title, :description
participant :author, :assignee, :notes_with_associations
+ strip_attributes :title
end
module ClassMethods
diff --git a/app/models/concerns/strip_attribute.rb b/app/models/concerns/strip_attribute.rb
new file mode 100644
index 00000000000..8806ebe897a
--- /dev/null
+++ b/app/models/concerns/strip_attribute.rb
@@ -0,0 +1,34 @@
+# == Strip Attribute module
+#
+# Contains functionality to clean attributes before validation
+#
+# Usage:
+#
+# class Milestone < ActiveRecord::Base
+# strip_attributes :title
+# end
+#
+#
+module StripAttribute
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ def strip_attributes(*attrs)
+ strip_attrs.concat(attrs)
+ end
+
+ def strip_attrs
+ @strip_attrs ||= []
+ end
+ end
+
+ included do
+ before_validation :strip_attributes
+ end
+
+ def strip_attributes
+ self.class.strip_attrs.each do |attr|
+ self[attr].strip! if self[attr] && self[attr].respond_to?(:strip!)
+ end
+ end
+end
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 2ff16e2825c..c2642b75b8a 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -22,6 +22,7 @@ class Milestone < ActiveRecord::Base
include InternalId
include Sortable
+ include StripAttribute
belongs_to :project
has_many :issues
@@ -35,6 +36,8 @@ class Milestone < ActiveRecord::Base
validates :title, presence: true
validates :project, presence: true
+ strip_attributes :title
+
state_machine :state, initial: :active do
event :close do
transition active: :closed
diff --git a/app/models/user.rb b/app/models/user.rb
index 9374f01f99f..e1144ca77be 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -637,11 +637,11 @@ class User < ActiveRecord::Base
email.start_with?('temp-email-for-oauth')
end
- def avatar_url(size = nil)
+ def avatar_url(size = nil, scale = 2)
if avatar.present?
[gitlab_config.url, avatar.url].join
else
- GravatarService.new.execute(email, size)
+ GravatarService.new.execute(email, size, scale)
end
end
diff --git a/app/services/gravatar_service.rb b/app/services/gravatar_service.rb
index 4bee0c26a68..433ecc2df32 100644
--- a/app/services/gravatar_service.rb
+++ b/app/services/gravatar_service.rb
@@ -1,13 +1,13 @@
class GravatarService
include Gitlab::CurrentSettings
- def execute(email, size = nil)
+ def execute(email, size = nil, scale = 2)
if current_application_settings.gravatar_enabled? && email.present?
size = 40 if size.nil? || size <= 0
sprintf gravatar_url,
hash: Digest::MD5.hexdigest(email.strip.downcase),
- size: size,
+ size: size * scale,
email: email.strip
end
end
diff --git a/app/views/admin/users/_projects.html.haml b/app/views/admin/users/_projects.html.haml
new file mode 100644
index 00000000000..a126a858ea8
--- /dev/null
+++ b/app/views/admin/users/_projects.html.haml
@@ -0,0 +1,13 @@
+- if local_assigns.has_key?(:contributed_projects) && contributed_projects.present?
+ .panel.panel-default.contributed-projects
+ .panel-heading Projects contributed to
+ = render 'shared/projects/list',
+ projects: contributed_projects.sort_by(&:star_count).reverse,
+ projects_limit: 5, stars: true, avatar: false
+
+- if local_assigns.has_key?(:projects) && projects.present?
+ .panel.panel-default
+ .panel-heading Personal projects
+ = render 'shared/projects/list',
+ projects: projects.sort_by(&:star_count).reverse,
+ projects_limit: 10, stars: true, avatar: false
diff --git a/app/views/admin/users/projects.html.haml b/app/views/admin/users/projects.html.haml
index 0d7a1a25a80..b655b2a15f5 100644
--- a/app/views/admin/users/projects.html.haml
+++ b/app/views/admin/users/projects.html.haml
@@ -14,7 +14,7 @@
.row
.col-md-6
- if @personal_projects.present?
- = render 'users/projects', projects: @personal_projects
+ = render 'admin/users/projects', projects: @personal_projects
- else
.nothing-here-block This user has no personal projects.
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index 88d54bf6f21..b30036966a7 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -1,5 +1,5 @@
- empty_repo = @project.empty_repo?
-.project-home-panel.clearfix{:class => ("empty-project" if empty_repo)}
+.project-home-panel.cover-block.clearfix{:class => ("empty-project" if empty_repo)}
.project-identicon-holder
= project_icon(@project, alt: '', class: 'project-avatar avatar s90')
.project-home-desc
@@ -12,8 +12,10 @@
Forked from
= link_to project_path(forked_from_project) do
= forked_from_project.namespace.try(:name)
-
-
+ .cover-controls
+ .visibility-level-label
+ = visibility_level_icon(@project.visibility_level)
+ = visibility_level_label(@project.visibility_level)
.project-repo-buttons
.split-one
@@ -21,7 +23,7 @@
= render 'projects/buttons/fork'
= render "shared/clone_panel"
-
+
.split-repo-buttons
= render "projects/buttons/download"
= render 'projects/buttons/dropdown'
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index 7b21095ea3e..8218cf11201 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -5,7 +5,7 @@
%a.js-md-write-button(href="#md-write-holder" tabindex="-1")
Write
%li
- %a.js-md-preview-button(href="md-preview-holder" tabindex="-1")
+ %a.js-md-preview-button(href="#md-preview-holder" tabindex="-1")
Preview
- if defined?(referenced_users) && referenced_users
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 805be332e64..2e489a0a4d5 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -20,8 +20,8 @@
- if ci_commit
= render_ci_status(ci_commit)
&nbsp;
- = clipboard_button
- = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id", data: {clipboard_text: commit.id}
+ = clipboard_button(clipboard_text: commit.id)
+ = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
.notes_count
- if note_count > 0
diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml
index 020952dd001..8f0a1ed9be2 100644
--- a/app/views/projects/issues/_discussion.html.haml
+++ b/app/views/projects/issues/_discussion.html.haml
@@ -18,9 +18,9 @@
= link_to_member(@project, participant, name: false, size: 24)
.col-md-3
.input-group.cross-project-reference
- %span.slead.has_tooltip{title: 'Cross-project reference'}
+ %span#cross-project-reference.slead.has_tooltip{title: 'Cross-project reference'}
= cross_project_reference(@project, @issue)
- = clipboard_button
+ = clipboard_button(clipboard_target: '#cross-project-reference')
.row
%section.col-md-9
diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml
index cb75bd8c5ba..2b3c3eff5e4 100644
--- a/app/views/projects/merge_requests/_discussion.html.haml
+++ b/app/views/projects/merge_requests/_discussion.html.haml
@@ -15,9 +15,9 @@
= render "projects/merge_requests/show/participants"
.col-md-3
.input-group.cross-project-reference
- %span.slead.has_tooltip{title: 'Cross-project reference'}
+ %span#cross-project-reference.slead.has_tooltip{title: 'Cross-project reference'}
= cross_project_reference(@project, @merge_request)
- = clipboard_button
+ = clipboard_button(clipboard_target: '#cross-project-reference')
.row
%section.col-md-9
diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml
index 8bcb24ae9df..edb5778f424 100644
--- a/app/views/shared/_clone_panel.html.haml
+++ b/app/views/shared/_clone_panel.html.haml
@@ -2,25 +2,9 @@
.git-clone-holder.input-group
.input-group-addon.git-protocols
.input-group-btn
- %button{ |
- type: 'button', |
- class: "btn #{ 'active' if default_clone_protocol == 'ssh' }#{ ' has_tooltip' if current_user && current_user.require_ssh_key? }", |
- :"data-clone" => project.ssh_url_to_repo, |
- :"data-title" => "Add an SSH key to your profile<br> to pull or push via SSH.",
- :"data-html" => "true",
- :"data-container" => "body"}
- SSH
+ = ssh_clone_button(project)
.input-group-btn
- %button{ |
- type: 'button', |
- class: "btn #{ 'active' if default_clone_protocol == 'http' }#{ ' has_tooltip' if current_user && current_user.require_password? }", |
- :"data-clone" => project.http_url_to_repo, |
- :"data-title" => "Set a password on your account<br> to pull or push via #{gitlab_config.protocol.upcase}.",
- :"data-html" => "true",
- :"data-container" => "body"}
- = gitlab_config.protocol.upcase
+ = http_clone_button(project)
= text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true
- - if project.kind_of?(Project)
- .input-group-addon.has_tooltip{title: "#{visibility_level_label(project.visibility_level)} project", data: { container: "body" } }
- .visibility-level-label
- = visibility_level_icon(project.visibility_level)
+ .input-group-btn
+ = clipboard_button(clipboard_target: '#project_clone')
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index b162b8a83fc..62619241001 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -33,13 +33,15 @@ class Settings < Settingslogic
end
def build_gitlab_shell_ssh_path_prefix
+ user_host = "#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}"
+
if gitlab_shell.ssh_port != 22
- "ssh://#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}:#{gitlab_shell.ssh_port}/"
+ "ssh://#{user_host}:#{gitlab_shell.ssh_port}/"
else
if gitlab_shell.ssh_host.include? ':'
- "[#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}]:"
+ "[#{user_host}]:"
else
- "#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}:"
+ "#{user_host}:"
end
end
end
@@ -293,3 +295,12 @@ if Rails.env.test?
Settings.gitlab['default_can_create_group'] = true
Settings.gitlab['default_can_create_team'] = false
end
+
+# Force a refresh of application settings at startup
+begin
+ ApplicationSetting.expire
+ Ci::ApplicationSetting.expire
+rescue
+ # Gracefully handle when Redis is not available. For example,
+ # omnibus may fail here during assets:precompile.
+end
diff --git a/doc/public_access/public_access.md b/doc/public_access/public_access.md
index bd439f7c6f3..6e22ea7b72a 100644
--- a/doc/public_access/public_access.md
+++ b/doc/public_access/public_access.md
@@ -1,44 +1,59 @@
# Public access
-GitLab allows you to open selected projects to be accessed **publicly** or **internally**.
+GitLab allows you to change your projects' visibility in order be accessed
+**publicly** or **internally**.
-Projects with either of these visibility levels will be listed in the [public access directory](/public).
+Projects with either of these visibility levels will be listed in the
+public access directory (`/public` under your GitLab instance).
+Here is the [GitLab.com example](https://gitlab.com/public).
Internal projects will only be available to authenticated users.
-## Public projects
+## Visibility of projects
+
+### Public projects
Public projects can be cloned **without any** authentication.
-It will also be listed on the [public access directory](/public).
+They will also be listed on the public access directory (`/public`).
-**Any logged in user** will have [Guest](../permissions/permissions) permissions on the repository.
+**Any logged in user** will have [Guest](../permissions/permissions)
+permissions on the repository.
-## Internal projects
+### Internal projects
Internal projects can be cloned by any logged in user.
-It will also be listed on the [public access directory](/public) for logged in users.
+They will also be listed on the public access directory (`/public`) for logged
+in users.
-Any logged in user will have [Guest](../permissions/permissions) permissions on the repository.
+Any logged in user will have [Guest](../permissions/permissions) permissions on
+the repository.
-## How to change project visibility
+### How to change project visibility
-1. Go to your project dashboard
-1. Click on the "Edit" tab
-1. Change "Visibility Level"
+1. Go to your project's **Settings**
+1. Change "Visibility Level" to either Public, Internal or Private
## Visibility of users
-The public page of users, located at `/u/username` is visible if either:
+The public page of a user, located at `/u/username`, is always visible whether
+you are logged in or not.
+
+When visiting the public page of a user, you can only see the projects which
+you are privileged to.
-- You are logged in.
-- You are logged out, and the target user is authorized to (is Guest, Reporter, etc.) at least one public project.
+## Visibility of groups
-Otherwise, you will be redirected to the sign in page.
+The public page of a group, located at `/groups/groupname`, is always visible
+to everyone.
-When visiting the public page of an user, you will only see listed projects which you can view yourself.
+Logged out users will be able to see the description and the avatar of the
+group as well as all public projects belonging to that group.
## Restricting the use of public or internal projects
-In the Admin area under Settings you can disable public projects or public and internal projects for the entire GitLab installation to prevent people making code public by accident. The restricted visibility settings do not apply to admin users.
+In the Admin area under **Settings** (`/admin/application_settings`), you can
+restrict the use of visibility levels for users when they create a project or a
+snippet. This is useful to prevent people exposing their repositories to public
+by accident. The restricted visibility settings do not apply to admin users.
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 4e645b21a85..b4d2786bd76 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -274,9 +274,6 @@ sudo gitlab-rake gitlab:backup:restore BACKUP=1393513186
# Start GitLab
sudo gitlab-ctl start
-# Create satellites
-sudo gitlab-rake gitlab:satellites:create
-
# Check GitLab
sudo gitlab-rake gitlab:check SANITIZE=true
```
diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab
index 0cf5292b290..2a79fbdcf93 100644
--- a/lib/support/nginx/gitlab
+++ b/lib/support/nginx/gitlab
@@ -114,24 +114,28 @@ server {
}
location ~ ^/[\w\.-]+/[\w\.-]+/gitlab-lfs/objects {
+ client_max_body_size 0;
# 'Error' 418 is a hack to re-use the @gitlab-workhorse block
error_page 418 = @gitlab-workhorse;
return 418;
}
location ~ ^/[\w\.-]+/[\w\.-]+/(info/refs|git-upload-pack|git-receive-pack)$ {
+ client_max_body_size 0;
# 'Error' 418 is a hack to re-use the @gitlab-workhorse block
error_page 418 = @gitlab-workhorse;
return 418;
}
location ~ ^/[\w\.-]+/[\w\.-]+/repository/archive {
+ client_max_body_size 0;
# 'Error' 418 is a hack to re-use the @gitlab-workhorse block
error_page 418 = @gitlab-workhorse;
return 418;
}
location ~ ^/api/v3/projects/.*/repository/archive {
+ client_max_body_size 0;
# 'Error' 418 is a hack to re-use the @gitlab-workhorse block
error_page 418 = @gitlab-workhorse;
return 418;
@@ -139,16 +143,18 @@ server {
# Build artifacts should be submitted to this location
location ~ ^/[\w\.-]+/[\w\.-]+/builds/download {
- # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
- error_page 418 = @gitlab-workhorse;
- return 418;
+ client_max_body_size 0;
+ # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
+ error_page 418 = @gitlab-workhorse;
+ return 418;
}
# Build artifacts should be submitted to this location
location ~ /ci/api/v1/builds/[0-9]+/artifacts {
- # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
- error_page 418 = @gitlab-workhorse;
- return 418;
+ client_max_body_size 0;
+ # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
+ error_page 418 = @gitlab-workhorse;
+ return 418;
}
location @gitlab-workhorse {
diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl
index 31a651c87fd..016f7a536fb 100644
--- a/lib/support/nginx/gitlab-ssl
+++ b/lib/support/nginx/gitlab-ssl
@@ -161,24 +161,28 @@ server {
}
location ~ ^/[\w\.-]+/[\w\.-]+/gitlab-lfs/objects {
+ client_max_body_size 0;
# 'Error' 418 is a hack to re-use the @gitlab-workhorse block
error_page 418 = @gitlab-workhorse;
return 418;
}
location ~ ^/[\w\.-]+/[\w\.-]+/(info/refs|git-upload-pack|git-receive-pack)$ {
+ client_max_body_size 0;
# 'Error' 418 is a hack to re-use the @gitlab-workhorse block
error_page 418 = @gitlab-workhorse;
return 418;
}
location ~ ^/[\w\.-]+/[\w\.-]+/repository/archive {
+ client_max_body_size 0;
# 'Error' 418 is a hack to re-use the @gitlab-workhorse block
error_page 418 = @gitlab-workhorse;
return 418;
}
location ~ ^/api/v3/projects/.*/repository/archive {
+ client_max_body_size 0;
# 'Error' 418 is a hack to re-use the @gitlab-workhorse block
error_page 418 = @gitlab-workhorse;
return 418;
@@ -186,16 +190,18 @@ server {
# Build artifacts should be submitted to this location
location ~ ^/[\w\.-]+/[\w\.-]+/builds/download {
- # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
- error_page 418 = @gitlab-workhorse;
- return 418;
+ client_max_body_size 0;
+ # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
+ error_page 418 = @gitlab-workhorse;
+ return 418;
}
# Build artifacts should be submitted to this location
location ~ /ci/api/v1/builds/[0-9]+/artifacts {
- # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
- error_page 418 = @gitlab-workhorse;
- return 418;
+ client_max_body_size 0;
+ # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
+ error_page 418 = @gitlab-workhorse;
+ return 418;
}
location @gitlab-workhorse {
diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh
index dca5e1c5db3..119cc90fc1e 100755
--- a/scripts/prepare_build.sh
+++ b/scripts/prepare_build.sh
@@ -1,7 +1,7 @@
#!/bin/bash
if [ -f /.dockerinit ]; then
- wget -q http://ftp.de.debian.org/debian/pool/main/p/phantomjs/phantomjs_1.9.0-1+b1_amd64.deb
- dpkg -i phantomjs_1.9.0-1+b1_amd64.deb
+ wget -q https://gitlab.com/axil/phantomjs-debian/raw/master/phantomjs_1.9.8-0jessie_amd64.deb
+ dpkg -i phantomjs_1.9.8-0jessie_amd64.deb
apt-get update -qq
apt-get install -y -qq libicu-dev libkrb5-dev cmake nodejs postgresql-client mysql-client
diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb
index e9b823c523c..b3dcb52c500 100644
--- a/spec/controllers/snippets_controller_spec.rb
+++ b/spec/controllers/snippets_controller_spec.rb
@@ -115,4 +115,119 @@ describe SnippetsController do
end
end
end
+
+ describe 'GET #raw' do
+ let(:user) { create(:user) }
+
+ context 'when the personal snippet is private' do
+ let(:personal_snippet) { create(:personal_snippet, :private, author: user) }
+
+ context 'when signed in' do
+ before do
+ sign_in(user)
+ end
+
+ context 'when signed in user is not the author' do
+ let(:other_author) { create(:author) }
+ let(:other_personal_snippet) { create(:personal_snippet, :private, author: other_author) }
+
+ it 'responds with status 404' do
+ get :raw, id: other_personal_snippet.to_param
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'when signed in user is the author' do
+ it 'renders the raw snippet' do
+ get :raw, id: personal_snippet.to_param
+
+ expect(assigns(:snippet)).to eq(personal_snippet)
+ expect(response.status).to eq(200)
+ end
+ end
+ end
+
+ context 'when not signed in' do
+ it 'redirects to the sign in page' do
+ get :raw, id: personal_snippet.to_param
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+ end
+
+ context 'when the personal snippet is internal' do
+ let(:personal_snippet) { create(:personal_snippet, :internal, author: user) }
+
+ context 'when signed in' do
+ before do
+ sign_in(user)
+ end
+
+ it 'renders the raw snippet' do
+ get :raw, id: personal_snippet.to_param
+
+ expect(assigns(:snippet)).to eq(personal_snippet)
+ expect(response.status).to eq(200)
+ end
+ end
+
+ context 'when not signed in' do
+ it 'redirects to the sign in page' do
+ get :raw, id: personal_snippet.to_param
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+ end
+
+ context 'when the personal snippet is public' do
+ let(:personal_snippet) { create(:personal_snippet, :public, author: user) }
+
+ context 'when signed in' do
+ before do
+ sign_in(user)
+ end
+
+ it 'renders the raw snippet' do
+ get :raw, id: personal_snippet.to_param
+
+ expect(assigns(:snippet)).to eq(personal_snippet)
+ expect(response.status).to eq(200)
+ end
+ end
+
+ context 'when not signed in' do
+ it 'renders the raw snippet' do
+ get :raw, id: personal_snippet.to_param
+
+ expect(assigns(:snippet)).to eq(personal_snippet)
+ expect(response.status).to eq(200)
+ end
+ end
+ end
+
+ context 'when the personal snippet does not exist' do
+ context 'when signed in' do
+ before do
+ sign_in(user)
+ end
+
+ it 'responds with status 404' do
+ get :raw, id: 'doesntexist'
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'when not signed in' do
+ it 'responds with status 404' do
+ get :raw, id: 'doesntexist'
+
+ expect(response.status).to eq(404)
+ end
+ end
+ end
+ end
end
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 4b8000ecc44..0a64b70d6a6 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -95,9 +95,9 @@ describe ApplicationHelper do
end
it 'should call gravatar_icon when no User exists with the given email' do
- expect(helper).to receive(:gravatar_icon).with('foo@example.com', 20)
+ expect(helper).to receive(:gravatar_icon).with('foo@example.com', 20, 2)
- helper.avatar_icon('foo@example.com', 20)
+ helper.avatar_icon('foo@example.com', 20, 2)
end
describe 'using a User' do
@@ -150,15 +150,19 @@ describe ApplicationHelper do
stub_gravatar_setting(plain_url: 'http://example.local/?s=%{size}&hash=%{hash}')
expect(gravatar_icon(user_email, 20)).
- to eq('http://example.local/?s=20&hash=b58c6f14d292556214bd64909bcdb118')
+ to eq('http://example.local/?s=40&hash=b58c6f14d292556214bd64909bcdb118')
end
it 'accepts a custom size argument' do
- expect(helper.gravatar_icon(user_email, 64)).to include '?s=64'
+ expect(helper.gravatar_icon(user_email, 64)).to include '?s=128'
end
- it 'defaults size to 40 when given an invalid size' do
- expect(helper.gravatar_icon(user_email, nil)).to include '?s=40'
+ it 'defaults size to 40@2x when given an invalid size' do
+ expect(helper.gravatar_icon(user_email, nil)).to include '?s=80'
+ end
+
+ it 'accepts a scaling factor' do
+ expect(helper.gravatar_icon(user_email, 40, 3)).to include '?s=120'
end
it 'ignores case and surrounding whitespace' do
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 47863d54579..d6796b07a5b 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -77,6 +77,32 @@ describe Notify do
end
end
+ shared_examples 'it should have Gmail Actions links' do
+ it { is_expected.to have_body_text /ViewAction/ }
+ end
+
+ shared_examples 'it should not have Gmail Actions links' do
+ it { is_expected.to_not have_body_text /ViewAction/ }
+ end
+
+ shared_examples 'it should show Gmail Actions View Issue link' do
+ it_behaves_like 'it should have Gmail Actions links'
+
+ it { is_expected.to have_body_text /View Issue/ }
+ end
+
+ shared_examples 'it should show Gmail Actions View Merge request link' do
+ it_behaves_like 'it should have Gmail Actions links'
+
+ it { is_expected.to have_body_text /View Merge request/ }
+ end
+
+ shared_examples 'it should show Gmail Actions View Commit link' do
+ it_behaves_like 'it should have Gmail Actions links'
+
+ it { is_expected.to have_body_text /View Commit/ }
+ end
+
describe 'for new users, the email' do
let(:example_site_path) { root_path }
let(:new_user) { create(:user, email: new_user_address, created_by_id: 1) }
@@ -87,6 +113,7 @@ describe Notify do
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'a new user email', new_user_address
+ it_behaves_like 'it should not have Gmail Actions links'
it 'contains the password text' do
is_expected.to have_body_text /Click here to set your password/
@@ -115,6 +142,7 @@ describe Notify do
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'a new user email', new_user_address
+ it_behaves_like 'it should not have Gmail Actions links'
it 'should not contain the new user\'s password' do
is_expected.not_to have_body_text /password/
@@ -127,6 +155,7 @@ describe Notify do
subject { Notify.new_ssh_key_email(key.id) }
it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'it should not have Gmail Actions links'
it 'is sent to the new user' do
is_expected.to deliver_to key.user.email
@@ -150,6 +179,8 @@ describe Notify do
subject { Notify.new_email_email(email.id) }
+ it_behaves_like 'it should not have Gmail Actions links'
+
it 'is sent to the new user' do
is_expected.to deliver_to email.user.email
end
@@ -194,6 +225,7 @@ describe Notify do
it_behaves_like 'an assignee email'
it_behaves_like 'an email starting a new thread', 'issue'
+ it_behaves_like 'it should show Gmail Actions View Issue link'
it 'has the correct subject' do
is_expected.to have_subject /#{project.name} \| #{issue.title} \(##{issue.iid}\)/
@@ -207,6 +239,8 @@ describe Notify do
describe 'that are new with a description' do
subject { Notify.new_issue_email(issue_with_description.assignee_id, issue_with_description.id) }
+ it_behaves_like 'it should show Gmail Actions View Issue link'
+
it 'contains the description' do
is_expected.to have_body_text /#{issue_with_description.description}/
end
@@ -217,6 +251,7 @@ describe Notify do
it_behaves_like 'a multiple recipients email'
it_behaves_like 'an answer to an existing thread', 'issue'
+ it_behaves_like 'it should show Gmail Actions View Issue link'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -246,6 +281,7 @@ describe Notify do
subject { Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user) }
it_behaves_like 'an answer to an existing thread', 'issue'
+ it_behaves_like 'it should show Gmail Actions View Issue link'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -269,7 +305,6 @@ describe Notify do
is_expected.to have_body_text /#{namespace_project_issue_path project.namespace, project, issue}/
end
end
-
end
context 'for merge requests' do
@@ -282,6 +317,7 @@ describe Notify do
it_behaves_like 'an assignee email'
it_behaves_like 'an email starting a new thread', 'merge_request'
+ it_behaves_like 'it should show Gmail Actions View Merge request link'
it 'has the correct subject' do
is_expected.to have_subject /#{merge_request.title} \(##{merge_request.iid}\)/
@@ -307,6 +343,8 @@ describe Notify do
describe 'that are new with a description' do
subject { Notify.new_merge_request_email(merge_request_with_description.assignee_id, merge_request_with_description.id) }
+ it_behaves_like 'it should show Gmail Actions View Merge request link'
+
it 'contains the description' do
is_expected.to have_body_text /#{merge_request_with_description.description}/
end
@@ -317,6 +355,7 @@ describe Notify do
it_behaves_like 'a multiple recipients email'
it_behaves_like 'an answer to an existing thread', 'merge_request'
+ it_behaves_like 'it should show Gmail Actions View Merge request link'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -346,6 +385,7 @@ describe Notify do
subject { Notify.merge_request_status_email(recipient.id, merge_request.id, status, current_user) }
it_behaves_like 'an answer to an existing thread', 'merge_request'
+ it_behaves_like 'it should show Gmail Actions View Merge request link'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -375,6 +415,7 @@ describe Notify do
it_behaves_like 'a multiple recipients email'
it_behaves_like 'an answer to an existing thread', 'merge_request'
+ it_behaves_like 'it should show Gmail Actions View Merge request link'
it 'is sent as the merge author' do
sender = subject.header[:from].addrs[0]
@@ -403,6 +444,7 @@ describe Notify do
subject { Notify.project_was_moved_email(project.id, user.id, "gitlab/gitlab") }
it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'it should not have Gmail Actions links'
it 'has the correct subject' do
is_expected.to have_subject /Project was moved/
@@ -424,13 +466,16 @@ describe Notify do
subject { Notify.project_access_granted_email(project_member.id) }
it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'it should not have Gmail Actions links'
it 'has the correct subject' do
is_expected.to have_subject /Access to project was granted/
end
+
it 'contains name of project' do
is_expected.to have_body_text /#{project.name}/
end
+
it 'contains new user role' do
is_expected.to have_body_text /#{project_member.human_access}/
end
@@ -445,6 +490,8 @@ describe Notify do
end
shared_examples 'a note email' do
+ it_behaves_like 'it should have Gmail Actions links'
+
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
expect(sender.display_name).to eq(note_author.name)
@@ -469,6 +516,7 @@ describe Notify do
it_behaves_like 'a note email'
it_behaves_like 'an answer to an existing thread', 'commit'
+ it_behaves_like 'it should show Gmail Actions View Commit link'
it 'has the correct subject' do
is_expected.to have_subject /#{commit.title} \(#{commit.short_id}\)/
@@ -488,6 +536,7 @@ describe Notify do
it_behaves_like 'a note email'
it_behaves_like 'an answer to an existing thread', 'merge_request'
+ it_behaves_like 'it should show Gmail Actions View Merge request link'
it 'has the correct subject' do
is_expected.to have_subject /#{merge_request.title} \(##{merge_request.iid}\)/
@@ -507,6 +556,7 @@ describe Notify do
it_behaves_like 'a note email'
it_behaves_like 'an answer to an existing thread', 'issue'
+ it_behaves_like 'it should show Gmail Actions View Issue link'
it 'has the correct subject' do
is_expected.to have_subject /#{issue.title} \(##{issue.iid}\)/
@@ -527,6 +577,7 @@ describe Notify do
subject { Notify.group_access_granted_email(membership.id) }
it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'it should not have Gmail Actions links'
it 'has the correct subject' do
is_expected.to have_subject /Access to group was granted/
@@ -574,6 +625,8 @@ describe Notify do
subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :create) }
+ it_behaves_like 'it should not have Gmail Actions links'
+
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
expect(sender.display_name).to eq(user.name)
@@ -600,6 +653,8 @@ describe Notify do
subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/tags/v1.0', action: :create) }
+ it_behaves_like 'it should not have Gmail Actions links'
+
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
expect(sender.display_name).to eq(user.name)
@@ -625,6 +680,8 @@ describe Notify do
subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :delete) }
+ it_behaves_like 'it should not have Gmail Actions links'
+
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
expect(sender.display_name).to eq(user.name)
@@ -646,6 +703,8 @@ describe Notify do
subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/tags/v1.0', action: :delete) }
+ it_behaves_like 'it should not have Gmail Actions links'
+
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
expect(sender.display_name).to eq(user.name)
@@ -671,6 +730,8 @@ describe Notify do
subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare, reverse_compare: false, send_from_committer_email: send_from_committer_email) }
+ it_behaves_like 'it should not have Gmail Actions links'
+
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
expect(sender.display_name).to eq(user.name)
@@ -774,6 +835,8 @@ describe Notify do
subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare) }
+ it_behaves_like 'it should show Gmail Actions View Commit link'
+
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
expect(sender.display_name).to eq(user.name)
diff --git a/spec/models/concerns/strip_attribute_spec.rb b/spec/models/concerns/strip_attribute_spec.rb
new file mode 100644
index 00000000000..6445e29c3ef
--- /dev/null
+++ b/spec/models/concerns/strip_attribute_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe Milestone, "StripAttribute" do
+ let(:milestone) { create(:milestone) }
+
+ describe ".strip_attributes" do
+ it { expect(Milestone).to respond_to(:strip_attributes) }
+ it { expect(Milestone.strip_attrs).to include(:title) }
+ end
+
+ describe "#strip_attributes" do
+ before do
+ milestone.title = ' 8.3 '
+ milestone.valid?
+ end
+
+ it { expect(milestone.title).to eq('8.3') }
+ end
+
+end