Commit 3da32ef7 authored by Zeger-Jan van de Weg's avatar Zeger-Jan van de Weg

Merge branch 'master' into rake-tasks-git

parents 113325a9 234f4bf2
...@@ -888,7 +888,7 @@ Lint/RequireParentheses: ...@@ -888,7 +888,7 @@ Lint/RequireParentheses:
Lint/RescueException: Lint/RescueException:
Description: 'Avoid rescuing the Exception class.' Description: 'Avoid rescuing the Exception class.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-blind-rescues' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-blind-rescues'
Enabled: false Enabled: true
Lint/ShadowingOuterLocalVariable: Lint/ShadowingOuterLocalVariable:
Description: >- Description: >-
......
...@@ -3,6 +3,28 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -3,6 +3,28 @@ Please view this file on the master branch, on stable branches it's out of date.
v 8.3.0 (unreleased) v 8.3.0 (unreleased)
- Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera) - Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera)
- Add rake tasks for git repository maintainance (Zeger-Jan van de Weg) - Add rake tasks for git repository maintainance (Zeger-Jan van de Weg)
- Fix 500 error when update group member permission
- Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera)
- Recognize issue/MR/snippet/commit links as references
- Add ignore whitespace change option to commit view
- Fire update hook from GitLab
- Don't show project fork event as "imported"
- Add API endpoint to fetch merge request commits list
- Expose events API with comment information and author info
- Fix: Ensure "Remove Source Branch" button is not shown when branch is being deleted. #3583
v 8.2.3
- Fix application settings cache not expiring after changes (Stan Hu)
v 8.2.2
- Fix 404 in redirection after removing a project (Stan Hu)
- Ensure cached application settings are refreshed at startup (Stan Hu)
- Fix Error 500 when viewing user's personal projects from admin page (Stan Hu)
- Fix: Raw private snippets access workflow
- Prevent "413 Request entity too large" errors when pushing large files with LFS
- Fix invalid links within projects dashboard header
- Make current user the first user in assignee dropdown in issues detail page (Stan Hu)
- Fix: duplicate email notifications on issue comments
v 8.2.1 v 8.2.1
- Forcefully update builds that didn't want to update with state machine - Forcefully update builds that didn't want to update with state machine
...@@ -14,7 +36,6 @@ v 8.2.0 ...@@ -14,7 +36,6 @@ v 8.2.0
- Expose build artifacts path as config option - Expose build artifacts path as config option
- Fix grouping of contributors by email in graph. - Fix grouping of contributors by email in graph.
- Improved performance of finding issues with/without labels - Improved performance of finding issues with/without labels
- Remove CSS property preventing hard tabs from rendering in Chromium 45 (Stan Hu)
- Fix Drone CI service template not saving properly (Stan Hu) - Fix Drone CI service template not saving properly (Stan Hu)
- Fix avatars not showing in Atom feeds and project issues when Gravatar disabled (Stan Hu) - Fix avatars not showing in Atom feeds and project issues when Gravatar disabled (Stan Hu)
- Added a GitLab specific profiling tool called "Sherlock" (see GitLab CE merge request #1749) - Added a GitLab specific profiling tool called "Sherlock" (see GitLab CE merge request #1749)
......
...@@ -27,6 +27,16 @@ The channels people will reach out on can be found on the [getting help page](ht ...@@ -27,6 +27,16 @@ The channels people will reach out on can be found on the [getting help page](ht
Sign up for the mailinglist, answer GitLab questions on StackOverflow or respond in the IRC channel. Sign up for the mailinglist, answer GitLab questions on StackOverflow or respond in the IRC channel.
You can also sign up on [CodeTriage](http://www.codetriage.com/gitlabhq/gitlabhq) to help with one issue every day. You can also sign up on [CodeTriage](http://www.codetriage.com/gitlabhq/gitlabhq) to help with one issue every day.
## I want to contribute!
If you want to contribute to GitLab, but are not sure where to start,
look for [issues](https://gitlab.com/gitlab-org/gitlab-ce/issues?milestone_id=&scope=all&sort=created_desc&state=opened&utf8=%E2%9C%93&assignee_id=&author_id=&milestone_title=&label_name=up-for-grabs)
with the label `up-for-grabs`.
These issues will be of reasonable size and challenge, for anyone to start
contributing to GitLab.
This was inspired by [an article by Kent C. Dodds](https://medium.com/@kentcdodds/first-timers-only-78281ea47455#.i2f363mx4).
## Issue tracker ## Issue tracker
To get support for your particular problem please use the [getting help channels](https://about.gitlab.com/getting-help/). To get support for your particular problem please use the [getting help channels](https://about.gitlab.com/getting-help/).
...@@ -47,10 +57,10 @@ Please send a merge request with a tested solution or a merge request with a fai ...@@ -47,10 +57,10 @@ Please send a merge request with a tested solution or a merge request with a fai
1. **Observed behavior** 1. **Observed behavior**
1. **Relevant logs and/or screenshots:** Please use code blocks (\`\`\`) to format console output, logs, and code as it's very hard to read otherwise. 1. **Relevant logs and/or screenshots:** Please use code blocks (\`\`\`) to format console output, logs, and code as it's very hard to read otherwise.
1. **Output of checks** 1. **Output of checks**
* Results of GitLab [Application Check](doc/install/installation.md#check-application-status) (`sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true`); we will only investigate if the tests are passing * Results of GitLab [Application Check](doc/install/installation.md#check-application-status) (For installations with omnibus-gitlab package: `sudo gitlab-rake gitlab:check SANITIZE=true`); For installations from source: `sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true`); we will only investigate if the tests are passing
* Version of GitLab you are running; we will only investigate issues in the latest stable and development releases as per the [maintenance policy](MAINTENANCE.md) * Version of GitLab you are running; we will only investigate issues in the latest stable and development releases as per the [maintenance policy](MAINTENANCE.md)
* Add the last commit SHA-1 of the GitLab version you used to replicate the issue (obtainable from the help page) * Add the last commit SHA-1 of the GitLab version you used to replicate the issue (obtainable from the help page)
* Describe your setup (use relevant parts from `sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production`) * Describe your setup (use relevant parts from the env info: For installations with omnibus-gitlab package: `sudo gitlab-rake gitlab:env:info`; For installations from source: `sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production`)
1. **Possible fixes**: If you can, link to the line of code that might be responsible for the problem 1. **Possible fixes**: If you can, link to the line of code that might be responsible for the problem
## Merge requests ## Merge requests
......
source "https://rubygems.org" source "https://rubygems.org"
gem 'rails', '4.1.14' gem 'rails', '4.2.4'
gem 'rails-deprecated_sanitizer', '~> 1.0.3'
# Responders respond_to and respond_with
gem 'responders', '~> 2.0'
# Specify a sprockets version due to security issue # Specify a sprockets version due to security issue
# See https://groups.google.com/forum/#!topic/rubyonrails-security/doAVp0YaTqY # See https://groups.google.com/forum/#!topic/rubyonrails-security/doAVp0YaTqY
...@@ -16,7 +20,7 @@ gem "pg", '~> 0.18.2', group: :postgres ...@@ -16,7 +20,7 @@ gem "pg", '~> 0.18.2', group: :postgres
# Authentication libraries # Authentication libraries
gem 'devise', '~> 3.5.2' gem 'devise', '~> 3.5.2'
gem 'devise-async', '~> 0.9.0' gem 'devise-async', '~> 0.9.0'
gem 'doorkeeper', '~> 2.1.3' gem 'doorkeeper', '~> 2.2.0'
gem 'omniauth', '~> 1.2.2' gem 'omniauth', '~> 1.2.2'
gem 'omniauth-bitbucket', '~> 0.0.2' gem 'omniauth-bitbucket', '~> 0.0.2'
gem 'omniauth-facebook', '~> 3.0.0' gem 'omniauth-facebook', '~> 3.0.0'
...@@ -28,7 +32,7 @@ gem 'omniauth-saml', '~> 1.4.0' ...@@ -28,7 +32,7 @@ gem 'omniauth-saml', '~> 1.4.0'
gem 'omniauth-shibboleth', '~> 1.2.0' gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-twitter', '~> 1.2.0' gem 'omniauth-twitter', '~> 1.2.0'
gem 'omniauth_crowd' gem 'omniauth_crowd'
gem 'rack-oauth2', '~> 1.0.5' gem 'rack-oauth2', '~> 1.2.1'
# Two-factor authentication # Two-factor authentication
gem 'devise-two-factor', '~> 2.0.0' gem 'devise-two-factor', '~> 2.0.0'
...@@ -62,9 +66,6 @@ gem 'rack-cors', '~> 0.4.0', require: 'rack/cors' ...@@ -62,9 +66,6 @@ gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
# based on human-friendly examples # based on human-friendly examples
gem "stamp", '~> 0.6.0' gem "stamp", '~> 0.6.0'
# Enumeration fields
gem 'enumerize', '~> 0.7.0'
# Pagination # Pagination
gem "kaminari", "~> 0.16.3" gem "kaminari", "~> 0.16.3"
...@@ -95,9 +96,10 @@ gem 'redcarpet', '~> 3.3.3' ...@@ -95,9 +96,10 @@ gem 'redcarpet', '~> 3.3.3'
gem 'RedCloth', '~> 4.2.9' gem 'RedCloth', '~> 4.2.9'
gem 'rdoc', '~>3.6' gem 'rdoc', '~>3.6'
gem 'org-ruby', '~> 0.9.12' gem 'org-ruby', '~> 0.9.12'
gem 'creole', '~>0.3.6' gem 'creole', '~> 0.5.0'
gem 'wikicloth', '0.8.1' gem 'wikicloth', '0.8.1'
gem 'asciidoctor', '~> 1.5.2' gem 'asciidoctor', '~> 1.5.2'
gem 'net-ssh', '~> 3.0.1'
# Diffs # Diffs
gem 'diffy', '~> 3.0.3' gem 'diffy', '~> 3.0.3'
...@@ -125,8 +127,7 @@ gem 'sidetiq', '~> 0.6.3' ...@@ -125,8 +127,7 @@ gem 'sidetiq', '~> 0.6.3'
gem "httparty", '~> 0.13.3' gem "httparty", '~> 0.13.3'
# Colored output to console # Colored output to console
gem "colored", '~> 1.2' gem "colorize", '~> 0.7.0'
gem "colorize", '~> 0.5.8'
# GitLab settings # GitLab settings
gem 'settingslogic', '~> 2.0.9' gem 'settingslogic', '~> 2.0.9'
...@@ -154,7 +155,7 @@ gem "gemnasium-gitlab-service", "~> 0.2" ...@@ -154,7 +155,7 @@ gem "gemnasium-gitlab-service", "~> 0.2"
gem "slack-notifier", "~> 1.2.0" gem "slack-notifier", "~> 1.2.0"
# Asana integration # Asana integration
gem 'asana', '~> 0.0.6' gem 'asana', '~> 0.4.0'
# FogBugz integration # FogBugz integration
gem 'ruby-fogbugz', '~> 0.2.1' gem 'ruby-fogbugz', '~> 0.2.1'
...@@ -187,13 +188,13 @@ gem "sass-rails", '~> 4.0.5' ...@@ -187,13 +188,13 @@ gem "sass-rails", '~> 4.0.5'
gem "coffee-rails", '~> 4.1.0' gem "coffee-rails", '~> 4.1.0'
gem "uglifier", '~> 2.7.2' gem "uglifier", '~> 2.7.2'
gem 'turbolinks', '~> 2.5.0' gem 'turbolinks', '~> 2.5.0'
gem 'jquery-turbolinks', '~> 2.0.1' gem 'jquery-turbolinks', '~> 2.1.0'
gem 'addressable', '~> 2.3.8' gem 'addressable', '~> 2.3.8'
gem 'bootstrap-sass', '~> 3.0' gem 'bootstrap-sass', '~> 3.0'
gem 'font-awesome-rails', '~> 4.2' gem 'font-awesome-rails', '~> 4.2'
gem 'gitlab_emoji', '~> 0.1' gem 'gitlab_emoji', '~> 0.1'
gem 'gon', '~> 5.0.0' gem 'gon', '~> 6.0.1'
gem 'jquery-atwho-rails', '~> 1.3.2' gem 'jquery-atwho-rails', '~> 1.3.2'
gem 'jquery-rails', '~> 3.1.3' gem 'jquery-rails', '~> 3.1.3'
gem 'jquery-scrollto-rails', '~> 1.4.3' gem 'jquery-scrollto-rails', '~> 1.4.3'
...@@ -214,6 +215,7 @@ group :development do ...@@ -214,6 +215,7 @@ group :development do
gem 'rerun', '~> 0.10.0' gem 'rerun', '~> 0.10.0'
gem 'bullet', require: false gem 'bullet', require: false
gem 'rblineprof', platform: :mri, require: false gem 'rblineprof', platform: :mri, require: false
gem 'web-console', '~> 2.0'
# Better errors handler # Better errors handler
gem 'better_errors', '~> 1.0.1' gem 'better_errors', '~> 1.0.1'
...@@ -270,7 +272,7 @@ group :test do ...@@ -270,7 +272,7 @@ group :test do
gem 'shoulda-matchers', '~> 2.8.0', require: false gem 'shoulda-matchers', '~> 2.8.0', require: false
gem 'email_spec', '~> 1.6.0' gem 'email_spec', '~> 1.6.0'
gem 'webmock', '~> 1.21.0' gem 'webmock', '~> 1.21.0'
gem 'test_after_commit', '~> 0.2.2' gem 'test_after_commit', '~> 0.4.2'
gem 'sham_rack' gem 'sham_rack'
end end
......
This diff is collapsed.
...@@ -44,6 +44,7 @@ Workflow labels are purposely not very detailed since that would be hard to keep ...@@ -44,6 +44,7 @@ Workflow labels are purposely not very detailed since that would be hard to keep
- *UX* needs needs help from a UX designer - *UX* needs needs help from a UX designer
- *Frontend* needs help from a Front-end engineer - *Frontend* needs help from a Front-end engineer
- *Graphics* needs help from a Graphics designer - *Graphics* needs help from a Graphics designer
- *up-for-grabs* is an issue suitable for first-time contributors, of reasonable difficulty and size. Not exclusive with other labels.
Example workflow: when a UX designer provided a design but it needs frontend work they remove the UX label and add the frontend label. Example workflow: when a UX designer provided a design but it needs frontend work they remove the UX label and add the frontend label.
......
# For DEVELOPMENT only. Production uses Runit in
# https://gitlab.com/gitlab-org/omnibus-gitlab or the init scripts in
# lib/support/init.d, which call scripts in bin/ .
#
web: bundle exec unicorn_rails -p ${PORT:="3000"} -E ${RAILS_ENV:="development"} -c ${UNICORN_CONFIG:="config/unicorn.rb"} web: bundle exec unicorn_rails -p ${PORT:="3000"} -E ${RAILS_ENV:="development"} -c ${UNICORN_CONFIG:="config/unicorn.rb"}
worker: bundle exec sidekiq -q post_receive -q mailer -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default worker: bundle exec sidekiq -q post_receive -q mailers -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default
# mail_room: bundle exec mail_room -q -c config/mail_room.yml # mail_room: bundle exec mail_room -q -c config/mail_room.yml
app/assets/images/icon-link.png

726 Bytes | W: | H:

app/assets/images/icon-link.png

1.1 KB | W: | H:

app/assets/images/icon-link.png
app/assets/images/icon-link.png
app/assets/images/icon-link.png
app/assets/images/icon-link.png
  • 2-up
  • Swipe
  • Onion skin
...@@ -135,17 +135,25 @@ $ -> ...@@ -135,17 +135,25 @@ $ ->
), 1 ), 1
# Initialize tooltips # Initialize tooltips
$('body').tooltip({ $('body').tooltip(
selector: '.has_tooltip, [data-toggle="tooltip"], .page-sidebar-collapsed .nav-sidebar a' selector: '.has_tooltip, [data-toggle="tooltip"]'
placement: (_, el) -> placement: (_, el) ->
$el = $(el) $el = $(el)
if $el.attr('id') == 'js-shortcuts-home' $el.data('placement') || 'bottom'
# Place the logo tooltip on the right when collapsed, bottom when expanded )
$el.parents('header').hasClass('header-collapsed') and 'right' or 'bottom'
else $('.header-logo .home').tooltip(
# Otherwise use the data-placement attribute, or 'bottom' if undefined placement: (_, el) ->
$el.data('placement') or 'bottom' $el = $(el)
}) if $('.page-with-sidebar').hasClass('page-sidebar-collapsed') then 'right' else 'bottom'
container: 'body'
)
$('.page-with-sidebar').tooltip(
selector: '.sidebar-collapsed .nav-sidebar a, .sidebar-collapsed a.sidebar-user'
placement: 'right'
container: 'body'
)
# Form submitter # Form submitter
$('.trigger-submit').on 'change', -> $('.trigger-submit').on 'change', ->
......
#= require clipboard #= require clipboard
$ -> genericSuccess = (e) ->
clipboard = new Clipboard '.js-clipboard-trigger', showTooltip(e.trigger, 'Copied!')
text: (trigger) ->
$target = $(trigger.nextElementSibling || trigger.previousElementSibling) # Clear the selection and blur the trigger so it loses its border
$target.data('clipboard-text') || $target.text().trim() e.clearSelection()
$(e.trigger).blur()
clipboard.on 'success', (e) -> # Safari doesn't support `execCommand`, so instead we inform the user to
$(e.trigger). # copy manually.
tooltip(trigger: 'manual', placement: 'auto bottom', title: 'Copied!'). #
tooltip('show'). # See http://clipboardjs.com/#browser-support
one('mouseleave', -> $(this).tooltip('hide')) genericError = (e) ->
if /Mac/i.test(navigator.userAgent)
key = '⌘' # Command
else
key = 'Ctrl'
# Clear the selection and blur the trigger so it loses its border showTooltip(e.trigger, "Press #{key}-C to copy")
e.clearSelection()
$(e.trigger).blur()
# Safari doesn't support `execCommand`, so instead we inform the user to showTooltip = (target, title) ->
# copy manually. $(target).
# tooltip(
# See http://clipboardjs.com/#browser-support container: 'body'
clipboard.on 'error', (e) -> html: 'true'
if /Mac/i.test(navigator.userAgent) placement: 'auto bottom'
title = "Press ⌘-C to copy" title: title
else trigger: 'manual'
title = "Press Ctrl-C to copy" ).
tooltip('show').
one('mouseleave', -> $(this).tooltip('hide'))
$(e.trigger). $ ->
tooltip(trigger: 'manual', placement: 'auto bottom', html: true, title: title). clipboard = new Clipboard '[data-clipboard-target], [data-clipboard-text]'
tooltip('show'). clipboard.on 'success', genericSuccess
one('mouseleave', -> $(this).tooltip('hide')) clipboard.on 'error', genericError
#= require markdown_preview
class @DropzoneInput class @DropzoneInput
constructor: (form) -> constructor: (form) ->
Dropzone.autoDiscover = false Dropzone.autoDiscover = false
...@@ -11,17 +13,14 @@ class @DropzoneInput ...@@ -11,17 +13,14 @@ class @DropzoneInput
uploadProgress = $("<div class=\"div-dropzone-progress\"></div>") uploadProgress = $("<div class=\"div-dropzone-progress\"></div>")
btnAlert = "<button type=\"button\"" + alertAttr + ">&times;</button>" btnAlert = "<button type=\"button\"" + alertAttr + ">&times;</button>"
project_uploads_path = window.project_uploads_path or null project_uploads_path = window.project_uploads_path or null
markdown_preview_path = window.markdown_preview_path or null
max_file_size = gon.max_file_size or 10 max_file_size = gon.max_file_size or 10
form_textarea = $(form).find("textarea.markdown-area") form_textarea = $(form).find("textarea.markdown-area")
form_textarea.wrap "<div class=\"div-dropzone\"></div>" form_textarea.wrap "<div class=\"div-dropzone\"></div>"
form_textarea.on 'paste', (event) => form_textarea.on 'paste', (event) =>
handlePaste(event) handlePaste(event)
form_textarea.on "input", ->
hideReferencedUsers() $(form).setupMarkdownPreview()
form_textarea.on "blur", ->
renderMarkdown()
form_dropzone = $(form).find('.div-dropzone') form_dropzone = $(form).find('.div-dropzone')
form_dropzone.parent().addClass "div-dropzone-wrapper" form_dropzone.parent().addClass "div-dropzone-wrapper"
...@@ -34,42 +33,6 @@ class @DropzoneInput ...@@ -34,42 +33,6 @@ class @DropzoneInput
"opacity": 0 "opacity": 0
"display": "none" "display": "none"
# Preview button
$(document).off "click", ".js-md-preview-button"
$(document).on "click", ".js-md-preview-button", (e) ->
###
Shows the Markdown preview.
Lets the server render GFM into Html and displays it.
###
e.preventDefault()
form = $(this).closest("form")
# toggle tabs
form.find(".js-md-write-button").parent().removeClass "active"
form.find(".js-md-preview-button").parent().addClass "active"
# toggle content
form.find(".md-write-holder").hide()
form.find(".md-preview-holder").show()
renderMarkdown()
# Write button
$(document).off "click", ".js-md-write-button"
$(document).on "click", ".js-md-write-button", (e) ->
###
Shows the Markdown textarea.
###
e.preventDefault()
form = $(this).closest("form")
# toggle tabs
form.find(".js-md-write-button").parent().addClass "active"
form.find(".js-md-preview-button").parent().removeClass "active"
# toggle content
form.find(".md-write-holder").show()
form.find(".md-preview-holder").hide()
dropzone = form_dropzone.dropzone( dropzone = form_dropzone.dropzone(
url: project_uploads_path url: project_uploads_path
dictDefaultMessage: "" dictDefaultMessage: ""
...@@ -136,41 +99,6 @@ class @DropzoneInput ...@@ -136,41 +99,6 @@ class @DropzoneInput
child = $(dropzone[0]).children("textarea") child = $(dropzone[0]).children("textarea")
hideReferencedUsers = ->
referencedUsers = form.find(".referenced-users")
referencedUsers.hide()
renderReferencedUsers = (users) ->
referencedUsers = form.find(".referenced-users")
if referencedUsers.length
if users.length >= 10
referencedUsers.show()
referencedUsers.find(".js-referenced-users-count").text users.length
else
referencedUsers.hide()
renderMarkdown = ->
preview = form.find(".js-md-preview")
mdText = form.find(".markdown-area").val()
if mdText.trim().length is 0
preview.text "Nothing to preview."
hideReferencedUsers()
else
preview.text "Loading..."
$.ajax(
type: "POST",
url: markdown_preview_path,
data: {
text: mdText
},
dataType: "json"
).success (data) ->
preview.html data.body
preview.syntaxHighlight()
renderReferencedUsers data.references.users
formatLink = (link) -> formatLink = (link) ->
text = "[#{link.alt}](#{link.url})" text = "[#{link.alt}](#{link.url})"
text = "!#{text}" if link.is_image text = "!#{text}" if link.is_image
......
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
$('#filter_issue_search').val($('#issue_search').val()) $('#filter_issue_search').val($('#issue_search').val())
initSelects: -> initSelects: ->
$("select#update_status").select2(width: 'resolve', dropdownAutoWidth: true) $("select#update_state_event").select2(width: 'resolve', dropdownAutoWidth: true)
$("select#update_assignee_id").select2(width: 'resolve', dropdownAutoWidth: true) $("select#update_assignee_id").select2(width: 'resolve', dropdownAutoWidth: true)
$("select#update_milestone_id").select2(width: 'resolve', dropdownAutoWidth: true) $("select#update_milestone_id").select2(width: 'resolve', dropdownAutoWidth: true)
$("select#label_name").select2(width: 'resolve', dropdownAutoWidth: true) $("select#label_name").select2(width: 'resolve', dropdownAutoWidth: true)
......
# MarkdownPreview
#
# Handles toggling the "Write" and "Preview" tab clicks, rendering the preview,
# and showing a warning when more than `x` users are referenced.
#
class @MarkdownPreview
# Minimum number of users referenced before triggering a warning
referenceThreshold: 10
showPreview: (form) ->
preview = form.find('.js-md-preview')
mdText = form.find('textarea.markdown-area').val()
if mdText.trim().length == 0
preview.text('Nothing to preview.')
@hideReferencedUsers(form)
else
preview.text('Loading...')
@renderMarkdown mdText, (response) =>
preview.html(response.body)
preview.syntaxHighlight()
@renderReferencedUsers(response.references.users, form)
renderMarkdown: (text, success) ->
return unless window.markdown_preview_path
$.ajax
type: 'POST'
url: window.markdown_preview_path
data: { text: text }
dataType: 'json'
success: success
hideReferencedUsers: (form) ->
referencedUsers = form.find('.referenced-users')
referencedUsers.hide()
renderReferencedUsers: (users, form) ->
referencedUsers = form.find('.referenced-users')
if referencedUsers.length
if users.length >= @referenceThreshold
referencedUsers.show()
referencedUsers.find('.js-referenced-users-count').text(users.length)
else
referencedUsers.hide()
markdownPreview = new MarkdownPreview()
previewButtonSelector = '.js-md-preview-button'
writeButtonSelector = '.js-md-write-button'
$.fn.setupMarkdownPreview = ->
$form = $(this)
form_textarea = $form.find('textarea.markdown-area')
form_textarea.on 'input', -> markdownPreview.hideReferencedUsers($form)
form_textarea.on 'blur', -> markdownPreview.showPreview($form)
$(document).on 'click', previewButtonSelector, (e) ->
e.preventDefault()
$form = $(this).closest('form')
# toggle tabs
$form.find(writeButtonSelector).parent().removeClass('active')
$form.find(previewButtonSelector).parent().addClass('active')
# toggle content
$form.find('.md-write-holder').hide()
$form.find('.md-preview-holder').show()
markdownPreview.showPreview($form)
$(document).on 'click', writeButtonSelector, (e) ->
e.preventDefault()
$form = $(this).closest('form')
# toggle tabs
$form.find(writeButtonSelector).parent().addClass('active')
$form.find(previewButtonSelector).parent().removeClass('active')
# toggle content
$form.find('.md-write-holder').show()
$form.find('.md-preview-holder').hide()
...@@ -10,17 +10,20 @@ class @MergeRequestWidget ...@@ -10,17 +10,20 @@ class @MergeRequestWidget
constructor: (@opts) -> constructor: (@opts) ->
modal = $('#modal_merge_info').modal(show: false) modal = $('#modal_merge_info').modal(show: false)
mergeInProgress: -> mergeInProgress: (deleteSourceBranch = false)->
$.ajax $.ajax
type: 'GET' type: 'GET'
url: $('.merge-request').data('url') url: $('.merge-request').data('url')
success: (data) => success: (data) =>
if data.state == "merged" if data.state == "merged"
location.reload() urlSuffix = if deleteSourceBranch then '?delete_source=true' else ''
window.location.href = window.location.href + urlSuffix
else if data.merge_error else if data.merge_error
$('.mr-widget-body').html("<h4>" + data.merge_error + "</h4>") $('.mr-widget-body').html("<h4>" + data.merge_error + "</h4>")
else else
setTimeout(merge_request_widget.mergeInProgress, 2000) callback = -> merge_request_widget.mergeInProgress(deleteSourceBranch)
setTimeout(callback, 2000)
dataType: 'json' dataType: 'json'
getMergeStatus: -> getMergeStatus: ->
......
class @Project class @Project
constructor: -> constructor: ->
# Git clone panel switcher # Git protocol switcher
cloneHolder = $('.git-clone-holder') $('.js-protocol-switch').click ->
if cloneHolder.length return if $(@).hasClass('active')
$('a, button', cloneHolder).click ->
$('a, button', cloneHolder).removeClass 'active' # Toggle 'active' for both buttons
$(@).addClass 'active' $('.js-protocol-switch').toggleClass('active')
$('#project_clone', cloneHolder).val $(@).data 'clone'
$(".clone").text("").append $(@).data 'clone' url = $(@).data('clone')
# Update the input field
$('#project_clone').val(url)
# Update the command line instructions
$('.clone').text(url)
# Ref switcher # Ref switcher
$('.project-refs-select').on 'change', -> $('.project-refs-select').on 'change', ->
...@@ -39,4 +45,4 @@ class @Project ...@@ -39,4 +45,4 @@ class @Project
when 4 then label = ' On Mention ' when 4 then label = ' On Mention '
$('#notifications-button').empty().append("<i class='fa fa-bell'></i>" + label + "<i class='fa fa-angle-down'></i>") $('#notifications-button').empty().append("<i class='fa fa-bell'></i>" + label + "<i class='fa fa-angle-down'></i>")
$(@).parents('ul').find('li.active').removeClass 'active' $(@).parents('ul').find('li.active').removeClass 'active'
$(@).parent().addClass 'active' $(@).parent().addClass 'active'
\ No newline at end of file
...@@ -5,6 +5,7 @@ $(document).on("click", '.toggle-nav-collapse', (e) -> ...@@ -5,6 +5,7 @@ $(document).on("click", '.toggle-nav-collapse', (e) ->
$('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}") $('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}")
$('header').toggleClass("header-collapsed header-expanded") $('header').toggleClass("header-collapsed header-expanded")
$('.sidebar-wrapper').toggleClass("sidebar-collapsed sidebar-expanded")
$('.toggle-nav-collapse i').toggleClass("fa-angle-right fa-angle-left") $('.toggle-nav-collapse i').toggleClass("fa-angle-right fa-angle-left")
$.cookie("collapsed_nav", $('.page-with-sidebar').hasClass(collapsed), { path: '/' }) $.cookie("collapsed_nav", $('.page-with-sidebar').hasClass(collapsed), { path: '/' })
) )
...@@ -2,3 +2,9 @@ class @User ...@@ -2,3 +2,9 @@ class @User
constructor: -> constructor: ->
$('.profile-groups-avatars').tooltip("placement": "top") $('.profile-groups-avatars').tooltip("placement": "top")
new ProjectsList() new ProjectsList()
$('.hide-project-limit-message').on 'click', (e) ->
path = '/'
$.cookie('hide_project_limit_message', 'false', { path: path })
$(@).parents('.project-limit-message').remove()
e.preventDefault()
...@@ -32,17 +32,15 @@ class @UsersSelect ...@@ -32,17 +32,15 @@ class @UsersSelect
if showNullUser if showNullUser
nullUser = { nullUser = {
name: 'Unassigned', name: 'Unassigned',
avatar: null,
username: 'none',
id: 0 id: 0
} }
data.results.unshift(nullUser) data.results.unshift(nullUser)
if showAnyUser if showAnyUser
name = showAnyUser
name = 'Any User' if name == true
anyUser = { anyUser = {
name: 'Any', name: name,
avatar: null,
username: 'none',
id: null id: null
} }
data.results.unshift(anyUser) data.results.unshift(anyUser)
...@@ -50,7 +48,6 @@ class @UsersSelect ...@@ -50,7 +48,6 @@ class @UsersSelect
if showEmailUser && data.results.length == 0 && query.term.match(/^[^@]+@[^@]+$/) if showEmailUser && data.results.length == 0 && query.term.match(/^[^@]+@[^@]+$/)
emailUser = { emailUser = {
name: "Invite \"#{query.term}\"", name: "Invite \"#{query.term}\"",
avatar: null,
username: query.term, username: query.term,
id: query.term id: query.term
} }
...@@ -82,10 +79,10 @@ class @UsersSelect ...@@ -82,10 +79,10 @@ class @UsersSelect
else else
avatar = gon.default_avatar_url avatar = gon.default_avatar_url
"<div class='user-result'> "<div class='user-result #{'no-username' unless user.username}'>
<div class='user-image'><img class='avatar s24' src='#{avatar}'></div> <div class='user-image'><img class='avatar s24' src='#{avatar}'></div>
<div class='user-name'>#{user.name}</div> <div class='user-name'>#{user.name}</div>
<div class='user-username'>#{user.username}</div> <div class='user-username'>#{user.username || ""}</div>
</div>" </div>"
formatSelection: (user) -> formatSelection: (user) ->
......
@import "framework/fonts"; @import "framework/fonts";
@import "framework/variables"; @import "framework/variables";
@import "framework/mixins"; @import "framework/mixins";
@import "framework/layout";
@import 'framework/tw_bootstrap_variables'; @import 'framework/tw_bootstrap_variables';
@import 'framework/tw_bootstrap'; @import 'framework/tw_bootstrap';
@import "framework/layout";
@import "framework/avatar.scss"; @import "framework/avatar.scss";
@import "framework/blocks.scss"; @import "framework/blocks.scss";
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
@import "framework/markdown_area.scss"; @import "framework/markdown_area.scss";
@import "framework/mobile.scss"; @import "framework/mobile.scss";
@import "framework/pagination.scss"; @import "framework/pagination.scss";
@import "framework/panels.scss";
@import "framework/selects.scss"; @import "framework/selects.scss";
@import "framework/sidebar.scss"; @import "framework/sidebar.scss";
@import "framework/tables.scss"; @import "framework/tables.scss";
......
...@@ -68,6 +68,10 @@ ...@@ -68,6 +68,10 @@
.oneline { .oneline {
line-height: 42px; line-height: 42px;
} }
> p:last-child {
margin-bottom: 0;
}
} }
.cover-block { .cover-block {
...@@ -112,5 +116,14 @@ ...@@ -112,5 +116,14 @@
position: absolute; position: absolute;
top: 10px; top: 10px;
right: 10px; right: 10px;
&.left {
left: 10px;
right: auto;
}
} }
} }
.block-connector {
margin-top: -1px;
}
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
/** COMMON CLASSES **/ /** COMMON CLASSES **/
.prepend-top-10 { margin-top:10px } .prepend-top-10 { margin-top:10px }
.prepend-top-default { margin-top: $gl-padding; } .prepend-top-default { margin-top: $gl-padding !important; }
.prepend-top-20 { margin-top:20px } .prepend-top-20 { margin-top:20px }
.prepend-left-10 { margin-left:10px } .prepend-left-10 { margin-left:10px }
.prepend-left-20 { margin-left:20px } .prepend-left-20 { margin-left:20px }
...@@ -52,6 +52,10 @@ pre { ...@@ -52,6 +52,10 @@ pre {
} }
} }
hr {
margin: $gl-padding 0;
}
.dropdown-menu > li > a { .dropdown-menu > li > a {
text-shadow: none; text-shadow: none;
} }
...@@ -337,10 +341,6 @@ table { ...@@ -337,10 +341,6 @@ table {
text-align: center; text-align: center;
} }
.task-status {
margin-left: 10px;
}
#nprogress .spinner { #nprogress .spinner {
top: 15px !important; top: 15px !important;
right: 10px !important; right: 10px !important;
...@@ -433,3 +433,7 @@ table { ...@@ -433,3 +433,7 @@ table {
.space-right { .space-right {
margin-right: 10px; margin-right: 10px;
} }
.alert, .progress {
margin-bottom: $gl-padding;
}
...@@ -8,7 +8,6 @@ ...@@ -8,7 +8,6 @@
border: none; border: none;
border-top: 1px solid #E7E9EE; border-top: 1px solid #E7E9EE;
border-bottom: 1px solid #E7E9EE; border-bottom: 1px solid #E7E9EE;
margin-bottom: 1em;
&.readme-holder { &.readme-holder {
border-bottom: 0; border-bottom: 0;
...@@ -25,7 +24,7 @@ ...@@ -25,7 +24,7 @@
text-shadow: 0 1px 1px #fff; text-shadow: 0 1px 1px #fff;
margin: 0; margin: 0;
text-align: left; text-align: left;
padding: 10px 15px; padding: 10px $gl-padding;
.file-actions { .file-actions {
float: right; float: right;
...@@ -171,4 +170,3 @@ ...@@ -171,4 +170,3 @@
} }
} }
} }
...@@ -22,9 +22,10 @@ input[type='text'].danger { ...@@ -22,9 +22,10 @@ input[type='text'].danger {
} }
.form-actions { .form-actions {
padding: 17px 20px 18px; margin: -$gl-padding;
margin-top: 18px; margin-top: 0;
margin-bottom: 18px; margin-bottom: -$gl-padding;
padding: $gl-padding;
background-color: $background-color; background-color: $background-color;
border-top: 1px solid $border-color; border-top: 1px solid $border-color;
} }
...@@ -73,6 +74,8 @@ label { ...@@ -73,6 +74,8 @@ label {
.form-control { .form-control {
@include box-shadow(none); @include box-shadow(none);
height: 42px;
padding: 8px $gl-padding;
} }
.wiki-content { .wiki-content {
...@@ -88,7 +91,19 @@ label { ...@@ -88,7 +91,19 @@ label {
} }
.input-group { .input-group {
.select2-container {
display: table-cell;
width: 200px !important;
}
.input-group-addon { .input-group-addon {
background-color: #f7f8fa; background-color: #f7f8fa;
} }
.input-group-addon:not(:first-child):not(:last-child) {
border-left: 0;
border-right: 0;
}
}
.help-block {
margin-bottom: 0;
} }
...@@ -6,15 +6,17 @@ header { ...@@ -6,15 +6,17 @@ header {
transition-duration: .3s; transition-duration: .3s;
&.navbar-empty { &.navbar-empty {
height: 58px;
background: #FFF; background: #FFF;
border-bottom: 1px solid #EEE; border-bottom: 1px solid #EEE;
.center-logo { .center-logo {
margin: 8px 0; margin: 11px 0;
text-align: center; text-align: center;
img { #tanuki-logo, img {
height: 32px; width: 36px;
height: 36px;
} }
} }
} }
......
...@@ -7,8 +7,9 @@ ...@@ -7,8 +7,9 @@
.issue-box { .issue-box {
@include border-radius(2px); @include border-radius(2px);
display: inline-block; display: block;
padding: 10px $gl-padding; float: left;
padding: 0 $gl-padding;
font-weight: normal; font-weight: normal;
margin-right: 10px; margin-right: 10px;
font-size: $gl-font-size; font-size: $gl-font-size;
......
...@@ -2,9 +2,13 @@ html { ...@@ -2,9 +2,13 @@ html {
overflow-y: scroll; overflow-y: scroll;
&.touch .tooltip { display: none !important; } &.touch .tooltip { display: none !important; }
}
body {
background-color: #EAEBEC !important;
body { &.navless {
padding-top: $header-height; background-color: white !important;
} }
} }
...@@ -18,7 +22,8 @@ html { ...@@ -18,7 +22,8 @@ html {
} }
.navless-container { .navless-container {
margin-top: 30px; margin-top: $header-height;
padding-top: $gl-padding * 2;
} }
.container-limited { .container-limited {
......
...@@ -127,3 +127,8 @@ ul.content-list { ...@@ -127,3 +127,8 @@ ul.content-list {
} }
} }
.panel > .content-list {
li {
margin: 0;
}
}
...@@ -32,3 +32,7 @@ ...@@ -32,3 +32,7 @@
} }
} }
} }
.panel > .gl-pagination {
margin: 0;
}
.panel {
margin-bottom: $gl-padding;
.panel-heading {
padding: 10px $gl-padding;
}
.panel-body {
padding: $gl-padding;
.form-actions {
margin: -$gl-padding;
margin-top: $gl-padding;
}
}
}
...@@ -15,6 +15,16 @@ ...@@ -15,6 +15,16 @@
border-left: none; border-left: none;
padding-top: 5px; padding-top: 5px;
} }
.select2-chosen {
color: $gl-text-color;
}
&.select2-default {
.select2-chosen {
color: #999;
}
}
} }
} }
...@@ -23,6 +33,7 @@ ...@@ -23,6 +33,7 @@
border: 1px solid #e7e9ed; border: 1px solid #e7e9ed;
} }
.select2-drop { .select2-drop {
@include box-shadow(rgba(76, 86, 103, 0.247059) 0px 0px 1px 0px, rgba(31, 37, 50, 0.317647) 0px 2px 18px 0px); @include box-shadow(rgba(76, 86, 103, 0.247059) 0px 0px 1px 0px, rgba(31, 37, 50, 0.317647) 0px 2px 18px 0px);
@include border-radius (0px); @include border-radius (0px);
...@@ -48,17 +59,38 @@ ...@@ -48,17 +59,38 @@
color: #313236; color: #313236;
} }
.select2-container-multi {
.select2-choices {
@include border-radius(2px);
border-color: $input-border;
background: white;
padding-left: $gl-padding / 2;
.select2-search-field input {
padding: $gl-padding / 2;
font-size: 13px;
height: auto;
font-family: inherit;
font-size: inherit;
}
.select2-container-multi .select2-choices { .select2-search-choice {
@include border-radius(2px); margin: 8px 0 0 8px;
border-color: #CCC; background: white;
} box-shadow: none;
border-color: $input-border;
.select2-container-multi .select2-choices .select2-search-field input { color: $gl-text-color;
padding: 8px 14px; line-height: 15px;
font-size: 13px;
line-height: 18px; .select2-search-choice-close {
height: auto; top: 5px;
}
&.select2-search-choice-focus {
border-color: $gl-text-color;
}
}
}
} }
.select2-drop-active { .select2-drop-active {
...@@ -123,10 +155,16 @@ ...@@ -123,10 +155,16 @@
} }
.user-result { .user-result {
min-height: 24px;
.user-image { .user-image {
float: left; float: left;
} }
.user-name {
&.no-username {
.user-name {
line-height: 24px;
}
} }
} }
......
.page-with-sidebar { .page-with-sidebar {
padding-top: $header-height;
transition-duration: .3s;
.sidebar-wrapper { .sidebar-wrapper {
position: fixed; position: fixed;
top: 0; top: 0;
...@@ -14,19 +17,15 @@ ...@@ -14,19 +17,15 @@
.sidebar-wrapper { .sidebar-wrapper {
z-index: 99; z-index: 99;
background: $background-color; background: $background-color;
transition-duration: .3s;
} }
.content-wrapper { .content-wrapper {
min-height: 100vh;
width: 100%; width: 100%;
padding: 20px; padding: 20px;
background: #EAEBEC;
.container-fluid { .container-fluid {
background: #FFF; background: #FFF;
padding: $gl-padding; padding: $gl-padding;
min-height: 90vh;
&.container-blank { &.container-blank {
background: none; background: none;
...@@ -36,6 +35,83 @@ ...@@ -36,6 +35,83 @@
} }
} }
.sidebar-wrapper {
.header-logo {
border-bottom: 1px solid transparent;
float: left;
height: $header-height;
width: $sidebar_width;
position: fixed;
z-index: 999;
overflow: hidden;
transition-duration: .3s;
a {
float: left;
height: $header-height;
width: 100%;
padding: 11px 0 11px 22px;
overflow: hidden;
outline: none;
transition-duration: .3s;
img {
width: 36px;
height: 36px;
}
#tanuki-logo, img {
float: left;
}
.gitlab-text-container {
width: 230px;
h3 {
width: 158px;
float: left;
margin: 0;
margin-left: 14px;
font-size: 19px;
line-height: 41px;
font-weight: normal;
}
}
}
&:hover {
background-color: #EEE;
}
}
.sidebar-user {
padding: 9px 22px;
position: fixed;
bottom: 40px;
width: $sidebar_width;
overflow: hidden;
transition-duration: .3s;
.username {
margin-left: 10px;
width: $sidebar_width - 2 * 10px;
font-size: 16px;
line-height: 34px;
}
}
}
.tanuki-shape {
transition: all 0.8s;
&:hover {
fill: rgb(255, 255, 255);
transition: all 0.1s;
}
}
.nav-sidebar { .nav-sidebar {
margin-top: 14 + $header-height; margin-top: 14 + $header-height;
margin-bottom: 100px; margin-bottom: 100px;
...@@ -62,7 +138,7 @@ ...@@ -62,7 +138,7 @@
color: $gray; color: $gray;
display: block; display: block;
text-decoration: none; text-decoration: none;
padding-left: 22px; padding-left: 23px;
font-weight: normal; font-weight: normal;
outline: none; outline: none;
...@@ -86,6 +162,10 @@ ...@@ -86,6 +162,10 @@
padding: 0px 8px; padding: 0px 8px;
@include border-radius(6px); @include border-radius(6px);
} }
&.back-link i {
transition-duration: .3s;
}
} }
} }
} }
...@@ -101,7 +181,6 @@ ...@@ -101,7 +181,6 @@
@mixin expanded-sidebar { @mixin expanded-sidebar {
padding-left: $sidebar_width; padding-left: $sidebar_width;
transition-duration: .3s;
.sidebar-wrapper { .sidebar-wrapper {
width: $sidebar_width; width: $sidebar_width;
...@@ -115,16 +194,15 @@ ...@@ -115,16 +194,15 @@
&.back-link { &.back-link {
i { i {
visibility: hidden; opacity: 0;
} }
} }
} }
} }
} }
@mixin folded-sidebar { @mixin collapsed-sidebar {
padding-left: 60px; padding-left: $sidebar_collapsed_width;
transition-duration: .3s;
.sidebar-wrapper { .sidebar-wrapper {
width: $sidebar_collapsed_width; width: $sidebar_collapsed_width;
...@@ -133,7 +211,7 @@ ...@@ -133,7 +211,7 @@
width: $sidebar_collapsed_width; width: $sidebar_collapsed_width;
a { a {
padding-left: 12px; padding-left: ($sidebar_collapsed_width - 36) / 2;
.gitlab-text-container { .gitlab-text-container {
display: none; display: none;
...@@ -144,9 +222,13 @@ ...@@ -144,9 +222,13 @@
.nav-sidebar { .nav-sidebar {
width: $sidebar_collapsed_width; width: $sidebar_collapsed_width;
li a { li {
span { width: auto;
display: none;
a {
span {
display: none;
}
} }
} }
} }
...@@ -156,7 +238,7 @@ ...@@ -156,7 +238,7 @@
} }
.sidebar-user { .sidebar-user {
padding-left: 12px; padding-left: ($sidebar_collapsed_width - 36) / 2;
width: $sidebar_collapsed_width; width: $sidebar_collapsed_width;
.username { .username {
...@@ -187,11 +269,11 @@ ...@@ -187,11 +269,11 @@
@media (max-width: $screen-md-max) { @media (max-width: $screen-md-max) {
.page-sidebar-collapsed { .page-sidebar-collapsed {
@include folded-sidebar; @include collapsed-sidebar;
} }
.page-sidebar-expanded { .page-sidebar-expanded {
@include folded-sidebar; @include collapsed-sidebar;
} }
.collapse-nav { .collapse-nav {
...@@ -201,83 +283,10 @@ ...@@ -201,83 +283,10 @@
@media(min-width: $screen-md-max) { @media(min-width: $screen-md-max) {
.page-sidebar-collapsed { .page-sidebar-collapsed {
@include folded-sidebar; @include collapsed-sidebar;
} }
.page-sidebar-expanded { .page-sidebar-expanded {
@include expanded-sidebar; @include expanded-sidebar;
} }
} }
.sidebar-user {
padding: 9px 22px;
position: fixed;
bottom: 40px;
width: $sidebar_width;
overflow: hidden;
transition-duration: .3s;
.username {
margin-left: 10px;
width: $sidebar_width - 2 * 10px;
font-size: 16px;
line-height: 34px;
}
}
.sidebar-wrapper {
.header-logo {
border-bottom: 1px solid transparent;
float: left;
height: $header-height;
width: $sidebar_width;
overflow: hidden;
transition-duration: .3s;
a {
float: left;
height: $header-height;
width: 100%;
padding: 10px 22px;
overflow: hidden;
outline: none;
img {
width: 36px;
height: 36px;
}
#tanuki-logo, img {
float: left;
}
.gitlab-text-container {
width: 230px;
h3 {
width: 158px;
float: left;
margin: 0;
margin-left: 14px;
font-size: 19px;
line-height: 41px;
font-weight: normal;
}
}
}
&:hover {
background-color: #EEE;
}
}
}
.tanuki-shape {
transition: all 0.8s;
&:hover {
fill: rgb(255, 255, 255);
transition: all 0.1s;
}
}
...@@ -6,6 +6,8 @@ ...@@ -6,6 +6,8 @@
table { table {
&.table { &.table {
margin-bottom: $gl-padding;
.dropdown-menu a { .dropdown-menu a {
text-decoration: none; text-decoration: none;
} }
......
...@@ -181,6 +181,10 @@ body { ...@@ -181,6 +181,10 @@ body {
line-height: 1.3; line-height: 1.3;
font-size: 1.25em; font-size: 1.25em;
font-weight: 600; font-weight: 600;
&:last-child {
margin-bottom: 0;
}
} }
.page-title-empty { .page-title-empty {
......
...@@ -2,10 +2,6 @@ ...@@ -2,10 +2,6 @@
display: block; display: block;
} }
.commit-title{
margin-bottom: 10px;
}
.commit-author, .commit-committer{ .commit-author, .commit-committer{
display: block; display: block;
color: #999; color: #999;
...@@ -41,6 +37,8 @@ ...@@ -41,6 +37,8 @@
.commit-box { .commit-box {
.commit-title { .commit-title {
margin: 0; margin: 0;
font-size: 23px;
color: #313236;
} }
.commit-description { .commit-description {
...@@ -108,16 +106,3 @@ ...@@ -108,16 +106,3 @@
z-index: 2; z-index: 2;
} }
} }
.commit-ci-menu {
padding: 0;
margin: 0;
list-style: none;
margin-top: 5px;
height: 56px;
margin: -16px;
padding: 16px;
text-align: center;
margin-top: 0px;
margin-bottom: 2px;
}
...@@ -19,48 +19,38 @@ ...@@ -19,48 +19,38 @@
color: #B94A48; color: #B94A48;
} }
} }
.commit-button-annotation {
display: inline-block;
margin: 0;
padding: 2px;
> * {
float: left;
}
.message {
display: inline-block;
margin: 5px 8px 0 8px;
}
}
.file-title { .file-title {
@extend .monospace; @extend .monospace;
line-height: 42px;
padding-top: 7px;
padding-bottom: 7px;
} }
.editor-ref { .editor-ref {
background: $background-color; background: $background-color;
padding: 11px 15px; padding-right: $gl-padding;
border-right: 1px solid $border-color; border-right: 1px solid $border-color;
display: inline-block; display: block;
margin: -5px -5px; float: left;
margin-right: 10px; margin-right: 10px;
} }
.editor-file-name { .editor-file-name {
.new-file-name { @extend .monospace;
display: inline-block;
width: 450px; float: left;
} margin-right: 10px;
}
.form-control { .new-file-name {
margin-top: -3px; display: inline-block;
} width: 450px;
float: left;
} }
.form-actions { .select2 {
margin: -$gl-padding; float: right;
margin-top: 0;
padding: $gl-padding
} }
} }
.new-group-member-holder {
margin-top: 50px;
padding-top: 20px;
}
.member-search-form { .member-search-form {
float: left; float: left;
} }
...@@ -15,4 +10,4 @@ ...@@ -15,4 +10,4 @@
.form-control { .form-control {
height: 42px; height: 42px;
} }
} }
\ No newline at end of file
...@@ -51,11 +51,12 @@ ...@@ -51,11 +51,12 @@
.issuable-details { .issuable-details {
.page-title { .page-title {
margin-top: -15px; margin-top: -$gl-padding;
padding: 10px 0; padding: 7px 0;
margin-bottom: 0; margin-bottom: 0;
color: #5c5d5e; color: #5c5d5e;
font-size: 16px; font-size: 16px;
line-height: 42px;
.author { .author {
color: #5c5d5e; color: #5c5d5e;
...@@ -89,6 +90,17 @@ ...@@ -89,6 +90,17 @@
} }
} }
.issuable-show-labels {
a {
margin-right: 5px;
margin-bottom: 5px;
display: inline-block;
.color-label {
padding: 6px 10px;
}
}
}
.cross-project-reference { .cross-project-reference {
text-align: center; text-align: center;
width: 100%; width: 100%;
...@@ -157,6 +169,7 @@ ...@@ -157,6 +169,7 @@
min-width: 214px; min-width: 214px;
> li { > li {
cursor: pointer;
margin: 5px; margin: 5px;
} }
} }
......
...@@ -56,17 +56,6 @@ ...@@ -56,17 +56,6 @@
} }
} }
.issue-show-labels {
a {
margin-right: 5px;
margin-bottom: 5px;
display: inline-block;
.color-label {
padding: 6px 10px;
}
}
}
form.edit-issue { form.edit-issue {
margin: 0; margin: 0;
} }
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
h1:first-child { h1:first-child {
font-weight: normal; font-weight: normal;
margin-bottom: 30px; margin-bottom: 30px;
margin-top: 0;
} }
img { img {
......
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
*/ */
.mr-state-widget { .mr-state-widget {
background: #F7F8FA; background: #F7F8FA;
margin-bottom: 20px;
color: $gl-gray; color: $gl-gray;
border: 1px solid #dce0e6; border: 1px solid #dce0e6;
@include border-radius(2px); @include border-radius(2px);
...@@ -87,7 +86,7 @@ ...@@ -87,7 +86,7 @@
.mr-widget-body, .mr-widget-body,
.ci_widget, .ci_widget,
.mr-widget-footer { .mr-widget-footer {
padding: 15px; padding: $gl-padding;
} }
.normal { .normal {
...@@ -116,26 +115,8 @@ ...@@ -116,26 +115,8 @@
} }
} }
.merge-request .merge-request-tabs { .merge-request-details {
@include nav-menu; margin-bottom: $gl-padding;
margin: -$gl-padding;
padding: $gl-padding;
text-align: center;
margin-bottom: 1px;
}
// Mobile
@media (max-width: 480px) {
.merge-request .merge-request-tabs {
margin: 0;
padding: 0;
li {
a {
padding: 0;
}
}
}
} }
.mr_source_commit, .mr_source_commit,
...@@ -192,27 +173,12 @@ ...@@ -192,27 +173,12 @@
line-height: 1.1; line-height: 1.1;
} }
.merge-request-form-info {
padding-top: 15px;
}
// hide mr close link for inline diff comment form // hide mr close link for inline diff comment form
.diff-file .close-mr-link, .diff-file .close-mr-link,
.diff-file .reopen-mr-link { .diff-file .reopen-mr-link {
display: none; display: none;
} }
.merge-request-show-labels {
a {
margin-right: 5px;
margin-bottom: 5px;
display: inline-block;
.color-label {
padding: 6px 10px;
}
}
}
.merge-request-form .select2-container { .merge-request-form .select2-container {
width: 250px !important; width: 250px !important;
} }
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
} }
.reply-btn { .reply-btn {
@extend .btn-primary; @extend .btn-primary;
margin: 10px $gl-padding;
} }
.diff-file .diff-content { .diff-file .diff-content {
tr.line_holder:hover { tr.line_holder:hover {
...@@ -38,9 +39,8 @@ ...@@ -38,9 +39,8 @@
} }
.new_note, .edit_note { .new_note, .edit_note {
.buttons { .note-form-actions {
margin-top: 8px; margin-top: $gl-padding;
margin-bottom: 3px;
} }
.note-preview-holder { .note-preview-holder {
...@@ -79,8 +79,8 @@ ...@@ -79,8 +79,8 @@
padding: $gl-padding; padding: $gl-padding;
margin-left: -$gl-padding; margin-left: -$gl-padding;
margin-right: -$gl-padding; margin-right: -$gl-padding;
border-right: 1px solid #ECEEF1; border-right: 1px solid $border-color;
border-top: 1px solid #ECEEF1; border-top: 1px solid $border-color;
margin-bottom: -$gl-padding; margin-bottom: -$gl-padding;
} }
...@@ -150,7 +150,6 @@ ...@@ -150,7 +150,6 @@
.discussion-reply-holder { .discussion-reply-holder {
background: $background-color; background: $background-color;
padding: 10px 15px;
border-top: 1px solid $border-color; border-top: 1px solid $border-color;
} }
} }
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
font-weight: normal; font-weight: normal;
} }
} }
.no-ssh-key-message { .no-ssh-key-message, .project-limit-message {
background-color: #f28d35; background-color: #f28d35;
margin-bottom: 16px; margin-bottom: 16px;
} }
...@@ -18,10 +18,6 @@ ...@@ -18,10 +18,6 @@
} }
} }
.project-edit-content {
padding: 7px;
}
.project-name-holder { .project-name-holder {
.help-inline { .help-inline {
vertical-align: top; vertical-align: top;
...@@ -30,12 +26,6 @@ ...@@ -30,12 +26,6 @@
} }
.project-home-panel { .project-home-panel {
text-align: center;
background: #f7f8fa;
margin: -$gl-padding;
padding: $gl-padding;
padding: 44px 0 17px 0;
.project-identicon-holder { .project-identicon-holder {
margin-bottom: 16px; margin-bottom: 16px;
...@@ -90,7 +80,12 @@ ...@@ -90,7 +80,12 @@
} }
.visibility-level-label { .visibility-level-label {
@extend .btn;
@extend .btn-gray;
color: $gray; color: $gray;
cursor: default;
i { i {
color: inherit; color: inherit;
} }
...@@ -100,7 +95,6 @@ ...@@ -100,7 +95,6 @@
display: inline-table; display: inline-table;
position: relative; position: relative;
top: 17px; top: 17px;
margin-bottom: 44px;
} }
.project-repo-buttons { .project-repo-buttons {
...@@ -178,6 +172,11 @@ ...@@ -178,6 +172,11 @@
&:active { &:active {
outline: none; outline: none;
} }
&.btn-clipboard {
padding-left: 15px;
padding-right: 15px;
}
} }
.active { .active {
...@@ -366,7 +365,7 @@ table.table.protected-branches-list tr.no-border { ...@@ -366,7 +365,7 @@ table.table.protected-branches-list tr.no-border {
.project-stats { .project-stats {
text-align: center; text-align: center;
margin-top: 15px; margin-top: $gl-padding;
margin-bottom: 0; margin-bottom: 0;
padding-top: 10px; padding-top: 10px;
padding-bottom: 4px; padding-bottom: 4px;
...@@ -552,4 +551,4 @@ pre.light-well { ...@@ -552,4 +551,4 @@ pre.light-well {
z-index: 100; z-index: 100;
position: relative; position: relative;
} }
} }
\ No newline at end of file
...@@ -27,56 +27,22 @@ ...@@ -27,56 +27,22 @@
} }
.snippet-holder { .snippet-holder {
.snippet-details { margin-bottom: -$gl-padding;
.page-title {
margin-top: -15px;
padding: 10px 0;
margin-bottom: 0;
color: #5c5d5e;
font-size: 16px;
.author {
color: #5c5d5e;
}
.snippet-id {
color: #5c5d5e;
}
}
.snippet-title {
margin: 0;
font-size: 23px;
color: #313236;
}
@media (max-width: $screen-md-max) {
.new-snippet-link {
display: none;
}
}
@media (max-width: $screen-sm-max) {
.creator,
.page-title .btn-close {
display: none;
}
}
}
.file-holder { .file-holder {
border-top: 0; border-top: 0;
} }
} }
.snippet-box { .snippet-box {
@include border-radius(2px); @include border-radius(2px);
display: inline-block; display: block;
padding: 10px $gl-padding; float: left;
padding: 0 $gl-padding;
font-weight: normal; font-weight: normal;
margin-right: 10px; margin-right: 10px;
font-size: $gl-font-size; font-size: $gl-font-size;
border: 1px solid; border: 1px solid;
line-height: 40px;
} }
...@@ -4,3 +4,8 @@ ...@@ -4,3 +4,8 @@
margin-right: auto; margin-right: auto;
padding-right: 7px; padding-right: 7px;
} }
.wiki-last-edit-by {
font-size: 80%;
font-weight: normal;
}
...@@ -10,7 +10,7 @@ class AbuseReportsController < ApplicationController ...@@ -10,7 +10,7 @@ class AbuseReportsController < ApplicationController
if @abuse_report.save if @abuse_report.save
if current_application_settings.admin_notification_email.present? if current_application_settings.admin_notification_email.present?
AbuseReportMailer.delay.notify(@abuse_report.id) AbuseReportMailer.notify(@abuse_report.id).deliver_later
end end
message = "Thank you for your report. A GitLab administrator will look into it shortly." message = "Thank you for your report. A GitLab administrator will look into it shortly."
......
...@@ -5,14 +5,20 @@ class Admin::ImpersonationController < Admin::ApplicationController ...@@ -5,14 +5,20 @@ class Admin::ImpersonationController < Admin::ApplicationController
before_action :authorize_impersonator! before_action :authorize_impersonator!
def create def create
session[:impersonator_id] = current_user.username if @user.blocked?
session[:impersonator_return_to] = request.env['HTTP_REFERER'] flash[:alert] = "You cannot impersonate a blocked user"
warden.set_user(user, scope: 'user') redirect_to admin_user_path(@user)
else
session[:impersonator_id] = current_user.username
session[:impersonator_return_to] = admin_user_path(@user)
warden.set_user(user, scope: 'user')
flash[:alert] = "You are impersonating #{user.username}." flash[:alert] = "You are impersonating #{user.username}."
redirect_to root_path redirect_to root_path
end
end end
def destroy def destroy
......
...@@ -9,7 +9,7 @@ class AutocompleteController < ApplicationController ...@@ -9,7 +9,7 @@ class AutocompleteController < ApplicationController
@users = @users.reorder(:name) @users = @users.reorder(:name)
@users = @users.page(params[:page]).per(PER_PAGE) @users = @users.page(params[:page]).per(PER_PAGE)
unless params[:search].present? if params[:search].blank?
# Include current user if available to filter by "Me" # Include current user if available to filter by "Me"
if params[:current_user] && current_user if params[:current_user] && current_user
@users = [*@users, current_user].uniq @users = [*@users, current_user].uniq
......
...@@ -70,6 +70,7 @@ class ProfilesController < Profiles::ApplicationController ...@@ -70,6 +70,7 @@ class ProfilesController < Profiles::ApplicationController
:email, :email,
:hide_no_password, :hide_no_password,
:hide_no_ssh_key, :hide_no_ssh_key,
:hide_project_limit,
:linkedin, :linkedin,
:location, :location,
:name, :name,
......
...@@ -3,7 +3,7 @@ class Projects::BranchesController < Projects::ApplicationController ...@@ -3,7 +3,7 @@ class Projects::BranchesController < 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 :authorize_push_code!, only: [:create, :destroy] before_action :authorize_push_code!, only: [:new, :create, :destroy]
def index def index
@sort = params[:sort] || 'name' @sort = params[:sort] || 'name'
......
...@@ -67,7 +67,12 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -67,7 +67,12 @@ class Projects::CommitController < Projects::ApplicationController
end end
def define_show_vars def define_show_vars
@diffs = commit.diffs if params[:w].to_i == 1
@diffs = commit.diffs({ ignore_whitespace_change: true })
else
@diffs = commit.diffs
end
@notes_count = commit.notes.count @notes_count = commit.notes.count
@builds = ci_commit.builds if ci_commit @builds = ci_commit.builds if ci_commit
......
...@@ -158,12 +158,10 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -158,12 +158,10 @@ class Projects::IssuesController < Projects::ApplicationController
end end
def issue_params def issue_params
permitted = params.require(:issue).permit( params.require(:issue).permit(
:title, :assignee_id, :position, :description, :title, :assignee_id, :position, :description,
:milestone_id, :state_event, :task_num, label_ids: [] :milestone_id, :state_event, :task_num, label_ids: []
) )
params[:issue][:title].strip! if params[:issue][:title]
permitted
end end
def bulk_update_params def bulk_update_params
......
...@@ -276,13 +276,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -276,13 +276,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
def merge_request_params def merge_request_params
permitted = params.require(:merge_request).permit( params.require(:merge_request).permit(
:title, :assignee_id, :source_project_id, :source_branch, :title, :assignee_id, :source_project_id, :source_branch,
:target_project_id, :target_branch, :milestone_id, :target_project_id, :target_branch, :milestone_id,
:state_event, :description, :task_num, label_ids: [] :state_event, :description, :task_num, label_ids: []
) )
params[:merge_request][:title].strip! if params[:merge_request][:title]
permitted
end end
# Make sure merge requests created before 8.0 # Make sure merge requests created before 8.0
......
...@@ -2,7 +2,7 @@ class Projects::TagsController < Projects::ApplicationController ...@@ -2,7 +2,7 @@ class Projects::TagsController < 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 :authorize_push_code!, only: [:create] before_action :authorize_push_code!, only: [:new, :create]
before_action :authorize_admin_project!, only: [:destroy] before_action :authorize_admin_project!, only: [:destroy]
def index def index
......
...@@ -123,7 +123,7 @@ class ProjectsController < ApplicationController ...@@ -123,7 +123,7 @@ class ProjectsController < ApplicationController
::Projects::DestroyService.new(@project, current_user, {}).execute ::Projects::DestroyService.new(@project, current_user, {}).execute
flash[:alert] = "Project '#{@project.name}' was deleted." flash[:alert] = "Project '#{@project.name}' was deleted."
redirect_back_or_default(default: dashboard_projects_path, options: {}) redirect_to dashboard_projects_path
rescue Projects::DestroyService::DestroyError => ex rescue Projects::DestroyService::DestroyError => ex
redirect_to edit_project_path(@project), alert: ex.message redirect_to edit_project_path(@project), alert: ex.message
end end
......
...@@ -2,7 +2,7 @@ class SnippetsController < ApplicationController ...@@ -2,7 +2,7 @@ class SnippetsController < ApplicationController
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw] before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
# Allow read snippet # Allow read snippet
before_action :authorize_read_snippet!, only: [:show] before_action :authorize_read_snippet!, only: [:show, :raw]
# Allow modify snippet # Allow modify snippet
before_action :authorize_update_snippet!, only: [:edit, :update] before_action :authorize_update_snippet!, only: [:edit, :update]
......
...@@ -68,7 +68,7 @@ module ApplicationHelper ...@@ -68,7 +68,7 @@ module ApplicationHelper
end end
end end
def avatar_icon(user_or_email = nil, size = nil) def avatar_icon(user_or_email = nil, size = nil, scale = 2)
if user_or_email.is_a?(User) if user_or_email.is_a?(User)
user = user_or_email user = user_or_email
else else
...@@ -78,12 +78,12 @@ module ApplicationHelper ...@@ -78,12 +78,12 @@ module ApplicationHelper
if user if user
user.avatar_url(size) || default_avatar user.avatar_url(size) || default_avatar
else else
gravatar_icon(user_or_email, size) gravatar_icon(user_or_email, size, scale)
end end
end end
def gravatar_icon(user_email = '', size = nil) def gravatar_icon(user_email = '', size = nil, scale = 2)
GravatarService.new.execute(user_email, size) || GravatarService.new.execute(user_email, size, scale) ||
default_avatar default_avatar
end end
......
...@@ -60,7 +60,7 @@ module BlobHelper ...@@ -60,7 +60,7 @@ module BlobHelper
if Gitlab::MarkupHelper.previewable?(filename) if Gitlab::MarkupHelper.previewable?(filename)
'Preview' 'Preview'
else else
'Preview changes' 'Preview Changes'
end end
end end
......
module ButtonHelper
# Output a "Copy to Clipboard" button
#
# data - Data attributes passed to `content_tag`
#
# Examples:
#
# # Define the clipboard's text
# clipboard_button(clipboard_text: "Foo")
# # => "<button class='...' data-clipboard-text='Foo'>...</button>"
#
# # Define the target element
# clipboard_button(clipboard_target: "#foo")
# # => "<button class='...' data-clipboard-target='#foo'>...</button>"
#
# See http://clipboardjs.com/#usage
def clipboard_button(data = {})
content_tag :button,
icon('clipboard'),
class: 'btn btn-xs btn-clipboard',
data: data,
type: :button
end
def http_clone_button(project)
klass = 'btn js-protocol-switch'
klass << ' active' if default_clone_protocol == 'http'
klass << ' has_tooltip' if current_user.try(:require_password?)
protocol = gitlab_config.protocol.upcase
content_tag :button, protocol,
class: klass,
data: {
clone: project.http_url_to_repo,
container: 'body',
html: 'true',
title: "Set a password on your account<br>to pull or push via #{protocol}"
},
type: :button
end
def ssh_clone_button(project)
klass = 'btn js-protocol-switch'
klass << ' active' if default_clone_protocol == 'ssh'
klass << ' has_tooltip' if current_user.try(:require_ssh_key?)
content_tag :button, 'SSH',
class: klass,
data: {
clone: project.ssh_url_to_repo,
container: 'body',
html: 'true',
title: 'Add an SSH key to your profile<br>to pull or push via SSH.'
},
type: :button
end
end
module ClipboardHelper
def clipboard_button
content_tag :button,
icon('clipboard'),
class: 'btn btn-xs btn-clipboard js-clipboard-trigger',
type: :button
end
end
...@@ -109,7 +109,7 @@ module CommitsHelper ...@@ -109,7 +109,7 @@ module CommitsHelper
) )
elsif @path.present? elsif @path.present?
return link_to( return link_to(
"Browse Dir »", "Browse Directory »",
namespace_project_tree_path(project.namespace, project, namespace_project_tree_path(project.namespace, project,
tree_join(commit.id, @path)), tree_join(commit.id, @path)),
class: "pull-right" class: "pull-right"
...@@ -117,7 +117,7 @@ module CommitsHelper ...@@ -117,7 +117,7 @@ module CommitsHelper
end end
end end
link_to( link_to(
"Browse Code »", "Browse Files »",
namespace_project_tree_path(project.namespace, project, commit), namespace_project_tree_path(project.namespace, project, commit),
class: "pull-right" class: "pull-right"
) )
......
...@@ -146,9 +146,9 @@ module DiffHelper ...@@ -146,9 +146,9 @@ module DiffHelper
def submodule_link(blob, ref, repository = @repository) def submodule_link(blob, ref, repository = @repository)
tree, commit = submodule_links(blob, ref, repository) tree, commit = submodule_links(blob, ref, repository)
commit_id = if commit.nil? commit_id = if commit.nil?
blob.id[0..10] Commit.truncate_sha(blob.id)
else else
link_to "#{blob.id[0..10]}", commit link_to Commit.truncate_sha(blob.id), commit
end end
[ [
......
...@@ -28,6 +28,8 @@ module EmailsHelper ...@@ -28,6 +28,8 @@ module EmailsHelper
return "View #{action.humanize.singularize}" return "View #{action.humanize.singularize}"
end end
end end
nil
end end
def color_email_diff(diffcontent) def color_email_diff(diffcontent)
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
# #
# For example instead of this: # For example instead of this:
# #
# namespace_project_merge_request_path(merge_request.project.namespace, merge_request.projects, merge_request) # namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request)
# #
# We can simply use shortcut: # We can simply use shortcut:
# #
......
...@@ -27,16 +27,20 @@ module IconsHelper ...@@ -27,16 +27,20 @@ module IconsHelper
end end
end end
def public_icon def visibility_level_icon(level, fw: true)
icon('globe fw') name =
end case level
when Gitlab::VisibilityLevel::PRIVATE
def internal_icon 'lock'
icon('shield fw') when Gitlab::VisibilityLevel::INTERNAL
end 'shield'
else # Gitlab::VisibilityLevel::PUBLIC
'globe'
end
name << " fw" if fw
def private_icon icon(name)
icon('lock fw')
end end
def file_type_icon_class(type, mode, name) def file_type_icon_class(type, mode, name)
......
...@@ -44,14 +44,17 @@ module IssuesHelper ...@@ -44,14 +44,17 @@ module IssuesHelper
end end
def bulk_update_milestone_options def bulk_update_milestone_options
options_for_select([['None (backlog)', -1]]) + milestones = project_active_milestones.to_a
options_from_collection_for_select(project_active_milestones, 'id', milestones.unshift(Milestone::None)
'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)
options_from_collection_for_select(object.project.milestones.active, milestones = object.project.milestones.active.to_a
'id', 'title', object.milestone_id) milestones.unshift(Milestone::None)
options_from_collection_for_select(milestones, 'id', 'title', object.milestone_id)
end end
def issue_box_class(item) def issue_box_class(item)
...@@ -84,7 +87,11 @@ module IssuesHelper ...@@ -84,7 +87,11 @@ module IssuesHelper
end end
def merge_requests_sentence(merge_requests) def merge_requests_sentence(merge_requests)
merge_requests.map(&:to_reference).to_sentence(last_word_connector: ', or ') # Sorting based on the `!123` or `group/project!123` reference will sort
# local merge requests first.
merge_requests.map do |merge_request|
merge_request.to_reference(@project)
end.sort.to_sentence(last_word_connector: ', or ')
end end
def url_to_emoji(name) def url_to_emoji(name)
...@@ -96,7 +103,7 @@ module IssuesHelper ...@@ -96,7 +103,7 @@ module IssuesHelper
def emoji_author_list(notes, current_user) def emoji_author_list(notes, current_user)
list = notes.map do |note| list = notes.map do |note|
note.author == current_user ? "me" : note.author.username note.author == current_user ? "me" : note.author.name
end end
list.join(", ") list.join(", ")
......
...@@ -39,7 +39,11 @@ module MergeRequestsHelper ...@@ -39,7 +39,11 @@ module MergeRequestsHelper
end end
def issues_sentence(issues) def issues_sentence(issues)
issues.map(&:to_reference).to_sentence # Sorting based on the `#123` or `group/project#123` reference will sort
# local issues first.
issues.map do |issue|
issue.to_reference(@project)
end.sort.to_sentence
end end
def mr_change_branches_path(merge_request) def mr_change_branches_path(merge_request)
...@@ -49,18 +53,21 @@ module MergeRequestsHelper ...@@ -49,18 +53,21 @@ module MergeRequestsHelper
source_project_id: @merge_request.source_project_id, source_project_id: @merge_request.source_project_id,
target_project_id: @merge_request.target_project_id, target_project_id: @merge_request.target_project_id,
source_branch: @merge_request.source_branch, source_branch: @merge_request.source_branch,
target_branch: nil target_branch: @merge_request.target_branch,
} },
change_branches: true
) )
end end
def source_branch_with_namespace(merge_request) def source_branch_with_namespace(merge_request)
branch = link_to(merge_request.source_branch, namespace_project_commits_path(merge_request.source_project.namespace, merge_request.source_project, merge_request.source_branch))
if merge_request.for_fork? if merge_request.for_fork?
namespace = link_to(merge_request.source_project_namespace, namespace = link_to(merge_request.source_project_namespace,
project_path(merge_request.source_project)) project_path(merge_request.source_project))
namespace + ":#{merge_request.source_branch}" namespace + ":" + branch
else else
merge_request.source_branch branch
end end
end end
......
module NamespacesHelper module NamespacesHelper
def namespaces_options(selected = :current_user, scope = :default) def namespaces_options(selected = :current_user, display_path: false)
groups = current_user.owned_groups + current_user.masters_groups groups = current_user.owned_groups + current_user.masters_groups
users = [current_user.namespace] users = [current_user.namespace]
group_opts = ["Groups", groups.sort_by(&:human_name).map {|g| [g.human_name, g.id]} ] group_opts = ["Groups", groups.sort_by(&:human_name).map {|g| [display_path ? g.path : g.human_name, g.id]} ]
users_opts = [ "Users", users.sort_by(&:human_name).map {|u| [u.human_name, u.id]} ] users_opts = [ "Users", users.sort_by(&:human_name).map {|u| [display_path ? u.path : u.human_name, u.id]} ]
options = [] options = []
options << group_opts options << group_opts
......
...@@ -4,6 +4,14 @@ module NavHelper ...@@ -4,6 +4,14 @@ module NavHelper
end end
def nav_sidebar_class def nav_sidebar_class
if nav_menu_collapsed?
"sidebar-collapsed"
else
"sidebar-expanded"
end
end
def page_sidebar_class
if nav_menu_collapsed? if nav_menu_collapsed?
"page-sidebar-collapsed" "page-sidebar-collapsed"
else else
......
...@@ -21,7 +21,7 @@ module ProjectsHelper ...@@ -21,7 +21,7 @@ module ProjectsHelper
end end
def link_to_member(project, author, opts = {}) def link_to_member(project, author, opts = {})
default_opts = { avatar: true, name: true, size: 16, author_class: 'author' } default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name" }
opts = default_opts.merge(opts) opts = default_opts.merge(opts)
return "(deleted)" unless author return "(deleted)" unless author
...@@ -39,7 +39,8 @@ module ProjectsHelper ...@@ -39,7 +39,8 @@ module ProjectsHelper
if opts[:name] if opts[:name]
link_to(author_html, user_path(author), class: "author_link").html_safe link_to(author_html, user_path(author), class: "author_link").html_safe
else else
link_to(author_html, user_path(author), class: "author_link has_tooltip", data: { :'original-title' => sanitize(author.name) } ).html_safe title = opts[:title].sub(":name", sanitize(author.name))
link_to(author_html, user_path(author), class: "author_link has_tooltip", data: { :'original-title' => title, container: 'body' } ).html_safe
end end
end end
...@@ -173,8 +174,7 @@ module ProjectsHelper ...@@ -173,8 +174,7 @@ module ProjectsHelper
'unknown' 'unknown'
end end
def default_url_to_repo(project = nil) def default_url_to_repo(project = @project)
project = project || @project
current_user ? project.url_to_repo : project.http_url_to_repo current_user ? project.url_to_repo : project.http_url_to_repo
end end
......
...@@ -15,12 +15,14 @@ module SelectsHelper ...@@ -15,12 +15,14 @@ module SelectsHelper
html = { html = {
class: css_class, class: css_class,
'data-placeholder' => placeholder, data: {
'data-null-user' => null_user, placeholder: placeholder,
'data-any-user' => any_user, null_user: null_user,
'data-email-user' => email_user, any_user: any_user,
'data-first-user' => first_user, email_user: email_user,
'data-current-user' => current_user first_user: first_user,
current_user: current_user
}
} }
unless opts[:scope] == :all unless opts[:scope] == :all
......
...@@ -25,48 +25,24 @@ module VisibilityLevelHelper ...@@ -25,48 +25,24 @@ module VisibilityLevelHelper
end end
def project_visibility_level_description(level) def project_visibility_level_description(level)
capture_haml do case level
haml_tag :span do when Gitlab::VisibilityLevel::PRIVATE
case level "Project access must be granted explicitly for each user."
when Gitlab::VisibilityLevel::PRIVATE when Gitlab::VisibilityLevel::INTERNAL
haml_concat "Project access must be granted explicitly for each user." "The project can be cloned by any logged in user."
when Gitlab::VisibilityLevel::INTERNAL when Gitlab::VisibilityLevel::PUBLIC
haml_concat "The project can be cloned by" "The project can be cloned without any authentication."
haml_concat "any logged in user."
when Gitlab::VisibilityLevel::PUBLIC
haml_concat "The project can be cloned"
haml_concat "without any"
haml_concat "authentication."
end
end
end end
end end
def snippet_visibility_level_description(level) def snippet_visibility_level_description(level)
capture_haml do
haml_tag :span do
case level
when Gitlab::VisibilityLevel::PRIVATE
haml_concat "The snippet is visible only for me."
when Gitlab::VisibilityLevel::INTERNAL
haml_concat "The snippet is visible for any logged in user."
when Gitlab::VisibilityLevel::PUBLIC
haml_concat "The snippet can be accessed"
haml_concat "without any"
haml_concat "authentication."
end
end
end
end
def visibility_level_icon(level)
case level case level
when Gitlab::VisibilityLevel::PRIVATE when Gitlab::VisibilityLevel::PRIVATE
private_icon "The snippet is visible only for me."
when Gitlab::VisibilityLevel::INTERNAL when Gitlab::VisibilityLevel::INTERNAL
internal_icon "The snippet is visible for any logged in user."
when Gitlab::VisibilityLevel::PUBLIC when Gitlab::VisibilityLevel::PUBLIC
public_icon "The snippet can be accessed without any authentication."
end end
end end
......
...@@ -8,10 +8,6 @@ class BaseMailer < ActionMailer::Base ...@@ -8,10 +8,6 @@ class BaseMailer < ActionMailer::Base
default from: Proc.new { default_sender_address.format } default from: Proc.new { default_sender_address.format }
default reply_to: Proc.new { default_reply_to_address.format } default reply_to: Proc.new { default_reply_to_address.format }
def self.delay
delay_for(2.seconds)
end
def can? def can?
Ability.abilities.allowed?(current_user, action, subject) Ability.abilities.allowed?(current_user, action, subject)
end end
......
...@@ -30,6 +30,8 @@ ...@@ -30,6 +30,8 @@
# #
class ApplicationSetting < ActiveRecord::Base class ApplicationSetting < ActiveRecord::Base
CACHE_KEY = 'application_setting.last'
serialize :restricted_visibility_levels serialize :restricted_visibility_levels
serialize :import_sources serialize :import_sources
serialize :restricted_signup_domains, Array serialize :restricted_signup_domains, Array
...@@ -73,15 +75,19 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -73,15 +75,19 @@ class ApplicationSetting < ActiveRecord::Base
end end
after_commit do after_commit do
Rails.cache.write('application_setting.last', self) Rails.cache.write(CACHE_KEY, self)
end end
def self.current def self.current
Rails.cache.fetch('application_setting.last') do Rails.cache.fetch(CACHE_KEY) do
ApplicationSetting.last ApplicationSetting.last
end end
end end
def self.expire
Rails.cache.delete(CACHE_KEY)
end
def self.create_from_defaults def self.create_from_defaults
create( create(
default_projects_limit: Settings.gitlab['default_projects_limit'], default_projects_limit: Settings.gitlab['default_projects_limit'],
......
...@@ -12,13 +12,18 @@ ...@@ -12,13 +12,18 @@
module Ci module Ci
class ApplicationSetting < ActiveRecord::Base class ApplicationSetting < ActiveRecord::Base
extend Ci::Model extend Ci::Model
CACHE_KEY = 'ci_application_setting.last'
after_commit do after_commit do
Rails.cache.write('ci_application_setting.last', self) Rails.cache.write(CACHE_KEY, self)
end
def self.expire
Rails.cache.delete(CACHE_KEY)
end end
def self.current def self.current
Rails.cache.fetch('ci_application_setting.last') do Rails.cache.fetch(CACHE_KEY) do
Ci::ApplicationSetting.last Ci::ApplicationSetting.last
end end
end end
......
...@@ -78,11 +78,23 @@ class Commit ...@@ -78,11 +78,23 @@ class Commit
}x }x
end end
def self.link_reference_pattern
super("commit", /(?<commit>\h{6,40})/)
end
def to_reference(from_project = nil) def to_reference(from_project = nil)
if cross_project_reference?(from_project) if cross_project_reference?(from_project)
"#{project.to_reference}@#{id}" project.to_reference + self.class.reference_prefix + self.id
else
self.id
end
end
def reference_link_text(from_project = nil)
if cross_project_reference?(from_project)
project.to_reference + self.class.reference_prefix + self.short_id
else else
id self.short_id
end end
end end
......
...@@ -2,36 +2,38 @@ ...@@ -2,36 +2,38 @@
# #
# Examples: # Examples:
# #
# range = CommitRange.new('f3f85602...e86e1013') # range = CommitRange.new('f3f85602...e86e1013', project)
# range.exclude_start? # => false # range.exclude_start? # => false
# range.reference_title # => "Commits f3f85602 through e86e1013" # range.reference_title # => "Commits f3f85602 through e86e1013"
# range.to_s # => "f3f85602...e86e1013" # range.to_s # => "f3f85602...e86e1013"
# #
# range = CommitRange.new('f3f856029bc5f966c5a7ee24cf7efefdd20e6019..e86e1013709735be5bb767e2b228930c543f25ae') # range = CommitRange.new('f3f856029bc5f966c5a7ee24cf7efefdd20e6019..e86e1013709735be5bb767e2b228930c543f25ae', project)
# range.exclude_start? # => true # range.exclude_start? # => true
# range.reference_title # => "Commits f3f85602^ through e86e1013" # range.reference_title # => "Commits f3f85602^ through e86e1013"
# range.to_param # => {from: "f3f856029bc5f966c5a7ee24cf7efefdd20e6019^", to: "e86e1013709735be5bb767e2b228930c543f25ae"} # range.to_param # => {from: "f3f856029bc5f966c5a7ee24cf7efefdd20e6019^", to: "e86e1013709735be5bb767e2b228930c543f25ae"}
# range.to_s # => "f3f85602..e86e1013" # range.to_s # => "f3f85602..e86e1013"
# #
# # Assuming `project` is a Project with a repository containing both commits: # # Assuming the specified project has a repository containing both commits:
# range.project = project
# range.valid_commits? # => true # range.valid_commits? # => true
# #
class CommitRange class CommitRange
include ActiveModel::Conversion include ActiveModel::Conversion
include Referable include Referable
attr_reader :sha_from, :notation, :sha_to attr_reader :commit_from, :notation, :commit_to
attr_reader :ref_from, :ref_to
# Optional Project model # Optional Project model
attr_accessor :project attr_accessor :project
# See `exclude_start?` # The beginning and ending refs can be named or SHAs, and
attr_reader :exclude_start
# The beginning and ending SHAs can be between 6 and 40 hex characters, and
# the range notation can be double- or triple-dot. # the range notation can be double- or triple-dot.
PATTERN = /\h{6,40}\.{2,3}\h{6,40}/ REF_PATTERN = /[0-9a-zA-Z][0-9a-zA-Z_.-]*[0-9a-zA-Z\^]/
PATTERN = /#{REF_PATTERN}\.{2,3}#{REF_PATTERN}/
# In text references, the beginning and ending refs can only be SHAs
# between 6 and 40 hex characters.
STRICT_PATTERN = /\h{6,40}\.{2,3}\h{6,40}/
def self.reference_prefix def self.reference_prefix
'@' '@'
...@@ -43,27 +45,40 @@ class CommitRange ...@@ -43,27 +45,40 @@ class CommitRange
def self.reference_pattern def self.reference_pattern
%r{ %r{
(?:#{Project.reference_pattern}#{reference_prefix})? (?:#{Project.reference_pattern}#{reference_prefix})?
(?<commit_range>#{PATTERN}) (?<commit_range>#{STRICT_PATTERN})
}x }x
end end
def self.link_reference_pattern
super("compare", /(?<commit_range>#{PATTERN})/)
end
# Initialize a CommitRange # Initialize a CommitRange
# #
# range_string - The String commit range. # range_string - The String commit range.
# project - An optional Project model. # project - An optional Project model.
# #
# Raises ArgumentError if `range_string` does not match `PATTERN`. # Raises ArgumentError if `range_string` does not match `PATTERN`.
def initialize(range_string, project = nil) def initialize(range_string, project)
@project = project
range_string.strip! range_string.strip!
unless range_string.match(/\A#{PATTERN}\z/) unless range_string =~ /\A#{PATTERN}\z/
raise ArgumentError, "invalid CommitRange string format: #{range_string}" raise ArgumentError, "invalid CommitRange string format: #{range_string}"
end end
@exclude_start = !range_string.include?('...') @ref_from, @notation, @ref_to = range_string.split(/(\.{2,3})/, 2)
@sha_from, @notation, @sha_to = range_string.split(/(\.{2,3})/, 2)
@project = project if project.valid_repo?
@commit_from = project.commit(@ref_from)
@commit_to = project.commit(@ref_to)
end
if valid_commits?
@ref_from = Commit.truncate_sha(sha_from) if sha_from.start_with?(@ref_from)
@ref_to = Commit.truncate_sha(sha_to) if sha_to.start_with?(@ref_to)
end
end end
def inspect def inspect
...@@ -71,15 +86,24 @@ class CommitRange ...@@ -71,15 +86,24 @@ class CommitRange
end end
def to_s def to_s
"#{sha_from[0..7]}#{notation}#{sha_to[0..7]}" sha_from + notation + sha_to
end end
alias_method :id, :to_s
def to_reference(from_project = nil) def to_reference(from_project = nil)
# Not using to_s because we want the full SHAs if cross_project_reference?(from_project)
reference = sha_from + notation + sha_to project.to_reference + self.class.reference_prefix + self.id
else
self.id
end
end
def reference_link_text(from_project = nil)
reference = ref_from + notation + ref_to
if cross_project_reference?(from_project) if cross_project_reference?(from_project)
reference = project.to_reference + '@' + reference reference = project.to_reference + self.class.reference_prefix + reference
end end
reference reference
...@@ -87,46 +111,58 @@ class CommitRange ...@@ -87,46 +111,58 @@ class CommitRange
# Returns a String for use in a link's title attribute # Returns a String for use in a link's title attribute
def reference_title def reference_title
"Commits #{suffixed_sha_from} through #{sha_to}" "Commits #{sha_start} through #{sha_to}"
end end
# Return a Hash of parameters for passing to a URL helper # Return a Hash of parameters for passing to a URL helper
# #
# See `namespace_project_compare_url` # See `namespace_project_compare_url`
def to_param def to_param
{ from: suffixed_sha_from, to: sha_to } { from: sha_start, to: sha_to }
end end
def exclude_start? def exclude_start?
exclude_start @notation == '..'
end end
# Check if both the starting and ending commit IDs exist in a project's # Check if both the starting and ending commit IDs exist in a project's
# repository # repository
# def valid_commits?
# project - An optional Project to check (default: `project`) commit_start.present? && commit_end.present?
def valid_commits?(project = project)
return nil unless project.present?
return false unless project.valid_repo?
commit_from.present? && commit_to.present?
end end
def persisted? def persisted?
true true
end end
def commit_from def sha_from
@commit_from ||= project.repository.commit(suffixed_sha_from) return nil unless @commit_from
@commit_from.id
end
def sha_to
return nil unless @commit_to
@commit_to.id
end end
def commit_to def sha_start
@commit_to ||= project.repository.commit(sha_to) return nil unless sha_from
exclude_start? ? sha_from + '^' : sha_from
end end
private def commit_start
return nil unless sha_start
def suffixed_sha_from if exclude_start?
sha_from + (exclude_start? ? '^' : '') @commit_start ||= project.commit(sha_start)
else
commit_from
end
end end
alias_method :sha_end, :sha_to
alias_method :commit_end, :commit_to
end end
...@@ -8,6 +8,7 @@ module Issuable ...@@ -8,6 +8,7 @@ module Issuable
extend ActiveSupport::Concern extend ActiveSupport::Concern
include Participable include Participable
include Mentionable include Mentionable
include StripAttribute
included do included do
belongs_to :author, class_name: "User" belongs_to :author, class_name: "User"
...@@ -51,6 +52,7 @@ module Issuable ...@@ -51,6 +52,7 @@ module Issuable
attr_mentionable :title, :description attr_mentionable :title, :description
participant :author, :assignee, :notes_with_associations participant :author, :assignee, :notes_with_associations
strip_attributes :title
end end
module ClassMethods module ClassMethods
......
...@@ -62,13 +62,18 @@ module Mentionable ...@@ -62,13 +62,18 @@ module Mentionable
return [] if text.blank? return [] if text.blank?
refs = all_references(current_user, text, load_lazy_references: load_lazy_references) refs = all_references(current_user, text, load_lazy_references: load_lazy_references)
(refs.issues + refs.merge_requests + refs.commits) - [local_reference] refs = (refs.issues + refs.merge_requests + refs.commits)
# We're using this method instead of Array diffing because that requires
# both of the object's `hash` values to be the same, which may not be the
# case for otherwise identical Commit objects.
refs.reject { |ref| ref == local_reference }
end end
# Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+. # Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+.
def create_cross_references!(author = self.author, without = [], text = self.mentionable_text) def create_cross_references!(author = self.author, without = [], text = self.mentionable_text)
refs = referenced_mentionables(author, text) refs = referenced_mentionables(author, text)
# We're using this method instead of Array diffing because that requires # We're using this method instead of Array diffing because that requires
# both of the object's `hash` values to be the same, which may not be the # both of the object's `hash` values to be the same, which may not be the
# case for otherwise identical Commit objects. # case for otherwise identical Commit objects.
...@@ -111,7 +116,7 @@ module Mentionable ...@@ -111,7 +116,7 @@ module Mentionable
# Only include changed fields that are mentionable # Only include changed fields that are mentionable
source.select { |key, val| mentionable.include?(key) } source.select { |key, val| mentionable.include?(key) }
end end
# Determine whether or not a cross-reference Note has already been created between this Mentionable and # Determine whether or not a cross-reference Note has already been created between this Mentionable and
# the specified target. # the specified target.
def cross_reference_exists?(target) def cross_reference_exists?(target)
......
...@@ -21,6 +21,10 @@ module Referable ...@@ -21,6 +21,10 @@ module Referable
'' ''
end end
def reference_link_text(from_project = nil)
to_reference(from_project)
end
module ClassMethods module ClassMethods
# The character that prefixes the actual reference identifier # The character that prefixes the actual reference identifier
# #
...@@ -44,6 +48,25 @@ module Referable ...@@ -44,6 +48,25 @@ module Referable
def reference_pattern def reference_pattern
raise NotImplementedError, "#{self} does not implement #{__method__}" raise NotImplementedError, "#{self} does not implement #{__method__}"
end end
def link_reference_pattern(route, pattern)
%r{
(?<url>
#{Regexp.escape(Gitlab.config.gitlab.url)}
\/#{Project.reference_pattern}
\/#{Regexp.escape(route)}
\/#{pattern}
(?<path>
(\/[a-z0-9_=-]+)*
)?
(?<query>
\?[a-z0-9_=-]+
(&[a-z0-9_=-]+)*
)?
(?<anchor>\#[a-z0-9_-]+)?
)
}x
end
end end
private private
......
# == Strip Attribute module
#
# Contains functionality to clean attributes before validation
#
# Usage:
#
# class Milestone < ActiveRecord::Base
# strip_attributes :title
# end
#
#
module StripAttribute
extend ActiveSupport::Concern
module ClassMethods
def strip_attributes(*attrs)
strip_attrs.concat(attrs)
end
def strip_attrs
@strip_attrs ||= []
end
end
included do
before_validation :strip_attributes
end
def strip_attributes
self.class.strip_attrs.each do |attr|
self[attr].strip! if self[attr] && self[attr].respond_to?(:strip!)
end
end
end
...@@ -201,7 +201,7 @@ class Event < ActiveRecord::Base ...@@ -201,7 +201,7 @@ class Event < ActiveRecord::Base
elsif commented? elsif commented?
"commented on" "commented on"
elsif created_project? elsif created_project?
if project.import? if project.external_import?
"imported" "imported"
else else
"created" "created"
......
...@@ -69,6 +69,10 @@ class Issue < ActiveRecord::Base ...@@ -69,6 +69,10 @@ class Issue < ActiveRecord::Base
}x }x
end end
def self.link_reference_pattern
super("issues", /(?<issue>\d+)/)
end
def to_reference(from_project = nil) def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{iid}" reference = "#{self.class.reference_prefix}#{iid}"
......
...@@ -17,7 +17,7 @@ class Label < ActiveRecord::Base ...@@ -17,7 +17,7 @@ class Label < ActiveRecord::Base
# Requests that have no label assigned. # Requests that have no label assigned.
LabelStruct = Struct.new(:title, :name) LabelStruct = Struct.new(:title, :name)
None = LabelStruct.new('No Label', 'No Label') None = LabelStruct.new('No Label', 'No Label')
Any = LabelStruct.new('Any', '') Any = LabelStruct.new('Any Label', '')
DEFAULT_COLOR = '#428BCA' DEFAULT_COLOR = '#428BCA'
......
...@@ -151,6 +151,10 @@ class MergeRequest < ActiveRecord::Base ...@@ -151,6 +151,10 @@ class MergeRequest < ActiveRecord::Base
}x }x
end end
def self.link_reference_pattern
super("merge_requests", /(?<merge_request>\d+)/)
end
def to_reference(from_project = nil) def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{iid}" reference = "#{self.class.reference_prefix}#{iid}"
...@@ -316,7 +320,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -316,7 +320,7 @@ class MergeRequest < ActiveRecord::Base
issues = commits.flat_map { |c| c.closes_issues(current_user) } issues = commits.flat_map { |c| c.closes_issues(current_user) }
issues.push(*Gitlab::ClosingIssueExtractor.new(project, current_user). issues.push(*Gitlab::ClosingIssueExtractor.new(project, current_user).
closed_by_message(description)) closed_by_message(description))
issues.uniq.sort_by(&:id) issues.uniq
else else
[] []
end end
......
...@@ -16,12 +16,13 @@ ...@@ -16,12 +16,13 @@
class Milestone < ActiveRecord::Base class Milestone < ActiveRecord::Base
# Represents a "No Milestone" state used for filtering Issues and Merge # Represents a "No Milestone" state used for filtering Issues and Merge
# Requests that have no milestone assigned. # Requests that have no milestone assigned.
MilestoneStruct = Struct.new(:title, :name) MilestoneStruct = Struct.new(:title, :name, :id)
None = MilestoneStruct.new('No Milestone', 'No Milestone') None = MilestoneStruct.new('No Milestone', 'No Milestone', 0)
Any = MilestoneStruct.new('Any', '') Any = MilestoneStruct.new('Any Milestone', '', -1)
include InternalId include InternalId
include Sortable include Sortable
include StripAttribute
belongs_to :project belongs_to :project
has_many :issues has_many :issues
...@@ -35,6 +36,8 @@ class Milestone < ActiveRecord::Base ...@@ -35,6 +36,8 @@ class Milestone < ActiveRecord::Base
validates :title, presence: true validates :title, presence: true
validates :project, presence: true validates :project, presence: true
strip_attributes :title
state_machine :state, initial: :active do state_machine :state, initial: :active do
event :close do event :close do
transition active: :closed transition active: :closed
......
...@@ -42,9 +42,8 @@ class Project < ActiveRecord::Base ...@@ -42,9 +42,8 @@ class Project < ActiveRecord::Base
include Sortable include Sortable
include AfterCommitQueue include AfterCommitQueue
include CaseSensitivity include CaseSensitivity
extend Gitlab::ConfigHelper extend Gitlab::ConfigHelper
extend Enumerize
UNKNOWN_IMPORT_URL = 'http://unknown.git' UNKNOWN_IMPORT_URL = 'http://unknown.git'
......
...@@ -37,7 +37,7 @@ class BuildkiteService < CiService ...@@ -37,7 +37,7 @@ class BuildkiteService < CiService
def compose_service_hook def compose_service_hook
hook = service_hook || build_service_hook hook = service_hook || build_service_hook
hook.url = webhook_url hook.url = webhook_url
hook.enable_ssl_verification = enable_ssl_verification hook.enable_ssl_verification = !!enable_ssl_verification
hook.save hook.save
end end
......
...@@ -64,9 +64,9 @@ module Ci ...@@ -64,9 +64,9 @@ module Ci
build.project_recipients.each do |recipient| build.project_recipients.each do |recipient|
case build.status.to_sym case build.status.to_sym
when :success when :success
mailer.build_success_email(build.id, recipient) mailer.build_success_email(build.id, recipient).deliver_later
when :failed when :failed
mailer.build_fail_email(build.id, recipient) mailer.build_fail_email(build.id, recipient).deliver_later
end end
end end
end end
...@@ -78,7 +78,7 @@ module Ci ...@@ -78,7 +78,7 @@ module Ci
end end
def mailer def mailer
Ci::Notify.delay Ci::Notify
end end
end end
end end
...@@ -34,7 +34,7 @@ class DroneCiService < CiService ...@@ -34,7 +34,7 @@ class DroneCiService < CiService
hook = service_hook || build_service_hook hook = service_hook || build_service_hook
# If using a service template, project may not be available # If using a service template, project may not be available
hook.url = [drone_url, "/api/hook", "?owner=#{project.namespace.path}", "&name=#{project.path}", "&access_token=#{token}"].join if project hook.url = [drone_url, "/api/hook", "?owner=#{project.namespace.path}", "&name=#{project.path}", "&access_token=#{token}"].join if project
hook.enable_ssl_verification = enable_ssl_verification hook.enable_ssl_verification = !!enable_ssl_verification
hook.save hook.save
end end
......
...@@ -55,7 +55,7 @@ class GitlabCiService < CiService ...@@ -55,7 +55,7 @@ class GitlabCiService < CiService
end end
def get_ci_commit(sha, ref) def get_ci_commit(sha, ref)
Ci::Project.find(project.gitlab_ci_project).commits.find_by_sha!(sha) Ci::Project.find(project.gitlab_ci_project.id).commits.find_by_sha!(sha)
end end
def commit_status(sha, ref) def commit_status(sha, ref)
......
...@@ -571,9 +571,13 @@ class Repository ...@@ -571,9 +571,13 @@ class Repository
# Run GitLab pre-receive hook # Run GitLab pre-receive hook
pre_receive_hook = Gitlab::Git::Hook.new('pre-receive', path_to_repo) pre_receive_hook = Gitlab::Git::Hook.new('pre-receive', path_to_repo)
status = pre_receive_hook.trigger(gl_id, oldrev, newrev, ref) pre_receive_hook_status = pre_receive_hook.trigger(gl_id, oldrev, newrev, ref)
if status # Run GitLab update hook
update_hook = Gitlab::Git::Hook.new('update', path_to_repo)
update_hook_status = update_hook.trigger(gl_id, oldrev, newrev, ref)
if pre_receive_hook_status && update_hook_status
if was_empty if was_empty
# Create branch # Create branch
rugged.references.create(ref, newrev) rugged.references.create(ref, newrev)
...@@ -596,7 +600,7 @@ class Repository ...@@ -596,7 +600,7 @@ class Repository
# Remove tmp ref and return error to user # Remove tmp ref and return error to user
rugged.references.delete(tmp_ref) rugged.references.delete(tmp_ref)
raise PreReceiveError.new('Commit was rejected by pre-receive hook') raise PreReceiveError.new('Commit was rejected by git hook')
end end
end end
......
...@@ -17,9 +17,8 @@ class SentNotification < ActiveRecord::Base ...@@ -17,9 +17,8 @@ class SentNotification < ActiveRecord::Base
belongs_to :noteable, polymorphic: true belongs_to :noteable, polymorphic: true
belongs_to :recipient, class_name: "User" belongs_to :recipient, class_name: "User"
validate :project, :recipient, :reply_key, presence: true validates :project, :recipient, :reply_key, presence: true
validate :reply_key, uniqueness: true validates :reply_key, uniqueness: true
validates :noteable_id, presence: true, unless: :for_commit? validates :noteable_id, presence: true, unless: :for_commit?
validates :commit_id, presence: true, if: :for_commit? validates :commit_id, presence: true, if: :for_commit?
validates :line_code, format: { with: /\A[a-z0-9]+_\d+_\d+\Z/ }, allow_blank: true validates :line_code, format: { with: /\A[a-z0-9]+_\d+_\d+\Z/ }, allow_blank: true
......
...@@ -65,6 +65,10 @@ class Snippet < ActiveRecord::Base ...@@ -65,6 +65,10 @@ class Snippet < ActiveRecord::Base
}x }x
end end
def self.link_reference_pattern
super("snippets", /(?<snippet>\d+)/)
end
def to_reference(from_project = nil) def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{id}" reference = "#{self.class.reference_prefix}#{id}"
......
...@@ -637,11 +637,11 @@ class User < ActiveRecord::Base ...@@ -637,11 +637,11 @@ class User < ActiveRecord::Base
email.start_with?('temp-email-for-oauth') email.start_with?('temp-email-for-oauth')
end end
def avatar_url(size = nil) def avatar_url(size = nil, scale = 2)
if avatar.present? if avatar.present?
[gitlab_config.url, avatar.url].join [gitlab_config.url, avatar.url].join
else else
GravatarService.new.execute(email, size) GravatarService.new.execute(email, size, scale)
end end
end end
...@@ -690,7 +690,7 @@ class User < ActiveRecord::Base ...@@ -690,7 +690,7 @@ class User < ActiveRecord::Base
end end
def starred?(project) def starred?(project)
starred_projects.exists?(project) starred_projects.exists?(project.id)
end end
def toggle_star(project) def toggle_star(project)
...@@ -794,4 +794,9 @@ class User < ActiveRecord::Base ...@@ -794,4 +794,9 @@ class User < ActiveRecord::Base
Gitlab::SQL::Union.new([personal_projects.select(:id), groups.select(:id), Gitlab::SQL::Union.new([personal_projects.select(:id), groups.select(:id),
other.select(:id)]) other.select(:id)])
end end
# Added according to https://github.com/plataformatec/devise/blob/7df57d5081f9884849ca15e4fde179ef164a575f/README.md#activejob-integration
def send_devise_notification(notification, *args)
devise_mailer.send(notification, self, *args).deliver_later
end
end end
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
# #
class UsersStarProject < ActiveRecord::Base class UsersStarProject < ActiveRecord::Base
belongs_to :project, counter_cache: :star_count belongs_to :project, counter_cache: :star_count, touch: true
belongs_to :user belongs_to :user
validates :user, presence: true validates :user, presence: true
......
class GravatarService class GravatarService
include Gitlab::CurrentSettings include Gitlab::CurrentSettings
def execute(email, size = nil) def execute(email, size = nil, scale = 2)
if current_application_settings.gravatar_enabled? && email.present? if current_application_settings.gravatar_enabled? && email.present?
size = 40 if size.nil? || size <= 0 size = 40 if size.nil? || size <= 0
sprintf gravatar_url, sprintf gravatar_url,
hash: Digest::MD5.hexdigest(email.strip.downcase), hash: Digest::MD5.hexdigest(email.strip.downcase),
size: size, size: size * scale,
email: email.strip email: email.strip
end end
end end
......
...@@ -38,7 +38,7 @@ module MergeRequests ...@@ -38,7 +38,7 @@ module MergeRequests
} }
repository.merge(current_user, merge_request.source_sha, merge_request.target_branch, options) repository.merge(current_user, merge_request.source_sha, merge_request.target_branch, options)
rescue Exception => e rescue StandardError => e
merge_request.update(merge_error: "Something went wrong during merge") merge_request.update(merge_error: "Something went wrong during merge")
Rails.logger.error(e.message) Rails.logger.error(e.message)
return false return false
......
...@@ -13,14 +13,14 @@ class NotificationService ...@@ -13,14 +13,14 @@ class NotificationService
# even if user disabled notifications # even if user disabled notifications
def new_key(key) def new_key(key)
if key.user if key.user
mailer.new_ssh_key_email(key.id) mailer.new_ssh_key_email(key.id).deliver_later
end end
end end
# Always notify user about email added to profile # Always notify user about email added to profile
def new_email(email) def new_email(email)
if email.user if email.user
mailer.new_email_email(email.id) mailer.new_email_email(email.id).deliver_later
end end
end end
...@@ -79,17 +79,27 @@ class NotificationService ...@@ -79,17 +79,27 @@ class NotificationService
end end
def merge_mr(merge_request, current_user) def merge_mr(merge_request, current_user)
close_resource_email(merge_request, merge_request.target_project, current_user, 'merged_merge_request_email') close_resource_email(
merge_request,
merge_request.target_project,
current_user,
'merged_merge_request_email'
)
end end
def reopen_mr(merge_request, current_user) def reopen_mr(merge_request, current_user)
reopen_resource_email(merge_request, merge_request.target_project, current_user, 'merge_request_status_email', 'reopened') reopen_resource_email(
merge_request,
merge_request.target_project,
current_user, 'merge_request_status_email',
'reopened'
)
end end
# Notify new user with email after creation # Notify new user with email after creation
def new_user(user, token = nil) def new_user(user, token = nil)
# Don't email omniauth created users # Don't email omniauth created users
mailer.new_user_email(user.id, token) unless user.identities.any? mailer.new_user_email(user.id, token).deliver_later unless user.identities.any?
end end
# Notify users on new note in system # Notify users on new note in system
...@@ -135,53 +145,63 @@ class NotificationService ...@@ -135,53 +145,63 @@ class NotificationService
recipients = reject_unsubscribed_users(recipients, note.noteable) recipients = reject_unsubscribed_users(recipients, note.noteable)
recipients.delete(note.author) recipients.delete(note.author)
recipients = recipients.uniq
# build notify method like 'note_commit_email' # build notify method like 'note_commit_email'
notify_method = "note_#{note.noteable_type.underscore}_email".to_sym notify_method = "note_#{note.noteable_type.underscore}_email".to_sym
recipients.each do |recipient| recipients.each do |recipient|
mailer.send(notify_method, recipient.id, note.id) mailer.send(notify_method, recipient.id, note.id).deliver_later
end end
end end
def invite_project_member(project_member, token) def invite_project_member(project_member, token)
mailer.project_member_invited_email(project_member.id, token) mailer.project_member_invited_email(project_member.id, token).deliver_later
end end
def accept_project_invite(project_member) def accept_project_invite(project_member)
mailer.project_invite_accepted_email(project_member.id) mailer.project_invite_accepted_email(project_member.id).deliver_later
end end
def decline_project_invite(project_member) def decline_project_invite(project_member)
mailer.project_invite_declined_email(project_member.project.id, project_member.invite_email, project_member.access_level, project_member.created_by_id) mailer.project_invite_declined_email(
project_member.project.id,
project_member.invite_email,
project_member.access_level,
project_member.created_by_id
).deliver_later
end end
def new_project_member(project_member) def new_project_member(project_member)
mailer.project_access_granted_email(project_member.id) mailer.project_access_granted_email(project_member.id).deliver_later
end end
def update_project_member(project_member) def update_project_member(project_member)
mailer.project_access_granted_email(project_member.id) mailer.project_access_granted_email(project_member.id).deliver_later
end end
def invite_group_member(group_member, token) def invite_group_member(group_member, token)
mailer.group_member_invited_email(group_member.id, token) mailer.group_member_invited_email(group_member.id, token).deliver_later
end end
def accept_group_invite(group_member) def accept_group_invite(group_member)
mailer.group_invite_accepted_email(group_member.id) mailer.group_invite_accepted_email(group_member.id).deliver_later
end end
def decline_group_invite(group_member) def decline_group_invite(group_member)
mailer.group_invite_declined_email(group_member.group.id, group_member.invite_email, group_member.access_level, group_member.created_by_id) mailer.group_invite_declined_email(
group_member.group.id,
group_member.invite_email,
group_member.access_level,
group_member.created_by_id
).deliver_later
end end
def new_group_member(group_member) def new_group_member(group_member)
mailer.group_access_granted_email(group_member.id) mailer.group_access_granted_email(group_member.id).deliver_later
end end
def update_group_member(group_member) def update_group_member(group_member)
mailer.group_access_granted_email(group_member.id) mailer.group_access_granted_email(group_member.id).deliver_later
end end
def project_was_moved(project, old_path_with_namespace) def project_was_moved(project, old_path_with_namespace)
...@@ -189,7 +209,11 @@ class NotificationService ...@@ -189,7 +209,11 @@ class NotificationService
recipients = reject_muted_users(recipients, project) recipients = reject_muted_users(recipients, project)
recipients.each do |recipient| recipients.each do |recipient|
mailer.project_was_moved_email(project.id, recipient.id, old_path_with_namespace) mailer.project_was_moved_email(
project.id,
recipient.id,
old_path_with_namespace
).deliver_later
end end
end end
...@@ -339,7 +363,7 @@ class NotificationService ...@@ -339,7 +363,7 @@ class NotificationService
recipients = build_recipients(target, project, target.author) recipients = build_recipients(target, project, target.author)
recipients.each do |recipient| recipients.each do |recipient|
mailer.send(method, recipient.id, target.id) mailer.send(method, recipient.id, target.id).deliver_later
end end
end end
...@@ -347,7 +371,7 @@ class NotificationService ...@@ -347,7 +371,7 @@ class NotificationService
recipients = build_recipients(target, project, current_user) recipients = build_recipients(target, project, current_user)
recipients.each do |recipient| recipients.each do |recipient|
mailer.send(method, recipient.id, target.id, current_user.id) mailer.send(method, recipient.id, target.id, current_user.id).deliver_later
end end
end end
...@@ -358,7 +382,13 @@ class NotificationService ...@@ -358,7 +382,13 @@ class NotificationService
recipients = build_recipients(target, project, current_user, [previous_assignee]) recipients = build_recipients(target, project, current_user, [previous_assignee])
recipients.each do |recipient| recipients.each do |recipient|
mailer.send(method, recipient.id, target.id, previous_assignee_id, current_user.id) mailer.send(
method,
recipient.id,
target.id,
previous_assignee_id,
current_user.id
).deliver_later
end end
end end
...@@ -366,7 +396,7 @@ class NotificationService ...@@ -366,7 +396,7 @@ class NotificationService
recipients = build_recipients(target, project, current_user) recipients = build_recipients(target, project, current_user)
recipients.each do |recipient| recipients.each do |recipient|
mailer.send(method, recipient.id, target.id, status, current_user.id) mailer.send(method, recipient.id, target.id, status, current_user.id).deliver_later
end end
end end
...@@ -388,7 +418,7 @@ class NotificationService ...@@ -388,7 +418,7 @@ class NotificationService
end end
def mailer def mailer
Notify.delay Notify
end end
def previous_record(object, attribute) def previous_record(object, attribute)
......
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.
File mode changed from 100644 to 100755
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
File mode changed from 100644 to 100755
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