summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG3
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock10
-rw-r--r--app/assets/javascripts/awards_handler.coffee170
-rw-r--r--app/assets/javascripts/labels_select.js.coffee40
-rw-r--r--app/assets/javascripts/notes.js.coffee8
-rw-r--r--app/assets/stylesheets/framework.scss1
-rw-r--r--app/assets/stylesheets/framework/modal.scss22
-rw-r--r--app/assets/stylesheets/pages/help.scss19
-rw-r--r--app/models/project.rb19
-rw-r--r--app/models/repository.rb10
-rw-r--r--app/services/git_push_service.rb9
-rw-r--r--app/views/profiles/emails/index.html.haml2
-rw-r--r--app/views/projects/diffs/_file.html.haml26
-rw-r--r--app/views/shared/_confirm_modal.html.haml2
-rw-r--r--app/views/votes/_votes_block.html.haml18
-rw-r--r--config/application.rb25
-rw-r--r--config/initializers/sentry.rb3
-rw-r--r--doc/monitoring/performance/gitlab_configuration.md2
-rw-r--r--doc/monitoring/performance/influxdb_configuration.md2
-rw-r--r--doc/monitoring/performance/influxdb_schema.md2
-rw-r--r--spec/features/dashboard/label_filter_spec.rb29
-rw-r--r--spec/models/project_spec.rb14
-rw-r--r--spec/models/repository_spec.rb10
-rw-r--r--spec/services/git_push_service_spec.rb30
25 files changed, 328 insertions, 150 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 9dcb1c1557e..121b03be320 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
Please view this file on the master branch, on stable branches it's out of date.
v 8.8.0 (unreleased)
+ - Project#open_branches has been cleaned up and no longer loads entire records into memory.
- Make build status canceled if any of the jobs was canceled and none failed
- Remove future dates from contribution calendar graph.
- Use ActionDispatch Remote IP for Akismet checking
@@ -13,6 +14,8 @@ v 8.8.0 (unreleased)
- Added button to toggle whitespaces changes on diff view
- Backport GitLab Enterprise support from EE
- Files over 5MB can only be viewed in their raw form, files over 1MB without highlighting !3718
+ - Add support for supressing text diffs using .gitattributes on the default branch (Matt Oakes)
+ - Added multiple colors for labels in dropdowns when dups happen.
v 8.7.2
- The "New Branch" button is now loaded asynchronously
diff --git a/Gemfile b/Gemfile
index 7882e467f8d..25c13fda575 100644
--- a/Gemfile
+++ b/Gemfile
@@ -19,7 +19,7 @@ gem "pg", '~> 0.18.2', group: :postgres
# Authentication libraries
gem 'devise', '~> 3.5.4'
-gem 'doorkeeper', '~> 2.2.0'
+gem 'doorkeeper', '~> 3.1'
gem 'omniauth', '~> 1.3.1'
gem 'omniauth-auth0', '~> 1.4.1'
gem 'omniauth-azure-oauth2', '~> 0.0.6'
diff --git a/Gemfile.lock b/Gemfile.lock
index 91d89b4875a..b1e954e0884 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -173,7 +173,7 @@ GEM
diff-lcs (1.2.5)
diffy (3.0.7)
docile (1.1.5)
- doorkeeper (2.2.2)
+ doorkeeper (3.1.0)
railties (>= 3.2)
dropzonejs-rails (0.7.2)
rails (> 3.1)
@@ -184,7 +184,7 @@ GEM
encryptor (1.3.0)
equalizer (0.0.11)
erubis (2.7.0)
- escape_utils (1.1.0)
+ escape_utils (1.1.1)
eventmachine (1.0.8)
excon (0.45.4)
execjs (2.6.0)
@@ -334,7 +334,7 @@ GEM
json
get_process_mem (0.2.0)
gherkin-ruby (0.3.2)
- github-linguist (4.7.5)
+ github-linguist (4.7.6)
charlock_holmes (~> 0.7.3)
escape_utils (~> 1.1.0)
mime-types (>= 1.19)
@@ -351,7 +351,7 @@ GEM
posix-spawn (~> 0.3)
gitlab_emoji (0.3.1)
gemojione (~> 2.2, >= 2.2.1)
- gitlab_git (10.0.0)
+ gitlab_git (10.0.1)
activesupport (~> 4.0)
charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0)
@@ -922,7 +922,7 @@ DEPENDENCIES
devise (~> 3.5.4)
devise-two-factor (~> 2.0.0)
diffy (~> 3.0.3)
- doorkeeper (~> 2.2.0)
+ doorkeeper (~> 3.1)
dropzonejs-rails (~> 0.7.1)
email_reply_parser (~> 0.5.8)
email_spec (~> 1.6.0)
diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee
index fcba9818726..bf95e06b4e5 100644
--- a/app/assets/javascripts/awards_handler.coffee
+++ b/app/assets/javascripts/awards_handler.coffee
@@ -1,58 +1,58 @@
class @AwardsHandler
- constructor: (@get_emojis_url, @post_emoji_url, @noteable_type, @noteable_id, @unicodes) ->
- $(".js-add-award").on "click", (event) =>
+ constructor: (@getEmojisUrl, @postEmojiUrl, @noteableType, @noteableId, @unicodes) ->
+ $('.js-add-award').on 'click', (event) =>
event.stopPropagation()
event.preventDefault()
@showEmojiMenu()
- $("html").on 'click', (event) ->
- if !$(event.target).closest(".emoji-menu").length
- if $(".emoji-menu").is(":visible")
- $(".emoji-menu").removeClass "is-visible"
+ $('html').on 'click', (event) ->
+ if !$(event.target).closest('.emoji-menu').length
+ if $('.emoji-menu').is(':visible')
+ $('.emoji-menu').removeClass 'is-visible'
- $(".awards")
- .off "click"
- .on "click", ".js-emoji-btn", @handleClick
+ $('.awards')
+ .off 'click'
+ .on 'click', '.js-emoji-btn', @handleClick
@renderFrequentlyUsedBlock()
handleClick: (e) ->
e.preventDefault()
emoji = $(this)
- .find(".icon")
- .data "emoji"
+ .find('.icon')
+ .data 'emoji'
- if emoji is "thumbsup" and awards_handler.didUserClickEmoji $(this), "thumbsdown"
- awards_handler.addAward "thumbsdown"
+ if emoji is 'thumbsup' and awardsHandler.didUserClickEmoji $(this), 'thumbsdown'
+ awardsHandler.addAward 'thumbsdown'
- else if emoji is "thumbsdown" and awards_handler.didUserClickEmoji $(this), "thumbsup"
- awards_handler.addAward "thumbsup"
+ else if emoji is 'thumbsdown' and awardsHandler.didUserClickEmoji $(this), 'thumbsup'
+ awardsHandler.addAward 'thumbsup'
- awards_handler.addAward emoji
+ awardsHandler.addAward emoji
$(this).trigger 'blur'
didUserClickEmoji: (that, emoji) ->
- if $(that).siblings("button:has([data-emoji=#{emoji}])").attr("data-original-title")
- $(that).siblings("button:has([data-emoji=#{emoji}])").attr("data-original-title").indexOf('me') > -1
+ if $(that).siblings("button:has([data-emoji=#{emoji}])").attr('data-original-title')
+ $(that).siblings("button:has([data-emoji=#{emoji}])").attr('data-original-title').indexOf('me') > -1
showEmojiMenu: ->
- if $(".emoji-menu").length
- if $(".emoji-menu").is ".is-visible"
- $(".emoji-menu").removeClass "is-visible"
- $("#emoji_search").blur()
+ if $('.emoji-menu').length
+ if $('.emoji-menu').is '.is-visible'
+ $('.emoji-menu').removeClass 'is-visible'
+ $('#emoji_search').blur()
else
- $(".emoji-menu").addClass "is-visible"
- $("#emoji_search").focus()
+ $('.emoji-menu').addClass 'is-visible'
+ $('#emoji_search').focus()
else
- $('.js-add-award').addClass "is-loading"
- $.get @get_emojis_url, (response) =>
- $('.js-add-award').removeClass "is-loading"
- $(".js-award-holder").append response
+ $('.js-add-award').addClass 'is-loading'
+ $.get @getEmojisUrl, (response) =>
+ $('.js-add-award').removeClass 'is-loading'
+ $('.js-award-holder').append response
setTimeout =>
- $(".emoji-menu").addClass "is-visible"
- $("#emoji_search").focus()
+ $('.emoji-menu').addClass 'is-visible'
+ $('#emoji_search').focus()
@setupSearch()
, 200
@@ -60,7 +60,7 @@ class @AwardsHandler
@postEmoji emoji, =>
@addAwardToEmojiBar(emoji)
- $(".emoji-menu").removeClass "is-visible"
+ $('.emoji-menu').removeClass 'is-visible'
addAwardToEmojiBar: (emoji) ->
@addEmojiToFrequentlyUsedList(emoji)
@@ -69,9 +69,9 @@ class @AwardsHandler
if @isActive(emoji)
@decrementCounter(emoji)
else
- counter = @findEmojiIcon(emoji).siblings(".js-counter")
+ counter = @findEmojiIcon(emoji).siblings('.js-counter')
counter.text(parseInt(counter.text()) + 1)
- counter.parent().addClass("active")
+ counter.parent().addClass('active')
@addMeToAuthorList(emoji)
else
@createEmoji(emoji)
@@ -80,47 +80,47 @@ class @AwardsHandler
@findEmojiIcon(emoji).length > 0
isActive: (emoji) ->
- @findEmojiIcon(emoji).parent().hasClass("active")
+ @findEmojiIcon(emoji).parent().hasClass('active')
decrementCounter: (emoji) ->
- counter = @findEmojiIcon(emoji).siblings(".js-counter")
+ counter = @findEmojiIcon(emoji).siblings('.js-counter')
emojiIcon = counter.parent()
if parseInt(counter.text()) > 1
counter.text(parseInt(counter.text()) - 1)
- emojiIcon.removeClass("active")
+ emojiIcon.removeClass('active')
@removeMeFromAuthorList(emoji)
- else if emoji == "thumbsup" || emoji == "thumbsdown"
- emojiIcon.tooltip("destroy")
+ else if emoji == 'thumbsup' || emoji == 'thumbsdown'
+ emojiIcon.tooltip('destroy')
counter.text(0)
- emojiIcon.removeClass("active")
+ emojiIcon.removeClass('active')
@removeMeFromAuthorList(emoji)
else
- emojiIcon.tooltip("destroy")
+ emojiIcon.tooltip('destroy')
emojiIcon.remove()
removeMeFromAuthorList: (emoji) ->
- award_block = @findEmojiIcon(emoji).parent()
- authors = award_block
- .attr("data-original-title")
- .split(", ")
- authors.splice(authors.indexOf("me"),1)
- award_block
- .closest(".js-emoji-btn")
- .attr("data-original-title", authors.join(", "))
- @resetTooltip(award_block)
+ awardBlock = @findEmojiIcon(emoji).parent()
+ authors = awardBlock
+ .attr('data-original-title')
+ .split(', ')
+ authors.splice(authors.indexOf('me'),1)
+ awardBlock
+ .closest('.js-emoji-btn')
+ .attr('data-original-title', authors.join(', '))
+ @resetTooltip(awardBlock)
addMeToAuthorList: (emoji) ->
- award_block = @findEmojiIcon(emoji).parent()
- origTitle = award_block.attr("data-original-title").trim()
+ awardBlock = @findEmojiIcon(emoji).parent()
+ origTitle = awardBlock.attr('data-original-title').trim()
authors = []
if origTitle
authors = origTitle.split(', ')
- authors.push("me")
- award_block.attr("data-original-title", authors.join(", "))
- @resetTooltip(award_block)
+ authors.push('me')
+ awardBlock.attr('data-original-title', authors.join(', '))
+ @resetTooltip(awardBlock)
resetTooltip: (award) ->
- award.tooltip("destroy")
+ award.tooltip('destroy')
# "destroy" call is asynchronous and there is no appropriate callback on it, this is why we need to set timeout.
setTimeout (->
@@ -139,20 +139,28 @@ class @AwardsHandler
"</button>"
)
- emoji_node = $(nodes.join("\n"))
- .insertBefore(".js-award-holder")
- .find(".emoji-icon")
- .data("emoji", emoji)
+ $(nodes.join("\n"))
+ .insertBefore('.js-award-holder')
+ .find('.emoji-icon')
+ .data('emoji', emoji)
$('.award-control').tooltip()
resolveNameToCssClass: (emoji) ->
- "emoji-#{@unicodes[emoji]}"
+ emojiIcon = $(".emoji-menu-content [data-emoji='#{emoji}']")
+
+ if emojiIcon.length > 0
+ unicodeName = emojiIcon.data('unicode-name')
+ else
+ # Find by alias
+ unicodeName = $(".emoji-menu-content [data-aliases*=':#{emoji}:']").data('unicode-name')
+
+ "emoji-#{unicodeName}"
postEmoji: (emoji, callback) ->
- $.post @post_emoji_url, { note: {
+ $.post @postEmojiUrl, { note: {
note: ":#{emoji}:"
- noteable_type: @noteable_type
- noteable_id: @noteable_id
+ noteable_type: @noteableType
+ noteable_id: @noteableId
}},(data) ->
if data.ok
callback.call()
@@ -166,42 +174,42 @@ class @AwardsHandler
}, 200)
addEmojiToFrequentlyUsedList: (emoji) ->
- frequently_used_emojis = @getFrequentlyUsedEmojis()
- frequently_used_emojis.push(emoji)
- $.cookie('frequently_used_emojis', frequently_used_emojis.join(","), { expires: 365 })
+ frequentlyUsedEmojis = @getFrequentlyUsedEmojis()
+ frequentlyUsedEmojis.push(emoji)
+ $.cookie('frequently_used_emojis', frequentlyUsedEmojis.join(','), { expires: 365 })
getFrequentlyUsedEmojis: ->
- frequently_used_emojis = ($.cookie('frequently_used_emojis') || "").split(",")
- _.compact(_.uniq(frequently_used_emojis))
+ frequentlyUsedEmojis = ($.cookie('frequently_used_emojis') || '').split(',')
+ _.compact(_.uniq(frequentlyUsedEmojis))
renderFrequentlyUsedBlock: ->
if $.cookie('frequently_used_emojis')
- frequently_used_emojis = @getFrequentlyUsedEmojis()
+ frequentlyUsedEmojis = @getFrequentlyUsedEmojis()
- ul = $("<ul>")
+ ul = $('<ul>')
- for emoji in frequently_used_emojis
+ for emoji in frequentlyUsedEmojis
do (emoji) ->
- $(".emoji-menu-content [data-emoji='#{emoji}']").closest("li").clone().appendTo(ul)
+ $(".emoji-menu-content [data-emoji='#{emoji}']").closest('li').clone().appendTo(ul)
- $("input.emoji-search").after(ul).after($("<h5>").text("Frequently used"))
+ $('input.emoji-search').after(ul).after($('<h5>').text('Frequently used'))
setupSearch: ->
- $("input.emoji-search").keyup (ev) =>
+ $('input.emoji-search').keyup (ev) =>
term = $(ev.target).val()
# Clean previous search results
- $("ul.emoji-menu-search, h5.emoji-search").remove()
+ $('ul.emoji-menu-search, h5.emoji-search').remove()
if term
# Generate a search result block
- h5 = $("<h5>").text("Search results").addClass("emoji-search")
- found_emojis = @searchEmojis(term).show()
- ul = $("<ul>").addClass("emoji-menu-list emoji-menu-search").append(found_emojis)
- $(".emoji-menu-content ul, .emoji-menu-content h5").hide()
- $(".emoji-menu-content").append(h5).append(ul)
+ h5 = $('<h5>').text('Search results').addClass('emoji-search')
+ foundEmojis = @searchEmojis(term).show()
+ ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(foundEmojis)
+ $('.emoji-menu-content ul, .emoji-menu-content h5').hide()
+ $('.emoji-menu-content').append(h5).append(ul)
else
- $(".emoji-menu-content").children().show()
+ $('.emoji-menu-content').children().show()
searchEmojis: (term)->
$(".emoji-menu-content [data-emoji*='#{term}']").closest("li").clone()
diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee
index 7d61cd9bf73..995fd768603 100644
--- a/app/assets/javascripts/labels_select.js.coffee
+++ b/app/assets/javascripts/labels_select.js.coffee
@@ -163,6 +163,21 @@ class @LabelsSelect
$.ajax(
url: labelUrl
).done (data) ->
+ data = _.chain data
+ .groupBy (label) ->
+ label.title
+ .map (label) ->
+ color = _.map label, (dup) ->
+ dup.color
+
+ return {
+ id: label[0].id
+ title: label[0].title
+ color: color
+ duplicate: color.length > 1
+ }
+ .value()
+
if $dropdown.hasClass 'js-extra-options'
if showNo
data.unshift(
@@ -178,6 +193,7 @@ class @LabelsSelect
if data.length > 2
data.splice 2, 0, 'divider'
+
callback data
renderRow: (label) ->
@@ -192,11 +208,31 @@ class @LabelsSelect
if $dropdown.hasClass('js-multiselect') and removesAll
selectedClass.push 'dropdown-clear-active'
- color = if label.color? then "<span class='dropdown-label-box' style='background-color: #{label.color}'></span>" else ""
+ if label.duplicate
+ spacing = 100 / label.color.length
+
+ # Reduce the colors to 4
+ label.color = label.color.filter (color, i) ->
+ i < 4
+
+ color = _.map(label.color, (color, i) ->
+ percentFirst = Math.floor(spacing * i)
+ percentSecond = Math.floor(spacing * (i + 1))
+ "#{color} #{percentFirst}%,#{color} #{percentSecond}% "
+ ).join(',')
+ color = "linear-gradient(#{color})"
+ else
+ if label.color?
+ color = label.color[0]
+
+ if color
+ colorEl = "<span class='dropdown-label-box' style='background: #{color}'></span>"
+ else
+ colorEl = ''
"<li>
<a href='#' class='#{selectedClass.join(' ')}'>
- #{color}
+ #{colorEl}
#{_.escape(label.title)}
</a>
</li>"
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index 82e210fed7d..efb3e8e2198 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -167,8 +167,8 @@ class @Notes
return
if note.award
- awards_handler.addAwardToEmojiBar(note.note)
- awards_handler.scrollToAwards()
+ awardsHandler.addAwardToEmojiBar(note.note)
+ awardsHandler.scrollToAwards()
# render note if it not present in loaded list
# or skip if rendered
@@ -373,11 +373,11 @@ class @Notes
new GLForm form
if scrollTo? and myLastNote?
- # scroll to the bottom
+ # scroll to the bottom
# so the open of the last element doesn't make a jump
$('html, body').scrollTop($(document).height());
$('html, body').animate({
- scrollTop: myLastNote.offset().top - 150
+ scrollTop: myLastNote.offset().top - 150
}, 500, ->
$noteText = form.find(".js-note-text")
$noteText.focus()
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index c85ab9148d0..560de9fc0bd 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -25,6 +25,7 @@
@import "framework/lists.scss";
@import "framework/markdown_area.scss";
@import "framework/mobile.scss";
+@import "framework/modal.scss";
@import "framework/nav.scss";
@import "framework/pagination.scss";
@import "framework/progress.scss";
diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss
new file mode 100644
index 00000000000..26ad2870aa0
--- /dev/null
+++ b/app/assets/stylesheets/framework/modal.scss
@@ -0,0 +1,22 @@
+.modal-body {
+ position: relative;
+ overflow-y: auto;
+ padding: 15px;
+
+ .form-actions {
+ margin: -$gl-padding+1;
+ margin-top: 15px;
+ }
+
+ .text-danger {
+ font-weight: bold;
+ }
+}
+
+body.modal-open {
+ overflow: hidden;
+}
+
+.modal .modal-dialog {
+ width: 860px;
+}
diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss
index ee95bdf488e..4a95b7b852e 100644
--- a/app/assets/stylesheets/pages/help.scss
+++ b/app/assets/stylesheets/pages/help.scss
@@ -55,25 +55,6 @@
}
}
-.modal-body {
- position: relative;
- overflow-y: auto;
- padding: 15px;
-
- .form-actions {
- margin: -$gl-padding+1;
- margin-top: 15px;
- }
-}
-
-body.modal-open {
- overflow: hidden;
-}
-
-.modal .modal-dialog {
- width: 860px;
-}
-
.documentation {
padding: 7px;
}
diff --git a/app/models/project.rb b/app/models/project.rb
index 0420c6a61ae..af62e8ecd90 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -735,19 +735,17 @@ class Project < ActiveRecord::Base
end
def open_branches
- all_branches = repository.branches
+ # We're using a Set here as checking values in a large Set is faster than
+ # checking values in a large Array.
+ protected_set = Set.new(protected_branch_names)
- if protected_branches.present?
- all_branches.reject! do |branch|
- protected_branches_names.include?(branch.name)
- end
+ repository.branches.reject do |branch|
+ protected_set.include?(branch.name)
end
-
- all_branches
end
- def protected_branches_names
- @protected_branches_names ||= protected_branches.map(&:name)
+ def protected_branch_names
+ @protected_branch_names ||= protected_branches.pluck(:name)
end
def root_ref?(branch)
@@ -764,7 +762,7 @@ class Project < ActiveRecord::Base
# Check if current branch name is marked as protected in the system
def protected_branch?(branch_name)
- protected_branches_names.include?(branch_name)
+ protected_branches.where(name: branch_name).any?
end
def developers_can_push_to_protected_branch?(branch_name)
@@ -901,6 +899,7 @@ class Project < ActiveRecord::Base
repository.rugged.references.create('HEAD',
"refs/heads/#{branch}",
force: true)
+ repository.copy_gitattributes(branch)
reload_default_branch
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index d495c8d18f5..b4319297e43 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -938,6 +938,16 @@ class Repository
raw_repository.ls_files(actual_ref)
end
+ def copy_gitattributes(ref)
+ actual_ref = ref || root_ref
+ begin
+ raw_repository.copy_gitattributes(actual_ref)
+ true
+ rescue Gitlab::Git::Repository::InvalidRef
+ false
+ end
+ end
+
def main_language
return if empty? || rugged.head_unborn?
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index 1e1be8cd04b..b7af80055bf 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -42,7 +42,12 @@ class GitPushService < BaseService
# Collect data for this git push
@push_commits = @project.repository.commits_between(params[:oldrev], params[:newrev])
process_commit_messages
+
+ # Update the bare repositories info/attributes file using the contents of the default branches
+ # .gitattributes file
+ update_gitattributes if is_default_branch?
end
+
# Update merge requests that may be affected by this push. A new branch
# could cause the last commit of a merge request to change.
update_merge_requests
@@ -54,6 +59,10 @@ class GitPushService < BaseService
perform_housekeeping
end
+ def update_gitattributes
+ @project.repository.copy_gitattributes(params[:ref])
+ end
+
def update_main_language
# Performance can be bad so for now only check main_language once
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/14937
diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml
index 57527361eb6..6f7fefdb46d 100644
--- a/app/views/profiles/emails/index.html.haml
+++ b/app/views/profiles/emails/index.html.haml
@@ -45,4 +45,4 @@
%span.label.label-info Public Email
- if email.email === current_user.notification_email
%span.label.label-info Notification Email
- = link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-remove pull-right'
+ = link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-warning prepend-left-10'
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index 83a8d7ae9bf..0f04fc5d33c 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -40,19 +40,19 @@
= view_file_btn(diff_commit.id, diff_file, project)
.diff-content.diff-wrap-lines
- -# Skipp all non non-supported blobs
+ - # Skip all non non-supported blobs
- return unless blob.respond_to?('text?')
- if diff_file.too_large?
- .nothing-here-block
- This diff could not be displayed because it is too large.
- - else
- - if blob_text_viewable?(blob)
- - if diff_view == 'parallel'
- = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i
- - else
- = render "projects/diffs/text_file", diff_file: diff_file, index: i
- - elsif blob.image?
- - old_file = project.repository.prev_blob_for_diff(diff_commit, diff_file)
- = render "projects/diffs/image", diff_file: diff_file, old_file: old_file, file: blob, index: i, diff_refs: diff_refs
+ .nothing-here-block This diff could not be displayed because it is too large.
+ - elsif blob_text_viewable?(blob) && !project.repository.diffable?(blob)
+ .nothing-here-block This diff was suppressed by a .gitattributes entry.
+ - elsif blob_text_viewable?(blob)
+ - if diff_view == 'parallel'
+ = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i
- else
- .nothing-here-block No preview for this file type
+ = render "projects/diffs/text_file", diff_file: diff_file, index: i
+ - elsif blob.image?
+ - old_file = project.repository.prev_blob_for_diff(diff_commit, diff_file)
+ = render "projects/diffs/image", diff_file: diff_file, old_file: old_file, file: blob, index: i, diff_refs: diff_refs
+ - else
+ .nothing-here-block No preview for this file type
diff --git a/app/views/shared/_confirm_modal.html.haml b/app/views/shared/_confirm_modal.html.haml
index 34241cd8aad..b0fc60573f7 100644
--- a/app/views/shared/_confirm_modal.html.haml
+++ b/app/views/shared/_confirm_modal.html.haml
@@ -7,7 +7,7 @@
Confirmation required
.modal-body
- %p.cred.lead.js-confirm-text
+ %p.text-danger.js-confirm-text
%p
This action can lead to data loss.
diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml
index 59e12798691..4beb8746444 100644
--- a/app/views/votes/_votes_block.html.haml
+++ b/app/views/votes/_votes_block.html.haml
@@ -15,16 +15,16 @@
- if current_user
:javascript
- var get_emojis_url = "#{emojis_path}";
- var post_emoji_url = "#{award_toggle_namespace_project_notes_path(@project.namespace, @project)}";
- var noteable_type = "#{votable.class.name.underscore}";
- var noteable_id = "#{votable.id}";
+ var getEmojisUrl = "#{emojis_path}";
+ var postEmojiUrl = "#{award_toggle_namespace_project_notes_path(@project.namespace, @project)}";
+ var noteableType = "#{votable.class.name.underscore}";
+ var noteableId = "#{votable.id}";
var unicodes = #{AwardEmoji.unicode.to_json};
- window.awards_handler = new AwardsHandler(
- get_emojis_url,
- post_emoji_url,
- noteable_type,
- noteable_id,
+ window.awardsHandler = new AwardsHandler(
+ getEmojisUrl,
+ postEmojiUrl,
+ noteableType,
+ noteableId,
unicodes
);
diff --git a/config/application.rb b/config/application.rb
index 2e2ed48db07..b602e2b6168 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -32,7 +32,30 @@ module Gitlab
config.encoding = "utf-8"
# Configure sensitive parameters which will be filtered from the log file.
- config.filter_parameters.push(:password, :password_confirmation, :private_token, :otp_attempt, :variables, :import_url)
+ #
+ # Parameters filtered:
+ # - Password (:password, :password_confirmation)
+ # - Private tokens (:private_token)
+ # - Two-factor tokens (:otp_attempt)
+ # - Repo/Project Import URLs (:import_url)
+ # - Build variables (:variables)
+ # - GitLab Pages SSL cert/key info (:certificate, :encrypted_key)
+ # - Webhook URLs (:hook)
+ # - Sentry DSN (:sentry_dsn)
+ # - Deploy keys (:key)
+ config.filter_parameters += %i(
+ certificate
+ encrypted_key
+ hook
+ import_url
+ key
+ otp_attempt
+ password
+ password_confirmation
+ private_token
+ sentry_dsn
+ variables
+ )
# Enable escaping HTML in JSON.
config.active_support.escape_html_entities_in_json = true
diff --git a/config/initializers/sentry.rb b/config/initializers/sentry.rb
index e87899b2d5c..74fef7cadfe 100644
--- a/config/initializers/sentry.rb
+++ b/config/initializers/sentry.rb
@@ -15,6 +15,9 @@ if Rails.env.production?
Raven.configure do |config|
config.dsn = current_application_settings.sentry_dsn
config.release = Gitlab::REVISION
+
+ # Sanitize fields based on those sanitized from Rails.
+ config.sanitize_fields = Rails.application.config.filter_parameters.map(&:to_s)
end
end
end
diff --git a/doc/monitoring/performance/gitlab_configuration.md b/doc/monitoring/performance/gitlab_configuration.md
index 90e99302210..771584268d9 100644
--- a/doc/monitoring/performance/gitlab_configuration.md
+++ b/doc/monitoring/performance/gitlab_configuration.md
@@ -37,4 +37,4 @@ Read more on:
- [Introduction to GitLab Performance Monitoring](introduction.md)
- [InfluxDB Configuration](influxdb_configuration.md)
- [InfluxDB Schema](influxdb_schema.md)
-- [Grafana Install/Configuration](grafana_configuration.md
+- [Grafana Install/Configuration](grafana_configuration.md)
diff --git a/doc/monitoring/performance/influxdb_configuration.md b/doc/monitoring/performance/influxdb_configuration.md
index 63aa03985ef..c30cd2950d8 100644
--- a/doc/monitoring/performance/influxdb_configuration.md
+++ b/doc/monitoring/performance/influxdb_configuration.md
@@ -181,7 +181,7 @@ Read more on:
- [Introduction to GitLab Performance Monitoring](introduction.md)
- [GitLab Configuration](gitlab_configuration.md)
- [InfluxDB Schema](influxdb_schema.md)
-- [Grafana Install/Configuration](grafana_configuration.md
+- [Grafana Install/Configuration](grafana_configuration.md)
[influxdb-retention]: https://docs.influxdata.com/influxdb/v0.9/query_language/database_management/#retention-policy-management
[influxdb documentation]: https://docs.influxdata.com/influxdb/v0.9/
diff --git a/doc/monitoring/performance/influxdb_schema.md b/doc/monitoring/performance/influxdb_schema.md
index d31b3788f36..41861860b6d 100644
--- a/doc/monitoring/performance/influxdb_schema.md
+++ b/doc/monitoring/performance/influxdb_schema.md
@@ -85,4 +85,4 @@ Read more on:
- [Introduction to GitLab Performance Monitoring](introduction.md)
- [GitLab Configuration](gitlab_configuration.md)
- [InfluxDB Configuration](influxdb_configuration.md)
-- [Grafana Install/Configuration](grafana_configuration.md
+- [Grafana Install/Configuration](grafana_configuration.md)
diff --git a/spec/features/dashboard/label_filter_spec.rb b/spec/features/dashboard/label_filter_spec.rb
new file mode 100644
index 00000000000..24e83d44010
--- /dev/null
+++ b/spec/features/dashboard/label_filter_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+describe 'Dashboard > label filter', feature: true, js: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, name: 'test', namespace: user.namespace) }
+ let(:project2) { create(:project, name: 'test2', path: 'test2', namespace: user.namespace) }
+ let(:label) { create(:label, title: 'bug', color: '#ff0000') }
+ let(:label2) { create(:label, title: 'bug') }
+
+ before do
+ project.labels << label
+ project2.labels << label2
+
+ login_as(user)
+ visit issues_dashboard_path
+ end
+
+ context 'duplicate labels' do
+ it 'should remove duplicate labels' do
+ page.within('.labels-filter') do
+ click_button 'Label'
+ end
+
+ page.within('.dropdown-menu-labels') do
+ expect(page).to have_selector('.dropdown-content a', text: 'bug', count: 1)
+ end
+ end
+ end
+end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index e33c7d62ff4..5b1cf71337e 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -798,4 +798,18 @@ describe Project, models: true do
end
end
end
+
+ describe '#protected_branch?' do
+ let(:project) { create(:empty_project) }
+
+ it 'returns true when a branch is a protected branch' do
+ project.protected_branches.create!(name: 'foo')
+
+ expect(project.protected_branch?('foo')).to eq(true)
+ end
+
+ it 'returns false when a branch is not a protected branch' do
+ expect(project.protected_branch?('foo')).to eq(false)
+ end
+ end
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index a306cc4aef8..397bb5a8028 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -795,6 +795,16 @@ describe Repository, models: true do
end
+ describe "#copy_gitattributes" do
+ it 'returns true with a valid ref' do
+ expect(repository.copy_gitattributes('master')).to be_truthy
+ end
+
+ it 'returns false with an invalid ref' do
+ expect(repository.copy_gitattributes('invalid')).to be_falsey
+ end
+ end
+
describe "#main_language" do
it 'shows the main language of the project' do
expect(repository.main_language).to eq("Ruby")
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index b40a5c1c818..eeab540c2fd 100644
--- a/spec/services/git_push_service_spec.rb
+++ b/spec/services/git_push_service_spec.rb
@@ -201,6 +201,36 @@ describe GitPushService, services: true do
end
+ describe "Updates git attributes" do
+ context "for default branch" do
+ it "calls the copy attributes method for the first push to the default branch" do
+ expect(project.repository).to receive(:copy_gitattributes).with('master')
+
+ execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master')
+ end
+
+ it "calls the copy attributes method for changes to the default branch" do
+ expect(project.repository).to receive(:copy_gitattributes).with('refs/heads/master')
+
+ execute_service(project, user, 'oldrev', 'newrev', 'refs/heads/master')
+ end
+ end
+
+ context "for non-default branch" do
+ before do
+ # Make sure the "default" branch is different
+ allow(project).to receive(:default_branch).and_return('not-master')
+ end
+
+ it "does not call copy attributes method" do
+ expect(project.repository).not_to receive(:copy_gitattributes)
+
+ execute_service(project, user, @oldrev, @newrev, @ref)
+ end
+ end
+ end
+
+
describe "Webhooks" do
context "execute webhooks" do
it "when pushing a branch for the first time" do