summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml3
-rw-r--r--.rubocop.yml43
-rw-r--r--CHANGELOG22
-rw-r--r--Gemfile10
-rw-r--r--Gemfile.lock24
-rw-r--r--README.md2
-rw-r--r--app/assets/javascripts/awards_handler.coffee7
-rw-r--r--app/assets/javascripts/flash.js.coffee4
-rw-r--r--app/assets/javascripts/issuable_context.js.coffee12
-rw-r--r--app/assets/javascripts/merge_request_tabs.js.coffee9
-rw-r--r--app/assets/javascripts/notes.js.coffee38
-rw-r--r--app/assets/javascripts/project.js.coffee7
-rw-r--r--app/assets/stylesheets/framework/common.scss6
-rw-r--r--app/assets/stylesheets/framework/flash.scss10
-rw-r--r--app/assets/stylesheets/framework/selects.scss2
-rw-r--r--app/assets/stylesheets/pages/awards.scss86
-rw-r--r--app/assets/stylesheets/pages/issuable.scss158
-rw-r--r--app/assets/stylesheets/pages/issues.scss20
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss4
-rw-r--r--app/controllers/projects/issues_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb2
-rw-r--r--app/helpers/ci_status_helper.rb2
-rw-r--r--app/mailers/emails/projects.rb88
-rw-r--r--app/mailers/notify.rb4
-rw-r--r--app/models/issue.rb8
-rw-r--r--app/models/note.rb5
-rw-r--r--app/models/repository.rb20
-rw-r--r--app/services/projects/create_service.rb6
-rw-r--r--app/views/notify/_note_message.html.haml2
-rw-r--r--app/views/notify/repository_push_email.html.haml28
-rw-r--r--app/views/notify/repository_push_email.text.haml24
-rw-r--r--app/views/projects/issues/_discussion.html.haml22
-rw-r--r--app/views/projects/issues/_merge_requests.html.haml25
-rw-r--r--app/views/projects/issues/show.html.haml45
-rw-r--r--app/views/projects/issues/update.js.haml4
-rw-r--r--app/views/projects/merge_requests/_discussion.html.haml23
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml1
-rw-r--r--app/views/projects/merge_requests/_show.html.haml126
-rw-r--r--app/views/projects/merge_requests/show/_mr_title.html.haml4
-rw-r--r--app/views/projects/merge_requests/update.js.haml4
-rw-r--r--app/views/projects/merge_requests/widget/_merged.html.haml2
-rw-r--r--app/views/projects/runners/_shared_runners.html.haml4
-rw-r--r--app/views/shared/issuable/_context.html.haml57
-rw-r--r--app/views/shared/issuable/_participants.html.haml8
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml83
-rw-r--r--app/views/votes/_votes_block.html.haml3
-rw-r--r--config/initializers/sidekiq.rb8
-rw-r--r--doc/api/settings.md2
-rw-r--r--doc/ci/yaml/README.md3
-rw-r--r--doc/development/db_dump.md2
-rw-r--r--doc/install/installation.md22
-rw-r--r--doc/integration/saml.md5
-rw-r--r--doc/raketasks/backup_restore.md4
-rw-r--r--doc/release/monthly.md2
-rw-r--r--doc/security/README.md3
-rw-r--r--doc/security/user_file_uploads.md11
-rw-r--r--doc/update/8.1-to-8.2.md2
-rw-r--r--doc/web_hooks/web_hooks.md4
-rw-r--r--features/project/merge_requests.feature20
-rw-r--r--features/steps/project/merge_requests.rb44
-rw-r--r--lib/award_emoji.rb35
-rw-r--r--lib/ci/gitlab_ci_yaml_processor.rb66
-rw-r--r--lib/gitlab/email/message/repository_push.rb137
-rw-r--r--lib/gitlab/o_auth/auth_hash.rb2
-rw-r--r--lib/tasks/gitlab/check.rake2
-rw-r--r--spec/features/atom/users_spec.rb2
-rw-r--r--spec/features/issues_spec.rb29
-rw-r--r--spec/lib/ci/gitlab_ci_yaml_processor_spec.rb6
-rw-r--r--spec/lib/gitlab/email/message/repository_push_spec.rb122
-rw-r--r--spec/lib/gitlab/o_auth/auth_hash_spec.rb4
-rw-r--r--spec/models/note_spec.rb26
-rw-r--r--spec/models/repository_spec.rb21
-rw-r--r--spec/services/projects/create_service_spec.rb7
-rw-r--r--spec/support/mentionable_shared_examples.rb6
74 files changed, 1136 insertions, 530 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index c614e14e243..a8da3de83f8 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -117,9 +117,10 @@ flay:
- mysql
bundler:audit:
- script:
+ script:
- "bundle exec bundle-audit update"
- "bundle exec bundle-audit check"
tags:
- ruby
- mysql
+ allow_failure: true
diff --git a/.rubocop.yml b/.rubocop.yml
index d59edbc8b17..b4ca11c8343 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -735,23 +735,39 @@ Metrics/AbcSize:
Description: >-
A calculated magnitude based on number of assignments,
branches, and conditions.
- Enabled: false
+ Enabled: true
+ Max: 70
+
+Metrics/CyclomaticComplexity:
+ Description: >-
+ A complexity metric that is strongly correlated to the number
+ of test cases needed to validate a method.
+ Enabled: true
+ Max: 17
+
+Metrics/PerceivedComplexity:
+ Description: >-
+ A complexity metric geared towards measuring complexity for a
+ human reader.
+ Enabled: true
+ Max: 17
+
+Metrics/ParameterLists:
+ Description: 'Avoid parameter lists longer than three or four parameters.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#too-many-params'
+ Enabled: true
+ Max: 8
Metrics/BlockNesting:
Description: 'Avoid excessive block nesting'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#three-is-the-number-thou-shalt-count'
- Enabled: false
+ Enabled: true
+ Max: 4
Metrics/ClassLength:
Description: 'Avoid classes longer than 100 lines of code.'
Enabled: false
-Metrics/CyclomaticComplexity:
- Description: >-
- A complexity metric that is strongly correlated to the number
- of test cases needed to validate a method.
- Enabled: false
-
Metrics/LineLength:
Description: 'Limit lines to 80 characters.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#80-character-limits'
@@ -762,17 +778,6 @@ Metrics/MethodLength:
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#short-methods'
Enabled: false
-Metrics/ParameterLists:
- Description: 'Avoid parameter lists longer than three or four parameters.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#too-many-params'
- Enabled: false
-
-Metrics/PerceivedComplexity:
- Description: >-
- A complexity metric geared towards measuring complexity for a
- human reader.
- Enabled: false
-
#################### Lint ################################
### Warnings
diff --git a/CHANGELOG b/CHANGELOG
index 2f94eb6c66c..365b9e973fc 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,12 +1,17 @@
Please view this file on the master branch, on stable branches it's out of date.
v 8.3.0 (unreleased)
+ - Expand character set of usernames created by Omniauth (Corey Hinshaw)
+ - Add button to automatically merge a merge request when the build succeeds (Zeger-Jan van de Weg)
- Merge when build succeeds (Zeger-Jan van de Weg)
+ - Provide better diagnostic message upon project creation errors (Stan Hu)
+ - Bump devise to 3.5.3 to fix reset token expiring after account creation (Stan Hu)
- Bump gollum-lib to 4.1.0 (Stan Hu)
- Fix broken group avatar upload under "New group" (Stan Hu)
- Update project repositorize size and commit count during import:repos task (Stan Hu)
- Fix API setting of 'public' attribute to false will make a project private (Stan Hu)
- Handle and report SSL errors in Web hook test (Stan Hu)
+ - Bump Redis requirement to 2.8 for Sidekiq 4 (Stan Hu)
- Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera)
- Add rake tasks for git repository maintainance (Zeger-Jan van de Weg)
- Fix 500 error when update group member permission
@@ -26,9 +31,26 @@ v 8.3.0 (unreleased)
- Add languages page to graphs
- Block LDAP user when they are no longer found in the LDAP server
- Improve wording on project visibility levels (Zeger-Jan van de Weg)
+ - Fix editing notes on a merge request diff
- Automatically select default clone protocol based on user preferences (Eirik Lygre)
- Make Network page as sub tab of Commits
- Add copy-to-clipboard button for Snippets
+ - Add indication to merge request list item that MR cannot be merged automatically
+ - Default target branch to patch-n when editing file in protected branch
+ - Add Builds tab to merge request detail page
+ - Allow milestones, issues and MRs to be created from dashboard and group indexes
+ - Use new style for wiki
+ - Use new style for milestone detail page
+ - Fix sidebar tooltips when collapsed
+ - Prevent possible XSS attack with award-emoji
+ - Upgraded Sidekiq to 4.x
+ - Accept COPYING,COPYING.lesser, and licence as license file (Zeger-Jan van de Weg)
+ - Fix emoji aliases problem
+ - Fix award-emojis Flash alert's width
+ - Fix deleting notes on a merge request diff
+ - Display referenced merge request statuses in the issue description (Greg Smethells)
+ - Implement new sidebar for issue and merge request pages
+ - Emoji picker improvements
v 8.2.3
- Fix application settings cache not expiring after changes (Stan Hu)
diff --git a/Gemfile b/Gemfile
index 473770f53e5..7298e21ce66 100644
--- a/Gemfile
+++ b/Gemfile
@@ -18,7 +18,7 @@ gem "mysql2", '~> 0.3.16', group: :mysql
gem "pg", '~> 0.18.2', group: :postgres
# Authentication libraries
-gem 'devise', '~> 3.5.2'
+gem 'devise', '~> 3.5.3'
gem 'devise-async', '~> 0.9.0'
gem 'doorkeeper', '~> 2.2.0'
gem 'omniauth', '~> 1.2.2'
@@ -93,6 +93,7 @@ gem 'html-pipeline', '~> 1.11.0'
gem 'task_list', '~> 1.0.2', require: 'task_list/railtie'
gem 'github-markup', '~> 1.3.1'
gem 'redcarpet', '~> 3.3.3'
+gem 'RedCloth', '~> 4.2.9'
gem 'rdoc', '~>3.6'
gem 'org-ruby', '~> 0.9.12'
gem 'creole', '~> 0.5.0'
@@ -119,8 +120,9 @@ gem 'acts-as-taggable-on', '~> 3.4'
# Background jobs
gem 'sinatra', '~> 1.4.4', require: nil
-gem 'sidekiq', '~> 3.5.0'
-gem 'sidekiq-cron', '~> 0.3.0'
+gem 'sidekiq', '~> 4.0'
+gem 'sidekiq-cron', '~> 0.4.0'
+gem 'redis-namespace'
# HTTP requests
gem "httparty", '~> 0.13.3'
@@ -193,7 +195,7 @@ gem 'jquery-turbolinks', '~> 2.1.0'
gem 'addressable', '~> 2.3.8'
gem 'bootstrap-sass', '~> 3.0'
gem 'font-awesome-rails', '~> 4.2'
-gem 'gitlab_emoji', '~> 0.1'
+gem 'gitlab_emoji', '~> 0.2.0'
gem 'gon', '~> 6.0.1'
gem 'jquery-atwho-rails', '~> 1.3.2'
gem 'jquery-rails', '~> 3.1.3'
diff --git a/Gemfile.lock b/Gemfile.lock
index 37ba22b7ceb..ff57460f5bb 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -2,6 +2,7 @@ GEM
remote: https://rubygems.org/
specs:
CFPropertyList (2.3.2)
+ RedCloth (4.2.9)
ace-rails-ap (2.0.1)
actionmailer (4.2.4)
actionpack (= 4.2.4)
@@ -147,6 +148,7 @@ GEM
execjs
coffee-script-source (1.10.0)
colorize (0.7.7)
+ concurrent-ruby (1.0.0)
connection_pool (2.2.0)
coveralls (0.8.9)
json (~> 1.8)
@@ -168,7 +170,7 @@ GEM
activerecord (>= 3.2.0, < 5.0)
descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1)
- devise (3.5.2)
+ devise (3.5.3)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 3.2.6, < 5)
@@ -682,15 +684,15 @@ GEM
rack
shoulda-matchers (2.8.0)
activesupport (>= 3.0.0)
- sidekiq (3.5.3)
- celluloid (~> 0.17.2)
+ sidekiq (4.0.1)
+ concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0)
json (~> 1.0)
redis (~> 3.2, >= 3.2.1)
- redis-namespace (~> 1.5, >= 1.5.2)
- sidekiq-cron (0.3.1)
+ sidekiq-cron (0.4.0)
+ redis-namespace (>= 1.5.2)
rufus-scheduler (>= 2.0.24)
- sidekiq (>= 2.17.3)
+ sidekiq (>= 4.0.0)
simple_oauth (0.1.9)
simplecov (0.10.0)
docile (~> 1.1.0)
@@ -825,6 +827,7 @@ PLATFORMS
ruby
DEPENDENCIES
+ RedCloth (~> 4.2.9)
ace-rails-ap (~> 2.0.1)
activerecord-deprecated_finders (~> 1.0.3)
activerecord-session_store (~> 0.1.0)
@@ -858,7 +861,7 @@ DEPENDENCIES
d3_rails (~> 3.5.5)
database_cleaner (~> 1.4.0)
default_value_for (~> 3.0.0)
- devise (~> 3.5.2)
+ devise (~> 3.5.3)
devise-async (~> 0.9.0)
devise-two-factor (~> 2.0.0)
diffy (~> 3.0.3)
@@ -878,7 +881,7 @@ DEPENDENCIES
github-linguist (~> 4.7.0)
github-markup (~> 1.3.1)
gitlab-flowdock-git-hook (~> 1.0.1)
- gitlab_emoji (~> 0.1)
+ gitlab_emoji (~> 0.2.0)
gitlab_git (~> 7.2.20)
gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1)
@@ -934,6 +937,7 @@ DEPENDENCIES
rblineprof
rdoc (~> 3.6)
redcarpet (~> 3.3.3)
+ redis-namespace
redis-rails (~> 4.0.0)
request_store (~> 1.2.0)
rerun (~> 0.10.0)
@@ -951,8 +955,8 @@ DEPENDENCIES
settingslogic (~> 2.0.9)
sham_rack
shoulda-matchers (~> 2.8.0)
- sidekiq (~> 3.5.0)
- sidekiq-cron (~> 0.3.0)
+ sidekiq (~> 4.0)
+ sidekiq-cron (~> 0.4.0)
simplecov (~> 0.10.0)
sinatra (~> 1.4.4)
six (~> 0.2.0)
diff --git a/README.md b/README.md
index c459e67baa1..3ec1d4a776c 100644
--- a/README.md
+++ b/README.md
@@ -69,7 +69,7 @@ GitLab is a Ruby on Rails application that runs on the following software:
- Ubuntu/Debian/CentOS/RHEL
- Ruby (MRI) 2.1
- Git 1.7.10+
-- Redis 2.4+
+- Redis 2.8+
- MySQL or PostgreSQL
For more information please see the [architecture documentation](http://doc.gitlab.com/ce/development/architecture.html).
diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee
index 96fd8f8773e..3ff9ba77dfc 100644
--- a/app/assets/javascripts/awards_handler.coffee
+++ b/app/assets/javascripts/awards_handler.coffee
@@ -1,11 +1,13 @@
class @AwardsHandler
- constructor: (@post_emoji_url, @noteable_type, @noteable_id) ->
+ constructor: (@post_emoji_url, @noteable_type, @noteable_id, @aliases) ->
addAward: (emoji) ->
+ emoji = @normilizeEmojiName(emoji)
@postEmoji emoji, =>
@addAwardToEmojiBar(emoji)
addAwardToEmojiBar: (emoji, custom_path = '') ->
+ emoji = @normilizeEmojiName(emoji)
if @exist(emoji)
if @isActive(emoji)
@decrementCounter(emoji)
@@ -94,3 +96,6 @@ class @AwardsHandler
$('body, html').animate({
scrollTop: $('.awards').offset().top - 80
}, 200)
+
+ normilizeEmojiName: (emoji) ->
+ @aliases[emoji] || emoji
diff --git a/app/assets/javascripts/flash.js.coffee b/app/assets/javascripts/flash.js.coffee
index 9b59d4e57f7..5de012e409f 100644
--- a/app/assets/javascripts/flash.js.coffee
+++ b/app/assets/javascripts/flash.js.coffee
@@ -12,5 +12,5 @@ class @Flash
@flash.click -> $(@).fadeOut()
@flash.show()
- pin: ->
- @flash.addClass('flash-pinned flash-raised')
+ pinTo: (selector) ->
+ @flash.detach().appendTo(selector)
diff --git a/app/assets/javascripts/issuable_context.js.coffee b/app/assets/javascripts/issuable_context.js.coffee
index c4d3e619f5e..01bd515cc02 100644
--- a/app/assets/javascripts/issuable_context.js.coffee
+++ b/app/assets/javascripts/issuable_context.js.coffee
@@ -5,9 +5,9 @@ class @IssuableContext
new UsersSelect()
$('select.select2').select2({width: 'resolve', dropdownAutoWidth: true})
- $(".context .inline-update").on "change", "select", ->
+ $(".issuable-sidebar .inline-update").on "change", "select", ->
$(this).submit()
- $(".context .inline-update").on "change", ".js-assignee", ->
+ $(".issuable-sidebar .inline-update").on "change", ".js-assignee", ->
$(this).submit()
$('.issuable-details').waitForImages ->
@@ -18,6 +18,12 @@ class @IssuableContext
$('.issuable-affix').affix offset:
top: ->
- @top = ($('.issuable-affix').offset().top - 70)
+ @top = ($('.issuable-affix').offset().top - 60)
bottom: ->
@bottom = $('.footer').outerHeight(true)
+
+ $(".edit-link").click (e) ->
+ block = $(@).parents('.block')
+ block.find('.selectbox').show()
+ block.find('.value').hide()
+ block.find('.js-select2').select2("open")
diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee
index b0eeb1db536..9e2dc1250c9 100644
--- a/app/assets/javascripts/merge_request_tabs.js.coffee
+++ b/app/assets/javascripts/merge_request_tabs.js.coffee
@@ -77,7 +77,7 @@ class @MergeRequestTabs
scrollToElement: (container) ->
if window.location.hash
- $el = $("#{container} #{window.location.hash}")
+ $el = $("div#{container} #{window.location.hash}")
$('body').scrollTo($el.offset().top) if $el.length
# Activate a tab based on the current action
@@ -133,7 +133,7 @@ class @MergeRequestTabs
@_get
url: "#{source}.json"
success: (data) =>
- document.getElementById('commits').innerHTML = data.html
+ document.querySelector("div#commits").innerHTML = data.html
$('.js-timeago').timeago()
@commitsLoaded = true
@scrollToElement("#commits")
@@ -144,7 +144,8 @@ class @MergeRequestTabs
@_get
url: "#{source}.json" + @_location.search
success: (data) =>
- document.getElementById('diffs').innerHTML = data.html
+ document.querySelector("div#diffs").innerHTML = data.html
+ $('div#diffs .js-syntax-highlight').syntaxHighlight()
@diffsLoaded = true
@scrollToElement("#diffs")
@@ -154,7 +155,7 @@ class @MergeRequestTabs
@_get
url: "#{source}.json"
success: (data) =>
- document.getElementById('builds').innerHTML = data.html
+ document.querySelector("div#builds").innerHTML = data.html
$('.js-timeago').timeago()
@buildsLoaded = true
@scrollToElement("#builds")
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index 533d00bfb0c..35dc7829da2 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -114,7 +114,7 @@ class @Notes
unless note.valid
if note.award
flash = new Flash('You have already used this award emoji!', 'alert')
- flash.pin()
+ flash.pinTo('.header-content')
return
# render note if it not present in loaded list
@@ -148,6 +148,8 @@ class @Notes
@note_ids.push(note.id)
form = $("form[rel='" + note.discussion_id + "']")
row = form.closest("tr")
+ note_html = $(note.html)
+ note_html.syntaxHighlight()
# is this the first note of discussion?
if row.is(".js-temp-notes-holder")
@@ -158,14 +160,16 @@ class @Notes
row.next().find(".note").remove()
# Add note to 'Changes' page discussions
- $(".notes[rel='" + note.discussion_id + "']").append note.html
+ $(".notes[rel='" + note.discussion_id + "']").append note_html
# Init discussion on 'Discussion' page if it is merge request page
if $('body').attr('data-page').indexOf('projects:merge_request') == 0
- $('ul.main-notes-list').append(note.discussion_with_diff_html)
+ discussion_html = $(note.discussion_with_diff_html)
+ discussion_html.syntaxHighlight()
+ $('ul.main-notes-list').append(discussion_html)
else
# append new note to all matching discussions
- $(".notes[rel='" + note.discussion_id + "']").append note.html
+ $(".notes[rel='" + note.discussion_id + "']").append note_html
# cleanup after successfully creating a diff/discussion note
@removeDiscussionNoteForm(form)
@@ -286,7 +290,7 @@ class @Notes
$html.find('.js-task-list-container').taskList('enable')
# Find the note's `li` element by ID and replace it with the updated HTML
- $note_li = $("#note_#{note.id}")
+ $note_li = $('.note-row-' + note.id)
$note_li.replaceWith($html)
###
@@ -346,18 +350,26 @@ class @Notes
###
removeNote: ->
note = $(this).closest(".note")
- notes = note.closest(".notes")
+ note_id = note.attr('id')
- # check if this is the last note for this line
- if notes.find(".note").length is 1
+ $('.note[id="' + note_id + '"]').each ->
+ note = $(this)
+ notes = note.closest(".notes")
+ count = notes.closest(".notes_holder").find(".discussion-notes-count")
- # for discussions
- notes.closest(".discussion").remove()
+ # check if this is the last note for this line
+ if notes.find(".note").length is 1
- # for diff lines
- notes.closest("tr").remove()
+ # for discussions
+ notes.closest(".discussion").remove()
- note.remove()
+ # for diff lines
+ notes.closest("tr").remove()
+ else
+ # update notes count
+ count.get(0).lastChild.nodeValue = " #{notes.children().length - 1}"
+
+ note.remove()
###
Called in response to clicking the delete attachment link
diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee
index ec919f0cd67..1f221945c06 100644
--- a/app/assets/javascripts/project.js.coffee
+++ b/app/assets/javascripts/project.js.coffee
@@ -4,8 +4,11 @@ class @Project
$('.js-protocol-switch').click ->
return if $(@).hasClass('active')
- # Toggle 'active' for both buttons
- $('.js-protocol-switch').toggleClass('active')
+
+ # Remove the active class for all buttons (ssh, http, kerberos if shown)
+ $('.active').not($(@)).removeClass('active');
+ # Add the active class for the clicked button
+ $(@).toggleClass('active')
url = $(@).data('clone')
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 2e8515668f6..88da799ee2b 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -461,3 +461,9 @@ table {
visibility: hidden;
}
}
+
+.content-separator {
+ margin-left: -$gl-padding;
+ margin-right: -$gl-padding;
+ border-top: 1px solid $border-color;
+}
diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss
index 1b723021d76..82eb50ad4be 100644
--- a/app/assets/stylesheets/framework/flash.scss
+++ b/app/assets/stylesheets/framework/flash.scss
@@ -15,13 +15,3 @@
@extend .alert-danger;
}
}
-
-.flash-pinned {
- position: fixed;
- top: 80px;
- width: 80%;
-}
-
-.flash-raised {
- z-index: 1000;
-}
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index c01e1e32e41..af145191bc8 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -181,4 +181,4 @@
.ajax-users-dropdown {
min-width: 250px !important;
-} \ No newline at end of file
+}
diff --git a/app/assets/stylesheets/pages/awards.scss b/app/assets/stylesheets/pages/awards.scss
new file mode 100644
index 00000000000..041b811a606
--- /dev/null
+++ b/app/assets/stylesheets/pages/awards.scss
@@ -0,0 +1,86 @@
+.awards {
+ @include clearfix;
+ line-height: 34px;
+
+ .award {
+ @include border-radius(5px);
+
+ border: 1px solid;
+ padding: 0px 10px;
+ float: left;
+ margin-right: 5px;
+ border-color: $border-color;
+ cursor: pointer;
+
+ &:hover {
+ background-color: #dce0e5;
+ }
+
+ &.active {
+ border-color: $border-gray-light;
+ background-color: $gray-light;
+
+ &:hover {
+ background-color: #dce0e5;
+ }
+
+ .counter {
+ font-weight: bold;
+ }
+ }
+
+ .icon {
+ float: left;
+ margin-right: 10px;
+ }
+
+ .counter {
+ float: left;
+ }
+ }
+
+ .awards-controls {
+ margin-left: 10px;
+ float: left;
+
+ .add-award {
+ font-size: 24px;
+ color: $gl-gray;
+ position: relative;
+ top: 2px;
+
+ &:hover,
+ &:link {
+ text-decoration: none;
+ }
+ }
+
+ .awards-menu {
+ padding: $gl-padding;
+ min-width: 214px;
+
+ > li {
+ cursor: pointer;
+ width: 30px;
+ height: 30px;
+ text-align: center;
+ @include border-radius(5px);
+
+ img {
+ margin-bottom: 2px;
+ }
+
+ &:hover {
+ background-color: #ccc;
+ }
+ }
+ }
+ }
+
+ .awards-menu{
+ li {
+ float: left;
+ margin: 3px;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 957da5c182e..797a0af3720 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -18,26 +18,12 @@
&.affix {
position: fixed;
- top: 70px;
+ top: 60px;
margin-right: 35px;
}
}
}
-.issuable-context-title {
- margin-bottom: 5px;
-
- .avatar {
- margin-left: 0;
- }
-
- label {
- color: $gl-gray;
- font-weight: normal;
- margin-right: 4px;
- }
-}
-
.project-issuable-filter {
.controls {
float: right;
@@ -50,23 +36,6 @@
}
.issuable-details {
- .page-title {
- margin-top: -$gl-padding;
- padding: 7px 0;
- margin-bottom: 0;
- color: #5c5d5e;
- font-size: 16px;
- line-height: 42px;
-
- .author {
- color: #5c5d5e;
- }
-
- .issue-id {
- color: #5c5d5e;
- }
- }
-
.issue-title {
margin: 0;
font-size: 23px;
@@ -80,6 +49,21 @@
margin-bottom: 0;
}
}
+
+ section {
+ border-right: 1px solid #ECEEF1;
+
+ > .tab-content {
+ margin-right: 1px;
+ }
+
+ .issue-discussion > .gray-content-block,
+ > .gray-content-block {
+ margin-top: 0;
+ border-top: none;
+ margin-right: -15px;
+ }
+ }
}
.issuable-filter-count {
@@ -101,84 +85,72 @@
}
}
-.cross-project-reference {
- text-align: center;
- width: 100%;
-
- .slead {
- padding: 5px;
- }
+.issuable-sidebar {
+ .block {
+ @include clearfix;
+ padding: $gl-padding 0;
+ border-bottom: 1px solid #F0F0F0;
- span, button {
- background-color: $background-color;
+ &:last-child {
+ border: none;
+ }
}
-}
-.awards {
- @include clearfix;
- line-height: 34px;
- margin: 2px 0;
+ .title {
+ color: $gl-text-color;
+ margin-bottom: 8px;
- .award {
- @include border-radius(5px);
-
- border: 1px solid;
- padding: 0px 10px;
- float: left;
- margin: 0 5px;
- border-color: $border-color;
- cursor: pointer;
-
- &.active {
- border-color: $border-gray-light;
- background-color: $gray-light;
+ .avatar {
+ margin-left: 0;
+ }
- .counter {
- font-weight: bold;
- }
+ label {
+ font-weight: normal;
+ margin-right: 4px;
}
- .icon {
- float: left;
- margin-right: 10px;
+ .edit-link {
+ color: $gl-gray;
}
+ }
+
+ .cross-project-reference {
+ font-weight: bold;
+ color: $gl-link-color;
- .counter {
- float: left;
+ button {
+ float: right;
}
}
- .awards-controls {
- margin-left: 10px;
- float: left;
+ .selectbox {
+ display: none
+ }
- .add-award {
- font-size: 24px;
- color: $gl-gray;
- position: relative;
- top: 2px;
+ .btn-clipboard {
+ color: $gl-gray;
+ }
- &:hover,
- &:link {
- text-decoration: none;
- }
- }
+ .participants .avatar {
+ margin-top: 6px;
+ margin-right: 2px;
+ }
+}
- .awards-menu {
- padding: $gl-padding;
- min-width: 214px;
+.issuable-title {
+ margin: -$gl-padding;
+ padding: 7px $gl-padding;
+ margin-bottom: 0px;
+ border-bottom: 1px solid $border-color;
+ color: #5c5d5e;
+ font-size: 16px;
+ line-height: 42px;
- > li {
- cursor: pointer;
- margin: 5px;
- }
- }
+ .author {
+ color: #5c5d5e;
}
- .awards-menu{
- li {
- float: left;
- margin: 3px;
- }
+ .issuable-id {
+ color: #5c5d5e;
}
}
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index f5548c5b88b..a652b65502f 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -60,6 +60,26 @@ form.edit-issue {
margin: 0;
}
+.merge-requests-title {
+ font-size: 16px;
+ font-weight: 600;
+}
+
+.merge-request-id {
+ display: inline-block;
+ width: 3em;
+}
+
+.merge-request-info {
+ padding-left: 5px;
+}
+
+.merge-request-status {
+ color: $gl-gray;
+ font-size: 15px;
+ font-weight: bold;
+}
+
.merge-request,
.issue {
&.today {
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index fc8c7161991..502e9552acd 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -121,10 +121,6 @@
}
}
-.merge-request-details {
- margin-bottom: $gl-padding;
-}
-
.mr_source_commit,
.mr_target_commit {
.commit {
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index ae474cf8d68..b59b52291fb 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -58,10 +58,10 @@ class Projects::IssuesController < Projects::ApplicationController
end
def show
- @participants = @issue.participants(current_user)
@note = @project.notes.new(noteable: @issue)
@notes = @issue.notes.nonawards.with_associations.fresh
@noteable = @issue
+ @merge_requests = @issue.referenced_merge_requests
respond_with(@issue)
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 530f3d3dcb8..e8fa10fafb1 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -279,8 +279,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def define_show_vars
- @participants = @merge_request.participants(current_user)
-
# Build a note object for comment form
@note = @project.notes.new(noteable: @merge_request)
@notes = @merge_request.mr_and_commit_notes.nonawards.inc_author.fresh
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index 8e1f8f9ba6d..f8f2cbf1319 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -52,7 +52,7 @@ module CiStatusHelper
'circle'
end
- icon(icon_name)
+ icon(icon_name + ' fw')
end
def render_ci_status(ci_commit)
diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb
index caba63006da..b96418679bd 100644
--- a/app/mailers/emails/projects.rb
+++ b/app/mailers/emails/projects.rb
@@ -59,85 +59,17 @@ module Emails
subject: subject("Project was moved"))
end
- def repository_push_email(project_id, recipient, author_id: nil,
- ref: nil,
- action: nil,
- compare: nil,
- reverse_compare: false,
- send_from_committer_email: false,
- disable_diffs: false)
- unless author_id && ref && action
- raise ArgumentError, "missing keywords: author_id, ref, action"
- end
+ def repository_push_email(project_id, recipient, opts = {})
+ @message =
+ Gitlab::Email::Message::RepositoryPush.new(self, project_id, recipient, opts)
- @project = Project.find(project_id)
- @current_user = @author = User.find(author_id)
- @reverse_compare = reverse_compare
- @compare = compare
- @ref_name = Gitlab::Git.ref_name(ref)
- @ref_type = Gitlab::Git.tag_ref?(ref) ? "tag" : "branch"
- @action = action
- @disable_diffs = disable_diffs
-
- if @compare
- @commits = Commit.decorate(compare.commits, @project)
- @diffs = compare.diffs
- end
-
- @action_name =
- case action
- when :create
- "pushed new"
- when :delete
- "deleted"
- else
- "pushed to"
- end
-
- @subject = "[Git]"
- @subject << "[#{@project.path_with_namespace}]"
- @subject << "[#{@ref_name}]" if action == :push
- @subject << " "
-
- if action == :push
- if @commits.length > 1
- @target_url = namespace_project_compare_url(@project.namespace,
- @project,
- from: Commit.new(@compare.base, @project),
- to: Commit.new(@compare.head, @project))
- @subject << "Deleted " if @reverse_compare
- @subject << "#{@commits.length} commits: #{@commits.first.title}"
- else
- @target_url = namespace_project_commit_url(@project.namespace,
- @project, @commits.first)
-
- @subject << "Deleted 1 commit: " if @reverse_compare
- @subject << @commits.first.title
- end
- else
- unless action == :delete
- @target_url = namespace_project_tree_url(@project.namespace,
- @project, @ref_name)
- end
-
- subject_action = @action_name.dup
- subject_action[0] = subject_action[0].capitalize
- @subject << "#{subject_action} #{@ref_type} #{@ref_name}"
- end
-
- @disable_footer = true
-
- reply_to =
- if send_from_committer_email && can_send_from_user_email?(@author)
- @author.email
- else
- Gitlab.config.gitlab.email_reply_to
- end
-
- mail(from: sender(author_id, send_from_committer_email),
- reply_to: reply_to,
- to: recipient,
- subject: @subject)
+ # used in notify layout
+ @target_url = @message.target_url
+
+ mail(from: sender(@message.author_id, @message.send_from_committer_email?),
+ reply_to: @message.reply_to,
+ to: @message.recipient,
+ subject: @message.subject)
end
end
end
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 874769e239a..9beb0de68f3 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -34,13 +34,13 @@ class Notify < BaseMailer
allowed_domains
end
- private
-
def can_send_from_user_email?(sender)
sender_domain = sender.email.split("@").last
self.class.allowed_email_domains.include?(sender_domain)
end
+ private
+
# Return an email address that displays the name of the sender.
# Only the displayed name changes; the actual email address is always the same.
def sender(sender_id, send_from_user_email = false)
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 187b6482b6c..e04035b3af8 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -83,6 +83,14 @@ class Issue < ActiveRecord::Base
reference
end
+ def referenced_merge_requests
+ references = [self, *notes].flat_map do |note|
+ note.all_references(load_lazy_references: false).merge_requests
+ end.uniq
+
+ Gitlab::Markdown::ReferenceFilter::LazyReference.load(references).uniq.sort_by(&:iid)
+ end
+
# Reset issue events cache
#
# Since we do cache @event we need to reset cache in special cases:
diff --git a/app/models/note.rb b/app/models/note.rb
index de9392adbf4..04053ccc61e 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -350,7 +350,7 @@ class Note < ActiveRecord::Base
end
def editable?
- !system?
+ !system? && !is_award
end
# Checks if note is an award added as a comment
@@ -377,6 +377,7 @@ class Note < ActiveRecord::Base
end
def award_emoji_name
- note.match(Gitlab::Markdown::EmojiFilter.emoji_pattern)[1]
+ original_name = note.match(Gitlab::Markdown::EmojiFilter.emoji_pattern)[1]
+ AwardEmoji.normilize_emoji_name(original_name)
end
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 1edec52c09e..2c25f4ce451 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -271,9 +271,25 @@ class Repository
def license
cache.fetch(:license) do
- tree(:head).blobs.find do |file|
- file.name =~ /\Alicense/i
+ licenses = tree(:head).blobs.find_all do |file|
+ file.name =~ /\A(copying|license|licence)/i
+ end
+
+ preferences = [
+ /\Alicen[sc]e\z/i, # LICENSE, LICENCE
+ /\Alicen[sc]e\./i, # LICENSE.md, LICENSE.txt
+ /\Acopying\z/i, # COPYING
+ /\Acopying\.(?!lesser)/i, # COPYING.txt
+ /Acopying.lesser/i # COPYING.LESSER
+ ]
+
+ license = nil
+ preferences.each do |r|
+ license = licenses.find { |l| l.name =~ r }
+ break if license
end
+
+ license
end
end
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index 700a1db04d8..a6820183bee 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -64,8 +64,10 @@ module Projects
after_create_actions if @project.persisted?
@project
- rescue
- @project.errors.add(:base, "Can't save project. Please try again later")
+ rescue => e
+ message = "Unable to save project: #{e.message}"
+ Rails.logger.error(message)
+ @project.errors.add(:base, message) if @project
@project
end
diff --git a/app/views/notify/_note_message.html.haml b/app/views/notify/_note_message.html.haml
index 00cb4aa24cc..27112c6745a 100644
--- a/app/views/notify/_note_message.html.haml
+++ b/app/views/notify/_note_message.html.haml
@@ -1,2 +1,4 @@
%div
+ "#{link_to @note.author_name, user_url(@note.author)} wrote:"
+%div
= markdown(@note.note, pipeline: :email)
diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml
index 12f83aae04b..4361f67a74d 100644
--- a/app/views/notify/repository_push_email.html.haml
+++ b/app/views/notify/repository_push_email.html.haml
@@ -1,30 +1,32 @@
-%h3 #{@author.name} #{@action_name} #{@ref_type} #{@ref_name} at #{link_to @project.name_with_namespace, namespace_project_url(@project.namespace, @project)}
+%h3
+ #{@message.author_name} #{@message.action_name} #{@message.ref_type} #{@message.ref_name}
+ at #{link_to(@message.project_name_with_namespace, namespace_project_url(@message.project_namespace, @message.project))}
-- if @compare
- - if @reverse_compare
+- if @message.compare
+ - if @message.reverse_compare?
%p
%strong WARNING:
The push did not contain any new commits, but force pushed to delete the commits and changes below.
%h4
- = @reverse_compare ? "Deleted commits:" : "Commits:"
+ = @message.reverse_compare? ? "Deleted commits:" : "Commits:"
%ul
- - @commits.each do |commit|
+ - @message.commits.each do |commit|
%li
- %strong #{link_to commit.short_id, namespace_project_commit_url(@project.namespace, @project, commit)}
+ %strong #{link_to(commit.short_id, namespace_project_commit_url(@message.project_namespace, @message.project, commit))}
%div
%span by #{commit.author_name}
%i at #{commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ")}
%pre.commit-message
= commit.safe_message
- %h4 #{pluralize @diffs.count, "changed file"}:
+ %h4 #{pluralize @message.diffs_count, "changed file"}:
%ul
- - @diffs.each_with_index do |diff, i|
+ - @message.diffs.each_with_index do |diff, i|
%li.file-stats
- %a{href: "#{@target_url if @disable_diffs}#diff-#{i}" }
+ %a{href: "#{@message.target_url if @message.disable_diffs?}#diff-#{i}" }
- if diff.deleted_file
%span.deleted-file
&minus;
@@ -40,11 +42,11 @@
- else
= diff.new_path
- - unless @disable_diffs
+ - unless @message.disable_diffs?
%h4 Changes:
- - @diffs.each_with_index do |diff, i|
+ - @message.diffs.each_with_index do |diff, i|
%li{id: "diff-#{i}"}
- %a{href: @target_url + "#diff-#{i}"}
+ %a{href: @message.target_url + "#diff-#{i}"}
- if diff.deleted_file
%strong
= diff.old_path
@@ -62,5 +64,5 @@
= color_email_diff(diff.diff)
%br
- - if @compare.timeout
+ - if @message.compare_timeout
%h5 Huge diff. To prevent performance issues changes are hidden
diff --git a/app/views/notify/repository_push_email.text.haml b/app/views/notify/repository_push_email.text.haml
index 97a176ed2a3..aa0e263b6df 100644
--- a/app/views/notify/repository_push_email.text.haml
+++ b/app/views/notify/repository_push_email.text.haml
@@ -1,21 +1,21 @@
-#{@author.name} #{@action_name} #{@ref_type} #{@ref_name} at #{@project.name_with_namespace}
-- if @compare
+#{@message.author_name} #{@message.action_name} #{@message.ref_type} #{@message.ref_name} at #{@message.project_name_with_namespace}
+- if @message.compare
\
\
- - if @reverse_compare
+ - if @message.reverse_compare?
WARNING: The push did not contain any new commits, but force pushed to delete the commits and changes below.
\
\
- = @reverse_compare ? "Deleted commits:" : "Commits:"
- - @commits.each do |commit|
+ = @message.reverse_compare? ? "Deleted commits:" : "Commits:"
+ - @message.commits.each do |commit|
#{commit.short_id} by #{commit.author_name} at #{commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ")}
#{commit.safe_message}
\- - - - -
\
\
- #{pluralize @diffs.count, "changed file"}:
+ #{pluralize @message.diffs_count, "changed file"}:
\
- - @diffs.each do |diff|
+ - @message.diffs.each do |diff|
- if diff.deleted_file
\- − #{diff.old_path}
- elsif diff.renamed_file
@@ -24,11 +24,11 @@
\- + #{diff.new_path}
- else
\- #{diff.new_path}
- - unless @disable_diffs
+ - unless @message.disable_diffs?
\
\
Changes:
- - @diffs.each do |diff|
+ - @message.diffs.each do |diff|
\
\=====================================
- if diff.deleted_file
@@ -39,11 +39,11 @@
= diff.new_path
\=====================================
!= diff.diff
- - if @compare.timeout
+ - if @message.compare_timeout
\
\
Huge diff. To prevent performance issues it was hidden
- - if @target_url
+ - if @message.target_url
\
\
- View it on GitLab: #{@target_url}
+ View it on GitLab: #{@message.target_url}
diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml
index f2011542ca7..405bae1bbb9 100644
--- a/app/views/projects/issues/_discussion.html.haml
+++ b/app/views/projects/issues/_discussion.html.haml
@@ -5,24 +5,8 @@
- else
= link_to 'Close Issue', issue_path(@issue, issue: {state_event: :close}, status_only: true), method: :put, class: 'btn btn-grouped btn-close js-note-target-close', title: 'Close Issue'
-= render 'shared/show_aside'
-
.gray-content-block.second-block.oneline-block
- .row
- .col-md-9
- .votes-holder.pull-right
- #votes= render 'votes/votes_block', votable: @issue
- = render "shared/issuable/participants"
- .col-md-3
- .input-group.cross-project-reference
- %span#cross-project-reference.slead.has_tooltip{title: 'Cross-project reference'}
- = cross_project_reference(@project, @issue)
- = clipboard_button(clipboard_target: 'span#cross-project-reference')
+ = render 'votes/votes_block', votable: @issue
-.row
- %section.col-md-9
- .voting_notes#notes= render 'projects/notes/notes_with_form'
- %aside.col-md-3
- .issuable-affix
- .context
- = render 'shared/issuable/context', issuable: @issue
+#notes
+ = render 'projects/notes/notes_with_form'
diff --git a/app/views/projects/issues/_merge_requests.html.haml b/app/views/projects/issues/_merge_requests.html.haml
new file mode 100644
index 00000000000..fe856ac991e
--- /dev/null
+++ b/app/views/projects/issues/_merge_requests.html.haml
@@ -0,0 +1,25 @@
+-if @merge_requests.any?
+ %h2.merge-requests-title
+ = pluralize(@merge_requests.count, 'Related Merge Request')
+ %ul.bordered-list
+ - has_any_ci = @merge_requests.any?(&:ci_commit)
+ - @merge_requests.each do |merge_request|
+ %li
+ %span.merge-request-ci-status
+ - if merge_request.ci_commit
+ = render_ci_status(merge_request.ci_commit)
+ - elsif has_any_ci
+ = icon('blank fw')
+ %span.merge-request-id
+ \##{merge_request.iid}
+ %span.merge-request-info
+ %strong
+ = link_to_gfm merge_request.title, merge_request_path(merge_request), class: "row_title"
+ in
+ - project = merge_request.target_project
+ = link_to project.name_with_namespace, namespace_project_path(project.namespace, project)
+ %span.merge-request-status.prepend-left-10
+ - if merge_request.merged?
+ MERGED
+ - elsif merge_request.closed?
+ CLOSED
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index a78d20cc07e..cc2cf8c8716 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -3,13 +3,13 @@
.issue
.issue-details.issuable-details
- .page-title
+ .issuable-title
.issue-box{ class: issue_box_class(@issue) }
- if @issue.closed?
Closed
- else
Open
- %span.issue-id Issue ##{@issue.iid}
+ %span.issuable-id Issue ##{@issue.iid}
%span.creator
&middot;
opened by #{link_to_member(@project, @issue.author, size: 24)}
@@ -36,18 +36,29 @@
= icon('pencil-square-o')
Edit
- .gray-content-block.middle-block
- %h2.issue-title
- = markdown escape_once(@issue.title), pipeline: :single_line
- %div
- - if @issue.description.present?
- .description{class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : ''}
- .wiki
- = preserve do
- = markdown(@issue.description, cache_key: [@issue, "description"])
- %textarea.hidden.js-task-list-field
- = @issue.description
- - if @closed_by_merge_requests.present?
- = render 'projects/issues/closed_by_box'
- .issue-discussion
- = render 'projects/issues/discussion'
+ .row
+ %section.col-md-9
+ .gray-content-block
+ %h2.issue-title
+ = markdown escape_once(@issue.title), pipeline: :single_line
+ %div
+ - if @issue.description.present?
+ .description{class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : ''}
+ .wiki
+ = preserve do
+ = markdown(@issue.description, cache_key: [@issue, "description"])
+ %textarea.hidden.js-task-list-field
+ = @issue.description
+
+ .merge-requests
+ = render 'merge_requests'
+
+ - if @closed_by_merge_requests.present?
+ = render 'projects/issues/closed_by_box'
+ .issue-discussion
+ = render 'projects/issues/discussion'
+
+ %aside.col-md-3
+ = render 'shared/issuable/sidebar', issuable: @issue
+
+ = render 'shared/show_aside'
diff --git a/app/views/projects/issues/update.js.haml b/app/views/projects/issues/update.js.haml
index b7735aaf3c1..2f0f3fcfb06 100644
--- a/app/views/projects/issues/update.js.haml
+++ b/app/views/projects/issues/update.js.haml
@@ -1,3 +1,3 @@
-$('.context').html("#{escape_javascript(render 'shared/issuable/context', issuable: @issue)}");
-$('.context').effect('highlight')
+$('.issuable-sidebar').html("#{escape_javascript(render 'shared/issuable/sidebar', issuable: @issue)}");
+$('.issuable-sidebar').parent().effect('highlight')
new Issue();
diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml
index d64b19ae91a..7a7428d35cc 100644
--- a/app/views/projects/merge_requests/_discussion.html.haml
+++ b/app/views/projects/merge_requests/_discussion.html.haml
@@ -5,24 +5,7 @@
- if @merge_request.closed?
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request"
-= render 'shared/show_aside'
+.gray-content-block.second-block.oneline-block
+ = render 'votes/votes_block', votable: @merge_request
-.gray-content-block.middle-block.oneline-block
- .row
- .col-md-9
- .votes-holder.pull-right
- #votes= render 'votes/votes_block', votable: @merge_request
- = render "shared/issuable/participants"
- .col-md-3
- .input-group.cross-project-reference
- %span#cross-project-reference.slead.has_tooltip{title: 'Cross-project reference'}
- = cross_project_reference(@project, @merge_request)
- = clipboard_button(clipboard_target: 'span#cross-project-reference')
-
-.row
- %section.col-md-9
- .voting_notes#notes= render "projects/notes/notes_with_form"
- %aside.col-md-3
- .issuable-affix
- .context
- = render 'shared/issuable/context', issuable: @merge_request
+#notes= render "projects/notes/notes_with_form"
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index cf9570f7c7e..105c731c7e1 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -5,7 +5,6 @@
%ul.controls.light
- if merge_request.merged?
%li
- = icon('check')
MERGED
- elsif merge_request.closed?
%li
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 960d1561e73..04f8fd74422 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -7,71 +7,79 @@
.merge-request{'data-url' => merge_request_path(@merge_request)}
.merge-request-details.issuable-details
= render "projects/merge_requests/show/mr_title"
- = render "projects/merge_requests/show/mr_box"
- .append-bottom-default.mr-source-target.prepend-top-default
- - if @merge_request.open?
- .pull-right
- - if @merge_request.source_branch_exists?
- = link_to "#modal_merge_info", class: "btn btn-sm", "data-toggle" => "modal" do
- = icon('cloud-download fw')
- Check out branch
+ .row
+ %section.col-md-9
+ = render "projects/merge_requests/show/mr_box"
+ .append-bottom-default.mr-source-target.prepend-top-default
+ - if @merge_request.open?
+ .pull-right
+ - if @merge_request.source_branch_exists?
+ = link_to "#modal_merge_info", class: "btn btn-sm", "data-toggle" => "modal" do
+ = icon('cloud-download fw')
+ Check out branch
- %span.dropdown
- %a.btn.btn-sm.dropdown-toggle{ data: {toggle: :dropdown} }
- = icon('download')
- Download as
- %span.caret
- %ul.dropdown-menu
- %li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch)
- %li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff)
- .normal
- %span Request to merge
- %span.label-branch= source_branch_with_namespace(@merge_request)
- %span into
- = link_to namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch" do
- = @merge_request.target_branch
+ %span.dropdown
+ %a.btn.btn-sm.dropdown-toggle{ data: {toggle: :dropdown} }
+ = icon('download')
+ Download as
+ %span.caret
+ %ul.dropdown-menu
+ %li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch)
+ %li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff)
+ .normal
+ %span Request to merge
+ %span.label-branch= source_branch_with_namespace(@merge_request)
+ %span into
+ = link_to namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch" do
+ = @merge_request.target_branch
- = render "projects/merge_requests/show/how_to_merge"
- = render "projects/merge_requests/widget/show.html.haml"
+ = render "projects/merge_requests/show/how_to_merge"
+ = render "projects/merge_requests/widget/show.html.haml"
- - if @merge_request.open? && @merge_request.source_branch_exists? && @merge_request.can_be_merged? && @merge_request.can_be_merged_by?(current_user)
- .light.prepend-top-default
- You can also accept this merge request manually using the
- = succeed '.' do
- = link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
+ - if @merge_request.open? && @merge_request.source_branch_exists? && @merge_request.can_be_merged? && @merge_request.can_be_merged_by?(current_user)
+ .light.prepend-top-default
+ You can also accept this merge request manually using the
+ = succeed '.' do
+ = link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
- - if @commits.present?
- %ul.merge-request-tabs.center-top-menu.no-top.no-bottom
- %li.notes-tab
- = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#notes', action: 'notes', toggle: 'tab'} do
- Discussion
- %span.badge= @merge_request.mr_and_commit_notes.user.count
- %li.commits-tab
- = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#commits', action: 'commits', toggle: 'tab'} do
- Commits
- %span.badge= @commits.size
- %li.diffs-tab
- = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do
- Changes
- %span.badge= @merge_request.diffs.size
- - if @ci_commit
- %li.builds-tab
- = link_to builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#builds', action: 'builds', toggle: 'tab'} do
- Builds
- %span.badge= @statuses.size
+ - if @commits.present?
+ %ul.merge-request-tabs.center-top-menu.no-top.no-bottom
+ %li.notes-tab
+ = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#notes', action: 'notes', toggle: 'tab'} do
+ Discussion
+ %span.badge= @merge_request.mr_and_commit_notes.user.count
+ %li.commits-tab
+ = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#commits', action: 'commits', toggle: 'tab'} do
+ Commits
+ %span.badge= @commits.size
+ %li.diffs-tab
+ = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do
+ Changes
+ %span.badge= @merge_request.diffs.size
+ - if @ci_commit
+ %li.builds-tab
+ = link_to builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#builds', action: 'builds', toggle: 'tab'} do
+ Builds
+ %span.badge= @statuses.size
- .tab-content
- #notes.notes.tab-pane.voting_notes
- = render "projects/merge_requests/discussion"
- #commits.commits.tab-pane
- - # This tab is always loaded via AJAX
- #diffs.diffs.tab-pane
- - # This tab is always loaded via AJAX
- #builds.builds.tab-pane
- - # This tab is always loaded via AJAX
+ .tab-content
+ #notes.notes.tab-pane.voting_notes
+ = render "projects/merge_requests/discussion"
+ #commits.commits.tab-pane
+ - # This tab is always loaded via AJAX
+ #diffs.diffs.tab-pane
+ - # This tab is always loaded via AJAX
+ #builds.builds.tab-pane
+ - # This tab is always loaded via AJAX
+
+ .mr-loading-status
+ = spinner
+
+ %aside.col-md-3
+ = render 'shared/issuable/sidebar', issuable: @merge_request
+
+ = render 'shared/show_aside'
- .mr-loading-status
- = spinner
:javascript
var merge_request;
diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml
index 4dfe46e2b86..d65c3b16618 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -1,7 +1,7 @@
-.page-title
+.issuable-title
.issue-box{ class: issue_box_class(@merge_request) }
= @merge_request.state_human_name
- %span.issue-id Merge Request ##{@merge_request.iid}
+ %span.issuable-id Merge Request ##{@merge_request.iid}
%span.creator
&middot;
opened by #{link_to_member(@project, @merge_request.author, size: 24)}
diff --git a/app/views/projects/merge_requests/update.js.haml b/app/views/projects/merge_requests/update.js.haml
index 25583b2cc6f..93db65ddf79 100644
--- a/app/views/projects/merge_requests/update.js.haml
+++ b/app/views/projects/merge_requests/update.js.haml
@@ -1,3 +1,3 @@
-$('.context').html("#{escape_javascript(render 'shared/issuable/context', issuable: @merge_request)}");
-$('.context').effect('highlight')
+$('.issuable-sidebar').html("#{escape_javascript(render 'shared/issuable/sidebar', issuable: @merge_request)}");
+$('.issuable-sidebar').parent().effect('highlight')
merge_request = new MergeRequest();
diff --git a/app/views/projects/merge_requests/widget/_merged.html.haml b/app/views/projects/merge_requests/widget/_merged.html.haml
index 8c2b5366a06..6f52c963a53 100644
--- a/app/views/projects/merge_requests/widget/_merged.html.haml
+++ b/app/views/projects/merge_requests/widget/_merged.html.haml
@@ -33,7 +33,7 @@
.remove_source_branch_in_progress.hide
%p
= icon('spinner spin')
- Removing source branch '#{@merge_request.source_branch}'. Please wait. This page will be automatically reload.
+ Removing source branch '#{@merge_request.source_branch}'. Please wait, this page will be automatically reloaded.
:javascript
$('.remove_source_branch').on('click', function() {
diff --git a/app/views/projects/runners/_shared_runners.html.haml b/app/views/projects/runners/_shared_runners.html.haml
index 316ea747b14..8be3d379828 100644
--- a/app/views/projects/runners/_shared_runners.html.haml
+++ b/app/views/projects/runners/_shared_runners.html.haml
@@ -12,8 +12,8 @@
&nbsp; for this project
- if @shared_runners_count.zero?
- This application has no shared runners yet.
- Please use specific runners or ask administrator to create one
+ This GitLab server does not provide any shared runners yet.
+ Please use specific runners or ask the administrator to create one.
- else
%h4.underlined-title Available shared runners - #{@shared_runners_count}
%ul.bordered-list.available-shared-runners
diff --git a/app/views/shared/issuable/_context.html.haml b/app/views/shared/issuable/_context.html.haml
deleted file mode 100644
index 2aa46662613..00000000000
--- a/app/views/shared/issuable/_context.html.haml
+++ /dev/null
@@ -1,57 +0,0 @@
-= form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f|
- %div.prepend-top-default
- .issuable-context-title
- %label
- Assignee:
- - if issuable.assignee
- %strong= link_to_member(@project, issuable.assignee, size: 24)
- - else
- none
- .issuable-context-selectbox
- - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
- = users_select_tag("#{issuable.class.table_name.singularize}[assignee_id]", placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: issuable.assignee_id, project: @target_project, null_user: true, current_user: true, first_user: true)
-
- %div.prepend-top-default.clearfix
- .issuable-context-title
- %label
- Milestone:
- - if issuable.milestone
- %span.back-to-milestone
- = link_to namespace_project_milestone_path(@project.namespace, @project, issuable.milestone) do
- %strong
- = icon('clock-o')
- = issuable.milestone.title
- - else
- none
- .issuable-context-selectbox
- - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
- = f.select(:milestone_id, milestone_options(issuable), { include_blank: true }, { class: 'select2 select2-compact js-select2 js-milestone', data: { placeholder: 'Select milestone' }})
- = hidden_field_tag :issuable_context
- = f.submit class: 'btn hide'
-
- - if issuable.labels.any?
- %div.prepend-top-default.clearfix
- .issuable-context-title
- %label Labels
- .issuable-show-labels
- - issuable.labels.each do |label|
- = link_to_label(label)
-
- - if current_user
- - subscribed = issuable.subscribed?(current_user)
- %div.prepend-top-default.clearfix
- .issuable-context-title
- %label Subscription
- - subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed'
- .subscription-status{data: {status: subscribtion_status}}
- .description-block.unsubscribed{class: ( 'hidden' if subscribed )}
- You're not receiving notifications from this thread.
- .description-block.subscribed{class: ( 'hidden' unless subscribed )}
- You're receiving notifications because you're subscribed to this thread.
- %button.btn.btn-block.subscribe-button{:type => 'button'}
- = icon('eye')
- %span= subscribed ? 'Unsubscribe' : 'Subscribe'
-
-:javascript
- new Subscription("#{toggle_subscription_path(issuable)}");
- new IssuableContext();
diff --git a/app/views/shared/issuable/_participants.html.haml b/app/views/shared/issuable/_participants.html.haml
index b4e0def48b6..da6bacbb74a 100644
--- a/app/views/shared/issuable/_participants.html.haml
+++ b/app/views/shared/issuable/_participants.html.haml
@@ -1,5 +1,5 @@
-.participants
- %span
- = pluralize @participants.count, "participant"
- - @participants.each do |participant|
+.block.participants
+ .title
+ = pluralize participants.count, "participant"
+ - participants.each do |participant|
= link_to_member(@project, participant, name: false, size: 24)
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
new file mode 100644
index 00000000000..0019f739b89
--- /dev/null
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -0,0 +1,83 @@
+.issuable-sidebar.issuable-affix
+ = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f|
+ .block
+ .title
+ Cross-project reference
+ .cross-project-reference
+ %span#cross-project-reference
+ = cross_project_reference(@project, issuable)
+ = clipboard_button(clipboard_target: 'span#cross-project-reference')
+
+ .block.assignee
+ .title
+ %label
+ Assignee
+ - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
+ .pull-right
+ = link_to 'Edit', '#', class: 'edit-link'
+ .value
+ - if issuable.assignee
+ %strong= link_to_member(@project, issuable.assignee, size: 24)
+ - else
+ .light None
+
+ .selectbox
+ = users_select_tag("#{issuable.class.table_name.singularize}[assignee_id]", placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: issuable.assignee_id, project: @target_project, null_user: true, current_user: true, first_user: true)
+
+ .block.milestone
+ .title
+ %label
+ Milestone
+ - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
+ .pull-right
+ = link_to 'Edit', '#', class: 'edit-link'
+ .value
+ - if issuable.milestone
+ %span.back-to-milestone
+ = link_to namespace_project_milestone_path(@project.namespace, @project, issuable.milestone) do
+ %strong
+ = icon('clock-o')
+ = issuable.milestone.title
+ - else
+ .light None
+ .selectbox
+ = f.select(:milestone_id, milestone_options(issuable), { include_blank: true }, { class: 'select2 select2-compact js-select2 js-milestone', data: { placeholder: 'Select milestone' }})
+ = hidden_field_tag :issuable_context
+ = f.submit class: 'btn hide'
+
+ - if issuable.project.labels.any?
+ .block
+ .title
+ %label Labels
+ - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
+ .pull-right
+ = link_to 'Edit', '#', class: 'edit-link'
+ .value.issuable-show-labels
+ - if issuable.labels.any?
+ - issuable.labels.each do |label|
+ = link_to_label(label)
+ - else
+ .light None
+ .selectbox
+ = f.collection_select :label_ids, issuable.project.labels.all, :id, :name,
+ { selected: issuable.label_ids }, multiple: true, class: 'select2 js-select2', data: { placeholder: "Select labels" }
+
+ = render "shared/issuable/participants", participants: issuable.participants(current_user)
+
+ - if current_user
+ - subscribed = issuable.subscribed?(current_user)
+ .block.light
+ .title
+ %label.light Notifications
+ - subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed'
+ %button.btn.btn-block.btn-gray.subscribe-button{:type => 'button'}
+ %span= subscribed ? 'Unsubscribe' : 'Subscribe'
+ .subscription-status{data: {status: subscribtion_status}}
+ .unsubscribed{class: ( 'hidden' if subscribed )}
+ You're not receiving notifications from this thread.
+ .subscribed{class: ( 'hidden' unless subscribed )}
+ You're receiving notifications because you're subscribed to this thread.
+
+ :javascript
+ new Subscription("#{toggle_subscription_path(issuable)}");
+ new IssuableContext();
diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml
index 7eb27c12d33..6071f1484c6 100644
--- a/app/views/votes/_votes_block.html.haml
+++ b/app/views/votes/_votes_block.html.haml
@@ -19,7 +19,8 @@
post_emoji_url = "#{award_toggle_namespace_project_notes_path(@project.namespace, @project)}"
noteable_type = "#{votable.class.name.underscore}"
noteable_id = "#{votable.id}"
- window.awards_handler = new AwardsHandler(post_emoji_url, noteable_type, noteable_id)
+ aliases = #{AwardEmoji::ALIASES.to_json}
+ window.awards_handler = new AwardsHandler(post_emoji_url, noteable_type, noteable_id, aliases)
$(".awards-menu li").click (e)->
emoji = $(this).data("emoji")
diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb
index 6e5701e33da..2e3a71912ef 100644
--- a/config/initializers/sidekiq.rb
+++ b/config/initializers/sidekiq.rb
@@ -23,6 +23,14 @@ Sidekiq.configure_server do |config|
if File.exists?(schedule_file)
Sidekiq::Cron::Job.load_from_hash YAML.load_file(schedule_file)
end
+
+ # Database pool should be at least `sidekiq_concurrency` + 2
+ # For more info, see: https://github.com/mperham/sidekiq/blob/master/4.0-Upgrade.md
+ config = ActiveRecord::Base.configurations[Rails.env] ||
+ Rails.application.config.database_configuration[Rails.env]
+ config['pool'] = Sidekiq.options[:concurrency] + 2
+ ActiveRecord::Base.establish_connection(config)
+ Rails.logger.debug("Connection Pool size for Sidekiq Server is now: #{ActiveRecord::Base.connection.pool.instance_variable_get('@size')}")
end
Sidekiq.configure_client do |config|
diff --git a/doc/api/settings.md b/doc/api/settings.md
index d1b93a09c02..96867c67915 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -57,7 +57,7 @@ Parameters:
- `default_project_visibility` - what visibility level new project receives
- `default_snippet_visibility` - what visibility level new snippet receives
- `restricted_signup_domains` - force people to use only corporate emails for signup
-- `user_oauth_applications` - allow users to create oauth applicaitons
+- `user_oauth_applications` - allow users to create oauth applications
- `after_sign_out_path` - where redirect user after logout
All parameters are optional. You can send only one that you want to change.
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 3dbf1afc7a9..7e2edb945da 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -113,6 +113,9 @@ The YAML-defined variables are also set to all created service containers, thus
### cache
`cache` is used to specify list of files and directories which should be cached between builds.
+Caches are stored according to the branch/ref and the job name. Caches are not
+currently shared between different job names or between branches/refs. This means
+caching will benefit you if you push subsequent commits to an existing feature branch.
**The global setting allows to specify default cached files for all jobs.**
diff --git a/doc/development/db_dump.md b/doc/development/db_dump.md
index 21f1b3edecd..e4ff72aa349 100644
--- a/doc/development/db_dump.md
+++ b/doc/development/db_dump.md
@@ -1,4 +1,4 @@
-# Importing a database dump into a staging enviroment
+# Importing a database dump into a staging environment
Sometimes it is useful to import the database from a production environment
into a staging environment for testing. The procedure below assumes you have
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 618391e16d2..0a19a060a9a 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -62,7 +62,7 @@ up-to-date and install it.
Install the required packages (needed to compile Ruby and native extensions to Ruby gems):
- sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl openssh-server redis-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev logrotate python-docutils pkg-config cmake nodejs
+ sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl openssh-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev logrotate python-docutils pkg-config cmake nodejs
If you want to use Kerberos for user authentication, then install libkrb5-dev:
@@ -174,7 +174,23 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
## 6. Redis
- sudo apt-get install redis-server
+As of this writing, most Debian/Ubuntu distributions ship with Redis 2.2 or
+2.4. GitLab requires at least Redis 2.8. If your platform doesn't provide
+this, the following instructions cover building and installing Redis from
+scratch.
+
+Ubuntu users [can also use a PPA](https://launchpad.net/~chris-lea/+archive/ubuntu/redis-server)
+to install a recent version of Redis.
+
+ # Build Redis
+ wget http://download.redis.io/releases/redis-2.8.23.tar.gz
+ tar xzf redis-2.8.23.tar.gz
+ cd redis-2.8.23
+ make
+
+ # Install Redis
+ cd utils
+ sudo ./install_server.sh
# Configure redis to use sockets
sudo cp /etc/redis/redis.conf /etc/redis/redis.conf.orig
@@ -197,7 +213,7 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
fi
# Activate the changes to redis.conf
- sudo service redis-server restart
+ sudo service redis_6379 start
# Add git to the redis group
sudo usermod -aG redis git
diff --git a/doc/integration/saml.md b/doc/integration/saml.md
index 1b8c28dd0f4..1632e42f701 100644
--- a/doc/integration/saml.md
+++ b/doc/integration/saml.md
@@ -38,7 +38,8 @@ First configure SAML 2.0 support in GitLab, then register the GitLab application
idp_sso_target_url: 'https://login.example.com/idp',
issuer: 'https://gitlab.example.com',
name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
- }
+ },
+ "label" => "Company Login" # optional label for SAML login button, defaults to "Saml"
}
]
```
@@ -79,4 +80,4 @@ On the sign in page there should now be a SAML button below the regular sign in
If you see a "500 error" in GitLab when you are redirected back from the SAML sign in page, this likely indicates that GitLab could not get the email address for the SAML user.
-Make sure the IdP provides a claim containing the user's email address, using claim name 'email' or 'mail'. The email will be used to automatically generate the GitLab username.
+Make sure the IdP provides a claim containing the user's email address, using claim name 'email' or 'mail'. The email will be used to automatically generate the GitLab username. \ No newline at end of file
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index b4d2786bd76..093450a6de3 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -357,8 +357,8 @@ If you are using backup restore procedures you might encounter the following war
```
psql:/var/opt/gitlab/backups/db/database.sql:22: ERROR: must be owner of extension plpgsql
-psql:/var/opt/gitlab/backups/db/database.sql:2931: WARNING: no privileges could be revoked for "public" (two occurences)
-psql:/var/opt/gitlab/backups/db/database.sql:2933: WARNING: no privileges were granted for "public" (two occurences)
+psql:/var/opt/gitlab/backups/db/database.sql:2931: WARNING: no privileges could be revoked for "public" (two occurrences)
+psql:/var/opt/gitlab/backups/db/database.sql:2933: WARNING: no privileges were granted for "public" (two occurrences)
```
diff --git a/doc/release/monthly.md b/doc/release/monthly.md
index aff3f066b24..907c19e65a0 100644
--- a/doc/release/monthly.md
+++ b/doc/release/monthly.md
@@ -176,7 +176,7 @@ Tweet about the RC release:
1. Also check the CI changelog
1. Add a proposed tweet text to the blog post WIP MR description.
1. Create a WIP MR for the blog post
-1. Make sure merge request title starts with `WIP` so it can not be accidently merged until ready.
+1. Make sure merge request title starts with `WIP` so it can not be accidentally merged until ready.
1. Ask Dmitriy (or a team member with OS X) to add screenshots to the WIP MR.
1. Decide with core team who will be the MVP user.
1. Create WIP MR for adding MVP to MVP page on website
diff --git a/doc/security/README.md b/doc/security/README.md
index 473f3632dcd..fba6013d9c1 100644
--- a/doc/security/README.md
+++ b/doc/security/README.md
@@ -4,4 +4,5 @@
- [Rack attack](rack_attack.md)
- [Web Hooks and insecure internal web services](webhooks.md)
- [Information exclusivity](information_exclusivity.md)
-- [Reset your root password](reset_root_password.md) \ No newline at end of file
+- [Reset your root password](reset_root_password.md)
+- [User File Uploads](user_file_uploads.md)
diff --git a/doc/security/user_file_uploads.md b/doc/security/user_file_uploads.md
new file mode 100644
index 00000000000..98493d33b00
--- /dev/null
+++ b/doc/security/user_file_uploads.md
@@ -0,0 +1,11 @@
+# User File Uploads
+
+Images attached to issues, merge requests or comments do not require authentication
+to be viewed if someone knows the direct URL. This direct URL contains a random
+32-character ID that prevents unauthorized people from guessing the URL to an
+image containing sensitive information. We don't enable authentication because
+these images need to be visible in the body of notification emails, which are
+often read from email clients that are not authenticated with GitLab, like
+Outlook, Apple Mail, or the Mail app on your mobile device.
+
+Note that non-image attachments do require authentication to be viewed.
diff --git a/doc/update/8.1-to-8.2.md b/doc/update/8.1-to-8.2.md
index b08a79ca0aa..46dfa2232b4 100644
--- a/doc/update/8.1-to-8.2.md
+++ b/doc/update/8.1-to-8.2.md
@@ -142,7 +142,7 @@ git diff origin/8-1-stable:lib/support/nginx/gitlab origin/8-2-stable:lib/suppor
If you are using Apache instead of NGINX please see the updated [Apache templates].
Also note that because Apache does not support upstreams behind Unix sockets you
-will need to let gitlab-git-http-server listen on a TCP port. You can do this
+will need to let gitlab-workhorse listen on a TCP port. You can do this
via [/etc/default/gitlab].
[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache
diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md
index 03746dd9df3..6420d65cf1b 100644
--- a/doc/web_hooks/web_hooks.md
+++ b/doc/web_hooks/web_hooks.md
@@ -188,7 +188,7 @@ X-Gitlab-Event: Note Hook
{
"object_kind": "note",
"user": {
- "name": "Adminstrator",
+ "name": "Administrator",
"username": "root",
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
},
@@ -341,7 +341,7 @@ X-Gitlab-Event: Note Hook
{
"object_kind": "note",
"user": {
- "name": "Adminstrator",
+ "name": "Administrator",
"username": "root",
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
},
diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature
index 35ace948888..aa9078b878f 100644
--- a/features/project/merge_requests.feature
+++ b/features/project/merge_requests.feature
@@ -85,6 +85,26 @@ Feature: Project Merge Requests
Then I should see a discussion has started on diff
@javascript
+ Scenario: I edit a comment on a merge request diff
+ Given project "Shop" have "Bug NS-05" open merge request with diffs inside
+ And I visit merge request page "Bug NS-05"
+ And I click on the Changes tab
+ And I leave a comment like "Line is wrong" on diff
+ And I change the comment "Line is wrong" to "Typo, please fix" on diff
+ Then I should not see a diff comment saying "Line is wrong"
+ And I should see a diff comment saying "Typo, please fix"
+
+ @javascript
+ Scenario: I delete a comment on a merge request diff
+ Given project "Shop" have "Bug NS-05" open merge request with diffs inside
+ And I visit merge request page "Bug NS-05"
+ And I click on the Changes tab
+ And I leave a comment like "Line is wrong" on diff
+ And I delete the comment "Line is wrong" on diff
+ And I click on the Discussion tab
+ Then I should not see any discussion
+
+ @javascript
Scenario: I comment on a line of a commit in merge request
Given project "Shop" have "Bug NS-05" open merge request with diffs inside
And I visit merge request page "Bug NS-05"
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index 0107d9d8486..f0cf4818d0c 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -186,6 +186,50 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
leave_comment "Line is wrong"
end
+ step 'I change the comment "Line is wrong" to "Typo, please fix" on diff' do
+ page.within('.diff-file:nth-of-type(5) .note') do
+ find('.js-note-edit').click
+
+ page.within('.current-note-edit-form', visible: true) do
+ fill_in 'note_note', with: 'Typo, please fix'
+ click_button 'Save Comment'
+ end
+
+ expect(page).not_to have_button 'Save Comment', disabled: true, visible: true
+ end
+ end
+
+ step 'I should not see a diff comment saying "Line is wrong"' do
+ page.within('.diff-file:nth-of-type(5) .note') do
+ expect(page).not_to have_visible_content 'Line is wrong'
+ end
+ end
+
+ step 'I should see a diff comment saying "Typo, please fix"' do
+ page.within('.diff-file:nth-of-type(5) .note') do
+ expect(page).to have_visible_content 'Typo, please fix'
+ end
+ end
+
+ step 'I delete the comment "Line is wrong" on diff' do
+ page.within('.diff-file:nth-of-type(5) .note') do
+ find('.js-note-delete').click
+ end
+ end
+
+ step 'I click on the Discussion tab' do
+ page.within '.merge-request-tabs' do
+ click_link 'Discussion'
+ end
+
+ # Waits for load
+ expect(page).to have_css('.tab-content #notes.active')
+ end
+
+ step 'I should not see any discussion' do
+ expect(page).not_to have_css('.notes .discussion')
+ end
+
step 'I should see a discussion has started on diff' do
page.within(".notes .discussion") do
page.should have_content "#{current_user.name} started a discussion"
diff --git a/lib/award_emoji.rb b/lib/award_emoji.rb
index d58a196c4ef..4d99164bc33 100644
--- a/lib/award_emoji.rb
+++ b/lib/award_emoji.rb
@@ -6,7 +6,42 @@ class AwardEmoji
"ambulance", "anguished", "two_hearts", "wink"
]
+ ALIASES = {
+ pout: "rage",
+ satisfied: "laughing",
+ hankey: "shit",
+ poop: "shit",
+ collision: "boom",
+ thumbsup: "+1",
+ thumbsdown: "-1",
+ punch: "facepunch",
+ raised_hand: "hand",
+ running: "runner",
+ ng_woman: "no_good",
+ shoe: "mans_shoe",
+ tshirt: "shirt",
+ honeybee: "bee",
+ flipper: "dolphin",
+ paw_prints: "feet",
+ waxing_gibbous_moon: "moon",
+ telephone: "phone",
+ knife: "hocho",
+ envelope: "email",
+ pencil: "memo",
+ open_book: "book",
+ sailboat: "boat",
+ red_car: "car",
+ lantern: "izakaya_lantern",
+ uk: "gb",
+ heavy_exclamation_mark: "exclamation",
+ squirrel: "shipit"
+ }.with_indifferent_access
+
def self.path_to_emoji_image(name)
"emoji/#{Emoji.emoji_filename(name)}.png"
end
+
+ def self.normilize_emoji_name(name)
+ ALIASES[name] || name
+ end
end
diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb
index e7fb1b79b7c..bcdfd38d292 100644
--- a/lib/ci/gitlab_ci_yaml_processor.rb
+++ b/lib/ci/gitlab_ci_yaml_processor.rb
@@ -132,26 +132,36 @@ module Ci
end
def validate_job!(name, job)
+ validate_job_name!(name)
+ validate_job_keys!(name, job)
+ validate_job_types!(name, job)
+
+ validate_job_stage!(name, job) if job[:stage]
+ validate_job_cache!(name, job) if job[:cache]
+ validate_job_artifacts!(name, job) if job[:artifacts]
+ end
+
+ private
+
+ def validate_job_name!(name)
if name.blank? || !validate_string(name)
raise ValidationError, "job name should be non-empty string"
end
+ end
+ def validate_job_keys!(name, job)
job.keys.each do |key|
unless ALLOWED_JOB_KEYS.include? key
raise ValidationError, "#{name} job: unknown parameter #{key}"
end
end
+ end
+ def validate_job_types!(name, job)
if !validate_string(job[:script]) && !validate_array_of_strings(job[:script])
raise ValidationError, "#{name} job: script should be a string or an array of a strings"
end
- if job[:stage]
- unless job[:stage].is_a?(String) && job[:stage].in?(stages)
- raise ValidationError, "#{name} job: stage parameter should be #{stages.join(", ")}"
- end
- end
-
if job[:image] && !validate_string(job[:image])
raise ValidationError, "#{name} job: image should be a string"
end
@@ -172,36 +182,40 @@ module Ci
raise ValidationError, "#{name} job: except parameter should be an array of strings"
end
- if job[:cache]
- if job[:cache][:untracked] && !validate_boolean(job[:cache][:untracked])
- raise ValidationError, "#{name} job: cache:untracked parameter should be an boolean"
- end
-
- if job[:cache][:paths] && !validate_array_of_strings(job[:cache][:paths])
- raise ValidationError, "#{name} job: cache:paths parameter should be an array of strings"
- end
+ if job[:allow_failure] && !validate_boolean(job[:allow_failure])
+ raise ValidationError, "#{name} job: allow_failure parameter should be an boolean"
end
- if job[:artifacts]
- if job[:artifacts][:untracked] && !validate_boolean(job[:artifacts][:untracked])
- raise ValidationError, "#{name} job: artifacts:untracked parameter should be an boolean"
- end
+ if job[:when] && !job[:when].in?(%w(on_success on_failure always))
+ raise ValidationError, "#{name} job: when parameter should be on_success, on_failure or always"
+ end
+ end
- if job[:artifacts][:paths] && !validate_array_of_strings(job[:artifacts][:paths])
- raise ValidationError, "#{name} job: artifacts:paths parameter should be an array of strings"
- end
+ def validate_job_stage!(name, job)
+ unless job[:stage].is_a?(String) && job[:stage].in?(stages)
+ raise ValidationError, "#{name} job: stage parameter should be #{stages.join(", ")}"
end
+ end
- if job[:allow_failure] && !validate_boolean(job[:allow_failure])
- raise ValidationError, "#{name} job: allow_failure parameter should be an boolean"
+ def validate_job_cache!(name, job)
+ if job[:cache][:untracked] && !validate_boolean(job[:cache][:untracked])
+ raise ValidationError, "#{name} job: cache:untracked parameter should be an boolean"
end
- if job[:when] && !job[:when].in?(%w(on_success on_failure always))
- raise ValidationError, "#{name} job: when parameter should be on_success, on_failure or always"
+ if job[:cache][:paths] && !validate_array_of_strings(job[:cache][:paths])
+ raise ValidationError, "#{name} job: cache:paths parameter should be an array of strings"
end
end
- private
+ def validate_job_artifacts!(name, job)
+ if job[:artifacts][:untracked] && !validate_boolean(job[:artifacts][:untracked])
+ raise ValidationError, "#{name} job: artifacts:untracked parameter should be an boolean"
+ end
+
+ if job[:artifacts][:paths] && !validate_array_of_strings(job[:artifacts][:paths])
+ raise ValidationError, "#{name} job: artifacts:paths parameter should be an array of strings"
+ end
+ end
def validate_array_of_strings(values)
values.is_a?(Array) && values.all? { |value| validate_string(value) }
diff --git a/lib/gitlab/email/message/repository_push.rb b/lib/gitlab/email/message/repository_push.rb
new file mode 100644
index 00000000000..a2eb7a70bd2
--- /dev/null
+++ b/lib/gitlab/email/message/repository_push.rb
@@ -0,0 +1,137 @@
+module Gitlab
+ module Email
+ module Message
+ class RepositoryPush
+ attr_accessor :recipient
+ attr_reader :author_id, :ref, :action
+
+ include Gitlab::Application.routes.url_helpers
+
+ delegate :namespace, :name_with_namespace, to: :project, prefix: :project
+ delegate :name, to: :author, prefix: :author
+
+ def initialize(notify, project_id, recipient, opts = {})
+ raise ArgumentError, 'Missing options: author_id, ref, action' unless
+ opts[:author_id] && opts[:ref] && opts[:action]
+
+ @notify = notify
+ @project_id = project_id
+ @recipient = recipient
+ @opts = opts.dup
+
+ @author_id = @opts.delete(:author_id)
+ @ref = @opts.delete(:ref)
+ @action = @opts.delete(:action)
+ end
+
+ def project
+ @project ||= Project.find(@project_id)
+ end
+
+ def author
+ @author ||= User.find(@author_id)
+ end
+
+ def commits
+ @commits ||= (Commit.decorate(compare.commits, project) if compare)
+ end
+
+ def diffs
+ @diffs ||= (compare.diffs if compare)
+ end
+
+ def diffs_count
+ diffs.count if diffs
+ end
+
+ def compare
+ @opts[:compare]
+ end
+
+ def compare_timeout
+ compare.timeout if compare
+ end
+
+ def reverse_compare?
+ @opts[:reverse_compare] || false
+ end
+
+ def disable_diffs?
+ @opts[:disable_diffs] || false
+ end
+
+ def send_from_committer_email?
+ @opts[:send_from_committer_email] || false
+ end
+
+ def action_name
+ @action_name ||=
+ case @action
+ when :create
+ 'pushed new'
+ when :delete
+ 'deleted'
+ else
+ 'pushed to'
+ end
+ end
+
+ def ref_name
+ @ref_name ||= Gitlab::Git.ref_name(@ref)
+ end
+
+ def ref_type
+ @ref_type ||= Gitlab::Git.tag_ref?(@ref) ? 'tag' : 'branch'
+ end
+
+ def target_url
+ if @action == :push && commits
+ if commits.length > 1
+ namespace_project_compare_url(project_namespace,
+ project,
+ from: Commit.new(compare.base, project),
+ to: Commit.new(compare.head, project))
+ else
+ namespace_project_commit_url(project_namespace,
+ project, commits.first)
+ end
+ else
+ unless @action == :delete
+ namespace_project_tree_url(project_namespace,
+ project, ref_name)
+ end
+ end
+ end
+
+ def reply_to
+ if send_from_committer_email? && @notify.can_send_from_user_email?(author)
+ author.email
+ else
+ Gitlab.config.gitlab.email_reply_to
+ end
+ end
+
+ def subject
+ subject_text = '[Git]'
+ subject_text << "[#{project.path_with_namespace}]"
+ subject_text << "[#{ref_name}]" if @action == :push
+ subject_text << ' '
+
+ if @action == :push && commits
+ if commits.length > 1
+ subject_text << "Deleted " if reverse_compare?
+ subject_text << "#{commits.length} commits: #{commits.first.title}"
+ else
+ subject_text << "Deleted 1 commit: " if reverse_compare?
+ subject_text << commits.first.title
+ end
+ else
+ subject_action = action_name.dup
+ subject_action[0] = subject_action[0].capitalize
+ subject_text << "#{subject_action} #{ref_type} #{ref_name}"
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/o_auth/auth_hash.rb b/lib/gitlab/o_auth/auth_hash.rb
index d94b104bbf8..ba31599432b 100644
--- a/lib/gitlab/o_auth/auth_hash.rb
+++ b/lib/gitlab/o_auth/auth_hash.rb
@@ -62,7 +62,7 @@ module Gitlab
# Get the first part of the email address (before @)
# In addtion in removes illegal characters
def generate_username(email)
- email.match(/^[^@]*/)[0].parameterize
+ email.match(/^[^@]*/)[0].mb_chars.normalize(:kd).gsub(/[^\x00-\x7F]/,'').to_s
end
def generate_temporarily_email(username)
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index a25fac62cfc..b5af3d88b4c 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -331,7 +331,7 @@ namespace :gitlab do
end
def check_redis_version
- min_redis_version = "2.4.0"
+ min_redis_version = "2.8.0"
print "Redis version >= #{min_redis_version}? ... "
redis_version = run(%W(redis-cli --version))
diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb
index 52134556339..dc41be8246f 100644
--- a/spec/features/atom/users_spec.rb
+++ b/spec/features/atom/users_spec.rb
@@ -79,6 +79,6 @@ describe "User Feed", feature: true do
end
def safe_name
- CGI.escapeHTML(user.name)
+ html_escape(user.name)
end
end
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index 3d6d87e764a..a2fb3e4c75d 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -69,7 +69,10 @@ describe 'Issues', feature: true do
click_button 'Save changes'
- expect(page).to have_content 'Assignee: none'
+ page.within('.assignee') do
+ expect(page).to have_content 'None'
+ end
+
expect(issue.reload.assignee).to be_nil
end
end
@@ -202,11 +205,11 @@ describe 'Issues', feature: true do
it 'with dropdown menu' do
visit namespace_project_issue_path(project.namespace, project, issue)
- find('.context #issue_assignee_id').
+ find('.issuable-sidebar #issue_assignee_id').
set project.team.members.first.id
click_button 'Update Issue'
- expect(page).to have_content 'Assignee:'
+ expect(page).to have_content 'Assignee'
has_select?('issue_assignee_id',
selected: project.team.members.first.name)
end
@@ -241,12 +244,16 @@ describe 'Issues', feature: true do
it 'with dropdown menu' do
visit namespace_project_issue_path(project.namespace, project, issue)
- find('.context').
+ find('.issuable-sidebar').
select(milestone.title, from: 'issue_milestone_id')
click_button 'Update Issue'
expect(page).to have_content "Milestone changed to #{milestone.title}"
- expect(page).to have_content "Milestone: #{milestone.title}"
+
+ page.within('.milestone') do
+ expect(page).to have_content milestone.title
+ end
+
has_select?('issue_assignee_id', selected: milestone.title)
end
end
@@ -279,13 +286,19 @@ describe 'Issues', feature: true do
it 'allows user to remove assignee', js: true do
visit namespace_project_issue_path(project.namespace, project, issue)
- expect(page).to have_content "Assignee: #{user2.name}"
- first('#s2id_issue_assignee_id').click
+ page.within('.assignee') do
+ expect(page).to have_content user2.name
+ end
+
+ find('.assignee .edit-link').click
sleep 2 # wait for ajax stuff to complete
first('.user-result').click
- expect(page).to have_content 'Assignee: none'
+ page.within('.assignee') do
+ expect(page).to have_content 'None'
+ end
+
sleep 2 # wait for ajax stuff to complete
expect(issue.reload.assignee).to be_nil
end
diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
index ce4a5244bd0..c90133fbf03 100644
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
@@ -532,21 +532,21 @@ module Ci
end
it "returns errors if job stage is not a string" do
- config = YAML.dump({ rspec: { script: "test", type: 1, allow_failure: "string" } })
+ config = YAML.dump({ rspec: { script: "test", type: 1 } })
expect do
GitlabCiYamlProcessor.new(config, path)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: stage parameter should be build, test, deploy")
end
it "returns errors if job stage is not a pre-defined stage" do
- config = YAML.dump({ rspec: { script: "test", type: "acceptance", allow_failure: "string" } })
+ config = YAML.dump({ rspec: { script: "test", type: "acceptance" } })
expect do
GitlabCiYamlProcessor.new(config, path)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: stage parameter should be build, test, deploy")
end
it "returns errors if job stage is not a defined stage" do
- config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", type: "acceptance", allow_failure: "string" } })
+ config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", type: "acceptance" } })
expect do
GitlabCiYamlProcessor.new(config, path)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: stage parameter should be build, test")
diff --git a/spec/lib/gitlab/email/message/repository_push_spec.rb b/spec/lib/gitlab/email/message/repository_push_spec.rb
new file mode 100644
index 00000000000..56ae2a8d121
--- /dev/null
+++ b/spec/lib/gitlab/email/message/repository_push_spec.rb
@@ -0,0 +1,122 @@
+require 'spec_helper'
+
+describe Gitlab::Email::Message::RepositoryPush do
+ include RepoHelpers
+
+ let!(:group) { create(:group, name: 'my_group') }
+ let!(:project) { create(:project, name: 'my_project', namespace: group) }
+ let!(:author) { create(:author, name: 'Author') }
+
+ let(:message) do
+ described_class.new(Notify, project.id, 'recipient@example.com', opts)
+ end
+
+ context 'new commits have been pushed to repository' do
+ let(:opts) do
+ { author_id: author.id, ref: 'master', action: :push, compare: compare,
+ send_from_committer_email: true }
+ end
+ let(:compare) do
+ Gitlab::Git::Compare.new(project.repository.raw_repository,
+ sample_image_commit.id, sample_commit.id)
+ end
+
+ describe '#project' do
+ subject { message.project }
+ it { is_expected.to eq project }
+ it { is_expected.to be_an_instance_of Project }
+ end
+
+ describe '#project_namespace' do
+ subject { message.project_namespace }
+ it { is_expected.to eq group }
+ it { is_expected.to be_kind_of Namespace }
+ end
+
+ describe '#project_name_with_namespace' do
+ subject { message.project_name_with_namespace }
+ it { is_expected.to eq 'my_group / my_project' }
+ end
+
+ describe '#author' do
+ subject { message.author }
+ it { is_expected.to eq author }
+ it { is_expected.to be_an_instance_of User }
+ end
+
+ describe '#author_name' do
+ subject { message.author_name }
+ it { is_expected.to eq 'Author' }
+ end
+
+ describe '#commits' do
+ subject { message.commits }
+ it { is_expected.to be_kind_of Array }
+ it { is_expected.to all(be_instance_of Commit) }
+ end
+
+ describe '#diffs' do
+ subject { message.diffs }
+ it { is_expected.to all(be_an_instance_of Gitlab::Git::Diff) }
+ end
+
+ describe '#diffs_count' do
+ subject { message.diffs_count }
+ it { is_expected.to eq compare.diffs.count }
+ end
+
+ describe '#compare' do
+ subject { message.compare }
+ it { is_expected.to be_an_instance_of Gitlab::Git::Compare }
+ end
+
+ describe '#compare_timeout' do
+ subject { message.compare_timeout }
+ it { is_expected.to eq compare.timeout }
+ end
+
+ describe '#reverse_compare?' do
+ subject { message.reverse_compare? }
+ it { is_expected.to eq false }
+ end
+
+ describe '#disable_diffs?' do
+ subject { message.disable_diffs? }
+ it { is_expected.to eq false }
+ end
+
+ describe '#send_from_committer_email?' do
+ subject { message.send_from_committer_email? }
+ it { is_expected.to eq true }
+ end
+
+ describe '#action_name' do
+ subject { message.action_name }
+ it { is_expected.to eq 'pushed to' }
+ end
+
+ describe '#ref_name' do
+ subject { message.ref_name }
+ it { is_expected.to eq 'master' }
+ end
+
+ describe '#ref_type' do
+ subject { message.ref_type }
+ it { is_expected.to eq 'branch' }
+ end
+
+ describe '#target_url' do
+ subject { message.target_url }
+ it { is_expected.to include 'compare' }
+ it { is_expected.to include compare.commits.first.parents.first.id }
+ it { is_expected.to include compare.commits.last.id }
+ end
+
+ describe '#subject' do
+ subject { message.subject }
+ it { is_expected.to include "[Git][#{project.path_with_namespace}]" }
+ it { is_expected.to include "#{compare.commits.length} commits" }
+ it { is_expected.to include compare.commits.first.message.split("\n").first }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/o_auth/auth_hash_spec.rb b/spec/lib/gitlab/o_auth/auth_hash_spec.rb
index a4f8b44e38e..8aaeb5779d3 100644
--- a/spec/lib/gitlab/o_auth/auth_hash_spec.rb
+++ b/spec/lib/gitlab/o_auth/auth_hash_spec.rb
@@ -14,7 +14,7 @@ describe Gitlab::OAuth::AuthHash, lib: true do
let(:uid_raw) do
"CN=Onur K\xC3\xBC\xC3\xA7\xC3\xBCk,OU=Test,DC=example,DC=net"
end
- let(:email_raw) { "onur.k\xC3\xBC\xC3\xA7\xC3\xBCk@example.net" }
+ let(:email_raw) { "onur.k\xC3\xBC\xC3\xA7\xC3\xBCk_ABC-123@example.net" }
let(:nickname_raw) { "ok\xC3\xBC\xC3\xA7\xC3\xBCk" }
let(:first_name_raw) { 'Onur' }
let(:last_name_raw) { "K\xC3\xBC\xC3\xA7\xC3\xBCk" }
@@ -66,7 +66,7 @@ describe Gitlab::OAuth::AuthHash, lib: true do
before { info_hash.delete(:nickname) }
it 'takes the first part of the email as username' do
- expect(auth_hash.username).to eql 'onur-kucuk'
+ expect(auth_hash.username).to eql 'onur.kucuk_ABC-123'
end
end
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index cd3c868ecc5..216c7dabae0 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -142,4 +142,30 @@ describe Note, models: true do
expect(Note.grouped_awards.first.last).to match_array(Note.all)
end
end
+
+ describe "editable?" do
+ it "returns true" do
+ note = build(:note)
+ expect(note.editable?).to be_truthy
+ end
+
+ it "returns false" do
+ note = build(:note, system: true)
+ expect(note.editable?).to be_falsy
+ end
+
+ it "returns false" do
+ note = build(:note, is_award: true, note: "smiley")
+ expect(note.editable?).to be_falsy
+ end
+ end
+
+ describe "set_award!" do
+ let(:issue) { create :issue }
+
+ it "converts aliases to actual name" do
+ note = create :note, note: ":thumbsup:", noteable: issue
+ expect(note.reload.note).to eq("+1")
+ end
+ end
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index e6c415da267..afbf62035ac 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -103,6 +103,26 @@ describe Repository, models: true do
end
+ describe "#license" do
+ before do
+ repository.send(:cache).expire(:license)
+ TestBlob = Struct.new(:name)
+ end
+
+ it 'test selection preference' do
+ files = [TestBlob.new('file'), TestBlob.new('license'), TestBlob.new('copying')]
+ expect(repository.tree).to receive(:blobs).and_return(files)
+
+ expect(repository.license.name).to eq('license')
+ end
+
+ it 'also accepts licence instead of license' do
+ expect(repository.tree).to receive(:blobs).and_return([TestBlob.new('licence')])
+
+ expect(repository.license.name).to eq('licence')
+ end
+ end
+
describe :add_branch do
context 'when pre hooks were successful' do
it 'should run without errors' do
@@ -199,5 +219,4 @@ describe Repository, models: true do
end
end
end
-
end
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index 2d8c316e38d..5d0b18558b1 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -49,6 +49,13 @@ describe Projects::CreateService, services: true do
it { expect(@project.namespace).to eq(@group) }
end
+ context 'error handling' do
+ it 'handles invalid options' do
+ @opts.merge!({ default_branch: 'master' } )
+ expect(create_project(@user, @opts)).to eq(nil)
+ end
+ end
+
context 'wiki_enabled creates repository directory' do
context 'wiki_enabled true creates wiki repository directory' do
before do
diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb
index 33d2b14583c..fce91015fd4 100644
--- a/spec/support/mentionable_shared_examples.rb
+++ b/spec/support/mentionable_shared_examples.rb
@@ -4,7 +4,7 @@
# - let(:backref_text) { "the way that +subject+ should refer to itself in backreferences " }
# - let(:set_mentionable_text) { lambda { |txt| "block that assigns txt to the subject's mentionable_text" } }
-def common_mentionable_setup
+shared_context 'mentionable context' do
let(:project) { subject.project }
let(:author) { subject.author }
@@ -56,7 +56,7 @@ def common_mentionable_setup
end
shared_examples 'a mentionable' do
- common_mentionable_setup
+ include_context 'mentionable context'
it 'generates a descriptive back-reference' do
expect(subject.gfm_reference).to eq(backref_text)
@@ -88,7 +88,7 @@ shared_examples 'a mentionable' do
end
shared_examples 'an editable mentionable' do
- common_mentionable_setup
+ include_context 'mentionable context'
it_behaves_like 'a mentionable'