Commit 21ba6790 authored by James Lopez's avatar James Lopez

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ee into feature/improve-weight-filter

parents b62d30d4 568fe1e2
...@@ -2,11 +2,17 @@ image: "ruby:2.2" ...@@ -2,11 +2,17 @@ image: "ruby:2.2"
services: services:
- mysql:latest - mysql:latest
- postgres:latest
- redis:latest - redis:latest
- elasticsearch:latest
cache:
key: "ruby22"
paths:
- vendor
variables: variables:
MYSQL_ALLOW_EMPTY_PASSWORD: "1" MYSQL_ALLOW_EMPTY_PASSWORD: "1"
ELASTIC_HOST: "elasticsearch"
before_script: before_script:
- source ./scripts/prepare_build.sh - source ./scripts/prepare_build.sh
...@@ -36,21 +42,17 @@ spec:api: ...@@ -36,21 +42,17 @@ spec:api:
spec:models: spec:models:
script: script:
- RAILS_ENV=test SIMPLECOV=true ELASTIC_HOST=elasticsearch bundle exec rake spec:models - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:models
tags: tags:
- ruby - ruby
- mysql - mysql
services:
- elasticsearch:latest
spec:lib: spec:lib:
script: script:
- RAILS_ENV=test SIMPLECOV=true ELASTIC_HOST=elasticsearch bundle exec rake spec:lib - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:lib
tags: tags:
- ruby - ruby
- mysql - mysql
services:
- elasticsearch:latest
spec:services: spec:services:
script: script:
...@@ -69,12 +71,10 @@ spec:benchmark: ...@@ -69,12 +71,10 @@ spec:benchmark:
spec:other: spec:other:
script: script:
- RAILS_ENV=test SIMPLECOV=true ELASTIC_HOST=elasticsearch bundle exec rake spec:other - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:other
tags: tags:
- ruby - ruby
- mysql - mysql
services:
- elasticsearch:latest
spinach:project:half: spinach:project:half:
script: script:
...@@ -85,12 +85,10 @@ spinach:project:half: ...@@ -85,12 +85,10 @@ spinach:project:half:
spinach:project:rest: spinach:project:rest:
script: script:
- RAILS_ENV=test SIMPLECOV=true ELASTIC_HOST=elasticsearch bundle exec rake spinach:project:rest - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest
tags: tags:
- ruby - ruby
- mysql - mysql
services:
- elasticsearch:latest
spinach:other: spinach:other:
script: script:
...@@ -145,23 +143,145 @@ bundler:audit: ...@@ -145,23 +143,145 @@ bundler:audit:
# Ruby 2.1 jobs # Ruby 2.1 jobs
spec:ruby21: spec:feature:ruby21:
image: ruby:2.1 image: ruby:2.1
only:
- master
script: script:
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:feature
cache:
key: "ruby21"
paths:
- vendor
tags:
- ruby
- mysql
spec:api:ruby21:
image: ruby:2.1
only:
- master
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:api
cache:
key: "ruby21"
paths:
- vendor
tags:
- ruby
- mysql
spec:models:ruby21:
image: ruby:2.1
only:
- master
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:models
cache:
key: "ruby21"
paths:
- vendor
tags:
- ruby
- mysql
spec:lib:ruby21:
image: ruby:2.1
only:
- master
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:lib
cache:
key: "ruby21"
paths:
- vendor
tags:
- ruby
- mysql
spec:services:ruby21:
image: ruby:2.1
only:
- master
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:services
cache:
key: "ruby21"
paths:
- vendor
tags: tags:
- ruby - ruby
- mysql - mysql
spec:benchmark:ruby21:
image: ruby:2.1
only: only:
- master - master
script:
- RAILS_ENV=test bundle exec rake spec:benchmark
cache:
key: "ruby21"
paths:
- vendor
tags:
- ruby
- mysql
allow_failure: true
spinach:ruby21: spec:other:ruby21:
image: ruby:2.1 image: ruby:2.1
only:
- master
script: script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:other
cache:
key: "ruby21"
paths:
- vendor
tags: tags:
- ruby - ruby
- mysql - mysql
spinach:project:half:ruby21:
image: ruby:2.1
only: only:
- master - master
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:half
cache:
key: "ruby21"
paths:
- vendor
tags:
- ruby
- mysql
spinach:project:rest:ruby21:
image: ruby:2.1
only:
- master
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest
cache:
key: "ruby21"
paths:
- vendor
tags:
- ruby
- mysql
spinach:other:ruby21:
image: ruby:2.1
only:
- master
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:other
cache:
key: "ruby21"
paths:
- vendor
tags:
- ruby
- mysql
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.5.0 (unreleased) v 8.5.0 (unreleased)
- Fix Elasticsearch blob results linking to the wrong reference ID (Stan Hu)
- Fix duplicate "me" in tooltip of the "thumbsup" awards Emoji (Stan Hu)
- Cache various Repository methods to improve performance (Yorick Peterse)
- Fix duplicated branch creation/deletion Web hooks/service notifications when using Web UI (Stan Hu)
- Ensure rake tasks that don't need a DB connection can be run without one - Ensure rake tasks that don't need a DB connection can be run without one
- Update New Relic gem to 3.14.1.311 (Stan Hu) - Update New Relic gem to 3.14.1.311 (Stan Hu)
- Add "visibility" flag to GET /projects api endpoint - Add "visibility" flag to GET /projects api endpoint
- Add an option to supply root email through an environmental variable (Koichiro Mikami)
- Ignore binary files in code search to prevent Error 500 (Stan Hu) - Ignore binary files in code search to prevent Error 500 (Stan Hu)
- Render sanitized SVG images (Stan Hu) - Render sanitized SVG images (Stan Hu)
- Support download access by PRIVATE-TOKEN header (Stan Hu) - Support download access by PRIVATE-TOKEN header (Stan Hu)
- Upgrade gitlab_git to 7.2.23 to fix commit message mentions in first branch push - Upgrade gitlab_git to 7.2.23 to fix commit message mentions in first branch push
- Add option to include the sender name in body of Notify email (Jason Lee)
- New UI for pagination - New UI for pagination
- Don't prevent sign out when 2FA enforcement is enabled and user hasn't yet - Don't prevent sign out when 2FA enforcement is enabled and user hasn't yet
set it up set it up
- API: Added "merge_requests/:merge_request_id/closes_issues" (Gal Schlezinger)
- Fix diff comments loaded by AJAX to load comment with diff in discussion tab - Fix diff comments loaded by AJAX to load comment with diff in discussion tab
- Fix relative links in other markup formats (Ben Boeckel)
- Whitelist raw "abbr" elements when parsing Markdown (Benedict Etzel) - Whitelist raw "abbr" elements when parsing Markdown (Benedict Etzel)
- Fix label links for a merge request pointing to issues list - Fix label links for a merge request pointing to issues list
- Don't vendor minified JS - Don't vendor minified JS
- Increase project import timeout to 15 minutes
- Be more permissive with email address validation: it only has to contain a single '@'
- Display 404 error on group not found - Display 404 error on group not found
- Track project import failure - Track project import failure
- Support Two-factor Authentication for LDAP users - Support Two-factor Authentication for LDAP users
- Display database type and version in Administration dashboard - Display database type and version in Administration dashboard
- Allow limited Markdown in Broadcast Messages
- Fix visibility level text in admin area (Zeger-Jan van de Weg) - Fix visibility level text in admin area (Zeger-Jan van de Weg)
- Warn admin during OAuth of granting admin rights (Zeger-Jan van de Weg) - Warn admin during OAuth of granting admin rights (Zeger-Jan van de Weg)
- Update the ExternalIssue regex pattern (Blake Hitchcock) - Update the ExternalIssue regex pattern (Blake Hitchcock)
- Remember user's inline/side-by-side diff view preference in a cookie (Kirill Katsnelson) - Remember user's inline/side-by-side diff view preference in a cookie (Kirill Katsnelson)
- Optimized performance of finding issues to be closed by a merge request - Optimized performance of finding issues to be closed by a merge request
- Add `avatar_url`, `description`, `git_ssh_url`, `git_http_url`, `path_with_namespace`
and `default_branch` in `project` in push, issue, merge-request and note webhooks data (Kirill Zaitsev)
- Deprecate the `ssh_url` in favor of `git_ssh_url` and `http_url` in favor of `git_http_url`
in `project` for push, issue, merge-request and note webhooks data (Kirill Zaitsev)
- Deprecate the `repository` key in push, issue, merge-request and note webhooks data, use `project` instead (Kirill Zaitsev)
- API: Expose MergeRequest#merge_status (Andrei Dziahel)
- Revert "Add IP check against DNSBLs at account sign-up" - Revert "Add IP check against DNSBLs at account sign-up"
- Actually use the `skip_merges` option in Repository#commits (Tony Chu)
- Fix API to keep request parameters in Link header (Michael Potthoff) - Fix API to keep request parameters in Link header (Michael Potthoff)
- Deprecate API "merge_request/:merge_request_id/comments". Use "merge_requests/:merge_request_id/notes" instead - Deprecate API "merge_request/:merge_request_id/comments". Use "merge_requests/:merge_request_id/notes" instead
- Deprecate API "merge_request/:merge_request_id/...". Use "merge_requests/:merge_request_id/..." instead - Deprecate API "merge_request/:merge_request_id/...". Use "merge_requests/:merge_request_id/..." instead
- Prevent parse error when name of project ends with .atom and prevent path issues - Prevent parse error when name of project ends with .atom and prevent path issues
- Mark inline difference between old and new paths when a file is renamed - Mark inline difference between old and new paths when a file is renamed
- Support Akismet spam checking for creation of issues via API (Stan Hu) - Support Akismet spam checking for creation of issues via API (Stan Hu)
- API: Allow to set or update a merge-request's milestone (Kirill Skachkov)
- Improve UI consistency between projects and groups lists - Improve UI consistency between projects and groups lists
- Add sort dropdown to dashboard projects page - Add sort dropdown to dashboard projects page
- Fixed logo animation on Safari (Roman Rott) - Fixed logo animation on Safari (Roman Rott)
- Hide remove source branch button when the MR is merged but new commits are pushed (Zeger-Jan van de Weg) - Hide remove source branch button when the MR is merged but new commits are pushed (Zeger-Jan van de Weg)
- In seach autocomplete show only groups and projects you are member of - In seach autocomplete show only groups and projects you are member of
- Don't process cross-reference notes from forks
- Fix: init.d script not working on OS X - Fix: init.d script not working on OS X
- Faster snippet search
- Title for milestones should be unique (Zeger-Jan van de Weg)
- Validate correctness of maximum attachment size application setting
- Replaces "Create merge request" link with one to the "Merge Request" when one exists
- Fix CI builds badge, add a new link to builds badge, deprecate the old one
- Fix broken link to project in build notification emails
- Ability to see and sort on vote count from Issues and MR lists
- Fix builds scheduler when first build in stage was allowed to fail
v 8.4.4 v 8.4.4
- Update omniauth-saml gem to 1.4.2 - Update omniauth-saml gem to 1.4.2
- Prevent long-running backup tasks from timing out the database connection
- Add a Project setting to allow guests to view build logs (defaults to true)
- Sort project milestones by due date including issue editor (Oliver Rogers / Orih)
v 8.4.3 v 8.4.3
- Increase lfs_objects size column to 8-byte integer to allow files larger - Increase lfs_objects size column to 8-byte integer to allow files larger
...@@ -381,6 +412,7 @@ v 8.1.0 ...@@ -381,6 +412,7 @@ v 8.1.0
- Improved performance of the trending projects page - Improved performance of the trending projects page
- Remove CI migration task - Remove CI migration task
- Improved performance of finding projects by their namespace - Improved performance of finding projects by their namespace
- Add assignee data to Issuables' hook_data (Bram Daams)
- Fix bug where transferring a project would result in stale commit links (Stan Hu) - Fix bug where transferring a project would result in stale commit links (Stan Hu)
- Fix build trace updating - Fix build trace updating
- Include full path of source and target branch names in New Merge Request page (Stan Hu) - Include full path of source and target branch names in New Merge Request page (Stan Hu)
......
...@@ -3,6 +3,10 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -3,6 +3,10 @@ Please view this file on the master branch, on stable branches it's out of date.
v 8.5.0 (unreleased) v 8.5.0 (unreleased)
- Show warning when mirror repository default branch could not be updated because it has diverged from upstream. - Show warning when mirror repository default branch could not be updated because it has diverged from upstream.
- More reliable wiki indexer - More reliable wiki indexer
- GitLab Pages gets support for custom domain and custom certificate
- Fix of Elastic indexer. It should not trigger record validation for projects
- Fix of Elastic indexer. Stabilze indexer when serialized data is corrupted
- [Elastic] Don't index unnecessary data into elastic
v 8.4.4 v 8.4.4
- Re-introduce "Send email to users" link in Admin area - Re-introduce "Send email to users" link in Admin area
......
...@@ -364,6 +364,7 @@ corresponding merge request should be updated to have the following: ...@@ -364,6 +364,7 @@ corresponding merge request should be updated to have the following:
This makes it easier for release managers to keep track of what still has to be This makes it easier for release managers to keep track of what still has to be
merged and where changes have to be merged into. merged and where changes have to be merged into.
Like all merge requests the target should be master so all bugfixes are in master.
## Definition of done ## Definition of done
......
...@@ -21,7 +21,7 @@ gem "pg", '~> 0.18.2', group: :postgres ...@@ -21,7 +21,7 @@ gem "pg", '~> 0.18.2', group: :postgres
gem 'devise', '~> 3.5.4' gem 'devise', '~> 3.5.4'
gem 'devise-async', '~> 0.9.0' gem 'devise-async', '~> 0.9.0'
gem 'doorkeeper', '~> 2.2.0' gem 'doorkeeper', '~> 2.2.0'
gem 'omniauth', '~> 1.2.2' gem 'omniauth', '~> 1.3.1'
gem 'omniauth-azure-oauth2', '~> 0.0.6' gem 'omniauth-azure-oauth2', '~> 0.0.6'
gem 'omniauth-bitbucket', '~> 0.0.2' gem 'omniauth-bitbucket', '~> 0.0.2'
gem 'omniauth-cas3', '~> 1.1.2' gem 'omniauth-cas3', '~> 1.1.2'
...@@ -46,6 +46,9 @@ gem 'devise-two-factor', '~> 2.0.0' ...@@ -46,6 +46,9 @@ gem 'devise-two-factor', '~> 2.0.0'
gem 'rqrcode-rails3', '~> 0.1.7' gem 'rqrcode-rails3', '~> 0.1.7'
gem 'attr_encrypted', '~> 1.3.4' gem 'attr_encrypted', '~> 1.3.4'
# GitLab Pages
gem 'validates_hostname', '~> 1.0.0'
# Browser detection # Browser detection
gem "browser", '~> 1.0.0' gem "browser", '~> 1.0.0'
...@@ -112,14 +115,14 @@ gem 'rouge', '~> 1.10.1' ...@@ -112,14 +115,14 @@ gem 'rouge', '~> 1.10.1'
# See https://groups.google.com/forum/#!topic/ruby-security-ann/aSbgDiwb24s # See https://groups.google.com/forum/#!topic/ruby-security-ann/aSbgDiwb24s
# and https://groups.google.com/forum/#!topic/ruby-security-ann/Dy7YiKb_pMM # and https://groups.google.com/forum/#!topic/ruby-security-ann/Dy7YiKb_pMM
gem 'nokogiri', '1.6.7.2' gem 'nokogiri', '~> 1.6.7', '>= 1.6.7.2'
# Diffs # Diffs
gem 'diffy', '~> 3.0.3' gem 'diffy', '~> 3.0.3'
# Application server # Application server
group :unicorn do group :unicorn do
gem "unicorn", '~> 4.8.2' gem "unicorn", '~> 4.9.0'
gem 'unicorn-worker-killer', '~> 0.4.2' gem 'unicorn-worker-killer', '~> 0.4.2'
end end
......
...@@ -50,7 +50,7 @@ GEM ...@@ -50,7 +50,7 @@ GEM
after_commit_queue (1.3.0) after_commit_queue (1.3.0)
activerecord (>= 3.0) activerecord (>= 3.0)
akismet (2.0.0) akismet (2.0.0)
allocations (1.0.3) allocations (1.0.4)
annotate (2.6.10) annotate (2.6.10)
activerecord (>= 3.2, <= 4.3) activerecord (>= 3.2, <= 4.3)
rake (~> 10.4) rake (~> 10.4)
...@@ -515,9 +515,9 @@ GEM ...@@ -515,9 +515,9 @@ GEM
rack (~> 1.2) rack (~> 1.2)
octokit (3.8.0) octokit (3.8.0)
sawyer (~> 0.6.0, >= 0.5.3) sawyer (~> 0.6.0, >= 0.5.3)
omniauth (1.2.2) omniauth (1.3.1)
hashie (>= 1.2, < 4) hashie (>= 1.2, < 4)
rack (~> 1.0) rack (>= 1.0, < 3)
omniauth-azure-oauth2 (0.0.6) omniauth-azure-oauth2 (0.0.6)
jwt (~> 1.0) jwt (~> 1.0)
omniauth (~> 1.0) omniauth (~> 1.0)
...@@ -857,7 +857,7 @@ GEM ...@@ -857,7 +857,7 @@ GEM
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
unf_ext (0.0.7.1) unf_ext (0.0.7.1)
unicorn (4.8.3) unicorn (4.9.0)
kgio (~> 2.6) kgio (~> 2.6)
rack rack
raindrops (~> 0.7) raindrops (~> 0.7)
...@@ -867,6 +867,9 @@ GEM ...@@ -867,6 +867,9 @@ GEM
uniform_notifier (1.9.0) uniform_notifier (1.9.0)
uuid (2.3.8) uuid (2.3.8)
macaddr (~> 1.0) macaddr (~> 1.0)
validates_hostname (1.0.5)
activerecord (>= 3.0)
activesupport (>= 3.0)
version_sorter (2.0.0) version_sorter (2.0.0)
virtus (1.0.5) virtus (1.0.5)
axiom-types (~> 0.1) axiom-types (~> 0.1)
...@@ -989,11 +992,11 @@ DEPENDENCIES ...@@ -989,11 +992,11 @@ DEPENDENCIES
nested_form (~> 0.3.2) nested_form (~> 0.3.2)
net-ldap net-ldap
net-ssh (~> 3.0.1) net-ssh (~> 3.0.1)
nokogiri (= 1.6.7.2) nokogiri (~> 1.6.7, >= 1.6.7.2)
nprogress-rails (~> 0.1.6.7) nprogress-rails (~> 0.1.6.7)
oauth2 (~> 1.0.0) oauth2 (~> 1.0.0)
octokit (~> 3.8.0) octokit (~> 3.8.0)
omniauth (~> 1.2.2) omniauth (~> 1.3.1)
omniauth-azure-oauth2 (~> 0.0.6) omniauth-azure-oauth2 (~> 0.0.6)
omniauth-bitbucket (~> 0.0.2) omniauth-bitbucket (~> 0.0.2)
omniauth-cas3 (~> 1.1.2) omniauth-cas3 (~> 1.1.2)
...@@ -1064,8 +1067,9 @@ DEPENDENCIES ...@@ -1064,8 +1067,9 @@ DEPENDENCIES
uglifier (~> 2.7.2) uglifier (~> 2.7.2)
underscore-rails (~> 1.8.0) underscore-rails (~> 1.8.0)
unf (~> 0.1.4) unf (~> 0.1.4)
unicorn (~> 4.8.2) unicorn (~> 4.9.0)
unicorn-worker-killer (~> 0.4.2) unicorn-worker-killer (~> 0.4.2)
validates_hostname (~> 1.0.0)
version_sorter (~> 2.0.0) version_sorter (~> 2.0.0)
virtus (~> 1.0.1) virtus (~> 1.0.1)
web-console (~> 2.0) web-console (~> 2.0)
......
...@@ -12,19 +12,6 @@ class @Admin ...@@ -12,19 +12,6 @@ class @Admin
e.preventDefault() e.preventDefault()
$('.js-toggle-colors-container').toggle() $('.js-toggle-colors-container').toggle()
$('input#broadcast_message_color').on 'input', ->
previewColor = $(@).val()
$('div.broadcast-message-preview').css('background-color', previewColor)
$('input#broadcast_message_font').on 'input', ->
previewColor = $(@).val()
$('div.broadcast-message-preview').css('color', previewColor)
$('textarea#broadcast_message_message').on 'input', ->
previewMessage = $(@).val()
previewMessage = "Your message here" if previewMessage.trim() == ''
$('div.broadcast-message-preview span').text(previewMessage)
$('.log-tabs a').click (e) -> $('.log-tabs a').click (e) ->
e.preventDefault() e.preventDefault()
$(this).tab('show') $(this).tab('show')
......
...@@ -49,10 +49,11 @@ class @AwardsHandler ...@@ -49,10 +49,11 @@ class @AwardsHandler
counter.text(parseInt(counter.text()) - 1) counter.text(parseInt(counter.text()) - 1)
emojiIcon.removeClass("active") emojiIcon.removeClass("active")
@removeMeFromAuthorList(emoji) @removeMeFromAuthorList(emoji)
else if emoji =="thumbsup" || emoji == "thumbsdown" else if emoji == "thumbsup" || emoji == "thumbsdown"
emojiIcon.tooltip("destroy") emojiIcon.tooltip("destroy")
counter.text(0) counter.text(0)
emojiIcon.removeClass("active") emojiIcon.removeClass("active")
@removeMeFromAuthorList(emoji)
else else
emojiIcon.tooltip("destroy") emojiIcon.tooltip("destroy")
emojiIcon.remove() emojiIcon.remove()
......
$ ->
$('input#broadcast_message_color').on 'input', ->
previewColor = $(@).val()
$('div.broadcast-message-preview').css('background-color', previewColor)
$('input#broadcast_message_font').on 'input', ->
previewColor = $(@).val()
$('div.broadcast-message-preview').css('color', previewColor)
previewPath = $('textarea#broadcast_message_message').data('preview-path')
$('textarea#broadcast_message_message').on 'input', ->
message = $(@).val()
if message == ''
$('.js-broadcast-message-preview').text("Your message here")
else
$.ajax(
url: previewPath
type: "POST"
data: { broadcast_message: { message: message } }
)
@Dashboard = @Dashboard =
init: -> init: ->
$(".projects-list-filter").off('keyup')
this.initSearch() this.initSearch()
initSearch: -> initSearch: ->
@timer = null @timer = null
$("#project-filter-form-field").on('keyup', -> $(".projects-list-filter").on('keyup', ->
clearTimeout(@timer) clearTimeout(@timer)
@timer = setTimeout(Dashboard.filterResults, 500) @timer = setTimeout(Dashboard.filterResults, 500)
) )
...@@ -13,8 +14,8 @@ ...@@ -13,8 +14,8 @@
$('.projects-list-holder').fadeTo(250, 0.5) $('.projects-list-holder').fadeTo(250, 0.5)
form = null form = null
form = $("#project-filter-form") form = $("form#project-filter-form")
search = $("#project-filter-form-field").val() search = $(".projects-list-filter").val()
project_filter_url = form.attr('action') + '?' + form.serialize() project_filter_url = form.attr('action') + '?' + form.serialize()
$.ajax $.ajax
...@@ -24,7 +25,7 @@ ...@@ -24,7 +25,7 @@
complete: -> complete: ->
$('.projects-list-holder').fadeTo(250, 1) $('.projects-list-holder').fadeTo(250, 1)
success: (data) -> success: (data) ->
$('div.projects-list-holder').replaceWith(data.html) $('.projects-list-holder').replaceWith(data.html)
# Change url so if user reload a page - search results are saved # Change url so if user reload a page - search results are saved
history.replaceState {page: project_filter_url}, document.title, project_filter_url history.replaceState {page: project_filter_url}, document.title, project_filter_url
dataType: "json" dataType: "json"
...@@ -16,6 +16,8 @@ class Dispatcher ...@@ -16,6 +16,8 @@ class Dispatcher
shortcut_handler = null shortcut_handler = null
switch page switch page
when 'explore:projects:index', 'explore:projects:starred', 'explore:projects:trending'
Dashboard.init()
when 'projects:issues:index' when 'projects:issues:index'
Issues.init() Issues.init()
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
......
...@@ -15,3 +15,5 @@ class @IssuableContext ...@@ -15,3 +15,5 @@ class @IssuableContext
block.find('.selectbox').show() block.find('.selectbox').show()
block.find('.value').hide() block.find('.value').hide()
block.find('.js-select2').select2("open") block.find('.js-select2').select2("open")
$(".right-sidebar").niceScroll()
...@@ -44,6 +44,7 @@ $(document).on('page:fetch', start) ...@@ -44,6 +44,7 @@ $(document).on('page:fetch', start)
$(document).on('page:change', stop) $(document).on('page:change', stop)
$ -> $ ->
# Make logo clickable # Make logo clickable as part of a workaround for Safari visited
# link behaviour (See !2690).
$('#logo').on 'click', -> $('#logo').on 'click', ->
$('#js-shortcuts-home').get(0).click() $('#js-shortcuts-home').get(0).click()
...@@ -3,22 +3,24 @@ class @ProjectsList ...@@ -3,22 +3,24 @@ class @ProjectsList
$(".projects-list .js-expand").on 'click', (e) -> $(".projects-list .js-expand").on 'click', (e) ->
e.preventDefault() e.preventDefault()
list = $(this).closest('.projects-list') list = $(this).closest('.projects-list')
list.find("li").show()
list.find("li.bottom").hide()
$(".projects-list-filter").keyup -> $("#filter_projects").on 'keyup', ->
terms = $(this).val() ProjectsList.filter_results($("#filter_projects"))
uiBox = $('div.projects-list-holder')
filterSelector = $(this).data('filter-selector') || 'span.filter-title'
if terms == "" || terms == undefined @filter_results: ($element) ->
uiBox.find("ul.projects-list li").show() terms = $element.val()
filterSelector = $element.data('filter-selector') || 'span.filter-title'
if not terms
$(".projects-list li").show()
$('.gl-pagination').show()
else else
uiBox.find("ul.projects-list li").each (index) -> $(".projects-list li").each (index) ->
name = $(this).find(filterSelector).text() $this = $(this)
name = $this.find(filterSelector).text()
if name.toLowerCase().search(terms.toLowerCase()) == -1 if name.toLowerCase().indexOf(terms.toLowerCase()) == -1
$(this).hide() $this.hide()
else else
$(this).show() $this.show()
uiBox.find("ul.projects-list li.bottom").hide() $('.gl-pagination').hide()
...@@ -55,6 +55,16 @@ ...@@ -55,6 +55,16 @@
@extend .alert-warning; @extend .alert-warning;
padding: 10px; padding: 10px;
text-align: center; text-align: center;
> div, p {
display: inline;
margin: 0;
a {
color: inherit;
text-decoration: underline;
}
}
} }
.broadcast-message-preview { .broadcast-message-preview {
......
...@@ -12,6 +12,14 @@ ...@@ -12,6 +12,14 @@
.identifier { .identifier {
color: #5c5d5e; color: #5c5d5e;
} }
.issue_created_ago, .author_link {
white-space: nowrap;
}
.issue-meta {
margin-left: 65px
}
} }
.detail-page-description { .detail-page-description {
......
...@@ -80,6 +80,10 @@ ...@@ -80,6 +80,10 @@
display: inline-block; display: inline-block;
} }
.select2-container span {
margin-top: 0;
}
.issuable-count { .issuable-count {
} }
...@@ -88,6 +92,10 @@ ...@@ -88,6 +92,10 @@
margin-left: 20px; margin-left: 20px;
border-left: 1px solid $border-gray-light; border-left: 1px solid $border-gray-light;
padding-left: 10px; padding-left: 10px;
&:hover {
color: $gray-darkest;
}
} }
} }
...@@ -192,6 +200,10 @@ ...@@ -192,6 +200,10 @@
.btn { .btn {
background: $gray-normal; background: $gray-normal;
border: 1px solid $border-gray-normal; border: 1px solid $border-gray-normal;
&:hover {
background: $gray-dark;
border: 1px solid $border-gray-dark;
}
} }
&.right-sidebar-collapsed { &.right-sidebar-collapsed {
...@@ -223,6 +235,19 @@ ...@@ -223,6 +235,19 @@
display: block; display: block;
margin-top: 0; margin-top: 0;
} }
.btn-clipboard {
border: none;
&:hover {
background: transparent;
}
i {
color: #999999;
}
}
} }
} }
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
display: inline-block; display: inline-block;
} }
.issue-no-comments { .issue-no-comments, .issue-no-votes {
opacity: 0.5; opacity: 0.5;
} }
} }
......
...@@ -163,7 +163,7 @@ ...@@ -163,7 +163,7 @@
display: inline-block; display: inline-block;
} }
.merge-request-no-comments { .merge-request-no-comments, .merge-request-no-votes {
opacity: 0.5; opacity: 0.5;
} }
} }
......
...@@ -83,6 +83,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -83,6 +83,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:sentry_dsn, :sentry_dsn,
:akismet_enabled, :akismet_enabled,
:akismet_api_key, :akismet_api_key,
:email_author_in_body,
restricted_visibility_levels: [], restricted_visibility_levels: [],
import_sources: [] import_sources: []
) )
......
...@@ -36,6 +36,10 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController ...@@ -36,6 +36,10 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController
end end
end end
def preview
@message = broadcast_message_params[:message]
end
protected protected
def finder def finder
......
...@@ -168,7 +168,7 @@ class ApplicationController < ActionController::Base ...@@ -168,7 +168,7 @@ class ApplicationController < ActionController::Base
end end
def git_not_found! def git_not_found!
render html: "errors/git_not_found", layout: "errors", status: 404 render "errors/git_not_found.html", layout: "errors", status: 404
end end
def method_missing(method_sym, *arguments, &block) def method_missing(method_sym, *arguments, &block)
......
...@@ -3,52 +3,5 @@ module Ci ...@@ -3,52 +3,5 @@ module Ci
def self.railtie_helpers_paths def self.railtie_helpers_paths
"app/helpers/ci" "app/helpers/ci"
end end
private
def authorize_access_project!
unless can?(current_user, :read_project, project)
return page_404
end
end
def authorize_manage_builds!
unless can?(current_user, :manage_builds, project)
return page_404
end
end
def authenticate_admin!
return render_404 unless current_user.is_admin?
end
def authorize_manage_project!
unless can?(current_user, :admin_project, project)
return page_404
end
end
def page_404
render file: "#{Rails.root}/public/404.html", status: 404, layout: false
end
def default_headers
headers['X-Frame-Options'] = 'DENY'
headers['X-XSS-Protection'] = '1; mode=block'
end
# JSON for infinite scroll via Pager object
def pager_json(partial, count)
html = render_to_string(
partial,
layout: false,
formats: [:html]
)
render json: {
html: html,
count: count
}
end
end end
end end
module Ci module Ci
class ProjectsController < Ci::ApplicationController class ProjectsController < Ci::ApplicationController
before_action :project, except: [:index] before_action :project
before_action :authenticate_user!, except: [:index, :build, :badge] before_action :authorize_read_project!, except: [:badge]
before_action :authorize_access_project!, except: [:index, :badge]
before_action :no_cache, only: [:badge] before_action :no_cache, only: [:badge]
protect_from_forgery protect_from_forgery
...@@ -13,9 +12,13 @@ module Ci ...@@ -13,9 +12,13 @@ module Ci
# Project status badge # Project status badge
# Image with build status for sha or ref # Image with build status for sha or ref
#
# This action in DEPRECATED, this is here only for backwards compatibility
# with projects migrated from GitLab CI.
#
def badge def badge
return render_404 unless @project
image = Ci::ImageForBuildService.new.execute(@project, params) image = Ci::ImageForBuildService.new.execute(@project, params)
send_file image.path, filename: image.name, disposition: 'inline', type:"image/svg+xml" send_file image.path, filename: image.name, disposition: 'inline', type:"image/svg+xml"
end end
......
...@@ -12,7 +12,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController ...@@ -12,7 +12,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
@projects = @projects.search(terms) @projects = @projects.search(terms)
end end
@projects = @projects.page(params[:page]).per(PER_PAGE) @projects = @projects.page(params[:page]).per(PER_PAGE) if terms.blank?
@last_push = current_user.recent_push @last_push = current_user.recent_push
respond_to do |format| respond_to do |format|
...@@ -41,7 +41,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController ...@@ -41,7 +41,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
@projects = @projects.search(terms) @projects = @projects.search(terms)
end end
@projects = @projects.page(params[:page]).per(PER_PAGE) @projects = @projects.page(params[:page]).per(PER_PAGE) if terms.blank?
@last_push = current_user.recent_push @last_push = current_user.recent_push
@groups = [] @groups = []
......
...@@ -6,19 +6,49 @@ class Explore::ProjectsController < Explore::ApplicationController ...@@ -6,19 +6,49 @@ class Explore::ProjectsController < Explore::ApplicationController
@projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present? @projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present?
@projects = @projects.non_archived @projects = @projects.non_archived
@projects = @projects.search(params[:search]) if params[:search].present? @projects = @projects.search(params[:search]) if params[:search].present?
@projects = @projects.search(params[:filter_projects]) if params[:filter_projects].present?
@projects = @projects.sort(@sort = params[:sort]) @projects = @projects.sort(@sort = params[:sort])
@projects = @projects.includes(:namespace).page(params[:page]).per(PER_PAGE) @projects = @projects.includes(:namespace).page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank?
respond_to do |format|
format.html
format.json do
render json: {
html: view_to_html_string("dashboard/projects/_projects", locals: { projects: @projects })
}
end
end
end end
def trending def trending
@projects = TrendingProjectsFinder.new.execute(current_user) @projects = TrendingProjectsFinder.new.execute(current_user)
@projects = @projects.non_archived @projects = @projects.non_archived
@projects = @projects.page(params[:page]).per(PER_PAGE) @projects = @projects.search(params[:filter_projects]) if params[:filter_projects].present?
@projects = @projects.page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank?
respond_to do |format|
format.html
format.json do
render json: {
html: view_to_html_string("dashboard/projects/_projects", locals: { projects: @projects })
}
end
end
end end
def starred def starred
@projects = ProjectsFinder.new.execute(current_user) @projects = ProjectsFinder.new.execute(current_user)
@projects = @projects.search(params[:filter_projects]) if params[:filter_projects].present?
@projects = @projects.reorder('star_count DESC') @projects = @projects.reorder('star_count DESC')
@projects = @projects.page(params[:page]).per(PER_PAGE) @projects = @projects.page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank?
respond_to do |format|
format.html
format.json do
render json: {
html: view_to_html_string("dashboard/projects/_projects", locals: { projects: @projects })
}
end
end
end end
end end
...@@ -5,7 +5,7 @@ class GroupsController < Groups::ApplicationController ...@@ -5,7 +5,7 @@ class GroupsController < Groups::ApplicationController
respond_to :html respond_to :html
skip_before_action :authenticate_user!, only: [:index, :show, :issues, :merge_requests] skip_before_action :authenticate_user!, only: [:index, :show, :issues, :merge_requests]
before_action :group, except: [:index, :new, :create] before_action :group, except: [:index, :new, :create, :autocomplete]
# Authorize # Authorize
before_action :authorize_read_group!, except: [:index, :show, :new, :create, :autocomplete] before_action :authorize_read_group!, except: [:index, :show, :new, :create, :autocomplete]
...@@ -14,7 +14,7 @@ class GroupsController < Groups::ApplicationController ...@@ -14,7 +14,7 @@ class GroupsController < Groups::ApplicationController
# Load group projects # Load group projects
before_action :load_projects, except: [:index, :new, :create, :projects, :edit, :update, :autocomplete] before_action :load_projects, except: [:index, :new, :create, :projects, :edit, :update, :autocomplete]
before_action :event_filter, only: :show before_action :event_filter, only: [:show, :events]
layout :determine_layout layout :determine_layout
...@@ -41,7 +41,8 @@ class GroupsController < Groups::ApplicationController ...@@ -41,7 +41,8 @@ class GroupsController < Groups::ApplicationController
def show def show
@last_push = current_user.recent_push if current_user @last_push = current_user.recent_push if current_user
@projects = @projects.includes(:namespace) @projects = @projects.includes(:namespace)
@projects = @projects.page(params[:page]).per(PER_PAGE) @projects = @projects.search(params[:filter_projects]) if params[:filter_projects].present?
@projects = @projects.page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank?
@shared_projects = @group.shared_projects @shared_projects = @group.shared_projects
...@@ -49,8 +50,9 @@ class GroupsController < Groups::ApplicationController ...@@ -49,8 +50,9 @@ class GroupsController < Groups::ApplicationController
format.html format.html
format.json do format.json do
load_events render json: {
pager_json("events/_events", @events.count) html: view_to_html_string("dashboard/projects/_projects", locals: { projects: @projects })
}
end end
format.atom do format.atom do
...@@ -60,6 +62,15 @@ class GroupsController < Groups::ApplicationController ...@@ -60,6 +62,15 @@ class GroupsController < Groups::ApplicationController
end end
end end
def events
respond_to do |format|
format.json do
load_events
pager_json("events/_events", @events.count)
end
end
end
def edit def edit
end end
......
class Projects::ArtifactsController < Projects::ApplicationController class Projects::ArtifactsController < Projects::ApplicationController
layout 'project' layout 'project'
before_action :authorize_read_build_artifacts! before_action :authorize_read_build!
def download def download
unless artifacts_file.file_storage? unless artifacts_file.file_storage?
...@@ -43,14 +43,4 @@ class Projects::ArtifactsController < Projects::ApplicationController ...@@ -43,14 +43,4 @@ class Projects::ArtifactsController < Projects::ApplicationController
def artifacts_file def artifacts_file
@artifacts_file ||= build.artifacts_file @artifacts_file ||= build.artifacts_file
end end
def authorize_read_build_artifacts!
unless can?(current_user, :read_build_artifacts, @project)
if current_user.nil?
return authenticate_user!
else
return render_404
end
end
end
end end
class Projects::BadgesController < Projects::ApplicationController
def build
respond_to do |format|
format.html { render_404 }
format.svg do
image = Ci::ImageForBuildService.new.execute(project, ref: params[:ref])
send_file(image.path, filename: image.name, disposition: 'inline', type: 'image/svg+xml')
end
end
end
end
class Projects::BuildsController < Projects::ApplicationController class Projects::BuildsController < Projects::ApplicationController
before_action :build, except: [:index, :cancel_all] before_action :build, except: [:index, :cancel_all]
before_action :authorize_read_build!, except: [:cancel, :cancel_all, :retry]
before_action :authorize_manage_builds!, except: [:index, :show, :status] before_action :authorize_update_build!, except: [:index, :show, :status]
layout 'project'
layout "project"
def index def index
@scope = params[:scope] @scope = params[:scope]
...@@ -23,7 +22,6 @@ class Projects::BuildsController < Projects::ApplicationController ...@@ -23,7 +22,6 @@ class Projects::BuildsController < Projects::ApplicationController
def cancel_all def cancel_all
@project.builds.running_or_pending.each(&:cancel) @project.builds.running_or_pending.each(&:cancel)
redirect_to namespace_project_builds_path(project.namespace, project) redirect_to namespace_project_builds_path(project.namespace, project)
end end
...@@ -46,20 +44,18 @@ class Projects::BuildsController < Projects::ApplicationController ...@@ -46,20 +44,18 @@ class Projects::BuildsController < Projects::ApplicationController
end end
build = Ci::Build.retry(@build) build = Ci::Build.retry(@build)
redirect_to build_path(build) redirect_to build_path(build)
end end
def status
render json: @build.to_json(only: [:status, :id, :sha, :coverage], methods: :sha)
end
def cancel def cancel
@build.cancel @build.cancel
redirect_to build_path(@build) redirect_to build_path(@build)
end end
def status
render json: @build.to_json(only: [:status, :id, :sha, :coverage], methods: :sha)
end
private private
def build def build
...@@ -69,10 +65,4 @@ class Projects::BuildsController < Projects::ApplicationController ...@@ -69,10 +65,4 @@ class Projects::BuildsController < Projects::ApplicationController
def build_path(build) def build_path(build)
namespace_project_build_path(build.project.namespace, build.project, build) namespace_project_build_path(build.project.namespace, build.project, build)
end end
def authorize_manage_builds!
unless can?(current_user, :manage_builds, project)
return render_404
end
end
end end
...@@ -4,15 +4,13 @@ ...@@ -4,15 +4,13 @@
class Projects::CommitController < Projects::ApplicationController class Projects::CommitController < Projects::ApplicationController
# Authorize # Authorize
before_action :require_non_empty_project before_action :require_non_empty_project
before_action :authorize_download_code!, except: [:cancel_builds] before_action :authorize_download_code!, except: [:cancel_builds, :retry_builds]
before_action :authorize_manage_builds!, only: [:cancel_builds] before_action :authorize_update_build!, only: [:cancel_builds, :retry_builds]
before_action :authorize_read_commit_status!, only: [:builds]
before_action :commit before_action :commit
before_action :authorize_manage_builds!, only: [:cancel_builds, :retry_builds]
before_action :define_show_vars, only: [:show, :builds] before_action :define_show_vars, only: [:show, :builds]
def show def show
return git_not_found! unless @commit
apply_diff_view_cookie! apply_diff_view_cookie!
@line_notes = commit.notes.inline @line_notes = commit.notes.inline
...@@ -68,6 +66,8 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -68,6 +66,8 @@ class Projects::CommitController < Projects::ApplicationController
end end
def define_show_vars def define_show_vars
return git_not_found! unless commit
if params[:w].to_i == 1 if params[:w].to_i == 1
@diffs = commit.diffs({ ignore_whitespace_change: true }) @diffs = commit.diffs({ ignore_whitespace_change: true })
else else
...@@ -79,10 +79,4 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -79,10 +79,4 @@ class Projects::CommitController < Projects::ApplicationController
@statuses = ci_commit.statuses if ci_commit @statuses = ci_commit.statuses if ci_commit
end end
def authorize_manage_builds!
unless can?(current_user, :manage_builds, project)
return render_404
end
end
end end
...@@ -21,6 +21,9 @@ class Projects::CommitsController < Projects::ApplicationController ...@@ -21,6 +21,9 @@ class Projects::CommitsController < Projects::ApplicationController
@note_counts = project.notes.where(commit_id: @commits.map(&:id)). @note_counts = project.notes.where(commit_id: @commits.map(&:id)).
group(:commit_id).count group(:commit_id).count
@merge_request = @project.merge_requests.opened.
find_by(source_project: @project, source_branch: @ref, target_branch: @repository.root_ref)
respond_to do |format| respond_to do |format|
format.html format.html
format.json { pager_json("projects/commits/_commits", @commits.size) } format.json { pager_json("projects/commits/_commits", @commits.size) }
......
...@@ -4,24 +4,23 @@ class Projects::CompareController < Projects::ApplicationController ...@@ -4,24 +4,23 @@ class Projects::CompareController < Projects::ApplicationController
# Authorize # Authorize
before_action :require_non_empty_project before_action :require_non_empty_project
before_action :authorize_download_code! before_action :authorize_download_code!
before_action :assign_ref_vars, only: [:index, :show]
before_action :merge_request, only: [:index, :show]
def index def index
@ref = Addressable::URI.unescape(params[:to])
end end
def show def show
base_ref = Addressable::URI.unescape(params[:from])
@ref = head_ref = Addressable::URI.unescape(params[:to])
diff_options = { ignore_whitespace_change: true } if params[:w] == '1' diff_options = { ignore_whitespace_change: true } if params[:w] == '1'
compare_result = CompareService.new. compare_result = CompareService.new.
execute(@project, head_ref, @project, base_ref, diff_options) execute(@project, @head_ref, @project, @base_ref, diff_options)
if compare_result if compare_result
@commits = Commit.decorate(compare_result.commits, @project) @commits = Commit.decorate(compare_result.commits, @project)
@diffs = compare_result.diffs @diffs = compare_result.diffs
@commit = @project.commit(head_ref) @commit = @project.commit(@head_ref)
@base_commit = @project.merge_base_commit(base_ref, head_ref) @base_commit = @project.merge_base_commit(@base_ref, @head_ref)
@diff_refs = [@base_commit, @commit] @diff_refs = [@base_commit, @commit]
@line_notes = [] @line_notes = []
end end
...@@ -31,4 +30,16 @@ class Projects::CompareController < Projects::ApplicationController ...@@ -31,4 +30,16 @@ class Projects::CompareController < Projects::ApplicationController
redirect_to namespace_project_compare_path(@project.namespace, @project, redirect_to namespace_project_compare_path(@project.namespace, @project,
params[:from], params[:to]) params[:from], params[:to])
end end
private
def assign_ref_vars
@base_ref = Addressable::URI.unescape(params[:from])
@ref = @head_ref = Addressable::URI.unescape(params[:to])
end
def merge_request
@merge_request ||= @project.merge_requests.opened.
find_by(source_project: @project, source_branch: @head_ref, target_branch: @base_ref)
end
end end
...@@ -3,6 +3,7 @@ class Projects::ImportsController < Projects::ApplicationController ...@@ -3,6 +3,7 @@ class Projects::ImportsController < Projects::ApplicationController
before_action :authorize_admin_project! before_action :authorize_admin_project!
before_action :require_no_repo, only: [:new, :create] before_action :require_no_repo, only: [:new, :create]
before_action :redirect_if_progress, only: [:new, :create] before_action :redirect_if_progress, only: [:new, :create]
before_action :redirect_if_no_import, only: :show
def new def new
end end
...@@ -61,14 +62,19 @@ class Projects::ImportsController < Projects::ApplicationController ...@@ -61,14 +62,19 @@ class Projects::ImportsController < Projects::ApplicationController
def require_no_repo def require_no_repo
if @project.repository_exists? if @project.repository_exists?
redirect_to(namespace_project_path(@project.namespace, @project)) redirect_to namespace_project_path(@project.namespace, @project)
end end
end end
def redirect_if_progress def redirect_if_progress
if @project.import_in_progress? if @project.import_in_progress?
redirect_to namespace_project_import_path(@project.namespace, @project) && redirect_to namespace_project_import_path(@project.namespace, @project)
return end
end
def redirect_if_no_import
if @project.repository_exists? && @project.no_import?
redirect_to namespace_project_path(@project.namespace, @project)
end end
end end
......
...@@ -11,10 +11,11 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -11,10 +11,11 @@ class Projects::MilestonesController < Projects::ApplicationController
respond_to :html respond_to :html
def index def index
@milestones = case params[:state] @milestones =
when 'all'; @project.milestones.order("state, due_date DESC") case params[:state]
when 'closed'; @project.milestones.closed.order("due_date DESC") when 'all' then @project.milestones.reorder(due_date: :desc, title: :asc)
else @project.milestones.active.order("due_date ASC") when 'closed' then @project.milestones.closed.reorder(due_date: :desc, title: :asc)
else @project.milestones.active.reorder(due_date: :asc, title: :asc)
end end
@milestones = @milestones.includes(:project) @milestones = @milestones.includes(:project)
......
class Projects::PagesController < Projects::ApplicationController
layout 'project_settings'
before_action :authorize_read_pages!, only: [:show]
before_action :authorize_update_pages!, except: [:show]
def show
@domains = @project.pages_domains.order(:domain)
end
def destroy
project.remove_pages
project.pages_domains.destroy_all
respond_to do |format|
format.html do
redirect_to(namespace_project_pages_path(@project.namespace, @project),
notice: 'Pages were removed')
end
end
end
end
class Projects::PagesDomainsController < Projects::ApplicationController
layout 'project_settings'
before_action :authorize_update_pages!, except: [:show]
before_action :domain, only: [:show, :destroy]
def show
end
def new
@domain = @project.pages_domains.new
end
def create
@domain = @project.pages_domains.create(pages_domain_params)
if @domain.valid?
redirect_to namespace_project_pages_path(@project.namespace, @project)
else
render 'new'
end
end
def destroy
@domain.destroy
respond_to do |format|
format.html do
redirect_to(namespace_project_pages_path(@project.namespace, @project),
notice: 'Domain was removed')
end
format.js
end
end
private
def pages_domain_params
params.require(:pages_domain).permit(
:certificate,
:key,
:domain
)
end
def domain
@domain ||= @project.pages_domains.find_by(domain: params[:id].to_s)
end
end
...@@ -11,7 +11,9 @@ class Projects::RepositoriesController < Projects::ApplicationController ...@@ -11,7 +11,9 @@ class Projects::RepositoriesController < Projects::ApplicationController
end end
def archive def archive
render json: ArchiveRepositoryService.new(@project, params[:ref], params[:format]).execute RepositoryArchiveCacheWorker.perform_async
headers.store(*Gitlab::Workhorse.send_git_archive(@project, params[:ref], params[:format]))
head :ok
rescue => ex rescue => ex
logger.error("#{self.class.name}: #{ex}") logger.error("#{self.class.name}: #{ex}")
return git_not_found! return git_not_found!
......
class Projects::RunnerProjectsController < Projects::ApplicationController class Projects::RunnerProjectsController < Projects::ApplicationController
before_action :authorize_admin_project! before_action :authorize_admin_build!
layout 'project_settings' layout 'project_settings'
......
class Projects::RunnersController < Projects::ApplicationController class Projects::RunnersController < Projects::ApplicationController
before_action :authorize_admin_build!
before_action :set_runner, only: [:edit, :update, :destroy, :pause, :resume, :show] before_action :set_runner, only: [:edit, :update, :destroy, :pause, :resume, :show]
before_action :authorize_admin_project!
layout 'project_settings' layout 'project_settings'
......
class Projects::TriggersController < Projects::ApplicationController class Projects::TriggersController < Projects::ApplicationController
before_action :authorize_admin_project! before_action :authorize_admin_build!
layout 'project_settings' layout 'project_settings'
......
class Projects::VariablesController < Projects::ApplicationController class Projects::VariablesController < Projects::ApplicationController
before_action :authorize_admin_project! before_action :authorize_admin_build!
layout 'project_settings' layout 'project_settings'
......
...@@ -171,16 +171,6 @@ class ProjectsController < ApplicationController ...@@ -171,16 +171,6 @@ class ProjectsController < ApplicationController
end end
end end
def remove_pages
return access_denied! unless can?(current_user, :remove_pages, @project)
@project.remove_pages
respond_to do |format|
format.html { redirect_to project_path(@project) }
end
end
def housekeeping def housekeeping
::Projects::HousekeepingService.new(@project).execute ::Projects::HousekeepingService.new(@project).execute
...@@ -234,27 +224,11 @@ class ProjectsController < ApplicationController ...@@ -234,27 +224,11 @@ class ProjectsController < ApplicationController
def project_params def project_params
params.require(:project).permit( params.require(:project).permit(
:avatar, :name, :path, :description, :issues_tracker, :tag_list, :runners_token,
:build_allow_git_fetch, :issues_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, :default_branch,
:build_coverage_regex, :wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar,
:build_timeout_in_minutes, :builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
:builds_enabled, :public_builds,
:default_branch,
:description,
:import_url,
:issues_enabled,
:issues_tracker,
:issues_tracker_id,
:last_activity_at,
:merge_requests_enabled,
:name,
:namespace_id,
:path,
:runners_token,
:snippets_enabled,
:tag_list,
:visibility_level,
:wiki_enabled,
# EE-only # EE-only
:approvals_before_merge, :approvals_before_merge,
......
...@@ -215,8 +215,7 @@ module ApplicationHelper ...@@ -215,8 +215,7 @@ module ApplicationHelper
file_content file_content
end end
else else
GitHub::Markup.render(file_name, file_content). other_markup(file_name, file_content)
force_encoding(file_content.encoding).html_safe
end end
rescue RuntimeError rescue RuntimeError
simple_format(file_content) simple_format(file_content)
...@@ -284,76 +283,6 @@ module ApplicationHelper ...@@ -284,76 +283,6 @@ module ApplicationHelper
end end
end end
def issuable_link_next(project,issuable)
if project.nil?
nil
elsif current_controller?(:issues)
namespace_project_issue_path(project.namespace, project, next_issuable_for(project, issuable.id).try(:iid))
elsif current_controller?(:merge_requests)
namespace_project_merge_request_path(project.namespace, project, next_issuable_for(project, issuable.id).try(:iid))
end
end
def issuable_link_prev(project,issuable)
if project.nil?
nil
elsif current_controller?(:issues)
namespace_project_issue_path(project.namespace, project, prev_issuable_for(project, issuable.id).try(:iid))
elsif current_controller?(:merge_requests)
namespace_project_merge_request_path(project.namespace, project, prev_issuable_for(project, issuable.id).try(:iid))
end
end
def issuable_count(entity, project)
if project.nil?
0
elsif current_controller?(:issues)
project.issues.send(entity).count
elsif current_controller?(:merge_requests)
project.merge_requests.send(entity).count
end
end
def next_issuable_for(project, id)
if project.nil?
nil
elsif current_controller?(:issues)
project.issues.where("id > ?", id).last
elsif current_controller?(:merge_requests)
project.merge_requests.where("id > ?", id).last
end
end
def has_next_issuable?(project, id)
if project.nil?
nil
elsif current_controller?(:issues)
project.issues.where("id > ?", id).last
elsif current_controller?(:merge_requests)
project.merge_requests.where("id > ?", id).last
end
end
def prev_issuable_for(project, id)
if project.nil?
nil
elsif current_controller?(:issues)
project.issues.where("id < ?", id).first
elsif current_controller?(:merge_requests)
project.merge_requests.where("id < ?", id).first
end
end
def has_prev_issuable?(project, id)
if project.nil?
nil
elsif current_controller?(:issues)
project.issues.where("id < ?", id).first
elsif current_controller?(:merge_requests)
project.merge_requests.where("id < ?", id).first
end
end
def state_filters_text_for(entity, project) def state_filters_text_for(entity, project)
titles = { titles = {
opened: "Open" opened: "Open"
......
...@@ -3,7 +3,7 @@ module BroadcastMessagesHelper ...@@ -3,7 +3,7 @@ module BroadcastMessagesHelper
return unless message.present? return unless message.present?
content_tag :div, class: 'broadcast-message', style: broadcast_message_style(message) do content_tag :div, class: 'broadcast-message', style: broadcast_message_style(message) do
icon('bullhorn') << ' ' << message.message icon('bullhorn') << ' ' << render_broadcast_message(message.message)
end end
end end
...@@ -31,4 +31,8 @@ module BroadcastMessagesHelper ...@@ -31,4 +31,8 @@ module BroadcastMessagesHelper
'Pending' 'Pending'
end end
end end
def render_broadcast_message(message)
Banzai.render(message, pipeline: :broadcast_message).html_safe
end
end end
...@@ -69,7 +69,7 @@ module DiffHelper ...@@ -69,7 +69,7 @@ module DiffHelper
end end
def line_comments def line_comments
@line_comments ||= @line_notes.select(&:active?).group_by(&:line_code) @line_comments ||= @line_notes.select(&:active?).sort_by(&:created_at).group_by(&:line_code)
end end
def organize_comments(type_left, type_right, line_code_left, line_code_right) def organize_comments(type_left, type_right, line_code_left, line_code_right)
......
...@@ -78,6 +78,21 @@ module GitlabMarkdownHelper ...@@ -78,6 +78,21 @@ module GitlabMarkdownHelper
) )
end end
def other_markup(file_name, text)
Gitlab::OtherMarkup.render(
file_name,
text,
project: @project,
current_user: (current_user if defined?(current_user)),
# RelativeLinkFilter
project_wiki: @project_wiki,
requested_path: @path,
ref: @ref,
commit: @commit
)
end
# Return the first line of +text+, up to +max_chars+, after parsing the line # Return the first line of +text+, up to +max_chars+, after parsing the line
# as Markdown. HTML tags in the parsed output are not counted toward the # as Markdown. HTML tags in the parsed output are not counted toward the
# +max_chars+ limit. If the length limit falls within a tag's contents, then # +max_chars+ limit. If the length limit falls within a tag's contents, then
......
module IssuablesHelper
def sidebar_gutter_toggle_icon
sidebar_gutter_collapsed? ? icon('angle-double-left') : icon('angle-double-right')
end
def sidebar_gutter_collapsed_class
"right-sidebar-#{sidebar_gutter_collapsed? ? 'collapsed' : 'expanded'}"
end
def issuables_count(issuable)
base_issuable_scope(issuable).maximum(:iid)
end
def next_issuable_for(issuable)
base_issuable_scope(issuable).where('iid > ?', issuable.iid).last
end
def prev_issuable_for(issuable)
base_issuable_scope(issuable).where('iid < ?', issuable.iid).first
end
private
def sidebar_gutter_collapsed?
cookies[:collapsed_gutter] == 'true'
end
def base_issuable_scope(issuable)
issuable.project.send(issuable.class.table_name).send(issuable_state_scope(issuable))
end
def issuable_state_scope(issuable)
issuable.open? ? :opened : :closed
end
end
...@@ -44,14 +44,14 @@ module IssuesHelper ...@@ -44,14 +44,14 @@ module IssuesHelper
end end
def bulk_update_milestone_options def bulk_update_milestone_options
milestones = project_active_milestones.to_a milestones = @project.milestones.active.reorder(due_date: :asc, title: :asc).to_a
milestones.unshift(Milestone::None) milestones.unshift(Milestone::None)
options_from_collection_for_select(milestones, 'id', 'title', params[:milestone_id]) options_from_collection_for_select(milestones, 'id', 'title', params[:milestone_id])
end end
def milestone_options(object) def milestone_options(object)
milestones = object.project.milestones.active.to_a milestones = object.project.milestones.active.reorder(due_date: :asc, title: :asc).to_a
milestones.unshift(Milestone::None) milestones.unshift(Milestone::None)
options_from_collection_for_select(milestones, 'id', 'title', object.milestone_id) options_from_collection_for_select(milestones, 'id', 'title', object.milestone_id)
......
...@@ -3,18 +3,6 @@ module NavHelper ...@@ -3,18 +3,6 @@ module NavHelper
cookies[:collapsed_nav] == 'true' cookies[:collapsed_nav] == 'true'
end end
def sidebar_gutter_collapsed_class
if cookies[:collapsed_gutter] == 'true'
"right-sidebar-collapsed"
else
"right-sidebar-expanded"
end
end
def sidebar_gutter_collapsed?
cookies[:collapsed_gutter] == 'true'
end
def nav_sidebar_class def nav_sidebar_class
if nav_menu_collapsed? if nav_menu_collapsed?
"sidebar-collapsed" "sidebar-collapsed"
......
...@@ -98,10 +98,6 @@ module ProjectsHelper ...@@ -98,10 +98,6 @@ module ProjectsHelper
project_nav_tabs.include? name project_nav_tabs.include? name
end end
def project_active_milestones
@project.milestones.active.order("due_date, title ASC")
end
def project_for_deploy_key(deploy_key) def project_for_deploy_key(deploy_key)
if deploy_key.projects.include?(@project) if deploy_key.projects.include?(@project)
@project @project
...@@ -141,7 +137,7 @@ module ProjectsHelper ...@@ -141,7 +137,7 @@ module ProjectsHelper
nav_tabs << :merge_requests nav_tabs << :merge_requests
end end
if project.builds_enabled? && can?(current_user, :read_build, project) if can?(current_user, :read_build, project)
nav_tabs << :builds nav_tabs << :builds
end end
......
...@@ -33,7 +33,7 @@ module SnippetsHelper ...@@ -33,7 +33,7 @@ module SnippetsHelper
# surrounding code. # surrounding code.
# #
# @returns Array, unique and sorted. # @returns Array, unique and sorted.
def matching_lines(lined_content, surrounding_lines) def matching_lines(lined_content, surrounding_lines, query)
used_lines = [] used_lines = []
lined_content.each_with_index do |line, line_number| lined_content.each_with_index do |line, line_number|
used_lines.concat bounded_line_numbers( used_lines.concat bounded_line_numbers(
...@@ -51,9 +51,9 @@ module SnippetsHelper ...@@ -51,9 +51,9 @@ module SnippetsHelper
# surrounding_lines() worth of unmatching lines. # surrounding_lines() worth of unmatching lines.
# #
# @returns a hash with {snippet_object, snippet_chunks:{data,start_line}} # @returns a hash with {snippet_object, snippet_chunks:{data,start_line}}
def chunk_snippet(snippet, surrounding_lines = 3) def chunk_snippet(snippet, query, surrounding_lines = 3)
lined_content = snippet.content.split("\n") lined_content = snippet.content.split("\n")
used_lines = matching_lines(lined_content, surrounding_lines) used_lines = matching_lines(lined_content, surrounding_lines, query)
snippet_chunk = [] snippet_chunk = []
snippet_chunks = [] snippet_chunks = []
......
...@@ -11,6 +11,8 @@ module SortingHelper ...@@ -11,6 +11,8 @@ module SortingHelper
sort_value_largest_repo => sort_title_largest_repo, sort_value_largest_repo => sort_title_largest_repo,
sort_value_recently_signin => sort_title_recently_signin, sort_value_recently_signin => sort_title_recently_signin,
sort_value_oldest_signin => sort_title_oldest_signin, sort_value_oldest_signin => sort_title_oldest_signin,
sort_value_downvotes => sort_title_downvotes,
sort_value_upvotes => sort_title_upvotes
} }
end end
...@@ -54,6 +56,14 @@ module SortingHelper ...@@ -54,6 +56,14 @@ module SortingHelper
'Oldest sign in' 'Oldest sign in'
end end
def sort_title_downvotes
'Least popular'
end
def sort_title_upvotes
'Most popular'
end
def sort_value_oldest_updated def sort_value_oldest_updated
'updated_asc' 'updated_asc'
end end
...@@ -93,4 +103,12 @@ module SortingHelper ...@@ -93,4 +103,12 @@ module SortingHelper
def sort_value_oldest_signin def sort_value_oldest_signin
'oldest_sign_in' 'oldest_sign_in'
end end
def sort_value_downvotes
'downvotes_desc'
end
def sort_value_upvotes
'upvotes_desc'
end
end end
...@@ -3,26 +3,27 @@ module Emails ...@@ -3,26 +3,27 @@ module Emails
def build_fail_email(build_id, to) def build_fail_email(build_id, to)
@build = Ci::Build.find(build_id) @build = Ci::Build.find(build_id)
@project = @build.project @project = @build.project
add_project_headers add_project_headers
add_build_headers add_build_headers('failed')
headers['X-GitLab-Build-Status'] = "failed"
mail(to: to, subject: subject("Build failed for #{@project.name}", @build.short_sha)) mail(to: to, subject: subject("Build failed for #{@project.name}", @build.short_sha))
end end
def build_success_email(build_id, to) def build_success_email(build_id, to)
@build = Ci::Build.find(build_id) @build = Ci::Build.find(build_id)
@project = @build.project @project = @build.project
add_project_headers add_project_headers
add_build_headers add_build_headers('success')
headers['X-GitLab-Build-Status'] = "success"
mail(to: to, subject: subject("Build success for #{@project.name}", @build.short_sha)) mail(to: to, subject: subject("Build success for #{@project.name}", @build.short_sha))
end end
private private
def add_build_headers
def add_build_headers(status)
headers['X-GitLab-Build-Id'] = @build.id headers['X-GitLab-Build-Id'] = @build.id
headers['X-GitLab-Build-Ref'] = @build.ref headers['X-GitLab-Build-Ref'] = @build.ref
headers['X-GitLab-Build-Status'] = status.to_s
end end
end end
end end
...@@ -6,21 +6,20 @@ class Ability ...@@ -6,21 +6,20 @@ class Ability
return [] if user.blocked? return [] if user.blocked?
abilities = abilities =
case subject.class.name case subject
when "Project" then project_abilities(user, subject) when CommitStatus then commit_status_abilities(user, subject)
when "Issue" then issue_abilities(user, subject) when Project then project_abilities(user, subject)
when "Note" then note_abilities(user, subject) when Issue then issue_abilities(user, subject)
when "ProjectSnippet" then project_snippet_abilities(user, subject) when Note then note_abilities(user, subject)
when "PersonalSnippet" then personal_snippet_abilities(user, subject) when ProjectSnippet then project_snippet_abilities(user, subject)
when "MergeRequest" then merge_request_abilities(user, subject) when PersonalSnippet then personal_snippet_abilities(user, subject)
when "Group" then group_abilities(user, subject) when MergeRequest then merge_request_abilities(user, subject)
when "Namespace" then namespace_abilities(user, subject) when Group then group_abilities(user, subject)
when "GroupMember" then group_member_abilities(user, subject) when Namespace then namespace_abilities(user, subject)
when "ProjectMember" then project_member_abilities(user, subject) when GroupMember then group_member_abilities(user, subject)
when ProjectMember then project_member_abilities(user, subject)
else [] else []
end end.concat(global_abilities(user))
abilities.concat(global_abilities(user))
abilities -= license_blocked_abilities if License.block_changes? abilities -= license_blocked_abilities if License.block_changes?
...@@ -41,6 +40,8 @@ class Ability ...@@ -41,6 +40,8 @@ class Ability
case true case true
when subject.is_a?(PersonalSnippet) when subject.is_a?(PersonalSnippet)
anonymous_personal_snippet_abilities(subject) anonymous_personal_snippet_abilities(subject)
when subject.is_a?(CommitStatus)
anonymous_commit_status_abilities(subject)
when subject.is_a?(Project) || subject.respond_to?(:project) when subject.is_a?(Project) || subject.respond_to?(:project)
anonymous_project_abilities(subject) anonymous_project_abilities(subject)
when subject.is_a?(Group) || subject.respond_to?(:group) when subject.is_a?(Group) || subject.respond_to?(:group)
...@@ -68,16 +69,26 @@ class Ability ...@@ -68,16 +69,26 @@ class Ability
:read_project_member, :read_project_member,
:read_merge_request, :read_merge_request,
:read_note, :read_note,
:read_build, :read_commit_status,
:download_code :download_code
] ]
# Allow to read builds by anonymous user if guests are allowed
rules << :read_build if project.public_builds?
rules - project_disabled_features_rules(project) rules - project_disabled_features_rules(project)
else else
[] []
end end
end end
def anonymous_commit_status_abilities(subject)
rules = anonymous_project_abilities(subject.project)
# If subject is Ci::Build which inherits from CommitStatus filter the abilities
rules = filter_build_abilities(rules) if subject.is_a?(Ci::Build)
rules
end
def anonymous_group_abilities(subject) def anonymous_group_abilities(subject)
group = if subject.is_a?(Group) group = if subject.is_a?(Group)
subject subject
...@@ -129,6 +140,9 @@ class Ability ...@@ -129,6 +140,9 @@ class Ability
if project.public? || project.internal? if project.public? || project.internal?
rules.push(*public_project_rules) rules.push(*public_project_rules)
# Allow to read builds for internal projects
rules << :read_build if project.public_builds?
end end
if project.owner == user || user.admin? if project.owner == user || user.admin?
...@@ -150,7 +164,8 @@ class Ability ...@@ -150,7 +164,8 @@ class Ability
def public_project_rules def public_project_rules
@public_project_rules ||= project_guest_rules + [ @public_project_rules ||= project_guest_rules + [
:download_code, :download_code,
:fork_project :fork_project,
:read_commit_status,
] ]
end end
...@@ -165,7 +180,6 @@ class Ability ...@@ -165,7 +180,6 @@ class Ability
:read_project_member, :read_project_member,
:read_merge_request, :read_merge_request,
:read_note, :read_note,
:read_build,
:create_project, :create_project,
:create_issue, :create_issue,
:create_note :create_note
...@@ -174,25 +188,26 @@ class Ability ...@@ -174,25 +188,26 @@ class Ability
def project_report_rules def project_report_rules
@project_report_rules ||= project_guest_rules + [ @project_report_rules ||= project_guest_rules + [
:create_commit_status,
:read_commit_statuses,
:read_build_artifacts,
:download_code, :download_code,
:fork_project, :fork_project,
:create_project_snippet, :create_project_snippet,
:update_issue, :update_issue,
:admin_issue, :admin_issue,
:admin_label :admin_label,
:read_commit_status,
:read_build,
] ]
end end
def project_dev_rules def project_dev_rules
@project_dev_rules ||= project_report_rules + [ @project_dev_rules ||= project_report_rules + [
:admin_merge_request, :admin_merge_request,
:create_commit_status,
:update_commit_status,
:create_build,
:update_build,
:create_merge_request, :create_merge_request,
:create_wiki, :create_wiki,
:manage_builds,
:read_build_artifacts,
:push_code :push_code
] ]
end end
...@@ -209,16 +224,21 @@ class Ability ...@@ -209,16 +224,21 @@ class Ability
def project_master_rules def project_master_rules
@project_master_rules ||= project_dev_rules + [ @project_master_rules ||= project_dev_rules + [
:read_pages,
:push_code_to_protected_branches, :push_code_to_protected_branches,
:update_project_snippet, :update_project_snippet,
:update_merge_request, :update_merge_request,
:update_pages,
:admin_milestone, :admin_milestone,
:admin_project_snippet, :admin_project_snippet,
:admin_project_member, :admin_project_member,
:admin_merge_request, :admin_merge_request,
:admin_note, :admin_note,
:admin_wiki, :admin_wiki,
:admin_project :admin_project,
:admin_commit_status,
:admin_build,
:admin_pages,
] ]
end end
...@@ -258,6 +278,10 @@ class Ability ...@@ -258,6 +278,10 @@ class Ability
rules += named_abilities('wiki') rules += named_abilities('wiki')
end end
unless project.builds_enabled
rules += named_abilities('build')
end
rules rules
end end
...@@ -398,6 +422,22 @@ class Ability ...@@ -398,6 +422,22 @@ class Ability
rules rules
end end
def commit_status_abilities(user, subject)
rules = project_abilities(user, subject.project)
# If subject is Ci::Build which inherits from CommitStatus filter the abilities
rules = filter_build_abilities(rules) if subject.is_a?(Ci::Build)
rules
end
def filter_build_abilities(rules)
# If we can't read build we should also not have that
# ability when looking at this in context of commit_status
%w(read create update admin).each do |rule|
rules.delete(:"#{rule}_commit_status") unless rules.include?(:"#{rule}_build")
end
rules
end
def abilities def abilities
@abilities ||= begin @abilities ||= begin
abilities = Six.new abilities = Six.new
......
...@@ -44,6 +44,7 @@ ...@@ -44,6 +44,7 @@
# metrics_port :integer default(8089) # metrics_port :integer default(8089)
# sentry_enabled :boolean default(FALSE) # sentry_enabled :boolean default(FALSE)
# sentry_dsn :string # sentry_dsn :string
# email_author_in_body :boolean default(FALSE)
# #
class ApplicationSetting < ActiveRecord::Base class ApplicationSetting < ActiveRecord::Base
...@@ -71,8 +72,8 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -71,8 +72,8 @@ class ApplicationSetting < ActiveRecord::Base
url: true url: true
validates :admin_notification_email, validates :admin_notification_email,
allow_blank: true, email: true,
email: true allow_blank: true
validates :two_factor_grace_period, validates :two_factor_grace_period,
numericality: { greater_than_or_equal_to: 0 } numericality: { greater_than_or_equal_to: 0 }
...@@ -93,6 +94,10 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -93,6 +94,10 @@ class ApplicationSetting < ActiveRecord::Base
presence: true, presence: true,
if: :akismet_enabled if: :akismet_enabled
validates :max_attachment_size,
presence: true,
numericality: { only_integer: true, greater_than: 0 }
validates_each :restricted_visibility_levels do |record, attr, value| validates_each :restricted_visibility_levels do |record, attr, value|
unless value.nil? unless value.nil?
value.each do |level| value.each do |level|
......
...@@ -69,10 +69,35 @@ module Issuable ...@@ -69,10 +69,35 @@ module Issuable
case method.to_s case method.to_s
when 'milestone_due_asc' then order_milestone_due_asc when 'milestone_due_asc' then order_milestone_due_asc
when 'milestone_due_desc' then order_milestone_due_desc when 'milestone_due_desc' then order_milestone_due_desc
when 'downvotes_desc' then order_downvotes_desc
when 'upvotes_desc' then order_upvotes_desc
else else
order_by(method) order_by(method)
end end
end end
def order_downvotes_desc
order_votes_desc('thumbsdown')
end
def order_upvotes_desc
order_votes_desc('thumbsup')
end
def order_votes_desc(award_emoji_name)
issuable_table = self.arel_table
note_table = Note.arel_table
join_clause = issuable_table.join(note_table, Arel::Nodes::OuterJoin).on(
note_table[:noteable_id].eq(issuable_table[:id]).and(
note_table[:noteable_type].eq(self.name).and(
note_table[:is_award].eq(true).and(note_table[:note].eq(award_emoji_name))
)
)
).join_sources
joins(join_clause).group(issuable_table[:id]).reorder("COUNT(notes.id) DESC")
end
end end
def today? def today?
...@@ -126,17 +151,17 @@ module Issuable ...@@ -126,17 +151,17 @@ module Issuable
end end
def to_hook_data(user) def to_hook_data(user)
{ hook_data = {
object_kind: self.class.name.underscore, object_kind: self.class.name.underscore,
user: user.hook_attrs, user: user.hook_attrs,
repository: { project: project.hook_attrs,
name: project.name, object_attributes: hook_attrs,
url: project.url_to_repo, # DEPRECATED
description: project.description, repository: project.hook_attrs.slice(:name, :url, :description, :homepage)
homepage: project.web_url
},
object_attributes: hook_attrs
} }
hook_data.merge!(assignee: assignee.hook_attrs) if assignee
hook_data
end end
def label_names def label_names
......
...@@ -15,7 +15,7 @@ class Email < ActiveRecord::Base ...@@ -15,7 +15,7 @@ class Email < ActiveRecord::Base
belongs_to :user belongs_to :user
validates :user_id, presence: true validates :user_id, presence: true
validates :email, presence: true, email: { strict_mode: true }, uniqueness: true validates :email, presence: true, uniqueness: true, email: true
validate :unique_email, if: ->(email) { email.email_changed? } validate :unique_email, if: ->(email) { email.email_changed? }
before_validation :cleanup_email before_validation :cleanup_email
......
...@@ -40,7 +40,6 @@ class Member < ActiveRecord::Base ...@@ -40,7 +40,6 @@ class Member < ActiveRecord::Base
if: :invite? if: :invite?
}, },
email: { email: {
strict_mode: true,
allow_nil: true allow_nil: true
}, },
uniqueness: { uniqueness: {
......
...@@ -140,6 +140,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -140,6 +140,7 @@ class MergeRequest < ActiveRecord::Base
scope :by_milestone, ->(milestone) { where(milestone_id: milestone) } scope :by_milestone, ->(milestone) { where(milestone_id: milestone) }
scope :in_projects, ->(project_ids) { where("source_project_id in (:project_ids) OR target_project_id in (:project_ids)", project_ids: project_ids) } scope :in_projects, ->(project_ids) { where("source_project_id in (:project_ids) OR target_project_id in (:project_ids)", project_ids: project_ids) }
scope :of_projects, ->(ids) { where(target_project_id: ids) } scope :of_projects, ->(ids) { where(target_project_id: ids) }
scope :opened, -> { with_states(:opened, :reopened) }
scope :merged, -> { with_state(:merged) } scope :merged, -> { with_state(:merged) }
scope :closed, -> { with_state(:closed) } scope :closed, -> { with_state(:closed) }
scope :closed_and_merged, -> { with_states(:closed, :merged) } scope :closed_and_merged, -> { with_states(:closed, :merged) }
...@@ -244,7 +245,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -244,7 +245,7 @@ class MergeRequest < ActiveRecord::Base
return unless unchecked? return unless unchecked?
can_be_merged = can_be_merged =
project.repository.can_be_merged?(source_sha, target_branch) !broken? && project.repository.can_be_merged?(source_sha, target_branch)
if can_be_merged if can_be_merged
mark_as_mergeable mark_as_mergeable
......
...@@ -35,7 +35,7 @@ class Milestone < ActiveRecord::Base ...@@ -35,7 +35,7 @@ class Milestone < ActiveRecord::Base
scope :closed, -> { with_state(:closed) } scope :closed, -> { with_state(:closed) }
scope :of_projects, ->(ids) { where(project_id: ids) } scope :of_projects, ->(ids) { where(project_id: ids) }
validates :title, presence: true validates :title, presence: true, uniqueness: { scope: :project_id }
validates :project, presence: true validates :project, presence: true
strip_attributes :title strip_attributes :title
......
class PagesDomain < ActiveRecord::Base
belongs_to :project
validates :domain, hostname: true
validates_uniqueness_of :domain, case_sensitive: false
validates :certificate, certificate: true, allow_nil: true, allow_blank: true
validates :key, certificate_key: true, allow_nil: true, allow_blank: true
validate :validate_pages_domain
validate :validate_matching_key, if: ->(domain) { domain.certificate.present? || domain.key.present? }
validate :validate_intermediates, if: ->(domain) { domain.certificate.present? }
attr_encrypted :key, mode: :per_attribute_iv_and_salt, key: Gitlab::Application.secrets.db_key_base
after_create :update
after_save :update
after_destroy :update
def to_param
domain
end
def url
return unless domain
if certificate
"https://#{domain}"
else
"http://#{domain}"
end
end
def has_matching_key?
return false unless x509
return false unless pkey
# We compare the public key stored in certificate with public key from certificate key
x509.check_private_key(pkey)
end
def has_intermediates?
return false unless x509
# self-signed certificates doesn't have the certificate chain
return true if x509.verify(x509.public_key)
store = OpenSSL::X509::Store.new
store.set_default_paths
# This forces to load all intermediate certificates stored in `certificate`
Tempfile.open('certificate_chain') do |f|
f.write(certificate)
f.flush
store.add_file(f.path)
end
store.verify(x509)
rescue OpenSSL::X509::StoreError
false
end
def expired?
return false unless x509
current = Time.new
current < x509.not_before || x509.not_after < current
end
def subject
return unless x509
x509.subject.to_s
end
def certificate_text
@certificate_text ||= x509.try(:to_text)
end
private
def update
::Projects::UpdatePagesConfigurationService.new(project).execute
end
def validate_matching_key
unless has_matching_key?
self.errors.add(:key, "doesn't match the certificate")
end
end
def validate_intermediates
unless has_intermediates?
self.errors.add(:certificate, 'misses intermediates')
end
end
def validate_pages_domain
return unless domain
if domain.downcase.ends_with?(".#{Settings.pages.host}".downcase)
self.errors.add(:domain, "*.#{Settings.pages.host} is restricted")
end
end
def x509
return unless certificate
@x509 ||= OpenSSL::X509::Certificate.new(certificate)
rescue OpenSSL::X509::CertificateError
nil
end
def pkey
return unless key
@pkey ||= OpenSSL::PKey::RSA.new(key)
rescue OpenSSL::PKey::PKeyError, OpenSSL::Cipher::CipherError
nil
end
end
...@@ -162,6 +162,7 @@ class Project < ActiveRecord::Base ...@@ -162,6 +162,7 @@ class Project < ActiveRecord::Base
has_many :lfs_objects, through: :lfs_objects_projects has_many :lfs_objects, through: :lfs_objects_projects
has_many :project_group_links, dependent: :destroy has_many :project_group_links, dependent: :destroy
has_many :invited_groups, through: :project_group_links, source: :group has_many :invited_groups, through: :project_group_links, source: :group
has_many :pages_domains, dependent: :destroy
has_one :import_data, dependent: :destroy, class_name: "ProjectImportData" has_one :import_data, dependent: :destroy, class_name: "ProjectImportData"
...@@ -377,7 +378,7 @@ class Project < ActiveRecord::Base ...@@ -377,7 +378,7 @@ class Project < ActiveRecord::Base
end end
def repository def repository
@repository ||= Repository.new(path_with_namespace, nil, self) @repository ||= Repository.new(path_with_namespace, self)
end end
def commit(id = 'HEAD') def commit(id = 'HEAD')
...@@ -425,6 +426,10 @@ class Project < ActiveRecord::Base ...@@ -425,6 +426,10 @@ class Project < ActiveRecord::Base
external_import? || forked? external_import? || forked?
end end
def no_import?
import_status == 'none'
end
def external_import? def external_import?
import_url.present? import_url.present?
end end
...@@ -844,11 +849,20 @@ class Project < ActiveRecord::Base ...@@ -844,11 +849,20 @@ class Project < ActiveRecord::Base
def hook_attrs def hook_attrs
{ {
name: name, name: name,
ssh_url: ssh_url_to_repo, description: description,
http_url: http_url_to_repo,
web_url: web_url, web_url: web_url,
avatar_url: avatar_url,
git_ssh_url: ssh_url_to_repo,
git_http_url: http_url_to_repo,
namespace: namespace.name, namespace: namespace.name,
visibility_level: visibility_level visibility_level: visibility_level,
path_with_namespace: path_with_namespace,
default_branch: default_branch,
# Backward compatibility
homepage: web_url,
url: url_to_repo,
ssh_url: ssh_url_to_repo,
http_url: http_url_to_repo
} }
end end
...@@ -896,6 +910,8 @@ class Project < ActiveRecord::Base ...@@ -896,6 +910,8 @@ class Project < ActiveRecord::Base
def change_head(branch) def change_head(branch)
# Cached divergent commit counts are based on repository head # Cached divergent commit counts are based on repository head
repository.expire_branch_cache repository.expire_branch_cache
repository.expire_root_ref_cache
gitlab_shell.update_repository_head(self.path_with_namespace, branch) gitlab_shell.update_repository_head(self.path_with_namespace, branch)
reload_default_branch reload_default_branch
end end
...@@ -1043,19 +1059,25 @@ class Project < ActiveRecord::Base ...@@ -1043,19 +1059,25 @@ class Project < ActiveRecord::Base
ensure_runners_token! ensure_runners_token!
end end
def pages_deployed?
Dir.exist?(public_pages_path)
end
def pages_url def pages_url
if Dir.exist?(public_pages_path) # The hostname always needs to be in downcased
host = "#{namespace.path}.#{Settings.pages.host}" # All web servers convert hostname to lowercase
host = "#{namespace.path}.#{Settings.pages.host}".downcase
# The host in URL always needs to be downcased
url = Gitlab.config.pages.url.sub(/^https?:\/\//) do |prefix| url = Gitlab.config.pages.url.sub(/^https?:\/\//) do |prefix|
"#{prefix}#{namespace.path}." "#{prefix}#{namespace.path}."
end end.downcase
# If the project path is the same as host, leave the short version # If the project path is the same as host, we serve it as group page
return url if host == path return url if host == path
"#{url}/#{path}" "#{url}/#{path}"
end end
end
def pages_path def pages_path
File.join(Settings.pages.path, path_with_namespace) File.join(Settings.pages.path, path_with_namespace)
...@@ -1069,7 +1091,7 @@ class Project < ActiveRecord::Base ...@@ -1069,7 +1091,7 @@ class Project < ActiveRecord::Base
# 1. We rename pages to temporary directory # 1. We rename pages to temporary directory
# 2. We wait 5 minutes, due to NFS caching # 2. We wait 5 minutes, due to NFS caching
# 3. We asynchronously remove pages with force # 3. We asynchronously remove pages with force
temp_path = "#{path}.#{SecureRandom.hex}" temp_path = "#{path}.#{SecureRandom.hex}.deleted"
if Gitlab::PagesTransfer.new.rename_project(path, temp_path, namespace.path) if Gitlab::PagesTransfer.new.rename_project(path, temp_path, namespace.path)
PagesWorker.perform_in(5.minutes, :remove, namespace.path, temp_path) PagesWorker.perform_in(5.minutes, :remove, namespace.path, temp_path)
......
...@@ -112,7 +112,7 @@ class PushoverService < Service ...@@ -112,7 +112,7 @@ class PushoverService < Service
priority: priority, priority: priority,
title: "#{project.name_with_namespace}", title: "#{project.name_with_namespace}",
message: message, message: message,
url: data[:repository][:homepage], url: data[:project][:web_url],
url_title: "See project #{project.name_with_namespace}" url_title: "See project #{project.name_with_namespace}"
} }
......
...@@ -131,7 +131,7 @@ class ProjectWiki ...@@ -131,7 +131,7 @@ class ProjectWiki
end end
def repository def repository
Repository.new(path_with_namespace, default_branch, @project) Repository.new(path_with_namespace, @project)
end end
def default_branch def default_branch
......
...@@ -19,7 +19,7 @@ class Repository ...@@ -19,7 +19,7 @@ class Repository
Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete)) Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete))
end end
def initialize(path_with_namespace, default_branch = nil, project = nil) def initialize(path_with_namespace, project)
@path_with_namespace = path_with_namespace @path_with_namespace = path_with_namespace
@project = project @project = project
end end
...@@ -27,13 +27,11 @@ class Repository ...@@ -27,13 +27,11 @@ class Repository
def raw_repository def raw_repository
return nil unless path_with_namespace return nil unless path_with_namespace
@raw_repository ||= begin @raw_repository ||= Gitlab::Git::Repository.new(path_to_repo)
repo = Gitlab::Git::Repository.new(path_to_repo)
repo.autocrlf = :input
repo
rescue Gitlab::Git::Repository::NoRepository
nil
end end
def update_autocrlf_option
raw_repository.autocrlf = :input if raw_repository.autocrlf != :input
end end
# Return absolute path to repository # Return absolute path to repository
...@@ -44,11 +42,18 @@ class Repository ...@@ -44,11 +42,18 @@ class Repository
end end
def exists? def exists?
raw_repository return false unless raw_repository
raw_repository.rugged
true
rescue Gitlab::Git::Repository::NoRepository
false
end end
def empty? def empty?
raw_repository.empty? return @empty unless @empty.nil?
@empty = cache.fetch(:empty?) { raw_repository.empty? }
end end
# #
...@@ -61,11 +66,15 @@ class Repository ...@@ -61,11 +66,15 @@ class Repository
# This method return true if repository contains some content visible in project page. # This method return true if repository contains some content visible in project page.
# #
def has_visible_content? def has_visible_content?
return @has_visible_content unless @has_visible_content.nil?
@has_visible_content = cache.fetch(:has_visible_content?) do
raw_repository.branch_count > 0 raw_repository.branch_count > 0
end end
end
def commit(id = 'HEAD') def commit(id = 'HEAD')
return nil unless raw_repository return nil unless exists?
commit = Gitlab::Git::Commit.find(raw_repository, id) commit = Gitlab::Git::Commit.find(raw_repository, id)
commit = Commit.new(commit, @project) if commit commit = Commit.new(commit, @project) if commit
commit commit
...@@ -82,7 +91,8 @@ class Repository ...@@ -82,7 +91,8 @@ class Repository
offset: offset, offset: offset,
# --follow doesn't play well with --skip. See: # --follow doesn't play well with --skip. See:
# https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520 # https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520
follow: false follow: false,
skip_merges: skip_merges
} }
commits = Gitlab::Git::Commit.where(options) commits = Gitlab::Git::Commit.where(options)
...@@ -223,12 +233,6 @@ class Repository ...@@ -223,12 +233,6 @@ class Repository
readme version contribution_guide changelog license) readme version contribution_guide changelog license)
end end
def branch_cache_keys
branches.map do |branch|
:"diverging_commit_counts_#{branch.name}"
end
end
def build_cache def build_cache
cache_keys.each do |key| cache_keys.each do |key|
unless cache.exist?(key) unless cache.exist?(key)
...@@ -253,18 +257,54 @@ class Repository ...@@ -253,18 +257,54 @@ class Repository
@branches = nil @branches = nil
end end
def expire_cache def expire_cache(branch_name = nil)
cache_keys.each do |key| cache_keys.each do |key|
cache.expire(key) cache.expire(key)
end end
expire_branch_cache expire_branch_cache(branch_name)
end end
def expire_branch_cache # Expires _all_ caches, including those that would normally only be expired
# under specific conditions.
def expire_all_caches!
expire_cache
expire_root_ref_cache
expire_emptiness_caches
expire_has_visible_content_cache
end
def expire_branch_cache(branch_name = nil)
# When we push to the root branch we have to flush the cache for all other
# branches as their statistics are based on the commits relative to the
# root branch.
if !branch_name || branch_name == root_ref
branches.each do |branch| branches.each do |branch|
cache.expire(:"diverging_commit_counts_#{branch.name}") cache.expire(:"diverging_commit_counts_#{branch.name}")
end end
# In case a commit is pushed to a non-root branch we only have to flush the
# cache for said branch.
else
cache.expire(:"diverging_commit_counts_#{branch_name}")
end
end
def expire_root_ref_cache
cache.expire(:root_ref)
@root_ref = nil
end
# Expires the cache(s) used to determine if a repository is empty or not.
def expire_emptiness_caches
cache.expire(:empty?)
@empty = nil
expire_has_visible_content_cache
end
def expire_has_visible_content_cache
cache.expire(:has_visible_content?)
@has_visible_content = nil
end end
def rebuild_cache def rebuild_cache
...@@ -504,7 +544,7 @@ class Repository ...@@ -504,7 +544,7 @@ class Repository
end end
def root_ref def root_ref
@root_ref ||= raw_repository.root_ref @root_ref ||= cache.fetch(:root_ref) { raw_repository.root_ref }
end end
def commit_dir(user, path, message, branch) def commit_dir(user, path, message, branch)
...@@ -666,6 +706,8 @@ class Repository ...@@ -666,6 +706,8 @@ class Repository
end end
def merge_base(first_commit_id, second_commit_id) def merge_base(first_commit_id, second_commit_id)
first_commit_id = commit(first_commit_id).try(:id) || first_commit_id
second_commit_id = commit(second_commit_id).try(:id) || second_commit_id
rugged.merge_base(first_commit_id, second_commit_id) rugged.merge_base(first_commit_id, second_commit_id)
rescue Rugged::ReferenceError rescue Rugged::ReferenceError
nil nil
...@@ -691,7 +733,7 @@ class Repository ...@@ -691,7 +733,7 @@ class Repository
end end
def parse_search_result_from_elastic(result) def parse_search_result_from_elastic(result)
ref = result["_source"]["blob"]["oid"] ref = result["_source"]["blob"]["commit_sha"]
filename = result["_source"]["blob"]["path"] filename = result["_source"]["blob"]["path"]
content = result["_source"]["blob"]["content"] content = result["_source"]["blob"]["content"]
total_lines = content.lines.size total_lines = content.lines.size
...@@ -775,6 +817,8 @@ class Repository ...@@ -775,6 +817,8 @@ class Repository
end end
def commit_with_hooks(current_user, branch) def commit_with_hooks(current_user, branch)
update_autocrlf_option
oldrev = Gitlab::Git::BLANK_SHA oldrev = Gitlab::Git::BLANK_SHA
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
was_empty = empty? was_empty = empty?
......
...@@ -148,11 +148,8 @@ class User < ActiveRecord::Base ...@@ -148,11 +148,8 @@ class User < ActiveRecord::Base
# Validations # Validations
# #
validates :name, presence: true validates :name, presence: true
# Note that a 'uniqueness' and presence check is provided by devise :validatable for email. We do not need to validates :notification_email, presence: true, email: true
# duplicate that here as the validation framework will have duplicate errors in the event of a failure. validates :public_email, presence: true, uniqueness: true, email: true, allow_blank: true
validates :email, presence: true, email: { strict_mode: true }
validates :notification_email, presence: true, email: { strict_mode: true }
validates :public_email, presence: true, email: { strict_mode: true }, allow_blank: true, uniqueness: true
validates :bio, length: { maximum: 255 }, allow_blank: true validates :bio, length: { maximum: 255 }, allow_blank: true
validates :projects_limit, presence: true, numericality: { greater_than_or_equal_to: 0 } validates :projects_limit, presence: true, numericality: { greater_than_or_equal_to: 0 }
validates :username, validates :username,
......
class ArchiveRepositoryService
attr_reader :project, :ref, :format
def initialize(project, ref, format)
format ||= 'tar.gz'
@project, @ref, @format = project, ref, format.downcase
end
def execute(options = {})
RepositoryArchiveCacheWorker.perform_async
metadata = project.repository.archive_metadata(ref, storage_path, format)
raise "Repository or ref not found" if metadata.empty?
metadata
end
private
def storage_path
Gitlab.config.gitlab.repository_downloads_path
end
end
...@@ -34,6 +34,7 @@ module Ci ...@@ -34,6 +34,7 @@ module Ci
build = commit.builds.create!(build_attrs) build = commit.builds.create!(build_attrs)
build.execute_hooks build.execute_hooks
build
end end
end end
end end
......
module Ci module Ci
class ImageForBuildService class ImageForBuildService
def execute(project, params) def execute(project, opts)
sha = params[:sha] sha = opts[:sha] || ref_sha(project, opts[:ref])
sha ||=
if params[:ref]
project.commit(params[:ref]).try(:sha)
end
commit = project.ci_commits.ordered.find_by(sha: sha) commit = project.ci_commits.ordered.find_by(sha: sha)
image_name = image_for_commit(commit) image_name = image_for_commit(commit)
image_path = Rails.root.join('public/ci', image_name) image_path = Rails.root.join('public/ci', image_name)
OpenStruct.new(path: image_path, name: image_name)
OpenStruct.new(
path: image_path,
name: image_name
)
end end
private private
def ref_sha(project, ref)
project.commit(ref).try(:sha) if ref
end
def image_for_commit(commit) def image_for_commit(commit)
return 'build-unknown.svg' unless commit return 'build-unknown.svg' unless commit
'build-' + commit.status + ".svg" 'build-' + commit.status + ".svg"
end end
end end
......
...@@ -29,11 +29,7 @@ class CreateBranchService < BaseService ...@@ -29,11 +29,7 @@ class CreateBranchService < BaseService
end end
if new_branch if new_branch
push_data = build_push_data(project, current_user, new_branch) # GitPushService handles execution of services and hooks for branch pushes
project.execute_hooks(push_data.dup, :push_hooks)
project.execute_services(push_data.dup, :push_hooks)
success(new_branch) success(new_branch)
else else
error('Invalid reference name') error('Invalid reference name')
......
...@@ -25,11 +25,7 @@ class DeleteBranchService < BaseService ...@@ -25,11 +25,7 @@ class DeleteBranchService < BaseService
end end
if repository.rm_branch(current_user, branch_name) if repository.rm_branch(current_user, branch_name)
push_data = build_push_data(branch) # GitPushService handles execution of services and hooks for branch pushes
project.execute_hooks(push_data.dup, :push_hooks)
project.execute_services(push_data.dup, :push_hooks)
success('Branch was removed') success('Branch was removed')
else else
error('Failed to remove branch') error('Failed to remove branch')
......
class GitPushService class GitPushService < BaseService
attr_accessor :project, :user, :push_data, :push_commits attr_accessor :push_data, :push_commits
include Gitlab::CurrentSettings include Gitlab::CurrentSettings
include Gitlab::Access include Gitlab::Access
# This method will be called after each git update # This method will be called after each git update
# and only if the provided user and project is present in GitLab. # and only if the provided user and project are present in GitLab.
# #
# All callbacks for post receive action should be placed here. # All callbacks for post receive action should be placed here.
# #
...@@ -15,71 +15,76 @@ class GitPushService ...@@ -15,71 +15,76 @@ class GitPushService
# 4. Executes the project's web hooks # 4. Executes the project's web hooks
# 5. Executes the project's services # 5. Executes the project's services
# #
def execute(project, user, oldrev, newrev, ref) def execute
@project, @user = project, user @project.repository.expire_cache(branch_name)
project.repository.expire_cache if push_remove_branch?
@project.repository.expire_has_visible_content_cache
if push_remove_branch?(ref, newrev)
@push_commits = [] @push_commits = []
elsif push_to_new_branch?(ref, oldrev) elsif push_to_new_branch?
@project.repository.expire_has_visible_content_cache
# Re-find the pushed commits. # Re-find the pushed commits.
if is_default_branch?(ref) if is_default_branch?
# Initial push to the default branch. Take the full history of that branch as "newly pushed". # Initial push to the default branch. Take the full history of that branch as "newly pushed".
@push_commits = project.repository.commits(newrev) process_default_branch
# Ensure HEAD points to the default branch in case it is not master
branch_name = Gitlab::Git.ref_name(ref)
project.change_head(branch_name)
# Set protection on the default branch if configured
if (current_application_settings.default_branch_protection != PROTECTION_NONE)
developers_can_push = current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_PUSH ? true : false
project.protected_branches.create({ name: project.default_branch, developers_can_push: developers_can_push })
end
else else
# Use the pushed commits that aren't reachable by the default branch # Use the pushed commits that aren't reachable by the default branch
# as a heuristic. This may include more commits than are actually pushed, but # as a heuristic. This may include more commits than are actually pushed, but
# that shouldn't matter because we check for existing cross-references later. # that shouldn't matter because we check for existing cross-references later.
@push_commits = project.repository.commits_between(project.default_branch, newrev) @push_commits = @project.repository.commits_between(@project.default_branch, params[:newrev])
# don't process commits for the initial push to the default branch # don't process commits for the initial push to the default branch
process_commit_messages(ref) process_commit_messages
end end
elsif push_to_existing_branch?(ref, oldrev) elsif push_to_existing_branch?
# Collect data for this git push # Collect data for this git push
@push_commits = project.repository.commits_between(oldrev, newrev) @push_commits = @project.repository.commits_between(params[:oldrev], params[:newrev])
process_commit_messages(ref) process_commit_messages
end end
# Update merge requests that may be affected by this push. A new branch # Update merge requests that may be affected by this push. A new branch
# could cause the last commit of a merge request to change. # could cause the last commit of a merge request to change.
project.update_merge_requests(oldrev, newrev, ref, @user) update_merge_requests
end
protected
@push_data = build_push_data(oldrev, newrev, ref) def update_merge_requests
@project.update_merge_requests(params[:oldrev], params[:newrev], params[:ref], current_user)
mirror_update = @project.mirror? && @project.repository.up_to_date_with_upstream?(branch_name)
branch_name = Gitlab::Git.ref_name(ref) EventCreateService.new.push(@project, current_user, build_push_data)
mirror_update = project.mirror? && project.repository.up_to_date_with_upstream?(branch_name) @project.execute_hooks(build_push_data.dup, :push_hooks)
@project.execute_services(build_push_data.dup, :push_hooks)
EventCreateService.new.push(project, user, @push_data) index_commits_blobs if Gitlab.config.elasticsearch.enabled
project.execute_hooks(@push_data.dup, :push_hooks)
project.execute_services(@push_data.dup, :push_hooks)
if Gitlab.config.elasticsearch.enabled CreateCommitBuildsService.new.execute(@project, current_user, build_push_data, mirror_update: mirror_update)
project.repository.index_commits(from_rev: oldrev, to_rev: newrev) ProjectCacheWorker.perform_async(@project.id)
project.repository.index_blobs(from_rev: oldrev, to_rev: newrev)
end end
CreateCommitBuildsService.new.execute(project, @user, @push_data, mirror_update: mirror_update) def index_commits_blobs
ProjectCacheWorker.perform_async(project.id) @project.repository.index_commits(from_rev: params[:oldrev], to_rev: params[:newrev])
@project.repository.index_blobs(from_rev: params[:oldrev], to_rev: params[:newrev])
end end
protected def process_default_branch
@push_commits = project.repository.commits(params[:newrev])
# Ensure HEAD points to the default branch in case it is not master
project.change_head(branch_name)
# Set protection on the default branch if configured
if (current_application_settings.default_branch_protection != PROTECTION_NONE)
developers_can_push = current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_PUSH ? true : false
@project.protected_branches.create({ name: @project.default_branch, developers_can_push: developers_can_push })
end
end
# Extract any GFM references from the pushed commit messages. If the configured issue-closing regex is matched, # Extract any GFM references from the pushed commit messages. If the configured issue-closing regex is matched,
# close the referenced Issue. Create cross-reference Notes corresponding to any other referenced Mentionables. # close the referenced Issue. Create cross-reference Notes corresponding to any other referenced Mentionables.
def process_commit_messages(ref) def process_commit_messages
is_default_branch = is_default_branch?(ref) is_default_branch = is_default_branch?
authors = Hash.new do |hash, commit| authors = Hash.new do |hash, commit|
email = commit.author_email email = commit.author_email
...@@ -98,7 +103,7 @@ class GitPushService ...@@ -98,7 +103,7 @@ class GitPushService
# Close issues if these commits were pushed to the project's default branch and the commit message matches the # Close issues if these commits were pushed to the project's default branch and the commit message matches the
# closing regex. Exclude any mentioned Issues from cross-referencing even if the commits are being pushed to # closing regex. Exclude any mentioned Issues from cross-referencing even if the commits are being pushed to
# a different branch. # a different branch.
closed_issues = commit.closes_issues(user) closed_issues = commit.closes_issues(current_user)
closed_issues.each do |issue| closed_issues.each do |issue|
Issues::CloseService.new(project, authors[commit], {}).execute(issue, commit) Issues::CloseService.new(project, authors[commit], {}).execute(issue, commit)
end end
...@@ -108,34 +113,38 @@ class GitPushService ...@@ -108,34 +113,38 @@ class GitPushService
end end
end end
def build_push_data(oldrev, newrev, ref) def build_push_data
Gitlab::PushDataBuilder. @push_data ||= Gitlab::PushDataBuilder.
build(project, user, oldrev, newrev, ref, push_commits) build(@project, current_user, params[:oldrev], params[:newrev], params[:ref], push_commits)
end end
def push_to_existing_branch?(ref, oldrev) def push_to_existing_branch?
# Return if this is not a push to a branch (e.g. new commits) # Return if this is not a push to a branch (e.g. new commits)
Gitlab::Git.branch_ref?(ref) && !Gitlab::Git.blank_ref?(oldrev) Gitlab::Git.branch_ref?(params[:ref]) && !Gitlab::Git.blank_ref?(params[:oldrev])
end end
def push_to_new_branch?(ref, oldrev) def push_to_new_branch?
Gitlab::Git.branch_ref?(ref) && Gitlab::Git.blank_ref?(oldrev) Gitlab::Git.branch_ref?(params[:ref]) && Gitlab::Git.blank_ref?(params[:oldrev])
end end
def push_remove_branch?(ref, newrev) def push_remove_branch?
Gitlab::Git.branch_ref?(ref) && Gitlab::Git.blank_ref?(newrev) Gitlab::Git.branch_ref?(params[:ref]) && Gitlab::Git.blank_ref?(params[:newrev])
end end
def push_to_branch?(ref) def push_to_branch?
Gitlab::Git.branch_ref?(ref) Gitlab::Git.branch_ref?(params[:ref])
end end
def is_default_branch?(ref) def is_default_branch?
Gitlab::Git.branch_ref?(ref) && Gitlab::Git.branch_ref?(params[:ref]) &&
(Gitlab::Git.ref_name(ref) == project.default_branch || project.default_branch.nil?) (Gitlab::Git.ref_name(params[:ref]) == project.default_branch || project.default_branch.nil?)
end end
def commit_user(commit) def commit_user(commit)
commit.author || user commit.author || current_user
end
def branch_name
@branch_name ||= Gitlab::Git.ref_name(params[:ref])
end end
end end
...@@ -16,11 +16,15 @@ module Projects ...@@ -16,11 +16,15 @@ module Projects
return false unless can?(current_user, :remove_project, project) return false unless can?(current_user, :remove_project, project)
project.team.truncate project.team.truncate
project.repository.expire_cache unless project.empty_repo?
repo_path = project.path_with_namespace repo_path = project.path_with_namespace
wiki_path = repo_path + '.wiki' wiki_path = repo_path + '.wiki'
# Flush the cache for both repositories. This has to be done _before_
# removing the physical repositories as some expiration code depends on
# Git data (e.g. a list of branch names).
flush_caches(project, wiki_path)
Project.transaction do Project.transaction do
project.destroy! project.destroy!
...@@ -70,5 +74,13 @@ module Projects ...@@ -70,5 +74,13 @@ module Projects
def removal_path(path) def removal_path(path)
"#{path}+#{project.id}#{DELETED_FLAG}" "#{path}+#{project.id}#{DELETED_FLAG}"
end end
def flush_caches(project, wiki_path)
project.repository.expire_all_caches! if project.repository.exists?
wiki_repo = Repository.new(wiki_path, project)
wiki_repo.expire_all_caches! if wiki_repo.exists?
end
end end
end end
module Projects
class UpdatePagesConfigurationService < BaseService
attr_reader :project
def initialize(project)
@project = project
end
def execute
update_file(pages_config_file, pages_config.to_json)
reload_daemon
success
rescue => e
error(e.message)
end
private
def pages_config
{
domains: pages_domains_config
}
end
def pages_domains_config
project.pages_domains.map do |domain|
{
domain: domain.domain,
certificate: domain.certificate,
key: domain.key,
}
end
end
def reload_daemon
# GitLab Pages daemon constantly watches for modification time of `pages.path`
# It reloads configuration when `pages.path` is modified
update_file(pages_update_file, SecureRandom.hex(64))
end
def pages_path
@pages_path ||= project.pages_path
end
def pages_config_file
File.join(pages_path, 'config.json')
end
def pages_update_file
File.join(Settings.pages.path, '.update')
end
def update_file(file, data)
unless data
FileUtils.remove(file, force: true)
return
end
temp_file = "#{file}.#{SecureRandom.hex(16)}"
File.open(temp_file, 'w') do |f|
f.write(data)
end
FileUtils.move(temp_file, file, force: true)
ensure
# In case if the updating fails
FileUtils.remove(temp_file, force: true)
end
end
end
...@@ -274,12 +274,15 @@ class SystemNoteService ...@@ -274,12 +274,15 @@ class SystemNoteService
# Check if a cross reference to a noteable from a mentioner already exists # Check if a cross reference to a noteable from a mentioner already exists
# #
# This method is used to prevent multiple notes being created for a mention # This method is used to prevent multiple notes being created for a mention
# when a issue is updated, for example. # when a issue is updated, for example. The method also calls notes_for_mentioner
# to check if the mentioner is a commit, and return matches only on commit hash
# instead of project + commit, to avoid repeated mentions from forks.
# #
# noteable - Noteable object being referenced # noteable - Noteable object being referenced
# mentioner - Mentionable object # mentioner - Mentionable object
# #
# Returns Boolean # Returns Boolean
def self.cross_reference_exists?(noteable, mentioner) def self.cross_reference_exists?(noteable, mentioner)
# Initial scope should be system notes of this noteable type # Initial scope should be system notes of this noteable type
notes = Note.system.where(noteable_type: noteable.class) notes = Note.system.where(noteable_type: noteable.class)
...@@ -291,10 +294,7 @@ class SystemNoteService ...@@ -291,10 +294,7 @@ class SystemNoteService
notes = notes.where(noteable_id: noteable.id) notes = notes.where(noteable_id: noteable.id)
end end
gfm_reference = mentioner.gfm_reference(noteable.project) notes_for_mentioner(mentioner, noteable, notes).count > 0
notes = notes.where(note: cross_reference_note_content(gfm_reference))
notes.count > 0
end end
# Called when the merge request is approved by user # Called when the merge request is approved by user
...@@ -315,6 +315,15 @@ class SystemNoteService ...@@ -315,6 +315,15 @@ class SystemNoteService
private private
def self.notes_for_mentioner(mentioner, noteable, notes)
if mentioner.is_a?(Commit)
notes.where('note LIKE ?', "#{cross_reference_note_prefix}%#{mentioner.to_reference(nil)}")
else
gfm_reference = mentioner.gfm_reference(noteable.project)
notes.where(note: cross_reference_note_content(gfm_reference))
end
end
def self.create_note(args = {}) def self.create_note(args = {})
Note.create(args.merge(system: true)) Note.create(args.merge(system: true))
end end
......
# UrlValidator
#
# Custom validator for private keys.
#
# class Project < ActiveRecord::Base
# validates :certificate_key, certificate_key: true
# end
#
class CertificateKeyValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless valid_private_key_pem?(value)
record.errors.add(attribute, "must be a valid PEM private key")
end
end
private
def valid_private_key_pem?(value)
return false unless value
pkey = OpenSSL::PKey::RSA.new(value)
pkey.private?
rescue OpenSSL::PKey::PKeyError
false
end
end
# UrlValidator
#
# Custom validator for private keys.
#
# class Project < ActiveRecord::Base
# validates :certificate_key, certificate: true
# end
#
class CertificateValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless valid_certificate_pem?(value)
record.errors.add(attribute, "must be a valid PEM certificate")
end
end
private
def valid_certificate_pem?(value)
return false unless value
OpenSSL::X509::Certificate.new(value).present?
rescue OpenSSL::X509::CertificateError
false
end
end
# EmailValidator
#
# Based on https://github.com/balexand/email_validator
#
# Extended to use only strict mode with following allowed characters:
# ' - apostrophe
#
# See http://www.remote.org/jochen/mail/info/chars.html
#
class EmailValidator < ActiveModel::EachValidator class EmailValidator < ActiveModel::EachValidator
PATTERN = /\A\s*([-a-z0-9+._']{1,64})@((?:[-a-z0-9]+\.)+[a-z]{2,})\s*\z/i.freeze
def validate_each(record, attribute, value) def validate_each(record, attribute, value)
unless value =~ PATTERN record.errors.add(attribute, :invalid) unless value =~ Devise.email_regexp
record.errors.add(attribute, options[:message] || :invalid)
end
end end
end end
...@@ -47,6 +47,16 @@ ...@@ -47,6 +47,16 @@
= f.label :version_check_enabled do = f.label :version_check_enabled do
= f.check_box :version_check_enabled = f.check_box :version_check_enabled
Version check enabled Version check enabled
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :email_author_in_body do
= f.check_box :email_author_in_body
Include author name in notification email body
.help-block
Some email servers do not support overriding the email sender name.
Enable this option to include the name of the author of the issue,
merge request or comment in the email body instead.
.form-group .form-group
= f.label :admin_notification_email, class: 'control-label col-sm-2' = f.label :admin_notification_email, class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
......
.broadcast-message-preview{ style: broadcast_message_style(@broadcast_message) } .broadcast-message-preview{ style: broadcast_message_style(@broadcast_message) }
= icon('bullhorn') = icon('bullhorn')
%span= @broadcast_message.message || "Your message here" .js-broadcast-message-preview
= render_broadcast_message(@broadcast_message.message.presence || "Your message here")
= form_for [:admin, @broadcast_message], html: { class: 'broadcast-message-form form-horizontal js-requires-input'} do |f| = form_for [:admin, @broadcast_message], html: { class: 'broadcast-message-form form-horizontal js-requires-input'} do |f|
-if @broadcast_message.errors.any? -if @broadcast_message.errors.any?
...@@ -10,7 +11,9 @@ ...@@ -10,7 +11,9 @@
.form-group .form-group
= f.label :message, class: 'control-label' = f.label :message, class: 'control-label'
.col-sm-10 .col-sm-10
= f.text_area :message, class: "form-control js-quick-submit", rows: 2, required: true = f.text_area :message, class: "form-control js-quick-submit js-autosize",
required: true,
data: { preview_path: preview_admin_broadcast_messages_path }
.form-group.js-toggle-colors-container .form-group.js-toggle-colors-container
.col-sm-10.col-sm-offset-2 .col-sm-10.col-sm-offset-2
= link_to 'Customize colors', '#', class: 'js-toggle-colors-link' = link_to 'Customize colors', '#', class: 'js-toggle-colors-link'
......
$('.js-broadcast-message-preview').html("#{j(render_broadcast_message(@message))}");
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
= ci_status_with_icon(build.status) = ci_status_with_icon(build.status)
%td.build-link %td.build-link
- if build.target_url - if can?(current_user, :read_build, project) && build.target_url
= link_to build.target_url do = link_to build.target_url do
%strong Build ##{build.id} %strong Build ##{build.id}
- else - else
...@@ -60,10 +60,10 @@ ...@@ -60,10 +60,10 @@
%td %td
.pull-right .pull-right
- if current_user && can?(current_user, :read_build_artifacts, project) && build.artifacts? - if can?(current_user, :read_build, project) && build.artifacts?
= link_to build.artifacts_download_url, title: 'Download artifacts' do = link_to build.artifacts_download_url, title: 'Download artifacts' do
%i.fa.fa-download %i.fa.fa-download
- if current_user && can?(current_user, :manage_builds, build.project) - if can?(current_user, :update_build, build.project)
- if build.active? - if build.active?
- if build.cancel_url - if build.cancel_url
= link_to build.cancel_url, method: :post, title: 'Cancel' do = link_to build.cancel_url, method: :post, title: 'Cancel' do
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
.nav-controls .nav-controls
= form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f| = form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
= search_field_tag :filter_projects, params[:filter_projects], placeholder: 'Filter by name...', class: 'project-filter-form-field form-control input-short', spellcheck: false, id: 'project-filter-form-field' = search_field_tag :filter_projects, params[:filter_projects], placeholder: 'Filter by name...', class: 'project-filter-form-field form-control input-short projects-list-filter', spellcheck: false, id: 'project-filter-form-field'
= render 'explore/projects/dropdown' = render 'explore/projects/dropdown'
- if current_user.can_create_project? - if current_user.can_create_project?
= link_to new_project_path, class: 'btn btn-new' do = link_to new_project_path, class: 'btn btn-new' do
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
%br %br
- if current_user.can_create_project? - if current_user.can_create_project?
You can create up to You can create up to
%strong= pluralize(current_user.projects_limit, "project") + "." %strong= pluralize(number_with_delimiter(current_user.projects_limit), "project") + "."
- else - else
If you are added to a project, it will be displayed here. If you are added to a project, it will be displayed here.
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
.dashboard-intro-text .dashboard-intro-text
%p.slead %p.slead
There are There are
%strong= publicish_project_count %strong= number_with_delimiter(publicish_project_count)
public projects on this server. public projects on this server.
%br %br
Public projects are an easy way to allow everyone to have read-only access. Public projects are an easy way to allow everyone to have read-only access.
......
.pull-left
= form_tag explore_projects_filter_path, method: :get, class: 'form-inline form-tiny' do |f|
.form-group
= search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input", id: "projects_search", spellcheck: false
= hidden_field_tag :sort, @sort
.form-group
= button_tag 'Search', class: "btn"
.pull-right.hidden-sm.hidden-xs .pull-right.hidden-sm.hidden-xs
- if current_user - if current_user
.dropdown.inline.append-right-10 .dropdown.inline.append-right-10
......
- if projects.any? - if projects.any?
.public-projects .projects-list-holder
= render 'shared/projects/list', projects: projects = render 'shared/projects/list', projects: projects
- else - else
.nothing-here-block .nothing-here-block
......
.projects-list-holder.prepend-top-default .top-area
.input-group .nav-controls
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false = form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
- if can? current_user, :create_projects, @group = search_field_tag :filter_projects, params[:filter_projects], placeholder: 'Filter by name...', class: 'input-short project-filter-form-field form-control projects-list-filter', spellcheck: false, id: 'project-filter-form-field'
%span.input-group-btn - if current_user && current_user.can_create_project?
= link_to new_project_path(namespace_id: @group.id), class: 'btn btn-new' do = link_to new_project_path, class: 'btn btn-new' do
= icon('plus') = icon('plus')
New Project New Project
.projects-list-holder
= render 'shared/projects/list', projects: @projects, projects_limit: 20, stars: false, skip_namespace: true = render 'shared/projects/list', projects: @projects, projects_limit: 20, stars: false, skip_namespace: true
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
- header_title group_title(@group, "Members", group_group_members_path(@group)) - header_title group_title(@group, "Members", group_group_members_path(@group))
.group-members-page.prepend-top-default .group-members-page.prepend-top-default
- if current_user && current_user.can?(:admin_group_member, @group) - if current_user && can?(current_user, :admin_group_member, @group)
.panel.panel-default .panel.panel-default
.panel-heading .panel-heading
Add new user to group Add new user to group
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
.new-group-member-holder .new-group-member-holder
= render "new_group_member" = render "new_group_member"
- if @group.ldap_synced? - if current_user && @group.ldap_synced?
.bs-callout.bs-callout-info .bs-callout.bs-callout-info
The members of this group are managed using LDAP and cannot be added, changed or removed here. The members of this group are managed using LDAP and cannot be added, changed or removed here.
Because LDAP permissions in GitLab get updated one user at a time and because GitLab caches LDAP check results, changes on your LDAP server or in this group's LDAP sync settings may take up to #{Gitlab.config.ldap['sync_time']}s to show in the list below. Because LDAP permissions in GitLab get updated one user at a time and because GitLab caches LDAP check results, changes on your LDAP server or in this group's LDAP sync settings may take up to #{Gitlab.config.ldap['sync_time']}s to show in the list below.
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
are given are given
%code= ldap_group_link.human_access %code= ldap_group_link.human_access
access. access.
- if current_user && current_user.can?(:admin_group, @group) - if can?(current_user, :admin_group_member, @group)
= form_tag(reset_access_group_ldap_path(@group), method: :put, class: 'inline') do = form_tag(reset_access_group_ldap_path(@group), method: :put, class: 'inline') do
= button_to 'Clear LDAP permission cache', '#', class: "btn btn-remove js-confirm-danger", = button_to 'Clear LDAP permission cache', '#', class: "btn btn-remove js-confirm-danger",
data: { "confirm-danger-message" => clear_ldap_permission_cache_message, data: { "confirm-danger-message" => clear_ldap_permission_cache_message,
......
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
= render 'shared/event_filter' = render 'shared/event_filter'
.content_list .content_list{data: {href: events_group_path}}
= spinner = spinner
.tab-pane#projects .tab-pane#projects
......
...@@ -49,6 +49,11 @@ ...@@ -49,6 +49,11 @@
= icon('clone fw') = icon('clone fw')
%span %span
Mirror Repository Mirror Repository
= nav_link(controller: :pages) do
= link_to namespace_project_pages_path(@project.namespace, @project), title: 'Pages', data: {placement: 'right'} do
= icon('cloud-upload fw')
%span
Pages
= nav_link(controller: :audit_events) do = nav_link(controller: :audit_events) do
= link_to namespace_project_audit_events_path(@project.namespace, @project) do = link_to namespace_project_audit_events_path(@project.namespace, @project) do
= icon('file-text-o fw') = icon('file-text-o fw')
......
- if current_application_settings.email_author_in_body
%div
#{link_to @note.author_name, user_url(@note.author)} wrote:
%div %div
= markdown(@note.note, pipeline: :email) = markdown(@note.note, pipeline: :email)
- content_for :header do - content_for :header do
%h1{style: "background: #c40834; color: #FFF; font: normal 20px Helvetica, Arial, sans-serif; margin: 0; padding: 5px 10px; line-height: 32px; font-size: 16px;"} %h1{style: "background: #c40834; color: #FFF; font: normal 20px Helvetica, Arial, sans-serif; margin: 0; padding: 5px 10px; line-height: 32px; font-size: 16px;"}
GitLab (build failed) GitLab (build failed)
%h3 %h3
Project: Project:
= link_to ci_project_url(@project) do = link_to namespace_project_url(@project.namespace, @project) do
= @project.name = @project.name
%p %p
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
%h3 %h3
Project: Project:
= link_to ci_project_url(@project) do = link_to namespace_project_url(@project.namespace, @project) do
= @project.name = @project.name
%p %p
......
- if current_application_settings.email_author_in_body
%div
#{link_to @issue.author_name, user_url(@issue.author)} wrote:
-if @issue.description -if @issue.description
= markdown(@issue.description, pipeline: :email) = markdown(@issue.description, pipeline: :email)
......
- if current_application_settings.email_author_in_body
%div
#{link_to @merge_request.author_name, user_url(@merge_request.author)} wrote:
%p.details %p.details
!= merge_path_description(@merge_request, '&rarr;') != merge_path_description(@merge_request, '&rarr;')
......
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
= render "download", blob: blob = render "download", blob: blob
- elsif blob.text? - elsif blob.text?
- if blob_svg?(blob) - if blob_svg?(blob)
= render "image", blob: sanitize_svg(blob) = render "image", blob: blob
- else - else
= render "text", blob: blob = render "text", blob: blob
- elsif blob.image? - elsif blob.image?
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment