summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG3
-rw-r--r--Gemfile22
-rw-r--r--Gemfile.lock9
-rw-r--r--app/assets/javascripts/behaviors/taskable.js.coffee21
-rw-r--r--app/assets/javascripts/issue.js.coffee31
-rw-r--r--app/assets/javascripts/merge_request.js.coffee31
-rw-r--r--app/assets/javascripts/notes.js.coffee26
-rw-r--r--app/assets/stylesheets/generic/typography.scss4
-rw-r--r--app/assets/stylesheets/pages/notes.scss10
-rw-r--r--app/helpers/notes_helper.rb4
-rw-r--r--app/mailers/devise_mailer.rb4
-rw-r--r--app/mailers/emails/projects.rb2
-rw-r--r--app/mailers/notify.rb8
-rw-r--r--app/models/concerns/taskable.rb47
-rw-r--r--app/views/projects/issues/_discussion.html.haml8
-rw-r--r--app/views/projects/issues/_issue_context.html.haml2
-rw-r--r--app/views/projects/issues/show.html.haml20
-rw-r--r--app/views/projects/merge_requests/show/_context.html.haml18
-rw-r--r--app/views/projects/merge_requests/show/_mr_box.html.haml6
-rw-r--r--app/views/projects/notes/_edit_form.html.haml13
-rw-r--r--app/views/projects/notes/_note.html.haml36
-rw-r--r--config/gitlab.teatro.yml7
-rw-r--r--config/gitlab.yml.example67
-rw-r--r--config/initializers/1_settings.rb24
-rw-r--r--config/initializers/6_rack_profiler.rb1
-rw-r--r--config/initializers/disable_email_interceptor.rb4
-rw-r--r--config/initializers/email_settings.rb5
-rw-r--r--config/initializers/smtp_settings.rb.sample22
-rw-r--r--db/migrate/20150425164646_gitlab_change_collation_for_tag_names.acts_as_taggable_on_engine.rb10
-rw-r--r--db/migrate/20150425164647_remove_duplicate_tags.rb17
-rw-r--r--db/migrate/20150425164648_add_missing_unique_indices.acts_as_taggable_on_engine.rb11
-rw-r--r--doc/markdown/markdown.md93
-rw-r--r--doc/operations/sidekiq_memory_killer.md2
-rw-r--r--doc/raketasks/backup_restore.md74
-rw-r--r--features/project/issues/issues.feature41
-rw-r--r--features/project/merge_requests.feature32
-rw-r--r--features/steps/project/issues/issues.rb8
-rw-r--r--features/steps/project/merge_requests.rb4
-rw-r--r--features/steps/shared/markdown.rb45
-rw-r--r--features/steps/shared/note.rb14
-rw-r--r--features/steps/shared/paths.rb20
-rw-r--r--lib/gitlab/markdown.rb36
-rw-r--r--lib/gitlab/markdown/table_of_contents_filter.rb2
-rw-r--r--lib/gitlab/sidekiq_middleware/memory_killer.rb11
-rw-r--r--lib/tasks/gitlab/check.rake2
-rw-r--r--lib/tasks/jasmine.rake12
-rw-r--r--spec/features/markdown_spec.rb15
-rw-r--r--spec/features/task_lists_spec.rb151
-rw-r--r--spec/fixtures/markdown.md.erb10
-rw-r--r--spec/helpers/gitlab_markdown_helper_spec.rb109
-rw-r--r--spec/javascripts/helpers/.gitkeep0
-rw-r--r--spec/javascripts/issue_spec.js.coffee36
-rw-r--r--spec/javascripts/merge_request_spec.js.coffee36
-rw-r--r--spec/javascripts/notes_spec.js.coffee30
-rw-r--r--spec/javascripts/support/jasmine.yml5
-rw-r--r--spec/mailers/notify_spec.rb6
-rw-r--r--spec/support/taskable_shared_examples.rb34
-rwxr-xr-x[-rw-r--r--]vendor/assets/javascripts/jasmine-fixture.js6
58 files changed, 706 insertions, 621 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 051870269f8..3206c625cd0 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -36,7 +36,7 @@ v 7.11.0 (unreleased)
- Show incompatible projects in Google Code import status (Stan Hu)
- Fix bug where commit data would not appear in some subdirectories (Stan Hu)
- Unescape branch names in compare commit (Stan Hu)
- -
+ - Task lists are now usable in comments, and will show up in Markdown previews.
- Fix bug where Slack service channel was not saved in admin template settings. (Stan Hu)
- Move snippets UI to fluid layout
- Improve UI for sidebar. Increase separation between navigation and content
@@ -48,6 +48,7 @@ v 7.11.0 (unreleased)
- Allow to use non-ASCII letters and dashes in project and namespace name. (Jakub Jirutka)
- Add footnotes support to Markdown (Guillaume Delbergue)
- Add current_sign_in_at to UserFull REST api.
+ - Make Sidekiq MemoryKiller shutdown signal configurable
v 7.10.2
- Fix CI links on MR page
diff --git a/Gemfile b/Gemfile
index ea62396d8a3..f950c5be154 100644
--- a/Gemfile
+++ b/Gemfile
@@ -87,20 +87,17 @@ gem "six"
# Seed data
gem "seed-fu"
-# Markup pipeline for GitLab
+# Markdown and HTML processing
gem 'html-pipeline', '~> 1.11.0'
-
-# Markdown to HTML
-gem "github-markup"
-
-# Required markup gems by github-markdown
-gem 'redcarpet', '~> 3.2.3'
+gem 'task_list', '~> 1.0.0', require: 'task_list/railtie'
+gem 'github-markup'
+gem 'redcarpet', '~> 3.2.3'
gem 'RedCloth'
-gem 'rdoc', '~>3.6'
-gem 'org-ruby', '= 0.9.12'
-gem 'creole', '~>0.3.6'
-gem 'wikicloth', '=0.8.1'
-gem 'asciidoctor', '= 0.1.4'
+gem 'rdoc', '~>3.6'
+gem 'org-ruby', '= 0.9.12'
+gem 'creole', '~>0.3.6'
+gem 'wikicloth', '=0.8.1'
+gem 'asciidoctor', '= 0.1.4'
# Diffs
gem 'diffy', '~> 3.0.3'
@@ -251,7 +248,6 @@ group :development, :test do
# PhantomJS driver for Capybara
gem 'poltergeist', '~> 1.5.1'
- gem 'jasmine', '~> 2.2.0'
gem 'jasmine-rails'
gem "spring", '~> 1.3.1'
diff --git a/Gemfile.lock b/Gemfile.lock
index 676a5197900..6f58c4f4fda 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -290,11 +290,6 @@ GEM
i18n (0.7.0)
ice_cube (0.11.1)
ice_nine (0.10.0)
- jasmine (2.2.0)
- jasmine-core (~> 2.2)
- phantomjs
- rack (>= 1.2.1)
- rake
jasmine-core (2.2.0)
jasmine-rails (0.10.8)
jasmine-core (>= 1.3, < 3.0)
@@ -597,6 +592,8 @@ GEM
stamp (0.5.0)
state_machine (1.2.0)
stringex (2.5.2)
+ task_list (1.0.2)
+ html-pipeline
temple (0.6.7)
term-ansicolor (1.2.2)
tins (~> 0.8)
@@ -724,7 +721,6 @@ DEPENDENCIES
hipchat (~> 1.5.0)
html-pipeline (~> 1.11.0)
httparty
- jasmine (~> 2.2.0)
jasmine-rails
jquery-atwho-rails (~> 1.0.0)
jquery-rails
@@ -789,6 +785,7 @@ DEPENDENCIES
spring-commands-spinach (= 1.0.0)
stamp
state_machine
+ task_list (~> 1.0.0)
test_after_commit
thin
tinder (~> 1.9.2)
diff --git a/app/assets/javascripts/behaviors/taskable.js.coffee b/app/assets/javascripts/behaviors/taskable.js.coffee
deleted file mode 100644
index ddce71c1886..00000000000
--- a/app/assets/javascripts/behaviors/taskable.js.coffee
+++ /dev/null
@@ -1,21 +0,0 @@
-window.updateTaskState = (taskableType) ->
- objType = taskableType.data
- isChecked = $(this).prop("checked")
- if $(this).is(":checked")
- stateEvent = "task_check"
- else
- stateEvent = "task_uncheck"
-
- taskableUrl = $("form.edit-" + objType).first().attr("action")
- taskableNum = taskableUrl.match(/\d+$/)
- taskNum = 0
- $("li.task-list-item input:checkbox").each( (index, e) =>
- if e == this
- taskNum = index + 1
- )
-
- $.ajax
- type: "PATCH"
- url: taskableUrl
- data: objType + "[state_event]=" + stateEvent +
- "&" + objType + "[task_num]=" + taskNum
diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee
index 4e2e6550eb2..86ad3d03bac 100644
--- a/app/assets/javascripts/issue.js.coffee
+++ b/app/assets/javascripts/issue.js.coffee
@@ -1,3 +1,7 @@
+#= require jquery
+#= require jquery.waitforimages
+#= require task_list
+
class @Issue
constructor: ->
$('.edit-issue.inline-update input[type="submit"]').hide()
@@ -6,11 +10,11 @@ class @Issue
$(".context .inline-update").on "change", "#issue_assignee_id", ->
$(this).submit()
- if $("a.btn-close").length
- $("li.task-list-item input:checkbox").prop("disabled", false)
+ # Prevent duplicate event bindings
+ @disableTaskList()
- $('.task-list-item input:checkbox').off('change')
- $('.task-list-item input:checkbox').change('issue', updateTaskState)
+ if $("a.btn-close").length
+ @initTaskList()
$('.issue-details').waitForImages ->
$('.issuable-affix').affix offset:
@@ -22,3 +26,22 @@ class @Issue
$(@).width($(@).outerWidth())
.on 'affixed-top.bs.affix affixed-bottom.bs.affix', ->
$(@).width('')
+
+ initTaskList: ->
+ $('.issue-details .js-task-list-container').taskList('enable')
+ $(document).on 'tasklist:changed', '.issue-details .js-task-list-container', @updateTaskList
+
+ disableTaskList: ->
+ $('.issue-details .js-task-list-container').taskList('disable')
+ $(document).off 'tasklist:changed', '.issue-details .js-task-list-container'
+
+ # TODO (rspeicher): Make the issue description inline-editable like a note so
+ # that we can re-use its form here
+ updateTaskList: ->
+ patchData = {}
+ patchData['issue'] = {'description': $('.js-task-list-field', this).val()}
+
+ $.ajax
+ type: 'PATCH'
+ url: $('form.js-issue-update').attr('action')
+ data: patchData
diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee
index ae5d088d593..7c1e2b822d7 100644
--- a/app/assets/javascripts/merge_request.js.coffee
+++ b/app/assets/javascripts/merge_request.js.coffee
@@ -1,3 +1,7 @@
+#= require jquery
+#= require bootstrap
+#= require task_list
+
class @MergeRequest
constructor: (@opts) ->
@initContextWidget()
@@ -17,8 +21,11 @@ class @MergeRequest
disableButtonIfEmptyField '#commit_message', '.accept_merge_request'
+ # Prevent duplicate event bindings
+ @disableTaskList()
+
if $("a.btn-close").length
- $("li.task-list-item input:checkbox").prop("disabled", false)
+ @initTaskList()
$('.merge-request-details').waitForImages ->
$('.issuable-affix').affix offset:
@@ -77,9 +84,6 @@ class @MergeRequest
this.$('.remove_source_branch_in_progress').hide()
this.$('.remove_source_branch_widget.failed').show()
- $('.task-list-item input:checkbox').off('change')
- $('.task-list-item input:checkbox').change('merge_request', updateTaskState)
-
activateTab: (action) ->
this.$('.merge-request-tabs li').removeClass 'active'
this.$('.tab-content').hide()
@@ -156,3 +160,22 @@ class @MergeRequest
else
setTimeout(merge_request.mergeInProgress, 3000)
dataType: 'json'
+
+ initTaskList: ->
+ $('.merge-request-details .js-task-list-container').taskList('enable')
+ $(document).on 'tasklist:changed', '.merge-request-details .js-task-list-container', @updateTaskList
+
+ disableTaskList: ->
+ $('.merge-request-details .js-task-list-container').taskList('disable')
+ $(document).off 'tasklist:changed', '.merge-request-details .js-task-list-container'
+
+ # TODO (rspeicher): Make the merge request description inline-editable like a
+ # note so that we can re-use its form here
+ updateTaskList: ->
+ patchData = {}
+ patchData['merge_request'] = {'description': $('.js-task-list-field', this).val()}
+
+ $.ajax
+ type: 'PATCH'
+ url: $('form.js-merge-request-update').attr('action')
+ data: patchData
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index 6dfe10f0006..c25b1ddb066 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -1,3 +1,12 @@
+#= require jquery
+#= require autosave
+#= require bootstrap
+#= require dropzone
+#= require dropzone_input
+#= require gfm_auto_complete
+#= require jquery.atwho
+#= require task_list
+
class @Notes
@interval: null
@@ -11,6 +20,7 @@ class @Notes
@setupMainTargetNoteForm()
@cleanBinding()
@addBinding()
+ @initTaskList()
addBinding: ->
# add note to UI after creation
@@ -81,6 +91,9 @@ class @Notes
$(document).off "click", ".js-note-target-reopen"
$(document).off "click", ".js-note-target-close"
+ $('.note .js-task-list-container').taskList('disable')
+ $(document).off 'tasklist:changed', '.note .js-task-list-container'
+
initRefresh: ->
clearInterval(Notes.interval)
Notes.interval = setInterval =>
@@ -114,6 +127,7 @@ class @Notes
if @isNewNote(note)
@note_ids.push(note.id)
$('ul.main-notes-list').append(note.html)
+ @initTaskList()
###
Check if note does not exists on page
@@ -268,6 +282,8 @@ class @Notes
note_li.replaceWith(note.html)
note_li.find('.note-edit-form').hide()
note_li.find('.note-body > .note-text').show()
+ note_li.find('js-task-list-container').taskList('enable')
+ @enableTaskList()
###
Called in response to clicking the edit note link
@@ -479,3 +495,13 @@ class @Notes
else
form.find('.js-note-target-reopen').text('Reopen')
form.find('.js-note-target-close').text('Close')
+
+ initTaskList: ->
+ @enableTaskList()
+ $(document).on 'tasklist:changed', '.note .js-task-list-container', @updateTaskList
+
+ enableTaskList: ->
+ $('.note .js-task-list-container').taskList('enable')
+
+ updateTaskList: ->
+ $('form', this).submit()
diff --git a/app/assets/stylesheets/generic/typography.scss b/app/assets/stylesheets/generic/typography.scss
index 36a9a540747..e5590897947 100644
--- a/app/assets/stylesheets/generic/typography.scss
+++ b/app/assets/stylesheets/generic/typography.scss
@@ -37,7 +37,9 @@ pre {
position: relative;
a.anchor {
- display: none;
+ // Setting `display: none` would prevent the anchor being scrolled to, so
+ // instead we set the height to 0 and it gets updated on hover.
+ height: 0;
}
&:hover > a.anchor {
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 97b19deb3ed..589a43c4264 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -62,6 +62,16 @@ ul.notes {
word-wrap: break-word;
@include md-typography;
+ // Reduce left padding of first ul element
+ ul.task-list:first-child {
+ padding-left: 10px;
+
+ // sub-lists should be padded normally
+ ul {
+ padding-left: 20px;
+ }
+ }
+
hr {
margin: 10px 0;
}
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index ab44fa6ee43..271b53aa2b6 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -9,6 +9,10 @@ module NotesHelper
hidden_field_tag(:target_id, note.noteable.id)
end
+ def note_editable?(note)
+ note.editable? && can?(current_user, :admin_note, note)
+ end
+
def link_to_commit_diff_line_note(note)
if note.for_commit_diff_line?
link_to(
diff --git a/app/mailers/devise_mailer.rb b/app/mailers/devise_mailer.rb
index e4b65c19b3f..b616add283a 100644
--- a/app/mailers/devise_mailer.rb
+++ b/app/mailers/devise_mailer.rb
@@ -1,4 +1,4 @@
class DeviseMailer < Devise::Mailer
- default from: "#{Gitlab.config.outgoing_emails.display_name} <#{Gitlab.config.outgoing_emails.from}>"
- default reply_to: Gitlab.config.outgoing_emails.reply_to
+ default from: "#{Gitlab.config.gitlab.email_display_name} <#{Gitlab.config.gitlab.email_from}>"
+ default reply_to: Gitlab.config.gitlab.email_reply_to
end
diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb
index c6bc716930d..9cb7077e59d 100644
--- a/app/mailers/emails/projects.rb
+++ b/app/mailers/emails/projects.rb
@@ -129,7 +129,7 @@ module Emails
if send_from_committer_email && can_send_from_user_email?(@author)
@author.email
else
- Gitlab.config.outgoing_emails.reply_to
+ Gitlab.config.gitlab.email_reply_to
end
mail(from: sender(author_id, send_from_committer_email),
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index a9800f7f103..79fb48b00d3 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -17,7 +17,7 @@ class Notify < ActionMailer::Base
helper_method :current_user, :can?
default from: Proc.new { default_sender_address.format }
- default reply_to: Gitlab.config.outgoing_emails.reply_to
+ default reply_to: Gitlab.config.gitlab.email_reply_to
# Just send email with 2 seconds delay
def self.delay
@@ -50,9 +50,9 @@ class Notify < ActionMailer::Base
# The default email address to send emails from
def default_sender_address
- Mail::Address.new(Gitlab.config.outgoing_emails.from).tap do |address|
- address.display_name = Gitlab.config.outgoing_emails.display_name
- end
+ address = Mail::Address.new(Gitlab.config.gitlab.email_from)
+ address.display_name = Gitlab.config.gitlab.email_display_name
+ address
end
def can_send_from_user_email?(sender)
diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb
index bbb3b301a9f..33b4814d7ec 100644
--- a/app/models/concerns/taskable.rb
+++ b/app/models/concerns/taskable.rb
@@ -1,51 +1,36 @@
+require 'task_list'
+
# Contains functionality for objects that can have task lists in their
# descriptions. Task list items can be added with Markdown like "* [x] Fix
# bugs".
#
# Used by MergeRequest and Issue
module Taskable
- TASK_PATTERN_MD = /^(?<bullet> *[*-] *)\[(?<checked>[ xX])\]/.freeze
- TASK_PATTERN_HTML = /^<li>(?<p_tag>\s*<p>)?\[(?<checked>[ xX])\]/.freeze
-
- # Change the state of a task list item for this Taskable. Edit the object's
- # description by finding the nth task item and changing its checkbox
- # placeholder to "[x]" if +checked+ is true, or "[ ]" if it's false.
- # Note: task numbering starts with 1
- def update_nth_task(n, checked)
- index = 0
- check_char = checked ? 'x' : ' '
+ # Called by `TaskList::Summary`
+ def task_list_items
+ return [] if description.blank?
- # Do this instead of using #gsub! so that ActiveRecord detects that a field
- # has changed.
- self.description = self.description.gsub(TASK_PATTERN_MD) do |match|
- index += 1
- case index
- when n then "#{$LAST_MATCH_INFO[:bullet]}[#{check_char}]"
- else match
- end
+ @task_list_items ||= description.scan(TaskList::Filter::ItemPattern).collect do |item|
+ # ItemPattern strips out the hyphen, but Item requires it. Rabble rabble.
+ TaskList::Item.new("- #{item}")
end
+ end
- save
+ def tasks
+ @tasks ||= TaskList.new(self)
end
# Return true if this object's description has any task list items.
def tasks?
- description && description.match(TASK_PATTERN_MD)
+ tasks.summary.items?
end
# Return a string that describes the current state of this Taskable's task
- # list items, e.g. "20 tasks (12 done, 8 unfinished)"
+ # list items, e.g. "20 tasks (12 completed, 8 remaining)"
def task_status
- return nil unless description
-
- num_tasks = 0
- num_done = 0
-
- description.scan(TASK_PATTERN_MD) do
- num_tasks += 1
- num_done += 1 unless $LAST_MATCH_INFO[:checked] == ' '
- end
+ return '' if description.blank?
- "#{num_tasks} tasks (#{num_done} done, #{num_tasks - num_done} unfinished)"
+ sum = tasks.summary
+ "#{sum.item_count} tasks (#{sum.complete_count} completed, #{sum.incomplete_count} remaining)"
end
end
diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml
index ceca2653016..2016f5c709c 100644
--- a/app/views/projects/issues/_discussion.html.haml
+++ b/app/views/projects/issues/_discussion.html.haml
@@ -1,9 +1,9 @@
- content_for :note_actions do
- if can?(current_user, :modify_issue, @issue)
- if @issue.closed?
- = link_to 'Reopen Issue', issue_path(@issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-grouped btn-reopen js-note-target-reopen", title: 'Reopen Issue'
+ = link_to 'Reopen Issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true), method: :put, class: 'btn btn-grouped btn-reopen js-note-target-reopen', title: 'Reopen Issue'
- else
- = link_to 'Close Issue', issue_path(@issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close js-note-target-close", title: "Close Issue"
+ = link_to 'Close Issue', issue_path(@issue, issue: {state_event: :close}, status_only: true), method: :put, class: 'btn btn-grouped btn-close js-note-target-close', title: 'Close Issue'
= render 'shared/show_aside'
@@ -15,11 +15,11 @@
%span= pluralize(@issue.participants(current_user).count, 'participant')
- @issue.participants(current_user).each do |participant|
= link_to_member(@project, participant, name: false, size: 24)
- .voting_notes#notes= render "projects/notes/notes_with_form"
+ .voting_notes#notes= render 'projects/notes/notes_with_form'
%aside.col-md-3
.issuable-affix
.clearfix
- %span.slead.has_tooltip{:"data-original-title" => 'Cross-project reference'}
+ %span.slead.has_tooltip{title: 'Cross-project reference'}
= cross_project_reference(@project, @issue)
%hr
.context
diff --git a/app/views/projects/issues/_issue_context.html.haml b/app/views/projects/issues/_issue_context.html.haml
index 7cadb9983fa..323f5c84a85 100644
--- a/app/views/projects/issues/_issue_context.html.haml
+++ b/app/views/projects/issues/_issue_context.html.haml
@@ -1,4 +1,4 @@
-= form_for [@project.namespace.becomes(Namespace), @project, @issue], remote: true, html: {class: 'edit-issue inline-update'} do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @issue], remote: true, html: {class: 'edit-issue inline-update js-issue-update'} do |f|
%div.prepend-top-20
.issuable-context-title
%label
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 81478dfe568..ee1b2a08bc4 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -13,17 +13,17 @@
.pull-right
- if can?(current_user, :write_issue, @project)
- = link_to new_namespace_project_issue_path(@project.namespace, @project), class: "btn btn-grouped new-issue-link", title: "New Issue", id: "new_issue_link" do
- %i.fa.fa-plus
+ = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'btn btn-grouped new-issue-link', title: 'New Issue', id: 'new_issue_link' do
+ = icon('plus')
New Issue
- if can?(current_user, :modify_issue, @issue)
- if @issue.closed?
- = link_to 'Reopen', issue_path(@issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-grouped btn-reopen"
+ = link_to 'Reopen', issue_path(@issue, issue: {state_event: :reopen}, status_only: true), method: :put, class: 'btn btn-grouped btn-reopen'
- else
- = link_to 'Close', issue_path(@issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close", title: "Close Issue"
+ = link_to 'Close', issue_path(@issue, issue: {state_event: :close}, status_only: true), method: :put, class: 'btn btn-grouped btn-close', title: 'Close Issue'
- = link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: "btn btn-grouped issuable-edit" do
- %i.fa.fa-pencil-square-o
+ = link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'btn btn-grouped issuable-edit' do
+ = icon('pencil-square-o')
Edit
%hr
@@ -31,11 +31,13 @@
= gfm escape_once(@issue.title)
%div
- if @issue.description.present?
- .description
+ .description{class: can?(current_user, :modify_issue, @issue) ? 'js-task-list-container' : ''}
.wiki
= preserve do
- = markdown(@issue.description, parse_tasks: true)
+ = markdown(@issue.description)
+ %textarea.hidden.js-task-list-field
+ = @issue.description
%hr
.issue-discussion
- = render "projects/issues/discussion"
+ = render 'projects/issues/discussion'
diff --git a/app/views/projects/merge_requests/show/_context.html.haml b/app/views/projects/merge_requests/show/_context.html.haml
index eb80391ebcd..a5a821c1847 100644
--- a/app/views/projects/merge_requests/show/_context.html.haml
+++ b/app/views/projects/merge_requests/show/_context.html.haml
@@ -1,4 +1,4 @@
-= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], remote: true, html: {class: 'edit-merge_request inline-update'} do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], remote: true, html: {class: 'edit-merge_request inline-update js-merge-request-update'} do |f|
%div.prepend-top-20
.issuable-context-title
%label
@@ -19,13 +19,13 @@
%span.back-to-milestone
= link_to namespace_project_milestone_path(@project.namespace, @project, @merge_request.milestone) do
%strong
- %i.fa.fa-clock-o
+ = icon('clock-o')
= @merge_request.milestone.title
- else
none
.issuable-context-selectbox
- if can?(current_user, :modify_merge_request, @merge_request)
- = f.select(:milestone_id, milestone_options(@merge_request), { include_blank: "Select milestone" }, {class: 'select2 select2-compact js-select2 js-milestone'})
+ = f.select(:milestone_id, milestone_options(@merge_request), { include_blank: 'Select milestone' }, {class: 'select2 select2-compact js-select2 js-milestone'})
= hidden_field_tag :merge_request_context
= f.submit class: 'btn'
@@ -35,13 +35,13 @@
%label
Subscription:
%button.btn.btn-block.subscribe-button{:type => 'button'}
- %i.fa.fa-eye
- %span= @merge_request.subscribed?(current_user) ? "Unsubscribe" : "Subscribe"
- - subscribtion_status = @merge_request.subscribed?(current_user) ? "subscribed" : "unsubscribed"
- .subscription-status{"data-status" => subscribtion_status}
- .description-block.unsubscribed{class: ( "hidden" if @merge_request.subscribed?(current_user) )}
+ = icon('eye')
+ %span= @merge_request.subscribed?(current_user) ? 'Unsubscribe' : 'Subscribe'
+ - subscribtion_status = @merge_request.subscribed?(current_user) ? 'subscribed' : 'unsubscribed'
+ .subscription-status{data: {status: subscribtion_status}}
+ .description-block.unsubscribed{class: ( 'hidden' if @merge_request.subscribed?(current_user) )}
You're not receiving notifications from this thread.
- .description-block.subscribed{class: ( "hidden" unless @merge_request.subscribed?(current_user) )}
+ .description-block.subscribed{class: ( 'hidden' unless @merge_request.subscribed?(current_user) )}
You're receiving notifications because you're subscribed to this thread.
:coffeescript
diff --git a/app/views/projects/merge_requests/show/_mr_box.html.haml b/app/views/projects/merge_requests/show/_mr_box.html.haml
index ada9ae58b8f..b3470ba37d6 100644
--- a/app/views/projects/merge_requests/show/_mr_box.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_box.html.haml
@@ -3,7 +3,9 @@
%div
- if @merge_request.description.present?
- .description
+ .description{class: can?(current_user, :modify_merge_request, @merge_request) ? 'js-task-list-container' : ''}
.wiki
= preserve do
- = markdown(@merge_request.description, parse_tasks: true)
+ = markdown(@merge_request.description)
+ %textarea.hidden.js-task-list-field
+ = @merge_request.description
diff --git a/app/views/projects/notes/_edit_form.html.haml b/app/views/projects/notes/_edit_form.html.haml
index acb3991d294..a663950f031 100644
--- a/app/views/projects/notes/_edit_form.html.haml
+++ b/app/views/projects/notes/_edit_form.html.haml
@@ -1,15 +1,14 @@
.note-edit-form
= form_for note, url: namespace_project_note_path(@project.namespace, @project, note), method: :put, remote: true, authenticity_token: true do |f|
= note_target_fields(note)
- = render layout: 'projects/md_preview', locals: { preview_class: "note-text" } do
- = render 'projects/zen', f: f, attr: :note,
- classes: 'note_text js-note-text'
+ = render layout: 'projects/md_preview', locals: { preview_class: 'note-text' } do
+ = render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text js-task-list-field'
.comment-hints.clearfix
- .pull-left Comments are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"),{ target: '_blank', tabindex: -1 }}
- .pull-right Attach files by dragging &amp; dropping or #{link_to "selecting them", '#', class: 'markdown-selector', tabindex: -1 }.
+ .pull-left Comments are parsed with #{link_to 'GitLab Flavored Markdown', help_page_path('markdown', 'markdown'),{ target: '_blank', tabindex: -1 }}
+ .pull-right Attach files by dragging &amp; dropping or #{link_to 'selecting them', '#', class: 'markdown-selector', tabindex: -1 }.
.note-form-actions
.buttons
- = f.submit 'Save Comment', class: "btn btn-primary btn-save btn-grouped js-comment-button"
- = link_to 'Cancel', "#", class: "btn btn-cancel note-edit-cancel" \ No newline at end of file
+ = f.submit 'Save Comment', class: 'btn btn-primary btn-save btn-grouped js-comment-button'
+ = link_to 'Cancel', '#', class: 'btn btn-cancel note-edit-cancel'
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index 0728f8fa42b..4d26b52df01 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -2,28 +2,28 @@
.timeline-entry-inner
.timeline-icon
- if note.system
- %span.fa.fa-circle
+ %span= icon('circle')
- else
= link_to user_path(note.author) do
- = image_tag avatar_icon(note.author_email), class: "avatar s40", alt: ''
+ = image_tag avatar_icon(note.author_email), class: 'avatar s40', alt: ''
.timeline-content
.note-header
.note-actions
= link_to "##{dom_id(note)}", name: dom_id(note) do
- %i.fa.fa-link
+ = icon('link')
Link here
&nbsp;
- - if can?(current_user, :admin_note, note) && note.editable?
- = link_to "#", title: "Edit comment", class: "js-note-edit" do
- %i.fa.fa-pencil-square-o
+ - if note_editable?(note)
+ = link_to '#', title: 'Edit comment', class: 'js-note-edit' do
+ = icon('pencil-square-o')
Edit
&nbsp;
- = link_to namespace_project_note_path(@project.namespace, @project, note), title: "Remove comment", method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: "danger js-note-delete" do
- %i.fa.fa-trash-o.cred
+ = link_to namespace_project_note_path(@project.namespace, @project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'danger js-note-delete' do
+ = icon('trash-o', class: 'cred')
Remove
- if note.system
= link_to user_path(note.author) do
- = image_tag avatar_icon(note.author_email), class: "avatar s16", alt: ''
+ = image_tag avatar_icon(note.author_email), class: 'avatar s16', alt: ''
= link_to_member(@project, note.author, avatar: false)
%span.author-username
= '@' + note.author.username
@@ -33,24 +33,24 @@
- if note.superceded?(@notes)
- if note.upvote?
%span.vote.upvote.label.label-gray.strikethrough
- %i.fa.fa-thumbs-up
+ = icon('thumbs-up')
\+1
- if note.downvote?
%span.vote.downvote.label.label-gray.strikethrough
- %i.fa.fa-thumbs-down
+ = icon('thumbs-down')
\-1
- else
- if note.upvote?
%span.vote.upvote.label.label-success
- %i.fa.fa-thumbs-up
+ = icon('thumbs-up')
\+1
- if note.downvote?
%span.vote.downvote.label.label-danger
- %i.fa.fa-thumbs-down
+ = icon('thumbs-down')
\-1
- .note-body
+ .note-body{class: note_editable?(note) ? 'js-task-list-container' : ''}
.note-text
= preserve do
= markdown(note.note, {no_header_anchors: true})
@@ -62,10 +62,10 @@
= link_to note.attachment.url, target: '_blank' do
= image_tag note.attachment.url, class: 'note-image-attach'
.attachment
- = link_to note.attachment.url, target: "_blank" do
- %i.fa.fa-paperclip
+ = link_to note.attachment.url, target: '_blank' do
+ = icon('paperclip')
= note.attachment_identifier
= link_to delete_attachment_namespace_project_note_path(@project.namespace, @project, note),
- title: "Delete this attachment", method: :delete, remote: true, data: { confirm: 'Are you sure you want to remove the attachment?' }, class: "danger js-note-attachment-delete" do
- %i.fa.fa-trash-o.cred
+ title: 'Delete this attachment', method: :delete, remote: true, data: { confirm: 'Are you sure you want to remove the attachment?' }, class: 'danger js-note-attachment-delete' do
+ = icon('trash-o', class: 'cred')
.clear
diff --git a/config/gitlab.teatro.yml b/config/gitlab.teatro.yml
index 07133503843..f0656400beb 100644
--- a/config/gitlab.teatro.yml
+++ b/config/gitlab.teatro.yml
@@ -7,6 +7,8 @@ production: &base
user: root
+ email_from: example@example.com
+
support_email: support@example.com
default_projects_features:
@@ -17,9 +19,6 @@ production: &base
snippets: false
visibility_level: "private" # can be "private" | "internal" | "public"
- outgoing_emails:
- from: example@example.com
-
issues_tracker:
gravatar:
@@ -76,8 +75,6 @@ test:
gitlab:
host: localhost
port: 80
- outgoing_emails:
- delivery_method: :test
issues_tracker:
redmine:
title: "Redmine"
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index f0b65e1e905..bd2081688d1 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -10,7 +10,7 @@
# How to use:
# 1. Copy file as gitlab.yml
# 2. Update gitlab -> host with your fully qualified domain name
-# 3. Update outgoing_emails -> from
+# 3. Update gitlab -> email_from
# 4. If you installed Git from source, change git -> bin_path to /usr/local/bin/git
# IMPORTANT: If Git was installed in a different location use that instead.
# You can check with `which git`. If a wrong path of Git is specified, it will
@@ -46,6 +46,16 @@ production: &base
# To see all available zones, run `bundle exec rake time:zones:all RAILS_ENV=production`
# time_zone: 'UTC'
+ ## Email settings
+ # Uncomment and set to false if you need to disable email sending from GitLab (default: true)
+ # email_enabled: true
+ # Email address used in the "From" field in mails sent by GitLab
+ email_from: example@example.com
+ email_display_name: GitLab
+ email_reply_to: noreply@example.com
+
+ # Email server smtp settings are in config/initializers/smtp_settings.rb.sample
+
# default_can_create_group: false # default: true
# username_changing_enabled: false # default: true - User can change her username/namespace
## Default theme
@@ -79,57 +89,6 @@ production: &base
# The default is 'tmp/repositories' relative to the root of the Rails app.
# repository_downloads_path: tmp/repositories
- ## Settings for sending emails
- outgoing_emails:
-
- # Set to false if you need to disable email sending from GitLab (default: true).
- enabled: true
-
- # Email address and name used in the "From" field in mails sent by GitLab.
- from: example@example.com
- display_name: GitLab
-
- # Email address used in "Reply-To" field in mails sent by GitLab.
- reply_to: noreply@example.com
-
- # Defines a delivery method. Possible values are:
- # :sendmail - send emails using sendmail command (default).
- # :smtp - send emails using SMTP server.
- # :letter_opener - open sent emails in browser.
- # :test - save emails to ActionMailer::Base.deliveries array.
- delivery_method: :sendmail
-
- # Settings for the :sendmail delivery method.
- sendmail_settings:
- # The location of the sendmail executable (default: /usr/sbin/sendmail).
- location: /usr/sbin/sendmail
-
- # The command line arguments to be passed to sendmail (default: -i -t).
- arguments: '-i -t'
-
- # Settings for the :smtp delivery method.
- smtp_settings:
- # Hostname of the SMTP server used to send emails (default: localhost).
- address: localhost
-
- # Port of the SMTP server used to send emails (default: 25).
- port: 25
-
- # If your mail server requires authentication, you need to specify the
- # authentication type here. Possible values are:
- # :plain - send the password in the clear text.
- # :login - send password Base64 encoded.
- # :cram_md5 - combines a Challenge/Response mechanism to exchange information and
- # a cryptographic Message Digest 5 algorithm to hash important information.
- # authentication: :plain
- # user_name: 'gitlab'
- # password: '123456'
-
- # Detects if STARTTLS is enabled in your SMTP server and starts to use it.
- # Set this to false if there is a problem with your server certificate
- # that you cannot resolve. It should be true for Gmail SMTP.
- # enable_starttls_auto: true
-
## Gravatar
## For Libravatar see: http://doc.gitlab.com/ce/customization/libravatar.html
gravatar:
@@ -336,8 +295,6 @@ production: &base
development:
<<: *base
- outgoing_emails:
- delivery_method: :letter_opener
test:
<<: *base
@@ -346,8 +303,6 @@ test:
gitlab:
host: localhost
port: 80
- outgoing_emails:
- delivery_method: :test
# When you run tests we clone and setup gitlab-shell
# In order to setup it correctly you need to specify
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index ce0023e0795..e5ac66a2323 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -105,6 +105,10 @@ Settings.gitlab['https'] = false if Settings.gitlab['https'].nil?
Settings.gitlab['port'] ||= Settings.gitlab.https ? 443 : 80
Settings.gitlab['relative_url_root'] ||= ENV['RAILS_RELATIVE_URL_ROOT'] || ''
Settings.gitlab['protocol'] ||= Settings.gitlab.https ? "https" : "http"
+Settings.gitlab['email_enabled'] ||= true if Settings.gitlab['email_enabled'].nil?
+Settings.gitlab['email_from'] ||= "gitlab@#{Settings.gitlab.host}"
+Settings.gitlab['email_display_name'] ||= "GitLab"
+Settings.gitlab['email_reply_to'] ||= "noreply@#{Settings.gitlab.host}"
Settings.gitlab['url'] ||= Settings.send(:build_gitlab_url)
Settings.gitlab['user'] ||= 'git'
Settings.gitlab['user_home'] ||= begin
@@ -131,26 +135,6 @@ Settings.gitlab['repository_downloads_path'] = File.absolute_path(Settings.gitla
Settings.gitlab['restricted_signup_domains'] ||= []
#
-# Outgoing emails
-#
-Settings['outgoing_emails'] ||= Settingslogic.new({})
-Settings['outgoing_emails'].tap do |opts|
- # For backward compatibility. TODO remove in next major release.
- opts['enabled'] ||= Settings.gitlab['email_enabled']
- opts['from'] ||= Settings.gitlab['email_from']
- opts['display_name'] ||= Settings.gitlab['display_name']
- opts['reply_to'] ||= Settings.gitlab['email_reply_to']
-
- opts['enabled'] ||= opts['enabled'].nil?
- opts['display_name'] ||= "GitLab"
- opts['from'] ||= "gitlab@#{Settings.gitlab.host}"
- opts['reply_to'] ||= "noreply@#{Settings.gitlab.host}"
- opts['delivery_method'] ||= :sendmail
- opts['sendmail_settings'] ||= {}
- opts['smtp_settings'] ||= {}
-end
-
-#
# Gravatar
#
Settings['gravatar'] ||= Settingslogic.new({})
diff --git a/config/initializers/6_rack_profiler.rb b/config/initializers/6_rack_profiler.rb
index bdad6202b11..38a5fa98dc2 100644
--- a/config/initializers/6_rack_profiler.rb
+++ b/config/initializers/6_rack_profiler.rb
@@ -5,4 +5,5 @@ if Rails.env.development?
Rack::MiniProfilerRails.initialize!(Rails.application)
Rack::MiniProfiler.config.position = 'right'
Rack::MiniProfiler.config.start_hidden = true
+ Rack::MiniProfiler.config.skip_paths << '/specs'
end
diff --git a/config/initializers/disable_email_interceptor.rb b/config/initializers/disable_email_interceptor.rb
index 122a5829916..c76a6b8b19f 100644
--- a/config/initializers/disable_email_interceptor.rb
+++ b/config/initializers/disable_email_interceptor.rb
@@ -1,4 +1,2 @@
# Interceptor in lib/disable_email_interceptor.rb
-unless Gitlab.config.outgoing_emails.enabled
- ActionMailer::Base.register_interceptor(DisableEmailInterceptor)
-end
+ActionMailer::Base.register_interceptor(DisableEmailInterceptor) unless Gitlab.config.gitlab.email_enabled
diff --git a/config/initializers/email_settings.rb b/config/initializers/email_settings.rb
deleted file mode 100644
index 99cb09ebfd0..00000000000
--- a/config/initializers/email_settings.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-Gitlab.config.outgoing_emails.tap do |c|
- Gitlab::Application.config.action_mailer.delivery_method = c.delivery_method
- ActionMailer::Base.smtp_settings = c.smtp_settings.symbolize_keys
- ActionMailer::Base.sendmail_settings = c.sendmail_settings.symbolize_keys
-end
diff --git a/config/initializers/smtp_settings.rb.sample b/config/initializers/smtp_settings.rb.sample
new file mode 100644
index 00000000000..f0fe2fdfa43
--- /dev/null
+++ b/config/initializers/smtp_settings.rb.sample
@@ -0,0 +1,22 @@
+# To enable smtp email delivery for your GitLab instance do the following:
+# 1. Rename this file to smtp_settings.rb
+# 2. Edit settings inside this file
+# 3. Restart GitLab instance
+#
+# For full list of options and their values see http://api.rubyonrails.org/classes/ActionMailer/Base.html
+#
+
+if Rails.env.production?
+ Gitlab::Application.config.action_mailer.delivery_method = :smtp
+
+ ActionMailer::Base.smtp_settings = {
+ address: "email.server.com",
+ port: 456,
+ user_name: "smtp",
+ password: "123456",
+ domain: "gitlab.company.com",
+ authentication: :login,
+ enable_starttls_auto: true,
+ openssl_verify_mode: 'peer' # See ActionMailer documentation for other possible options
+ }
+end
diff --git a/db/migrate/20150425164646_gitlab_change_collation_for_tag_names.acts_as_taggable_on_engine.rb b/db/migrate/20150425164646_gitlab_change_collation_for_tag_names.acts_as_taggable_on_engine.rb
new file mode 100644
index 00000000000..281c88d2a7d
--- /dev/null
+++ b/db/migrate/20150425164646_gitlab_change_collation_for_tag_names.acts_as_taggable_on_engine.rb
@@ -0,0 +1,10 @@
+# This migration is a duplicate of 20150425164651_change_collation_for_tag_names.acts_as_taggable_on_engine.rb
+# It shold be applied before the index additions to ensure that `name` is case sensitive.
+
+class GitlabChangeCollationForTagNames < ActiveRecord::Migration
+ def up
+ if ActsAsTaggableOn::Utils.using_mysql?
+ execute("ALTER TABLE tags MODIFY name varchar(255) CHARACTER SET utf8 COLLATE utf8_bin;")
+ end
+ end
+end
diff --git a/db/migrate/20150425164647_remove_duplicate_tags.rb b/db/migrate/20150425164647_remove_duplicate_tags.rb
new file mode 100644
index 00000000000..13e5038db9c
--- /dev/null
+++ b/db/migrate/20150425164647_remove_duplicate_tags.rb
@@ -0,0 +1,17 @@
+class RemoveDuplicateTags < ActiveRecord::Migration
+ def up
+ select_all("SELECT name, COUNT(id) as cnt FROM tags GROUP BY name HAVING COUNT(id) > 1").each do |tag|
+ tag_name = quote_string(tag["name"])
+ duplicate_ids = select_all("SELECT id FROM tags WHERE name = '#{tag_name}'").map{|tag| tag["id"]}
+ origin_tag_id = duplicate_ids.first
+ duplicate_ids.delete origin_tag_id
+
+ execute("UPDATE taggings SET tag_id = #{origin_tag_id} WHERE tag_id IN(#{duplicate_ids.join(",")})")
+ execute("DELETE FROM tags WHERE id IN(#{duplicate_ids.join(",")})")
+ end
+ end
+
+ def down
+
+ end
+end
diff --git a/db/migrate/20150425164648_add_missing_unique_indices.acts_as_taggable_on_engine.rb b/db/migrate/20150425164648_add_missing_unique_indices.acts_as_taggable_on_engine.rb
index 4ca676f6c72..c1b78681519 100644
--- a/db/migrate/20150425164648_add_missing_unique_indices.acts_as_taggable_on_engine.rb
+++ b/db/migrate/20150425164648_add_missing_unique_indices.acts_as_taggable_on_engine.rb
@@ -3,8 +3,15 @@ class AddMissingUniqueIndices < ActiveRecord::Migration
def self.up
add_index :tags, :name, unique: true
- remove_index :taggings, :tag_id
- remove_index :taggings, [:taggable_id, :taggable_type, :context]
+ # pre-GitLab v6.7.0 may not have these indices since there were no
+ # migrations for them
+ if index_exists?(:taggings, :tag_id)
+ remove_index :taggings, :tag_id
+ end
+
+ if index_exists?(:taggings, [:taggable_id, :taggable_type, :context])
+ remove_index :taggings, [:taggable_id, :taggable_type, :context]
+ end
add_index :taggings,
[:tag_id, :taggable_id, :taggable_type, :context, :tagger_id, :tagger_type],
unique: true, name: 'taggings_idx'
diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md
index 8ec5a20035f..e95ddbb7578 100644
--- a/doc/markdown/markdown.md
+++ b/doc/markdown/markdown.md
@@ -172,7 +172,7 @@ GFM will turn that reference into a link so you can navigate between them easily
GFM will recognize the following:
| input | references |
-|-----------------------:|:---------------------------|
+|:-----------------------|:---------------------------|
| `@user_name` | specific user |
| `@group_name` | specific group |
| `@all` | entire team |
@@ -189,7 +189,7 @@ GFM will recognize the following:
GFM also recognizes certain cross-project references:
| input | references |
-|----------------------------------------:|:------------------------|
+|:----------------------------------------|:------------------------|
| `namespace/project#123` | issue |
| `namespace/project!123` | merge request |
| `namespace/project$123` | snippet |
@@ -198,15 +198,23 @@ GFM also recognizes certain cross-project references:
## Task Lists
-You can add task lists to merge request and issue descriptions to keep track of to-do items. To create a task, add an unordered list to the description in an issue or merge request, formatted like so:
+You can add task lists to issues, merge requests and comments. To create a task list, add a specially-formatted Markdown list, like so:
```no-highlight
-* [x] Completed task
-* [ ] Unfinished task
- * [x] Nested task
+- [x] Completed task
+- [ ] Incomplete task
+ - [ ] Sub-task 1
+ - [x] Sub-task 2
+ - [ ] Sub-task 3
```
-Task lists can only be created in descriptions, not in titles or comments. Task item state can be managed by editing the description's Markdown or by clicking the rendered checkboxes.
+- [x] Completed task
+- [ ] Incomplete task
+ - [ ] Sub-task 1
+ - [x] Sub-task 2
+ - [ ] Sub-task 3
+
+Task lists can only be created in descriptions, not in titles. Task item state can be managed by editing the description's Markdown or by toggling the rendered check boxes.
# Standard Markdown
@@ -246,51 +254,38 @@ Alt-H2
### Header IDs and links
-All markdown rendered headers automatically get IDs, except for comments.
+All Markdown-rendered headers automatically get IDs, except in comments.
On hover a link to those IDs becomes visible to make it easier to copy the link to the header to give it to someone else.
The IDs are generated from the content of the header according to the following rules:
-1. remove the heading hashes `#` and process the rest of the line as it would be processed if it were not a header
-2. from the result, remove all HTML tags, but keep their inner content
-3. convert all characters to lowercase
-4. convert all characters except `[a-z0-9_-]` into hyphens `-`
-5. transform multiple adjacent hyphens into a single hyphen
-6. remove trailing and heading hyphens
+1. All text is converted to lowercase
+1. All non-word text (e.g., punctuation, HTML) is removed
+1. All spaces are converted to hyphens
+1. Two or more hyphens in a row are converted to one
+1. If a header with the same ID has already been generated, a unique
+ incrementing number is appended.
For example:
```
-###### ..Ab_c-d. e [anchor](URL) ![alt text](URL)..
+# This header has spaces in it
+## This header has a :thumbsup: in it
+# This header has Unicode in it: 한글
+## This header has spaces in it
+### This header has spaces in it
```
-which renders as:
-
-###### ..Ab_c-d. e [anchor](URL) ![alt text](URL)..
-
-will first be converted by step 1) into a string like:
+Would generate the following link IDs:
-```
-..Ab_c-d. e &lt;a href="URL">anchor&lt;/a> &lt;img src="URL" alt="alt text"/>..
-```
+1. `this-header-has-spaces-in-it`
+1. `this-header-has-a-in-it`
+1. `this-header-has-unicode-in-it-한글`
+1. `this-header-has-spaces-in-it-1`
+1. `this-header-has-spaces-in-it-2`
-After removing the tags in step 2) we get:
-
-```
-..Ab_c-d. e anchor ..
-```
-
-And applying all the other steps gives the id:
-
-```
-ab_c-d-e-anchor
-```
-
-Note in particular how:
-
-- for markdown anchors `[text](URL)`, only the `text` is used
-- markdown images `![alt](URL)` are completely ignored
+Note that the Emoji processing happens before the header IDs are generated, so the Emoji is converted to an image which then gets removed from the ID.
## Emphasis
@@ -322,8 +317,6 @@ Strikethrough uses two tildes. ~~Scratch this.~~
1. Ordered sub-list
4. And another item.
- Some text that should be aligned with the above item.
-
* Unordered list can use asterisks
- Or minuses
+ Or pluses
@@ -336,8 +329,6 @@ Strikethrough uses two tildes. ~~Scratch this.~~
1. Ordered sub-list
4. And another item.
- Some text that should be aligned with the above item.
-
* Unordered list can use asterisks
- Or minuses
+ Or pluses
@@ -432,7 +423,7 @@ Quote break.
You can also use raw HTML in your Markdown, and it'll mostly work pretty well.
-See the documentation for HTML::Pipeline's [SanitizationFilter](http://www.rubydoc.info/gems/html-pipeline/HTML/Pipeline/SanitizationFilter#WHITELIST-constant) class for the list of allowed HTML tags and attributes. In addition to the default `SanitizationFilter` whitelist, GitLab allows the `class`, `id`, and `style` attributes.
+See the documentation for HTML::Pipeline's [SanitizationFilter](http://www.rubydoc.info/gems/html-pipeline/HTML/Pipeline/SanitizationFilter#WHITELIST-constant) class for the list of allowed HTML tags and attributes. In addition to the default `SanitizationFilter` whitelist, GitLab allows `span` elements, as well as the `class`, and `id` attributes on all elements.
```no-highlight
<dl>
@@ -536,6 +527,20 @@ Code above produces next output:
The row of dashes between the table header and body must have at least three dashes in each column.
+By including colons in the header row, you can align the text within that column:
+
+```
+| Left Aligned | Centered | Right Aligned | Left Aligned | Centered | Right Aligned |
+| :----------- | :------: | ------------: | :----------- | :------: | ------------: |
+| Cell 1 | Cell 2 | Cell 3 | Cell 4 | Cell 5 | Cell 6 |
+| Cell 7 | Cell 8 | Cell 9 | Cell 10 | Cell 11 | Cell 12 |
+```
+
+| Left Aligned | Centered | Right Aligned | Left Aligned | Centered | Right Aligned |
+| :----------- | :------: | ------------: | :----------- | :------: | ------------: |
+| Cell 1 | Cell 2 | Cell 3 | Cell 4 | Cell 5 | Cell 6 |
+| Cell 7 | Cell 8 | Cell 9 | Cell 10 | Cell 11 | Cell 12 |
+
## References
- This document leveraged heavily from the [Markdown-Cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet).
diff --git a/doc/operations/sidekiq_memory_killer.md b/doc/operations/sidekiq_memory_killer.md
index 867b01b0d5a..811c2192a19 100644
--- a/doc/operations/sidekiq_memory_killer.md
+++ b/doc/operations/sidekiq_memory_killer.md
@@ -36,3 +36,5 @@ The MemoryKiller is controlled using environment variables.
Existing jobs get 30 seconds to finish. After that, the MemoryKiller tells
Sidekiq to shut down, and an external supervision mechanism (e.g. Runit) must
restart Sidekiq.
+- `SIDEKIQ_MEMORY_KILLER_SHUTDOWN_SIGNAL`: defaults to 'SIGTERM'. The name of
+ the final signal sent to the Sidekiq process when we want it to shut down.
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index bfef975024f..bca4fcfb404 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -9,6 +9,8 @@ This archive will be saved in backup_path (see `config/gitlab.yml`).
The filename will be `[TIMESTAMP]_gitlab_backup.tar`. This timestamp can be used to restore an specific backup.
You can only restore a backup to exactly the same version of GitLab that you created it on, for example 7.2.1.
+If you are interested in GitLab CI backup please follow to the [CI backup documentation](https://gitlab.com/gitlab-org/gitlab-ci/blob/master/doc/raketasks/backup_restore.md)*
+
```
# use this command if you've installed GitLab with the Omnibus package
sudo gitlab-rake gitlab:backup:create
@@ -150,11 +152,9 @@ If you have an installation from source, please consider backing up your `gitlab
You can only restore a backup to exactly the same version of GitLab that you created it on, for example 7.2.1.
-```
-# Omnibus package installation
-sudo gitlab-rake gitlab:backup:restore
+### Installation from source
-# installation from source
+```
bundle exec rake gitlab:backup:restore RAILS_ENV=production
```
@@ -196,11 +196,45 @@ Restoring repositories:
Deleting tmp directories...[DONE]
```
-## Configure cron to make daily backups
+### Omnibus installations
+
+We will assume that you have installed GitLab from an omnibus package and run
+`sudo gitlab-ctl reconfigure` at least once.
+
+First make sure your backup tar file is in `/var/opt/gitlab/backups`.
+
+```shell
+sudo cp 1393513186_gitlab_backup.tar /var/opt/gitlab/backups/
+```
-For Omnibus package installations, see https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#scheduling-a-backup .
+Next, restore the backup by running the restore command. You need to specify the
+timestamp of the backup you are restoring.
-For installation from source:
+```shell
+# Stop processes that are connected to the database
+sudo gitlab-ctl stop unicorn
+sudo gitlab-ctl stop sidekiq
+
+# This command will overwrite the contents of your GitLab database!
+sudo gitlab-rake gitlab:backup:restore BACKUP=1393513186
+
+# Start GitLab
+sudo gitlab-ctl start
+
+# Create satellites
+sudo gitlab-rake gitlab:satellites:create
+
+# Check GitLab
+sudo gitlab-rake gitlab:check SANITIZE=true
+```
+
+If there is a GitLab version mismatch between your backup tar file and the installed
+version of GitLab, the restore command will abort with an error. Install a package for
+the [required version](https://www.gitlab.com/downloads/archives/) and try again.
+
+## Configure cron to make daily backups
+
+### For installation from source:
```
cd /home/git/gitlab
sudo -u git -H editor config/gitlab.yml # Enable keep_time in the backup section to automatically delete old backups
@@ -217,6 +251,32 @@ Add the following lines at the bottom:
The `CRON=1` environment setting tells the backup script to suppress all progress output if there are no errors.
This is recommended to reduce cron spam.
+### For omnibus installations
+
+To schedule a cron job that backs up your repositories and GitLab metadata, use the root user:
+
+```
+sudo su -
+crontab -e
+```
+
+There, add the following line to schedule the backup for everyday at 2 AM:
+
+```
+0 2 * * * /opt/gitlab/bin/gitlab-rake gitlab:backup:create CRON=1
+```
+
+You may also want to set a limited lifetime for backups to prevent regular
+backups using all your disk space. To do this add the following lines to
+`/etc/gitlab/gitlab.rb` and reconfigure:
+
+```
+# limit backup lifetime to 7 days - 604800 seconds
+gitlab_rails['backup_keep_time'] = 604800
+```
+
+NOTE: This cron job does not [backup your omnibus-gitlab configuration](#backup-and-restore-omnibus-gitlab-configuration) or [SSH host keys](https://superuser.com/questions/532040/copy-ssh-keys-from-one-server-to-another-server/532079#532079).
+
## Alternative backup strategies
If your GitLab server contains a lot of Git repository data you may find the GitLab backup script to be too slow.
diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature
index eb813884d1e..bf84e2f8e87 100644
--- a/features/project/issues/issues.feature
+++ b/features/project/issues/issues.feature
@@ -134,48 +134,15 @@ Feature: Project Issues
And I should see "Release 0.4" in issues
And I should not see "Tweet control" in issues
- Scenario: Issue description should render task checkboxes
- Given project "Shop" has "Tasks-open" open issue with task markdown
- When I visit issue page "Tasks-open"
- Then I should see task checkboxes in the description
-
- @javascript
- Scenario: Issue notes should not render task checkboxes
- Given project "Shop" has "Tasks-open" open issue with task markdown
- When I visit issue page "Tasks-open"
- And I leave a comment with task markdown
- Then I should not see task checkboxes in the comment
-
@javascript
Scenario: Issue notes should be editable with +1
- Given project "Shop" has "Tasks-open" open issue with task markdown
- When I visit issue page "Tasks-open"
+ Given project "Shop" have "Release 0.4" open issue
+ When I visit issue page "Release 0.4"
And I leave a comment with a header containing "Comment with a header"
Then The comment with the header should not have an ID
And I edit the last comment with a +1
Then I should see +1 in the description
- # Task status in issues list
-
- Scenario: Issues list should display task status
- Given project "Shop" has "Tasks-open" open issue with task markdown
- When I visit project "Shop" issues page
- Then I should see the task status for the Taskable
-
- # Toggling task items
-
- @javascript
- Scenario: Task checkboxes should be enabled for an open issue
- Given project "Shop" has "Tasks-open" open issue with task markdown
- When I visit issue page "Tasks-open"
- Then Task checkboxes should be enabled
-
- @javascript
- Scenario: Task checkboxes should be disabled for a closed issue
- Given project "Shop" has "Tasks-closed" closed issue with task markdown
- When I visit issue page "Tasks-closed"
- Then Task checkboxes should be disabled
-
# Issue description preview
@javascript
@@ -212,8 +179,8 @@ Feature: Project Issues
@javascript
Scenario: I can unsubscribe from issue
- Given project "Shop" has "Tasks-open" open issue with task markdown
- When I visit issue page "Tasks-open"
+ Given project "Shop" have "Release 0.4" open issue
+ When I visit issue page "Release 0.4"
Then I should see that I am subscribed
When I click button "Unsubscribe"
Then I should see that I am unsubscribed
diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature
index cbb5c8eb39b..60caf783fe4 100644
--- a/features/project/merge_requests.feature
+++ b/features/project/merge_requests.feature
@@ -96,16 +96,6 @@ Feature: Project Merge Requests
And I leave a comment with a header containing "Comment with a header"
Then The comment with the header should not have an ID
- Scenario: Merge request description should render task checkboxes
- Given project "Shop" has "MR-task-open" open MR with task markdown
- When I visit merge request page "MR-task-open"
- Then I should see task checkboxes in the description
-
- Scenario: Merge request notes should not render task checkboxes
- Given project "Shop" has "MR-task-open" open MR with task markdown
- When I visit merge request page "MR-task-open"
- Then I should not see task checkboxes in the comment
-
# Toggling inline comments
@javascript
@@ -173,28 +163,6 @@ Feature: Project Merge Requests
And I click on the Changes tab via Javascript
Then I should see the proper Inline and Side-by-side links
- # Task status in issues list
-
- Scenario: Merge requests list should display task status
- Given project "Shop" has "MR-task-open" open MR with task markdown
- When I visit project "Shop" merge requests page
- Then I should see the task status for the Taskable
-
- # Toggling task items
-
- @javascript
- Scenario: Task checkboxes should be enabled for an open merge request
- Given project "Shop" has "MR-task-open" open MR with task markdown
- When I visit merge request page "MR-task-open"
- Then Task checkboxes should be enabled
-
- @javascript
- Scenario: Task checkboxes should be disabled for a closed merge request
- Given project "Shop" has "MR-task-open" open MR with task markdown
- And I visit merge request page "MR-task-open"
- And I click link "Close"
- Then Task checkboxes should be disabled
-
# Description preview
@javascript
diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb
index b8e282b2029..504f0cff724 100644
--- a/features/steps/project/issues/issues.rb
+++ b/features/steps/project/issues/issues.rb
@@ -179,14 +179,6 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
author: project.users.first)
end
- step 'project "Shop" has "Tasks-open" open issue with task markdown' do
- create_taskable(:issue, 'Tasks-open')
- end
-
- step 'project "Shop" has "Tasks-closed" closed issue with task markdown' do
- create_taskable(:closed_issue, 'Tasks-closed')
- end
-
step 'empty project "Empty Project"' do
create :empty_project, name: 'Empty Project', namespace: @user.namespace
end
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index 12d4d81159e..f67e6e3d8ca 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -108,10 +108,6 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
author: project.users.first)
end
- step 'project "Shop" has "MR-task-open" open MR with task markdown' do
- create_taskable(:merge_request, 'MR-task-open')
- end
-
step 'I switch to the diff tab' do
visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
end
diff --git a/features/steps/shared/markdown.rb b/features/steps/shared/markdown.rb
index a7231c47d14..943640007a9 100644
--- a/features/steps/shared/markdown.rb
+++ b/features/steps/shared/markdown.rb
@@ -10,55 +10,10 @@ module SharedMarkdown
find(:xpath, "#{node.path}/..").text.should == text
end
- def create_taskable(type, title)
- desc_text = <<EOT.gsub(/^ {6}/, '')
- * [ ] Task 1
- * [x] Task 2
-EOT
-
- case type
- when :issue, :closed_issue
- options = { project: project }
- when :merge_request
- options = { source_project: project, target_project: project }
- end
-
- create(
- type,
- options.merge(title: title,
- author: project.users.first,
- description: desc_text)
- )
- end
-
step 'Header "Description header" should have correct id and link' do
header_should_have_correct_id_and_link(1, 'Description header', 'description-header')
end
- step 'I should see task checkboxes in the description' do
- expect(page).to have_selector(
- 'div.description li.task-list-item input[type="checkbox"]'
- )
- end
-
- step 'I should see the task status for the Taskable' do
- expect(find(:css, 'span.task-status').text).to eq(
- '2 tasks (1 done, 1 unfinished)'
- )
- end
-
- step 'Task checkboxes should be enabled' do
- expect(page).to have_selector(
- 'div.description li.task-list-item input[type="checkbox"]:enabled'
- )
- end
-
- step 'Task checkboxes should be disabled' do
- expect(page).to have_selector(
- 'div.description li.task-list-item input[type="checkbox"]:disabled'
- )
- end
-
step 'I should not see the Markdown preview' do
expect(find('.gfm-form .js-md-preview')).not_to be_visible
end
diff --git a/features/steps/shared/note.rb b/features/steps/shared/note.rb
index 583746d4475..2f66e61b214 100644
--- a/features/steps/shared/note.rb
+++ b/features/steps/shared/note.rb
@@ -122,20 +122,6 @@ module SharedNote
end
end
- step 'I leave a comment with task markdown' do
- within('.js-main-target-form') do
- fill_in 'note[note]', with: '* [x] Task item'
- click_button 'Add Comment'
- sleep 0.05
- end
- end
-
- step 'I should not see task checkboxes in the comment' do
- expect(page).not_to have_selector(
- 'li.note div.timeline-content input[type="checkbox"]'
- )
- end
-
step 'I edit the last comment with a +1' do
find(".note").hover
find('.js-note-edit').click
diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb
index e3cf1b92cda..d9401bd540c 100644
--- a/features/steps/shared/paths.rb
+++ b/features/steps/shared/paths.rb
@@ -323,16 +323,6 @@ module SharedPaths
visit namespace_project_issue_path(issue.project.namespace, issue.project, issue)
end
- step 'I visit issue page "Tasks-open"' do
- issue = Issue.find_by(title: 'Tasks-open')
- visit namespace_project_issue_path(issue.project.namespace, issue.project, issue)
- end
-
- step 'I visit issue page "Tasks-closed"' do
- issue = Issue.find_by(title: 'Tasks-closed')
- visit namespace_project_issue_path(issue.project.namespace, issue.project, issue)
- end
-
step 'I visit project "Shop" labels page' do
project = Project.find_by(name: 'Shop')
visit namespace_project_labels_path(project.namespace, project)
@@ -363,16 +353,6 @@ module SharedPaths
visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr)
end
- step 'I visit merge request page "MR-task-open"' do
- mr = MergeRequest.find_by(title: 'MR-task-open')
- visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr)
- end
-
- step 'I visit merge request page "MR-task-closed"' do
- mr = MergeRequest.find_by(title: 'MR-task-closed')
- visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr)
- end
-
step 'I visit project "Shop" merge requests page' do
visit namespace_project_merge_requests_path(project.namespace, project)
end
diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb
index 8348e28d0f5..63294aa54c0 100644
--- a/lib/gitlab/markdown.rb
+++ b/lib/gitlab/markdown.rb
@@ -1,4 +1,5 @@
require 'html/pipeline'
+require 'task_list/filter'
module Gitlab
# Custom parser for GitLab-flavored Markdown
@@ -31,9 +32,9 @@ module Gitlab
# Public: Parse the provided text with GitLab-Flavored Markdown
#
# text - the source text
- # options - parse_tasks - render tasks
- # - xhtml - output XHTML instead of HTML
- # - reference_only_path - Use relative path for reference links
+ # options - A Hash of options used to customize output (default: {}):
+ # :xhtml - output XHTML instead of HTML
+ # :reference_only_path - Use relative path for reference links
# project - the project
# html_options - extra options for the reference links as given to link_to
def gfm_with_options(text, options = {}, project = @project, html_options = {})
@@ -45,7 +46,6 @@ module Gitlab
text = text.dup.to_str
options.reverse_merge!(
- parse_tasks: false,
xhtml: false,
reference_only_path: true
)
@@ -76,10 +76,6 @@ module Gitlab
text = result[:output].to_html(save_with: save_options)
- if options[:parse_tasks]
- text = parse_tasks(text)
- end
-
text.html_safe
end
@@ -106,28 +102,10 @@ module Gitlab
Gitlab::Markdown::SnippetReferenceFilter,
Gitlab::Markdown::CommitRangeReferenceFilter,
Gitlab::Markdown::CommitReferenceFilter,
- Gitlab::Markdown::LabelReferenceFilter
- ]
- end
+ Gitlab::Markdown::LabelReferenceFilter,
- # Turn list items that start with "[ ]" into HTML checkbox inputs.
- def parse_tasks(text)
- li_tag = '<li class="task-list-item">'
- unchecked_box = '<input type="checkbox" value="on" disabled />'
- checked_box = unchecked_box.sub(/\/>$/, 'checked="checked" />')
-
- # Regexp captures don't seem to work when +text+ is an
- # ActiveSupport::SafeBuffer, hence the `String.new`
- String.new(text).gsub(Taskable::TASK_PATTERN_HTML) do
- checked = $LAST_MATCH_INFO[:checked].downcase == 'x'
- p_tag = $LAST_MATCH_INFO[:p_tag]
-
- if checked
- "#{li_tag}#{p_tag}#{checked_box}"
- else
- "#{li_tag}#{p_tag}#{unchecked_box}"
- end
- end
+ TaskList::Filter
+ ]
end
end
end
diff --git a/lib/gitlab/markdown/table_of_contents_filter.rb b/lib/gitlab/markdown/table_of_contents_filter.rb
index c97612dafb8..38887c9778c 100644
--- a/lib/gitlab/markdown/table_of_contents_filter.rb
+++ b/lib/gitlab/markdown/table_of_contents_filter.rb
@@ -31,7 +31,7 @@ module Gitlab
id = text.downcase
id.gsub!(PUNCTUATION_REGEXP, '') # remove punctuation
id.gsub!(' ', '-') # replace spaces with dash
- id.squeeze!(' -') # replace multiple spaces or dashes with one
+ id.squeeze!('-') # replace multiple dashes with one
uniq = (headers[id] > 0) ? "-#{headers[id]}" : ''
headers[id] += 1
diff --git a/lib/gitlab/sidekiq_middleware/memory_killer.rb b/lib/gitlab/sidekiq_middleware/memory_killer.rb
index 0f2db50e98c..f33b2dedf4a 100644
--- a/lib/gitlab/sidekiq_middleware/memory_killer.rb
+++ b/lib/gitlab/sidekiq_middleware/memory_killer.rb
@@ -7,6 +7,7 @@ module Gitlab
GRACE_TIME = (ENV['SIDEKIQ_MEMORY_KILLER_GRACE_TIME'] || 15 * 60).to_s.to_i
# Wait 30 seconds for running jobs to finish during graceful shutdown
SHUTDOWN_WAIT = (ENV['SIDEKIQ_MEMORY_KILLER_SHUTDOWN_WAIT'] || 30).to_s.to_i
+ SHUTDOWN_SIGNAL = (ENV['SIDEKIQ_MEMORY_KILLER_SHUTDOWN_SIGNAL'] || 'SIGTERM').to_s
# Create a mutex used to ensure there will be only one thread waiting to
# shut Sidekiq down
@@ -24,19 +25,19 @@ module Gitlab
Sidekiq.logger.warn "current RSS #{current_rss} exceeds maximum RSS "\
"#{MAX_RSS}"
- Sidekiq.logger.warn "spawned thread that will shut down PID "\
- "#{Process.pid} in #{GRACE_TIME} seconds"
+ Sidekiq.logger.warn "this thread will shut down PID #{Process.pid} "\
+ "in #{GRACE_TIME} seconds"
sleep(GRACE_TIME)
Sidekiq.logger.warn "sending SIGUSR1 to PID #{Process.pid}"
Process.kill('SIGUSR1', Process.pid)
Sidekiq.logger.warn "waiting #{SHUTDOWN_WAIT} seconds before sending "\
- "SIGTERM to PID #{Process.pid}"
+ "#{SHUTDOWN_SIGNAL} to PID #{Process.pid}"
sleep(SHUTDOWN_WAIT)
- Sidekiq.logger.warn "sending SIGTERM to PID #{Process.pid}"
- Process.kill('SIGTERM', Process.pid)
+ Sidekiq.logger.warn "sending #{SHUTDOWN_SIGNAL} to PID #{Process.pid}"
+ Process.kill(SHUTDOWN_SIGNAL, Process.pid)
end
end
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index f967fddf56d..1a6303b6c82 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -320,7 +320,7 @@ namespace :gitlab do
options = {
"user.name" => "GitLab",
- "user.email" => Gitlab.config.outgoing_emails.from,
+ "user.email" => Gitlab.config.gitlab.email_from,
"core.autocrlf" => "input"
}
correct_options = options.map do |name, value|
diff --git a/lib/tasks/jasmine.rake b/lib/tasks/jasmine.rake
new file mode 100644
index 00000000000..9e2cceffa19
--- /dev/null
+++ b/lib/tasks/jasmine.rake
@@ -0,0 +1,12 @@
+# Since we no longer explicitly require the 'jasmine' gem, we lost the
+# `jasmine:ci` task used by GitLab CI jobs.
+#
+# This provides a simple alias to run the `spec:javascript` task from the
+# 'jasmine-rails' gem.
+task jasmine: ['jasmine:ci']
+
+namespace :jasmine do
+ task :ci do
+ Rake::Task['spec:javascript'].invoke
+ end
+end
diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb
index 57d01236505..1746ce128e4 100644
--- a/spec/features/markdown_spec.rb
+++ b/spec/features/markdown_spec.rb
@@ -24,6 +24,7 @@ require 'erb'
# -> Rinku (http, https, ftp)
# -> Other schemes
# -> References
+# -> TaskList
# -> `html_safe`
# -> Template
#
@@ -279,6 +280,15 @@ describe 'GitLab Markdown' do
expect(body).to have_selector('a.gfm.gfm-label', count: 3)
end
end
+
+ describe 'Task Lists' do
+ it 'generates task lists' do
+ body = get_section('task-lists')
+ expect(body).to have_selector('ul.task-list', count: 2)
+ expect(body).to have_selector('li.task-list-item', count: 7)
+ expect(body).to have_selector('input[checked]', count: 3)
+ end
+ end
end
end
@@ -289,9 +299,8 @@ end
# once. Unfortunately RSpec will not let you access `let`s in a `before(:all)`
# block, so we fake it by encapsulating all the shared setup in this class.
#
-# The class contains the raw Markup used in the test, dynamically substituting
-# real objects, created from factories and setup on-demand, when referenced in
-# the Markdown.
+# The class renders `spec/fixtures/markdown.md.erb` using ERB, allowing for
+# reference to the factory-created objects.
class MarkdownFeature
include FactoryGirl::Syntax::Methods
diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb
new file mode 100644
index 00000000000..2099fc40cca
--- /dev/null
+++ b/spec/features/task_lists_spec.rb
@@ -0,0 +1,151 @@
+require 'spec_helper'
+
+feature 'Task Lists' do
+ include Warden::Test::Helpers
+
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:user2) { create(:user) }
+
+ let(:markdown) do
+ <<-MARKDOWN.strip_heredoc
+ This is a task list:
+
+ - [ ] Incomplete entry 1
+ - [x] Complete entry 1
+ - [ ] Incomplete entry 2
+ - [x] Complete entry 2
+ - [ ] Incomplete entry 3
+ - [ ] Incomplete entry 4
+ MARKDOWN
+ end
+
+ before do
+ Warden.test_mode!
+
+ project.team << [user, :master]
+ project.team << [user2, :guest]
+
+ login_as(user)
+ end
+
+ def visit_issue(project, issue)
+ visit namespace_project_issue_path(project.namespace, project, issue)
+ end
+
+ describe 'for Issues' do
+ let!(:issue) { create(:issue, description: markdown, author: user, project: project) }
+
+ it 'renders' do
+ visit_issue(project, issue)
+
+ expect(page).to have_selector('ul.task-list', count: 1)
+ expect(page).to have_selector('li.task-list-item', count: 6)
+ expect(page).to have_selector('ul input[checked]', count: 2)
+ end
+
+ it 'contains the required selectors' do
+ visit_issue(project, issue)
+
+ container = '.issue-details .description.js-task-list-container'
+
+ expect(page).to have_selector(container)
+ expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox")
+ expect(page).to have_selector("#{container} .js-task-list-field")
+ expect(page).to have_selector('form.js-issue-update')
+ expect(page).to have_selector('a.btn-close')
+ end
+
+ it 'is only editable by author' do
+ visit_issue(project, issue)
+ expect(page).to have_selector('.js-task-list-container')
+
+ logout(:user)
+
+ login_as(user2)
+ visit current_path
+ expect(page).not_to have_selector('.js-task-list-container')
+ end
+
+ it 'provides a summary on Issues#index' do
+ visit namespace_project_issues_path(project.namespace, project)
+ expect(page).to have_content("6 tasks (2 completed, 4 remaining)")
+ end
+ end
+
+ describe 'for Notes' do
+ let!(:issue) { create(:issue, author: user, project: project) }
+ let!(:note) { create(:note, note: markdown, noteable: issue, author: user) }
+
+ it 'renders for note body' do
+ visit_issue(project, issue)
+
+ expect(page).to have_selector('.note ul.task-list', count: 1)
+ expect(page).to have_selector('.note li.task-list-item', count: 6)
+ expect(page).to have_selector('.note ul input[checked]', count: 2)
+ end
+
+ it 'contains the required selectors' do
+ visit_issue(project, issue)
+
+ expect(page).to have_selector('.note .js-task-list-container')
+ expect(page).to have_selector('.note .js-task-list-container .task-list .task-list-item .task-list-item-checkbox')
+ expect(page).to have_selector('.note .js-task-list-container .js-task-list-field')
+ end
+
+ it 'is only editable by author' do
+ visit_issue(project, issue)
+ expect(page).to have_selector('.js-task-list-container')
+
+ logout(:user)
+
+ login_as(user2)
+ visit current_path
+ expect(page).not_to have_selector('.js-task-list-container')
+ end
+ end
+
+ describe 'for Merge Requests' do
+ def visit_merge_request(project, merge)
+ visit namespace_project_merge_request_path(project.namespace, project, merge)
+ end
+
+ let!(:merge) { create(:merge_request, :simple, description: markdown, author: user, source_project: project) }
+
+ it 'renders for description' do
+ visit_merge_request(project, merge)
+
+ expect(page).to have_selector('ul.task-list', count: 1)
+ expect(page).to have_selector('li.task-list-item', count: 6)
+ expect(page).to have_selector('ul input[checked]', count: 2)
+ end
+
+ it 'contains the required selectors' do
+ visit_merge_request(project, merge)
+
+ container = '.merge-request-details .description.js-task-list-container'
+
+ expect(page).to have_selector(container)
+ expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox")
+ expect(page).to have_selector("#{container} .js-task-list-field")
+ expect(page).to have_selector('form.js-merge-request-update')
+ expect(page).to have_selector('a.btn-close')
+ end
+
+ it 'is only editable by author' do
+ visit_merge_request(project, merge)
+ expect(page).to have_selector('.js-task-list-container')
+
+ logout(:user)
+
+ login_as(user2)
+ visit current_path
+ expect(page).not_to have_selector('.js-task-list-container')
+ end
+
+ it 'provides a summary on MergeRequests#index' do
+ visit namespace_project_merge_requests_path(project.namespace, project)
+ expect(page).to have_content("6 tasks (2 completed, 4 remaining)")
+ end
+ end
+end
diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb
index 6b79aad8f86..bc023ecf793 100644
--- a/spec/fixtures/markdown.md.erb
+++ b/spec/fixtures/markdown.md.erb
@@ -184,3 +184,13 @@ References should be parseable even inside _!<%= merge_request.iid %>_ emphasis.
- Label by name in quotes: ~"<%= label.name %>"
- Ignored in code: `~<%= simple_label.name %>`
- Ignored in links: [Link to ~<%= simple_label.id %>](#label-link)
+
+### Task Lists
+
+- [ ] Incomplete task 1
+- [x] Complete task 1
+- [ ] Incomplete task 2
+ - [ ] Incomplete sub-task 1
+ - [ ] Incomplete sub-task 2
+ - [x] Complete sub-task 1
+- [X] Complete task 2
diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb
index b6be82e4109..2f67879efdc 100644
--- a/spec/helpers/gitlab_markdown_helper_spec.rb
+++ b/spec/helpers/gitlab_markdown_helper_spec.rb
@@ -43,115 +43,6 @@ describe GitlabMarkdownHelper do
expect(gfm(actual)).to match(expected)
end
end
-
- context 'parse_tasks: true' do
- before(:all) do
- @source_text_asterisk = <<-EOT.strip_heredoc
- * [ ] valid unchecked task
- * [x] valid lowercase checked task
- * [X] valid uppercase checked task
- * [ ] valid unchecked nested task
- * [x] valid checked nested task
-
- [ ] not an unchecked task - no list item
- [x] not a checked task - no list item
-
- * [ ] not an unchecked task - too many spaces
- * [x ] not a checked task - too many spaces
- * [] not an unchecked task - no spaces
- * Not a task [ ] - not at beginning
- EOT
-
- @source_text_dash = <<-EOT.strip_heredoc
- - [ ] valid unchecked task
- - [x] valid lowercase checked task
- - [X] valid uppercase checked task
- - [ ] valid unchecked nested task
- - [x] valid checked nested task
- EOT
- end
-
- it 'should render checkboxes at beginning of asterisk list items' do
- rendered_text = markdown(@source_text_asterisk, parse_tasks: true)
-
- expect(rendered_text).to match(/<input.*checkbox.*valid unchecked task/)
- expect(rendered_text).to match(
- /<input.*checkbox.*valid lowercase checked task/
- )
- expect(rendered_text).to match(
- /<input.*checkbox.*valid uppercase checked task/
- )
- end
-
- it 'should render checkboxes at beginning of dash list items' do
- rendered_text = markdown(@source_text_dash, parse_tasks: true)
-
- expect(rendered_text).to match(/<input.*checkbox.*valid unchecked task/)
- expect(rendered_text).to match(
- /<input.*checkbox.*valid lowercase checked task/
- )
- expect(rendered_text).to match(
- /<input.*checkbox.*valid uppercase checked task/
- )
- end
-
- it 'should render checkboxes for nested tasks' do
- rendered_text = markdown(@source_text_asterisk, parse_tasks: true)
-
- expect(rendered_text).to match(
- /<input.*checkbox.*valid unchecked nested task/
- )
- expect(rendered_text).to match(
- /<input.*checkbox.*valid checked nested task/
- )
- end
-
- it 'should not be confused by whitespace before bullets' do
- rendered_text_asterisk = markdown(@source_text_asterisk, parse_tasks: true)
- rendered_text_dash = markdown(@source_text_dash, parse_tasks: true)
-
- expect(rendered_text_asterisk).to match(
- /<input.*checkbox.*valid unchecked nested task/
- )
- expect(rendered_text_asterisk).to match(
- /<input.*checkbox.*valid checked nested task/
- )
- expect(rendered_text_dash).to match(
- /<input.*checkbox.*valid unchecked nested task/
- )
- expect(rendered_text_dash).to match(
- /<input.*checkbox.*valid checked nested task/
- )
- end
-
- it 'should not render checkboxes outside of list items' do
- rendered_text = markdown(@source_text_asterisk, parse_tasks: true)
-
- expect(rendered_text).not_to match(
- /<input.*checkbox.*not an unchecked task - no list item/
- )
- expect(rendered_text).not_to match(
- /<input.*checkbox.*not a checked task - no list item/
- )
- end
-
- it 'should not render checkboxes with invalid formatting' do
- rendered_text = markdown(@source_text_asterisk, parse_tasks: true)
-
- expect(rendered_text).not_to match(
- /<input.*checkbox.*not an unchecked task - too many spaces/
- )
- expect(rendered_text).not_to match(
- /<input.*checkbox.*not a checked task - too many spaces/
- )
- expect(rendered_text).not_to match(
- /<input.*checkbox.*not an unchecked task - no spaces/
- )
- expect(rendered_text).not_to match(
- /Not a task.*<input.*checkbox.*not at beginning/
- )
- end
- end
end
describe '#link_to_gfm' do
diff --git a/spec/javascripts/helpers/.gitkeep b/spec/javascripts/helpers/.gitkeep
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/spec/javascripts/helpers/.gitkeep
+++ /dev/null
diff --git a/spec/javascripts/issue_spec.js.coffee b/spec/javascripts/issue_spec.js.coffee
new file mode 100644
index 00000000000..13b25862f57
--- /dev/null
+++ b/spec/javascripts/issue_spec.js.coffee
@@ -0,0 +1,36 @@
+#= require jquery
+#= require jasmine-fixture
+#= require issue
+
+describe 'Issue', ->
+ describe 'task lists', ->
+ selectors = {
+ container: '.issue-details .description.js-task-list-container'
+ item: '.wiki ul.task-list li.task-list-item input.task-list-item-checkbox[type=checkbox] {Task List Item}'
+ textarea: '.wiki textarea.js-task-list-field{- [ ] Task List Item}'
+ form: 'form.js-issue-update[action="/foo"]'
+ close: 'a.btn-close'
+ }
+
+ beforeEach ->
+ $container = affix(selectors.container)
+
+ # # These two elements are siblings inside the container
+ $container.find('.js-task-list-container').append(affix(selectors.item))
+ $container.find('.js-task-list-container').append(affix(selectors.textarea))
+
+ # Task lists don't get initialized unless this button exists. Not ideal.
+ $container.append(affix(selectors.close))
+
+ # This form is used to get the `update` URL. Not ideal.
+ $container.append(affix(selectors.form))
+
+ @issue = new Issue()
+
+ it 'submits an ajax request on tasklist:changed', ->
+ spyOn($, 'ajax').and.callFake (req) ->
+ expect(req.type).toBe('PATCH')
+ expect(req.url).toBe('/foo')
+ expect(req.data.issue.description).not.toBe(null)
+
+ $('.js-task-list-field').trigger('tasklist:changed')
diff --git a/spec/javascripts/merge_request_spec.js.coffee b/spec/javascripts/merge_request_spec.js.coffee
new file mode 100644
index 00000000000..3ebc4a4eed5
--- /dev/null
+++ b/spec/javascripts/merge_request_spec.js.coffee
@@ -0,0 +1,36 @@
+#= require jquery
+#= require jasmine-fixture
+#= require merge_request
+
+describe 'MergeRequest', ->
+ describe 'task lists', ->
+ selectors = {
+ container: '.merge-request-details .description.js-task-list-container'
+ item: '.wiki ul.task-list li.task-list-item input.task-list-item-checkbox[type=checkbox] {Task List Item}'
+ textarea: '.wiki textarea.js-task-list-field{- [ ] Task List Item}'
+ form: 'form.js-merge-request-update[action="/foo"]'
+ close: 'a.btn-close'
+ }
+
+ beforeEach ->
+ $container = affix(selectors.container)
+
+ # # These two elements are siblings inside the container
+ $container.find('.js-task-list-container').append(affix(selectors.item))
+ $container.find('.js-task-list-container').append(affix(selectors.textarea))
+
+ # Task lists don't get initialized unless this button exists. Not ideal.
+ $container.append(affix(selectors.close))
+
+ # This form is used to get the `update` URL. Not ideal.
+ $container.append(affix(selectors.form))
+
+ @merge = new MergeRequest({})
+
+ it 'submits an ajax request on tasklist:changed', ->
+ spyOn($, 'ajax').and.callFake (req) ->
+ expect(req.type).toBe('PATCH')
+ expect(req.url).toBe('/foo')
+ expect(req.data.merge_request.description).not.toBe(null)
+
+ $('.js-task-list-field').trigger('tasklist:changed')
diff --git a/spec/javascripts/notes_spec.js.coffee b/spec/javascripts/notes_spec.js.coffee
new file mode 100644
index 00000000000..de2e8e7f6c8
--- /dev/null
+++ b/spec/javascripts/notes_spec.js.coffee
@@ -0,0 +1,30 @@
+#= require jquery
+#= require jasmine-fixture
+#= require notes
+
+window.gon = {}
+window.disableButtonIfEmptyField = -> null
+
+describe 'Notes', ->
+ describe 'task lists', ->
+ selectors = {
+ container: 'li.note .js-task-list-container'
+ item: '.note-text ul.task-list li.task-list-item input.task-list-item-checkbox[type=checkbox] {Task List Item}'
+ textarea: '.note-edit-form form textarea.js-task-list-field{- [ ] Task List Item}'
+ }
+
+ beforeEach ->
+ $container = affix(selectors.container)
+
+ # These two elements are siblings inside the container
+ $container.find('.js-task-list-container').append(affix(selectors.item))
+ $container.find('.js-task-list-container').append(affix(selectors.textarea))
+
+ @notes = new Notes()
+
+ it 'submits the form on tasklist:changed', ->
+ submitted = false
+ $('form').on 'submit', (e) -> submitted = true; e.preventDefault()
+
+ $('.js-task-list-field').trigger('tasklist:changed')
+ expect(submitted).toBe(true)
diff --git a/spec/javascripts/support/jasmine.yml b/spec/javascripts/support/jasmine.yml
index f4b01c9f27a..168c9618643 100644
--- a/spec/javascripts/support/jasmine.yml
+++ b/spec/javascripts/support/jasmine.yml
@@ -9,11 +9,6 @@
# defaults to spec/javascripts
spec_dir: spec/javascripts
-# list of file expressions to include as helpers into spec runner
-# relative path from spec_dir
-helpers:
- - "helpers/**/*.{js.coffee,js,coffee}"
-
# list of file expressions to include as specs into spec runner
# relative path from spec_dir
spec_files:
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index f626efe1f76..dbcf7286e45 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -5,9 +5,9 @@ describe Notify do
include EmailSpec::Matchers
include RepoHelpers
- let(:gitlab_sender_display_name) { Gitlab.config.outgoing_emails.display_name }
- let(:gitlab_sender) { Gitlab.config.outgoing_emails.from }
- let(:gitlab_sender_reply_to) { Gitlab.config.outgoing_emails.reply_to }
+ let(:gitlab_sender_display_name) { Gitlab.config.gitlab.email_display_name }
+ let(:gitlab_sender) { Gitlab.config.gitlab.email_from }
+ let(:gitlab_sender_reply_to) { Gitlab.config.gitlab.email_reply_to }
let(:recipient) { create(:user, email: 'recipient@example.com') }
let(:project) { create(:project) }
diff --git a/spec/support/taskable_shared_examples.rb b/spec/support/taskable_shared_examples.rb
index 8e5e3a8aafc..927c72c7409 100644
--- a/spec/support/taskable_shared_examples.rb
+++ b/spec/support/taskable_shared_examples.rb
@@ -4,39 +4,29 @@
# subject { Issue or MergeRequest }
shared_examples 'a Taskable' do
before do
- subject.description = <<EOT.gsub(/ {6}/, '')
+ subject.description = <<-EOT.strip_heredoc
* [ ] Task 1
* [x] Task 2
* [x] Task 3
* [ ] Task 4
* [ ] Task 5
-EOT
- end
-
- it 'updates the Nth task correctly' do
- subject.update_nth_task(1, true)
- expect(subject.description).to match(/\[x\] Task 1/)
-
- subject.update_nth_task(2, true)
- expect(subject.description).to match('\[x\] Task 2')
-
- subject.update_nth_task(3, false)
- expect(subject.description).to match('\[ \] Task 3')
-
- subject.update_nth_task(4, false)
- expect(subject.description).to match('\[ \] Task 4')
+ EOT
end
it 'returns the correct task status' do
expect(subject.task_status).to match('5 tasks')
- expect(subject.task_status).to match('2 done')
- expect(subject.task_status).to match('3 unfinished')
+ expect(subject.task_status).to match('2 completed')
+ expect(subject.task_status).to match('3 remaining')
end
- it 'knows if it has tasks' do
- expect(subject.tasks?).to be_truthy
+ describe '#tasks?' do
+ it 'returns true when object has tasks' do
+ expect(subject.tasks?).to eq true
+ end
- subject.description = 'Now I have no tasks'
- expect(subject.tasks?).to be_falsey
+ it 'returns false when object has no tasks' do
+ subject.description = 'Now I have no tasks'
+ expect(subject.tasks?).to eq false
+ end
end
end
diff --git a/vendor/assets/javascripts/jasmine-fixture.js b/vendor/assets/javascripts/jasmine-fixture.js
index 3815731fd2f..9980aec6ddb 100644..100755
--- a/vendor/assets/javascripts/jasmine-fixture.js
+++ b/vendor/assets/javascripts/jasmine-fixture.js
@@ -1,4 +1,4 @@
-/* jasmine-fixture - 1.2.2
+/* jasmine-fixture - 1.3.1
* Makes injecting HTML snippets into the DOM easy & clean!
* https://github.com/searls/jasmine-fixture
*/
@@ -8,7 +8,7 @@
(function($) {
var ewwSideEffects, jasmineFixture, originalAffix, originalJasmineDotFixture, originalJasmineFixture, root, _, _ref;
- root = this;
+ root = (1, eval)('this');
originalJasmineFixture = root.jasmineFixture;
originalJasmineDotFixture = (_ref = root.jasmine) != null ? _ref.fixture : void 0;
originalAffix = root.affix;
@@ -33,7 +33,7 @@
create = function(selectorOptions, attach) {
var $top;
$top = null;
- _(selectorOptions.split(/[ ](?=[^\]]*?(?:\[|$))/)).inject(function($parent, elementSelector) {
+ _(selectorOptions.split(/[ ](?![^\{]*\})(?=[^\]]*?(?:\[|$))/)).inject(function($parent, elementSelector) {
var $el;
if (elementSelector === ">") {
return $parent;