From 5ba2d53cfa1704b7ab76d3ac5dda2a880fa55c57 Mon Sep 17 00:00:00 2001 From: karen Carias Date: Mon, 10 Aug 2015 16:56:02 -0700 Subject: fixed text --- doc/workflow/importing/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/workflow/importing/README.md b/doc/workflow/importing/README.md index cd98d1b9852..c14709b6b6b 100644 --- a/doc/workflow/importing/README.md +++ b/doc/workflow/importing/README.md @@ -8,5 +8,6 @@ ### Note * If you'd like to migrate from a self-hosted GitLab instance to GitLab.com, you can copy your repos by changing the remote and pushing to the new server; but issues and merge requests can't be imported. -* Repositories are imported to GitLab via HTTP. -If the repository is too large, it can timeout. We have a soft limit of 10GB. +* You can import any Git repository via HTTP from the New Project page. +If the repository is too large, it can timeout. We have a soft limit of +10GB. -- cgit v1.2.1 From 10491b5438d1a41628bbb732f58329fb1b6270a3 Mon Sep 17 00:00:00 2001 From: karen Carias Date: Mon, 17 Aug 2015 14:42:34 -0700 Subject: deleted line --- doc/workflow/importing/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/workflow/importing/README.md b/doc/workflow/importing/README.md index c14709b6b6b..5cde90993d2 100644 --- a/doc/workflow/importing/README.md +++ b/doc/workflow/importing/README.md @@ -9,5 +9,4 @@ * If you'd like to migrate from a self-hosted GitLab instance to GitLab.com, you can copy your repos by changing the remote and pushing to the new server; but issues and merge requests can't be imported. * You can import any Git repository via HTTP from the New Project page. -If the repository is too large, it can timeout. We have a soft limit of -10GB. +If the repository is too large, it can timeout. -- cgit v1.2.1 From f76eac56b9d7d4ae61010cddcca68682824b2239 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 18 Aug 2015 15:46:36 -0700 Subject: Reply by email POC --- .gitignore | 1 + Gemfile | 2 + Gemfile.lock | 8 ++++ Procfile | 3 +- app/mailers/emails/issues.rb | 8 ++++ app/mailers/emails/merge_requests.rb | 11 ++++- app/mailers/emails/notes.rb | 6 +++ app/mailers/notify.rb | 54 ++++++++++++++-------- app/models/sent_notification.rb | 29 ++++++++++++ app/workers/email_receiver_worker.rb | 22 +++++++++ config/gitlab.yml.example | 5 ++ config/initializers/1_settings.rb | 6 +++ config/mail_room.yml.example | 14 ++++++ .../20150818213832_add_sent_notifications.rb | 13 ++++++ db/schema.rb | 13 +++++- lib/gitlab/email_receiver.rb | 50 ++++++++++++++++++++ 16 files changed, 224 insertions(+), 21 deletions(-) create mode 100644 app/models/sent_notification.rb create mode 100644 app/workers/email_receiver_worker.rb create mode 100644 config/mail_room.yml.example create mode 100644 db/migrate/20150818213832_add_sent_notifications.rb create mode 100644 lib/gitlab/email_receiver.rb diff --git a/.gitignore b/.gitignore index 3e30fb8cf77..8a68bb3e4f0 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ config/initializers/rack_attack.rb config/initializers/smtp_settings.rb config/resque.yml config/unicorn.rb +config/mail_room.yml coverage/* db/*.sqlite3 db/*.sqlite3-journal diff --git a/Gemfile b/Gemfile index 8f65a274baa..e3f76671f5b 100644 --- a/Gemfile +++ b/Gemfile @@ -272,3 +272,5 @@ end gem "newrelic_rpm" gem 'octokit', '3.7.0' + +gem "mail_room", github: "DouweM/mail_room", branch: "sidekiq" diff --git a/Gemfile.lock b/Gemfile.lock index f0c661fa9c5..3aca2c8890b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,10 @@ +GIT + remote: git://github.com/DouweM/mail_room.git + revision: e1795b807f492533ad40afcb80abf870f1baddb5 + branch: sidekiq + specs: + mail_room (0.3.1) + GEM remote: https://rubygems.org/ specs: @@ -805,6 +812,7 @@ DEPENDENCIES jquery-ui-rails kaminari (~> 0.15.1) letter_opener + mail_room! minitest (~> 5.3.0) mousetrap-rails mysql2 diff --git a/Procfile b/Procfile index 799b92729fa..51154f150d1 100644 --- a/Procfile +++ b/Procfile @@ -1,2 +1,3 @@ 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 common -q default +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 common -q default +mail_room: bundle exec mail_room -c config/mail_room.yml diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb index 687bac3aa31..c8b7775c328 100644 --- a/app/mailers/emails/issues.rb +++ b/app/mailers/emails/issues.rb @@ -8,6 +8,8 @@ module Emails from: sender(@issue.author_id), to: recipient(recipient_id), subject: subject("#{@issue.title} (##{@issue.iid})")) + + sent_notification!(@issue, recipient_id) end def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id, updated_by_user_id) @@ -19,6 +21,8 @@ module Emails from: sender(updated_by_user_id), to: recipient(recipient_id), subject: subject("#{@issue.title} (##{@issue.iid})")) + + sent_notification!(@issue, recipient_id) end def closed_issue_email(recipient_id, issue_id, updated_by_user_id) @@ -30,6 +34,8 @@ module Emails from: sender(updated_by_user_id), to: recipient(recipient_id), subject: subject("#{@issue.title} (##{@issue.iid})")) + + sent_notification!(@issue, recipient_id) end def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id) @@ -42,6 +48,8 @@ module Emails from: sender(updated_by_user_id), to: recipient(recipient_id), subject: subject("#{@issue.title} (##{@issue.iid})")) + + sent_notification!(@issue, recipient_id) end end end diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb index 512a8f7ea6b..7a685c04ebc 100644 --- a/app/mailers/emails/merge_requests.rb +++ b/app/mailers/emails/merge_requests.rb @@ -10,6 +10,8 @@ module Emails from: sender(@merge_request.author_id), to: recipient(recipient_id), subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) + + sent_notification!(@merge_request, recipient_id) end def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id, updated_by_user_id) @@ -23,6 +25,8 @@ module Emails from: sender(updated_by_user_id), to: recipient(recipient_id), subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) + + sent_notification!(@merge_request, recipient_id) end def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id) @@ -36,6 +40,8 @@ module Emails from: sender(updated_by_user_id), to: recipient(recipient_id), subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) + + sent_notification!(@merge_request, recipient_id) end def merged_merge_request_email(recipient_id, merge_request_id, updated_by_user_id) @@ -48,6 +54,8 @@ module Emails from: sender(updated_by_user_id), to: recipient(recipient_id), subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) + + sent_notification!(@merge_request, recipient_id) end def merge_request_status_email(recipient_id, merge_request_id, status, updated_by_user_id) @@ -58,11 +66,12 @@ module Emails @target_url = namespace_project_merge_request_url(@project.namespace, @project, @merge_request) - set_reference("merge_request_#{merge_request_id}") mail_answer_thread(@merge_request, from: sender(updated_by_user_id), to: recipient(recipient_id), subject: subject("#{@merge_request.title} (##{@merge_request.iid}) #{@mr_status}")) + + sent_notification!(@merge_request, recipient_id) end end diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb index ff251209e01..2612074a5ff 100644 --- a/app/mailers/emails/notes.rb +++ b/app/mailers/emails/notes.rb @@ -11,6 +11,8 @@ module Emails from: sender(@note.author_id), to: recipient(recipient_id), subject: subject("#{@commit.title} (#{@commit.short_id})")) + + sent_notification!(@commit, recipient_id) end def note_issue_email(recipient_id, note_id) @@ -24,6 +26,8 @@ module Emails from: sender(@note.author_id), to: recipient(recipient_id), subject: subject("#{@issue.title} (##{@issue.iid})")) + + sent_notification!(@issue, recipient_id) end def note_merge_request_email(recipient_id, note_id) @@ -38,6 +42,8 @@ module Emails from: sender(@note.author_id), to: recipient(recipient_id), subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) + + sent_notification!(@merge_request, recipient_id) end end end diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 79fb48b00d3..44df3d6407d 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -46,6 +46,17 @@ class Notify < ActionMailer::Base allowed_domains end + def sent_notification!(noteable, recipient_id) + return unless reply_key + + SentNotification.create( + project: noteable.project, + noteable: noteable, + recipient_id: recipient_id, + reply_key: reply_key + ) + end + private # The default email address to send emails from @@ -85,14 +96,6 @@ class Notify < ActionMailer::Base @current_user.notification_email end - # Set the References header field - # - # local_part - The local part of the referenced message ID - # - def set_reference(local_part) - headers["References"] = "<#{local_part}@#{Gitlab.config.gitlab.host}>" - end - # Formats arguments into a String suitable for use as an email subject # # extra - Extra Strings to be inserted into the subject @@ -130,10 +133,23 @@ class Notify < ActionMailer::Base # with headers suitable for grouping by thread in email clients. # # See: mail_answer_thread - def mail_new_thread(model, headers = {}, &block) + def mail_new_thread(model, headers = {}) + if @project + headers['X-GitLab-Project'] = @project.name + headers['X-GitLab-Project-Id'] = @project.id + headers['X-GitLab-Project-Path'] = @project.path_with_namespace + end + + headers["X-GitLab-#{model.class.name}-ID"] = model.id + headers['Message-ID'] = message_id(model) - headers['X-GitLab-Project'] = "#{@project.name} | " if @project - mail(headers, &block) + + if reply_key + headers['X-GitLab-Reply-Key'] = reply_key + headers['Reply-To'] = Gitlab.config.reply_by_email.address.gsub('%{reply_key}', reply_key) + end + + mail(headers) end # Send an email that responds to an existing conversation thread, @@ -144,19 +160,21 @@ class Notify < ActionMailer::Base # * have a subject that begin by 'Re: ' # * have a 'In-Reply-To' or 'References' header that references the original 'Message-ID' # - def mail_answer_thread(model, headers = {}, &block) + def mail_answer_thread(model, headers = {}) + headers['Message-ID'] = SecureRandom.hex headers['In-Reply-To'] = message_id(model) headers['References'] = message_id(model) - headers['X-GitLab-Project'] = "#{@project.name} | " if @project - if headers[:subject] - headers[:subject].prepend('Re: ') - end - - mail(headers, &block) + mail_new_thread(model, headers) end def can? Ability.abilities.allowed?(user, action, subject) end + + def reply_key + return nil unless Gitlab.config.reply_by_email.enabled + + @reply_key ||= SecureRandom.hex(16) + end end diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb new file mode 100644 index 00000000000..a3d24669b52 --- /dev/null +++ b/app/models/sent_notification.rb @@ -0,0 +1,29 @@ +class SentNotification < ActiveRecord::Base + belongs_to :project + belongs_to :noteable, polymorphic: true + belongs_to :recipient, class_name: "User" + + validate :project, :recipient, :reply_key, presence: true + validate :reply_key, uniqueness: true + + validates :noteable_id, presence: true, if: ->(n) { n.noteable_type.present? && n.noteable_type != 'Commit' } + validates :commit_id, presence: true, if: ->(n) { n.noteable_type == 'Commit' } + + def self.for(reply_key) + find_by(reply_key: reply_key) + end + + def for_commit? + noteable_type == "Commit" + end + + def noteable + if for_commit? + project.commit(commit_id) + else + super + end + rescue + nil + end +end diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb new file mode 100644 index 00000000000..e44a430f6bc --- /dev/null +++ b/app/workers/email_receiver_worker.rb @@ -0,0 +1,22 @@ +class EmailReceiverWorker + include Sidekiq::Worker + + sidekiq_options queue: :incoming_email + + def perform(raw) + return unless Gitlab.config.reply_by_email.enabled + + # begin + Gitlab::EmailReceiver.new(raw).process + # rescue => e + # handle_failure(raw, e) + # end + end + + private + + def handle_failure(raw, e) + # TODO: Handle better. + Rails.logger.warn("Email can not be processed: #{e}\n\n#{raw}") + end +end diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 56770335ddc..e78429b29a3 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -94,6 +94,11 @@ production: &base # The default is 'tmp/repositories' relative to the root of the Rails app. # repository_downloads_path: tmp/repositories + ## Reply by email + reply_by_email: + enabled: false + address: "replies+%{reply_key}@gitlab.example.com" + ## Gravatar ## For Libravatar see: http://doc.gitlab.com/ce/customization/libravatar.html gravatar: diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 026c1a5792c..9e83660e103 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -149,6 +149,12 @@ Settings.gitlab.default_projects_features['visibility_level'] = Settings.send Settings.gitlab['repository_downloads_path'] = File.absolute_path(Settings.gitlab['repository_downloads_path'] || 'tmp/repositories', Rails.root) Settings.gitlab['restricted_signup_domains'] ||= [] +# +# Reply by email +# +Settings['reply_by_email'] ||= Settingslogic.new({}) +Settings.reply_by_email['enabled'] = false if Settings.gravatar['enabled'].nil? + # # Gravatar # diff --git a/config/mail_room.yml.example b/config/mail_room.yml.example new file mode 100644 index 00000000000..a7a6a25362e --- /dev/null +++ b/config/mail_room.yml.example @@ -0,0 +1,14 @@ +:mailboxes: + # - + # :host: "imap.gmail.com" + # :port: 993 + # :ssl: true + # :email: "replies@gitlab.example.com" + # :password: "password" + # :name: "inbox" + # :delivery_method: sidekiq + # :delivery_options: + # :redis_url: redis://localhost:6379 + # :namespace: resque:gitlab + # :queue: incoming_email + # :worker: EmailReceiverWorker diff --git a/db/migrate/20150818213832_add_sent_notifications.rb b/db/migrate/20150818213832_add_sent_notifications.rb new file mode 100644 index 00000000000..43e8d6a1a82 --- /dev/null +++ b/db/migrate/20150818213832_add_sent_notifications.rb @@ -0,0 +1,13 @@ +class AddSentNotifications < ActiveRecord::Migration + def change + create_table :sent_notifications do |t| + t.references :project + t.references :noteable, polymorphic: true + t.references :recipient + t.string :commit_id + t.string :reply_key, null: false + end + + add_index :sent_notifications, :reply_key, unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 6e919f2883b..0827e810f18 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150806104937) do +ActiveRecord::Schema.define(version: 20150818213832) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -404,6 +404,17 @@ ActiveRecord::Schema.define(version: 20150806104937) do add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree + create_table "sent_notifications", force: true do |t| + t.integer "project_id" + t.integer "noteable_id" + t.string "noteable_type" + t.integer "recipient_id" + t.string "commit_id" + t.string "reply_key", null: false + end + + add_index "sent_notifications", ["reply_key"], name: "index_sent_notifications_on_reply_key", unique: true, using: :btree + create_table "services", force: true do |t| t.string "type" t.string "title" diff --git a/lib/gitlab/email_receiver.rb b/lib/gitlab/email_receiver.rb new file mode 100644 index 00000000000..a9f67bb5da0 --- /dev/null +++ b/lib/gitlab/email_receiver.rb @@ -0,0 +1,50 @@ +module Gitlab + class EmailReceiver + def initialize(raw) + @raw = raw + end + + def message + @message ||= Mail::Message.new(@raw) + end + + def process + return unless message && sent_notification + + Notes::CreateService.new( + sent_notification.project, + sent_notification.recipient, + note: message.text_part.to_s, + noteable_type: sent_notification.noteable_type, + noteable_id: sent_notification.noteable_id, + commit_id: sent_notification.commit_id + ).execute + end + + private + + def reply_key + address = Gitlab.config.reply_by_email.address + return nil unless address + + regex = Regexp.escape(address) + regex = regex.gsub(Regexp.escape('%{reply_key}'), "(.*)") + regex = Regexp.new(regex) + + address = message.to.find { |address| address =~ regex } + return nil unless address + + match = address.match(regex) + + return nil unless match && match[1].present? + + match[1] + end + + def sent_notification + return nil unless reply_key + + SentNotification.for(reply_key) + end + end +end -- cgit v1.2.1 From a428838dc00dd95f5dad0e433ad2eb73df2eff0f Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 18 Aug 2015 15:50:46 -0700 Subject: Update mail_room --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 3aca2c8890b..597eb4cde05 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: git://github.com/DouweM/mail_room.git - revision: e1795b807f492533ad40afcb80abf870f1baddb5 + revision: b9deb6430ca13b2d44676827c45476d76966922c branch: sidekiq specs: mail_room (0.3.1) -- cgit v1.2.1 From 8906cabae7a6be44cafcedcaf27352614fcc462b Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 18 Aug 2015 17:02:26 -0700 Subject: Changes and stuff. --- Gemfile | 2 + Gemfile.lock | 2 + app/mailers/notify.rb | 10 ++- app/models/sent_notification.rb | 8 +- app/workers/email_receiver_worker.rb | 2 +- config/initializers/1_settings.rb | 2 +- lib/gitlab/email_html_cleaner.rb | 133 ++++++++++++++++++++++++++++++++ lib/gitlab/email_receiver.rb | 144 ++++++++++++++++++++++++++++++----- lib/gitlab/reply_by_email.rb | 47 ++++++++++++ 9 files changed, 320 insertions(+), 30 deletions(-) create mode 100644 lib/gitlab/email_html_cleaner.rb create mode 100644 lib/gitlab/reply_by_email.rb diff --git a/Gemfile b/Gemfile index e3f76671f5b..9879141f5cb 100644 --- a/Gemfile +++ b/Gemfile @@ -274,3 +274,5 @@ gem "newrelic_rpm" gem 'octokit', '3.7.0' gem "mail_room", github: "DouweM/mail_room", branch: "sidekiq" + +gem 'email_reply_parser' diff --git a/Gemfile.lock b/Gemfile.lock index 597eb4cde05..629d128b424 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -163,6 +163,7 @@ GEM dotenv (0.9.0) dropzonejs-rails (0.7.1) rails (> 3.1) + email_reply_parser (0.5.8) email_spec (1.6.0) launchy (~> 2.1) mail (~> 2.2) @@ -780,6 +781,7 @@ DEPENDENCIES diffy (~> 3.0.3) doorkeeper (= 2.1.3) dropzonejs-rails + email_reply_parser email_spec (~> 1.6.0) enumerize factory_girl_rails diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 44df3d6407d..c2ea99d9688 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -146,7 +146,7 @@ class Notify < ActionMailer::Base if reply_key headers['X-GitLab-Reply-Key'] = reply_key - headers['Reply-To'] = Gitlab.config.reply_by_email.address.gsub('%{reply_key}', reply_key) + headers['Reply-To'] = Gitlab::ReplyByEmail.reply_address(reply_key) end mail(headers) @@ -165,6 +165,10 @@ class Notify < ActionMailer::Base headers['In-Reply-To'] = message_id(model) headers['References'] = message_id(model) + if headers[:subject] + headers[:subject].prepend('Re: ') + end + mail_new_thread(model, headers) end @@ -173,8 +177,6 @@ class Notify < ActionMailer::Base end def reply_key - return nil unless Gitlab.config.reply_by_email.enabled - - @reply_key ||= SecureRandom.hex(16) + @reply_key ||= Gitlab::ReplyByEmail.reply_key end end diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb index a3d24669b52..23a1b19ea7c 100644 --- a/app/models/sent_notification.rb +++ b/app/models/sent_notification.rb @@ -6,8 +6,8 @@ class SentNotification < ActiveRecord::Base validate :project, :recipient, :reply_key, presence: true validate :reply_key, uniqueness: true - validates :noteable_id, presence: true, if: ->(n) { n.noteable_type.present? && n.noteable_type != 'Commit' } - validates :commit_id, presence: true, if: ->(n) { n.noteable_type == 'Commit' } + validates :noteable_id, presence: true, unless: :for_commit? + validates :commit_id, presence: true, if: :for_commit? def self.for(reply_key) find_by(reply_key: reply_key) @@ -19,11 +19,9 @@ class SentNotification < ActiveRecord::Base def noteable if for_commit? - project.commit(commit_id) + project.commit(commit_id) rescue nil else super end - rescue - nil end end diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb index e44a430f6bc..94e346b5a51 100644 --- a/app/workers/email_receiver_worker.rb +++ b/app/workers/email_receiver_worker.rb @@ -4,7 +4,7 @@ class EmailReceiverWorker sidekiq_options queue: :incoming_email def perform(raw) - return unless Gitlab.config.reply_by_email.enabled + return unless Gitlab::ReplyByEmail.enabled? # begin Gitlab::EmailReceiver.new(raw).process diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 9e83660e103..654de6238d0 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -153,7 +153,7 @@ Settings.gitlab['restricted_signup_domains'] ||= [] # Reply by email # Settings['reply_by_email'] ||= Settingslogic.new({}) -Settings.reply_by_email['enabled'] = false if Settings.gravatar['enabled'].nil? +Settings.reply_by_email['enabled'] = false if Settings.reply_by_email['enabled'].nil? # # Gravatar diff --git a/lib/gitlab/email_html_cleaner.rb b/lib/gitlab/email_html_cleaner.rb new file mode 100644 index 00000000000..6d7a17fe87c --- /dev/null +++ b/lib/gitlab/email_html_cleaner.rb @@ -0,0 +1,133 @@ +# Taken mostly from Discourse's Email::HtmlCleaner +module Gitlab + # HtmlCleaner cleans up the extremely dirty HTML that many email clients + # generate by stripping out any excess divs or spans, removing styling in + # the process (which also makes the html more suitable to be parsed as + # Markdown). + class EmailHtmlCleaner + # Elements to hoist all children out of + HTML_HOIST_ELEMENTS = %w(div span font table tbody th tr td) + # Node types to always delete + HTML_DELETE_ELEMENT_TYPES = [ + Nokogiri::XML::Node::DTD_NODE, + Nokogiri::XML::Node::COMMENT_NODE, + ] + + # Private variables: + # @doc - nokogiri document + # @out - same as @doc, but only if trimming has occured + def initialize(html) + if html.is_a?(String) + @doc = Nokogiri::HTML(html) + else + @doc = html + end + end + + class << self + # EmailHtmlCleaner.trim(inp, opts={}) + # + # Arguments: + # inp - Either a HTML string or a Nokogiri document. + # Options: + # :return => :doc, :string + # Specify the desired return type. + # Defaults to the type of the input. + # A value of :string is equivalent to calling get_document_text() + # on the returned document. + def trim(inp, opts={}) + cleaner = EmailHtmlCleaner.new(inp) + + opts[:return] ||= (inp.is_a?(String) ? :string : :doc) + + if opts[:return] == :string + cleaner.output_html + else + cleaner.output_document + end + end + + # EmailHtmlCleaner.get_document_text(doc) + # + # Get the body portion of the document, including html, as a string. + def get_document_text(doc) + body = doc.xpath('//body') + if body + body.inner_html + else + doc.inner_html + end + end + end + + def output_document + @out ||= begin + doc = @doc + trim_process_node doc + add_newlines doc + doc + end + end + + def output_html + EmailHtmlCleaner.get_document_text(output_document) + end + + private + + def add_newlines(doc) + # Replace
tags with a markdown \n + doc.xpath('//br').each do |br| + br.replace(new_linebreak_node doc, 2) + end + # Surround

tags with newlines, to help with line-wise postprocessing + # and ensure markdown paragraphs + doc.xpath('//p').each do |p| + p.before(new_linebreak_node doc) + p.after(new_linebreak_node doc, 2) + end + end + + def new_linebreak_node(doc, count=1) + Nokogiri::XML::Text.new("\n" * count, doc) + end + + def trim_process_node(node) + if should_hoist?(node) + hoisted = trim_hoist_element node + hoisted.each { |child| trim_process_node child } + elsif should_delete?(node) + node.remove + else + if children = node.children + children.each { |child| trim_process_node child } + end + end + + node + end + + def trim_hoist_element(element) + hoisted = [] + element.children.each do |child| + element.before(child) + hoisted << child + end + element.remove + hoisted + end + + def should_hoist?(node) + return false unless node.element? + HTML_HOIST_ELEMENTS.include? node.name + end + + def should_delete?(node) + return true if HTML_DELETE_ELEMENT_TYPES.include? node.type + return true if node.element? && node.name == 'head' + return true if node.text? && node.text.strip.blank? + + false + end + end +end diff --git a/lib/gitlab/email_receiver.rb b/lib/gitlab/email_receiver.rb index a9f67bb5da0..18a06d2ee8c 100644 --- a/lib/gitlab/email_receiver.rb +++ b/lib/gitlab/email_receiver.rb @@ -1,44 +1,69 @@ +# Inspired in great part by Discourse's Email::Receiver module Gitlab class EmailReceiver + class ProcessingError < StandardError; end + class EmailUnparsableError < ProcessingError; end + class EmptyEmailError < ProcessingError; end + class UserNotFoundError < ProcessingError; end + class UserNotAuthorizedLevelError < ProcessingError; end + class NoteableNotFoundError < ProcessingError; end + class AutoGeneratedEmailError < ProcessingError; end + class SentNotificationNotFound < ProcessingError; end + class InvalidNote < ProcessingError; end + def initialize(raw) @raw = raw end def message @message ||= Mail::Message.new(@raw) + rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError => e + raise EmailUnparsableError, e end def process - return unless message && sent_notification + raise EmptyEmailError if @raw.blank? + + raise AutoGeneratedEmailError if message.header.to_s =~ /auto-(generated|replied)/ + + raise SentNotificationNotFound unless sent_notification + + author = sent_notification.recipient + + raise UserNotFoundError unless author + + project = sent_notification.project + + raise UserNotAuthorizedLevelError unless author.can?(:create_note, project) + + raise NoteableNotFoundError unless sent_notification.noteable + + body = parse_body(message) - Notes::CreateService.new( - sent_notification.project, - sent_notification.recipient, - note: message.text_part.to_s, + note = Notes::CreateService.new( + project, + author, + note: body, noteable_type: sent_notification.noteable_type, noteable_id: sent_notification.noteable_id, commit_id: sent_notification.commit_id ).execute + + unless note.persisted? + raise InvalidNote, note.errors.full_messages.join("\n") + end end private def reply_key - address = Gitlab.config.reply_by_email.address - return nil unless address - - regex = Regexp.escape(address) - regex = regex.gsub(Regexp.escape('%{reply_key}'), "(.*)") - regex = Regexp.new(regex) - - address = message.to.find { |address| address =~ regex } - return nil unless address + reply_key = nil + message.to.each do |address| + reply_key = Gitlab::ReplyByEmail.reply_key_from_address(address) + break if reply_key + end - match = address.match(regex) - - return nil unless match && match[1].present? - - match[1] + reply_key end def sent_notification @@ -46,5 +71,86 @@ module Gitlab SentNotification.for(reply_key) end + + def parse_body(message) + body = select_body(message) + + encoding = body.encoding + raise EmptyEmailError if body.strip.blank? + + body = discourse_email_trimmer(body) + raise EmptyEmailError if body.strip.blank? + + body = EmailReplyParser.parse_reply(body) + raise EmptyEmailError if body.strip.blank? + + body.force_encoding(encoding).encode("UTF-8") + end + + def select_body(message) + html = nil + text = nil + + if message.multipart? + html = fix_charset(message.html_part) + text = fix_charset(message.text_part) + elsif message.content_type =~ /text\/html/ + html = fix_charset(message) + end + + # prefer plain text + return text if text + + if html + body = EmailHtmlCleaner.new(html).output_html + else + body = fix_charset(message) + end + + # Certain trigger phrases that means we didn't parse correctly + if body =~ /(Content\-Type\:|multipart\/alternative|text\/plain)/ + raise EmptyEmailError + end + + body + end + + # Force encoding to UTF-8 on a Mail::Message or Mail::Part + def fix_charset(object) + return nil if object.nil? + + if object.charset + object.body.decoded.force_encoding(object.charset.gsub(/utf8/i, "UTF-8")).encode("UTF-8").to_s + else + object.body.to_s + end + rescue + nil + end + + REPLYING_HEADER_LABELS = %w(From Sent To Subject Reply To Cc Bcc Date) + REPLYING_HEADER_REGEX = Regexp.union(REPLYING_HEADER_LABELS.map { |label| "#{label}:" }) + + def discourse_email_trimmer(body) + lines = body.scrub.lines.to_a + range_end = 0 + + lines.each_with_index do |l, idx| + break if l =~ /\A\s*\-{3,80}\s*\z/ || + # This one might be controversial but so many reply lines have years, times and end with a colon. + # Let's try it and see how well it works. + (l =~ /\d{4}/ && l =~ /\d:\d\d/ && l =~ /\:$/) || + (l =~ /On \w+ \d+,? \d+,?.*wrote:/) + + # Headers on subsequent lines + break if (0..2).all? { |off| lines[idx+off] =~ REPLYING_HEADER_REGEX } + # Headers on the same line + break if REPLYING_HEADER_LABELS.count { |label| l.include?(label) } >= 3 + + range_end = idx + end + + lines[0..range_end].join.strip + end end end diff --git a/lib/gitlab/reply_by_email.rb b/lib/gitlab/reply_by_email.rb new file mode 100644 index 00000000000..b6157de3610 --- /dev/null +++ b/lib/gitlab/reply_by_email.rb @@ -0,0 +1,47 @@ +module Gitlab + module ReplyByEmail + class << self + def enabled? + config.enabled && + config.address && + config.address.include?("%{reply_key}") + end + + def reply_key + return nil unless enabled? + + SecureRandom.hex(16) + end + + def reply_address(reply_key) + config.address.gsub('%{reply_key}', reply_key) + end + + def reply_key_from_address(address) + return unless address_regex + + match = address.match(address_regex) + return unless match + + match[1] + end + + private + + def config + Gitlab.config.reply_by_email + end + + def address_regex + @address_regex ||= begin + wildcard_address = config.address + return nil unless wildcard_address + + regex = Regexp.escape(wildcard_address) + regex = regex.gsub(Regexp.escape('%{reply_key}'), "(.+)") + Regexp.new(regex).freeze + end + end + end + end +end -- cgit v1.2.1 From 73eef57d5e3dfc4a60e3e661c04a9172b94f1535 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 10:16:30 -0700 Subject: Make MR notification email subject more standard. --- app/mailers/emails/merge_requests.rb | 43 +----------------------------------- 1 file changed, 1 insertion(+), 42 deletions(-) diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb index 7a685c04ebc..76902b58f32 100644 --- a/app/mailers/emails/merge_requests.rb +++ b/app/mailers/emails/merge_requests.rb @@ -69,50 +69,9 @@ module Emails mail_answer_thread(@merge_request, from: sender(updated_by_user_id), to: recipient(recipient_id), - subject: subject("#{@merge_request.title} (##{@merge_request.iid}) #{@mr_status}")) + subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) sent_notification!(@merge_request, recipient_id) end end - - # Over rides default behaviour to show source/target - # Formats arguments into a String suitable for use as an email subject - # - # extra - Extra Strings to be inserted into the subject - # - # Examples - # - # >> subject('Lorem ipsum') - # => "GitLab Merge Request | Lorem ipsum" - # - # # Automatically inserts Project name: - # Forked MR - # => source project => - # => target project => - # => source branch => source - # => target branch => target - # >> subject('Lorem ipsum') - # => "GitLab Merge Request | Ruby on Rails:source >> My Ror:target | Lorem ipsum " - # - # Non Forked MR - # => source project => - # => target project => - # => source branch => source - # => target branch => target - # >> subject('Lorem ipsum') - # => "GitLab Merge Request | Ruby on Rails | source >> target | Lorem ipsum " - # # Accepts multiple arguments - # >> subject('Lorem ipsum', 'Dolor sit amet') - # => "GitLab Merge Request | Lorem ipsum | Dolor sit amet" - def subject(*extra) - subject = "Merge Request | " - if @merge_request.for_fork? - subject << "#{@merge_request.source_project.name_with_namespace}:#{merge_request.source_branch} >> #{@merge_request.target_project.name_with_namespace}:#{merge_request.target_branch}" - else - subject << "#{@merge_request.source_project.name_with_namespace} | #{merge_request.source_branch} >> #{merge_request.target_branch}" - end - subject << " | " + extra.join(' | ') if extra.present? - subject - end - end -- cgit v1.2.1 From 4f34d363a532613df60f3e566644c082ed3fa395 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 10:17:20 -0700 Subject: Correctly set Message-ID for comment notifications. --- app/mailers/notify.rb | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index c2ea99d9688..e893009c728 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -129,11 +129,7 @@ class Notify < ActionMailer::Base "<#{model_name}_#{model.id}@#{Gitlab.config.gitlab.host}>" end - # Send an email that starts a new conversation thread, - # with headers suitable for grouping by thread in email clients. - # - # See: mail_answer_thread - def mail_new_thread(model, headers = {}) + def mail_thread(model, headers = {}) if @project headers['X-GitLab-Project'] = @project.name headers['X-GitLab-Project-Id'] = @project.id @@ -142,8 +138,6 @@ class Notify < ActionMailer::Base headers["X-GitLab-#{model.class.name}-ID"] = model.id - headers['Message-ID'] = message_id(model) - if reply_key headers['X-GitLab-Reply-Key'] = reply_key headers['Reply-To'] = Gitlab::ReplyByEmail.reply_address(reply_key) @@ -152,6 +146,16 @@ class Notify < ActionMailer::Base mail(headers) end + # Send an email that starts a new conversation thread, + # with headers suitable for grouping by thread in email clients. + # + # See: mail_answer_thread + def mail_new_thread(model, headers = {}) + headers['Message-ID'] = message_id(model) + + mail_thread(model, headers) + end + # Send an email that responds to an existing conversation thread, # with headers suitable for grouping by thread in email clients. # @@ -165,11 +169,9 @@ class Notify < ActionMailer::Base headers['In-Reply-To'] = message_id(model) headers['References'] = message_id(model) - if headers[:subject] - headers[:subject].prepend('Re: ') - end + headers[:subject].prepend('Re: ') if headers[:subject] - mail_new_thread(model, headers) + mail_thread(model, headers) end def can? -- cgit v1.2.1 From 6b31827a41b3a057569d8476f9e8d5a22980cc16 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 10:17:32 -0700 Subject: Include display name with reply to address. --- app/mailers/notify.rb | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index e893009c728..8a386de9a39 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.gitlab.email_reply_to + default reply_to: Proc.new { default_reply_to_address.format } # Just send email with 2 seconds delay def self.delay @@ -66,6 +66,13 @@ class Notify < ActionMailer::Base address end + # The default email address to send emails from + def default_reply_to_address + address = Mail::Address.new(Gitlab.config.gitlab.email_reply_to) + address.display_name = Gitlab.config.gitlab.email_display_name + address + end + def can_send_from_user_email?(sender) sender_domain = sender.email.split("@").last self.class.allowed_email_domains.include?(sender_domain) @@ -140,7 +147,13 @@ class Notify < ActionMailer::Base if reply_key headers['X-GitLab-Reply-Key'] = reply_key - headers['Reply-To'] = Gitlab::ReplyByEmail.reply_address(reply_key) + + address = Mail::Address.new(Gitlab::ReplyByEmail.reply_address(reply_key)) + address.display_name = @project.name_with_namespace + + headers['Reply-To'] = address + + @reply_by_email = true end mail(headers) -- cgit v1.2.1 From 01c5ecedd405fed29dbae11039ecb1a90f170c28 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 10:17:51 -0700 Subject: Add "Reply to this email directly" to notification footer. --- app/views/layouts/notify.html.haml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml index c8662a15adb..ec209c38eed 100644 --- a/app/views/layouts/notify.html.haml +++ b/app/views/layouts/notify.html.haml @@ -36,7 +36,11 @@ — %br - if @target_url - #{link_to "View it on GitLab", @target_url} + - if @reply_by_email + Reply to this email directly or + #{link_to "view it on GitLab", @target_url}. + - else + #{link_to "View it on GitLab", @target_url} = email_action @target_url - if @project && !@disable_footer You're receiving this notification because you are a member of the #{link_to_unless @target_url, @project.name_with_namespace, namespace_project_url(@project.namespace, @project)} project team. -- cgit v1.2.1 From ee1eb2948d30365be241bed6a68b29da57495af8 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 10:18:05 -0700 Subject: Turn reply-by-email attachments into uploads. --- app/services/projects/upload_service.rb | 6 +++--- lib/gitlab/email_receiver.rb | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/app/services/projects/upload_service.rb b/app/services/projects/upload_service.rb index 992a7a7a1dc..279550d6f4a 100644 --- a/app/services/projects/upload_service.rb +++ b/app/services/projects/upload_service.rb @@ -13,9 +13,9 @@ module Projects filename = uploader.image? ? uploader.file.basename : uploader.file.filename { - 'alt' => filename, - 'url' => uploader.secure_url, - 'is_image' => uploader.image? + alt: filename, + url: uploader.secure_url, + is_image: uploader.image? } end diff --git a/lib/gitlab/email_receiver.rb b/lib/gitlab/email_receiver.rb index 18a06d2ee8c..3dd8942a262 100644 --- a/lib/gitlab/email_receiver.rb +++ b/lib/gitlab/email_receiver.rb @@ -40,6 +40,10 @@ module Gitlab body = parse_body(message) + upload_attachments.each do |link| + body << "\n\n#{link}" + end + note = Notes::CreateService.new( project, author, @@ -152,5 +156,34 @@ module Gitlab lines[0..range_end].join.strip end + + def upload_attachments + attachments = [] + + message.attachments.each do |attachment| + tmp = Tempfile.new("gitlab-email-attachment") + begin + File.open(tmp.path, "w+b") { |f| f.write attachment.body.decoded } + + file = { + tempfile: tmp, + filename: attachment.filename, + content_type: attachment.content_type + } + + link = ::Projects::UploadService.new(sent_notification.project, file).execute + if link + text = "[#{link[:alt]}](#{link[:url]})" + text.prepend("!") if link[:is_image] + + attachments << text + end + ensure + tmp.close! + end + end + + attachments + end end end -- cgit v1.2.1 From 170aa3b43b5186f73b149eae6b80b96a9b1171b2 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 10:18:20 -0700 Subject: Update mail_room. --- Gemfile.lock | 2 +- Procfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 629d128b424..c4c327aa758 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: git://github.com/DouweM/mail_room.git - revision: b9deb6430ca13b2d44676827c45476d76966922c + revision: c17c74cdef55dcd962333545fdb4d6e496cd9050 branch: sidekiq specs: mail_room (0.3.1) diff --git a/Procfile b/Procfile index 51154f150d1..0b58566e2fe 100644 --- a/Procfile +++ b/Procfile @@ -1,3 +1,3 @@ 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 common -q default -mail_room: bundle exec mail_room -c config/mail_room.yml +mail_room: bundle exec mail_room -q -c config/mail_room.yml -- cgit v1.2.1 From 76dbafba86dda96b7ba2f93fc7e07eea3ca48302 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 11:10:21 -0700 Subject: Send a rejection email when the incoming email couldn't be processed. --- app/mailers/email_rejection_mailer.rb | 35 ++++++++++++++++++++ .../email_rejection_mailer/rejection.html.haml | 5 +++ .../email_rejection_mailer/rejection.text.haml | 4 +++ app/workers/email_receiver_worker.rb | 37 ++++++++++++++++++---- lib/gitlab/email_receiver.rb | 16 ++++++---- 5 files changed, 85 insertions(+), 12 deletions(-) create mode 100644 app/mailers/email_rejection_mailer.rb create mode 100644 app/views/email_rejection_mailer/rejection.html.haml create mode 100644 app/views/email_rejection_mailer/rejection.text.haml diff --git a/app/mailers/email_rejection_mailer.rb b/app/mailers/email_rejection_mailer.rb new file mode 100644 index 00000000000..f29c4e052fc --- /dev/null +++ b/app/mailers/email_rejection_mailer.rb @@ -0,0 +1,35 @@ +class EmailRejectionMailer < ActionMailer::Base + add_template_helper ApplicationHelper + add_template_helper GitlabMarkdownHelper + + helper_method :current_user, :can? + + default from: "#{Gitlab.config.gitlab.email_display_name} <#{Gitlab.config.gitlab.email_from}>" + default reply_to: "#{Gitlab.config.gitlab.email_display_name} <#{Gitlab.config.gitlab.email_reply_to}>" + + def rejection(reason, original_raw, can_retry = false) + @reason = reason + @original_message = Mail::Message.new(original_raw) + + headers = { + to: @original_message.from, + subject: "[Rejected] #{@original_message.subject}" + } + + headers['Message-ID'] = SecureRandom.hex + headers['In-Reply-To'] = @original_message.message_id + headers['References'] = @original_message.message_id + + headers['Reply-To'] = @original_message.to.first if can_retry + + mail(headers) + end + + def current_user + nil + end + + def can? + false + end +end diff --git a/app/views/email_rejection_mailer/rejection.html.haml b/app/views/email_rejection_mailer/rejection.html.haml new file mode 100644 index 00000000000..de29d399d32 --- /dev/null +++ b/app/views/email_rejection_mailer/rejection.html.haml @@ -0,0 +1,5 @@ +%p + Unfortunately, your email message to GitLab could not be processed. + +%p + = gfm @reason diff --git a/app/views/email_rejection_mailer/rejection.text.haml b/app/views/email_rejection_mailer/rejection.text.haml new file mode 100644 index 00000000000..6693e6f90e8 --- /dev/null +++ b/app/views/email_rejection_mailer/rejection.text.haml @@ -0,0 +1,4 @@ +Unfortunately, your email message to GitLab could not be processed. + + += @reason diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb index 94e346b5a51..2cfd64cefad 100644 --- a/app/workers/email_receiver_worker.rb +++ b/app/workers/email_receiver_worker.rb @@ -6,17 +6,42 @@ class EmailReceiverWorker def perform(raw) return unless Gitlab::ReplyByEmail.enabled? - # begin - Gitlab::EmailReceiver.new(raw).process - # rescue => e - # handle_failure(raw, e) - # end + begin + Gitlab::EmailReceiver.new(raw).execute + rescue => e + handle_failure(raw, e) + end end private def handle_failure(raw, e) - # TODO: Handle better. Rails.logger.warn("Email can not be processed: #{e}\n\n#{raw}") + + can_retry = false + reason = nil + + case e + when Gitlab::EmailReceiver::SentNotificationNotFound + reason = "We couldn't figure out what the email is in reply to. Please create your comment through the web interface." + when Gitlab::EmailReceiver::EmptyEmailError + can_retry = true + reason = "It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies." + when Gitlab::EmailReceiver::AutoGeneratedEmailError + reason = "The email was marked as 'auto generated', which we can't accept. Please create your comment through the web interface." + when Gitlab::EmailReceiver::UserNotFoundError + reason = "We couldn't figure out what user corresponds to the email. Please create your comment through the web interface." + when Gitlab::EmailReceiver::UserNotAuthorizedError + reason = "You are not allowed to respond to the thread you are replying to. If you believe this is in error, contact a staff member." + when Gitlab::EmailReceiver::NoteableNotFoundError + reason = "The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member." + when Gitlab::EmailReceiver::InvalidNote + can_retry = true + reason = e.message + else + return + end + + EmailRejectionMailer.delay.rejection(reason, raw, can_retry) end end diff --git a/lib/gitlab/email_receiver.rb b/lib/gitlab/email_receiver.rb index 3dd8942a262..a0b4ff87e02 100644 --- a/lib/gitlab/email_receiver.rb +++ b/lib/gitlab/email_receiver.rb @@ -5,7 +5,7 @@ module Gitlab class EmailUnparsableError < ProcessingError; end class EmptyEmailError < ProcessingError; end class UserNotFoundError < ProcessingError; end - class UserNotAuthorizedLevelError < ProcessingError; end + class UserNotAuthorizedError < ProcessingError; end class NoteableNotFoundError < ProcessingError; end class AutoGeneratedEmailError < ProcessingError; end class SentNotificationNotFound < ProcessingError; end @@ -21,20 +21,20 @@ module Gitlab raise EmailUnparsableError, e end - def process + def execute + raise SentNotificationNotFound unless sent_notification + raise EmptyEmailError if @raw.blank? raise AutoGeneratedEmailError if message.header.to_s =~ /auto-(generated|replied)/ - raise SentNotificationNotFound unless sent_notification - author = sent_notification.recipient raise UserNotFoundError unless author project = sent_notification.project - raise UserNotAuthorizedLevelError unless author.can?(:create_note, project) + raise UserNotAuthorizedError unless author.can?(:create_note, project) raise NoteableNotFoundError unless sent_notification.noteable @@ -54,7 +54,11 @@ module Gitlab ).execute unless note.persisted? - raise InvalidNote, note.errors.full_messages.join("\n") + message = "The comment could not be created for the following reasons:" + note.errors.full_messages.each do |error| + message << "\n\n- #{error}" + end + raise InvalidNote, message end end -- cgit v1.2.1 From 03b54f1f0324c4d424d66e4bf36e42c2a88fc26d Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 11:35:13 -0700 Subject: Fix responding to commit notes --- app/mailers/notify.rb | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 8a386de9a39..b4c83f8c5fc 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -49,11 +49,21 @@ class Notify < ActionMailer::Base def sent_notification!(noteable, recipient_id) return unless reply_key + noteable_id = nil + commit_id = nil + if noteable.is_a?(Commit) + commit_id = noteable.id + else + noteable_id = noteable.id + end + SentNotification.create( - project: noteable.project, - noteable: noteable, - recipient_id: recipient_id, - reply_key: reply_key + project: noteable.project, + noteable_type: noteable.class.name, + noteable_id: noteable_id, + commit_id: commit_id, + recipient_id: recipient_id, + reply_key: reply_key ) end -- cgit v1.2.1 From a8a861ae2a122b310d1aca6f9f9b1d0601b8c49f Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 12:35:08 -0700 Subject: Add BaseMailer to house shared mailer functionality. --- app/mailers/base_mailer.rb | 50 +++++++++++++++++++++++++++++++++++ app/mailers/email_rejection_mailer.rb | 18 +------------ app/mailers/notify.rb | 33 +---------------------- 3 files changed, 52 insertions(+), 49 deletions(-) create mode 100644 app/mailers/base_mailer.rb diff --git a/app/mailers/base_mailer.rb b/app/mailers/base_mailer.rb new file mode 100644 index 00000000000..38b8a296842 --- /dev/null +++ b/app/mailers/base_mailer.rb @@ -0,0 +1,50 @@ +class EmailRejectionMailer < ActionMailer::Base + add_template_helper ApplicationHelper + add_template_helper GitlabMarkdownHelper + + attr_accessor :current_user + helper_method :current_user, :can? + + default from: Proc.new { default_sender_address.format } + default reply_to: Proc.new { default_reply_to_address.format } + + def self.delay + delay_for(2.seconds) + end + + def rejection(reason, original_raw, can_retry = false) + @reason = reason + @original_message = Mail::Message.new(original_raw) + + headers = { + to: @original_message.from, + subject: "[Rejected] #{@original_message.subject}" + } + + headers['Message-ID'] = SecureRandom.hex + headers['In-Reply-To'] = @original_message.message_id + headers['References'] = @original_message.message_id + + headers['Reply-To'] = @original_message.to.first if can_retry + + mail(headers) + end + + def can? + Ability.abilities.allowed?(user, action, subject) + end + + private + + def default_sender_address + address = Mail::Address.new(Gitlab.config.gitlab.email_from) + address.display_name = Gitlab.config.gitlab.email_display_name + address + end + + def default_reply_to_address + address = Mail::Address.new(Gitlab.config.gitlab.email_reply_to) + address.display_name = Gitlab.config.gitlab.email_display_name + address + end +end diff --git a/app/mailers/email_rejection_mailer.rb b/app/mailers/email_rejection_mailer.rb index f29c4e052fc..89aceda82d1 100644 --- a/app/mailers/email_rejection_mailer.rb +++ b/app/mailers/email_rejection_mailer.rb @@ -1,12 +1,4 @@ -class EmailRejectionMailer < ActionMailer::Base - add_template_helper ApplicationHelper - add_template_helper GitlabMarkdownHelper - - helper_method :current_user, :can? - - default from: "#{Gitlab.config.gitlab.email_display_name} <#{Gitlab.config.gitlab.email_from}>" - default reply_to: "#{Gitlab.config.gitlab.email_display_name} <#{Gitlab.config.gitlab.email_reply_to}>" - +class EmailRejectionMailer < BaseMailer def rejection(reason, original_raw, can_retry = false) @reason = reason @original_message = Mail::Message.new(original_raw) @@ -24,12 +16,4 @@ class EmailRejectionMailer < ActionMailer::Base mail(headers) end - - def current_user - nil - end - - def can? - false - end end diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index b4c83f8c5fc..1a2286f9d47 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -1,4 +1,4 @@ -class Notify < ActionMailer::Base +class Notify < BaseMailer include ActionDispatch::Routing::PolymorphicRoutes include Emails::Issues @@ -8,22 +8,9 @@ class Notify < ActionMailer::Base include Emails::Profile include Emails::Groups - add_template_helper ApplicationHelper - add_template_helper GitlabMarkdownHelper add_template_helper MergeRequestsHelper add_template_helper EmailsHelper - attr_accessor :current_user - helper_method :current_user, :can? - - default from: Proc.new { default_sender_address.format } - default reply_to: Proc.new { default_reply_to_address.format } - - # Just send email with 2 seconds delay - def self.delay - delay_for(2.seconds) - end - def test_email(recipient_email, subject, body) mail(to: recipient_email, subject: subject, @@ -69,20 +56,6 @@ class Notify < ActionMailer::Base private - # The default email address to send emails from - def default_sender_address - address = Mail::Address.new(Gitlab.config.gitlab.email_from) - address.display_name = Gitlab.config.gitlab.email_display_name - address - end - - # The default email address to send emails from - def default_reply_to_address - address = Mail::Address.new(Gitlab.config.gitlab.email_reply_to) - address.display_name = Gitlab.config.gitlab.email_display_name - address - end - def can_send_from_user_email?(sender) sender_domain = sender.email.split("@").last self.class.allowed_email_domains.include?(sender_domain) @@ -197,10 +170,6 @@ class Notify < ActionMailer::Base mail_thread(model, headers) end - def can? - Ability.abilities.allowed?(user, action, subject) - end - def reply_key @reply_key ||= Gitlab::ReplyByEmail.reply_key end -- cgit v1.2.1 From 992dbbd9fe619ab3002f7c0c552657da8269df49 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 12:35:23 -0700 Subject: Move `sent_notification!` out of Notify. --- app/mailers/emails/issues.rb | 8 ++++---- app/mailers/emails/merge_requests.rb | 10 +++++----- app/mailers/emails/notes.rb | 6 +++--- app/mailers/notify.rb | 21 --------------------- app/models/sent_notification.rb | 27 +++++++++++++++++++++++++-- 5 files changed, 37 insertions(+), 35 deletions(-) diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb index c8b7775c328..2c035fbb70b 100644 --- a/app/mailers/emails/issues.rb +++ b/app/mailers/emails/issues.rb @@ -9,7 +9,7 @@ module Emails to: recipient(recipient_id), subject: subject("#{@issue.title} (##{@issue.iid})")) - sent_notification!(@issue, recipient_id) + SentNotification.record(@issue, recipient_id, reply_key) end def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id, updated_by_user_id) @@ -22,7 +22,7 @@ module Emails to: recipient(recipient_id), subject: subject("#{@issue.title} (##{@issue.iid})")) - sent_notification!(@issue, recipient_id) + SentNotification.record(@issue, recipient_id, reply_key) end def closed_issue_email(recipient_id, issue_id, updated_by_user_id) @@ -35,7 +35,7 @@ module Emails to: recipient(recipient_id), subject: subject("#{@issue.title} (##{@issue.iid})")) - sent_notification!(@issue, recipient_id) + SentNotification.record(@issue, recipient_id, reply_key) end def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id) @@ -49,7 +49,7 @@ module Emails to: recipient(recipient_id), subject: subject("#{@issue.title} (##{@issue.iid})")) - sent_notification!(@issue, recipient_id) + SentNotification.record(@issue, recipient_id, reply_key) end end end diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb index 76902b58f32..7923fb770d0 100644 --- a/app/mailers/emails/merge_requests.rb +++ b/app/mailers/emails/merge_requests.rb @@ -11,7 +11,7 @@ module Emails to: recipient(recipient_id), subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) - sent_notification!(@merge_request, recipient_id) + SentNotification.record(@merge_request, recipient_id, reply_key) end def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id, updated_by_user_id) @@ -26,7 +26,7 @@ module Emails to: recipient(recipient_id), subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) - sent_notification!(@merge_request, recipient_id) + SentNotification.record(@merge_request, recipient_id, reply_key) end def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id) @@ -41,7 +41,7 @@ module Emails to: recipient(recipient_id), subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) - sent_notification!(@merge_request, recipient_id) + SentNotification.record(@merge_request, recipient_id, reply_key) end def merged_merge_request_email(recipient_id, merge_request_id, updated_by_user_id) @@ -55,7 +55,7 @@ module Emails to: recipient(recipient_id), subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) - sent_notification!(@merge_request, recipient_id) + SentNotification.record(@merge_request, recipient_id, reply_key) end def merge_request_status_email(recipient_id, merge_request_id, status, updated_by_user_id) @@ -71,7 +71,7 @@ module Emails to: recipient(recipient_id), subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) - sent_notification!(@merge_request, recipient_id) + SentNotification.record(@merge_request, recipient_id, reply_key) end end end diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb index 2612074a5ff..63d4aca61af 100644 --- a/app/mailers/emails/notes.rb +++ b/app/mailers/emails/notes.rb @@ -12,7 +12,7 @@ module Emails to: recipient(recipient_id), subject: subject("#{@commit.title} (#{@commit.short_id})")) - sent_notification!(@commit, recipient_id) + SentNotification.record(@commit, recipient_id, reply_key) end def note_issue_email(recipient_id, note_id) @@ -27,7 +27,7 @@ module Emails to: recipient(recipient_id), subject: subject("#{@issue.title} (##{@issue.iid})")) - sent_notification!(@issue, recipient_id) + SentNotification.record(@issue, recipient_id, reply_key) end def note_merge_request_email(recipient_id, note_id) @@ -43,7 +43,7 @@ module Emails to: recipient(recipient_id), subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) - sent_notification!(@merge_request, recipient_id) + SentNotification.record(@merge_request, recipient_id, reply_key) end end end diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 1a2286f9d47..5717c89e61d 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -33,27 +33,6 @@ class Notify < BaseMailer allowed_domains end - def sent_notification!(noteable, recipient_id) - return unless reply_key - - noteable_id = nil - commit_id = nil - if noteable.is_a?(Commit) - commit_id = noteable.id - else - noteable_id = noteable.id - end - - SentNotification.create( - project: noteable.project, - noteable_type: noteable.class.name, - noteable_id: noteable_id, - commit_id: commit_id, - recipient_id: recipient_id, - reply_key: reply_key - ) - end - private def can_send_from_user_email?(sender) diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb index 23a1b19ea7c..460ca40be3f 100644 --- a/app/models/sent_notification.rb +++ b/app/models/sent_notification.rb @@ -9,8 +9,31 @@ class SentNotification < ActiveRecord::Base validates :noteable_id, presence: true, unless: :for_commit? validates :commit_id, presence: true, if: :for_commit? - def self.for(reply_key) - find_by(reply_key: reply_key) + class << self + def for(reply_key) + find_by(reply_key: reply_key) + end + + def record(noteable, recipient_id, reply_key) + return unless reply_key + + noteable_id = nil + commit_id = nil + if noteable.is_a?(Commit) + commit_id = noteable.id + else + noteable_id = noteable.id + end + + create( + project: noteable.project, + noteable_type: noteable.class.name, + noteable_id: noteable_id, + commit_id: commit_id, + recipient_id: recipient_id, + reply_key: reply_key + ) + end end def for_commit? -- cgit v1.2.1 From ed6122d7ea1d7ffa1e89078cedf416c6ae2e0383 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 12:37:34 -0700 Subject: Markdown, not GFM. --- app/views/email_rejection_mailer/rejection.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/email_rejection_mailer/rejection.html.haml b/app/views/email_rejection_mailer/rejection.html.haml index de29d399d32..84b040d8cbc 100644 --- a/app/views/email_rejection_mailer/rejection.html.haml +++ b/app/views/email_rejection_mailer/rejection.html.haml @@ -2,4 +2,4 @@ Unfortunately, your email message to GitLab could not be processed. %p - = gfm @reason + = markdown @reason -- cgit v1.2.1 From 4e33127b3a72d507e093fd5cc520089c88640892 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 12:38:08 -0700 Subject: We'll get the

for free. --- app/views/email_rejection_mailer/rejection.html.haml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/views/email_rejection_mailer/rejection.html.haml b/app/views/email_rejection_mailer/rejection.html.haml index 84b040d8cbc..7f7d841fe21 100644 --- a/app/views/email_rejection_mailer/rejection.html.haml +++ b/app/views/email_rejection_mailer/rejection.html.haml @@ -1,5 +1,4 @@ %p Unfortunately, your email message to GitLab could not be processed. -%p - = markdown @reason += markdown @reason -- cgit v1.2.1 From 0bc94198fc64f7a0f9f7ee7d33dcae9aa50d1e3f Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 12:51:42 -0700 Subject: Fix BaseMailer. --- app/mailers/base_mailer.rb | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/app/mailers/base_mailer.rb b/app/mailers/base_mailer.rb index 38b8a296842..8299a4140dc 100644 --- a/app/mailers/base_mailer.rb +++ b/app/mailers/base_mailer.rb @@ -1,4 +1,4 @@ -class EmailRejectionMailer < ActionMailer::Base +class BaseMailer < ActionMailer::Base add_template_helper ApplicationHelper add_template_helper GitlabMarkdownHelper @@ -12,24 +12,6 @@ class EmailRejectionMailer < ActionMailer::Base delay_for(2.seconds) end - def rejection(reason, original_raw, can_retry = false) - @reason = reason - @original_message = Mail::Message.new(original_raw) - - headers = { - to: @original_message.from, - subject: "[Rejected] #{@original_message.subject}" - } - - headers['Message-ID'] = SecureRandom.hex - headers['In-Reply-To'] = @original_message.message_id - headers['References'] = @original_message.message_id - - headers['Reply-To'] = @original_message.to.first if can_retry - - mail(headers) - end - def can? Ability.abilities.allowed?(user, action, subject) end -- cgit v1.2.1 From 089aa2de2badcb4d6caf2d2001ad17db42d08de8 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 13:57:27 -0700 Subject: Add Reply by email feature to admin dashboard. --- app/views/admin/dashboard/index.html.haml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index 3732ff847b9..54191aadda6 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -55,6 +55,10 @@ OmniAuth %span.light.pull-right = boolean_to_icon Gitlab.config.omniauth.enabled + %p + Reply by email + %span.light.pull-right + = boolean_to_icon Gitlab::ReplyByEmail.enabled? .col-md-4 %h4 Components -- cgit v1.2.1 From f3b57ce677f016fbbbcc6426e33b2b61c34c068f Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 13:58:07 -0700 Subject: Update init scripts. --- Procfile | 2 +- bin/background_jobs | 2 +- bin/mail_room | 51 +++++++++++++++++ lib/support/init.d/gitlab | 95 ++++++++++++++++++++++++------- lib/support/init.d/gitlab.default.example | 9 +++ 5 files changed, 137 insertions(+), 22 deletions(-) create mode 100755 bin/mail_room diff --git a/Procfile b/Procfile index 0b58566e2fe..18fd9eb3d92 100644 --- a/Procfile +++ b/Procfile @@ -1,3 +1,3 @@ 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 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 diff --git a/bin/background_jobs b/bin/background_jobs index a041a4b0433..a4895cf6586 100755 --- a/bin/background_jobs +++ b/bin/background_jobs @@ -37,7 +37,7 @@ start_no_deamonize() start_sidekiq() { - bundle exec sidekiq -q post_receive -q mailer -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q common -q default -e $RAILS_ENV -P $sidekiq_pidfile $@ >> $sidekiq_logfile 2>&1 + 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 common -q default -e $RAILS_ENV -P $sidekiq_pidfile $@ >> $sidekiq_logfile 2>&1 } load_ok() diff --git a/bin/mail_room b/bin/mail_room new file mode 100755 index 00000000000..0fabfa778b7 --- /dev/null +++ b/bin/mail_room @@ -0,0 +1,51 @@ +#!/bin/sh + +cd $(dirname $0)/.. +app_root=$(pwd) + +mail_room_pidfile="$app_root/tmp/pids/mail_room.pid" +mail_room_config="$app_root/config/mail_room.yml" + +get_mail_room_pid() +{ + local pid=$(cat $mail_room_pidfile) + if [ -z "$pid" ] ; then + echo "Could not find a PID in $mail_room_pidfile" + exit 1 + fi + mail_room_pid=$pid +} + +start() +{ + bundle exec mail_room -q -c $mail_room_config + PID=$! + echo $PID > $mail_room_pidfile +} + +stop() +{ + get_mail_room_pid + kill -QUIT $mail_room_pid +} + +reload() +{ + get_mail_room_pid + kill -USR2 $mail_room_pid +} + +case "$1" in + start) + start + ;; + stop) + stop + ;; + reload) + reload + ;; + *) + echo "Usage: $0 {start|stop|reload}" + ;; +esac diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab index a3455728a94..57478434e09 100755 --- a/lib/support/init.d/gitlab +++ b/lib/support/init.d/gitlab @@ -35,6 +35,8 @@ pid_path="$app_root/tmp/pids" socket_path="$app_root/tmp/sockets" web_server_pid_path="$pid_path/unicorn.pid" sidekiq_pid_path="$pid_path/sidekiq.pid" +mail_room_enabled=false +mail_room_pid_path="$pid_path/mail_room.pid" shell_path="/bin/bash" # Read configuration variable file if it is present @@ -70,13 +72,20 @@ check_pids(){ else spid=0 fi + if [ "$mail_room_enabled" = true ]; then + if [ -f "$mail_room_pid_path" ]; then + mpid=$(cat "$mail_room_pid_path") + else + mpid=0 + fi + fi } ## Called when we have started the two processes and are waiting for their pid files. wait_for_pids(){ # We are sleeping a bit here mostly because sidekiq is slow at writing it's pid i=0; - while [ ! -f $web_server_pid_path -o ! -f $sidekiq_pid_path ]; do + while [ ! -f $web_server_pid_path -o ! -f $sidekiq_pid_path -o [ "$mail_room_enabled" = true && ! -f $mail_room_pid_path ] ]; do sleep 0.1; i=$((i+1)) if [ $((i%10)) = 0 ]; then @@ -111,7 +120,13 @@ check_status(){ else sidekiq_status="-1" fi - if [ $web_status = 0 -a $sidekiq_status = 0 ]; then + if [ "$mail_room_enabled" = true && $mpid -ne 0 ]; then + kill -0 "$mpid" 2>/dev/null + mail_room_status="$?" + else + mail_room_status="-1" + fi + if [ $web_status = 0 -a $sidekiq_status = 0 -a [ "$mail_room_enabled" != true || $mail_room_status = 0 ] ]; then gitlab_status=0 else # http://refspecs.linuxbase.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html @@ -139,12 +154,19 @@ check_stale_pids(){ exit 1 fi fi + if [ "$mail_room_enabled" = true && "$mpid" != "0" -a "$mail_room_status" != "0" ]; then + echo "Removing stale MailRoom job dispatcher pid. This is most likely caused by MailRoom crashing the last time it ran." + if ! rm "$mail_room_pid_path"; then + echo "Unable to remove stale pid, exiting" + exit 1 + fi + fi } ## If no parts of the service is running, bail out. exit_if_not_running(){ check_stale_pids - if [ "$web_status" != "0" -a "$sidekiq_status" != "0" ]; then + if [ "$web_status" != "0" -a "$sidekiq_status" != "0" -a [ "$mail_room_enabled" = true && "$mail_room_status" != "0" ] ]; then echo "GitLab is not running." exit fi @@ -154,13 +176,15 @@ exit_if_not_running(){ start_gitlab() { check_stale_pids - if [ "$web_status" != "0" -a "$sidekiq_status" != "0" ]; then - echo -n "Starting both the GitLab Unicorn and Sidekiq" - elif [ "$web_status" != "0" ]; then + if [ "$web_status" != "0" ]; then echo -n "Starting GitLab Unicorn" - elif [ "$sidekiq_status" != "0" ]; then + fi + if [ "$sidekiq_status" != "0" ]; then echo -n "Starting GitLab Sidekiq" fi + if [ "$mail_room_enabled" = true && "$mail_room_status" != "0" ]; then + echo -n "Starting GitLab MailRoom" + fi # Then check if the service is running. If it is: don't start again. if [ "$web_status" = "0" ]; then @@ -179,22 +203,33 @@ start_gitlab() { RAILS_ENV=$RAILS_ENV bin/background_jobs start & fi + if [ "$mail_room_enabled" = true ]; then + # If MailRoom is already running, don't start it again. + if [ "$mail_room_status" = "0" ]; then + echo "The MailRoom email processor is already running with pid $spid, not restarting" + else + RAILS_ENV=$RAILS_ENV bin/mail_room start & + fi + fi + # Wait for the pids to be planted wait_for_pids # Finally check the status to tell wether or not GitLab is running print_status } -## Asks the Unicorn and the Sidekiq if they would be so kind as to stop, if not kills them. +## Asks Unicorn, Sidekiq and MailRoom if they would be so kind as to stop, if not kills them. stop_gitlab() { exit_if_not_running - if [ "$web_status" = "0" -a "$sidekiq_status" = "0" ]; then - echo -n "Shutting down both Unicorn and Sidekiq" - elif [ "$web_status" = "0" ]; then - echo -n "Shutting down Unicorn" - elif [ "$sidekiq_status" = "0" ]; then - echo -n "Shutting down Sidekiq" + if [ "$web_status" = "0" ]; then + echo -n "Shutting down GitLab Unicorn" + fi + if [ "$sidekiq_status" = "0" ]; then + echo -n "Shutting down GitLab Sidekiq" + fi + if [ "$mail_room_enabled" = true && "$mail_room_status" = "0" ]; then + echo -n "Shutting down GitLab MailRoom" fi # If the Unicorn web server is running, tell it to stop; @@ -205,13 +240,17 @@ stop_gitlab() { if [ "$sidekiq_status" = "0" ]; then RAILS_ENV=$RAILS_ENV bin/background_jobs stop fi + # And do the same thing for the MailRoom. + if [ "$mail_room_enabled" = true && "$mail_room_status" = "0" ]; then + RAILS_ENV=$RAILS_ENV bin/mail_room stop + fi # If something needs to be stopped, lets wait for it to stop. Never use SIGKILL in a script. - while [ "$web_status" = "0" -o "$sidekiq_status" = "0" ]; do + while [ "$web_status" = "0" -o "$sidekiq_status" = "0" -o [ "$mail_room_enabled" = true && "$mail_room_status" = "0" ] ]; do sleep 1 check_status printf "." - if [ "$web_status" != "0" -a "$sidekiq_status" != "0" ]; then + if [ "$web_status" != "0" -a "$sidekiq_status" != "0" -a [ "$mail_room_enabled" != true || "$mail_room_status" != "0" ] ]; then printf "\n" break fi @@ -220,7 +259,10 @@ stop_gitlab() { sleep 1 # Cleaning up unused pids rm "$web_server_pid_path" 2>/dev/null - # rm "$sidekiq_pid_path" # Sidekiq seems to be cleaning up it's own pid. + # rm "$sidekiq_pid_path" 2>/dev/null # Sidekiq seems to be cleaning up it's own pid. + if [ "$mail_room_enabled" = true ]; then + rm "$mail_room_pid_path" 2>/dev/null + fi print_status } @@ -228,7 +270,7 @@ stop_gitlab() { ## Prints the status of GitLab and it's components. print_status() { check_status - if [ "$web_status" != "0" -a "$sidekiq_status" != "0" ]; then + if [ "$web_status" != "0" -a "$sidekiq_status" != "0" -a [ "$mail_room_enabled" != true || "$mail_room_status" != "0" ] ]; then echo "GitLab is not running." return fi @@ -242,7 +284,14 @@ print_status() { else printf "The GitLab Sidekiq job dispatcher is \033[31mnot running\033[0m.\n" fi - if [ "$web_status" = "0" -a "$sidekiq_status" = "0" ]; then + if [ "$mail_room_enabled" = true ]; then + if [ "$mail_room_status" = "0" ]; then + echo "The GitLab MailRoom email processor with pid $spid is running." + else + printf "The GitLab MailRoom email processor is \033[31mnot running\033[0m.\n" + fi + end + if [ "$web_status" = "0" -a "$sidekiq_status" = "0" -a [ "$mail_room_enabled" != true || "$mail_room_status" = "0" ] ]; then printf "GitLab and all its components are \033[32mup and running\033[0m.\n" fi } @@ -257,9 +306,15 @@ reload_gitlab(){ printf "Reloading GitLab Unicorn configuration... " RAILS_ENV=$RAILS_ENV bin/web reload echo "Done." + echo "Restarting GitLab Sidekiq since it isn't capable of reloading its config..." RAILS_ENV=$RAILS_ENV bin/background_jobs restart + if [ "$mail_room_enabled" != true ]; then + echo "Restarting GitLab MailRoom since it isn't capable of reloading its config..." + RAILS_ENV=$RAILS_ENV bin/mail_room restart + fi + wait_for_pids print_status } @@ -267,7 +322,7 @@ reload_gitlab(){ ## Restarts Sidekiq and Unicorn. restart_gitlab(){ check_status - if [ "$web_status" = "0" -o "$sidekiq_status" = "0" ]; then + if [ "$web_status" = "0" -o "$sidekiq_status" = "0" -o [ "$mail_room_enabled" = true && "$mail_room_status" = "0" ] ]; then stop_gitlab fi start_gitlab diff --git a/lib/support/init.d/gitlab.default.example b/lib/support/init.d/gitlab.default.example index cf7f4198cbf..fd70cb7cc74 100755 --- a/lib/support/init.d/gitlab.default.example +++ b/lib/support/init.d/gitlab.default.example @@ -30,6 +30,15 @@ web_server_pid_path="$pid_path/unicorn.pid" # The default is "$pid_path/sidekiq.pid" sidekiq_pid_path="$pid_path/sidekiq.pid" +# mail_room_enabled specifies whether mail_room, which is used to process incoming email, is enabled. +# This is required for the Reply by email feature. +# The default is "false" +mail_room_enabled=false + +# mail_room_pid_path defines the path in which to create the pid file for mail_room +# The default is "$pid_path/mail_room.pid" +mail_room_pid_path="$pid_path/mail_room.pid" + # shell_path defines the path of shell for "$app_user" in case you are using # shell other than "bash" # The default is "/bin/bash" -- cgit v1.2.1 From 34026c97b4d55026c414e839af926af3ffeb31cc Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 13:58:14 -0700 Subject: Add documentation. --- config/gitlab.yml.example | 2 ++ config/mail_room.yml.example | 37 ++++++++++++------- doc/README.md | 1 + doc/reply_by_email/README.md | 85 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 112 insertions(+), 13 deletions(-) create mode 100644 doc/reply_by_email/README.md diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index e78429b29a3..c7b60a1d4b1 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -95,6 +95,8 @@ production: &base # repository_downloads_path: tmp/repositories ## Reply by email + # Allow users to comment on issues and merge requests by replying to notification emails. + # For documentation on how to set this up, see http://doc.gitlab.com/ce/reply_by_email/README.md reply_by_email: enabled: false address: "replies+%{reply_key}@gitlab.example.com" diff --git a/config/mail_room.yml.example b/config/mail_room.yml.example index a7a6a25362e..28366eb7394 100644 --- a/config/mail_room.yml.example +++ b/config/mail_room.yml.example @@ -1,14 +1,25 @@ :mailboxes: - # - - # :host: "imap.gmail.com" - # :port: 993 - # :ssl: true - # :email: "replies@gitlab.example.com" - # :password: "password" - # :name: "inbox" - # :delivery_method: sidekiq - # :delivery_options: - # :redis_url: redis://localhost:6379 - # :namespace: resque:gitlab - # :queue: incoming_email - # :worker: EmailReceiverWorker + - + # # IMAP server host + # :host: "imap.gmail.com" + # # IMAP server port + # :port: 993 + # # Whether the IMAP server uses SSL + # :ssl: true + # # Email account username. Usually the full email address. + # :email: "replies@gitlab.example.com" + # # Email account password + # :password: "password" + # # The name of the mailbox where incoming mail will end up. Usually "inbox". + # :name: "inbox" + # # Always "sidekiq". + # :delivery_method: sidekiq + # :delivery_options: + # # The URL to the Redis server used by Sidekiq. Should match the URL in config/resque.yml. + # :redis_url: redis://localhost:6379 + # # Always "resque:gitlab". + # :namespace: resque:gitlab + # # Always "incoming_email". + # :queue: incoming_email + # # Always "EmailReceiverWorker" + # :worker: EmailReceiverWorker diff --git a/doc/README.md b/doc/README.md index 0524fda3ed6..337c4e6a62d 100644 --- a/doc/README.md +++ b/doc/README.md @@ -29,6 +29,7 @@ - [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed. - [Update](update/README.md) Update guides to upgrade your installation. - [Welcome message](customization/welcome_message.md) Add a custom welcome message to the sign-in page. +- [Reply by email](reply_by_email/README.md) Allow users to comment on issues and merge requests by replying to notification emails. ## Contributor documentation diff --git a/doc/reply_by_email/README.md b/doc/reply_by_email/README.md new file mode 100644 index 00000000000..fffc120d2bb --- /dev/null +++ b/doc/reply_by_email/README.md @@ -0,0 +1,85 @@ +# Reply by email + +GitLab can be set up to allow users to comment on issues and merge requests by replying to notification emails. + +In order to do this, you need access to an IMAP-enabled email account, with a provider or server that supports [email sub-addressing](https://en.wikipedia.org/wiki/Email_address#Sub-addressing). Sub-addressing is a feature where any email to `user+some_arbitrary_tag@example.com` will end up in the mailbox for `user@example.com`, and is supported by providers such as Gmail, Yahoo! Mail, Outlook.com and iCloud, as well as the [Postfix](http://www.postfix.org/) mail server which you can run on-premises. + +## Set it up + +In this example, we'll use the Gmail address `gitlab-replies@gmail.com`. If you're actually using Gmail with Reply by email, make sure you have [IMAP access enabled](https://support.google.com/mail/troubleshooter/1668960?hl=en#ts=1665018) and [allow less secure apps to access the account](https://support.google.com/accounts/answer/6010255). + +### Installations from source + + +1. Find the `reply_by_email` section in `config/gitlab.yml`, enable the feature and enter the email address including a placeholder for the `reply_key`: + + ```yaml + reply_by_email: + enabled: true + address: "gitlab-replies+%{reply_key}@gmail.com" + ``` + + As mentioned, the part after `+` is ignored, and this will end up in the mailbox for `gitlab-replies@gmail.com`. + +2. Find `config/mail_room.yml.example` and copy it to `config/mail_room.yml`: + + ```sh + cp config/mail_room.yml.example config/mail_room.yml + ``` + +3. Uncomment the configuration options in `config/mail_room.yml` and fill in the details for your specific IMAP server and email account: + + ```yaml + :mailboxes: + - + # IMAP server host + :host: "imap.gmail.com" + # IMAP server port + :port: 993 + # Whether the IMAP server uses SSL + :ssl: true + # Email account username. Usually the full email address. + :email: "gitlab-replies@gmail.com" + # Email account password + :password: "[REDACTED]" + # The name of the mailbox where incoming mail will end up. Usually "inbox". + :name: "inbox" + # Always "sidekiq". + :delivery_method: sidekiq + :delivery_options: + # The URL to the Redis server used by Sidekiq. Should match the URL in config/resque.yml. + :redis_url: redis://localhost:6379 + # Always "resque:gitlab". + :namespace: resque:gitlab + # Always "incoming_email". + :queue: incoming_email + # Always "EmailReceiverWorker" + :worker: EmailReceiverWorker + ``` + + +4. Find `lib/support/init.d/gitlab.default.example` and copy it to `/etc/default/gitlab`: + + ```sh + sudo cp lib/support/init.d/gitlab.default.example /etc/default/gitlab + ``` + +5. Edit `/etc/default/gitlab` to enable `mail_room`: + + ```sh + mail_room_enabled=true + ``` + +6. Restart GitLab + + ```sh + sudo service gitlab restart + ``` + +8. Reply by email should now be working. + +Note: If you're running GitLab in development mode and using `foreman`, make sure to also uncomment the `mail_room` line in your `Procfile`. + +### Omnibus package installations + +TODO -- cgit v1.2.1 From c50e5e68c7d4863c26700ced7ea31a18ddfc1c66 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 14:27:47 -0700 Subject: Fix bin/mail_room. --- bin/mail_room | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/bin/mail_room b/bin/mail_room index 0fabfa778b7..f4f1a170c04 100755 --- a/bin/mail_room +++ b/bin/mail_room @@ -4,6 +4,7 @@ cd $(dirname $0)/.. app_root=$(pwd) mail_room_pidfile="$app_root/tmp/pids/mail_room.pid" +mail_room_logfile="$app_root/log/mail_room.log" mail_room_config="$app_root/config/mail_room.yml" get_mail_room_pid() @@ -18,7 +19,7 @@ get_mail_room_pid() start() { - bundle exec mail_room -q -c $mail_room_config + bundle exec mail_room -q -c $mail_room_config >> $mail_room_logfile 2>&1 & PID=$! echo $PID > $mail_room_pidfile } @@ -26,13 +27,13 @@ start() stop() { get_mail_room_pid - kill -QUIT $mail_room_pid + kill -TERM $mail_room_pid } -reload() +restart() { - get_mail_room_pid - kill -USR2 $mail_room_pid + stop + start } case "$1" in @@ -42,10 +43,10 @@ case "$1" in stop) stop ;; - reload) - reload + restart) + restart ;; *) - echo "Usage: $0 {start|stop|reload}" + echo "Usage: $0 {start|stop|restart}" ;; esac -- cgit v1.2.1 From 1202875dff5a120c27842b80994b19a682d080f5 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 14:54:30 -0700 Subject: Fix lib/support/init.d/gitlab. --- lib/support/init.d/gitlab | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab index 57478434e09..3f13dfbfdfc 100755 --- a/lib/support/init.d/gitlab +++ b/lib/support/init.d/gitlab @@ -85,7 +85,7 @@ check_pids(){ wait_for_pids(){ # We are sleeping a bit here mostly because sidekiq is slow at writing it's pid i=0; - while [ ! -f $web_server_pid_path -o ! -f $sidekiq_pid_path -o [ "$mail_room_enabled" = true && ! -f $mail_room_pid_path ] ]; do + while [ ! -f $web_server_pid_path ] || [ ! -f $sidekiq_pid_path ] || { [ "$mail_room_enabled" = true ] && [ ! -f $mail_room_pid_path ] }; do sleep 0.1; i=$((i+1)) if [ $((i%10)) = 0 ]; then @@ -120,13 +120,15 @@ check_status(){ else sidekiq_status="-1" fi - if [ "$mail_room_enabled" = true && $mpid -ne 0 ]; then - kill -0 "$mpid" 2>/dev/null - mail_room_status="$?" - else - mail_room_status="-1" + if [ "$mail_room_enabled" = true ]; then + if [ $mpid -ne 0 ]; then + kill -0 "$mpid" 2>/dev/null + mail_room_status="$?" + else + mail_room_status="-1" + fi fi - if [ $web_status = 0 -a $sidekiq_status = 0 -a [ "$mail_room_enabled" != true || $mail_room_status = 0 ] ]; then + if [ $web_status = 0 ] && [ $sidekiq_status = 0 ] && { [ "$mail_room_enabled" != true ] || [ $mail_room_status = 0 ] }; then gitlab_status=0 else # http://refspecs.linuxbase.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html @@ -140,21 +142,21 @@ check_stale_pids(){ check_status # If there is a pid it is something else than 0, the service is running if # *_status is == 0. - if [ "$wpid" != "0" -a "$web_status" != "0" ]; then + if [ "$wpid" != "0" ] && [ "$web_status" != "0" ]; then echo "Removing stale Unicorn web server pid. This is most likely caused by the web server crashing the last time it ran." if ! rm "$web_server_pid_path"; then echo "Unable to remove stale pid, exiting." exit 1 fi fi - if [ "$spid" != "0" -a "$sidekiq_status" != "0" ]; then + if [ "$spid" != "0" ] && [ "$sidekiq_status" != "0" ]; then echo "Removing stale Sidekiq job dispatcher pid. This is most likely caused by Sidekiq crashing the last time it ran." if ! rm "$sidekiq_pid_path"; then echo "Unable to remove stale pid, exiting" exit 1 fi fi - if [ "$mail_room_enabled" = true && "$mpid" != "0" -a "$mail_room_status" != "0" ]; then + if [ "$mail_room_enabled" = true ] && [ "$mpid" != "0" ] && [ "$mail_room_status" != "0" ]; then echo "Removing stale MailRoom job dispatcher pid. This is most likely caused by MailRoom crashing the last time it ran." if ! rm "$mail_room_pid_path"; then echo "Unable to remove stale pid, exiting" @@ -166,7 +168,7 @@ check_stale_pids(){ ## If no parts of the service is running, bail out. exit_if_not_running(){ check_stale_pids - if [ "$web_status" != "0" -a "$sidekiq_status" != "0" -a [ "$mail_room_enabled" = true && "$mail_room_status" != "0" ] ]; then + if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ] }; then echo "GitLab is not running." exit fi @@ -182,7 +184,7 @@ start_gitlab() { if [ "$sidekiq_status" != "0" ]; then echo -n "Starting GitLab Sidekiq" fi - if [ "$mail_room_enabled" = true && "$mail_room_status" != "0" ]; then + if [ "$mail_room_enabled" = true ] && [ "$mail_room_status" != "0" ]; then echo -n "Starting GitLab MailRoom" fi @@ -206,7 +208,7 @@ start_gitlab() { if [ "$mail_room_enabled" = true ]; then # If MailRoom is already running, don't start it again. if [ "$mail_room_status" = "0" ]; then - echo "The MailRoom email processor is already running with pid $spid, not restarting" + echo "The MailRoom email processor is already running with pid $mpid, not restarting" else RAILS_ENV=$RAILS_ENV bin/mail_room start & fi @@ -228,7 +230,7 @@ stop_gitlab() { if [ "$sidekiq_status" = "0" ]; then echo -n "Shutting down GitLab Sidekiq" fi - if [ "$mail_room_enabled" = true && "$mail_room_status" = "0" ]; then + if [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; then echo -n "Shutting down GitLab MailRoom" fi @@ -241,16 +243,16 @@ stop_gitlab() { RAILS_ENV=$RAILS_ENV bin/background_jobs stop fi # And do the same thing for the MailRoom. - if [ "$mail_room_enabled" = true && "$mail_room_status" = "0" ]; then + if [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; then RAILS_ENV=$RAILS_ENV bin/mail_room stop fi # If something needs to be stopped, lets wait for it to stop. Never use SIGKILL in a script. - while [ "$web_status" = "0" -o "$sidekiq_status" = "0" -o [ "$mail_room_enabled" = true && "$mail_room_status" = "0" ] ]; do + while [ "$web_status" = "0" ] || [ "$sidekiq_status" = "0" ] || { [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ] }; do sleep 1 check_status printf "." - if [ "$web_status" != "0" -a "$sidekiq_status" != "0" -a [ "$mail_room_enabled" != true || "$mail_room_status" != "0" ] ]; then + if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ] }; then printf "\n" break fi @@ -270,7 +272,7 @@ stop_gitlab() { ## Prints the status of GitLab and it's components. print_status() { check_status - if [ "$web_status" != "0" -a "$sidekiq_status" != "0" -a [ "$mail_room_enabled" != true || "$mail_room_status" != "0" ] ]; then + if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ] }; then echo "GitLab is not running." return fi @@ -291,7 +293,7 @@ print_status() { printf "The GitLab MailRoom email processor is \033[31mnot running\033[0m.\n" fi end - if [ "$web_status" = "0" -a "$sidekiq_status" = "0" -a [ "$mail_room_enabled" != true || "$mail_room_status" = "0" ] ]; then + if [ "$web_status" = "0" ] && [ "$sidekiq_status" = "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" = "0" ] }; then printf "GitLab and all its components are \033[32mup and running\033[0m.\n" fi } @@ -322,7 +324,7 @@ reload_gitlab(){ ## Restarts Sidekiq and Unicorn. restart_gitlab(){ check_status - if [ "$web_status" = "0" -o "$sidekiq_status" = "0" -o [ "$mail_room_enabled" = true && "$mail_room_status" = "0" ] ]; then + if [ "$web_status" = "0" ] || [ "$sidekiq_status" = "0" ] || { [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ] }; then stop_gitlab fi start_gitlab -- cgit v1.2.1 From eacdd5080a4f62442e7ee885d4053562a4bb8acc Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 14:58:41 -0700 Subject: Fix Basemailer#can? --- app/mailers/base_mailer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/mailers/base_mailer.rb b/app/mailers/base_mailer.rb index 8299a4140dc..aedb0889185 100644 --- a/app/mailers/base_mailer.rb +++ b/app/mailers/base_mailer.rb @@ -13,7 +13,7 @@ class BaseMailer < ActionMailer::Base end def can? - Ability.abilities.allowed?(user, action, subject) + Ability.abilities.allowed?(current_user, action, subject) end private -- cgit v1.2.1 From 41fdd20c746f34a02d670dc2784cc279b2478cb7 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 16:05:58 -0700 Subject: Test Gitlab::ReplyByEmail. --- spec/lib/gitlab/reply_by_email_spec.rb | 86 ++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 spec/lib/gitlab/reply_by_email_spec.rb diff --git a/spec/lib/gitlab/reply_by_email_spec.rb b/spec/lib/gitlab/reply_by_email_spec.rb new file mode 100644 index 00000000000..a52af51e97c --- /dev/null +++ b/spec/lib/gitlab/reply_by_email_spec.rb @@ -0,0 +1,86 @@ +require "spec_helper" + +describe Gitlab::ReplyByEmail do + describe "self.enabled?" do + context "when reply by email is enabled" do + before do + allow(Gitlab.config.reply_by_email).to receive(:enabled).and_return(true) + end + + context "when the address is valid" do + before do + allow(Gitlab.config.reply_by_email).to receive(:address).and_return("replies+%{reply_key}@example.com") + end + + it "returns true" do + expect(described_class.enabled?).to be_truthy + end + end + + context "when the address is invalid" do + before do + allow(Gitlab.config.reply_by_email).to receive(:address).and_return("replies@example.com") + end + + it "returns false" do + expect(described_class.enabled?).to be_falsey + end + end + end + + context "when reply by email is disabled" do + before do + allow(Gitlab.config.reply_by_email).to receive(:enabled).and_return(false) + end + + it "returns false" do + expect(described_class.enabled?).to be_falsey + end + end + end + + describe "self.reply_key" do + context "when enabled" do + before do + allow(described_class).to receive(:enabled?).and_return(true) + end + + it "returns a random hex" do + key = described_class.reply_key + key2 = described_class.reply_key + + expect(key).not_to eq(key2) + end + end + + context "when disabled" do + before do + allow(described_class).to receive(:enabled?).and_return(false) + end + + it "returns nil" do + expect(described_class.reply_key).to be_nil + end + end + end + + context "self.reply_address" do + before do + allow(Gitlab.config.reply_by_email).to receive(:address).and_return("replies+%{reply_key}@example.com") + end + + it "returns the address with an interpolated reply key" do + expect(described_class.reply_address("key")).to eq("replies+key@example.com") + end + end + + context "self.reply_key_from_address" do + before do + allow(Gitlab.config.reply_by_email).to receive(:address).and_return("replies+%{reply_key}@example.com") + end + + it "returns reply key" do + expect(described_class.reply_key_from_address("replies+key@example.com")).to eq("key") + end + end +end -- cgit v1.2.1 From 9501495aeb512202dc4ce1a88afb39d66f3d39f1 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 17:43:24 -0700 Subject: Fix init.d script. --- lib/support/init.d/gitlab | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab index 3f13dfbfdfc..41a2f254db6 100755 --- a/lib/support/init.d/gitlab +++ b/lib/support/init.d/gitlab @@ -85,7 +85,7 @@ check_pids(){ wait_for_pids(){ # We are sleeping a bit here mostly because sidekiq is slow at writing it's pid i=0; - while [ ! -f $web_server_pid_path ] || [ ! -f $sidekiq_pid_path ] || { [ "$mail_room_enabled" = true ] && [ ! -f $mail_room_pid_path ] }; do + while [ ! -f $web_server_pid_path ] || [ ! -f $sidekiq_pid_path ] || { [ "$mail_room_enabled" = true ] && [ ! -f $mail_room_pid_path ]; }; do sleep 0.1; i=$((i+1)) if [ $((i%10)) = 0 ]; then @@ -128,7 +128,7 @@ check_status(){ mail_room_status="-1" fi fi - if [ $web_status = 0 ] && [ $sidekiq_status = 0 ] && { [ "$mail_room_enabled" != true ] || [ $mail_room_status = 0 ] }; then + if [ $web_status = 0 ] && [ $sidekiq_status = 0 ] && { [ "$mail_room_enabled" != true ] || [ $mail_room_status = 0 ]; }; then gitlab_status=0 else # http://refspecs.linuxbase.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html @@ -168,7 +168,7 @@ check_stale_pids(){ ## If no parts of the service is running, bail out. exit_if_not_running(){ check_stale_pids - if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ] }; then + if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; }; then echo "GitLab is not running." exit fi @@ -179,13 +179,13 @@ start_gitlab() { check_stale_pids if [ "$web_status" != "0" ]; then - echo -n "Starting GitLab Unicorn" + echo "Starting GitLab Unicorn" fi if [ "$sidekiq_status" != "0" ]; then - echo -n "Starting GitLab Sidekiq" + echo "Starting GitLab Sidekiq" fi if [ "$mail_room_enabled" = true ] && [ "$mail_room_status" != "0" ]; then - echo -n "Starting GitLab MailRoom" + echo "Starting GitLab MailRoom" fi # Then check if the service is running. If it is: don't start again. @@ -225,13 +225,13 @@ stop_gitlab() { exit_if_not_running if [ "$web_status" = "0" ]; then - echo -n "Shutting down GitLab Unicorn" + echo "Shutting down GitLab Unicorn" fi if [ "$sidekiq_status" = "0" ]; then - echo -n "Shutting down GitLab Sidekiq" + echo "Shutting down GitLab Sidekiq" fi if [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; then - echo -n "Shutting down GitLab MailRoom" + echo "Shutting down GitLab MailRoom" fi # If the Unicorn web server is running, tell it to stop; @@ -248,11 +248,11 @@ stop_gitlab() { fi # If something needs to be stopped, lets wait for it to stop. Never use SIGKILL in a script. - while [ "$web_status" = "0" ] || [ "$sidekiq_status" = "0" ] || { [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ] }; do + while [ "$web_status" = "0" ] || [ "$sidekiq_status" = "0" ] || { [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; }; do sleep 1 check_status printf "." - if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ] }; then + if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; }; then printf "\n" break fi @@ -272,7 +272,7 @@ stop_gitlab() { ## Prints the status of GitLab and it's components. print_status() { check_status - if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ] }; then + if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; }; then echo "GitLab is not running." return fi @@ -292,8 +292,8 @@ print_status() { else printf "The GitLab MailRoom email processor is \033[31mnot running\033[0m.\n" fi - end - if [ "$web_status" = "0" ] && [ "$sidekiq_status" = "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" = "0" ] }; then + fi + if [ "$web_status" = "0" ] && [ "$sidekiq_status" = "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" = "0" ]; }; then printf "GitLab and all its components are \033[32mup and running\033[0m.\n" fi } @@ -324,7 +324,7 @@ reload_gitlab(){ ## Restarts Sidekiq and Unicorn. restart_gitlab(){ check_status - if [ "$web_status" = "0" ] || [ "$sidekiq_status" = "0" ] || { [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ] }; then + if [ "$web_status" = "0" ] || [ "$sidekiq_status" = "0" ] || { [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; }; then stop_gitlab fi start_gitlab -- cgit v1.2.1 From 19a049ece9e350623e3adb141298c00344cab02d Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 17:43:36 -0700 Subject: Make documentation clearer. --- doc/install/installation.md | 2 +- doc/reply_by_email/README.md | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/doc/install/installation.md b/doc/install/installation.md index 202704088a6..c75e1d87862 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -370,7 +370,7 @@ Make sure to edit the config file to match your setup: # domain name of your host serving GitLab. # If using Ubuntu default nginx install: # either remove the default_server from the listen line - # or else rm -f /etc/sites-enabled/default + # or else sudo rm -f /etc/nginx/sites-enabled/default sudo editor /etc/nginx/sites-available/gitlab **Note:** If you want to use HTTPS, replace the `gitlab` Nginx config with `gitlab-ssl`. See [Using HTTPS](#using-https) for HTTPS configuration details. diff --git a/doc/reply_by_email/README.md b/doc/reply_by_email/README.md index fffc120d2bb..e168e9a17e4 100644 --- a/doc/reply_by_email/README.md +++ b/doc/reply_by_email/README.md @@ -10,8 +10,17 @@ In this example, we'll use the Gmail address `gitlab-replies@gmail.com`. If you' ### Installations from source +1. Go to the GitLab installation directory: + + ```sh + cd /home/git/gitlab + ``` 1. Find the `reply_by_email` section in `config/gitlab.yml`, enable the feature and enter the email address including a placeholder for the `reply_key`: + + ```sh + sudo editor config/gitlab.yml + ``` ```yaml reply_by_email: @@ -24,11 +33,15 @@ In this example, we'll use the Gmail address `gitlab-replies@gmail.com`. If you' 2. Find `config/mail_room.yml.example` and copy it to `config/mail_room.yml`: ```sh - cp config/mail_room.yml.example config/mail_room.yml + sudo cp config/mail_room.yml.example config/mail_room.yml ``` 3. Uncomment the configuration options in `config/mail_room.yml` and fill in the details for your specific IMAP server and email account: + ```sh + sudo editor config/mail_room.yml + ``` + ```yaml :mailboxes: - @@ -66,6 +79,10 @@ In this example, we'll use the Gmail address `gitlab-replies@gmail.com`. If you' 5. Edit `/etc/default/gitlab` to enable `mail_room`: + ```sh + sudo editor /etc/default/gitlab + ``` + ```sh mail_room_enabled=true ``` -- cgit v1.2.1 From 83081f167397c6c325a4f8c604191e766b2c3d3b Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 18:00:13 -0700 Subject: Start on tests. --- lib/gitlab/email_receiver.rb | 37 +- spec/fixtures/emails/android_gmail.eml | 177 +++++++++ spec/fixtures/emails/attachment.eml | 351 ++++++++++++++++++ spec/fixtures/emails/auto_reply.eml | 21 ++ spec/fixtures/emails/big5.eml | 26 ++ spec/fixtures/emails/bottom_reply.eml | 160 ++++++++ spec/fixtures/emails/boundary.eml | 61 ++++ spec/fixtures/emails/dutch.eml | 20 + spec/fixtures/emails/empty.eml | 21 ++ spec/fixtures/emails/gmail_web.eml | 181 ++++++++++ spec/fixtures/emails/hebrew.eml | 17 + spec/fixtures/emails/html_only.eml | 93 +++++ spec/fixtures/emails/html_paragraphs.eml | 205 +++++++++++ spec/fixtures/emails/inline_reply.eml | 60 +++ spec/fixtures/emails/ios_default.eml | 136 +++++++ spec/fixtures/emails/iphone_signature.eml | 29 ++ spec/fixtures/emails/multiple_destinations.eml | 40 ++ spec/fixtures/emails/newlines.eml | 84 +++++ spec/fixtures/emails/no_content_reply.eml | 34 ++ spec/fixtures/emails/on_wrote.eml | 277 ++++++++++++++ spec/fixtures/emails/outlook.eml | 188 ++++++++++ spec/fixtures/emails/paragraphs.cooked | 7 + spec/fixtures/emails/paragraphs.eml | 42 +++ spec/fixtures/emails/previous.eml | 38 ++ spec/fixtures/emails/previous_replies.eml | 180 +++++++++ spec/fixtures/emails/too_many_mentions.eml | 31 ++ spec/fixtures/emails/too_short.eml | 21 ++ spec/fixtures/emails/valid_incoming.cooked | 5 + spec/fixtures/emails/valid_incoming.eml | 25 ++ spec/fixtures/emails/valid_reply.cooked | 4 + spec/fixtures/emails/valid_reply.eml | 40 ++ spec/fixtures/emails/via_line.eml | 25 ++ spec/fixtures/emails/windows_8_metro.eml | 173 +++++++++ spec/fixtures/emails/wrong_reply_key.eml | 40 ++ spec/lib/gitlab/email_receiver_spec.rb | 481 +++++++++++++++++++++++++ 35 files changed, 3311 insertions(+), 19 deletions(-) create mode 100644 spec/fixtures/emails/android_gmail.eml create mode 100644 spec/fixtures/emails/attachment.eml create mode 100644 spec/fixtures/emails/auto_reply.eml create mode 100644 spec/fixtures/emails/big5.eml create mode 100644 spec/fixtures/emails/bottom_reply.eml create mode 100644 spec/fixtures/emails/boundary.eml create mode 100644 spec/fixtures/emails/dutch.eml create mode 100644 spec/fixtures/emails/empty.eml create mode 100644 spec/fixtures/emails/gmail_web.eml create mode 100644 spec/fixtures/emails/hebrew.eml create mode 100644 spec/fixtures/emails/html_only.eml create mode 100644 spec/fixtures/emails/html_paragraphs.eml create mode 100644 spec/fixtures/emails/inline_reply.eml create mode 100644 spec/fixtures/emails/ios_default.eml create mode 100644 spec/fixtures/emails/iphone_signature.eml create mode 100644 spec/fixtures/emails/multiple_destinations.eml create mode 100644 spec/fixtures/emails/newlines.eml create mode 100644 spec/fixtures/emails/no_content_reply.eml create mode 100644 spec/fixtures/emails/on_wrote.eml create mode 100644 spec/fixtures/emails/outlook.eml create mode 100644 spec/fixtures/emails/paragraphs.cooked create mode 100644 spec/fixtures/emails/paragraphs.eml create mode 100644 spec/fixtures/emails/previous.eml create mode 100644 spec/fixtures/emails/previous_replies.eml create mode 100644 spec/fixtures/emails/too_many_mentions.eml create mode 100644 spec/fixtures/emails/too_short.eml create mode 100644 spec/fixtures/emails/valid_incoming.cooked create mode 100644 spec/fixtures/emails/valid_incoming.eml create mode 100644 spec/fixtures/emails/valid_reply.cooked create mode 100644 spec/fixtures/emails/valid_reply.eml create mode 100644 spec/fixtures/emails/via_line.eml create mode 100644 spec/fixtures/emails/windows_8_metro.eml create mode 100644 spec/fixtures/emails/wrong_reply_key.eml create mode 100644 spec/lib/gitlab/email_receiver_spec.rb diff --git a/lib/gitlab/email_receiver.rb b/lib/gitlab/email_receiver.rb index a0b4ff87e02..3c1f346c0cf 100644 --- a/lib/gitlab/email_receiver.rb +++ b/lib/gitlab/email_receiver.rb @@ -62,6 +62,21 @@ module Gitlab end end + def parse_body(message) + body = select_body(message) + + encoding = body.encoding + raise EmptyEmailError if body.strip.blank? + + body = discourse_email_trimmer(body) + raise EmptyEmailError if body.strip.blank? + + body = EmailReplyParser.parse_reply(body) + raise EmptyEmailError if body.strip.blank? + + body.force_encoding(encoding).encode("UTF-8") + end + private def reply_key @@ -80,21 +95,6 @@ module Gitlab SentNotification.for(reply_key) end - def parse_body(message) - body = select_body(message) - - encoding = body.encoding - raise EmptyEmailError if body.strip.blank? - - body = discourse_email_trimmer(body) - raise EmptyEmailError if body.strip.blank? - - body = EmailReplyParser.parse_reply(body) - raise EmptyEmailError if body.strip.blank? - - body.force_encoding(encoding).encode("UTF-8") - end - def select_body(message) html = nil text = nil @@ -144,10 +144,9 @@ module Gitlab range_end = 0 lines.each_with_index do |l, idx| - break if l =~ /\A\s*\-{3,80}\s*\z/ || - # This one might be controversial but so many reply lines have years, times and end with a colon. - # Let's try it and see how well it works. - (l =~ /\d{4}/ && l =~ /\d:\d\d/ && l =~ /\:$/) || + # This one might be controversial but so many reply lines have years, times and end with a colon. + # Let's try it and see how well it works. + break if (l =~ /\d{4}/ && l =~ /\d:\d\d/ && l =~ /\:$/) || (l =~ /On \w+ \d+,? \d+,?.*wrote:/) # Headers on subsequent lines diff --git a/spec/fixtures/emails/android_gmail.eml b/spec/fixtures/emails/android_gmail.eml new file mode 100644 index 00000000000..21c5dde2346 --- /dev/null +++ b/spec/fixtures/emails/android_gmail.eml @@ -0,0 +1,177 @@ +Delivered-To: reply@discourse.org +Return-Path: +MIME-Version: 1.0 +In-Reply-To: +References: + +Date: Fri, 28 Nov 2014 12:53:21 -0800 +Subject: Re: [Discourse Meta] [Lounge] Testing default email replies +From: Walter White +To: Discourse Meta +Content-Type: multipart/alternative; boundary=089e0149cfa485c6630508f173df + +--089e0149cfa485c6630508f173df +Content-Type: text/plain; charset=UTF-8 + +### this is a reply from Android 5 gmail + +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over +the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown +fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. +The quick brown fox jumps over the lazy dog. + +This is **bold** in Markdown. + +This is a link to http://example.com +On Nov 28, 2014 12:36 PM, "Arpit Jalan" wrote: + +> techAPJ +> November 28 +> +> Test reply. +> +> First paragraph. +> +> Second paragraph. +> +> To respond, reply to this email or visit +> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in +> your browser. +> ------------------------------ +> Previous Replies codinghorror +> +> November 28 +> +> We're testing the latest GitHub email processing library which we are +> integrating now. +> +> https://github.com/github/email_reply_parser +> +> Go ahead and reply to this topic and I'll reply from various email clients +> for testing. +> ------------------------------ +> +> To respond, reply to this email or visit +> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in +> your browser. +> +> To unsubscribe from these emails, visit your user preferences +> . +> + +--089e0149cfa485c6630508f173df +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +

### this is a reply from Android 5 gmail

+

The quick brown fox jumps over the lazy dog. The quick brown= + fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. = +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over= + the lazy dog. The quick brown fox jumps over the lazy dog.

+

This is **bold** in Markdown.

+

This is a link to http://exam= +ple.com

+
On Nov 28, 2014 12:36 PM, "Arpit Jalan"= +; <info@discourse.org> wrot= +e:
+ + + + + + + + + + + +
+ + + techAPJ
+ November 28 +
+

Test reply.

+ +

First paragraph.

+ +

Second paragraph.

+
+ + +
+

To respond, reply to this email or visit https:/= +/meta.discourse.org/t/testing-default-email-replies/22638/3 in your bro= +wser.

+
+
+

Previous Replies

+ + + + + + + + + + + +
+ + + codinghorror + November 28 +
+

We're testing the latest GitHub emai= +l processing library which we are integrating now.

+ +

https://github.com/github/email_reply_parser

+ +

Go ahead and reply to this topic and I&#= +39;ll reply from various email clients for testing.

+
+ + +
+ +
+

To respond, reply to this email or visit https://met= +a.discourse.org/t/testing-default-email-replies/22638/3 in your browser= +.

+
+
+

To unsubscribe from these emails, visit your user preferences.

+
+
+
+ +--089e0149cfa485c6630508f173df-- diff --git a/spec/fixtures/emails/attachment.eml b/spec/fixtures/emails/attachment.eml new file mode 100644 index 00000000000..f25c3d1a449 --- /dev/null +++ b/spec/fixtures/emails/attachment.eml @@ -0,0 +1,351 @@ +Message-ID: <51C22E52.1030509@darthvader.ca> +Date: Wed, 19 Jun 2013 18:18:58 -0400 +From: Anakin Skywalker +User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:17.0) Gecko/20130510 Thunderbird/17.0.6 +MIME-Version: 1.0 +To: Han Solo via Death Star +Subject: Re: [Death Star] [PM] re: Regarding your post in "Site Customization + not working" +References: <51d23d33f41fb_5f4e4b35d7d60798@xwing.mail> +In-Reply-To: <51d23d33f41fb_5f4e4b35d7d60798@xwing.mail> +Content-Type: multipart/mixed; boundary=047d7b45041e19c68004eb9f3de8 + +--047d7b45041e19c68004eb9f3de8 +Content-Type: multipart/alternative; boundary=047d7b45041e19c67b04eb9f3de6 + +--047d7b45041e19c67b04eb9f3de6 +Content-Type: text/plain; charset=ISO-8859-1 + +here is an image attachment + + +On Tue, Nov 19, 2013 at 5:11 PM, Neil wrote: + +> Neil +> November 19 +> +> Actually, deleting a spammer does what it's supposed to. It does mark the +> topic as deleted. +> +> That topic has id 11002, and you're right that the user was deleted. +> +> @eviltrout Any idea why it showed up in +> suggested topics? +> +> To respond, reply to this email or visit +> http://meta.discourse.org/t/spam-post-pops-back-up-in-suggested-topics/11005/5in your browser. +> ------------------------------ +> Previous Replies Neil +> November 19 +> +> Looks like a bug when deleting a spammer. I'll look at it. +> riking +> November 19 +> +> codinghorror: +> +> I can't even find that topic by name. +> +> In that case, I'm fairly certain someone used the 'Delete Spammer' +> function on the user, which would explain your inability to find it - it's +> gone. +> +> I'm raising this because, well, it's gone and shouldn't be showing up. And +> even if it was hanging around, it should be invisible to me, and not +> showing up in Suggested Topics. +> codinghorror +> November 19 +> +> Hmm, that's interesting -- can you have a look @eviltrout? +> I can't even find that topic by name. +> riking +> November 19 +> +> I'm one of the users who flagged this particular spam post, and it was +> promptly deleted/hidden, but it just popped up in the Suggested Topics box: +> +> Pasted image1125x220 27.7 KB +> +> +> We may want to recheck the suppression on these. +> ------------------------------ +> +> To respond, reply to this email or visit +> http://meta.discourse.org/t/spam-post-pops-back-up-in-suggested-topics/11005/5in your browser. +> +> To unsubscribe from these emails, visit your user preferences +> . +> + +--047d7b45041e19c67b04eb9f3de6 +Content-Type: text/html; charset=ISO-8859-1 +Content-Transfer-Encoding: quoted-printable + +
here is an image attachment


On Tue, Nov 19, 2013 at 5:11 PM, Neil = +<info@discourse.org> wrote:
+
+ + + + + + + + +
+ + + Neil<= +/a>
+No= +vember 19 +
+

Actually, deleting a spammer does what it's s= +upposed to. It does mark the topic as deleted.

+ +

That topic has id 11002, and you're right tha= +t the user was deleted.

+ +

@eviltrout Any idea why it showed up in suggested topics?

+
+
+

To respond, reply to this email or visit http://meta.discourse.org/t/spam-post-pops-back= +-up-in-suggested-topics/11005/5 in your browser.

+ +
+
+

Previous Replies

+ + + + + + + + + +
+ + + Neil<= +/a>
+No= +vember 19 +

Looks= + like a bug when deleting a spammer. I'll look at it.

+ + + + + + + + +
+ + + rik= +ing
+No= +vember 19 +
+

+
+codinghorror:
+

I can't even find that topic by n= +ame.

+ +

In that case, I'm fairly certain someone used= + the 'Delete Spammer' function on the user, which would explain you= +r inability to find it - it's gone.

+ +

I'm raising this because, well, it's gone= + and shouldn't be showing up. And even if it was hanging around, it sho= +uld be invisible to me, and not showing up in Suggested Topics.

+
+ + + + + + + + +
+ + + codinghorror
+No= +vember 19 +

Hmm, = +that's interesting -- can you have a look @eviltrout? I can't even find that topic by= + name.

+
+ + + + + + + + +
+ + + rik= +ing
+No= +vember 19 +
+

I'm one of the users who flagged this particu= +lar spam post, and it was promptly deleted/hidden, but it just popped up in= + the Suggested Topics box:

+ +

+ + +

We may want to recheck the suppression on these.<= +/p> +

+
+
+

To respond, reply to this email or visit http://meta.discourse.org/t/spam-post-pops-back-up-= +in-suggested-topics/11005/5 in your browser.

+ +
+
+

To unsubscribe from these emails, visit your user pre= +ferences.

+
+

+ +--047d7b45041e19c67b04eb9f3de6-- +--047d7b45041e19c68004eb9f3de8 +Content-Type: image/png; name="bricks.png" +Content-Disposition: attachment; filename="bricks.png" +Content-Transfer-Encoding: base64 +X-Attachment-Id: f_ho8uteve0 + +iVBORw0KGgoAAAANSUhEUgAAASEAAAB+CAIAAADk0DDaAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJ +bWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdp +bj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6 +eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEz +NDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJo +dHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlw +dGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAv +IiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RS +ZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpD +cmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNl +SUQ9InhtcC5paWQ6MDYxQjcyOUUzMDM1MTFFM0JFRTFBOTQ1RUY4QUU4MDIiIHhtcE1NOkRvY3Vt +ZW50SUQ9InhtcC5kaWQ6MDYxQjcyOUYzMDM1MTFFM0JFRTFBOTQ1RUY4QUU4MDIiPiA8eG1wTU06 +RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDowNjFCNzI5QzMwMzUxMUUzQkVF +MUE5NDVFRjhBRTgwMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDowNjFCNzI5RDMwMzUxMUUz +QkVFMUE5NDVFRjhBRTgwMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1w +bWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pm2fyz0AAAyISURBVHja7F2/i11FFL6rL12aBdlGRDCF +EQmEbVJtChfSJJDGRkgZBBsVUhgQ7NSkCKiFVUr/AUGbhW1MlWaJBAkWVsFmG0HshMT7duJk9szc +uefOjzPn3vd9xfL2/bh35rtnznfOuXNnth7c/6ID2Lh261vO13669wm4SsZ7H3396gmePXu2OkH/ +Yr4Mv4IrCgAYY8Am4vnz51sn8EVsXth68P7eYq7Kj4cP3H+v79fq2tWDX/u/d25/7n/08/3PzIvb +u3vLs3sxhh/vXrOvb9/50v1o77W/X340B5IXMsbsta931eN24I6uRQ4wd3SJkUwYnqkLQ6wIAHWx +gn/Nx3ff3Ov/njvbWFcXFibESdZw3aFjAKBDx46Ofk/42e7u2/3f4G8jH5XF07+O7es3tnfSThps +beRNA/PRmd1rxrlGkMNDf8a2DLskJzOcRrJ5/7czb/Z/fzk8qESyjBlDxwBAZT4WGd/1/CtxLcaz +ZiLYWvOmezpXxMQwxKQYwzIkK2S4LMnQMQCorGMm4C7irhp6nUzPHfSs7un6176jffT4cULSuGkM ++1mWq5b2jDlqRpJGdWNsFqNLxqrstfejxEzjA8l+LBpkm+DihQucmodyhhErAoCOmkcvx4t3xsG4 +RaZEbgOeZZNMwu9u+P7EkkiGjgGADh2LDH21Ehd0Wvz82E/VqiLOsE6JizM8iWSZ2n0TM4aOAYAO +HUvzDW0RbNhoa8ld0Ui2cPHCBU7JCwz7DDPzMc7dEf0krzqAESsCIBmxIgAsN1YUSKMlU/9N8KxD ++b02hvn3oDWbMXQMADZMxyIOtUnqn1lTVluuWAzD+kmGjgGAeh2rcfMu7YDCd8PFKss10qRkhiV1 +Q7J2X8+Mpe+PuRcpOCEgp59lOWry1GCRfgVJdg+STFRxK4yTLFnzSCCZacaIFQGgcqworP5FvKlM +YFBwvuIGkszscny+Ij9WlJ/SyY+8oGMAUFnHZIa+tpnjRVrCn68o0PFFMqztdGkkQ8cAQCQfI87A +X0lGlZtJW4gmx9Mnr5lDGuyenawko82RJ5OczLCflfHriprNGDoGAOL5WD/63QX7tU1USV7oq2FH +yKmNf7Ukq2V4RiRrNuOVf+3LLsSrYXTlI7l2TwLUSgvxahhdNRhmRuMkQNVmxogVAUBEx9yh7zoz +STc2quwFHVKTdX7sc/WtGB4NUMsynH/AqXOpdJoxdAwAKuuYGwc3SXj0TL2NIFi7n+pfWyU8c2E4 +p6mazRg6BgAi+ZgbRIpF2yRDKIhRuRhdMJTTu8v7VyY9dpFAcr4nJhlCDZKTGS4uNTrNOLBXLeeU +beuhVefm8Q8bma/4ZLt756+XRyMkM0+xVJL5x4zU7nuGe1iSNZsxYkUAqBwrBoXbf1Os2F3E/cg0 +NeJle//qPyRLGkZiLcmJ83MhOVK7d8OEIZKDZizTcjwHDQCCOubGtfHbdpNSVc6+UuYL1/f33JRx +RttwRfKxvv2mI4Ze63pHb4zySWZuj9Z/gTDczWc3uUik4OqSJZljxt2UslYRM4aOAUBlHTNDPxJN +EmEx/wbfPBUcHy2fu4iXPeiOu22aPAyR7Eu3JTl4ITaH4QjWDPfYZjHc1oxXoxHL0DtumyIJJWl3 +8CHF0QZkJqxFbsj4ExE4aw0Er32wj3GG48Unsg4Zh2T/dHb05iy9mBnnE5KZ8xWHSK5nxt3Ak6DB +IyNWBADBmkca+P6YfPr08JS8vFD/kGc69au8+dTJP89xz5kkT2J4iGTTfkuy35jgNZJkOIdkYTM+ +RdeYGUPHAKCyjpV1BqXwZHs8nxGG8VsHR+u/r1+6sX7rdM3jj3/WPvjc2eNgR9QyrJPkqzfvBqtK +PcmEYf0kQ8cAoC62rr4FEibAKJipd333zb2hr/m+FphKskWwrjgjhrfO7+zgonLw8ae3bPRirrp5 +Jz7YgEm4vH/F/df4srmTjFgRAKBjOkQsqGAG7kdAvoL18jU0h2aOJEPHAKCyjn34wY2hz9xomIC4 +GfPNtJ1FyW8jJ423Ie7/cnpnvmzyAZIw1OtdPsnkXO4P7Uf1Llm9CxE5sqtywdSulJlN6iB0DAAq +69j3X92ND8rgqHXdwNBvR4e7+4W4L0xug+/5gv5s9Mi9g/QVLO5TM3vHVJtI++OdCrJX8JKNXohS +ZjYaZYiZ2dChoGMAUFnHvn1LS13xzM1bHH/z7kOU79Lx26XxLOXf+7jdl8uwa8Ar5sqsZPk482R1 +WRyZS3vSxKAo//nwh/Xfrru9u7e8a+Mv0FeD5O7EQ5GRZvHz/c/s600guR7Dj1DzAIDGsaIrbmlb +0dnFRsh+oaOyaX5lHa3RXNe/Xul2hprK34+UNM9/TY5vWz70acdexMZvedpWdP6pO/aq8f3X/Mjc +kkwY7pK21Q0yk8Yh+UICwzkkB814lGTXjKFjANBOx0aH/qjX4bwZdADGy3b/zwR1J1nb54KC25O6 +p+AIy1TxKQjOhmCZDEdIdlMyc+vWkuw+eRXcZdeehcleK5KVmDF0DABa6FiRhZzS3K3rAOzjDEwd +S0gXJ31UFkUWckpzt1bH3MlHHB3LbJiwrNUz4yE7CZrxKnigIovIBkkcqjQII3KB6117clXESN4o +hmXM2C/hRPaMR6wIAHWxqudaguVO88I9XbKaNdn3tZJrzyfZDxDs6XLihSb7vupk2Cd51IxNKA4d +AwARHavtVIJ3ISO5L//hnFn4VwGSh4gdKuEwl7kGyUN3g4LTGAjMcwDQMQCYrY4FnUHatKNMzGhP +syLulkNy2hPQINl9zTTjVUFC+UUIX3+rItIYzZYxtEULM34jYSRB8cVn5kiyjBkjVgQAlbFicHzz +d/4cFVmxJb40xzYJJPOfAzDL18ksDKqW5GQznhQrQscAYBE1j0ggG4QpemJV0KokAzIMQ8cAQETH +ZCo/m+BZI0wG64StGC5eu1fCsCozNjf6Vw2z0syqveZyRXA4geTaI00bw5h3DwAS4I6xzH24p6IX +2UlLw+e4wxpdS3ColVqiRHOKd61neC4kQ8cAoC64tfuykW6TJ3OL9MtNA4LTmpKJKp5LzJpkwrB7 +kByimpgxdAwAdOhYmqcfQsE5wcLzTYtIlgDD3dic4EnPjwncdQiS3LCqWcSM8Rw0AMxTx4r4Hm3P +QQs7coEuT5oNLNB3bc+/FGmJmRK4GurtpNPkEBRcbIQfyQTT4rRF8MWMLG21n2SSgwxPmncfNImE +RfAlh7EeM0asCACCsWLyQl8NJT64IHvaJh1imfTCSNY230qPGWMuFQAI6ljD9UAjixhPjZ5rLHat +wb+2YtgnucZi10rChBokG0DHAEBExwoO+iJF2KlPQFv/2mRaLTMJLEVykzK3q2AaSK7KcCmSTz0/ +1hCZlWX3h/LBmJ45gVMZnjTPw/62STA2X5IRKwKAYKxYMK0cXcuS4wKnPgnvxmnMXuS74d5pTT1v +keoIh+FRkgUYztc6PwgXq44UNGPoGACI61i9uXlFDvtk+8VmquZoZCIP8xRti871ihalGO66XJKb +l/U1mDHmUgFAIx2LD9Pm/qn3r/5DsqRtJNqWLHYX8fFtSXYVjJDsNoykJWIkFwlVZBg2+dhK59CP +VJbtmDEhjWsW8fs2/HoAcx3z/gvX9/dIUj6XLYLiDNuOWJI5DE+qB3BINp8Skme3CRNiRQAQjxWV +46A77jwFi0QCJPc1XjD45kv/fbT8Cx+p3a8Z7sEmmQiLZXjoQrzQug0gGTUPABDUsZwYt8gMJrub +06iXjTtXP/UayiLcLkeydvtmcJo/swH2+JkM55BMJvsw51KVJTnOcDcwzX8Sw6rMGPkYAIjomOsP +MudT5/ycOA/+jFX3hmmRNkf8Mfn06eEpz/cijQm5/+DPhUkmDE+aS2Xv+xdpc5zhU3QdUgG3JA8x +rMqMMZcKAATzseboncfB0dp/XL151//0j3/W7uHc2WNfwQq624Igt5WUMLzWgf9Jvnjyphsp9CQT +hn2SM6OGGgxrI9kw/PqlGy/HmG+prRAcXaMjjTDepDtPumOOKeghuY9hgtvicBgGyXGY0WXoRawI +ABLYOr+jYk6KWVGV1Dy6icvZAqMMu/7VAnvbFzdjN0yAjgHA0mseZukO4lnNv70zMI4BjrZgjOA7 +WqhZcZJde4aOAUDlfOz7r+6SYdd7OPJv51Si3AQp6CD9Hw65TytW/tCPwz9y/FyRb7r/Tu3pEFHx +/g7pCbOR8SP7Le/DBNI7v+Uckl2VC2YdkQMmXAi/zfGm+t8hJ2U2tdQldr/5nwADACLM1IGrPYuL +AAAAAElFTkSuQmCC +--047d7b45041e19c68004eb9f3de8-- diff --git a/spec/fixtures/emails/auto_reply.eml b/spec/fixtures/emails/auto_reply.eml new file mode 100644 index 00000000000..7999c8d78b7 --- /dev/null +++ b/spec/fixtures/emails/auto_reply.eml @@ -0,0 +1,21 @@ +Return-Path: +Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 +Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 +Date: Thu, 13 Jun 2013 17:03:48 -0400 +From: Jake the Dog +To: reply+636ca428858779856c226bb145ef4fad@appmail.adventuretime.ooo +Message-ID: +Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' +Mime-Version: 1.0 +Content-Type: text/plain; + charset=ISO-8859-1 +Content-Transfer-Encoding: 7bit +Auto-Submitted: auto-generated +X-Sieve: CMU Sieve 2.2 +X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, + 13 Jun 2013 14:03:48 -0700 (PDT) +X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 + +Test reply to Discourse email digest diff --git a/spec/fixtures/emails/big5.eml b/spec/fixtures/emails/big5.eml new file mode 100644 index 00000000000..4a7b2082486 --- /dev/null +++ b/spec/fixtures/emails/big5.eml @@ -0,0 +1,26 @@ + +Delivered-To: discourse-reply+cd480e301683c9902891f15968bf07a5@discourse.org +Received: by 10.194.216.104 with SMTP id op8csp80593wjc; + Wed, 24 Jul 2013 07:59:14 -0700 (PDT) +Return-Path: +References: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> +From: Walter White +In-Reply-To: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> +Mime-Version: 1.0 (1.0) +Date: Wed, 24 Jul 2013 15:59:10 +0100 +Message-ID: <4597127794206131679@unknownmsgid> +Subject: Re: [Discourse] new reply to your post in 'Crystal Blue' +To: walter via Discourse +Content-Type: multipart/alternative; boundary=20cf301cc47ada510404f040b262 + +--20cf301cc47ada510404f040b262 +Content-Type: text/plain; charset=Big5 +Content-Transfer-Encoding: base64 + +tv2hSafapFe5cbX4pEahSQ0K +--20cf301cc47ada510404f040b262 +Content-Type: text/html; charset=Big5 +Content-Transfer-Encoding: base64 + +PGRpdiBkaXI9Imx0ciI+tv2hSafapFe5cbX4pEahSTxicj48L2Rpdj4NCg== +--20cf301cc47ada510404f040b262-- diff --git a/spec/fixtures/emails/bottom_reply.eml b/spec/fixtures/emails/bottom_reply.eml new file mode 100644 index 00000000000..5fc992971fc --- /dev/null +++ b/spec/fixtures/emails/bottom_reply.eml @@ -0,0 +1,160 @@ +Received: by 10.107.19.29 with SMTP id b29csp111716ioj; + Wed, 30 Jul 2014 17:52:05 -0700 (PDT) +X-Received: by 10.194.238.6 with SMTP id vg6mr11340975wjc.24.1406767925330; + Wed, 30 Jul 2014 17:52:05 -0700 (PDT) +Received: from localhost (localhost [127.0.0.1]) + by bendel.debian.org (Postfix) with QMQP + id 18F5C417; Thu, 31 Jul 2014 00:52:04 +0000 (UTC) +Old-Return-Path: +X-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on bendel.debian.org +X-Spam-Level: +X-Spam-Status: No, score=-25.9 required=4.0 tests=FOURLA,LDOSUBSCRIBER, + LDO_WHITELIST,MURPHY_DEBIAN_MESSAGE,PGPSIGNATURE autolearn=unavailable + version=3.3.2 +X-Original-To: lists-debian-ctte@bendel.debian.org +Delivered-To: lists-debian-ctte@bendel.debian.org +Received: from localhost (localhost [127.0.0.1]) + by bendel.debian.org (Postfix) with ESMTP id CE6CDEE + for ; Thu, 31 Jul 2014 00:51:52 +0000 (UTC) +X-Virus-Scanned: at lists.debian.org with policy bank en-lt +X-Amavis-Spam-Status: No, score=-11.9 tagged_above=-10000 required=5.3 + tests=[BAYES_00=-2, FOURLA=0.1, LDO_WHITELIST=-5, PGPSIGNATURE=-5] + autolearn=ham +Received: from bendel.debian.org ([127.0.0.1]) + by localhost (lists.debian.org [127.0.0.1]) (amavisd-new, port 2525) + with ESMTP id SB451DwGZCOe for ; + Thu, 31 Jul 2014 00:51:47 +0000 (UTC) +X-policyd-weight: NOT_IN_SBL_XBL_SPAMHAUS=-1.5 NOT_IN_BL_NJABL=-1.5 CL_IP_EQ_HELO_IP=-2 (check from: .debian. - helo: .becquer.dodds. - helo-domain: .dodds.) FROM/MX_MATCHES_NOT_HELO(DOMAIN)=0; rate: -5 +Received: from becquer.dodds.net (becquer.dodds.net [207.224.24.209]) + by bendel.debian.org (Postfix) with ESMTP id 8E89A2B4 + for ; Thu, 31 Jul 2014 00:51:47 +0000 (UTC) +Received: from virgil.dodds.net (unknown [192.168.15.59]) + by becquer.dodds.net (Postfix) with ESMTPA id 9B0A9256EB + for ; Wed, 30 Jul 2014 17:51:19 -0700 (PDT) +Received: by virgil.dodds.net (Postfix, from userid 1000) + id 942FB60199; Wed, 30 Jul 2014 17:51:15 -0700 (PDT) +Date: Wed, 30 Jul 2014 17:51:15 -0700 +From: Jake +To: incoming+amazing@appmail.adventuretime.ooo +Subject: Re: Next Debian CTTE IRC Meeting at date -d'Thu Jul 31 17:00:00 UTC + 2014' +Message-ID: <20140731005115.GA19044@virgil.dodds.net> +Mail-Followup-To: debian-ctte@lists.debian.org +References: <20140730213924.GA12356@teltox.donarmstrong.com> +MIME-Version: 1.0 +Content-Type: multipart/signed; micalg=pgp-sha256; + protocol="application/pgp-signature"; boundary="qMm9M+Fa2AknHoGS" +Content-Disposition: inline +In-Reply-To: <20140730213924.GA12356@teltox.donarmstrong.com> +User-Agent: Mutt/1.5.23 (2014-03-12) +X-Debian-Message: Signature check passed for Debian member +X-Rc-Virus: 2007-09-13_01 +X-Rc-Spam: 2008-11-04_01 +Resent-Message-ID: +Resent-From: debian-ctte@lists.debian.org +X-Mailing-List: archive/latest/4791 +X-Loop: debian-ctte@lists.debian.org +List-Id: +List-Post: +List-Help: +List-Subscribe: +List-Unsubscribe: +Precedence: list +Resent-Sender: debian-ctte-request@lists.debian.org +Resent-Date: Thu, 31 Jul 2014 00:52:04 +0000 (UTC) + + +--qMm9M+Fa2AknHoGS +Content-Type: text/plain; charset=us-ascii +Content-Disposition: inline +Content-Transfer-Encoding: quoted-printable + +On Wed, Jul 30, 2014 at 02:39:24PM -0700, Don Armstrong wrote: +> The next Debian CTTE IRC meeting is at=20 + +> date -d 'Thu Jul 31 17:00:00 UTC 2014' on irc.debian.org in +> #debian-ctte. + +> The current meeting agenda is here, and more up-to-date ones may be +> found in the git repository. + +> #startmeeting + +> #topic Who is here? + +> #topic Next Meeting? + +> #topic #717076 Decide between libjpeg-turbo and libjpeg8 et al. + +This has been voted on; should probably be removed from the agenda, someone +just needs to confirm the vote results and get it on the website. (AIUI the +archive has already begun moving on accordingly.) + +> #topic #636783 constitution: super-majority bug + +> #topic #636783 constitution: casting vote + +> #topic #636783 constitution: minimum discussion period + +> #topic #636783 constitution: TC member retirement/rollover + +> #topic #636783 constitution: TC chair retirement/rollover + +> #topic #681419 Depends: foo | foo-nonfree + +> #topic #741573 menu systems and mime-support + +> #topic #746715 init system fallout + +Also voted and just needs to be confirmed. + +> #topic #750135 Maintainer of aptitude package +>=20 +> #topic #752400 Advice on util-linux + +This has been closed by mutual agreement of the people involved and doesn't +require any action from the TC. Removed from the agenda. + +There's also bug #744246, which was assigned to the TC at my request but +without any preamble so it may have escaped notice. However, that situation +has been evolving constructively among the related parties (apparently, an +informal comment from a member of the release team was mistaken for a +release team position, so that's now being revisited), so I don't believe +this is anything we need to put on the agenda for tomorrow despite being on +the open bug list. + +--=20 +Steve Langasek Give me a lever long enough and a Free OS +Debian Developer to set it on, and I can move the world. +Ubuntu Developer http://www.debian.org/ +slangasek@ubuntu.com vorlon@debian.org + +--qMm9M+Fa2AknHoGS +Content-Type: application/pgp-signature; name="signature.asc" +Content-Description: Digital signature + +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1 + +iQIcBAEBCAAGBQJT2ZMDAAoJEFaNMPMhshM9GLsP/244S3wtYZEeVgJWIdB5PE0A +sZVezEA692y++/0oVZVecwV67yBOyfSjPPetdAph2UDMRtfurwxfj2BkbOFA2+Y6 +++MErbmC3V7IGpd/L/fFGdXgvMQT2MNBpw0fnMA7bLpNjCvoj+Hr1HXRUcWoJSlj +WmHWwWSTVRcHg8a3iWYJzY6XfLyEEgHlahrlKvJExsTx/9mc1qg7g8KGdnhzHFBl +ttdH2fxpAk/624dReCcw5RKmOLfZ1HsEl9XcVe1cb4K+MDaQiXmoEK5v3xaNz1tS +NK5v2D5gDs229zoxKzQnnzOPLHxqI5E0L9PpI/mu4T9z7H2bHR3U5BvhnT99t5uw +ydf2cZNGY0uFCV3Rvn07BfAIW5WSXhOfN/5IymRKmdhjsTiwZ/wFjFrK8tVjtERu +yeyA7RIYiblGCEKYIYLWSxhoXeEdmAdfp6EA2/IA1CpgMB+ZdSfaeMgFY7xosgmG +ax3NTnaKyhr1QEUJ2gjAwHnKjuGbRVDAinYrSvP0o8Bh9sAs2BN2negWBCZVwwkN +S9hWTjVqsBmpaPOt5SEDwDo9O9dfzkmaamDsxOuUEz9F7v5jYg0mxA/WbogGty9R +vOMKxdxRkzflL/CferVbkzL/EkZRDfWDp9SleZggrpz7miiNDbS7jdRzJ4EttmJ8 +gHBAVrOzcnbIPOIkk9pw +=KXIu +-----END PGP SIGNATURE----- + +--qMm9M+Fa2AknHoGS-- + + +-- +To UNSUBSCRIBE, email to debian-ctte-REQUEST@lists.debian.org +with a subject of "unsubscribe". Trouble? Contact listmaster@lists.debian.org +Archive: https://lists.debian.org/20140731005115.GA19044@virgil.dodds.net diff --git a/spec/fixtures/emails/boundary.eml b/spec/fixtures/emails/boundary.eml new file mode 100644 index 00000000000..1250fe498b0 --- /dev/null +++ b/spec/fixtures/emails/boundary.eml @@ -0,0 +1,61 @@ + +MIME-Version: 1.0 +Received: by 10.64.14.41 with HTTP; Wed, 19 Jun 2013 06:29:41 -0700 (PDT) +In-Reply-To: <51c19490e928a_13442dd8ae892548@tree.mail> +References: <51c19490e928a_13442dd8ae892548@tree.mail> +Date: Wed, 19 Jun 2013 09:29:41 -0400 +Delivered-To: finn@adventuretime.ooo +Message-ID: +Subject: Re: [Adventure Time] jake mentioned you in 'peppermint butler is + missing' +From: Finn the Human +To: jake via Adventure Time +Content-Type: multipart/alternative; boundary=001a11c206a073876a04df81d2a9 + +--001a11c206a073876a04df81d2a9 +Content-Type: text/plain; charset=ISO-8859-1 + +I'll look into it, thanks! + + +On Wednesday, June 19, 2013, jake via Discourse wrote: + +> jake mentioned you in 'peppermint butler is missing' on Adventure +> Time: +> ------------------------------ +> +> yeah, just noticed this cc @jake +> ------------------------------ +> +> Please visit this link to respond: +> http://adventuretime.ooo/t/peppermint-butler-is-missing/7628/2 +> +> To unsubscribe from these emails, visit your user preferences +> . +> + +--001a11c206a073876a04df81d2a9 +Content-Type: text/html; charset=ISO-8859-1 +Content-Transfer-Encoding: quoted-printable + +I'll look into it, thanks!

On Wednesday, June 19, 2= +013, jake via Adventure Time wrote:

sa= +m mentioned you in 'Duplicate message are shown in profile' on Adve= +nture Time

+ + +

yeah, just noticed this cc @eviltrout

+ +

Please visit this link to respond: http= +://adventuretime.ooo/t/peppermint-butler-is-missing/7628/2 + + +

To unsubscribe from these emails, visit your user preferences.

+
+ +--001a11c206a073876a04df81d2a9-- diff --git a/spec/fixtures/emails/dutch.eml b/spec/fixtures/emails/dutch.eml new file mode 100644 index 00000000000..7be08dc4938 --- /dev/null +++ b/spec/fixtures/emails/dutch.eml @@ -0,0 +1,20 @@ + +Delivered-To: discourse-reply+cd480e301683c9902891f15968bf07a5@discourse.org +Received: by 10.194.216.104 with SMTP id op8csp80593wjc; + Wed, 24 Jul 2013 07:59:14 -0700 (PDT) +Return-Path: +References: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> +From: Walter White +In-Reply-To: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> +Mime-Version: 1.0 (1.0) +Date: Wed, 24 Jul 2013 15:59:10 +0100 +Message-ID: <4597127794206131679@unknownmsgid> +Subject: Re: [Discourse] new reply to your post in 'Crystal Blue' +To: walter via Discourse +Content-Type: multipart/alternative; boundary=001a11c20edc15a39304e2432790 + +Dit is een antwoord in het Nederlands. + +Op 18 juli 2013 10:23 schreef Sander Datema het volgende: + +Dit is de originele post. \ No newline at end of file diff --git a/spec/fixtures/emails/empty.eml b/spec/fixtures/emails/empty.eml new file mode 100644 index 00000000000..85bebc66245 --- /dev/null +++ b/spec/fixtures/emails/empty.eml @@ -0,0 +1,21 @@ +Return-Path: +Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 +Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 +Date: Thu, 13 Jun 2013 17:03:48 -0400 +From: Jake the Dog +To: reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo +Message-ID: +Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' +Mime-Version: 1.0 +Content-Type: text/plain; + charset=ISO-8859-1 +Content-Transfer-Encoding: 7bit +X-Sieve: CMU Sieve 2.2 +X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, + 13 Jun 2013 14:03:48 -0700 (PDT) +X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 + + + diff --git a/spec/fixtures/emails/gmail_web.eml b/spec/fixtures/emails/gmail_web.eml new file mode 100644 index 00000000000..8bb83835711 --- /dev/null +++ b/spec/fixtures/emails/gmail_web.eml @@ -0,0 +1,181 @@ +Delivered-To: reply@discourse.org +Return-Path: +MIME-Version: 1.0 +In-Reply-To: +References: + +Date: Fri, 28 Nov 2014 12:36:49 -0800 +Subject: Re: [Discourse Meta] [Lounge] Testing default email replies +From: Walter White +To: Discourse Meta +Content-Type: multipart/alternative; boundary=001a11c2e04e6544f30508f138ba + +--001a11c2e04e6544f30508f138ba +Content-Type: text/plain; charset=UTF-8 + +### This is a reply from standard GMail in Google Chrome. + +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over +the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown +fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over +the lazy dog. The quick brown fox jumps over the lazy dog. + +Here's some **bold** text in Markdown. + +Here's a link http://example.com + +On Fri, Nov 28, 2014 at 12:35 PM, Arpit Jalan wrote: + +> techAPJ +> November 28 +> +> Test reply. +> +> First paragraph. +> +> Second paragraph. +> +> To respond, reply to this email or visit +> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in +> your browser. +> ------------------------------ +> Previous Replies codinghorror +> +> November 28 +> +> We're testing the latest GitHub email processing library which we are +> integrating now. +> +> https://github.com/github/email_reply_parser +> +> Go ahead and reply to this topic and I'll reply from various email clients +> for testing. +> ------------------------------ +> +> To respond, reply to this email or visit +> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in +> your browser. +> +> To unsubscribe from these emails, visit your user preferences +> . +> + +--001a11c2e04e6544f30508f138ba +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +
### This is a reply from standard GMail in Google Chr= +ome.

The quick brown fox jumps over the lazy dog. = +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over= + the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown= + fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. = +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over= + the lazy dog.=C2=A0

Here's some **bold** text= + in Markdown.

Here's a link http://example.com
<= +br>
On Fri, Nov 28, 2014 at 12:35 PM, Arpit Jalan= + <info@discourse.org> wrote:
+ + + + + + + + + + + +
+ + + techAPJ
+ November 28 +
+

Test reply.

+ +

First paragraph.

+ +

Second paragraph.

+
+ + +
+

To respond, reply to this email or visit https:/= +/meta.discourse.org/t/testing-default-email-replies/22638/3 in your bro= +wser.

+
+
+

Previous Replies

+ + + + + + + + + + + +
+ + + codinghorror + November 28 +
+

We're testing the latest GitHub emai= +l processing library which we are integrating now.

+ +

https://github.com/github/email_reply_parser

+ +

Go ahead and reply to this topic and I&#= +39;ll reply from various email clients for testing.

+
+ + +
+ +
+

To respond, reply to this email or visit https://met= +a.discourse.org/t/testing-default-email-replies/22638/3 in your browser= +.

+
+
+

To unsubscribe from these emails, visit your user preferences.

+
+
+

+ +--001a11c2e04e6544f30508f138ba-- diff --git a/spec/fixtures/emails/hebrew.eml b/spec/fixtures/emails/hebrew.eml new file mode 100644 index 00000000000..f4c8f01adc3 --- /dev/null +++ b/spec/fixtures/emails/hebrew.eml @@ -0,0 +1,17 @@ + +Delivered-To: discourse-reply+cd480e301683c9902891f15968bf07a5@discourse.org +Received: by 10.194.216.104 with SMTP id op8csp80593wjc; + Wed, 24 Jul 2013 07:59:14 -0700 (PDT) +Return-Path: +References: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> +From: Walter White +In-Reply-To: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> +Mime-Version: 1.0 (1.0) +Date: Wed, 24 Jul 2013 15:59:10 +0100 +Message-ID: <4597127794206131679@unknownmsgid> +Subject: Re: [Discourse] new reply to your post in 'Crystal Blue' +To: walter via Discourse +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: base64 + +16nXnNeV150= \ No newline at end of file diff --git a/spec/fixtures/emails/html_only.eml b/spec/fixtures/emails/html_only.eml new file mode 100644 index 00000000000..561b8db2c79 --- /dev/null +++ b/spec/fixtures/emails/html_only.eml @@ -0,0 +1,93 @@ + +Delivered-To: walter@breakingbad.com +Received: by 10.64.13.41 with SMTP id m9csp29769iec; + Thu, 20 Jun 2013 08:53:22 -0700 (PDT) +X-Received: by 10.252.23.9 with SMTP id p9mr4055675lag.4.1371743601980; + Thu, 20 Jun 2013 08:53:21 -0700 (PDT) +Received: from mail-la0-x229.google.com (mail-la0-x229.google.com [2a00:1450:4010:c03::229]) + by mx.google.com with ESMTPS id u4si430203lae.48.2013.06.20.08.53.20 + for + (version=TLSv1 cipher=ECDHE-RSA-RC4-SHA bits=128/128); + Thu, 20 Jun 2013 08:53:21 -0700 (PDT) +X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; + d=google.com; s=20120113; + h=x-forwarded-to:x-forwarded-for:delivered-to:x-return-path + :content-type:mime-version:content-transfer-encoding:x-mailer + :message-id:date:subject:from:in-reply-to:to:resent-date:resent-from + :resent-to:resent-subject:resent-message-id:resent-user-agent + :x-scanned-by:x-gm-message-state; + bh=9O67r74ofh9WkEaKTRB/frQ3MKOtQlbCac2mz0/MiyY=; + b=YVAo2/JDMP53RxDmqDEKNcEMtggtfaVyq2DoseZ6vBAfB7G6NtHC9ZEkRs4oGhk6LU + fnyAPe0wnz5d9WINoMAuuTRIhplLxzcqysduSnAJAQ2qqR7mFBnlj9wJeVEKltNwmUME + nPwxsf8go20VBzrZCtECPedcLi60wbl32NCXVn0qwt2LvKiy6ktSS5Xgb4zY8i4dfXAP + 6Y5gu32boooWIb9DkH1TJkn3C0RrEugNlw/DUnXrnkFefgxWF3pt/zcoW/wYRyikOdx+ + smBClgR9my6QmsS2KsQrMvWJZUva7fddTiZ6FC22e4hW+8Wha0RaZOZu5O7hjg6G4/1g + IEyg== +X-Received: by 10.112.55.9 with SMTP id n9mr5916187lbp.5.1371743600857; + Thu, 20 Jun 2013 08:53:20 -0700 (PDT) +X-Forwarded-To: walter@breakingbad.com +X-Forwarded-For: walter@breakingbad.com +Delivered-To: walter@breakingbad.com +Content-Type: text/html; charset="us-ascii" +MIME-Version: 1.0 +Content-Transfer-Encoding: quoted-printable +X-Mailer: BlackBerry Email (10.1.0.1720) +Message-ID: <20130619231548.6307981.74194.2379@breakingbad.com> +Date: Wed, 19 Jun 2013 19:15:48 -0400 +Subject: Re: [Discourse Meta] [PM] re: Regarding your post in "Site + Customization not working" +From: aaron@breakingbad.com +In-Reply-To: <51c238655a394_5f4e3ce6690667bd@tiefighter2.mail> +To: reply+20c1b0a8bd1a63c0163cc7e7641ca06b@appmail.adventuretime.ooo +ReSent-Date: Thu, 20 Jun 2013 11:53:08 -0400 (EDT) +ReSent-From: Aaron +ReSent-Subject: Re: [Discourse Meta] [PM] re: Regarding your post in "Site + Customization not working" +X-Gm-Message-State: ALoCoQl1BtN83rAX7At808XAPv1yCqUK3Du2IvK7eCyY3jsI77u4e5cak28307pYYHAo1JlO/Eu9 + +
The EC2 instance - I've seen that th= +ere tends to be odd and unrecommended settings on the Bitnami installs that= + I've checked out.
= + = +

= + = +
= + = + = +
= +From: Grizzly B via Discourse Meta
Sent: Wednesday, J= +une 19, 2013 19:02
To: aaron@breakingbad.com
= +Reply To: Grizzly B via Discourse Meta
Subject: [Disc= +ourse Meta] [PM] re: Regarding your post in "Site Customization
not wor= +king"

Grizzly B just sent you a private message

+ +

Log in to our EC2 instance -or- log into a new Digital Ocean instanc= +e?

+ +

Please visit this link to respond: http://= +meta.discourse.org/t/regarding-your-post-in-site-customization-not-working/= +7641/5

+ +

To unsubscribe from these emails, visit your user preferences.

+
diff --git a/spec/fixtures/emails/html_paragraphs.eml b/spec/fixtures/emails/html_paragraphs.eml new file mode 100644 index 00000000000..3fe37fb8b17 --- /dev/null +++ b/spec/fixtures/emails/html_paragraphs.eml @@ -0,0 +1,205 @@ + +MIME-Version: 1.0 +Received: by 10.25.161.144 with HTTP; Tue, 7 Oct 2014 22:17:17 -0700 (PDT) +X-Originating-IP: [117.207.85.84] +In-Reply-To: <5434c8b52bb3a_623ff09fec70f049749@discourse-app.mail> +References: + <5434c8b52bb3a_623ff09fec70f049749@discourse-app.mail> +Date: Wed, 8 Oct 2014 10:47:17 +0530 +Delivered-To: arpit@techapj.com +Message-ID: +Subject: Re: [Discourse] [Meta] Welcome to techAPJ's Discourse! +From: Arpit Jalan +To: Discourse +Content-Type: multipart/alternative; boundary=001a114119d8f4e46e0504e26d5b + +--001a114119d8f4e46e0504e26d5b +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +Awesome! + +Pleasure to have you here! + +:boom: + +On Wed, Oct 8, 2014 at 10:46 AM, ajalan +wrote: + +> ajalan +> +> October 8 +> +> Nice to be here! Thanks! [image: smile] +> +> To respond, reply to this email or visit +> http://discourse.techapj.com/t/welcome-to-techapjs-discourse/35/2 +> +> in your browser. +> ------------------------------ +> Previous Replies techAPJ +> +> October 8 +> +> Welcome to techAPJ's Discourse! +> ------------------------------ +> +> To respond, reply to this email or visit +> http://discourse.techapj.com/t/welcome-to-techapjs-discourse/35/2 +> +> in your browser. +> +> To unsubscribe from these emails, visit your user preferences +> +> . +> + +--001a114119d8f4e46e0504e26d5b +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +
Awesome!

Pleasure to have you here!

:boom:

On Wed, Oct 8, 2014 at 10:46 AM, ajalan <info@unconfigured.discourse.org> wrote:
+ + + + + + + + + + + +
+ + + ajalan
+ October 8 +

Nice to be here! Thanks! 3D"smile"

+ + +
+

To respond, reply to this email or visit http://discourse= +.techapj.com/t/welcome-to-techapjs-discourse/35/2 in your browser.

+
+
+

Previous Replies

+ + + + + + + + + + + +
+ + + techAPJ
+ October 8 +

Welcome to techAPJ's Discourse!

+ + +
+ +
+

To respond, reply to this email or visit http://discourse.tec= +hapj.com/t/welcome-to-techapjs-discourse/35/2 in your browser.

+
+
+

To unsubscribe from these emails, visit your user preferences.

+
+
+ +
= +
+ +--001a114119d8f4e46e0504e26d5b-- diff --git a/spec/fixtures/emails/inline_reply.eml b/spec/fixtures/emails/inline_reply.eml new file mode 100644 index 00000000000..39625a225da --- /dev/null +++ b/spec/fixtures/emails/inline_reply.eml @@ -0,0 +1,60 @@ + +MIME-Version: 1.0 +In-Reply-To: +References: + <5434ced4ee0f9_663fb0b5f76070593b@discourse-app.mail> +Date: Mon, 1 Dec 2014 20:48:40 +0530 +Delivered-To: someone@googlemail.com +Subject: Re: [Discourse] [Meta] Testing reply via email +From: Walter White +To: Discourse +Content-Type: multipart/alternative; boundary=20cf30363f8522466905092920a6 + +--20cf30363f8522466905092920a6 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +On Wed, Oct 8, 2014 at 11:12 AM, techAPJ +wrote: + +> techAPJ +> November 28 +> +> Test reply. +> +> First paragraph. +> +> Second paragraph. +> +> To respond, reply to this email or visit +> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in +> your browser. +> ------------------------------ +> Previous Replies codinghorror +> +> November 28 +> +> We're testing the latest GitHub email processing library which we are +> integrating now. +> +> https://github.com/github/email_reply_parser +> +> Go ahead and reply to this topic and I'll reply from various email clients +> for testing. +> ------------------------------ +> +> To respond, reply to this email or visit +> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in +> your browser. +> +> To unsubscribe from these emails, visit your user preferences +> . +> + +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over +the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown +fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over +the lazy dog. The quick brown fox jumps over the lazy dog. + +--20cf30363f8522466905092920a6-- diff --git a/spec/fixtures/emails/ios_default.eml b/spec/fixtures/emails/ios_default.eml new file mode 100644 index 00000000000..8d4d58feb16 --- /dev/null +++ b/spec/fixtures/emails/ios_default.eml @@ -0,0 +1,136 @@ +Delivered-To: reply@discourse.org +Return-Path: +From: Walter White +Content-Type: multipart/alternative; + boundary=Apple-Mail-B41C7F8E-3639-49B0-A5D5-440E125A7105 +Content-Transfer-Encoding: 7bit +Mime-Version: 1.0 (1.0) +Subject: Re: [Discourse Meta] [Lounge] Testing default email replies +Date: Fri, 28 Nov 2014 12:41:41 -0800 +References: +In-Reply-To: +To: Discourse Meta +X-Mailer: iPhone Mail (12B436) + + +--Apple-Mail-B41C7F8E-3639-49B0-A5D5-440E125A7105 +Content-Type: text/plain; + charset=us-ascii +Content-Transfer-Encoding: quoted-printable + +### this is a reply from iOS default mail + +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over t= +he lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fo= +x jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The q= +uick brown fox jumps over the lazy dog. The quick brown fox jumps over the l= +azy dog.=20 + +Here's some **bold** markdown text. + +Here's a link http://example.com + + +> On Nov 28, 2014, at 12:35 PM, Arpit Jalan wrote: +>=20 +>=20 +> techAPJ +> November 28 +> Test reply. +>=20 +> First paragraph. +>=20 +> Second paragraph. +>=20 +> To respond, reply to this email or visit https://meta.discourse.org/t/test= +ing-default-email-replies/22638/3 in your browser. +>=20 +> Previous Replies +>=20 +> codinghorror +> November 28 +> We're testing the latest GitHub email processing library which we are inte= +grating now. +>=20 +> https://github.com/github/email_reply_parser +>=20 +> Go ahead and reply to this topic and I'll reply from various email clients= + for testing. +>=20 +> To respond, reply to this email or visit https://meta.discourse.org/t/test= +ing-default-email-replies/22638/3 in your browser. +>=20 +> To unsubscribe from these emails, visit your user preferences. + +--Apple-Mail-B41C7F8E-3639-49B0-A5D5-440E125A7105 +Content-Type: text/html; + charset=utf-8 +Content-Transfer-Encoding: 7bit + +
### this is a reply from iOS default mail

The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. 

Here's some **bold** markdown text.

Here's a link http://example.com


On Nov 28, 2014, at 12:35 PM, Arpit Jalan <info@discourse.org> wrote:

+ + + + + + + + + + + +
+ + + techAPJ
+ November 28 +
+

Test reply.

+ +

First paragraph.

+ +

Second paragraph.

+
+ + +
+

To respond, reply to this email or visit https://meta.discourse.org/t/testing-default-email-replies/22638/3 in your browser.

+
+
+

Previous Replies

+ + + + + + + + + + + +
+ + + codinghorror
+ November 28 +
+

We're testing the latest GitHub email processing library which we are integrating now.

+ +

https://github.com/github/email_reply_parser

+ +

Go ahead and reply to this topic and I'll reply from various email clients for testing.

+
+ + +
+ +
+

To respond, reply to this email or visit https://meta.discourse.org/t/testing-default-email-replies/22638/3 in your browser.

+
+
+

To unsubscribe from these emails, visit your user preferences.

+
+
+
+--Apple-Mail-B41C7F8E-3639-49B0-A5D5-440E125A7105-- diff --git a/spec/fixtures/emails/iphone_signature.eml b/spec/fixtures/emails/iphone_signature.eml new file mode 100644 index 00000000000..d314ad1f1ea --- /dev/null +++ b/spec/fixtures/emails/iphone_signature.eml @@ -0,0 +1,29 @@ +Delivered-To: test@mail.com +Return-Path: +From: Walter White +Content-Type: multipart/alternative; + boundary=Apple-Mail-8E182EEF-9DBC-41DE-A593-DF2E5EBD3975 +Content-Transfer-Encoding: 7bit +Mime-Version: 1.0 (1.0) +Subject: Re: Signature in email replies! +Date: Thu, 23 Oct 2014 14:43:49 +0530 +References: <1234@mail.gmail.com> +In-Reply-To: <1234@mail.gmail.com> +To: Arpit Jalan +X-Mailer: iPhone Mail (12A405) + + +--Apple-Mail-8E182EEF-9DBC-41DE-A593-DF2E5EBD3975 +Content-Type: text/plain; + charset=us-ascii +Content-Transfer-Encoding: 7bit + +This post should not include signature. + +Sent from my iPhone + +> On 23-Oct-2014, at 9:45 am, Arpit Jalan wrote: +> +> Signature in email replies! + +--Apple-Mail-8E182EEF-9DBC-41DE-A593-DF2E5EBD3975 diff --git a/spec/fixtures/emails/multiple_destinations.eml b/spec/fixtures/emails/multiple_destinations.eml new file mode 100644 index 00000000000..6d31bbf1974 --- /dev/null +++ b/spec/fixtures/emails/multiple_destinations.eml @@ -0,0 +1,40 @@ +Return-Path: +Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 +Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 +Date: Thu, 13 Jun 2013 17:03:48 -0400 +From: Jake the Dog +To: finn@adventuretime.ooo, reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo +Message-ID: +Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' +Mime-Version: 1.0 +Content-Type: text/plain; + charset=ISO-8859-1 +Content-Transfer-Encoding: 7bit +X-Sieve: CMU Sieve 2.2 +X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, + 13 Jun 2013 14:03:48 -0700 (PDT) +X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 + +I could not disagree more. I am obviously biased but adventure time is the +greatest show ever created. Everyone should watch it. + +- Jake out + + +On Sun, Jun 9, 2013 at 1:39 PM, eviltrout via Discourse Meta + wrote: +> +> +> +> eviltrout posted in 'Adventure Time Sux' on Discourse Meta: +> +> --- +> hey guys everyone knows adventure time sucks! +> +> --- +> Please visit this link to respond: http://localhost:3000/t/adventure-time-sux/1234/3 +> +> To unsubscribe from these emails, visit your [user preferences](http://localhost:3000/user_preferences). +> \ No newline at end of file diff --git a/spec/fixtures/emails/newlines.eml b/spec/fixtures/emails/newlines.eml new file mode 100644 index 00000000000..cf03b9d18bc --- /dev/null +++ b/spec/fixtures/emails/newlines.eml @@ -0,0 +1,84 @@ +In-Reply-To: +Date: Wed, 8 Oct 2014 10:36:19 +0530 +Delivered-To: walter.white@googlemail.com +Subject: Re: [Discourse] Welcome to Discourse +From: Walter White +To: Discourse +Content-Type: multipart/alternative; boundary=bcaec554078cc3d0c10504e24661 + +--bcaec554078cc3d0c10504e24661 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +This is my reply. +It is my best reply. +It will also be my *only* reply. + +On Wed, Oct 8, 2014 at 10:33 AM, ajalan +wrote: + +> ajalan +> +> October 8 +> +> Awesome! Thank You! [image: +1] +> +> To respond, reply to this email or visit +> http://discourse.techapj.com/t/welcome-to-discourse/8/2 +> +> in your browser. +> ------------------------------ +> Previous Replies system +> +> October 8 +> +> The first paragraph of this pinned topic will be visible as a welcome +> message to all new visitors on your homepage. It's important! +> +> *Edit this* into a brief description of your community: +> +> - Who is it for? +> - What can they find here? +> - Why should they come here? +> - Where can they read more (links, resources, etc)? +> +> You may want to close this topic via the wrench icon at the upper right, +> so that replies don't pile up on an announcement. +> ------------------------------ +> +> To respond, reply to this email or visit +> http://discourse.techapj.com/t/welcome-to-discourse/8/2 +> +> in your browser. +> +> To unsubscribe from these emails, visit your user preferences +> +> . +> + +--bcaec554078cc3d0c10504e24661 diff --git a/spec/fixtures/emails/no_content_reply.eml b/spec/fixtures/emails/no_content_reply.eml new file mode 100644 index 00000000000..95eb2055ce6 --- /dev/null +++ b/spec/fixtures/emails/no_content_reply.eml @@ -0,0 +1,34 @@ +Return-Path: +Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 +Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 +Date: Thu, 13 Jun 2013 17:03:48 -0400 +From: Jake the Dog +To: reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo +Message-ID: +Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' +Mime-Version: 1.0 +Content-Type: text/plain; + charset=ISO-8859-1 +Content-Transfer-Encoding: 7bit +X-Sieve: CMU Sieve 2.2 +X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, + 13 Jun 2013 14:03:48 -0700 (PDT) +X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 + +On Sun, Jun 9, 2013 at 1:39 PM, eviltrout via Discourse Meta + wrote: +> +> +> +> eviltrout posted in 'Adventure Time Sux' on Discourse Meta: +> +> --- +> hey guys everyone knows adventure time sucks! +> +> --- +> Please visit this link to respond: http://localhost:3000/t/adventure-time-sux/1234/3 +> +> To unsubscribe from these emails, visit your [user preferences](http://localhost:3000/user_preferences). +> \ No newline at end of file diff --git a/spec/fixtures/emails/on_wrote.eml b/spec/fixtures/emails/on_wrote.eml new file mode 100644 index 00000000000..feb59bd27bb --- /dev/null +++ b/spec/fixtures/emails/on_wrote.eml @@ -0,0 +1,277 @@ + +MIME-Version: 1.0 +Received: by 10.107.9.17 with HTTP; Tue, 9 Sep 2014 16:18:19 -0700 (PDT) +In-Reply-To: <540f16d4c08d9_4a3f9ff6d61890391c@tiefighter4-meta.mail> +References: + <540f16d4c08d9_4a3f9ff6d61890391c@tiefighter4-meta.mail> +Date: Tue, 9 Sep 2014 16:18:19 -0700 +Delivered-To: kanepyork@gmail.com +Message-ID: +Subject: Re: [Discourse Meta] Badge icons - where to find them? +From: Kane York +To: Discourse Meta +Content-Type: multipart/alternative; boundary=001a11c34c389e728f0502aa26a0 + +--001a11c34c389e728f0502aa26a0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +Sure, all you need to do is frobnicate the foobar and you'll be all set! + +On Tue, Sep 9, 2014 at 8:03 AM, gordon_ryan wrote: + +> gordon_ryan +> September 9 +> +> @riking - willing to step by +> step of the custom icon method for an admittedly ignorant admin? Seriousl= +y +> confused. +> +> Or anyone else who knows how to do this [image: smiley] +> +> To respond, reply to this email or visit +> https://meta.discourse.org/t/badge-icons-where-to-find-them/18058/9 in +> your browser. +> ------------------------------ +> Previous Replies riking +> July 25 +> +> Check out the "HTML Head" section in the "Content" tab of the admin panel= +. +> meglio +> July 25 +> +> How will it load the related custom font? +> riking +> July 25 +> +> Here's an example of the styles that FA applies. I'll use fa-heart"> as the example. +> +> .fa { +> display: inline-block; +> font-family: FontAwesome; +> font-style: normal; +> font-weight: normal; +> line-height: 1; +> -webkit-font-smoothing: antialiased; +> -moz-osx-font-smoothing: grayscale; +> } +> .fa-heart:before { +> content: "\f004"; +> } +> +> So you could do this in your site stylesheet: +> +> .fa-custom-burger:before { +> content: "\01f354"; +> font-family: inherit; +> } +> +> And get =F0=9F=8D=94 as your badge icon when you enter custom-burger. +> ------------------------------ +> +> To respond, reply to this email or visit +> https://meta.discourse.org/t/badge-icons-where-to-find-them/18058/9 in +> your browser. +> +> To unsubscribe from these emails, visit your user preferences +> . +> + +--001a11c34c389e728f0502aa26a0 +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +
Sure, all you need to do is frobnicate the foobar and you'll be all s= +et!


= +
On Tue, Sep 9, 2014 at 8:03 AM, gordon_ryan = +<info@discourse.org> wrote:
+ + + + + + + + + + + + +
+ + + gordon_ryan
+ September 9 +
+

@riking- willing to step by step of the custom icon me= +thod for an admittedly ignorant admin? Seriously confused.

+ +

Or anyone else who knows how to do this = +3D"smiley"

+
+ + +
+

To respond, reply to this email or visit https:= +//meta.discourse.org/t/badge-icons-where-to-find-them/18058/9 in your b= +rowser.

+
+
+

Previous Replies

+ + + + + + + + + + + +
+ + + riking
+ July 25 +

Check out the "HTML Head" section in the "Content&= +quot; tab of the admin panel.

+ + + + + + + + + + + +
+ + + meglio
+ July 25 +

How will it load the related custom font?

+ + + + + + + + + + + +
+ + + riking
+ July 25 +
+

Here's an example of the styles that= + FA applies. I'll use <i class=3D"fa fa-heart"></i> as the e= +xample.

+ +

+
.fa {
+  display: inline-block;
+  font-family: FontAwesome;
+  font-style: normal;
+  font-weight: normal;
+  line-height: 1;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+.fa-heart:before {
+  content: "\f004";
+}
+ +

So you could do this in your site styles= +heet:

+ +

+
.fa-custom-burger:before {
+  content: "\01f354";
+  font-family: inherit;
+}
+ +

And get =F0=9F=8D=94 as your badge icon = +when you enter cus= +tom-burger.

+
+ + +
+ +
+

To respond, reply to this email or visit https://me= +ta.discourse.org/t/badge-icons-where-to-find-them/18058/9 in your brows= +er.

+
+
+

To unsubscribe from these emails, visit your user preferences.

+
+
+

+ +--001a11c34c389e728f0502aa26a0-- \ No newline at end of file diff --git a/spec/fixtures/emails/outlook.eml b/spec/fixtures/emails/outlook.eml new file mode 100644 index 00000000000..fb1f590a30e --- /dev/null +++ b/spec/fixtures/emails/outlook.eml @@ -0,0 +1,188 @@ + +MIME-Version: 1.0 +Received: by 10.25.161.144 with HTTP; Tue, 7 Oct 2014 22:17:17 -0700 (PDT) +X-Originating-IP: [117.207.85.84] +In-Reply-To: <5434c8b52bb3a_623ff09fec70f049749@discourse-app.mail> +References: + <5434c8b52bb3a_623ff09fec70f049749@discourse-app.mail> +Date: Wed, 8 Oct 2014 10:47:17 +0530 +Delivered-To: arpit@techapj.com +Message-ID: +Subject: Re: [Discourse] [Meta] Welcome to techAPJ's Discourse! +From: Arpit Jalan +To: Discourse Accept-Language: en-US +Content-Language: en-US +X-MS-Has-Attach: +X-MS-TNEF-Correlator: +x-originating-ip: [134.68.31.227] +Content-Type: multipart/alternative; + boundary="_000_B0DFE1BEB3739743BC9B639D0E6BC8FF217A6341IUMSSGMBX104ads_" +MIME-Version: 1.0 + +--_000_B0DFE1BEB3739743BC9B639D0E6BC8FF217A6341IUMSSGMBX104ads_ +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: base64 + +TWljcm9zb2Z0IE91dGxvb2sgMjAxMA0KDQpGcm9tOiBtaWNoYWVsIFttYWlsdG86dGFsa0BvcGVu +bXJzLm9yZ10NClNlbnQ6IE1vbmRheSwgT2N0b2JlciAxMywgMjAxNCA5OjM4IEFNDQpUbzogUG93 +ZXIsIENocmlzDQpTdWJqZWN0OiBbUE1dIFlvdXIgcG9zdCBpbiAiQnVyZ2VyaGF1czogTmV3IHJl +c3RhdXJhbnQgLyBsdW5jaCB2ZW51ZSINCg0KDQptaWNoYWVsPGh0dHA6Ly9jbC5vcGVubXJzLm9y +Zy90cmFjay9jbGljay8zMDAzOTkwNS90YWxrLm9wZW5tcnMub3JnP3A9ZXlKeklqb2liR2xaYTFW +MGVYaENZMDFNUlRGc1VESm1ZelZRTTBabGVqRTRJaXdpZGlJNk1Td2ljQ0k2SW50Y0luVmNJam96 +TURBek9Ua3dOU3hjSW5aY0lqb3hMRndpZFhKc1hDSTZYQ0pvZEhSd2N6cGNYRnd2WEZ4Y0wzUmhi +R3N1YjNCbGJtMXljeTV2Y21kY1hGd3ZkWE5sY25OY1hGd3ZiV2xqYUdGbGJGd2lMRndpYVdSY0lq +cGNJbVExWW1Nd04yTmtORFJqWkRRNE1HTTRZVGcyTXpsalpXSTFOemd6WW1ZMlhDSXNYQ0oxY214 +ZmFXUnpYQ0k2VzF3aVlqaGtPRGcxTWprNU56ZG1aalkxWldZeU5URTNPV1JpTkdZeU1XSTNOekZq +TnpoalpqaGtPRndpWFgwaWZRPg0KT2N0b2JlciAxMw0KDQpodHRwczovL3RhbGsub3Blbm1ycy5v +cmcvdC9idXJnZXJoYXVzLW5ldy1yZXN0YXVyYW50LWx1bmNoLXZlbnVlLzY3Mi8zPGh0dHA6Ly9j +bC5vcGVubXJzLm9yZy90cmFjay9jbGljay8zMDAzOTkwNS90YWxrLm9wZW5tcnMub3JnP3A9ZXlK +eklqb2lVRVJJU1VOeVIzbFZNRGRCVlZocFduUjNXV3g0TVdOc1RXNVpJaXdpZGlJNk1Td2ljQ0k2 +SW50Y0luVmNJam96TURBek9Ua3dOU3hjSW5aY0lqb3hMRndpZFhKc1hDSTZYQ0pvZEhSd2N6cGNY +Rnd2WEZ4Y0wzUmhiR3N1YjNCbGJtMXljeTV2Y21kY1hGd3ZkRnhjWEM5aWRYSm5aWEpvWVhWekxX +NWxkeTF5WlhOMFlYVnlZVzUwTFd4MWJtTm9MWFpsYm5WbFhGeGNMelkzTWx4Y1hDOHpYQ0lzWENK +cFpGd2lPbHdpWkRWaVl6QTNZMlEwTkdOa05EZ3dZemhoT0RZek9XTmxZalUzT0ROaVpqWmNJaXhj +SW5WeWJGOXBaSE5jSWpwYlhDSmlOelppWWprMFpURmlOekk1WlRrMlpUUmxaV000TkdSbU1qUTRN +RE13WWpZeVlXWXlNR00wWENKZGZTSjk+DQoNCkxvb2tzIGxpa2UgeW91ciByZXBseS1ieS1lbWFp +bCB3YXNuJ3QgcHJvY2Vzc2VkIGNvcnJlY3RseSBieSBvdXIgc29mdHdhcmUuIENhbiB5b3UgbGV0 +IG1lIGtub3cgd2hhdCB2ZXJzaW9uL09TIG9mIHdoYXQgZW1haWwgcHJvZ3JhbSB5b3UncmUgdXNp +bmc/IFdlIHdpbGwgd2FudCB0byB0cnkgdG8gZml4IHRoZSBidWcuIDpzbWlsZToNCg0KVGhhbmtz +IQ0KDQoNCl9fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fDQoNClRvIHJlc3BvbmQsIHJl +cGx5IHRvIHRoaXMgZW1haWwgb3IgdmlzaXQgaHR0cHM6Ly90YWxrLm9wZW5tcnMub3JnL3QveW91 +ci1wb3N0LWluLWJ1cmdlcmhhdXMtbmV3LXJlc3RhdXJhbnQtbHVuY2gtdmVudWUvNjc0LzE8aHR0 +cDovL2NsLm9wZW5tcnMub3JnL3RyYWNrL2NsaWNrLzMwMDM5OTA1L3RhbGsub3Blbm1ycy5vcmc/ +cD1leUp6SWpvaWVYaDJWbnBGTUhSMU1uRm5aRWR1TlhFd01GcFFPVlp0VFZvNElpd2lkaUk2TVN3 +aWNDSTZJbnRjSW5WY0lqb3pNREF6T1Rrd05TeGNJblpjSWpveExGd2lkWEpzWENJNlhDSm9kSFJ3 +Y3pwY1hGd3ZYRnhjTDNSaGJHc3ViM0JsYm0xeWN5NXZjbWRjWEZ3dmRGeGNYQzk1YjNWeUxYQnZj +M1F0YVc0dFluVnlaMlZ5YUdGMWN5MXVaWGN0Y21WemRHRjFjbUZ1ZEMxc2RXNWphQzEyWlc1MVpW +eGNYQzgyTnpSY1hGd3ZNVndpTEZ3aWFXUmNJanBjSW1RMVltTXdOMk5rTkRSalpEUTRNR000WVRn +Mk16bGpaV0kxTnpnelltWTJYQ0lzWENKMWNteGZhV1J6WENJNlcxd2lZamMyWW1JNU5HVXhZamN5 +T1dVNU5tVTBaV1ZqT0RSa1pqSTBPREF6TUdJMk1tRm1NakJqTkZ3aVhYMGlmUT4gaW4geW91ciBi +cm93c2VyLg0KDQpUbyB1bnN1YnNjcmliZSBmcm9tIHRoZXNlIGVtYWlscywgdmlzaXQgeW91ciB1 +c2VyIHByZWZlcmVuY2VzPGh0dHA6Ly9jbC5vcGVubXJzLm9yZy90cmFjay9jbGljay8zMDAzOTkw +NS90YWxrLm9wZW5tcnMub3JnP3A9ZXlKeklqb2lkVXh1V2xnNVZGYzBPV1pXUzBZNGJGZExkbWx5 +V0dzeFRWOXpJaXdpZGlJNk1Td2ljQ0k2SW50Y0luVmNJam96TURBek9Ua3dOU3hjSW5aY0lqb3hM +RndpZFhKc1hDSTZYQ0pvZEhSd2N6cGNYRnd2WEZ4Y0wzUmhiR3N1YjNCbGJtMXljeTV2Y21kY1hG +d3ZiWGxjWEZ3dmNISmxabVZ5Wlc1alpYTmNJaXhjSW1sa1hDSTZYQ0prTldKak1EZGpaRFEwWTJR +ME9EQmpPR0U0TmpNNVkyVmlOVGM0TTJKbU5sd2lMRndpZFhKc1gybGtjMXdpT2x0Y0ltSTRNV1V3 +WmpBMU5EWTVORE0wTnpneU0yRm1NakEyTmpGalpqYzNaR05pTjJOaFl6ZG1NakpjSWwxOUluMD4u +DQoNCg== + +--_000_B0DFE1BEB3739743BC9B639D0E6BC8FF217A6341IUMSSGMBX104ads_ +Content-Type: text/html; charset="utf-8" +Content-Transfer-Encoding: base64 + +PGh0bWwgeG1sbnM6dj0idXJuOnNjaGVtYXMtbWljcm9zb2Z0LWNvbTp2bWwiIHhtbG5zOm89InVy +bjpzY2hlbWFzLW1pY3Jvc29mdC1jb206b2ZmaWNlOm9mZmljZSIgeG1sbnM6dz0idXJuOnNjaGVt +YXMtbWljcm9zb2Z0LWNvbTpvZmZpY2U6d29yZCIgeG1sbnM6bT0iaHR0cDovL3NjaGVtYXMubWlj +cm9zb2Z0LmNvbS9vZmZpY2UvMjAwNC8xMi9vbW1sIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv +VFIvUkVDLWh0bWw0MCI+DQo8aGVhZD4NCjxtZXRhIGh0dHAtZXF1aXY9IkNvbnRlbnQtVHlwZSIg +Y29udGVudD0idGV4dC9odG1sOyBjaGFyc2V0PXV0Zi04Ij4NCjxtZXRhIG5hbWU9IkdlbmVyYXRv +ciIgY29udGVudD0iTWljcm9zb2Z0IFdvcmQgMTQgKGZpbHRlcmVkIG1lZGl1bSkiPg0KPCEtLVtp +ZiAhbXNvXT48c3R5bGU+dlw6KiB7YmVoYXZpb3I6dXJsKCNkZWZhdWx0I1ZNTCk7fQ0Kb1w6KiB7 +YmVoYXZpb3I6dXJsKCNkZWZhdWx0I1ZNTCk7fQ0Kd1w6KiB7YmVoYXZpb3I6dXJsKCNkZWZhdWx0 +I1ZNTCk7fQ0KLnNoYXBlIHtiZWhhdmlvcjp1cmwoI2RlZmF1bHQjVk1MKTt9DQo8L3N0eWxlPjwh +W2VuZGlmXS0tPjxzdHlsZT48IS0tDQovKiBGb250IERlZmluaXRpb25zICovDQpAZm9udC1mYWNl +DQoJe2ZvbnQtZmFtaWx5OkNhbGlicmk7DQoJcGFub3NlLTE6MiAxNSA1IDIgMiAyIDQgMyAyIDQ7 +fQ0KQGZvbnQtZmFjZQ0KCXtmb250LWZhbWlseTpUYWhvbWE7DQoJcGFub3NlLTE6MiAxMSA2IDQg +MyA1IDQgNCAyIDQ7fQ0KLyogU3R5bGUgRGVmaW5pdGlvbnMgKi8NCnAuTXNvTm9ybWFsLCBsaS5N +c29Ob3JtYWwsIGRpdi5Nc29Ob3JtYWwNCgl7bWFyZ2luOjBpbjsNCgltYXJnaW4tYm90dG9tOi4w +MDAxcHQ7DQoJZm9udC1zaXplOjEyLjBwdDsNCglmb250LWZhbWlseToiVGltZXMgTmV3IFJvbWFu +Iiwic2VyaWYiO30NCmE6bGluaywgc3Bhbi5Nc29IeXBlcmxpbmsNCgl7bXNvLXN0eWxlLXByaW9y +aXR5Ojk5Ow0KCWNvbG9yOmJsdWU7DQoJdGV4dC1kZWNvcmF0aW9uOnVuZGVybGluZTt9DQphOnZp +c2l0ZWQsIHNwYW4uTXNvSHlwZXJsaW5rRm9sbG93ZWQNCgl7bXNvLXN0eWxlLXByaW9yaXR5Ojk5 +Ow0KCWNvbG9yOnB1cnBsZTsNCgl0ZXh0LWRlY29yYXRpb246dW5kZXJsaW5lO30NCnANCgl7bXNv +LXN0eWxlLXByaW9yaXR5Ojk5Ow0KCW1zby1tYXJnaW4tdG9wLWFsdDphdXRvOw0KCW1hcmdpbi1y +aWdodDowaW47DQoJbXNvLW1hcmdpbi1ib3R0b20tYWx0OmF1dG87DQoJbWFyZ2luLWxlZnQ6MGlu +Ow0KCWZvbnQtc2l6ZToxMi4wcHQ7DQoJZm9udC1mYW1pbHk6IlRpbWVzIE5ldyBSb21hbiIsInNl +cmlmIjt9DQpzcGFuLkVtYWlsU3R5bGUxOA0KCXttc28tc3R5bGUtdHlwZTpwZXJzb25hbC1yZXBs +eTsNCglmb250LWZhbWlseToiQ2FsaWJyaSIsInNhbnMtc2VyaWYiOw0KCWNvbG9yOiMxRjQ5N0Q7 +fQ0KLk1zb0NocERlZmF1bHQNCgl7bXNvLXN0eWxlLXR5cGU6ZXhwb3J0LW9ubHk7DQoJZm9udC1m +YW1pbHk6IkNhbGlicmkiLCJzYW5zLXNlcmlmIjt9DQpAcGFnZSBXb3JkU2VjdGlvbjENCgl7c2l6 +ZTo4LjVpbiAxMS4waW47DQoJbWFyZ2luOjEuMGluIDEuMGluIDEuMGluIDEuMGluO30NCmRpdi5X +b3JkU2VjdGlvbjENCgl7cGFnZTpXb3JkU2VjdGlvbjE7fQ0KLS0+PC9zdHlsZT48IS0tW2lmIGd0 +ZSBtc28gOV0+PHhtbD4NCjxvOnNoYXBlZGVmYXVsdHMgdjpleHQ9ImVkaXQiIHNwaWRtYXg9IjEw +MjYiIC8+DQo8L3htbD48IVtlbmRpZl0tLT48IS0tW2lmIGd0ZSBtc28gOV0+PHhtbD4NCjxvOnNo +YXBlbGF5b3V0IHY6ZXh0PSJlZGl0Ij4NCjxvOmlkbWFwIHY6ZXh0PSJlZGl0IiBkYXRhPSIxIiAv +Pg0KPC9vOnNoYXBlbGF5b3V0PjwveG1sPjwhW2VuZGlmXS0tPg0KPC9oZWFkPg0KPGJvZHkgbGFu +Zz0iRU4tVVMiIGxpbms9ImJsdWUiIHZsaW5rPSJwdXJwbGUiPg0KPGRpdiBjbGFzcz0iV29yZFNl +Y3Rpb24xIj4NCjxwIGNsYXNzPSJNc29Ob3JtYWwiPjxzcGFuIHN0eWxlPSJmb250LXNpemU6MTEu +MHB0O2ZvbnQtZmFtaWx5OiZxdW90O0NhbGlicmkmcXVvdDssJnF1b3Q7c2Fucy1zZXJpZiZxdW90 +Oztjb2xvcjojMUY0OTdEIj5NaWNyb3NvZnQgT3V0bG9vayAyMDEwPG86cD48L286cD48L3NwYW4+ +PC9wPg0KPHAgY2xhc3M9Ik1zb05vcm1hbCI+PHNwYW4gc3R5bGU9ImZvbnQtc2l6ZToxMS4wcHQ7 +Zm9udC1mYW1pbHk6JnF1b3Q7Q2FsaWJyaSZxdW90OywmcXVvdDtzYW5zLXNlcmlmJnF1b3Q7O2Nv +bG9yOiMxRjQ5N0QiPjxvOnA+Jm5ic3A7PC9vOnA+PC9zcGFuPjwvcD4NCjxwIGNsYXNzPSJNc29O +b3JtYWwiPjxiPjxzcGFuIHN0eWxlPSJmb250LXNpemU6MTAuMHB0O2ZvbnQtZmFtaWx5OiZxdW90 +O1RhaG9tYSZxdW90OywmcXVvdDtzYW5zLXNlcmlmJnF1b3Q7Ij5Gcm9tOjwvc3Bhbj48L2I+PHNw +YW4gc3R5bGU9ImZvbnQtc2l6ZToxMC4wcHQ7Zm9udC1mYW1pbHk6JnF1b3Q7VGFob21hJnF1b3Q7 +LCZxdW90O3NhbnMtc2VyaWYmcXVvdDsiPiBtaWNoYWVsIFttYWlsdG86dGFsa0BvcGVubXJzLm9y +Z10NCjxicj4NCjxiPlNlbnQ6PC9iPiBNb25kYXksIE9jdG9iZXIgMTMsIDIwMTQgOTozOCBBTTxi +cj4NCjxiPlRvOjwvYj4gUG93ZXIsIENocmlzPGJyPg0KPGI+U3ViamVjdDo8L2I+IFtQTV0gWW91 +ciBwb3N0IGluICZxdW90O0J1cmdlcmhhdXM6IE5ldyByZXN0YXVyYW50IC8gbHVuY2ggdmVudWUm +cXVvdDs8bzpwPjwvbzpwPjwvc3Bhbj48L3A+DQo8cCBjbGFzcz0iTXNvTm9ybWFsIj48bzpwPiZu +YnNwOzwvbzpwPjwvcD4NCjxkaXY+DQo8dGFibGUgY2xhc3M9Ik1zb05vcm1hbFRhYmxlIiBib3Jk +ZXI9IjAiIGNlbGxzcGFjaW5nPSIwIiBjZWxscGFkZGluZz0iMCI+DQo8dGJvZHk+DQo8dHI+DQo8 +dGQgdmFsaWduPSJ0b3AiIHN0eWxlPSJwYWRkaW5nOjBpbiAwaW4gMGluIDBpbiI+PC90ZD4NCjx0 +ZCBzdHlsZT0icGFkZGluZzowaW4gMGluIDBpbiAwaW4iPg0KPHAgY2xhc3M9Ik1zb05vcm1hbCIg +c3R5bGU9Im1hcmdpbi1ib3R0b206MTguNzVwdCI+PGEgaHJlZj0iaHR0cDovL2NsLm9wZW5tcnMu +b3JnL3RyYWNrL2NsaWNrLzMwMDM5OTA1L3RhbGsub3Blbm1ycy5vcmc/cD1leUp6SWpvaWJHbFph +MVYwZVhoQ1kwMU1SVEZzVURKbVl6VlFNMFpsZWpFNElpd2lkaUk2TVN3aWNDSTZJbnRjSW5WY0lq +b3pNREF6T1Rrd05TeGNJblpjSWpveExGd2lkWEpzWENJNlhDSm9kSFJ3Y3pwY1hGd3ZYRnhjTDNS +aGJHc3ViM0JsYm0xeWN5NXZjbWRjWEZ3dmRYTmxjbk5jWEZ3dmJXbGphR0ZsYkZ3aUxGd2lhV1Jj +SWpwY0ltUTFZbU13TjJOa05EUmpaRFE0TUdNNFlUZzJNemxqWldJMU56Z3pZbVkyWENJc1hDSjFj +bXhmYVdSelhDSTZXMXdpWWpoa09EZzFNams1TnpkbVpqWTFaV1l5TlRFM09XUmlOR1l5TVdJM056 +RmpOemhqWmpoa09Gd2lYWDBpZlEiIHRhcmdldD0iX2JsYW5rIj48Yj48c3BhbiBzdHlsZT0iZm9u +dC1zaXplOjEwLjBwdDtmb250LWZhbWlseTomcXVvdDtUYWhvbWEmcXVvdDssJnF1b3Q7c2Fucy1z +ZXJpZiZxdW90Oztjb2xvcjojMDA2Njk5O3RleHQtZGVjb3JhdGlvbjpub25lIj5taWNoYWVsPC9z +cGFuPjwvYj48L2E+PGJyPg0KPHNwYW4gc3R5bGU9ImZvbnQtc2l6ZTo4LjVwdDtmb250LWZhbWls +eTomcXVvdDtUYWhvbWEmcXVvdDssJnF1b3Q7c2Fucy1zZXJpZiZxdW90Oztjb2xvcjojOTk5OTk5 +Ij5PY3RvYmVyIDEzPC9zcGFuPg0KPG86cD48L286cD48L3A+DQo8L3RkPg0KPC90cj4NCjx0cj4N +Cjx0ZCBjb2xzcGFuPSIyIiBzdHlsZT0icGFkZGluZzozLjc1cHQgMGluIDBpbiAwaW4iPg0KPHAg +Y2xhc3M9Ik1zb05vcm1hbCIgc3R5bGU9Im1hcmdpbi1ib3R0b206MTguNzVwdCI+PGEgaHJlZj0i +aHR0cDovL2NsLm9wZW5tcnMub3JnL3RyYWNrL2NsaWNrLzMwMDM5OTA1L3RhbGsub3Blbm1ycy5v +cmc/cD1leUp6SWpvaVVFUklTVU55UjNsVk1EZEJWVmhwV25SM1dXeDRNV05zVFc1Wklpd2lkaUk2 +TVN3aWNDSTZJbnRjSW5WY0lqb3pNREF6T1Rrd05TeGNJblpjSWpveExGd2lkWEpzWENJNlhDSm9k +SFJ3Y3pwY1hGd3ZYRnhjTDNSaGJHc3ViM0JsYm0xeWN5NXZjbWRjWEZ3dmRGeGNYQzlpZFhKblpY +Sm9ZWFZ6TFc1bGR5MXlaWE4wWVhWeVlXNTBMV3gxYm1Ob0xYWmxiblZsWEZ4Y0x6WTNNbHhjWEM4 +elhDSXNYQ0pwWkZ3aU9sd2laRFZpWXpBM1kyUTBOR05rTkRnd1l6aGhPRFl6T1dObFlqVTNPRE5p +WmpaY0lpeGNJblZ5YkY5cFpITmNJanBiWENKaU56WmlZamswWlRGaU56STVaVGsyWlRSbFpXTTRO +R1JtTWpRNE1ETXdZall5WVdZeU1HTTBYQ0pkZlNKOSI+PGI+PHNwYW4gc3R5bGU9ImNvbG9yOiMw +MDY2OTk7dGV4dC1kZWNvcmF0aW9uOm5vbmUiPmh0dHBzOi8vdGFsay5vcGVubXJzLm9yZy90L2J1 +cmdlcmhhdXMtbmV3LXJlc3RhdXJhbnQtbHVuY2gtdmVudWUvNjcyLzM8L3NwYW4+PC9iPjwvYT4N +CjxvOnA+PC9vOnA+PC9wPg0KPHAgc3R5bGU9Im1hcmdpbi10b3A6MGluIj5Mb29rcyBsaWtlIHlv +dXIgcmVwbHktYnktZW1haWwgd2Fzbid0IHByb2Nlc3NlZCBjb3JyZWN0bHkgYnkgb3VyIHNvZnR3 +YXJlLiBDYW4geW91IGxldCBtZSBrbm93IHdoYXQgdmVyc2lvbi9PUyBvZiB3aGF0IGVtYWlsIHBy +b2dyYW0geW91J3JlIHVzaW5nPyBXZSB3aWxsIHdhbnQgdG8gdHJ5IHRvIGZpeCB0aGUgYnVnLiA6 +c21pbGU6PG86cD48L286cD48L3A+DQo8cCBzdHlsZT0ibWFyZ2luLXRvcDowaW4iPlRoYW5rcyE8 +bzpwPjwvbzpwPjwvcD4NCjwvdGQ+DQo8L3RyPg0KPC90Ym9keT4NCjwvdGFibGU+DQo8ZGl2IGNs +YXNzPSJNc29Ob3JtYWwiIGFsaWduPSJjZW50ZXIiIHN0eWxlPSJ0ZXh0LWFsaWduOmNlbnRlciI+ +DQo8aHIgc2l6ZT0iMSIgd2lkdGg9IjEwMCUiIGFsaWduPSJjZW50ZXIiPg0KPC9kaXY+DQo8ZGl2 +Pg0KPHA+PHNwYW4gc3R5bGU9ImNvbG9yOiM2NjY2NjYiPlRvIHJlc3BvbmQsIHJlcGx5IHRvIHRo +aXMgZW1haWwgb3IgdmlzaXQgPGEgaHJlZj0iaHR0cDovL2NsLm9wZW5tcnMub3JnL3RyYWNrL2Ns +aWNrLzMwMDM5OTA1L3RhbGsub3Blbm1ycy5vcmc/cD1leUp6SWpvaWVYaDJWbnBGTUhSMU1uRm5a +RWR1TlhFd01GcFFPVlp0VFZvNElpd2lkaUk2TVN3aWNDSTZJbnRjSW5WY0lqb3pNREF6T1Rrd05T +eGNJblpjSWpveExGd2lkWEpzWENJNlhDSm9kSFJ3Y3pwY1hGd3ZYRnhjTDNSaGJHc3ViM0JsYm0x +eWN5NXZjbWRjWEZ3dmRGeGNYQzk1YjNWeUxYQnZjM1F0YVc0dFluVnlaMlZ5YUdGMWN5MXVaWGN0 +Y21WemRHRjFjbUZ1ZEMxc2RXNWphQzEyWlc1MVpWeGNYQzgyTnpSY1hGd3ZNVndpTEZ3aWFXUmNJ +anBjSW1RMVltTXdOMk5rTkRSalpEUTRNR000WVRnMk16bGpaV0kxTnpnelltWTJYQ0lzWENKMWNt +eGZhV1J6WENJNlcxd2lZamMyWW1JNU5HVXhZamN5T1dVNU5tVTBaV1ZqT0RSa1pqSTBPREF6TUdJ +Mk1tRm1NakJqTkZ3aVhYMGlmUSI+DQo8Yj48c3BhbiBzdHlsZT0iY29sb3I6IzAwNjY5OTt0ZXh0 +LWRlY29yYXRpb246bm9uZSI+aHR0cHM6Ly90YWxrLm9wZW5tcnMub3JnL3QveW91ci1wb3N0LWlu +LWJ1cmdlcmhhdXMtbmV3LXJlc3RhdXJhbnQtbHVuY2gtdmVudWUvNjc0LzE8L3NwYW4+PC9iPjwv +YT4gaW4geW91ciBicm93c2VyLjxvOnA+PC9vOnA+PC9zcGFuPjwvcD4NCjwvZGl2Pg0KPGRpdj4N +CjxwPjxzcGFuIHN0eWxlPSJjb2xvcjojNjY2NjY2Ij5UbyB1bnN1YnNjcmliZSBmcm9tIHRoZXNl +IGVtYWlscywgdmlzaXQgeW91ciA8YSBocmVmPSJodHRwOi8vY2wub3Blbm1ycy5vcmcvdHJhY2sv +Y2xpY2svMzAwMzk5MDUvdGFsay5vcGVubXJzLm9yZz9wPWV5SnpJam9pZFV4dVdsZzVWRmMwT1da +V1MwWTRiRmRMZG1seVdHc3hUVjl6SWl3aWRpSTZNU3dpY0NJNkludGNJblZjSWpvek1EQXpPVGt3 +TlN4Y0luWmNJam94TEZ3aWRYSnNYQ0k2WENKb2RIUndjenBjWEZ3dlhGeGNMM1JoYkdzdWIzQmxi +bTF5Y3k1dmNtZGNYRnd2YlhsY1hGd3ZjSEpsWm1WeVpXNWpaWE5jSWl4Y0ltbGtYQ0k2WENKa05X +SmpNRGRqWkRRMFkyUTBPREJqT0dFNE5qTTVZMlZpTlRjNE0ySm1ObHdpTEZ3aWRYSnNYMmxrYzF3 +aU9sdGNJbUk0TVdVd1pqQTFORFk1TkRNME56Z3lNMkZtTWpBMk5qRmpaamMzWkdOaU4yTmhZemRt +TWpKY0lsMTlJbjAiPg0KPGI+PHNwYW4gc3R5bGU9ImNvbG9yOiMwMDY2OTk7dGV4dC1kZWNvcmF0 +aW9uOm5vbmUiPnVzZXIgcHJlZmVyZW5jZXM8L3NwYW4+PC9iPjwvYT4uPG86cD48L286cD48L3Nw +YW4+PC9wPg0KPC9kaXY+DQo8L2Rpdj4NCjxwIGNsYXNzPSJNc29Ob3JtYWwiPjxpbWcgYm9yZGVy +PSIwIiB3aWR0aD0iMSIgaGVpZ2h0PSIxIiBpZD0iX3gwMDAwX2kxMDI2IiBzcmM9Imh0dHA6Ly9j +bC5vcGVubXJzLm9yZy90cmFjay9vcGVuLnBocD91PTMwMDM5OTA1JmFtcDtpZD1kNWJjMDdjZDQ0 +Y2Q0ODBjOGE4NjM5Y2ViNTc4M2JmNiI+PG86cD48L286cD48L3A+DQo8L2Rpdj4NCjwvYm9keT4N +CjwvaHRtbD4NCg== + +--_000_B0DFE1BEB3739743BC9B639D0E6BC8FF217A6341IUMSSGMBX104ads_-- diff --git a/spec/fixtures/emails/paragraphs.cooked b/spec/fixtures/emails/paragraphs.cooked new file mode 100644 index 00000000000..da83260e09c --- /dev/null +++ b/spec/fixtures/emails/paragraphs.cooked @@ -0,0 +1,7 @@ +

Is there any reason the old candy can't be be kept in silos while the new candy +is imported into new silos?

+ +

The thing about candy is it stays delicious for a long time -- we can just keep +it there without worrying about it too much, imo.

+ +

Thanks for listening.

\ No newline at end of file diff --git a/spec/fixtures/emails/paragraphs.eml b/spec/fixtures/emails/paragraphs.eml new file mode 100644 index 00000000000..2d5b5283f7e --- /dev/null +++ b/spec/fixtures/emails/paragraphs.eml @@ -0,0 +1,42 @@ +Return-Path: +Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 +Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 +Date: Thu, 13 Jun 2013 17:03:48 -0400 +From: Jake the Dog +To: reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo +Message-ID: +Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' +Mime-Version: 1.0 +Content-Type: text/plain; + charset=ISO-8859-1 +Content-Transfer-Encoding: 7bit +X-Sieve: CMU Sieve 2.2 +X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, + 13 Jun 2013 14:03:48 -0700 (PDT) +X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 + +Is there any reason the *old* candy can't be be kept in silos while the new candy +is imported into *new* silos? + +The thing about candy is it stays delicious for a long time -- we can just keep +it there without worrying about it too much, imo. + +Thanks for listening. + +On Sun, Jun 9, 2013 at 1:39 PM, eviltrout via Discourse Meta + wrote: +> +> +> +> eviltrout posted in 'Adventure Time Sux' on Discourse Meta: +> +> --- +> hey guys everyone knows adventure time sucks! +> +> --- +> Please visit this link to respond: http://localhost:3000/t/adventure-time-sux/1234/3 +> +> To unsubscribe from these emails, visit your [user preferences](http://localhost:3000/user_preferences). +> diff --git a/spec/fixtures/emails/previous.eml b/spec/fixtures/emails/previous.eml new file mode 100644 index 00000000000..24ac5a63deb --- /dev/null +++ b/spec/fixtures/emails/previous.eml @@ -0,0 +1,38 @@ + +Delivered-To: discourse-reply+cd480e301683c9902891f15968bf07a5@discourse.org +Received: by 10.194.216.104 with SMTP id op8csp80593wjc; + Wed, 24 Jul 2013 07:59:14 -0700 (PDT) +Return-Path: +References: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> +From: Walter White +In-Reply-To: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> +Mime-Version: 1.0 (1.0) +Date: Wed, 24 Jul 2013 15:59:10 +0100 +Message-ID: <4597127794206131679@unknownmsgid> +Subject: Re: [Discourse] new reply to your post in 'Crystal Blue' +To: walter via Discourse +Content-Type: multipart/alternative; boundary=001a11c20edc15a39304e2432790 + +This will not include the previous discussion that is present in this email. + ------------------------------ +Previous discussion +skylerwhite July 24 + +This is a reply. + fring July 24 + +This is an older reply. + hank_schrader July 24 + +Of course another reply here. + walterwhite July 24 + + ------------------------------ + +To respond, reply to this email or visit +http://discourse.org/t/crystal-blue/5043/10in +your browser. + +To unsubscribe from these emails, visit your user +preferences +. diff --git a/spec/fixtures/emails/previous_replies.eml b/spec/fixtures/emails/previous_replies.eml new file mode 100644 index 00000000000..3fd74482c07 --- /dev/null +++ b/spec/fixtures/emails/previous_replies.eml @@ -0,0 +1,180 @@ +Delivered-To: reply@discourse.org +MIME-Version: 1.0 +In-Reply-To: +References: + +Date: Fri, 28 Nov 2014 12:55:32 -0800 +Subject: Re: [Discourse Meta] [Lounge] Testing default email replies +From: Walter White +To: Discourse Meta +Content-Type: multipart/alternative; boundary=001a1137c9285318bb0508f17bc5 + +--001a1137c9285318bb0508f17bc5 +Content-Type: text/plain; charset=UTF-8 + +### this is a reply from iOS Gmail app + +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over +the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown +fox jumps over the lazy dog. The quick brown fox jumps over the lazy +dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps +over the lazy dog. + +This is **bold** text in Markdown. + +This is a link to http://example.com + +On Friday, November 28, 2014, Arpit Jalan wrote: + +> techAPJ +> November 28 +> +> Test reply. +> +> First paragraph. +> +> Second paragraph. +> +> To respond, reply to this email or visit +> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in +> your browser. +> ------------------------------ +> Previous Replies codinghorror +> +> November 28 +> +> We're testing the latest GitHub email processing library which we are +> integrating now. +> +> https://github.com/github/email_reply_parser +> +> Go ahead and reply to this topic and I'll reply from various email clients +> for testing. +> ------------------------------ +> +> To respond, reply to this email or visit +> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in +> your browser. +> +> To unsubscribe from these emails, visit your user preferences +> . +> + +--001a1137c9285318bb0508f17bc5 +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +### this is a reply from iOS Gmail app

The quick brown f= +ox jumps over the lazy dog.=C2=A0The quick brown fox jumps over the lazy dog.=C2=A0The quic= +k brown fox jumps over the lazy dog.=C2=A0The quick brown fox jumps over th= +e lazy dog.=C2=A0The quick brown fox jumps over the lazy dog.=C2=A0The quic= +k brown fox jumps over the lazy dog.=C2=A0The quick brown fox jumps over th= +e lazy dog.=C2=A0

This is **bold** text in Markdown.

This is a link to http://example.com

On Friday, November 28, 2014, Arpit Jalan <
info@discourse.org> wrote:
+ + + + + + + + + + + +
+ + + techAPJ
+ November 28 +
+

Test reply.

+ +

First paragraph.

+ +

Second paragraph.

+
+ + +
+

To respond, reply to this email or visit https:/= +/meta.discourse.org/t/testing-default-email-replies/22638/3 in your bro= +wser.

+
+
+

Previous Replies

+ + + + + + + + + + + +
+ + + codinghorror + November 28 +
+

We're testing the latest GitHub emai= +l processing library which we are integrating now.

+ +

https://github.com/github/email_reply_parser

+ +

Go ahead and reply to this topic and I&#= +39;ll reply from various email clients for testing.

+
+ + +
+ +
+

To respond, reply to this email or visit https://met= +a.discourse.org/t/testing-default-email-replies/22638/3 in your browser= +.

+
+
+

To unsubscribe from these emails, visit your user preferences.

+
+
+
+ +--001a1137c9285318bb0508f17bc5-- diff --git a/spec/fixtures/emails/too_many_mentions.eml b/spec/fixtures/emails/too_many_mentions.eml new file mode 100644 index 00000000000..9cc7b75c94f --- /dev/null +++ b/spec/fixtures/emails/too_many_mentions.eml @@ -0,0 +1,31 @@ +Return-Path: +Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 +Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 +Date: Thu, 13 Jun 2013 17:03:48 -0400 +From: Jake the Dog +To: reply+636ca428858779856c226bb145ef4fad@appmail.adventuretime.ooo +Message-ID: +Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' +Mime-Version: 1.0 +Content-Type: text/plain; + charset=ISO-8859-1 +Content-Transfer-Encoding: 7bit +X-Sieve: CMU Sieve 2.2 +X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, + 13 Jun 2013 14:03:48 -0700 (PDT) +X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 + + +@user1 +@user2 +@user3 +@user4 +@user5 +@user6 +@user7 +@user8 +@user9 +@user10 +@user11 \ No newline at end of file diff --git a/spec/fixtures/emails/too_short.eml b/spec/fixtures/emails/too_short.eml new file mode 100644 index 00000000000..54fed0f98c5 --- /dev/null +++ b/spec/fixtures/emails/too_short.eml @@ -0,0 +1,21 @@ +Return-Path: +Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 +Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 +Date: Thu, 13 Jun 2013 17:03:48 -0400 +From: Jake the Dog +To: TO +Message-ID: +Subject: SUBJECT +Mime-Version: 1.0 +Content-Type: text/plain; + charset=ISO-8859-1 +Content-Transfer-Encoding: 7bit +X-Sieve: CMU Sieve 2.2 +X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, + 13 Jun 2013 14:03:48 -0700 (PDT) +X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 + + ++1 \ No newline at end of file diff --git a/spec/fixtures/emails/valid_incoming.cooked b/spec/fixtures/emails/valid_incoming.cooked new file mode 100644 index 00000000000..2bf35825373 --- /dev/null +++ b/spec/fixtures/emails/valid_incoming.cooked @@ -0,0 +1,5 @@ +

Hey folks,

+ +

I was thinking. Wouldn't it be great if we could post topics via email? Yes it would!

+ +

Jakie

diff --git a/spec/fixtures/emails/valid_incoming.eml b/spec/fixtures/emails/valid_incoming.eml new file mode 100644 index 00000000000..5e8db53319e --- /dev/null +++ b/spec/fixtures/emails/valid_incoming.eml @@ -0,0 +1,25 @@ +Return-Path: +Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 +Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 +Date: Thu, 13 Jun 2013 17:03:48 -0400 +From: Jake the Dog +To: +Message-ID: +Subject: We should have a post-by-email-feature. +Mime-Version: 1.0 +Content-Type: text/plain; + charset=ISO-8859-1 +Content-Transfer-Encoding: 7bit +X-Sieve: CMU Sieve 2.2 +X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, + 13 Jun 2013 14:03:48 -0700 (PDT) +X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 + +Hey folks, + +I was thinking. Wouldn't it be great if we could post topics via email? Yes it would! + +Jakie + diff --git a/spec/fixtures/emails/valid_reply.cooked b/spec/fixtures/emails/valid_reply.cooked new file mode 100644 index 00000000000..4bce79ad12a --- /dev/null +++ b/spec/fixtures/emails/valid_reply.cooked @@ -0,0 +1,4 @@ +

I could not disagree more. I am obviously biased but adventure time is the +greatest show ever created. Everyone should watch it.

+ +
  • Jake out
diff --git a/spec/fixtures/emails/valid_reply.eml b/spec/fixtures/emails/valid_reply.eml new file mode 100644 index 00000000000..1e696389954 --- /dev/null +++ b/spec/fixtures/emails/valid_reply.eml @@ -0,0 +1,40 @@ +Return-Path: +Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 +Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 +Date: Thu, 13 Jun 2013 17:03:48 -0400 +From: Jake the Dog +To: reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo +Message-ID: +Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' +Mime-Version: 1.0 +Content-Type: text/plain; + charset=ISO-8859-1 +Content-Transfer-Encoding: 7bit +X-Sieve: CMU Sieve 2.2 +X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, + 13 Jun 2013 14:03:48 -0700 (PDT) +X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 + +I could not disagree more. I am obviously biased but adventure time is the +greatest show ever created. Everyone should watch it. + +- Jake out + + +On Sun, Jun 9, 2013 at 1:39 PM, eviltrout via Discourse Meta + wrote: +> +> +> +> eviltrout posted in 'Adventure Time Sux' on Discourse Meta: +> +> --- +> hey guys everyone knows adventure time sucks! +> +> --- +> Please visit this link to respond: http://localhost:3000/t/adventure-time-sux/1234/3 +> +> To unsubscribe from these emails, visit your [user preferences](http://localhost:3000/user_preferences). +> \ No newline at end of file diff --git a/spec/fixtures/emails/via_line.eml b/spec/fixtures/emails/via_line.eml new file mode 100644 index 00000000000..0b2947ff954 --- /dev/null +++ b/spec/fixtures/emails/via_line.eml @@ -0,0 +1,25 @@ + +Delivered-To: discourse-reply+cd480e301683c9902891f15968bf07a5@discourse.org +Received: by 10.194.216.104 with SMTP id op8csp80593wjc; + Wed, 24 Jul 2013 07:59:14 -0700 (PDT) +Return-Path: +References: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> +From: Walter White +In-Reply-To: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> +Mime-Version: 1.0 (1.0) +Date: Wed, 24 Jul 2013 15:59:10 +0100 +Message-ID: <4597127794206131679@unknownmsgid> +Subject: Re: [Discourse] new reply to your post in 'Crystal Blue' +To: walter via Discourse +Content-Type: multipart/alternative; boundary=001a11c20edc15a39304e2432790 + +Hello this email has content! + +codinghorror via Discourse wrote: +> [codinghorror] codinghorror +> +> August 7 +> +> It wouldn't be great at the moment for 100% email, since there's no +> way to be notified of new topics via email. (you can get notified of +> new replies to your posts and topics via email, and reply to them.) diff --git a/spec/fixtures/emails/windows_8_metro.eml b/spec/fixtures/emails/windows_8_metro.eml new file mode 100644 index 00000000000..67d204af562 --- /dev/null +++ b/spec/fixtures/emails/windows_8_metro.eml @@ -0,0 +1,173 @@ +Delivered-To: reply@discourse.org +Return-Path: +MIME-Version: 1.0 +From: +To: + =?utf-8?Q?Discourse_Meta?= + +Subject: + =?utf-8?Q?Re:_[Discourse_Meta]_[Lounge]_Testing_default_email_replies?= +Importance: Normal +Date: Fri, 28 Nov 2014 21:29:10 +0000 +In-Reply-To: +References: + , +Content-Type: multipart/alternative; + boundary="_866E2678-BB4F-4DD8-BE18-81B04AD8D1BC_" + +--_866E2678-BB4F-4DD8-BE18-81B04AD8D1BC_ +Content-Transfer-Encoding: base64 +Content-Type: text/plain; charset="utf-8" + +IyMjIHJlcGx5IGZyb20gZGVmYXVsdCBtYWlsIGNsaWVudCBpbiBXaW5kb3dzIDguMSBNZXRybw0K +DQoNClRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWlj +ayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gg +anVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0 +aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cu +IFRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBi +cm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVt +cHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUg +bGF6eSBkb2cuDQoNCg0KVGhpcyBpcyBhICoqYm9sZCoqIHdvcmQgaW4gTWFya2Rvd24NCg0KDQpU +aGlzIGlzIGEgbGluayBodHRwOi8vZXhhbXBsZS5jb20NCiANCg0KDQoNCg0KDQpGcm9tOiBBcnBp +dCBKYWxhbg0KU2VudDog4oCORnJpZGF54oCOLCDigI5Ob3ZlbWJlcuKAjiDigI4yOOKAjiwg4oCO +MjAxNCDigI4xMuKAjjrigI4zNeKAjiDigI5QTQ0KVG86IGplZmYgYXR3b29kDQoNCg0KDQoNCg0K +DQogdGVjaEFQSg0KTm92ZW1iZXIgMjggDQoNClRlc3QgcmVwbHkuDQoNCkZpcnN0IHBhcmFncmFw +aC4NCg0KU2Vjb25kIHBhcmFncmFwaC4NCg0KDQoNClRvIHJlc3BvbmQsIHJlcGx5IHRvIHRoaXMg +ZW1haWwgb3IgdmlzaXQgaHR0cHM6Ly9tZXRhLmRpc2NvdXJzZS5vcmcvdC90ZXN0aW5nLWRlZmF1 +bHQtZW1haWwtcmVwbGllcy8yMjYzOC8zIGluIHlvdXIgYnJvd3Nlci4NCg0KDQoNClByZXZpb3Vz +IFJlcGxpZXMNCg0KIGNvZGluZ2hvcnJvcg0KTm92ZW1iZXIgMjggDQoNCldlJ3JlIHRlc3Rpbmcg +dGhlIGxhdGVzdCBHaXRIdWIgZW1haWwgcHJvY2Vzc2luZyBsaWJyYXJ5IHdoaWNoIHdlIGFyZSBp +bnRlZ3JhdGluZyBub3cuDQoNCmh0dHBzOi8vZ2l0aHViLmNvbS9naXRodWIvZW1haWxfcmVwbHlf +cGFyc2VyDQoNCkdvIGFoZWFkIGFuZCByZXBseSB0byB0aGlzIHRvcGljIGFuZCBJJ2xsIHJlcGx5 +IGZyb20gdmFyaW91cyBlbWFpbCBjbGllbnRzIGZvciB0ZXN0aW5nLg0KDQoNCg0KDQoNClRvIHJl +c3BvbmQsIHJlcGx5IHRvIHRoaXMgZW1haWwgb3IgdmlzaXQgaHR0cHM6Ly9tZXRhLmRpc2NvdXJz +ZS5vcmcvdC90ZXN0aW5nLWRlZmF1bHQtZW1haWwtcmVwbGllcy8yMjYzOC8zIGluIHlvdXIgYnJv +d3Nlci4NCg0KDQpUbyB1bnN1YnNjcmliZSBmcm9tIHRoZXNlIGVtYWlscywgdmlzaXQgeW91ciB1 +c2VyIHByZWZlcmVuY2VzLg== + +--_866E2678-BB4F-4DD8-BE18-81B04AD8D1BC_ +Content-Transfer-Encoding: base64 +Content-Type: text/html; charset="utf-8" + +CjxodG1sPgo8aGVhZD4KPG1ldGEgbmFtZT0iZ2VuZXJhdG9yIiBjb250ZW50PSJXaW5kb3dzIE1h +aWwgMTcuNS45NjAwLjIwNjA1Ij4KPHN0eWxlIGRhdGEtZXh0ZXJuYWxzdHlsZT0idHJ1ZSI+PCEt +LQpwLk1zb0xpc3RQYXJhZ3JhcGgsIGxpLk1zb0xpc3RQYXJhZ3JhcGgsIGRpdi5Nc29MaXN0UGFy +YWdyYXBoIHsKbWFyZ2luLXRvcDowaW47Cm1hcmdpbi1yaWdodDowaW47Cm1hcmdpbi1ib3R0b206 +MGluOwptYXJnaW4tbGVmdDouNWluOwptYXJnaW4tYm90dG9tOi4wMDAxcHQ7Cn0KcC5Nc29Ob3Jt +YWwsIGxpLk1zb05vcm1hbCwgZGl2Lk1zb05vcm1hbCB7Cm1hcmdpbjowaW47Cm1hcmdpbi1ib3R0 +b206LjAwMDFwdDsKfQpwLk1zb0xpc3RQYXJhZ3JhcGhDeFNwRmlyc3QsIGxpLk1zb0xpc3RQYXJh +Z3JhcGhDeFNwRmlyc3QsIGRpdi5Nc29MaXN0UGFyYWdyYXBoQ3hTcEZpcnN0LCAKcC5Nc29MaXN0 +UGFyYWdyYXBoQ3hTcE1pZGRsZSwgbGkuTXNvTGlzdFBhcmFncmFwaEN4U3BNaWRkbGUsIGRpdi5N +c29MaXN0UGFyYWdyYXBoQ3hTcE1pZGRsZSwgCnAuTXNvTGlzdFBhcmFncmFwaEN4U3BMYXN0LCBs +aS5Nc29MaXN0UGFyYWdyYXBoQ3hTcExhc3QsIGRpdi5Nc29MaXN0UGFyYWdyYXBoQ3hTcExhc3Qg +ewptYXJnaW4tdG9wOjBpbjsKbWFyZ2luLXJpZ2h0OjBpbjsKbWFyZ2luLWJvdHRvbTowaW47Cm1h +cmdpbi1sZWZ0Oi41aW47Cm1hcmdpbi1ib3R0b206LjAwMDFwdDsKbGluZS1oZWlnaHQ6MTE1JTsK +fQotLT48L3N0eWxlPjwvaGVhZD4KPGJvZHkgZGlyPSJsdHIiPgo8ZGl2IGRhdGEtZXh0ZXJuYWxz +dHlsZT0iZmFsc2UiIGRpcj0ibHRyIiBzdHlsZT0iZm9udC1mYW1pbHk6ICdDYWxpYnJpJywgJ1Nl +Z29lIFVJJywgJ01laXJ5bycsICdNaWNyb3NvZnQgWWFIZWkgVUknLCAnTWljcm9zb2Z0IEpoZW5n +SGVpIFVJJywgJ01hbGd1biBHb3RoaWMnLCAnc2Fucy1zZXJpZic7Zm9udC1zaXplOjEycHQ7Ij48 +ZGl2IHN0eWxlPSJmb250LXNpemU6IDE0cHQ7Ij4jIyMgcmVwbHkgZnJvbSBkZWZhdWx0IG1haWwg +Y2xpZW50IGluIFdpbmRvd3MgOC4xIE1ldHJvPC9kaXY+PGRpdiBzdHlsZT0iZm9udC1zaXplOiAx +NHB0OyI+PGJyPjwvZGl2PjxkaXYgc3R5bGU9ImZvbnQtc2l6ZTogMTRwdDsiPlRoZSBxdWljayBi +cm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVt +cHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUg +bGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRo +ZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93 +biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVtcHMg +b3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6 +eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuPC9kaXY+ +PGRpdiBzdHlsZT0iZm9udC1zaXplOiAxNHB0OyI+PGJyPjwvZGl2PjxkaXYgc3R5bGU9ImZvbnQt +c2l6ZTogMTRwdDsiPlRoaXMgaXMgYSAqKmJvbGQqKiB3b3JkIGluIE1hcmtkb3duPC9kaXY+PGRp +diBzdHlsZT0iZm9udC1zaXplOiAxNHB0OyI+PGJyPjwvZGl2PjxkaXYgc3R5bGU9ImZvbnQtc2l6 +ZTogMTRwdDsiPlRoaXMgaXMgYSBsaW5rIDxhIGhyZWY9Imh0dHA6Ly9leGFtcGxlLmNvbSI+aHR0 +cDovL2V4YW1wbGUuY29tPC9hPjxicj4mbmJzcDs8L2Rpdj48ZGl2IHN0eWxlPSJmb250LXNpemU6 +IDE0cHQ7Ij48YnI+PC9kaXY+PGRpdiBzdHlsZT0icGFkZGluZy10b3A6IDVweDsgYm9yZGVyLXRv +cC1jb2xvcjogcmdiKDIyOSwgMjI5LCAyMjkpOyBib3JkZXItdG9wLXdpZHRoOiAxcHg7IGJvcmRl +ci10b3Atc3R5bGU6IHNvbGlkOyI+PGRpdj48Zm9udCBmYWNlPSIgJ0NhbGlicmknLCAnU2Vnb2Ug +VUknLCAnTWVpcnlvJywgJ01pY3Jvc29mdCBZYUhlaSBVSScsICdNaWNyb3NvZnQgSmhlbmdIZWkg +VUknLCAnTWFsZ3VuIEdvdGhpYycsICdzYW5zLXNlcmlmJyIgc3R5bGU9J2xpbmUtaGVpZ2h0OiAx +NXB0OyBsZXR0ZXItc3BhY2luZzogMC4wMmVtOyBmb250LWZhbWlseTogIkNhbGlicmkiLCAiU2Vn +b2UgVUkiLCAiTWVpcnlvIiwgIk1pY3Jvc29mdCBZYUhlaSBVSSIsICJNaWNyb3NvZnQgSmhlbmdI +ZWkgVUkiLCAiTWFsZ3VuIEdvdGhpYyIsICJzYW5zLXNlcmlmIjsgZm9udC1zaXplOiAxMnB0Oyc+ +PGI+RnJvbTo8L2I+Jm5ic3A7PGEgaHJlZj0ibWFpbHRvOmluZm9AZGlzY291cnNlLm9yZyIgdGFy +Z2V0PSJfcGFyZW50Ij5BcnBpdCBKYWxhbjwvYT48YnI+PGI+U2VudDo8L2I+Jm5ic3A74oCORnJp +ZGF54oCOLCDigI5Ob3ZlbWJlcuKAjiDigI4yOOKAjiwg4oCOMjAxNCDigI4xMuKAjjrigI4zNeKA +jiDigI5QTTxicj48Yj5Ubzo8L2I+Jm5ic3A7PGEgaHJlZj0ibWFpbHRvOmphdHdvb2RAY29kaW5n +aG9ycm9yLmNvbSIgdGFyZ2V0PSJfcGFyZW50Ij5qZWZmIGF0d29vZDwvYT48L2ZvbnQ+PC9kaXY+ +PC9kaXY+PGRpdj48YnI+PC9kaXY+PGRpdiBkaXI9IiI+PGRpdj4KCjx0YWJsZSB0YWJpbmRleD0i +LTEiIHN0eWxlPSJtYXJnaW4tYm90dG9tOiAyNXB4OyIgYm9yZGVyPSIwIiBjZWxsc3BhY2luZz0i +MCIgY2VsbHBhZGRpbmc9IjAiPgogIDx0Ym9keT4KICAgIDx0cj4KICAgICAgPHRkIHN0eWxlPSJ3 +aWR0aDogNTVweDsgdmVydGljYWwtYWxpZ246IHRvcDsiPgogICAgICAgIDxpbWcgd2lkdGg9IjQ1 +IiBoZWlnaHQ9IjQ1IiB0YWJpbmRleD0iLTEiIHN0eWxlPSJtYXgtd2lkdGg6IDEwMCU7IiBzcmM9 +Imh0dHBzOi8vbWV0YS1kaXNjb3Vyc2UuZ2xvYmFsLnNzbC5mYXN0bHkubmV0L3VzZXJfYXZhdGFy +L21ldGEuZGlzY291cnNlLm9yZy90ZWNoYXBqLzQ1LzMyODEucG5nIiBkYXRhLW1zLWltZ3NyYz0i +aHR0cHM6Ly9tZXRhLWRpc2NvdXJzZS5nbG9iYWwuc3NsLmZhc3RseS5uZXQvdXNlcl9hdmF0YXIv +bWV0YS5kaXNjb3Vyc2Uub3JnL3RlY2hhcGovNDUvMzI4MS5wbmciPgogICAgICA8L3RkPgogICAg +ICA8dGQ+CiAgICAgICAgPGEgc3R5bGU9J2NvbG9yOiByZ2IoNTksIDg5LCAxNTIpOyBmb250LWZh +bWlseTogImx1Y2lkYSBncmFuZGUiLHRhaG9tYSx2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7IGZv +bnQtc2l6ZTogMTNweDsgZm9udC13ZWlnaHQ6IGJvbGQ7IHRleHQtZGVjb3JhdGlvbjogbm9uZTsn +IGhyZWY9Imh0dHBzOi8vbWV0YS5kaXNjb3Vyc2Uub3JnL3VzZXJzL3RlY2hhcGoiIHRhcmdldD0i +X3BhcmVudCI+dGVjaEFQSjwvYT48YnI+CiAgICAgICAgPHNwYW4gc3R5bGU9J3RleHQtYWxpZ246 +IHJpZ2h0OyBjb2xvcjogcmdiKDE1MywgMTUzLCAxNTMpOyBwYWRkaW5nLXJpZ2h0OiA1cHg7IGZv +bnQtZmFtaWx5OiAibHVjaWRhIGdyYW5kZSIsdGFob21hLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJp +ZjsgZm9udC1zaXplOiAxMXB4Oyc+Tm92ZW1iZXIgMjg8L3NwYW4+CiAgICAgIDwvdGQ+CiAgICA8 +L3RyPgogICAgPHRyPgogICAgICA8dGQgc3R5bGU9InBhZGRpbmctdG9wOiA1cHg7IiBjb2xzcGFu +PSIyIj4KPHAgc3R5bGU9ImJvcmRlcjogMHB4IGJsYWNrOyBib3JkZXItaW1hZ2U6IG5vbmU7IG1h +cmdpbi10b3A6IDBweDsiPlRlc3QgcmVwbHkuPC9wPgoKPHAgc3R5bGU9ImJvcmRlcjogMHB4IGJs +YWNrOyBib3JkZXItaW1hZ2U6IG5vbmU7IG1hcmdpbi10b3A6IDBweDsiPkZpcnN0IHBhcmFncmFw +aC48L3A+Cgo8cCBzdHlsZT0iYm9yZGVyOiAwcHggYmxhY2s7IGJvcmRlci1pbWFnZTogbm9uZTsg +bWFyZ2luLXRvcDogMHB4OyI+U2Vjb25kIHBhcmFncmFwaC48L3A+CjwvdGQ+CiAgICA8L3RyPgog +IDwvdGJvZHk+CjwvdGFibGU+CgoKICA8ZGl2IHN0eWxlPSJjb2xvcjogcmdiKDEwMiwgMTAyLCAx +MDIpOyI+CiAgICA8cD5UbyByZXNwb25kLCByZXBseSB0byB0aGlzIGVtYWlsIG9yIHZpc2l0IDxh +IHN0eWxlPSJjb2xvcjogcmdiKDEwMiwgMTAyLCAxMDIpOyBmb250LXdlaWdodDogYm9sZDsgdGV4 +dC1kZWNvcmF0aW9uOiBub25lOyIgaHJlZj0iaHR0cHM6Ly9tZXRhLmRpc2NvdXJzZS5vcmcvdC90 +ZXN0aW5nLWRlZmF1bHQtZW1haWwtcmVwbGllcy8yMjYzOC8zIiB0YXJnZXQ9Il9wYXJlbnQiPmh0 +dHBzOi8vbWV0YS5kaXNjb3Vyc2Uub3JnL3QvdGVzdGluZy1kZWZhdWx0LWVtYWlsLXJlcGxpZXMv +MjI2MzgvMzwvYT4gaW4geW91ciBicm93c2VyLjwvcD4KICA8L2Rpdj4KICA8aHIgc3R5bGU9ImJv +cmRlcjogMXB4IGJsYWNrOyBib3JkZXItaW1hZ2U6IG5vbmU7IGhlaWdodDogMXB4OyBiYWNrZ3Jv +dW5kLWNvbG9yOiByZ2IoMjIxLCAyMjEsIDIyMSk7Ij4KICA8aDQ+UHJldmlvdXMgUmVwbGllczwv +aDQ+CgogIDx0YWJsZSB0YWJpbmRleD0iLTEiIHN0eWxlPSJtYXJnaW4tYm90dG9tOiAyNXB4OyIg +Ym9yZGVyPSIwIiBjZWxsc3BhY2luZz0iMCIgY2VsbHBhZGRpbmc9IjAiPgogIDx0Ym9keT4KICAg +IDx0cj4KICAgICAgPHRkIHN0eWxlPSJ3aWR0aDogNTVweDsgdmVydGljYWwtYWxpZ246IHRvcDsi +PgogICAgICAgIDxpbWcgd2lkdGg9IjQ1IiBoZWlnaHQ9IjQ1IiB0YWJpbmRleD0iLTEiIHN0eWxl +PSJtYXgtd2lkdGg6IDEwMCU7IiBzcmM9Imh0dHBzOi8vbWV0YS1kaXNjb3Vyc2UuZ2xvYmFsLnNz +bC5mYXN0bHkubmV0L3VzZXJfYXZhdGFyL21ldGEuZGlzY291cnNlLm9yZy9jb2Rpbmdob3Jyb3Iv +NDUvNTI5Ny5wbmciIGRhdGEtbXMtaW1nc3JjPSJodHRwczovL21ldGEtZGlzY291cnNlLmdsb2Jh +bC5zc2wuZmFzdGx5Lm5ldC91c2VyX2F2YXRhci9tZXRhLmRpc2NvdXJzZS5vcmcvY29kaW5naG9y +cm9yLzQ1LzUyOTcucG5nIj4KICAgICAgPC90ZD4KICAgICAgPHRkPgogICAgICAgIDxhIHN0eWxl +PSdjb2xvcjogcmdiKDU5LCA4OSwgMTUyKTsgZm9udC1mYW1pbHk6ICJsdWNpZGEgZ3JhbmRlIix0 +YWhvbWEsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmOyBmb250LXNpemU6IDEzcHg7IGZvbnQtd2Vp +Z2h0OiBib2xkOyB0ZXh0LWRlY29yYXRpb246IG5vbmU7JyBocmVmPSJodHRwczovL21ldGEuZGlz +Y291cnNlLm9yZy91c2Vycy9jb2Rpbmdob3Jyb3IiIHRhcmdldD0iX3BhcmVudCI+Y29kaW5naG9y +cm9yPC9hPjxicj4KICAgICAgICA8c3BhbiBzdHlsZT0ndGV4dC1hbGlnbjogcmlnaHQ7IGNvbG9y +OiByZ2IoMTUzLCAxNTMsIDE1Myk7IHBhZGRpbmctcmlnaHQ6IDVweDsgZm9udC1mYW1pbHk6ICJs +dWNpZGEgZ3JhbmRlIix0YWhvbWEsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmOyBmb250LXNpemU6 +IDExcHg7Jz5Ob3ZlbWJlciAyODwvc3Bhbj4KICAgICAgPC90ZD4KICAgIDwvdHI+CiAgICA8dHI+ +CiAgICAgIDx0ZCBzdHlsZT0icGFkZGluZy10b3A6IDVweDsiIGNvbHNwYW49IjIiPgo8cCBzdHls +ZT0iYm9yZGVyOiAwcHggYmxhY2s7IGJvcmRlci1pbWFnZTogbm9uZTsgbWFyZ2luLXRvcDogMHB4 +OyI+V2UncmUgdGVzdGluZyB0aGUgbGF0ZXN0IEdpdEh1YiBlbWFpbCBwcm9jZXNzaW5nIGxpYnJh +cnkgd2hpY2ggd2UgYXJlIGludGVncmF0aW5nIG5vdy48L3A+Cgo8cCBzdHlsZT0iYm9yZGVyOiAw +cHggYmxhY2s7IGJvcmRlci1pbWFnZTogbm9uZTsgbWFyZ2luLXRvcDogMHB4OyI+PGEgc3R5bGU9 +ImNvbG9yOiByZ2IoMCwgMTAyLCAxNTMpOyBmb250LXdlaWdodDogYm9sZDsgdGV4dC1kZWNvcmF0 +aW9uOiBub25lOyIgaHJlZj0iaHR0cHM6Ly9naXRodWIuY29tL2dpdGh1Yi9lbWFpbF9yZXBseV9w +YXJzZXIiIHRhcmdldD0iX3BhcmVudCI+aHR0cHM6Ly9naXRodWIuY29tL2dpdGh1Yi9lbWFpbF9y +ZXBseV9wYXJzZXI8L2E+PC9wPgoKPHAgc3R5bGU9ImJvcmRlcjogMHB4IGJsYWNrOyBib3JkZXIt +aW1hZ2U6IG5vbmU7IG1hcmdpbi10b3A6IDBweDsiPkdvIGFoZWFkIGFuZCByZXBseSB0byB0aGlz +IHRvcGljIGFuZCBJJ2xsIHJlcGx5IGZyb20gdmFyaW91cyBlbWFpbCBjbGllbnRzIGZvciB0ZXN0 +aW5nLjwvcD4KPC90ZD4KICAgIDwvdHI+CiAgPC90Ym9keT4KPC90YWJsZT4KCgo8aHIgc3R5bGU9 +ImJvcmRlcjogMXB4IGJsYWNrOyBib3JkZXItaW1hZ2U6IG5vbmU7IGhlaWdodDogMXB4OyBiYWNr +Z3JvdW5kLWNvbG9yOiByZ2IoMjIxLCAyMjEsIDIyMSk7Ij4KCjxkaXYgc3R5bGU9ImNvbG9yOiBy +Z2IoMTAyLCAxMDIsIDEwMik7Ij4KPHA+VG8gcmVzcG9uZCwgcmVwbHkgdG8gdGhpcyBlbWFpbCBv +ciB2aXNpdCA8YSBzdHlsZT0iY29sb3I6IHJnYigxMDIsIDEwMiwgMTAyKTsgZm9udC13ZWlnaHQ6 +IGJvbGQ7IHRleHQtZGVjb3JhdGlvbjogbm9uZTsiIGhyZWY9Imh0dHBzOi8vbWV0YS5kaXNjb3Vy +c2Uub3JnL3QvdGVzdGluZy1kZWZhdWx0LWVtYWlsLXJlcGxpZXMvMjI2MzgvMyIgdGFyZ2V0PSJf +cGFyZW50Ij5odHRwczovL21ldGEuZGlzY291cnNlLm9yZy90L3Rlc3RpbmctZGVmYXVsdC1lbWFp +bC1yZXBsaWVzLzIyNjM4LzM8L2E+IGluIHlvdXIgYnJvd3Nlci48L3A+CjwvZGl2Pgo8ZGl2IHN0 +eWxlPSJjb2xvcjogcmdiKDEwMiwgMTAyLCAxMDIpOyI+CjxwPlRvIHVuc3Vic2NyaWJlIGZyb20g +dGhlc2UgZW1haWxzLCB2aXNpdCB5b3VyIDxhIHN0eWxlPSJjb2xvcjogcmdiKDEwMiwgMTAyLCAx +MDIpOyBmb250LXdlaWdodDogYm9sZDsgdGV4dC1kZWNvcmF0aW9uOiBub25lOyIgaHJlZj0iaHR0 +cHM6Ly9tZXRhLmRpc2NvdXJzZS5vcmcvbXkvcHJlZmVyZW5jZXMiIHRhcmdldD0iX3BhcmVudCI+ +dXNlciBwcmVmZXJlbmNlczwvYT4uPC9wPgo8L2Rpdj4KPC9kaXY+CjwvZGl2PjxkaXYgc3R5bGU9 +ImZvbnQtc2l6ZTogMTRwdDsiPjxicj48L2Rpdj48L2Rpdj4KPC9ib2R5Pgo8L2h0bWw+Cg== + +--_866E2678-BB4F-4DD8-BE18-81B04AD8D1BC_-- diff --git a/spec/fixtures/emails/wrong_reply_key.eml b/spec/fixtures/emails/wrong_reply_key.eml new file mode 100644 index 00000000000..491e078fb5b --- /dev/null +++ b/spec/fixtures/emails/wrong_reply_key.eml @@ -0,0 +1,40 @@ +Return-Path: +Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 +Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 +Date: Thu, 13 Jun 2013 17:03:48 -0400 +From: Jake the Dog +To: reply+QQd8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo +Message-ID: +Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' +Mime-Version: 1.0 +Content-Type: text/plain; + charset=ISO-8859-1 +Content-Transfer-Encoding: 7bit +X-Sieve: CMU Sieve 2.2 +X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, + 13 Jun 2013 14:03:48 -0700 (PDT) +X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 + +I could not disagree more. I am obviously biased but adventure time is the +greatest show ever created. Everyone should watch it. + +- Jake out + + +On Sun, Jun 9, 2013 at 1:39 PM, eviltrout via Discourse Meta + wrote: +> +> +> +> eviltrout posted in 'Adventure Time Sux' on Discourse Meta: +> +> --- +> hey guys everyone knows adventure time sucks! +> +> --- +> Please visit this link to respond: http://localhost:3000/t/adventure-time-sux/1234/3 +> +> To unsubscribe from these emails, visit your [user preferences](http://localhost:3000/user_preferences). +> \ No newline at end of file diff --git a/spec/lib/gitlab/email_receiver_spec.rb b/spec/lib/gitlab/email_receiver_spec.rb new file mode 100644 index 00000000000..d6302eac13a --- /dev/null +++ b/spec/lib/gitlab/email_receiver_spec.rb @@ -0,0 +1,481 @@ +require "spec_helper" + +# Inspired in great part by Discourse's Email::Receiver +describe Gitlab::EmailReceiver do + def fixture_file(filename) + return '' if filename.blank? + file_path = File.expand_path(Rails.root + 'spec/fixtures/' + filename) + File.read(file_path) + end + + before do + allow(Gitlab.config.reply_by_email).to receive(:enabled).and_return(true) + allow(Gitlab.config.reply_by_email).to receive(:address).and_return("reply+%{reply_key}@appmail.adventuretime.ooo") + end + + describe 'parse_body' do + def test_parse_body(mail_string) + Gitlab::EmailReceiver.new(nil).parse_body(Mail::Message.new(mail_string)) + end + + it "raises EmptyEmailError if the message is blank" do + expect { test_parse_body("") }.to raise_error(Gitlab::EmailReceiver::EmptyEmailError) + end + + it "raises EmptyEmailError if the message is not an email" do + expect { test_parse_body("asdf" * 30) }.to raise_error(Gitlab::EmailReceiver::EmptyEmailError) + end + + it "raises EmptyEmailError if there is no reply content" do + expect { test_parse_body(fixture_file("emails/no_content_reply.eml")) }.to raise_error(Gitlab::EmailReceiver::EmptyEmailError) + end + + it "can parse the html section" do + expect(test_parse_body(fixture_file("emails/html_only.eml"))).to eq("The EC2 instance - I've seen that there tends to be odd and " + + "unrecommended settings on the Bitnami installs that I've checked out.") + end + + it "supports a Dutch reply" do + expect(test_parse_body(fixture_file("emails/dutch.eml"))).to eq("Dit is een antwoord in het Nederlands.") + end + + it "removes an 'on date wrote' quoting line" do + expect(test_parse_body(fixture_file("emails/on_wrote.eml"))).to eq("Sure, all you need to do is frobnicate the foobar and you'll be all set!") + end + + it "handles multiple paragraphs" do + expect(test_parse_body(fixture_file("emails/paragraphs.eml"))). + to eq( +"Is there any reason the *old* candy can't be be kept in silos while the new candy +is imported into *new* silos? + +The thing about candy is it stays delicious for a long time -- we can just keep +it there without worrying about it too much, imo. + +Thanks for listening." + ) + end + + it "handles multiple paragraphs when parsing html" do + expect(test_parse_body(fixture_file("emails/html_paragraphs.eml"))). + to eq( +"Awesome! + +Pleasure to have you here! + +:boom:" + ) + end + + it "handles newlines" do + expect(test_parse_body(fixture_file("emails/newlines.eml"))). + to eq( +"This is my reply. +It is my best reply. +It will also be my *only* reply." + ) + end + + it "handles inline reply" do + expect(test_parse_body(fixture_file("emails/inline_reply.eml"))). + to eq( +"On Wed, Oct 8, 2014 at 11:12 AM, techAPJ wrote: + +> techAPJ +> November 28 +> +> Test reply. +> +> First paragraph. +> +> Second paragraph. +> +> To respond, reply to this email or visit +> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in +> your browser. +> ------------------------------ +> Previous Replies codinghorror +> +> November 28 +> +> We're testing the latest GitHub email processing library which we are +> integrating now. +> +> https://github.com/github/email_reply_parser +> +> Go ahead and reply to this topic and I'll reply from various email clients +> for testing. +> ------------------------------ +> +> To respond, reply to this email or visit +> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in +> your browser. +> +> To unsubscribe from these emails, visit your user preferences +> . +> + +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over +the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown +fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over +the lazy dog. The quick brown fox jumps over the lazy dog." + ) + end + + it "strips iPhone signature" do + expect(test_parse_body(fixture_file("emails/iphone_signature.eml"))).not_to match /Sent from my iPhone/ + end + + it "properly renders email reply from gmail web client" do + expect(test_parse_body(fixture_file("emails/gmail_web.eml"))). + to eq( +"### This is a reply from standard GMail in Google Chrome. + +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over +the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown +fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over +the lazy dog. The quick brown fox jumps over the lazy dog. + +Here's some **bold** text in Markdown. + +Here's a link http://example.com" + ) + end + + it "properly renders email reply from iOS default mail client" do + expect(test_parse_body(fixture_file("emails/ios_default.eml"))). + to eq( +"### this is a reply from iOS default mail + +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. + +Here's some **bold** markdown text. + +Here's a link http://example.com" + ) + end + + it "properly renders email reply from Android 5 gmail client" do + expect(test_parse_body(fixture_file("emails/android_gmail.eml"))). + to eq( +"### this is a reply from Android 5 gmail + +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over +the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown +fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. +The quick brown fox jumps over the lazy dog. + +This is **bold** in Markdown. + +This is a link to http://example.com" + ) + end + + it "properly renders email reply from Windows 8.1 Metro default mail client" do + expect(test_parse_body(fixture_file("emails/windows_8_metro.eml"))). + to eq( +"### reply from default mail client in Windows 8.1 Metro + + +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. + + +This is a **bold** word in Markdown + + +This is a link http://example.com" + ) + end + + it "properly renders email reply from MS Outlook client" do + expect(test_parse_body(fixture_file("emails/outlook.eml"))).to eq("Microsoft Outlook 2010") + end + end + +# describe "posting replies" do +# let(:reply_key) { raise "Override this in a lower describe block" } +# let(:email_raw) { raise "Override this in a lower describe block" } +# # ---- +# let(:receiver) { Gitlab::EmailReceiver.new(email_raw) } +# let(:post) { create_post } +# let(:topic) { post.topic } +# let(:posting_user) { post.user } +# let(:replying_user_email) { 'jake@adventuretime.ooo' } +# let(:replying_user) { Fabricate(:user, email: replying_user_email, trust_level: 2)} +# let(:email_log) { EmailLog.new(reply_key: reply_key, +# post: post, +# post_id: post.id, +# topic_id: post.topic_id, +# email_type: 'user_posted', +# user: replying_user, +# user_id: replying_user.id, +# to_address: replying_user_email +# ) } + +# before do +# email_log.save +# end + +# # === Success Posting === + +# describe "valid_reply.eml" do +# let!(:reply_key) { '59d8df8370b7e95c5a49fbf86aeb2c93' } +# let!(:email_raw) { fixture_file("emails/valid_reply.eml") } + +# it "creates a post with the correct content" do +# start_count = topic.posts.count + +# receiver.process + +# expect(topic.posts.count).to eq(start_count + 1) +# created_post = topic.posts.last +# expect(created_post.via_email).to eq(true) +# expect(created_post.raw_email).to eq(fixture_file("emails/valid_reply.eml")) +# expect(created_post.cooked.strip).to eq(fixture_file("emails/valid_reply.cooked").strip) +# end +# end + +# describe "paragraphs.eml" do +# let!(:reply_key) { '59d8df8370b7e95c5a49fbf86aeb2c93' } +# let!(:email_raw) { fixture_file("emails/paragraphs.eml") } + +# it "cooks multiple paragraphs with traditional Markdown linebreaks" do +# start_count = topic.posts.count + +# receiver.process + +# expect(topic.posts.count).to eq(start_count + 1) +# expect(topic.posts.last.cooked.strip).to eq(fixture_file("emails/paragraphs.cooked").strip) +# expect(topic.posts.last.cooked).not_to match /
/ +# expect(Upload.find_by(sha1: upload_sha)).not_to eq(nil) +# end + +# end + +# # === Failure Conditions === + +# describe "too_short.eml" do +# let!(:reply_key) { '636ca428858779856c226bb145ef4fad' } +# let!(:email_raw) { +# fixture_file("emails/too_short.eml") +# .gsub("TO", "reply+#{reply_key}@appmail.adventuretime.ooo") +# .gsub("FROM", replying_user_email) +# .gsub("SUBJECT", "re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux'") +# } + +# it "raises an InvalidPost error" do +# SiteSetting.min_post_length = 5 +# expect { receiver.process }.to raise_error(Gitlab::EmailReceiver::InvalidPost) +# end +# end + +# describe "too_many_mentions.eml" do +# let!(:reply_key) { '636ca428858779856c226bb145ef4fad' } +# let!(:email_raw) { fixture_file("emails/too_many_mentions.eml") } + +# it "raises an InvalidPost error" do +# SiteSetting.max_mentions_per_post = 10 +# (1..11).each do |i| +# Fabricate(:user, username: "user#{i}").save +# end + +# expect { receiver.process }.to raise_error(Gitlab::EmailReceiver::InvalidPost) +# end +# end + +# describe "auto response email replies should not be accepted" do +# let!(:reply_key) { '636ca428858779856c226bb145ef4fad' } +# let!(:email_raw) { fixture_file("emails/auto_reply.eml") } +# it "raises a AutoGeneratedEmailError" do +# expect { receiver.process }.to raise_error(Gitlab::EmailReceiver::AutoGeneratedEmailError) +# end +# end + +# end + +# describe "posting reply to a closed topic" do +# let(:reply_key) { raise "Override this in a lower describe block" } +# let(:email_raw) { raise "Override this in a lower describe block" } +# let(:receiver) { Gitlab::EmailReceiver.new(email_raw) } +# let(:topic) { Fabricate(:topic, closed: true) } +# let(:post) { Fabricate(:post, topic: topic, post_number: 1) } +# let(:replying_user_email) { 'jake@adventuretime.ooo' } +# let(:replying_user) { Fabricate(:user, email: replying_user_email, trust_level: 2) } +# let(:email_log) { EmailLog.new(reply_key: reply_key, +# post: post, +# post_id: post.id, +# topic_id: topic.id, +# email_type: 'user_posted', +# user: replying_user, +# user_id: replying_user.id, +# to_address: replying_user_email +# ) } + +# before do +# email_log.save +# end + +# describe "should not create post" do +# let!(:reply_key) { '59d8df8370b7e95c5a49fbf86aeb2c93' } +# let!(:email_raw) { fixture_file("emails/valid_reply.eml") } +# it "raises a TopicClosedError" do +# expect { receiver.process }.to raise_error(Gitlab::EmailReceiver::TopicClosedError) +# end +# end +# end + +# describe "posting reply to a deleted topic" do +# let(:reply_key) { raise "Override this in a lower describe block" } +# let(:email_raw) { raise "Override this in a lower describe block" } +# let(:receiver) { Gitlab::EmailReceiver.new(email_raw) } +# let(:deleted_topic) { Fabricate(:deleted_topic) } +# let(:post) { Fabricate(:post, topic: deleted_topic, post_number: 1) } +# let(:replying_user_email) { 'jake@adventuretime.ooo' } +# let(:replying_user) { Fabricate(:user, email: replying_user_email, trust_level: 2) } +# let(:email_log) { EmailLog.new(reply_key: reply_key, +# post: post, +# post_id: post.id, +# topic_id: deleted_topic.id, +# email_type: 'user_posted', +# user: replying_user, +# user_id: replying_user.id, +# to_address: replying_user_email +# ) } + +# before do +# email_log.save +# end + +# describe "should not create post" do +# let!(:reply_key) { '59d8df8370b7e95c5a49fbf86aeb2c93' } +# let!(:email_raw) { fixture_file("emails/valid_reply.eml") } +# it "raises a TopicNotFoundError" do +# expect { receiver.process }.to raise_error(Gitlab::EmailReceiver::TopicNotFoundError) +# end +# end +# end + +# describe "posting a new topic" do +# let(:category_destination) { raise "Override this in a lower describe block" } +# let(:email_raw) { raise "Override this in a lower describe block" } +# let(:allow_strangers) { false } +# # ---- +# let(:receiver) { Gitlab::EmailReceiver.new(email_raw) } +# let(:user_email) { 'jake@adventuretime.ooo' } +# let(:user) { Fabricate(:user, email: user_email, trust_level: 2)} +# let(:category) { Fabricate(:category, email_in: category_destination, email_in_allow_strangers: allow_strangers) } + +# before do +# SiteSetting.email_in = true +# user.save +# category.save +# end + +# describe "too_short.eml" do +# let!(:category_destination) { 'incoming+amazing@appmail.adventuretime.ooo' } +# let(:email_raw) { +# fixture_file("emails/too_short.eml") +# .gsub("TO", category_destination) +# .gsub("FROM", user_email) +# .gsub("SUBJECT", "A long subject that passes the checks") +# } + +# it "does not create a topic if the post fails" do +# before_topic_count = Topic.count + +# expect { receiver.process }.to raise_error(Gitlab::EmailReceiver::InvalidPost) + +# expect(Topic.count).to eq(before_topic_count) +# end + +# end + +# end + +# def fill_email(mail, from, to, body = nil, subject = nil) +# result = mail.gsub("FROM", from).gsub("TO", to) +# if body +# result.gsub!(/Hey.*/m, body) +# end +# if subject +# result.sub!(/We .*/, subject) +# end +# result +# end + +# def process_email(opts) +# incoming_email = fixture_file("emails/valid_incoming.eml") +# email = fill_email(incoming_email, opts[:from], opts[:to], opts[:body], opts[:subject]) +# Gitlab::EmailReceiver.new(email).process +# end + +# describe "with a valid email" do +# let(:reply_key) { "59d8df8370b7e95c5a49fbf86aeb2c93" } +# let(:to) { SiteSetting.reply_by_email_address.gsub("%{reply_key}", reply_key) } + +# let(:valid_reply) { +# reply = fixture_file("emails/valid_reply.eml") +# to = SiteSetting.reply_by_email_address.gsub("%{reply_key}", reply_key) +# fill_email(reply, "test@test.com", to) +# } + +# let(:receiver) { Gitlab::EmailReceiver.new(valid_reply) } +# let(:post) { create_post } +# let(:user) { post.user } +# let(:email_log) { EmailLog.new(reply_key: reply_key, +# post_id: post.id, +# topic_id: post.topic_id, +# user_id: post.user_id, +# post: post, +# user: user, +# email_type: 'test', +# to_address: 'test@test.com' +# ) } +# let(:reply_body) { +# "I could not disagree more. I am obviously biased but adventure time is the +# greatest show ever created. Everyone should watch it. + +# - Jake out" } + +# describe "with an email log" do + +# it "extracts data" do +# expect{ receiver.process }.to raise_error(Gitlab::EmailReceiver::EmailLogNotFound) + +# email_log.save! +# receiver.process + +# expect(receiver.body).to eq(reply_body) +# expect(receiver.email_log).to eq(email_log) +# end + +# end + +# end +end -- cgit v1.2.1 From 001c8cd0ee6e70cc96727cc37eedf263b916d24b Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 14 Aug 2015 22:21:22 -0700 Subject: Gracefully handle SMTP user input errors (e.g. incorrect email addresses) to prevent Sidekiq retries Closes https://github.com/gitlabhq/gitlabhq/issues/9560 --- CHANGELOG | 1 + app/workers/emails_on_push_worker.rb | 33 +++++++++++++++++------------ spec/workers/emails_on_push_worker_spec.rb | 34 ++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 14 deletions(-) create mode 100644 spec/workers/emails_on_push_worker_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 8214e57aaa7..6872ec16aa5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,7 @@ v 8.0.0 (unreleased) - Faster merge - Ability to fetch merge requests from refs/merge-requests/:id - Allow displaying of archived projects in the admin interface (Artem Sidorenko) + - Gracefully handle SMTP user input errors (e.g. incorrect email addresses) to prevent Sidekiq retries (Stan Hu) v 7.14.0 (unreleased) - Update default robots.txt rules to disallow crawling of irrelevant pages (Ben Bodenmiller) diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb index 1d21addece6..916a99bb273 100644 --- a/app/workers/emails_on_push_worker.rb +++ b/app/workers/emails_on_push_worker.rb @@ -4,7 +4,7 @@ class EmailsOnPushWorker def perform(project_id, recipients, push_data, options = {}) options.symbolize_keys! options.reverse_merge!( - send_from_committer_email: false, + send_from_committer_email: false, disable_diffs: false ) send_from_committer_email = options[:send_from_committer_email] @@ -16,9 +16,9 @@ class EmailsOnPushWorker ref = push_data["ref"] author_id = push_data["user_id"] - action = + action = if Gitlab::Git.blank_ref?(before_sha) - :create + :create elsif Gitlab::Git.blank_ref?(after_sha) :delete else @@ -42,17 +42,22 @@ class EmailsOnPushWorker end recipients.split(" ").each do |recipient| - Notify.repository_push_email( - project_id, - recipient, - author_id: author_id, - ref: ref, - action: action, - compare: compare, - reverse_compare: reverse_compare, - send_from_committer_email: send_from_committer_email, - disable_diffs: disable_diffs - ).deliver + begin + Notify.repository_push_email( + project_id, + recipient, + author_id: author_id, + ref: ref, + action: action, + compare: compare, + reverse_compare: reverse_compare, + send_from_committer_email: send_from_committer_email, + disable_diffs: disable_diffs + ).deliver + # These are input errors and won't be corrected even if Sidekiq retries + rescue Net::SMTPFatalError, Net::SMTPSyntaxError => e + logger.info("Failed to send e-mail for project '#{project.name_with_namespace}' to #{recipient}: #{e}") + end end ensure compare = nil diff --git a/spec/workers/emails_on_push_worker_spec.rb b/spec/workers/emails_on_push_worker_spec.rb new file mode 100644 index 00000000000..3600c771075 --- /dev/null +++ b/spec/workers/emails_on_push_worker_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +describe EmailsOnPushWorker do + include RepoHelpers + + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:data) { Gitlab::PushDataBuilder.build_sample(project, user) } + + subject { EmailsOnPushWorker.new } + + before do + allow(Project).to receive(:find).and_return(project) + end + + describe "#perform" do + it "sends mail" do + subject.perform(project.id, user.email, data.stringify_keys) + + email = ActionMailer::Base.deliveries.last + expect(email.subject).to include('Change some files') + expect(email.to).to eq([user.email]) + end + + it "gracefully handles an input SMTP error" do + ActionMailer::Base.deliveries.clear + allow(Notify).to receive(:repository_push_email).and_raise(Net::SMTPFatalError) + + subject.perform(project.id, user.email, data.stringify_keys) + + expect(ActionMailer::Base.deliveries.count).to eq(0) + end + end +end -- cgit v1.2.1 From 3ff9d5c64cef8bf8daed5e253e388545987fb945 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 10:58:04 -0700 Subject: Use canonical version of mail_room. --- Gemfile | 2 +- Gemfile.lock | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/Gemfile b/Gemfile index 9879141f5cb..3aa3c72e088 100644 --- a/Gemfile +++ b/Gemfile @@ -273,6 +273,6 @@ gem "newrelic_rpm" gem 'octokit', '3.7.0' -gem "mail_room", github: "DouweM/mail_room", branch: "sidekiq" +gem "mail_room", "~> 0.4.0" gem 'email_reply_parser' diff --git a/Gemfile.lock b/Gemfile.lock index c4c327aa758..5278fe243a8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,10 +1,3 @@ -GIT - remote: git://github.com/DouweM/mail_room.git - revision: c17c74cdef55dcd962333545fdb4d6e496cd9050 - branch: sidekiq - specs: - mail_room (0.3.1) - GEM remote: https://rubygems.org/ specs: @@ -379,6 +372,7 @@ GEM systemu (~> 2.6.2) mail (2.6.3) mime-types (>= 1.16, < 3) + mail_room (0.4.0) method_source (0.8.2) mime-types (1.25.1) mimemagic (0.3.0) @@ -814,7 +808,7 @@ DEPENDENCIES jquery-ui-rails kaminari (~> 0.15.1) letter_opener - mail_room! + mail_room (~> 0.4.0) minitest (~> 5.3.0) mousetrap-rails mysql2 -- cgit v1.2.1 From e9972efc2f3d730e989907585dd1438c517a0bba Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 11:05:06 -0700 Subject: Extract ReplyParser and AttachmentUploader from Receiver. --- app/workers/email_receiver_worker.rb | 16 +- lib/gitlab/email/attachment_uploader.rb | 35 +++ lib/gitlab/email/html_cleaner.rb | 135 ++++++++ lib/gitlab/email/receiver.rb | 101 ++++++ lib/gitlab/email/reply_parser.rb | 91 ++++++ lib/gitlab/email_html_cleaner.rb | 133 -------- lib/gitlab/email_receiver.rb | 192 ------------ spec/lib/gitlab/email/reply_parser_spec.rb | 191 ++++++++++++ spec/lib/gitlab/email_receiver_spec.rb | 481 ----------------------------- 9 files changed, 561 insertions(+), 814 deletions(-) create mode 100644 lib/gitlab/email/attachment_uploader.rb create mode 100644 lib/gitlab/email/html_cleaner.rb create mode 100644 lib/gitlab/email/receiver.rb create mode 100644 lib/gitlab/email/reply_parser.rb delete mode 100644 lib/gitlab/email_html_cleaner.rb delete mode 100644 lib/gitlab/email_receiver.rb create mode 100644 spec/lib/gitlab/email/reply_parser_spec.rb delete mode 100644 spec/lib/gitlab/email_receiver_spec.rb diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb index 2cfd64cefad..4f3556bfc03 100644 --- a/app/workers/email_receiver_worker.rb +++ b/app/workers/email_receiver_worker.rb @@ -7,7 +7,7 @@ class EmailReceiverWorker return unless Gitlab::ReplyByEmail.enabled? begin - Gitlab::EmailReceiver.new(raw).execute + Gitlab::Email::Receiver.new(raw).execute rescue => e handle_failure(raw, e) end @@ -22,20 +22,20 @@ class EmailReceiverWorker reason = nil case e - when Gitlab::EmailReceiver::SentNotificationNotFound + when Gitlab::Email::Receiver::SentNotificationNotFound reason = "We couldn't figure out what the email is in reply to. Please create your comment through the web interface." - when Gitlab::EmailReceiver::EmptyEmailError + when Gitlab::Email::Receiver::EmptyEmailError can_retry = true reason = "It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies." - when Gitlab::EmailReceiver::AutoGeneratedEmailError + when Gitlab::Email::Receiver::AutoGeneratedEmailError reason = "The email was marked as 'auto generated', which we can't accept. Please create your comment through the web interface." - when Gitlab::EmailReceiver::UserNotFoundError + when Gitlab::Email::Receiver::UserNotFoundError reason = "We couldn't figure out what user corresponds to the email. Please create your comment through the web interface." - when Gitlab::EmailReceiver::UserNotAuthorizedError + when Gitlab::Email::Receiver::UserNotAuthorizedError reason = "You are not allowed to respond to the thread you are replying to. If you believe this is in error, contact a staff member." - when Gitlab::EmailReceiver::NoteableNotFoundError + when Gitlab::Email::Receiver::NoteableNotFoundError reason = "The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member." - when Gitlab::EmailReceiver::InvalidNote + when Gitlab::Email::Receiver::InvalidNote can_retry = true reason = e.message else diff --git a/lib/gitlab/email/attachment_uploader.rb b/lib/gitlab/email/attachment_uploader.rb new file mode 100644 index 00000000000..0c0f50f2751 --- /dev/null +++ b/lib/gitlab/email/attachment_uploader.rb @@ -0,0 +1,35 @@ +module Gitlab + module Email + module AttachmentUploader + attr_accessor :message + + def initialize(message) + @message = message + end + + def execute(project) + attachments = [] + + message.attachments.each do |attachment| + tmp = Tempfile.new("gitlab-email-attachment") + begin + File.open(tmp.path, "w+b") { |f| f.write attachment.body.decoded } + + file = { + tempfile: tmp, + filename: attachment.filename, + content_type: attachment.content_type + } + + link = ::Projects::UploadService.new(project, file).execute + attachments << link if link + ensure + tmp.close! + end + end + + attachments + end + end + end +end diff --git a/lib/gitlab/email/html_cleaner.rb b/lib/gitlab/email/html_cleaner.rb new file mode 100644 index 00000000000..e1ae9eee56c --- /dev/null +++ b/lib/gitlab/email/html_cleaner.rb @@ -0,0 +1,135 @@ +# Taken mostly from Discourse's Email::HtmlCleaner +module Gitlab + module Email + # HtmlCleaner cleans up the extremely dirty HTML that many email clients + # generate by stripping out any excess divs or spans, removing styling in + # the process (which also makes the html more suitable to be parsed as + # Markdown). + class HtmlCleaner + # Elements to hoist all children out of + HTML_HOIST_ELEMENTS = %w(div span font table tbody th tr td) + # Node types to always delete + HTML_DELETE_ELEMENT_TYPES = [ + Nokogiri::XML::Node::DTD_NODE, + Nokogiri::XML::Node::COMMENT_NODE, + ] + + # Private variables: + # @doc - nokogiri document + # @out - same as @doc, but only if trimming has occured + def initialize(html) + if html.is_a?(String) + @doc = Nokogiri::HTML(html) + else + @doc = html + end + end + + class << self + # HtmlCleaner.trim(inp, opts={}) + # + # Arguments: + # inp - Either a HTML string or a Nokogiri document. + # Options: + # :return => :doc, :string + # Specify the desired return type. + # Defaults to the type of the input. + # A value of :string is equivalent to calling get_document_text() + # on the returned document. + def trim(inp, opts={}) + cleaner = HtmlCleaner.new(inp) + + opts[:return] ||= (inp.is_a?(String) ? :string : :doc) + + if opts[:return] == :string + cleaner.output_html + else + cleaner.output_document + end + end + + # HtmlCleaner.get_document_text(doc) + # + # Get the body portion of the document, including html, as a string. + def get_document_text(doc) + body = doc.xpath('//body') + if body + body.inner_html + else + doc.inner_html + end + end + end + + def output_document + @out ||= begin + doc = @doc + trim_process_node doc + add_newlines doc + doc + end + end + + def output_html + HtmlCleaner.get_document_text(output_document) + end + + private + + def add_newlines(doc) + # Replace
tags with a markdown \n + doc.xpath('//br').each do |br| + br.replace(new_linebreak_node doc, 2) + end + # Surround

tags with newlines, to help with line-wise postprocessing + # and ensure markdown paragraphs + doc.xpath('//p').each do |p| + p.before(new_linebreak_node doc) + p.after(new_linebreak_node doc, 2) + end + end + + def new_linebreak_node(doc, count=1) + Nokogiri::XML::Text.new("\n" * count, doc) + end + + def trim_process_node(node) + if should_hoist?(node) + hoisted = trim_hoist_element node + hoisted.each { |child| trim_process_node child } + elsif should_delete?(node) + node.remove + else + if children = node.children + children.each { |child| trim_process_node child } + end + end + + node + end + + def trim_hoist_element(element) + hoisted = [] + element.children.each do |child| + element.before(child) + hoisted << child + end + element.remove + hoisted + end + + def should_hoist?(node) + return false unless node.element? + HTML_HOIST_ELEMENTS.include? node.name + end + + def should_delete?(node) + return true if HTML_DELETE_ELEMENT_TYPES.include? node.type + return true if node.element? && node.name == 'head' + return true if node.text? && node.text.strip.blank? + + false + end + end + end +end diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb new file mode 100644 index 00000000000..c46fce6afe2 --- /dev/null +++ b/lib/gitlab/email/receiver.rb @@ -0,0 +1,101 @@ +# Inspired in great part by Discourse's Email::Receiver +module Gitlab + module Email + class Receiver + class ProcessingError < StandardError; end + class EmailUnparsableError < ProcessingError; end + class EmptyEmailError < ProcessingError; end + class UserNotFoundError < ProcessingError; end + class UserNotAuthorizedError < ProcessingError; end + class NoteableNotFoundError < ProcessingError; end + class AutoGeneratedEmailError < ProcessingError; end + class SentNotificationNotFound < ProcessingError; end + class InvalidNote < ProcessingError; end + + def initialize(raw) + @raw = raw + end + + def message + @message ||= Mail::Message.new(@raw) + rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError => e + raise EmailUnparsableError, e + end + + def execute + raise SentNotificationNotFound unless sent_notification + + raise EmptyEmailError if @raw.blank? + + raise AutoGeneratedEmailError if message.header.to_s =~ /auto-(generated|replied)/ + + author = sent_notification.recipient + + raise UserNotFoundError unless author + + project = sent_notification.project + + raise UserNotAuthorizedError unless author.can?(:create_note, project) + + raise NoteableNotFoundError unless sent_notification.noteable + + reply = ReplyParser.new(message).execute.strip + + raise EmptyEmailError if reply.blank? + + reply = add_attachments(reply) + + note = create_note(reply) + + unless note.persisted? + message = "The comment could not be created for the following reasons:" + note.errors.full_messages.each do |error| + message << "\n\n- #{error}" + end + + raise InvalidNote, message + end + end + + private + + def reply_key + reply_key = nil + message.to.each do |address| + reply_key = Gitlab::ReplyByEmail.reply_key_from_address(address) + break if reply_key + end + + reply_key + end + + def sent_notification + return nil unless reply_key + + SentNotification.for(reply_key) + end + + def add_attachments(reply) + attachments = AttachmentUploader.new(message).execute(project) + + attachments.each do |link| + text = "[#{link[:alt]}](#{link[:url]})" + text.prepend("!") if link[:is_image] + + reply << "\n\n#{text}" + end + end + + def create_note(reply) + Notes::CreateService.new( + sent_notification.project, + sent_notification.recipient, + note: reply, + noteable_type: sent_notification.noteable_type, + noteable_id: sent_notification.noteable_id, + commit_id: sent_notification.commit_id + ).execute + end + end + end +end diff --git a/lib/gitlab/email/reply_parser.rb b/lib/gitlab/email/reply_parser.rb new file mode 100644 index 00000000000..6ceb755968c --- /dev/null +++ b/lib/gitlab/email/reply_parser.rb @@ -0,0 +1,91 @@ +# Inspired in great part by Discourse's Email::Receiver +module Gitlab + module Email + class ReplyParser + attr_accessor :message + + def initialize(message) + @message = message + end + + def execute + body = select_body(message) + + encoding = body.encoding + + body = discourse_email_trimmer(body) + + body = EmailReplyParser.parse_reply(body) + + body.force_encoding(encoding).encode("UTF-8") + end + + private + + def select_body(message) + html = nil + text = nil + + if message.multipart? + html = fix_charset(message.html_part) + text = fix_charset(message.text_part) + elsif message.content_type =~ /text\/html/ + html = fix_charset(message) + end + + # prefer plain text + return text if text + + if html + body = HtmlCleaner.new(html).output_html + else + body = fix_charset(message) + end + + # Certain trigger phrases that means we didn't parse correctly + if body =~ /(Content\-Type\:|multipart\/alternative|text\/plain)/ + return "" + end + + body + end + + # Force encoding to UTF-8 on a Mail::Message or Mail::Part + def fix_charset(object) + return nil if object.nil? + + if object.charset + object.body.decoded.force_encoding(object.charset.gsub(/utf8/i, "UTF-8")).encode("UTF-8").to_s + else + object.body.to_s + end + rescue + nil + end + + REPLYING_HEADER_LABELS = %w(From Sent To Subject Reply To Cc Bcc Date) + REPLYING_HEADER_REGEX = Regexp.union(REPLYING_HEADER_LABELS.map { |label| "#{label}:" }) + + def discourse_email_trimmer(body) + lines = body.scrub.lines.to_a + range_end = 0 + + lines.each_with_index do |l, idx| + # This one might be controversial but so many reply lines have years, times and end with a colon. + # Let's try it and see how well it works. + break if (l =~ /\d{4}/ && l =~ /\d:\d\d/ && l =~ /\:$/) || + (l =~ /On \w+ \d+,? \d+,?.*wrote:/) + + # Headers on subsequent lines + break if (0..2).all? { |off| lines[idx+off] =~ REPLYING_HEADER_REGEX } + # Headers on the same line + break if REPLYING_HEADER_LABELS.count { |label| l.include?(label) } >= 3 + + range_end = idx + end + + lines[0..range_end].join.strip + end + end + end +end diff --git a/lib/gitlab/email_html_cleaner.rb b/lib/gitlab/email_html_cleaner.rb deleted file mode 100644 index 6d7a17fe87c..00000000000 --- a/lib/gitlab/email_html_cleaner.rb +++ /dev/null @@ -1,133 +0,0 @@ -# Taken mostly from Discourse's Email::HtmlCleaner -module Gitlab - # HtmlCleaner cleans up the extremely dirty HTML that many email clients - # generate by stripping out any excess divs or spans, removing styling in - # the process (which also makes the html more suitable to be parsed as - # Markdown). - class EmailHtmlCleaner - # Elements to hoist all children out of - HTML_HOIST_ELEMENTS = %w(div span font table tbody th tr td) - # Node types to always delete - HTML_DELETE_ELEMENT_TYPES = [ - Nokogiri::XML::Node::DTD_NODE, - Nokogiri::XML::Node::COMMENT_NODE, - ] - - # Private variables: - # @doc - nokogiri document - # @out - same as @doc, but only if trimming has occured - def initialize(html) - if html.is_a?(String) - @doc = Nokogiri::HTML(html) - else - @doc = html - end - end - - class << self - # EmailHtmlCleaner.trim(inp, opts={}) - # - # Arguments: - # inp - Either a HTML string or a Nokogiri document. - # Options: - # :return => :doc, :string - # Specify the desired return type. - # Defaults to the type of the input. - # A value of :string is equivalent to calling get_document_text() - # on the returned document. - def trim(inp, opts={}) - cleaner = EmailHtmlCleaner.new(inp) - - opts[:return] ||= (inp.is_a?(String) ? :string : :doc) - - if opts[:return] == :string - cleaner.output_html - else - cleaner.output_document - end - end - - # EmailHtmlCleaner.get_document_text(doc) - # - # Get the body portion of the document, including html, as a string. - def get_document_text(doc) - body = doc.xpath('//body') - if body - body.inner_html - else - doc.inner_html - end - end - end - - def output_document - @out ||= begin - doc = @doc - trim_process_node doc - add_newlines doc - doc - end - end - - def output_html - EmailHtmlCleaner.get_document_text(output_document) - end - - private - - def add_newlines(doc) - # Replace
tags with a markdown \n - doc.xpath('//br').each do |br| - br.replace(new_linebreak_node doc, 2) - end - # Surround

tags with newlines, to help with line-wise postprocessing - # and ensure markdown paragraphs - doc.xpath('//p').each do |p| - p.before(new_linebreak_node doc) - p.after(new_linebreak_node doc, 2) - end - end - - def new_linebreak_node(doc, count=1) - Nokogiri::XML::Text.new("\n" * count, doc) - end - - def trim_process_node(node) - if should_hoist?(node) - hoisted = trim_hoist_element node - hoisted.each { |child| trim_process_node child } - elsif should_delete?(node) - node.remove - else - if children = node.children - children.each { |child| trim_process_node child } - end - end - - node - end - - def trim_hoist_element(element) - hoisted = [] - element.children.each do |child| - element.before(child) - hoisted << child - end - element.remove - hoisted - end - - def should_hoist?(node) - return false unless node.element? - HTML_HOIST_ELEMENTS.include? node.name - end - - def should_delete?(node) - return true if HTML_DELETE_ELEMENT_TYPES.include? node.type - return true if node.element? && node.name == 'head' - return true if node.text? && node.text.strip.blank? - - false - end - end -end diff --git a/lib/gitlab/email_receiver.rb b/lib/gitlab/email_receiver.rb deleted file mode 100644 index 3c1f346c0cf..00000000000 --- a/lib/gitlab/email_receiver.rb +++ /dev/null @@ -1,192 +0,0 @@ -# Inspired in great part by Discourse's Email::Receiver -module Gitlab - class EmailReceiver - class ProcessingError < StandardError; end - class EmailUnparsableError < ProcessingError; end - class EmptyEmailError < ProcessingError; end - class UserNotFoundError < ProcessingError; end - class UserNotAuthorizedError < ProcessingError; end - class NoteableNotFoundError < ProcessingError; end - class AutoGeneratedEmailError < ProcessingError; end - class SentNotificationNotFound < ProcessingError; end - class InvalidNote < ProcessingError; end - - def initialize(raw) - @raw = raw - end - - def message - @message ||= Mail::Message.new(@raw) - rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError => e - raise EmailUnparsableError, e - end - - def execute - raise SentNotificationNotFound unless sent_notification - - raise EmptyEmailError if @raw.blank? - - raise AutoGeneratedEmailError if message.header.to_s =~ /auto-(generated|replied)/ - - author = sent_notification.recipient - - raise UserNotFoundError unless author - - project = sent_notification.project - - raise UserNotAuthorizedError unless author.can?(:create_note, project) - - raise NoteableNotFoundError unless sent_notification.noteable - - body = parse_body(message) - - upload_attachments.each do |link| - body << "\n\n#{link}" - end - - note = Notes::CreateService.new( - project, - author, - note: body, - noteable_type: sent_notification.noteable_type, - noteable_id: sent_notification.noteable_id, - commit_id: sent_notification.commit_id - ).execute - - unless note.persisted? - message = "The comment could not be created for the following reasons:" - note.errors.full_messages.each do |error| - message << "\n\n- #{error}" - end - raise InvalidNote, message - end - end - - def parse_body(message) - body = select_body(message) - - encoding = body.encoding - raise EmptyEmailError if body.strip.blank? - - body = discourse_email_trimmer(body) - raise EmptyEmailError if body.strip.blank? - - body = EmailReplyParser.parse_reply(body) - raise EmptyEmailError if body.strip.blank? - - body.force_encoding(encoding).encode("UTF-8") - end - - private - - def reply_key - reply_key = nil - message.to.each do |address| - reply_key = Gitlab::ReplyByEmail.reply_key_from_address(address) - break if reply_key - end - - reply_key - end - - def sent_notification - return nil unless reply_key - - SentNotification.for(reply_key) - end - - def select_body(message) - html = nil - text = nil - - if message.multipart? - html = fix_charset(message.html_part) - text = fix_charset(message.text_part) - elsif message.content_type =~ /text\/html/ - html = fix_charset(message) - end - - # prefer plain text - return text if text - - if html - body = EmailHtmlCleaner.new(html).output_html - else - body = fix_charset(message) - end - - # Certain trigger phrases that means we didn't parse correctly - if body =~ /(Content\-Type\:|multipart\/alternative|text\/plain)/ - raise EmptyEmailError - end - - body - end - - # Force encoding to UTF-8 on a Mail::Message or Mail::Part - def fix_charset(object) - return nil if object.nil? - - if object.charset - object.body.decoded.force_encoding(object.charset.gsub(/utf8/i, "UTF-8")).encode("UTF-8").to_s - else - object.body.to_s - end - rescue - nil - end - - REPLYING_HEADER_LABELS = %w(From Sent To Subject Reply To Cc Bcc Date) - REPLYING_HEADER_REGEX = Regexp.union(REPLYING_HEADER_LABELS.map { |label| "#{label}:" }) - - def discourse_email_trimmer(body) - lines = body.scrub.lines.to_a - range_end = 0 - - lines.each_with_index do |l, idx| - # This one might be controversial but so many reply lines have years, times and end with a colon. - # Let's try it and see how well it works. - break if (l =~ /\d{4}/ && l =~ /\d:\d\d/ && l =~ /\:$/) || - (l =~ /On \w+ \d+,? \d+,?.*wrote:/) - - # Headers on subsequent lines - break if (0..2).all? { |off| lines[idx+off] =~ REPLYING_HEADER_REGEX } - # Headers on the same line - break if REPLYING_HEADER_LABELS.count { |label| l.include?(label) } >= 3 - - range_end = idx - end - - lines[0..range_end].join.strip - end - - def upload_attachments - attachments = [] - - message.attachments.each do |attachment| - tmp = Tempfile.new("gitlab-email-attachment") - begin - File.open(tmp.path, "w+b") { |f| f.write attachment.body.decoded } - - file = { - tempfile: tmp, - filename: attachment.filename, - content_type: attachment.content_type - } - - link = ::Projects::UploadService.new(sent_notification.project, file).execute - if link - text = "[#{link[:alt]}](#{link[:url]})" - text.prepend("!") if link[:is_image] - - attachments << text - end - ensure - tmp.close! - end - end - - attachments - end - end -end diff --git a/spec/lib/gitlab/email/reply_parser_spec.rb b/spec/lib/gitlab/email/reply_parser_spec.rb new file mode 100644 index 00000000000..999515beb1c --- /dev/null +++ b/spec/lib/gitlab/email/reply_parser_spec.rb @@ -0,0 +1,191 @@ +require "spec_helper" + +# Inspired in great part by Discourse's Email::Receiver +describe Gitlab::Email::ReplyParser do + def fixture_file(filename) + return '' if filename.blank? + file_path = File.expand_path(Rails.root + 'spec/fixtures/' + filename) + File.read(file_path) + end + + describe 'self.parse_body' do + def test_parse_body(mail_string) + described_class.new(Mail::Message.new(mail_string)).execute + end + + it "returns an empty string if the message is blank" do + expect(test_parse_body("")).to eq("") + end + + it "returns an empty string if the message is not an email" do + expect(test_parse_body("asdf" * 30)).to eq("") + end + + it "returns an empty string if there is no reply content" do + expect(test_parse_body(fixture_file("emails/no_content_reply.eml"))).to eq("") + end + + it "can parse the html section" do + expect(test_parse_body(fixture_file("emails/html_only.eml"))).to eq("The EC2 instance - I've seen that there tends to be odd and " + + "unrecommended settings on the Bitnami installs that I've checked out.") + end + + it "supports a Dutch reply" do + expect(test_parse_body(fixture_file("emails/dutch.eml"))).to eq("Dit is een antwoord in het Nederlands.") + end + + it "removes an 'on date wrote' quoting line" do + expect(test_parse_body(fixture_file("emails/on_wrote.eml"))).to eq("Sure, all you need to do is frobnicate the foobar and you'll be all set!") + end + + it "handles multiple paragraphs" do + expect(test_parse_body(fixture_file("emails/paragraphs.eml"))). + to eq( +"Is there any reason the *old* candy can't be be kept in silos while the new candy +is imported into *new* silos? + +The thing about candy is it stays delicious for a long time -- we can just keep +it there without worrying about it too much, imo. + +Thanks for listening." + ) + end + + it "handles multiple paragraphs when parsing html" do + expect(test_parse_body(fixture_file("emails/html_paragraphs.eml"))). + to eq( +"Awesome! + +Pleasure to have you here! + +:boom:" + ) + end + + it "handles newlines" do + expect(test_parse_body(fixture_file("emails/newlines.eml"))). + to eq( +"This is my reply. +It is my best reply. +It will also be my *only* reply." + ) + end + + it "handles inline reply" do + expect(test_parse_body(fixture_file("emails/inline_reply.eml"))). + to eq( +"On Wed, Oct 8, 2014 at 11:12 AM, techAPJ wrote: + +> techAPJ +> November 28 +> +> Test reply. +> +> First paragraph. +> +> Second paragraph. +> +> To respond, reply to this email or visit +> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in +> your browser. +> ------------------------------ +> Previous Replies codinghorror +> +> November 28 +> +> We're testing the latest GitHub email processing library which we are +> integrating now. +> +> https://github.com/github/email_reply_parser +> +> Go ahead and reply to this topic and I'll reply from various email clients +> for testing. +> ------------------------------ +> +> To respond, reply to this email or visit +> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in +> your browser. +> +> To unsubscribe from these emails, visit your user preferences +> . +> + +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over +the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown +fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over +the lazy dog. The quick brown fox jumps over the lazy dog." + ) + end + + it "strips iPhone signature" do + expect(test_parse_body(fixture_file("emails/iphone_signature.eml"))).not_to match /Sent from my iPhone/ + end + + it "properly renders email reply from gmail web client" do + expect(test_parse_body(fixture_file("emails/gmail_web.eml"))). + to eq( +"### This is a reply from standard GMail in Google Chrome. + +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over +the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown +fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over +the lazy dog. The quick brown fox jumps over the lazy dog. + +Here's some **bold** text in Markdown. + +Here's a link http://example.com" + ) + end + + it "properly renders email reply from iOS default mail client" do + expect(test_parse_body(fixture_file("emails/ios_default.eml"))). + to eq( +"### this is a reply from iOS default mail + +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. + +Here's some **bold** markdown text. + +Here's a link http://example.com" + ) + end + + it "properly renders email reply from Android 5 gmail client" do + expect(test_parse_body(fixture_file("emails/android_gmail.eml"))). + to eq( +"### this is a reply from Android 5 gmail + +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over +the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown +fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. +The quick brown fox jumps over the lazy dog. + +This is **bold** in Markdown. + +This is a link to http://example.com" + ) + end + + it "properly renders email reply from Windows 8.1 Metro default mail client" do + expect(test_parse_body(fixture_file("emails/windows_8_metro.eml"))). + to eq( +"### reply from default mail client in Windows 8.1 Metro + + +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. + + +This is a **bold** word in Markdown + + +This is a link http://example.com" + ) + end + + it "properly renders email reply from MS Outlook client" do + expect(test_parse_body(fixture_file("emails/outlook.eml"))).to eq("Microsoft Outlook 2010") + end + end +end diff --git a/spec/lib/gitlab/email_receiver_spec.rb b/spec/lib/gitlab/email_receiver_spec.rb deleted file mode 100644 index d6302eac13a..00000000000 --- a/spec/lib/gitlab/email_receiver_spec.rb +++ /dev/null @@ -1,481 +0,0 @@ -require "spec_helper" - -# Inspired in great part by Discourse's Email::Receiver -describe Gitlab::EmailReceiver do - def fixture_file(filename) - return '' if filename.blank? - file_path = File.expand_path(Rails.root + 'spec/fixtures/' + filename) - File.read(file_path) - end - - before do - allow(Gitlab.config.reply_by_email).to receive(:enabled).and_return(true) - allow(Gitlab.config.reply_by_email).to receive(:address).and_return("reply+%{reply_key}@appmail.adventuretime.ooo") - end - - describe 'parse_body' do - def test_parse_body(mail_string) - Gitlab::EmailReceiver.new(nil).parse_body(Mail::Message.new(mail_string)) - end - - it "raises EmptyEmailError if the message is blank" do - expect { test_parse_body("") }.to raise_error(Gitlab::EmailReceiver::EmptyEmailError) - end - - it "raises EmptyEmailError if the message is not an email" do - expect { test_parse_body("asdf" * 30) }.to raise_error(Gitlab::EmailReceiver::EmptyEmailError) - end - - it "raises EmptyEmailError if there is no reply content" do - expect { test_parse_body(fixture_file("emails/no_content_reply.eml")) }.to raise_error(Gitlab::EmailReceiver::EmptyEmailError) - end - - it "can parse the html section" do - expect(test_parse_body(fixture_file("emails/html_only.eml"))).to eq("The EC2 instance - I've seen that there tends to be odd and " + - "unrecommended settings on the Bitnami installs that I've checked out.") - end - - it "supports a Dutch reply" do - expect(test_parse_body(fixture_file("emails/dutch.eml"))).to eq("Dit is een antwoord in het Nederlands.") - end - - it "removes an 'on date wrote' quoting line" do - expect(test_parse_body(fixture_file("emails/on_wrote.eml"))).to eq("Sure, all you need to do is frobnicate the foobar and you'll be all set!") - end - - it "handles multiple paragraphs" do - expect(test_parse_body(fixture_file("emails/paragraphs.eml"))). - to eq( -"Is there any reason the *old* candy can't be be kept in silos while the new candy -is imported into *new* silos? - -The thing about candy is it stays delicious for a long time -- we can just keep -it there without worrying about it too much, imo. - -Thanks for listening." - ) - end - - it "handles multiple paragraphs when parsing html" do - expect(test_parse_body(fixture_file("emails/html_paragraphs.eml"))). - to eq( -"Awesome! - -Pleasure to have you here! - -:boom:" - ) - end - - it "handles newlines" do - expect(test_parse_body(fixture_file("emails/newlines.eml"))). - to eq( -"This is my reply. -It is my best reply. -It will also be my *only* reply." - ) - end - - it "handles inline reply" do - expect(test_parse_body(fixture_file("emails/inline_reply.eml"))). - to eq( -"On Wed, Oct 8, 2014 at 11:12 AM, techAPJ wrote: - -> techAPJ -> November 28 -> -> Test reply. -> -> First paragraph. -> -> Second paragraph. -> -> To respond, reply to this email or visit -> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in -> your browser. -> ------------------------------ -> Previous Replies codinghorror -> -> November 28 -> -> We're testing the latest GitHub email processing library which we are -> integrating now. -> -> https://github.com/github/email_reply_parser -> -> Go ahead and reply to this topic and I'll reply from various email clients -> for testing. -> ------------------------------ -> -> To respond, reply to this email or visit -> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in -> your browser. -> -> To unsubscribe from these emails, visit your user preferences -> . -> - -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over -the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown -fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over -the lazy dog. The quick brown fox jumps over the lazy dog." - ) - end - - it "strips iPhone signature" do - expect(test_parse_body(fixture_file("emails/iphone_signature.eml"))).not_to match /Sent from my iPhone/ - end - - it "properly renders email reply from gmail web client" do - expect(test_parse_body(fixture_file("emails/gmail_web.eml"))). - to eq( -"### This is a reply from standard GMail in Google Chrome. - -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over -the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown -fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over -the lazy dog. The quick brown fox jumps over the lazy dog. - -Here's some **bold** text in Markdown. - -Here's a link http://example.com" - ) - end - - it "properly renders email reply from iOS default mail client" do - expect(test_parse_body(fixture_file("emails/ios_default.eml"))). - to eq( -"### this is a reply from iOS default mail - -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. - -Here's some **bold** markdown text. - -Here's a link http://example.com" - ) - end - - it "properly renders email reply from Android 5 gmail client" do - expect(test_parse_body(fixture_file("emails/android_gmail.eml"))). - to eq( -"### this is a reply from Android 5 gmail - -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over -the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown -fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. -The quick brown fox jumps over the lazy dog. - -This is **bold** in Markdown. - -This is a link to http://example.com" - ) - end - - it "properly renders email reply from Windows 8.1 Metro default mail client" do - expect(test_parse_body(fixture_file("emails/windows_8_metro.eml"))). - to eq( -"### reply from default mail client in Windows 8.1 Metro - - -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. - - -This is a **bold** word in Markdown - - -This is a link http://example.com" - ) - end - - it "properly renders email reply from MS Outlook client" do - expect(test_parse_body(fixture_file("emails/outlook.eml"))).to eq("Microsoft Outlook 2010") - end - end - -# describe "posting replies" do -# let(:reply_key) { raise "Override this in a lower describe block" } -# let(:email_raw) { raise "Override this in a lower describe block" } -# # ---- -# let(:receiver) { Gitlab::EmailReceiver.new(email_raw) } -# let(:post) { create_post } -# let(:topic) { post.topic } -# let(:posting_user) { post.user } -# let(:replying_user_email) { 'jake@adventuretime.ooo' } -# let(:replying_user) { Fabricate(:user, email: replying_user_email, trust_level: 2)} -# let(:email_log) { EmailLog.new(reply_key: reply_key, -# post: post, -# post_id: post.id, -# topic_id: post.topic_id, -# email_type: 'user_posted', -# user: replying_user, -# user_id: replying_user.id, -# to_address: replying_user_email -# ) } - -# before do -# email_log.save -# end - -# # === Success Posting === - -# describe "valid_reply.eml" do -# let!(:reply_key) { '59d8df8370b7e95c5a49fbf86aeb2c93' } -# let!(:email_raw) { fixture_file("emails/valid_reply.eml") } - -# it "creates a post with the correct content" do -# start_count = topic.posts.count - -# receiver.process - -# expect(topic.posts.count).to eq(start_count + 1) -# created_post = topic.posts.last -# expect(created_post.via_email).to eq(true) -# expect(created_post.raw_email).to eq(fixture_file("emails/valid_reply.eml")) -# expect(created_post.cooked.strip).to eq(fixture_file("emails/valid_reply.cooked").strip) -# end -# end - -# describe "paragraphs.eml" do -# let!(:reply_key) { '59d8df8370b7e95c5a49fbf86aeb2c93' } -# let!(:email_raw) { fixture_file("emails/paragraphs.eml") } - -# it "cooks multiple paragraphs with traditional Markdown linebreaks" do -# start_count = topic.posts.count - -# receiver.process - -# expect(topic.posts.count).to eq(start_count + 1) -# expect(topic.posts.last.cooked.strip).to eq(fixture_file("emails/paragraphs.cooked").strip) -# expect(topic.posts.last.cooked).not_to match /
/ -# expect(Upload.find_by(sha1: upload_sha)).not_to eq(nil) -# end - -# end - -# # === Failure Conditions === - -# describe "too_short.eml" do -# let!(:reply_key) { '636ca428858779856c226bb145ef4fad' } -# let!(:email_raw) { -# fixture_file("emails/too_short.eml") -# .gsub("TO", "reply+#{reply_key}@appmail.adventuretime.ooo") -# .gsub("FROM", replying_user_email) -# .gsub("SUBJECT", "re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux'") -# } - -# it "raises an InvalidPost error" do -# SiteSetting.min_post_length = 5 -# expect { receiver.process }.to raise_error(Gitlab::EmailReceiver::InvalidPost) -# end -# end - -# describe "too_many_mentions.eml" do -# let!(:reply_key) { '636ca428858779856c226bb145ef4fad' } -# let!(:email_raw) { fixture_file("emails/too_many_mentions.eml") } - -# it "raises an InvalidPost error" do -# SiteSetting.max_mentions_per_post = 10 -# (1..11).each do |i| -# Fabricate(:user, username: "user#{i}").save -# end - -# expect { receiver.process }.to raise_error(Gitlab::EmailReceiver::InvalidPost) -# end -# end - -# describe "auto response email replies should not be accepted" do -# let!(:reply_key) { '636ca428858779856c226bb145ef4fad' } -# let!(:email_raw) { fixture_file("emails/auto_reply.eml") } -# it "raises a AutoGeneratedEmailError" do -# expect { receiver.process }.to raise_error(Gitlab::EmailReceiver::AutoGeneratedEmailError) -# end -# end - -# end - -# describe "posting reply to a closed topic" do -# let(:reply_key) { raise "Override this in a lower describe block" } -# let(:email_raw) { raise "Override this in a lower describe block" } -# let(:receiver) { Gitlab::EmailReceiver.new(email_raw) } -# let(:topic) { Fabricate(:topic, closed: true) } -# let(:post) { Fabricate(:post, topic: topic, post_number: 1) } -# let(:replying_user_email) { 'jake@adventuretime.ooo' } -# let(:replying_user) { Fabricate(:user, email: replying_user_email, trust_level: 2) } -# let(:email_log) { EmailLog.new(reply_key: reply_key, -# post: post, -# post_id: post.id, -# topic_id: topic.id, -# email_type: 'user_posted', -# user: replying_user, -# user_id: replying_user.id, -# to_address: replying_user_email -# ) } - -# before do -# email_log.save -# end - -# describe "should not create post" do -# let!(:reply_key) { '59d8df8370b7e95c5a49fbf86aeb2c93' } -# let!(:email_raw) { fixture_file("emails/valid_reply.eml") } -# it "raises a TopicClosedError" do -# expect { receiver.process }.to raise_error(Gitlab::EmailReceiver::TopicClosedError) -# end -# end -# end - -# describe "posting reply to a deleted topic" do -# let(:reply_key) { raise "Override this in a lower describe block" } -# let(:email_raw) { raise "Override this in a lower describe block" } -# let(:receiver) { Gitlab::EmailReceiver.new(email_raw) } -# let(:deleted_topic) { Fabricate(:deleted_topic) } -# let(:post) { Fabricate(:post, topic: deleted_topic, post_number: 1) } -# let(:replying_user_email) { 'jake@adventuretime.ooo' } -# let(:replying_user) { Fabricate(:user, email: replying_user_email, trust_level: 2) } -# let(:email_log) { EmailLog.new(reply_key: reply_key, -# post: post, -# post_id: post.id, -# topic_id: deleted_topic.id, -# email_type: 'user_posted', -# user: replying_user, -# user_id: replying_user.id, -# to_address: replying_user_email -# ) } - -# before do -# email_log.save -# end - -# describe "should not create post" do -# let!(:reply_key) { '59d8df8370b7e95c5a49fbf86aeb2c93' } -# let!(:email_raw) { fixture_file("emails/valid_reply.eml") } -# it "raises a TopicNotFoundError" do -# expect { receiver.process }.to raise_error(Gitlab::EmailReceiver::TopicNotFoundError) -# end -# end -# end - -# describe "posting a new topic" do -# let(:category_destination) { raise "Override this in a lower describe block" } -# let(:email_raw) { raise "Override this in a lower describe block" } -# let(:allow_strangers) { false } -# # ---- -# let(:receiver) { Gitlab::EmailReceiver.new(email_raw) } -# let(:user_email) { 'jake@adventuretime.ooo' } -# let(:user) { Fabricate(:user, email: user_email, trust_level: 2)} -# let(:category) { Fabricate(:category, email_in: category_destination, email_in_allow_strangers: allow_strangers) } - -# before do -# SiteSetting.email_in = true -# user.save -# category.save -# end - -# describe "too_short.eml" do -# let!(:category_destination) { 'incoming+amazing@appmail.adventuretime.ooo' } -# let(:email_raw) { -# fixture_file("emails/too_short.eml") -# .gsub("TO", category_destination) -# .gsub("FROM", user_email) -# .gsub("SUBJECT", "A long subject that passes the checks") -# } - -# it "does not create a topic if the post fails" do -# before_topic_count = Topic.count - -# expect { receiver.process }.to raise_error(Gitlab::EmailReceiver::InvalidPost) - -# expect(Topic.count).to eq(before_topic_count) -# end - -# end - -# end - -# def fill_email(mail, from, to, body = nil, subject = nil) -# result = mail.gsub("FROM", from).gsub("TO", to) -# if body -# result.gsub!(/Hey.*/m, body) -# end -# if subject -# result.sub!(/We .*/, subject) -# end -# result -# end - -# def process_email(opts) -# incoming_email = fixture_file("emails/valid_incoming.eml") -# email = fill_email(incoming_email, opts[:from], opts[:to], opts[:body], opts[:subject]) -# Gitlab::EmailReceiver.new(email).process -# end - -# describe "with a valid email" do -# let(:reply_key) { "59d8df8370b7e95c5a49fbf86aeb2c93" } -# let(:to) { SiteSetting.reply_by_email_address.gsub("%{reply_key}", reply_key) } - -# let(:valid_reply) { -# reply = fixture_file("emails/valid_reply.eml") -# to = SiteSetting.reply_by_email_address.gsub("%{reply_key}", reply_key) -# fill_email(reply, "test@test.com", to) -# } - -# let(:receiver) { Gitlab::EmailReceiver.new(valid_reply) } -# let(:post) { create_post } -# let(:user) { post.user } -# let(:email_log) { EmailLog.new(reply_key: reply_key, -# post_id: post.id, -# topic_id: post.topic_id, -# user_id: post.user_id, -# post: post, -# user: user, -# email_type: 'test', -# to_address: 'test@test.com' -# ) } -# let(:reply_body) { -# "I could not disagree more. I am obviously biased but adventure time is the -# greatest show ever created. Everyone should watch it. - -# - Jake out" } - -# describe "with an email log" do - -# it "extracts data" do -# expect{ receiver.process }.to raise_error(Gitlab::EmailReceiver::EmailLogNotFound) - -# email_log.save! -# receiver.process - -# expect(receiver.body).to eq(reply_body) -# expect(receiver.email_log).to eq(email_log) -# end - -# end - -# end -end -- cgit v1.2.1 From 0b401f2e94c5062d26887cdeda73341c1aae82a5 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 11:17:14 -0700 Subject: Fix a couple of whoopsy daisies. --- lib/gitlab/email/attachment_uploader.rb | 2 +- lib/gitlab/email/receiver.rb | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/email/attachment_uploader.rb b/lib/gitlab/email/attachment_uploader.rb index 0c0f50f2751..32cece8316b 100644 --- a/lib/gitlab/email/attachment_uploader.rb +++ b/lib/gitlab/email/attachment_uploader.rb @@ -1,6 +1,6 @@ module Gitlab module Email - module AttachmentUploader + class AttachmentUploader attr_accessor :message def initialize(message) diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index c46fce6afe2..7160af29089 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -76,7 +76,7 @@ module Gitlab end def add_attachments(reply) - attachments = AttachmentUploader.new(message).execute(project) + attachments = Email::AttachmentUploader.new(message).execute(sent_notification.project) attachments.each do |link| text = "[#{link[:alt]}](#{link[:url]})" @@ -84,6 +84,8 @@ module Gitlab reply << "\n\n#{text}" end + + reply end def create_note(reply) -- cgit v1.2.1 From 2f78b5e8afbf0024211bbd5bfe3a6c6f53b2421e Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 11:33:18 -0700 Subject: Make error class names more consistent. --- app/workers/email_receiver_worker.rb | 4 ++-- lib/gitlab/email/receiver.rb | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb index 4f3556bfc03..8f6c27ce4af 100644 --- a/app/workers/email_receiver_worker.rb +++ b/app/workers/email_receiver_worker.rb @@ -22,7 +22,7 @@ class EmailReceiverWorker reason = nil case e - when Gitlab::Email::Receiver::SentNotificationNotFound + when Gitlab::Email::Receiver::SentNotificationNotFoundError reason = "We couldn't figure out what the email is in reply to. Please create your comment through the web interface." when Gitlab::Email::Receiver::EmptyEmailError can_retry = true @@ -35,7 +35,7 @@ class EmailReceiverWorker reason = "You are not allowed to respond to the thread you are replying to. If you believe this is in error, contact a staff member." when Gitlab::Email::Receiver::NoteableNotFoundError reason = "The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member." - when Gitlab::Email::Receiver::InvalidNote + when Gitlab::Email::Receiver::InvalidNoteError can_retry = true reason = e.message else diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index 7160af29089..466e48b22e1 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -4,13 +4,13 @@ module Gitlab class Receiver class ProcessingError < StandardError; end class EmailUnparsableError < ProcessingError; end + class SentNotificationNotFoundError < ProcessingError; end class EmptyEmailError < ProcessingError; end + class AutoGeneratedEmailError < ProcessingError; end class UserNotFoundError < ProcessingError; end class UserNotAuthorizedError < ProcessingError; end class NoteableNotFoundError < ProcessingError; end - class AutoGeneratedEmailError < ProcessingError; end - class SentNotificationNotFound < ProcessingError; end - class InvalidNote < ProcessingError; end + class InvalidNoteError < ProcessingError; end def initialize(raw) @raw = raw @@ -23,7 +23,7 @@ module Gitlab end def execute - raise SentNotificationNotFound unless sent_notification + raise SentNotificationNotFoundError unless sent_notification raise EmptyEmailError if @raw.blank? @@ -53,7 +53,7 @@ module Gitlab message << "\n\n- #{error}" end - raise InvalidNote, message + raise InvalidNoteError, message end end -- cgit v1.2.1 From 991c9f6fdab46909fbbe652b1c88a6521a969c02 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 11:33:35 -0700 Subject: Test Email::AttachmentUploader. --- spec/lib/gitlab/email/attachment_uploader_spec.rb | 26 ++++++++++++ spec/lib/gitlab/email/reply_parser_spec.rb | 2 +- spec/services/projects/upload_service_spec.rb | 48 +++++++++++------------ 3 files changed, 51 insertions(+), 25 deletions(-) create mode 100644 spec/lib/gitlab/email/attachment_uploader_spec.rb diff --git a/spec/lib/gitlab/email/attachment_uploader_spec.rb b/spec/lib/gitlab/email/attachment_uploader_spec.rb new file mode 100644 index 00000000000..b54ee8aa72a --- /dev/null +++ b/spec/lib/gitlab/email/attachment_uploader_spec.rb @@ -0,0 +1,26 @@ +require "spec_helper" + +describe Gitlab::Email::AttachmentUploader do + def fixture_file(filename) + return '' if filename.blank? + file_path = File.expand_path(Rails.root + 'spec/fixtures/' + filename) + File.read(file_path) + end + + describe "#execute" do + let(:project) { build(:project) } + let(:message_raw) { fixture_file("emails/attachment.eml") } + let(:message) { Mail::Message.new(message_raw) } + + it "creates a post with an attachment" do + links = described_class.new(message).execute(project) + link = links.first + + expect(link).not_to be_nil + expect(link[:is_image]).to be_truthy + expect(link[:alt]).to eq("bricks") + expect(link[:url]).to include("/#{project.path_with_namespace}") + expect(link[:url]).to include("bricks.png") + end + end +end diff --git a/spec/lib/gitlab/email/reply_parser_spec.rb b/spec/lib/gitlab/email/reply_parser_spec.rb index 999515beb1c..32120d56cbc 100644 --- a/spec/lib/gitlab/email/reply_parser_spec.rb +++ b/spec/lib/gitlab/email/reply_parser_spec.rb @@ -8,7 +8,7 @@ describe Gitlab::Email::ReplyParser do File.read(file_path) end - describe 'self.parse_body' do + describe '#execute' do def test_parse_body(mail_string) described_class.new(Mail::Message.new(mail_string)).execute end diff --git a/spec/services/projects/upload_service_spec.rb b/spec/services/projects/upload_service_spec.rb index 7aa26857649..fa4ff6b01ad 100644 --- a/spec/services/projects/upload_service_spec.rb +++ b/spec/services/projects/upload_service_spec.rb @@ -13,13 +13,13 @@ describe Projects::UploadService do @link_to_file = upload_file(@project.repository, gif) end - it { expect(@link_to_file).to have_key('alt') } - it { expect(@link_to_file).to have_key('url') } - it { expect(@link_to_file).to have_key('is_image') } + it { expect(@link_to_file).to have_key(:alt) } + it { expect(@link_to_file).to have_key(:url) } + it { expect(@link_to_file).to have_key(:is_image) } it { expect(@link_to_file).to have_value('banana_sample') } - it { expect(@link_to_file['is_image']).to equal(true) } - it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") } - it { expect(@link_to_file['url']).to match('banana_sample.gif') } + it { expect(@link_to_file[:is_image]).to equal(true) } + it { expect(@link_to_file[:url]).to match("/#{@project.path_with_namespace}") } + it { expect(@link_to_file[:url]).to match('banana_sample.gif') } end context 'for valid png file' do @@ -29,13 +29,13 @@ describe Projects::UploadService do @link_to_file = upload_file(@project.repository, png) end - it { expect(@link_to_file).to have_key('alt') } - it { expect(@link_to_file).to have_key('url') } + it { expect(@link_to_file).to have_key(:alt) } + it { expect(@link_to_file).to have_key(:url) } it { expect(@link_to_file).to have_value('dk') } - it { expect(@link_to_file).to have_key('is_image') } - it { expect(@link_to_file['is_image']).to equal(true) } - it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") } - it { expect(@link_to_file['url']).to match('dk.png') } + it { expect(@link_to_file).to have_key(:is_image) } + it { expect(@link_to_file[:is_image]).to equal(true) } + it { expect(@link_to_file[:url]).to match("/#{@project.path_with_namespace}") } + it { expect(@link_to_file[:url]).to match('dk.png') } end context 'for valid jpg file' do @@ -44,13 +44,13 @@ describe Projects::UploadService do @link_to_file = upload_file(@project.repository, jpg) end - it { expect(@link_to_file).to have_key('alt') } - it { expect(@link_to_file).to have_key('url') } - it { expect(@link_to_file).to have_key('is_image') } + it { expect(@link_to_file).to have_key(:alt) } + it { expect(@link_to_file).to have_key(:url) } + it { expect(@link_to_file).to have_key(:is_image) } it { expect(@link_to_file).to have_value('rails_sample') } - it { expect(@link_to_file['is_image']).to equal(true) } - it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") } - it { expect(@link_to_file['url']).to match('rails_sample.jpg') } + it { expect(@link_to_file[:is_image]).to equal(true) } + it { expect(@link_to_file[:url]).to match("/#{@project.path_with_namespace}") } + it { expect(@link_to_file[:url]).to match('rails_sample.jpg') } end context 'for txt file' do @@ -59,13 +59,13 @@ describe Projects::UploadService do @link_to_file = upload_file(@project.repository, txt) end - it { expect(@link_to_file).to have_key('alt') } - it { expect(@link_to_file).to have_key('url') } - it { expect(@link_to_file).to have_key('is_image') } + it { expect(@link_to_file).to have_key(:alt) } + it { expect(@link_to_file).to have_key(:url) } + it { expect(@link_to_file).to have_key(:is_image) } it { expect(@link_to_file).to have_value('doc_sample.txt') } - it { expect(@link_to_file['is_image']).to equal(false) } - it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") } - it { expect(@link_to_file['url']).to match('doc_sample.txt') } + it { expect(@link_to_file[:is_image]).to equal(false) } + it { expect(@link_to_file[:url]).to match("/#{@project.path_with_namespace}") } + it { expect(@link_to_file[:url]).to match('doc_sample.txt') } end context 'for too large a file' do -- cgit v1.2.1 From e44936f3ed4799714fc36a342c1dbab1d50f0ffc Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 11:47:09 -0700 Subject: Test EmailReceiverWorker. --- spec/fixtures/emails/valid_incoming.eml | 10 +++--- spec/workers/email_receiver_worker_spec.rb | 51 ++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 spec/workers/email_receiver_worker_spec.rb diff --git a/spec/fixtures/emails/valid_incoming.eml b/spec/fixtures/emails/valid_incoming.eml index 5e8db53319e..409711ab417 100644 --- a/spec/fixtures/emails/valid_incoming.eml +++ b/spec/fixtures/emails/valid_incoming.eml @@ -1,11 +1,11 @@ -Return-Path: +Return-Path: Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 -Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 -Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 +Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 Date: Thu, 13 Jun 2013 17:03:48 -0400 -From: Jake the Dog -To: +From: Jake the Dog +To: Message-ID: Subject: We should have a post-by-email-feature. Mime-Version: 1.0 diff --git a/spec/workers/email_receiver_worker_spec.rb b/spec/workers/email_receiver_worker_spec.rb new file mode 100644 index 00000000000..109a7b3eb1d --- /dev/null +++ b/spec/workers/email_receiver_worker_spec.rb @@ -0,0 +1,51 @@ +require "spec_helper" + +describe EmailReceiverWorker do + def fixture_file(filename) + return '' if filename.blank? + file_path = File.expand_path(Rails.root + 'spec/fixtures/' + filename) + File.read(file_path) + end + + let(:raw_message) { fixture_file('emails/valid_incoming.eml') } + + context "when reply by email is enabled" do + before do + allow(Gitlab::ReplyByEmail).to receive(:enabled?).and_return(true) + end + + it "calls the email receiver" do + expect(Gitlab::Email::Receiver).to receive(:new).with(raw_message).and_call_original + expect_any_instance_of(Gitlab::Email::Receiver).to receive(:execute) + + described_class.new.perform(raw_message) + end + + context "when an error occurs" do + before do + allow_any_instance_of(Gitlab::Email::Receiver).to receive(:execute).and_raise(Gitlab::Email::Receiver::EmptyEmailError) + end + + it "sends out a rejection email" do + described_class.new.perform(raw_message) + + email = ActionMailer::Base.deliveries.last + expect(email).not_to be_nil + expect(email.to).to eq(["from@example.com"]) + expect(email.subject).to include("Rejected") + end + end + end + + context "when reply by email is disabled" do + before do + allow(Gitlab::ReplyByEmail).to receive(:enabled?).and_return(false) + end + + it "doesn't call the email receiver" do + expect(Gitlab::Email::Receiver).not_to receive(:new) + + described_class.new.perform(raw_message) + end + end +end -- cgit v1.2.1 From 8ec5fb138dde9937814ac138352177399d3e776d Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 12:17:59 -0700 Subject: Test Gitlab::Email::Receiver. --- lib/gitlab/email/receiver.rb | 18 ++--- spec/lib/gitlab/email/receiver_spec.rb | 135 +++++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+), 9 deletions(-) create mode 100644 spec/lib/gitlab/email/receiver_spec.rb diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index 466e48b22e1..17b8339edcd 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -16,17 +16,11 @@ module Gitlab @raw = raw end - def message - @message ||= Mail::Message.new(@raw) - rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError => e - raise EmailUnparsableError, e - end - def execute - raise SentNotificationNotFoundError unless sent_notification - raise EmptyEmailError if @raw.blank? + raise SentNotificationNotFoundError unless sent_notification + raise AutoGeneratedEmailError if message.header.to_s =~ /auto-(generated|replied)/ author = sent_notification.recipient @@ -35,7 +29,7 @@ module Gitlab project = sent_notification.project - raise UserNotAuthorizedError unless author.can?(:create_note, project) + raise UserNotAuthorizedError unless project && author.can?(:create_note, project) raise NoteableNotFoundError unless sent_notification.noteable @@ -59,6 +53,12 @@ module Gitlab private + def message + @message ||= Mail::Message.new(@raw) + rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError => e + raise EmailUnparsableError, e + end + def reply_key reply_key = nil message.to.each do |address| diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb new file mode 100644 index 00000000000..65f3ceb5def --- /dev/null +++ b/spec/lib/gitlab/email/receiver_spec.rb @@ -0,0 +1,135 @@ +require "spec_helper" + +describe Gitlab::Email::Receiver do + def fixture_file(filename) + return '' if filename.blank? + file_path = File.expand_path(Rails.root + 'spec/fixtures/' + filename) + File.read(file_path) + end + + before do + allow(Gitlab.config.reply_by_email).to receive(:enabled).and_return(true) + allow(Gitlab.config.reply_by_email).to receive(:address).and_return("reply+%{reply_key}@appmail.adventuretime.ooo") + end + + let(:reply_key) { "59d8df8370b7e95c5a49fbf86aeb2c93" } + let(:email_raw) { fixture_file('emails/valid_reply.eml') } + + let(:project) { create(:project, :public) } + let(:noteable) { create(:issue, project: project) } + let(:user) { create(:user) } + let!(:sent_notification) { SentNotification.record(noteable, user.id, reply_key) } + + let(:receiver) { described_class.new(email_raw) } + + context "when the recipient address doesn't include a reply key" do + let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(reply_key, "") } + + it "raises a SentNotificationNotFoundError" do + expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::SentNotificationNotFoundError) + end + end + + context "when no sent notificiation for the reply key could be found" do + let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(reply_key, "nope") } + + it "raises a SentNotificationNotFoundError" do + expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::SentNotificationNotFoundError) + end + end + + context "when the email is blank" do + let(:email_raw) { "" } + + it "raises an EmptyEmailError" do + expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::EmptyEmailError) + end + end + + context "when the email was auto generated" do + let!(:reply_key) { '636ca428858779856c226bb145ef4fad' } + let!(:email_raw) { fixture_file("emails/auto_reply.eml") } + + it "raises an AutoGeneratedEmailError" do + expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::AutoGeneratedEmailError) + end + end + + context "when the user could not be found" do + before do + user.destroy + end + + it "raises a UserNotFoundError" do + expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::UserNotFoundError) + end + end + + context "when the user is not authorized to create a note" do + before do + project.update_attribute(:visibility_level, Project::PRIVATE) + end + + it "raises a UserNotAuthorizedError" do + expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::UserNotAuthorizedError) + end + end + + context "when the noteable could not be found" do + before do + noteable.destroy + end + + it "raises a NoteableNotFoundError" do + expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::NoteableNotFoundError) + end + end + + context "when the reply is blank" do + let!(:email_raw) { fixture_file("emails/no_content_reply.eml") } + + it "raises an EmptyEmailError" do + expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::EmptyEmailError) + end + end + + context "when the note could not be saved" do + before do + allow_any_instance_of(Note).to receive(:persisted?).and_return(false) + end + + it "raises an InvalidNoteError" do + expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::InvalidNoteError) + end + end + + context "when everything is fine" do + before do + allow_any_instance_of(Gitlab::Email::AttachmentUploader).to receive(:execute).and_return( + [ + { + url: "uploads/image.png", + is_image: true, + alt: "image" + } + ] + ) + end + + it "creates a comment" do + expect { receiver.execute }.to change { noteable.notes.count }.by(1) + note = noteable.notes.last + + expect(note.author).to eq(sent_notification.recipient) + expect(note.note).to include("I could not disagree more.") + end + + it "adds all attachments" do + receiver.execute + + note = noteable.notes.last + + expect(note.note).to include("![image](uploads/image.png)") + end + end +end -- cgit v1.2.1 From 2b5a2b8f398aa6af0a24c40cd6586eae76c85a3f Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 12:29:03 -0700 Subject: Removed unused fixtures. --- spec/fixtures/emails/boundary.eml | 61 --------- spec/fixtures/emails/multiple_destinations.eml | 40 ------ spec/fixtures/emails/paragraphs.cooked | 7 - spec/fixtures/emails/previous.eml | 38 ------ spec/fixtures/emails/previous_replies.eml | 180 ------------------------- spec/fixtures/emails/too_many_mentions.eml | 31 ----- spec/fixtures/emails/valid_incoming.cooked | 5 - spec/fixtures/emails/valid_incoming.eml | 25 ---- spec/fixtures/emails/valid_reply.cooked | 4 - spec/fixtures/emails/via_line.eml | 25 ---- spec/lib/gitlab/email/receiver_spec.rb | 2 +- 11 files changed, 1 insertion(+), 417 deletions(-) delete mode 100644 spec/fixtures/emails/boundary.eml delete mode 100644 spec/fixtures/emails/multiple_destinations.eml delete mode 100644 spec/fixtures/emails/paragraphs.cooked delete mode 100644 spec/fixtures/emails/previous.eml delete mode 100644 spec/fixtures/emails/previous_replies.eml delete mode 100644 spec/fixtures/emails/too_many_mentions.eml delete mode 100644 spec/fixtures/emails/valid_incoming.cooked delete mode 100644 spec/fixtures/emails/valid_incoming.eml delete mode 100644 spec/fixtures/emails/valid_reply.cooked delete mode 100644 spec/fixtures/emails/via_line.eml diff --git a/spec/fixtures/emails/boundary.eml b/spec/fixtures/emails/boundary.eml deleted file mode 100644 index 1250fe498b0..00000000000 --- a/spec/fixtures/emails/boundary.eml +++ /dev/null @@ -1,61 +0,0 @@ - -MIME-Version: 1.0 -Received: by 10.64.14.41 with HTTP; Wed, 19 Jun 2013 06:29:41 -0700 (PDT) -In-Reply-To: <51c19490e928a_13442dd8ae892548@tree.mail> -References: <51c19490e928a_13442dd8ae892548@tree.mail> -Date: Wed, 19 Jun 2013 09:29:41 -0400 -Delivered-To: finn@adventuretime.ooo -Message-ID: -Subject: Re: [Adventure Time] jake mentioned you in 'peppermint butler is - missing' -From: Finn the Human -To: jake via Adventure Time -Content-Type: multipart/alternative; boundary=001a11c206a073876a04df81d2a9 - ---001a11c206a073876a04df81d2a9 -Content-Type: text/plain; charset=ISO-8859-1 - -I'll look into it, thanks! - - -On Wednesday, June 19, 2013, jake via Discourse wrote: - -> jake mentioned you in 'peppermint butler is missing' on Adventure -> Time: -> ------------------------------ -> -> yeah, just noticed this cc @jake -> ------------------------------ -> -> Please visit this link to respond: -> http://adventuretime.ooo/t/peppermint-butler-is-missing/7628/2 -> -> To unsubscribe from these emails, visit your user preferences -> . -> - ---001a11c206a073876a04df81d2a9 -Content-Type: text/html; charset=ISO-8859-1 -Content-Transfer-Encoding: quoted-printable - -I'll look into it, thanks!

On Wednesday, June 19, 2= -013, jake via Adventure Time wrote:

sa= -m mentioned you in 'Duplicate message are shown in profile' on Adve= -nture Time

- - -

yeah, just noticed this cc @eviltrout

- -

Please visit this link to respond: http= -://adventuretime.ooo/t/peppermint-butler-is-missing/7628/2 - - -

To unsubscribe from these emails, visit your user preferences.

-
- ---001a11c206a073876a04df81d2a9-- diff --git a/spec/fixtures/emails/multiple_destinations.eml b/spec/fixtures/emails/multiple_destinations.eml deleted file mode 100644 index 6d31bbf1974..00000000000 --- a/spec/fixtures/emails/multiple_destinations.eml +++ /dev/null @@ -1,40 +0,0 @@ -Return-Path: -Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 -Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 -Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 -Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 -Date: Thu, 13 Jun 2013 17:03:48 -0400 -From: Jake the Dog -To: finn@adventuretime.ooo, reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo -Message-ID: -Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' -Mime-Version: 1.0 -Content-Type: text/plain; - charset=ISO-8859-1 -Content-Transfer-Encoding: 7bit -X-Sieve: CMU Sieve 2.2 -X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, - 13 Jun 2013 14:03:48 -0700 (PDT) -X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 - -I could not disagree more. I am obviously biased but adventure time is the -greatest show ever created. Everyone should watch it. - -- Jake out - - -On Sun, Jun 9, 2013 at 1:39 PM, eviltrout via Discourse Meta - wrote: -> -> -> -> eviltrout posted in 'Adventure Time Sux' on Discourse Meta: -> -> --- -> hey guys everyone knows adventure time sucks! -> -> --- -> Please visit this link to respond: http://localhost:3000/t/adventure-time-sux/1234/3 -> -> To unsubscribe from these emails, visit your [user preferences](http://localhost:3000/user_preferences). -> \ No newline at end of file diff --git a/spec/fixtures/emails/paragraphs.cooked b/spec/fixtures/emails/paragraphs.cooked deleted file mode 100644 index da83260e09c..00000000000 --- a/spec/fixtures/emails/paragraphs.cooked +++ /dev/null @@ -1,7 +0,0 @@ -

Is there any reason the old candy can't be be kept in silos while the new candy -is imported into new silos?

- -

The thing about candy is it stays delicious for a long time -- we can just keep -it there without worrying about it too much, imo.

- -

Thanks for listening.

\ No newline at end of file diff --git a/spec/fixtures/emails/previous.eml b/spec/fixtures/emails/previous.eml deleted file mode 100644 index 24ac5a63deb..00000000000 --- a/spec/fixtures/emails/previous.eml +++ /dev/null @@ -1,38 +0,0 @@ - -Delivered-To: discourse-reply+cd480e301683c9902891f15968bf07a5@discourse.org -Received: by 10.194.216.104 with SMTP id op8csp80593wjc; - Wed, 24 Jul 2013 07:59:14 -0700 (PDT) -Return-Path: -References: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> -From: Walter White -In-Reply-To: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> -Mime-Version: 1.0 (1.0) -Date: Wed, 24 Jul 2013 15:59:10 +0100 -Message-ID: <4597127794206131679@unknownmsgid> -Subject: Re: [Discourse] new reply to your post in 'Crystal Blue' -To: walter via Discourse -Content-Type: multipart/alternative; boundary=001a11c20edc15a39304e2432790 - -This will not include the previous discussion that is present in this email. - ------------------------------ -Previous discussion -skylerwhite July 24 - -This is a reply. - fring July 24 - -This is an older reply. - hank_schrader July 24 - -Of course another reply here. - walterwhite July 24 - - ------------------------------ - -To respond, reply to this email or visit -http://discourse.org/t/crystal-blue/5043/10in -your browser. - -To unsubscribe from these emails, visit your user -preferences -. diff --git a/spec/fixtures/emails/previous_replies.eml b/spec/fixtures/emails/previous_replies.eml deleted file mode 100644 index 3fd74482c07..00000000000 --- a/spec/fixtures/emails/previous_replies.eml +++ /dev/null @@ -1,180 +0,0 @@ -Delivered-To: reply@discourse.org -MIME-Version: 1.0 -In-Reply-To: -References: - -Date: Fri, 28 Nov 2014 12:55:32 -0800 -Subject: Re: [Discourse Meta] [Lounge] Testing default email replies -From: Walter White -To: Discourse Meta -Content-Type: multipart/alternative; boundary=001a1137c9285318bb0508f17bc5 - ---001a1137c9285318bb0508f17bc5 -Content-Type: text/plain; charset=UTF-8 - -### this is a reply from iOS Gmail app - -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over -the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown -fox jumps over the lazy dog. The quick brown fox jumps over the lazy -dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps -over the lazy dog. - -This is **bold** text in Markdown. - -This is a link to http://example.com - -On Friday, November 28, 2014, Arpit Jalan wrote: - -> techAPJ -> November 28 -> -> Test reply. -> -> First paragraph. -> -> Second paragraph. -> -> To respond, reply to this email or visit -> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in -> your browser. -> ------------------------------ -> Previous Replies codinghorror -> -> November 28 -> -> We're testing the latest GitHub email processing library which we are -> integrating now. -> -> https://github.com/github/email_reply_parser -> -> Go ahead and reply to this topic and I'll reply from various email clients -> for testing. -> ------------------------------ -> -> To respond, reply to this email or visit -> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in -> your browser. -> -> To unsubscribe from these emails, visit your user preferences -> . -> - ---001a1137c9285318bb0508f17bc5 -Content-Type: text/html; charset=UTF-8 -Content-Transfer-Encoding: quoted-printable - -### this is a reply from iOS Gmail app

The quick brown f= -ox jumps over the lazy dog.=C2=A0The quick brown fox jumps over the lazy dog.=C2=A0The quic= -k brown fox jumps over the lazy dog.=C2=A0The quick brown fox jumps over th= -e lazy dog.=C2=A0The quick brown fox jumps over the lazy dog.=C2=A0The quic= -k brown fox jumps over the lazy dog.=C2=A0The quick brown fox jumps over th= -e lazy dog.=C2=A0

This is **bold** text in Markdown.

This is a link to http://example.com

On Friday, November 28, 2014, Arpit Jalan <
info@discourse.org> wrote:
- - - - - - - - - - - -
- - - techAPJ
- November 28 -
-

Test reply.

- -

First paragraph.

- -

Second paragraph.

-
- - -
-

To respond, reply to this email or visit https:/= -/meta.discourse.org/t/testing-default-email-replies/22638/3 in your bro= -wser.

-
-
-

Previous Replies

- - - - - - - - - - - -
- - - codinghorror - November 28 -
-

We're testing the latest GitHub emai= -l processing library which we are integrating now.

- -

https://github.com/github/email_reply_parser

- -

Go ahead and reply to this topic and I&#= -39;ll reply from various email clients for testing.

-
- - -
- -
-

To respond, reply to this email or visit https://met= -a.discourse.org/t/testing-default-email-replies/22638/3 in your browser= -.

-
-
-

To unsubscribe from these emails, visit your user preferences.

-
-
-
- ---001a1137c9285318bb0508f17bc5-- diff --git a/spec/fixtures/emails/too_many_mentions.eml b/spec/fixtures/emails/too_many_mentions.eml deleted file mode 100644 index 9cc7b75c94f..00000000000 --- a/spec/fixtures/emails/too_many_mentions.eml +++ /dev/null @@ -1,31 +0,0 @@ -Return-Path: -Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 -Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 -Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 -Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 -Date: Thu, 13 Jun 2013 17:03:48 -0400 -From: Jake the Dog -To: reply+636ca428858779856c226bb145ef4fad@appmail.adventuretime.ooo -Message-ID: -Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' -Mime-Version: 1.0 -Content-Type: text/plain; - charset=ISO-8859-1 -Content-Transfer-Encoding: 7bit -X-Sieve: CMU Sieve 2.2 -X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, - 13 Jun 2013 14:03:48 -0700 (PDT) -X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 - - -@user1 -@user2 -@user3 -@user4 -@user5 -@user6 -@user7 -@user8 -@user9 -@user10 -@user11 \ No newline at end of file diff --git a/spec/fixtures/emails/valid_incoming.cooked b/spec/fixtures/emails/valid_incoming.cooked deleted file mode 100644 index 2bf35825373..00000000000 --- a/spec/fixtures/emails/valid_incoming.cooked +++ /dev/null @@ -1,5 +0,0 @@ -

Hey folks,

- -

I was thinking. Wouldn't it be great if we could post topics via email? Yes it would!

- -

Jakie

diff --git a/spec/fixtures/emails/valid_incoming.eml b/spec/fixtures/emails/valid_incoming.eml deleted file mode 100644 index 409711ab417..00000000000 --- a/spec/fixtures/emails/valid_incoming.eml +++ /dev/null @@ -1,25 +0,0 @@ -Return-Path: -Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 -Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 -Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 -Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 -Date: Thu, 13 Jun 2013 17:03:48 -0400 -From: Jake the Dog -To: -Message-ID: -Subject: We should have a post-by-email-feature. -Mime-Version: 1.0 -Content-Type: text/plain; - charset=ISO-8859-1 -Content-Transfer-Encoding: 7bit -X-Sieve: CMU Sieve 2.2 -X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, - 13 Jun 2013 14:03:48 -0700 (PDT) -X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 - -Hey folks, - -I was thinking. Wouldn't it be great if we could post topics via email? Yes it would! - -Jakie - diff --git a/spec/fixtures/emails/valid_reply.cooked b/spec/fixtures/emails/valid_reply.cooked deleted file mode 100644 index 4bce79ad12a..00000000000 --- a/spec/fixtures/emails/valid_reply.cooked +++ /dev/null @@ -1,4 +0,0 @@ -

I could not disagree more. I am obviously biased but adventure time is the -greatest show ever created. Everyone should watch it.

- -
  • Jake out
diff --git a/spec/fixtures/emails/via_line.eml b/spec/fixtures/emails/via_line.eml deleted file mode 100644 index 0b2947ff954..00000000000 --- a/spec/fixtures/emails/via_line.eml +++ /dev/null @@ -1,25 +0,0 @@ - -Delivered-To: discourse-reply+cd480e301683c9902891f15968bf07a5@discourse.org -Received: by 10.194.216.104 with SMTP id op8csp80593wjc; - Wed, 24 Jul 2013 07:59:14 -0700 (PDT) -Return-Path: -References: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> -From: Walter White -In-Reply-To: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> -Mime-Version: 1.0 (1.0) -Date: Wed, 24 Jul 2013 15:59:10 +0100 -Message-ID: <4597127794206131679@unknownmsgid> -Subject: Re: [Discourse] new reply to your post in 'Crystal Blue' -To: walter via Discourse -Content-Type: multipart/alternative; boundary=001a11c20edc15a39304e2432790 - -Hello this email has content! - -codinghorror via Discourse wrote: -> [codinghorror] codinghorror -> -> August 7 -> -> It wouldn't be great at the moment for 100% email, since there's no -> way to be notified of new topics via email. (you can get notified of -> new replies to your posts and topics via email, and reply to them.) diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb index 65f3ceb5def..3d434aeaf91 100644 --- a/spec/lib/gitlab/email/receiver_spec.rb +++ b/spec/lib/gitlab/email/receiver_spec.rb @@ -31,7 +31,7 @@ describe Gitlab::Email::Receiver do end context "when no sent notificiation for the reply key could be found" do - let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(reply_key, "nope") } + let(:email_raw) { fixture_file('emails/wrong_reply_key.eml') } it "raises a SentNotificationNotFoundError" do expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::SentNotificationNotFoundError) -- cgit v1.2.1 From e1eb09dd0e376768eb73d660b64343d7b3ad8fad Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 12:33:33 -0700 Subject: Remove more unused fixtures. --- spec/fixtures/emails/big5.eml | 26 ------ spec/fixtures/emails/bottom_reply.eml | 160 ---------------------------------- spec/fixtures/emails/empty.eml | 21 ----- spec/fixtures/emails/hebrew.eml | 17 ---- spec/fixtures/emails/too_short.eml | 21 ----- 5 files changed, 245 deletions(-) delete mode 100644 spec/fixtures/emails/big5.eml delete mode 100644 spec/fixtures/emails/bottom_reply.eml delete mode 100644 spec/fixtures/emails/empty.eml delete mode 100644 spec/fixtures/emails/hebrew.eml delete mode 100644 spec/fixtures/emails/too_short.eml diff --git a/spec/fixtures/emails/big5.eml b/spec/fixtures/emails/big5.eml deleted file mode 100644 index 4a7b2082486..00000000000 --- a/spec/fixtures/emails/big5.eml +++ /dev/null @@ -1,26 +0,0 @@ - -Delivered-To: discourse-reply+cd480e301683c9902891f15968bf07a5@discourse.org -Received: by 10.194.216.104 with SMTP id op8csp80593wjc; - Wed, 24 Jul 2013 07:59:14 -0700 (PDT) -Return-Path: -References: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> -From: Walter White -In-Reply-To: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> -Mime-Version: 1.0 (1.0) -Date: Wed, 24 Jul 2013 15:59:10 +0100 -Message-ID: <4597127794206131679@unknownmsgid> -Subject: Re: [Discourse] new reply to your post in 'Crystal Blue' -To: walter via Discourse -Content-Type: multipart/alternative; boundary=20cf301cc47ada510404f040b262 - ---20cf301cc47ada510404f040b262 -Content-Type: text/plain; charset=Big5 -Content-Transfer-Encoding: base64 - -tv2hSafapFe5cbX4pEahSQ0K ---20cf301cc47ada510404f040b262 -Content-Type: text/html; charset=Big5 -Content-Transfer-Encoding: base64 - -PGRpdiBkaXI9Imx0ciI+tv2hSafapFe5cbX4pEahSTxicj48L2Rpdj4NCg== ---20cf301cc47ada510404f040b262-- diff --git a/spec/fixtures/emails/bottom_reply.eml b/spec/fixtures/emails/bottom_reply.eml deleted file mode 100644 index 5fc992971fc..00000000000 --- a/spec/fixtures/emails/bottom_reply.eml +++ /dev/null @@ -1,160 +0,0 @@ -Received: by 10.107.19.29 with SMTP id b29csp111716ioj; - Wed, 30 Jul 2014 17:52:05 -0700 (PDT) -X-Received: by 10.194.238.6 with SMTP id vg6mr11340975wjc.24.1406767925330; - Wed, 30 Jul 2014 17:52:05 -0700 (PDT) -Received: from localhost (localhost [127.0.0.1]) - by bendel.debian.org (Postfix) with QMQP - id 18F5C417; Thu, 31 Jul 2014 00:52:04 +0000 (UTC) -Old-Return-Path: -X-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on bendel.debian.org -X-Spam-Level: -X-Spam-Status: No, score=-25.9 required=4.0 tests=FOURLA,LDOSUBSCRIBER, - LDO_WHITELIST,MURPHY_DEBIAN_MESSAGE,PGPSIGNATURE autolearn=unavailable - version=3.3.2 -X-Original-To: lists-debian-ctte@bendel.debian.org -Delivered-To: lists-debian-ctte@bendel.debian.org -Received: from localhost (localhost [127.0.0.1]) - by bendel.debian.org (Postfix) with ESMTP id CE6CDEE - for ; Thu, 31 Jul 2014 00:51:52 +0000 (UTC) -X-Virus-Scanned: at lists.debian.org with policy bank en-lt -X-Amavis-Spam-Status: No, score=-11.9 tagged_above=-10000 required=5.3 - tests=[BAYES_00=-2, FOURLA=0.1, LDO_WHITELIST=-5, PGPSIGNATURE=-5] - autolearn=ham -Received: from bendel.debian.org ([127.0.0.1]) - by localhost (lists.debian.org [127.0.0.1]) (amavisd-new, port 2525) - with ESMTP id SB451DwGZCOe for ; - Thu, 31 Jul 2014 00:51:47 +0000 (UTC) -X-policyd-weight: NOT_IN_SBL_XBL_SPAMHAUS=-1.5 NOT_IN_BL_NJABL=-1.5 CL_IP_EQ_HELO_IP=-2 (check from: .debian. - helo: .becquer.dodds. - helo-domain: .dodds.) FROM/MX_MATCHES_NOT_HELO(DOMAIN)=0; rate: -5 -Received: from becquer.dodds.net (becquer.dodds.net [207.224.24.209]) - by bendel.debian.org (Postfix) with ESMTP id 8E89A2B4 - for ; Thu, 31 Jul 2014 00:51:47 +0000 (UTC) -Received: from virgil.dodds.net (unknown [192.168.15.59]) - by becquer.dodds.net (Postfix) with ESMTPA id 9B0A9256EB - for ; Wed, 30 Jul 2014 17:51:19 -0700 (PDT) -Received: by virgil.dodds.net (Postfix, from userid 1000) - id 942FB60199; Wed, 30 Jul 2014 17:51:15 -0700 (PDT) -Date: Wed, 30 Jul 2014 17:51:15 -0700 -From: Jake -To: incoming+amazing@appmail.adventuretime.ooo -Subject: Re: Next Debian CTTE IRC Meeting at date -d'Thu Jul 31 17:00:00 UTC - 2014' -Message-ID: <20140731005115.GA19044@virgil.dodds.net> -Mail-Followup-To: debian-ctte@lists.debian.org -References: <20140730213924.GA12356@teltox.donarmstrong.com> -MIME-Version: 1.0 -Content-Type: multipart/signed; micalg=pgp-sha256; - protocol="application/pgp-signature"; boundary="qMm9M+Fa2AknHoGS" -Content-Disposition: inline -In-Reply-To: <20140730213924.GA12356@teltox.donarmstrong.com> -User-Agent: Mutt/1.5.23 (2014-03-12) -X-Debian-Message: Signature check passed for Debian member -X-Rc-Virus: 2007-09-13_01 -X-Rc-Spam: 2008-11-04_01 -Resent-Message-ID: -Resent-From: debian-ctte@lists.debian.org -X-Mailing-List: archive/latest/4791 -X-Loop: debian-ctte@lists.debian.org -List-Id: -List-Post: -List-Help: -List-Subscribe: -List-Unsubscribe: -Precedence: list -Resent-Sender: debian-ctte-request@lists.debian.org -Resent-Date: Thu, 31 Jul 2014 00:52:04 +0000 (UTC) - - ---qMm9M+Fa2AknHoGS -Content-Type: text/plain; charset=us-ascii -Content-Disposition: inline -Content-Transfer-Encoding: quoted-printable - -On Wed, Jul 30, 2014 at 02:39:24PM -0700, Don Armstrong wrote: -> The next Debian CTTE IRC meeting is at=20 - -> date -d 'Thu Jul 31 17:00:00 UTC 2014' on irc.debian.org in -> #debian-ctte. - -> The current meeting agenda is here, and more up-to-date ones may be -> found in the git repository. - -> #startmeeting - -> #topic Who is here? - -> #topic Next Meeting? - -> #topic #717076 Decide between libjpeg-turbo and libjpeg8 et al. - -This has been voted on; should probably be removed from the agenda, someone -just needs to confirm the vote results and get it on the website. (AIUI the -archive has already begun moving on accordingly.) - -> #topic #636783 constitution: super-majority bug - -> #topic #636783 constitution: casting vote - -> #topic #636783 constitution: minimum discussion period - -> #topic #636783 constitution: TC member retirement/rollover - -> #topic #636783 constitution: TC chair retirement/rollover - -> #topic #681419 Depends: foo | foo-nonfree - -> #topic #741573 menu systems and mime-support - -> #topic #746715 init system fallout - -Also voted and just needs to be confirmed. - -> #topic #750135 Maintainer of aptitude package ->=20 -> #topic #752400 Advice on util-linux - -This has been closed by mutual agreement of the people involved and doesn't -require any action from the TC. Removed from the agenda. - -There's also bug #744246, which was assigned to the TC at my request but -without any preamble so it may have escaped notice. However, that situation -has been evolving constructively among the related parties (apparently, an -informal comment from a member of the release team was mistaken for a -release team position, so that's now being revisited), so I don't believe -this is anything we need to put on the agenda for tomorrow despite being on -the open bug list. - ---=20 -Steve Langasek Give me a lever long enough and a Free OS -Debian Developer to set it on, and I can move the world. -Ubuntu Developer http://www.debian.org/ -slangasek@ubuntu.com vorlon@debian.org - ---qMm9M+Fa2AknHoGS -Content-Type: application/pgp-signature; name="signature.asc" -Content-Description: Digital signature - ------BEGIN PGP SIGNATURE----- -Version: GnuPG v1 - -iQIcBAEBCAAGBQJT2ZMDAAoJEFaNMPMhshM9GLsP/244S3wtYZEeVgJWIdB5PE0A -sZVezEA692y++/0oVZVecwV67yBOyfSjPPetdAph2UDMRtfurwxfj2BkbOFA2+Y6 -++MErbmC3V7IGpd/L/fFGdXgvMQT2MNBpw0fnMA7bLpNjCvoj+Hr1HXRUcWoJSlj -WmHWwWSTVRcHg8a3iWYJzY6XfLyEEgHlahrlKvJExsTx/9mc1qg7g8KGdnhzHFBl -ttdH2fxpAk/624dReCcw5RKmOLfZ1HsEl9XcVe1cb4K+MDaQiXmoEK5v3xaNz1tS -NK5v2D5gDs229zoxKzQnnzOPLHxqI5E0L9PpI/mu4T9z7H2bHR3U5BvhnT99t5uw -ydf2cZNGY0uFCV3Rvn07BfAIW5WSXhOfN/5IymRKmdhjsTiwZ/wFjFrK8tVjtERu -yeyA7RIYiblGCEKYIYLWSxhoXeEdmAdfp6EA2/IA1CpgMB+ZdSfaeMgFY7xosgmG -ax3NTnaKyhr1QEUJ2gjAwHnKjuGbRVDAinYrSvP0o8Bh9sAs2BN2negWBCZVwwkN -S9hWTjVqsBmpaPOt5SEDwDo9O9dfzkmaamDsxOuUEz9F7v5jYg0mxA/WbogGty9R -vOMKxdxRkzflL/CferVbkzL/EkZRDfWDp9SleZggrpz7miiNDbS7jdRzJ4EttmJ8 -gHBAVrOzcnbIPOIkk9pw -=KXIu ------END PGP SIGNATURE----- - ---qMm9M+Fa2AknHoGS-- - - --- -To UNSUBSCRIBE, email to debian-ctte-REQUEST@lists.debian.org -with a subject of "unsubscribe". Trouble? Contact listmaster@lists.debian.org -Archive: https://lists.debian.org/20140731005115.GA19044@virgil.dodds.net diff --git a/spec/fixtures/emails/empty.eml b/spec/fixtures/emails/empty.eml deleted file mode 100644 index 85bebc66245..00000000000 --- a/spec/fixtures/emails/empty.eml +++ /dev/null @@ -1,21 +0,0 @@ -Return-Path: -Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 -Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 -Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 -Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 -Date: Thu, 13 Jun 2013 17:03:48 -0400 -From: Jake the Dog -To: reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo -Message-ID: -Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' -Mime-Version: 1.0 -Content-Type: text/plain; - charset=ISO-8859-1 -Content-Transfer-Encoding: 7bit -X-Sieve: CMU Sieve 2.2 -X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, - 13 Jun 2013 14:03:48 -0700 (PDT) -X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 - - - diff --git a/spec/fixtures/emails/hebrew.eml b/spec/fixtures/emails/hebrew.eml deleted file mode 100644 index f4c8f01adc3..00000000000 --- a/spec/fixtures/emails/hebrew.eml +++ /dev/null @@ -1,17 +0,0 @@ - -Delivered-To: discourse-reply+cd480e301683c9902891f15968bf07a5@discourse.org -Received: by 10.194.216.104 with SMTP id op8csp80593wjc; - Wed, 24 Jul 2013 07:59:14 -0700 (PDT) -Return-Path: -References: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> -From: Walter White -In-Reply-To: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> -Mime-Version: 1.0 (1.0) -Date: Wed, 24 Jul 2013 15:59:10 +0100 -Message-ID: <4597127794206131679@unknownmsgid> -Subject: Re: [Discourse] new reply to your post in 'Crystal Blue' -To: walter via Discourse -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: base64 - -16nXnNeV150= \ No newline at end of file diff --git a/spec/fixtures/emails/too_short.eml b/spec/fixtures/emails/too_short.eml deleted file mode 100644 index 54fed0f98c5..00000000000 --- a/spec/fixtures/emails/too_short.eml +++ /dev/null @@ -1,21 +0,0 @@ -Return-Path: -Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 -Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 -Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 -Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 -Date: Thu, 13 Jun 2013 17:03:48 -0400 -From: Jake the Dog -To: TO -Message-ID: -Subject: SUBJECT -Mime-Version: 1.0 -Content-Type: text/plain; - charset=ISO-8859-1 -Content-Transfer-Encoding: 7bit -X-Sieve: CMU Sieve 2.2 -X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, - 13 Jun 2013 14:03:48 -0700 (PDT) -X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 - - -+1 \ No newline at end of file -- cgit v1.2.1 From ac268674edfdaa5b8232f9875481410fc5112f60 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 12:34:45 -0700 Subject: Update spec. --- spec/workers/email_receiver_worker_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/workers/email_receiver_worker_spec.rb b/spec/workers/email_receiver_worker_spec.rb index 109a7b3eb1d..e0081904db2 100644 --- a/spec/workers/email_receiver_worker_spec.rb +++ b/spec/workers/email_receiver_worker_spec.rb @@ -7,7 +7,7 @@ describe EmailReceiverWorker do File.read(file_path) end - let(:raw_message) { fixture_file('emails/valid_incoming.eml') } + let(:raw_message) { fixture_file('emails/valid_reply.eml') } context "when reply by email is enabled" do before do @@ -31,7 +31,7 @@ describe EmailReceiverWorker do email = ActionMailer::Base.deliveries.last expect(email).not_to be_nil - expect(email.to).to eq(["from@example.com"]) + expect(email.to).to eq(["jake@adventuretime.ooo"]) expect(email.subject).to include("Rejected") end end -- cgit v1.2.1 From 54b04f1c5b6c486a67c654ee9987e5f5ec4501fd Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 12:41:47 -0700 Subject: Add fixture_file helper. --- spec/lib/gitlab/email/attachment_uploader_spec.rb | 8 +------- spec/lib/gitlab/email/receiver_spec.rb | 6 ------ spec/lib/gitlab/email/reply_parser_spec.rb | 6 ------ spec/lib/gitlab/google_code_import/client_spec.rb | 2 +- spec/lib/gitlab/google_code_import/importer_spec.rb | 2 +- spec/support/fixture_helpers.rb | 11 +++++++++++ spec/support/markdown_feature.rb | 4 ++-- spec/workers/email_receiver_worker_spec.rb | 6 ------ 8 files changed, 16 insertions(+), 29 deletions(-) create mode 100644 spec/support/fixture_helpers.rb diff --git a/spec/lib/gitlab/email/attachment_uploader_spec.rb b/spec/lib/gitlab/email/attachment_uploader_spec.rb index b54ee8aa72a..e8208e15e29 100644 --- a/spec/lib/gitlab/email/attachment_uploader_spec.rb +++ b/spec/lib/gitlab/email/attachment_uploader_spec.rb @@ -1,18 +1,12 @@ require "spec_helper" describe Gitlab::Email::AttachmentUploader do - def fixture_file(filename) - return '' if filename.blank? - file_path = File.expand_path(Rails.root + 'spec/fixtures/' + filename) - File.read(file_path) - end - describe "#execute" do let(:project) { build(:project) } let(:message_raw) { fixture_file("emails/attachment.eml") } let(:message) { Mail::Message.new(message_raw) } - it "creates a post with an attachment" do + it "uploads all attachments and returns their links" do links = described_class.new(message).execute(project) link = links.first diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb index 3d434aeaf91..a6f682a2711 100644 --- a/spec/lib/gitlab/email/receiver_spec.rb +++ b/spec/lib/gitlab/email/receiver_spec.rb @@ -1,12 +1,6 @@ require "spec_helper" describe Gitlab::Email::Receiver do - def fixture_file(filename) - return '' if filename.blank? - file_path = File.expand_path(Rails.root + 'spec/fixtures/' + filename) - File.read(file_path) - end - before do allow(Gitlab.config.reply_by_email).to receive(:enabled).and_return(true) allow(Gitlab.config.reply_by_email).to receive(:address).and_return("reply+%{reply_key}@appmail.adventuretime.ooo") diff --git a/spec/lib/gitlab/email/reply_parser_spec.rb b/spec/lib/gitlab/email/reply_parser_spec.rb index 32120d56cbc..c552e632ce2 100644 --- a/spec/lib/gitlab/email/reply_parser_spec.rb +++ b/spec/lib/gitlab/email/reply_parser_spec.rb @@ -2,12 +2,6 @@ require "spec_helper" # Inspired in great part by Discourse's Email::Receiver describe Gitlab::Email::ReplyParser do - def fixture_file(filename) - return '' if filename.blank? - file_path = File.expand_path(Rails.root + 'spec/fixtures/' + filename) - File.read(file_path) - end - describe '#execute' do def test_parse_body(mail_string) described_class.new(Mail::Message.new(mail_string)).execute diff --git a/spec/lib/gitlab/google_code_import/client_spec.rb b/spec/lib/gitlab/google_code_import/client_spec.rb index 6aa4428f367..37985c062b4 100644 --- a/spec/lib/gitlab/google_code_import/client_spec.rb +++ b/spec/lib/gitlab/google_code_import/client_spec.rb @@ -1,7 +1,7 @@ require "spec_helper" describe Gitlab::GoogleCodeImport::Client do - let(:raw_data) { JSON.parse(File.read(Rails.root.join("spec/fixtures/GoogleCodeProjectHosting.json"))) } + let(:raw_data) { JSON.parse(fixture_file("GoogleCodeProjectHosting.json")) } subject { described_class.new(raw_data) } describe "#valid?" do diff --git a/spec/lib/gitlab/google_code_import/importer_spec.rb b/spec/lib/gitlab/google_code_import/importer_spec.rb index f49cbb7f532..65ad7524cc2 100644 --- a/spec/lib/gitlab/google_code_import/importer_spec.rb +++ b/spec/lib/gitlab/google_code_import/importer_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" describe Gitlab::GoogleCodeImport::Importer do let(:mapped_user) { create(:user, username: "thilo123") } - let(:raw_data) { JSON.parse(File.read(Rails.root.join("spec/fixtures/GoogleCodeProjectHosting.json"))) } + let(:raw_data) { JSON.parse(fixture_file("GoogleCodeProjectHosting.json")) } let(:client) { Gitlab::GoogleCodeImport::Client.new(raw_data) } let(:import_data) do { diff --git a/spec/support/fixture_helpers.rb b/spec/support/fixture_helpers.rb new file mode 100644 index 00000000000..a05c9d18002 --- /dev/null +++ b/spec/support/fixture_helpers.rb @@ -0,0 +1,11 @@ +module FixtureHelpers + def fixture_file(filename) + return '' if filename.blank? + file_path = File.expand_path(Rails.root.join('spec/fixtures/', filename)) + File.read(file_path) + end +end + +RSpec.configure do |config| + config.include FixtureHelpers +end diff --git a/spec/support/markdown_feature.rb b/spec/support/markdown_feature.rb index c59df4e84d6..f8b2ce03e6c 100644 --- a/spec/support/markdown_feature.rb +++ b/spec/support/markdown_feature.rb @@ -100,7 +100,7 @@ class MarkdownFeature end def raw_markdown - fixture = Rails.root.join('spec/fixtures/markdown.md.erb') - ERB.new(File.read(fixture)).result(binding) + markdown = fixture_file('markdown.md.erb') + ERB.new(markdown).result(binding) end end diff --git a/spec/workers/email_receiver_worker_spec.rb b/spec/workers/email_receiver_worker_spec.rb index e0081904db2..e8f1bd2fa2f 100644 --- a/spec/workers/email_receiver_worker_spec.rb +++ b/spec/workers/email_receiver_worker_spec.rb @@ -1,12 +1,6 @@ require "spec_helper" describe EmailReceiverWorker do - def fixture_file(filename) - return '' if filename.blank? - file_path = File.expand_path(Rails.root + 'spec/fixtures/' + filename) - File.read(file_path) - end - let(:raw_message) { fixture_file('emails/valid_reply.eml') } context "when reply by email is enabled" do -- cgit v1.2.1 From 6110e175dc3bcf6d9ef416c7b95696efe509d02b Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 13:10:55 -0700 Subject: Use heredocs. --- spec/fixtures/emails/iphone_signature.eml | 29 ----- spec/lib/gitlab/email/reply_parser_spec.rb | 202 +++++++++++++++-------------- 2 files changed, 107 insertions(+), 124 deletions(-) delete mode 100644 spec/fixtures/emails/iphone_signature.eml diff --git a/spec/fixtures/emails/iphone_signature.eml b/spec/fixtures/emails/iphone_signature.eml deleted file mode 100644 index d314ad1f1ea..00000000000 --- a/spec/fixtures/emails/iphone_signature.eml +++ /dev/null @@ -1,29 +0,0 @@ -Delivered-To: test@mail.com -Return-Path: -From: Walter White -Content-Type: multipart/alternative; - boundary=Apple-Mail-8E182EEF-9DBC-41DE-A593-DF2E5EBD3975 -Content-Transfer-Encoding: 7bit -Mime-Version: 1.0 (1.0) -Subject: Re: Signature in email replies! -Date: Thu, 23 Oct 2014 14:43:49 +0530 -References: <1234@mail.gmail.com> -In-Reply-To: <1234@mail.gmail.com> -To: Arpit Jalan -X-Mailer: iPhone Mail (12A405) - - ---Apple-Mail-8E182EEF-9DBC-41DE-A593-DF2E5EBD3975 -Content-Type: text/plain; - charset=us-ascii -Content-Transfer-Encoding: 7bit - -This post should not include signature. - -Sent from my iPhone - -> On 23-Oct-2014, at 9:45 am, Arpit Jalan wrote: -> -> Signature in email replies! - ---Apple-Mail-8E182EEF-9DBC-41DE-A593-DF2E5EBD3975 diff --git a/spec/lib/gitlab/email/reply_parser_spec.rb b/spec/lib/gitlab/email/reply_parser_spec.rb index c552e632ce2..a94c92ad53c 100644 --- a/spec/lib/gitlab/email/reply_parser_spec.rb +++ b/spec/lib/gitlab/email/reply_parser_spec.rb @@ -34,148 +34,160 @@ describe Gitlab::Email::ReplyParser do it "handles multiple paragraphs" do expect(test_parse_body(fixture_file("emails/paragraphs.eml"))). - to eq( -"Is there any reason the *old* candy can't be be kept in silos while the new candy -is imported into *new* silos? + to eq( + <<-BODY.strip_heredoc.chomp + Is there any reason the *old* candy can't be be kept in silos while the new candy + is imported into *new* silos? -The thing about candy is it stays delicious for a long time -- we can just keep -it there without worrying about it too much, imo. + The thing about candy is it stays delicious for a long time -- we can just keep + it there without worrying about it too much, imo. -Thanks for listening." - ) + Thanks for listening. + BODY + ) end it "handles multiple paragraphs when parsing html" do expect(test_parse_body(fixture_file("emails/html_paragraphs.eml"))). - to eq( -"Awesome! + to eq( + <<-BODY.strip_heredoc.chomp + Awesome! -Pleasure to have you here! + Pleasure to have you here! -:boom:" - ) + :boom: + BODY + ) end it "handles newlines" do expect(test_parse_body(fixture_file("emails/newlines.eml"))). - to eq( -"This is my reply. -It is my best reply. -It will also be my *only* reply." - ) + to eq( + <<-BODY.strip_heredoc.chomp + This is my reply. + It is my best reply. + It will also be my *only* reply. + BODY + ) end it "handles inline reply" do expect(test_parse_body(fixture_file("emails/inline_reply.eml"))). - to eq( -"On Wed, Oct 8, 2014 at 11:12 AM, techAPJ wrote: - -> techAPJ -> November 28 -> -> Test reply. -> -> First paragraph. -> -> Second paragraph. -> -> To respond, reply to this email or visit -> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in -> your browser. -> ------------------------------ -> Previous Replies codinghorror -> -> November 28 -> -> We're testing the latest GitHub email processing library which we are -> integrating now. -> -> https://github.com/github/email_reply_parser -> -> Go ahead and reply to this topic and I'll reply from various email clients -> for testing. -> ------------------------------ -> -> To respond, reply to this email or visit -> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in -> your browser. -> -> To unsubscribe from these emails, visit your user preferences -> . -> - -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over -the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown -fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over -the lazy dog. The quick brown fox jumps over the lazy dog." - ) - end - - it "strips iPhone signature" do - expect(test_parse_body(fixture_file("emails/iphone_signature.eml"))).not_to match /Sent from my iPhone/ + to eq( + <<-BODY.strip_heredoc.chomp + On Wed, Oct 8, 2014 at 11:12 AM, techAPJ wrote: + + > techAPJ + > November 28 + > + > Test reply. + > + > First paragraph. + > + > Second paragraph. + > + > To respond, reply to this email or visit + > https://meta.discourse.org/t/testing-default-email-replies/22638/3 in + > your browser. + > ------------------------------ + > Previous Replies codinghorror + > + > November 28 + > + > We're testing the latest GitHub email processing library which we are + > integrating now. + > + > https://github.com/github/email_reply_parser + > + > Go ahead and reply to this topic and I'll reply from various email clients + > for testing. + > ------------------------------ + > + > To respond, reply to this email or visit + > https://meta.discourse.org/t/testing-default-email-replies/22638/3 in + > your browser. + > + > To unsubscribe from these emails, visit your user preferences + > . + > + + The quick brown fox jumps over the lazy dog. The quick brown fox jumps over + the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown + fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. + The quick brown fox jumps over the lazy dog. The quick brown fox jumps over + the lazy dog. The quick brown fox jumps over the lazy dog. + BODY + ) end it "properly renders email reply from gmail web client" do expect(test_parse_body(fixture_file("emails/gmail_web.eml"))). - to eq( -"### This is a reply from standard GMail in Google Chrome. + to eq( + <<-BODY.strip_heredoc.chomp + ### This is a reply from standard GMail in Google Chrome. -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over -the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown -fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over -the lazy dog. The quick brown fox jumps over the lazy dog. + The quick brown fox jumps over the lazy dog. The quick brown fox jumps over + the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown + fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. + The quick brown fox jumps over the lazy dog. The quick brown fox jumps over + the lazy dog. The quick brown fox jumps over the lazy dog. -Here's some **bold** text in Markdown. + Here's some **bold** text in Markdown. -Here's a link http://example.com" - ) + Here's a link http://example.com + BODY + ) end it "properly renders email reply from iOS default mail client" do expect(test_parse_body(fixture_file("emails/ios_default.eml"))). - to eq( -"### this is a reply from iOS default mail + to eq( + <<-BODY.strip_heredoc.chomp + ### this is a reply from iOS default mail -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. + The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. -Here's some **bold** markdown text. + Here's some **bold** markdown text. -Here's a link http://example.com" - ) + Here's a link http://example.com + BODY + ) end it "properly renders email reply from Android 5 gmail client" do expect(test_parse_body(fixture_file("emails/android_gmail.eml"))). - to eq( -"### this is a reply from Android 5 gmail + to eq( + <<-BODY.strip_heredoc.chomp + ### this is a reply from Android 5 gmail -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over -the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown -fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. -The quick brown fox jumps over the lazy dog. + The quick brown fox jumps over the lazy dog. The quick brown fox jumps over + the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown + fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. + The quick brown fox jumps over the lazy dog. -This is **bold** in Markdown. + This is **bold** in Markdown. -This is a link to http://example.com" - ) + This is a link to http://example.com + BODY + ) end it "properly renders email reply from Windows 8.1 Metro default mail client" do expect(test_parse_body(fixture_file("emails/windows_8_metro.eml"))). - to eq( -"### reply from default mail client in Windows 8.1 Metro + to eq( + <<-BODY.strip_heredoc.chomp + ### reply from default mail client in Windows 8.1 Metro -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. + The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. -This is a **bold** word in Markdown + This is a **bold** word in Markdown -This is a link http://example.com" - ) + This is a link http://example.com + BODY + ) end it "properly renders email reply from MS Outlook client" do -- cgit v1.2.1 From 48e25a019a7dacbadeb3a49ed96e41d2c2241feb Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 13:21:22 -0700 Subject: Add stub_reply_by_email_setting helper. --- spec/lib/gitlab/email/receiver_spec.rb | 3 +-- spec/lib/gitlab/reply_by_email_spec.rb | 12 ++++++------ spec/support/stub_configuration.rb | 4 ++++ 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb index a6f682a2711..af44c1242ed 100644 --- a/spec/lib/gitlab/email/receiver_spec.rb +++ b/spec/lib/gitlab/email/receiver_spec.rb @@ -2,8 +2,7 @@ require "spec_helper" describe Gitlab::Email::Receiver do before do - allow(Gitlab.config.reply_by_email).to receive(:enabled).and_return(true) - allow(Gitlab.config.reply_by_email).to receive(:address).and_return("reply+%{reply_key}@appmail.adventuretime.ooo") + stub_reply_by_email_setting(enabled: true, address: "reply+%{reply_key}@appmail.adventuretime.ooo") end let(:reply_key) { "59d8df8370b7e95c5a49fbf86aeb2c93" } diff --git a/spec/lib/gitlab/reply_by_email_spec.rb b/spec/lib/gitlab/reply_by_email_spec.rb index a52af51e97c..a678c7e1a76 100644 --- a/spec/lib/gitlab/reply_by_email_spec.rb +++ b/spec/lib/gitlab/reply_by_email_spec.rb @@ -4,12 +4,12 @@ describe Gitlab::ReplyByEmail do describe "self.enabled?" do context "when reply by email is enabled" do before do - allow(Gitlab.config.reply_by_email).to receive(:enabled).and_return(true) + stub_reply_by_email_setting(enabled: true) end context "when the address is valid" do before do - allow(Gitlab.config.reply_by_email).to receive(:address).and_return("replies+%{reply_key}@example.com") + stub_reply_by_email_setting(address: "replies+%{reply_key}@example.com") end it "returns true" do @@ -19,7 +19,7 @@ describe Gitlab::ReplyByEmail do context "when the address is invalid" do before do - allow(Gitlab.config.reply_by_email).to receive(:address).and_return("replies@example.com") + stub_reply_by_email_setting(address: "replies@example.com") end it "returns false" do @@ -30,7 +30,7 @@ describe Gitlab::ReplyByEmail do context "when reply by email is disabled" do before do - allow(Gitlab.config.reply_by_email).to receive(:enabled).and_return(false) + stub_reply_by_email_setting(enabled: false) end it "returns false" do @@ -66,7 +66,7 @@ describe Gitlab::ReplyByEmail do context "self.reply_address" do before do - allow(Gitlab.config.reply_by_email).to receive(:address).and_return("replies+%{reply_key}@example.com") + stub_reply_by_email_setting(address: "replies+%{reply_key}@example.com") end it "returns the address with an interpolated reply key" do @@ -76,7 +76,7 @@ describe Gitlab::ReplyByEmail do context "self.reply_key_from_address" do before do - allow(Gitlab.config.reply_by_email).to receive(:address).and_return("replies+%{reply_key}@example.com") + stub_reply_by_email_setting(address: "replies+%{reply_key}@example.com") end it "returns reply key" do diff --git a/spec/support/stub_configuration.rb b/spec/support/stub_configuration.rb index e4004ec8f79..ef3a120d44a 100644 --- a/spec/support/stub_configuration.rb +++ b/spec/support/stub_configuration.rb @@ -17,6 +17,10 @@ module StubConfiguration allow(Gitlab.config.gravatar).to receive_messages(messages) end + def stub_reply_by_email_setting(messages) + allow(Gitlab.config.reply_by_email).to receive_messages(messages) + end + private # Modifies stubbed messages to also stub possible predicate versions -- cgit v1.2.1 From 123af7856167f01ac0a7873bca1ef2cbb835e3b5 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 14:03:04 -0700 Subject: Add gitlab:reply_by_email:check rake task. --- doc/reply_by_email/README.md | 6 ++ lib/gitlab/reply_by_email.rb | 9 ++- lib/tasks/gitlab/check.rake | 145 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 157 insertions(+), 3 deletions(-) diff --git a/doc/reply_by_email/README.md b/doc/reply_by_email/README.md index e168e9a17e4..1a0ce79bc5b 100644 --- a/doc/reply_by_email/README.md +++ b/doc/reply_by_email/README.md @@ -93,6 +93,12 @@ In this example, we'll use the Gmail address `gitlab-replies@gmail.com`. If you' sudo service gitlab restart ``` +7. Check if everything is configured correctly + + ```sh + sudo bundle exec rake gitlab:reply_by_email:check RAILS_ENV=production + ``` + 8. Reply by email should now be working. Note: If you're running GitLab in development mode and using `foreman`, make sure to also uncomment the `mail_room` line in your `Procfile`. diff --git a/lib/gitlab/reply_by_email.rb b/lib/gitlab/reply_by_email.rb index b6157de3610..61d048fd200 100644 --- a/lib/gitlab/reply_by_email.rb +++ b/lib/gitlab/reply_by_email.rb @@ -2,9 +2,12 @@ module Gitlab module ReplyByEmail class << self def enabled? - config.enabled && - config.address && - config.address.include?("%{reply_key}") + config.enabled && address_formatted_correctly? + end + + def address_formatted_correctly? + config.address && + config.address.include?("%{reply_key}") end def reply_key diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index 80ee572938d..608253d916c 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -2,6 +2,7 @@ namespace :gitlab do desc "GitLab | Check the configuration of GitLab and its environment" task check: %w{gitlab:gitlab_shell:check gitlab:sidekiq:check + gitlab:reply_by_email:check gitlab:ldap:check gitlab:app:check} @@ -577,6 +578,150 @@ namespace :gitlab do end end + + namespace :reply_by_email do + desc "GitLab | Check the configuration of Reply by email" + task check: :environment do + warn_user_is_not_gitlab + start_checking "Reply by email" + + if Gitlab.config.reply_by_email.enabled + check_address_formatted_correctly + check_mail_room_config_exists + check_imap_authentication + check_initd_configured_correctly + check_mail_room_running + else + puts 'Reply by email is disabled in config/gitlab.yml' + end + + finished_checking "Reply by email" + end + + + # Checks + ######################## + + def check_address_formatted_correctly + print "Address formatted correctly? ... " + + if Gitlab::ReplyByEmail.address_formatted_correctly? + puts "yes".green + else + puts "no".red + try_fixing_it( + "Make sure that the address in config/gitlab.yml includes the '%{reply_key}' placeholder." + ) + fix_and_rerun + end + end + + def check_initd_configured_correctly + print "Init.d configured correctly? ... " + + path = "/etc/default/gitlab" + + if File.exist?(path) && File.read(path).include?("mail_room_enabled=true") + puts "yes".green + else + puts "no".red + try_fixing_it( + "Enable mail_room in the init.d configuration." + ) + for_more_information( + "doc/reply_by_email/README.md" + ) + fix_and_rerun + end + end + + def check_mail_room_running + print "MailRoom running? ... " + + path = "/etc/default/gitlab" + + unless File.exist?(path) && File.read(path).include?("mail_room_enabled=true") + puts "can't check because of previous errors".magenta + return + end + + if mail_room_process_count > 0 + puts "yes".green + else + puts "no".red + try_fixing_it( + sudo_gitlab("RAILS_ENV=production bin/mail_room start") + ) + for_more_information( + see_installation_guide_section("Install Init Script"), + "see log/mail_room.log for possible errors" + ) + fix_and_rerun + end + end + + def check_mail_room_config_exists + print "MailRoom config exists? ... " + + mail_room_config_file = Rails.root.join("config", "mail_room.yml") + + if File.exists?(mail_room_config_file) + puts "yes".green + else + puts "no".red + try_fixing_it( + "Copy config/mail_room.yml.example to config/mail_room.yml", + "Check that the information in config/mail_room.yml is correct" + ) + for_more_information( + "doc/reply_by_email/README.md" + ) + fix_and_rerun + end + end + + def check_imap_authentication + print "IMAP server credentials are correct? ... " + + mail_room_config_file = Rails.root.join("config", "mail_room.yml") + + unless File.exists?(mail_room_config_file) + puts "can't check because of previous errors".magenta + return + end + + config = YAML.load_file(mail_room_config_file)[:mailboxes].first rescue nil + + if config + begin + imap = Net::IMAP.new(config[:host], port: config[:port], ssl: config[:ssl]) + imap.login(config[:email], config[:password]) + connected = true + rescue + connected = false + end + end + + if connected + puts "yes".green + else + puts "no".red + try_fixing_it( + "Check that the information in config/mail_room.yml is correct" + ) + for_more_information( + "doc/reply_by_email/README.md" + ) + fix_and_rerun + end + end + + def mail_room_process_count + ps_ux, _ = Gitlab::Popen.popen(%W(ps ux)) + ps_ux.scan(/mail_room \d+\.\d+\.\d+/).count + end + end + namespace :ldap do task :check, [:limit] => :environment do |t, args| # Only show up to 100 results because LDAP directories can be very big. -- cgit v1.2.1 From 02b7be4a6847c0f79c6088edcc84600008ba0c1e Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 14:03:09 -0700 Subject: Remove weirdness. --- app/helpers/icons_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb index 30b17a736a7..1cf5b96481a 100644 --- a/app/helpers/icons_helper.rb +++ b/app/helpers/icons_helper.rb @@ -20,7 +20,7 @@ module IconsHelper end def boolean_to_icon(value) - if value.to_s == "true" + if value icon('circle', class: 'cgreen') else icon('power-off', class: 'clgray') -- cgit v1.2.1 From 41ed60c21a34f5a413c45d2d303e28b438a09ef2 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 14:09:37 -0700 Subject: Fix MailRoom running check. --- lib/tasks/gitlab/check.rake | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index 608253d916c..3fbd429ef8d 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -645,7 +645,7 @@ namespace :gitlab do return end - if mail_room_process_count > 0 + if mail_room_running? puts "yes".green else puts "no".red @@ -716,9 +716,9 @@ namespace :gitlab do end end - def mail_room_process_count + def mail_room_running? ps_ux, _ = Gitlab::Popen.popen(%W(ps ux)) - ps_ux.scan(/mail_room \d+\.\d+\.\d+/).count + ps_ux.include?("mail_room") end end -- cgit v1.2.1 From f26c2905d198436ea4c4b7f26c6971edd4ff8049 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 14:25:56 -0700 Subject: Fix indentation --- lib/gitlab/email/reply_parser.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/email/reply_parser.rb b/lib/gitlab/email/reply_parser.rb index 6ceb755968c..6e768e46a71 100644 --- a/lib/gitlab/email/reply_parser.rb +++ b/lib/gitlab/email/reply_parser.rb @@ -72,8 +72,8 @@ module Gitlab lines.each_with_index do |l, idx| # This one might be controversial but so many reply lines have years, times and end with a colon. - # Let's try it and see how well it works. - break if (l =~ /\d{4}/ && l =~ /\d:\d\d/ && l =~ /\:$/) || + # Let's try it and see how well it works. + break if (l =~ /\d{4}/ && l =~ /\d:\d\d/ && l =~ /\:$/) || (l =~ /On \w+ \d+,? \d+,?.*wrote:/) # Headers on subsequent lines -- cgit v1.2.1 From 99ef8c81598ad31922dfbe28c0c56130e01bd13a Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 15:37:43 -0700 Subject: Fix markdown specs. --- lib/gitlab/reply_by_email.rb | 2 +- spec/support/markdown_feature.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/reply_by_email.rb b/lib/gitlab/reply_by_email.rb index 61d048fd200..4fdeb6b8a74 100644 --- a/lib/gitlab/reply_by_email.rb +++ b/lib/gitlab/reply_by_email.rb @@ -7,7 +7,7 @@ module Gitlab def address_formatted_correctly? config.address && - config.address.include?("%{reply_key}") + config.address.include?("%{reply_key}") end def reply_key diff --git a/spec/support/markdown_feature.rb b/spec/support/markdown_feature.rb index f8b2ce03e6c..cb47d290822 100644 --- a/spec/support/markdown_feature.rb +++ b/spec/support/markdown_feature.rb @@ -9,6 +9,7 @@ # reference to the factory-created objects. class MarkdownFeature include FactoryGirl::Syntax::Methods + include FixtureHelpers def user @user ||= create(:user) -- cgit v1.2.1 From afb765ad9ef6fd394a9709f6ab519267a761e6ad Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 15:41:34 -0700 Subject: Fix markdown specs again. Apparently development and test behave differently. --- spec/support/markdown_feature.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/support/markdown_feature.rb b/spec/support/markdown_feature.rb index cb47d290822..39a64391460 100644 --- a/spec/support/markdown_feature.rb +++ b/spec/support/markdown_feature.rb @@ -9,7 +9,6 @@ # reference to the factory-created objects. class MarkdownFeature include FactoryGirl::Syntax::Methods - include FixtureHelpers def user @user ||= create(:user) @@ -101,7 +100,7 @@ class MarkdownFeature end def raw_markdown - markdown = fixture_file('markdown.md.erb') + markdown = File.read(Rails.root.join('spec/fixtures/markdown.md.erb')) ERB.new(markdown).result(binding) end end -- cgit v1.2.1 From 3d141d1512a39b61159026457b31c6f0aecf5b6c Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 16:21:31 -0700 Subject: Fix spec by removing global state. --- lib/gitlab/reply_by_email.rb | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/gitlab/reply_by_email.rb b/lib/gitlab/reply_by_email.rb index 4fdeb6b8a74..f93fda4302c 100644 --- a/lib/gitlab/reply_by_email.rb +++ b/lib/gitlab/reply_by_email.rb @@ -36,14 +36,12 @@ module Gitlab end def address_regex - @address_regex ||= begin - wildcard_address = config.address - return nil unless wildcard_address - - regex = Regexp.escape(wildcard_address) - regex = regex.gsub(Regexp.escape('%{reply_key}'), "(.+)") - Regexp.new(regex).freeze - end + wildcard_address = config.address + return nil unless wildcard_address + + regex = Regexp.escape(wildcard_address) + regex = regex.gsub(Regexp.escape('%{reply_key}'), "(.+)") + Regexp.new(regex).freeze end end end -- cgit v1.2.1 From 35224d5e7f3e0c978640b7a6dd64e9778c4d1c60 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 16:44:44 -0700 Subject: Memoize address_regex locally. --- lib/gitlab/reply_by_email.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/reply_by_email.rb b/lib/gitlab/reply_by_email.rb index f93fda4302c..c3fe6778f06 100644 --- a/lib/gitlab/reply_by_email.rb +++ b/lib/gitlab/reply_by_email.rb @@ -21,9 +21,10 @@ module Gitlab end def reply_key_from_address(address) - return unless address_regex + regex = address_regex + return unless regex - match = address.match(address_regex) + match = address.match(regex) return unless match match[1] -- cgit v1.2.1 From 747fe7520b244a324b60049cbe22c22a5df29c82 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 20 Aug 2015 18:25:16 -0700 Subject: Remove trailing HTML entities from non-Rinku autolinks as well. --- lib/gitlab/markdown/autolink_filter.rb | 8 +++++++- spec/lib/gitlab/markdown/autolink_filter_spec.rb | 10 ++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/markdown/autolink_filter.rb b/lib/gitlab/markdown/autolink_filter.rb index 4e14a048cfb..541f1d88ffc 100644 --- a/lib/gitlab/markdown/autolink_filter.rb +++ b/lib/gitlab/markdown/autolink_filter.rb @@ -87,8 +87,14 @@ module Gitlab def autolink_filter(text) text.gsub(LINK_PATTERN) do |match| + # Remove any trailing HTML entities and store them for appending + # outside the link element. The entity must be marked HTML safe in + # order to be output literally rather than escaped. + match.gsub!(/((?:&[\w#]+;)+)\z/, '') + dropped = ($1 || '').html_safe + options = link_options.merge(href: match) - content_tag(:a, match, options) + content_tag(:a, match, options) + dropped end end diff --git a/spec/lib/gitlab/markdown/autolink_filter_spec.rb b/spec/lib/gitlab/markdown/autolink_filter_spec.rb index 982be0782c9..26332ba5217 100644 --- a/spec/lib/gitlab/markdown/autolink_filter_spec.rb +++ b/spec/lib/gitlab/markdown/autolink_filter_spec.rb @@ -86,6 +86,16 @@ module Gitlab::Markdown doc = filter("See #{link}, ok?") expect(doc.at_css('a').text).to eq link + + doc = filter("See #{link}...") + expect(doc.at_css('a').text).to eq link + end + + it 'does not include trailing HTML entities' do + doc = filter("See <<<#{link}>>>") + + expect(doc.at_css('a')['href']).to eq link + expect(doc.text).to eq "See <<<#{link}>>>" end it 'accepts link_attr options' do -- cgit v1.2.1 From 77e508d8fd7a915f5ae221f5e4d6022560398a9e Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 18:32:32 -0700 Subject: Fix bug where non-project members of the target project could set labels on new merge requests. --- CHANGELOG | 1 + app/views/shared/issuable/_form.html.haml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 54f83e5aeac..17b063536df 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,7 @@ v 8.0.0 (unreleased) - Allow configuration of import sources for new projects (Artem Sidorenko) v 7.14.0 (unreleased) + - Fix bug where non-project members of the target project could set labels on new merge requests. - Update default robots.txt rules to disallow crawling of irrelevant pages (Ben Bodenmiller) - Fix redirection after sign in when using auto_sign_in_with_provider - Upgrade gitlab_git to 7.2.14 to ignore CRLFs in .gitmodules (Stan Hu) diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 3489bf3f191..f6b09de3839 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -38,7 +38,7 @@ .clearfix .error-alert %hr -- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) +- if can?(current_user, :"admin_#{issuable.to_ability_name}", issuable.project) .form-group .issue-assignee = f.label :assignee_id, class: 'control-label' do -- cgit v1.2.1 From 414533ca48147120586f531f61f131dbef9d85fc Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 21 Aug 2015 09:47:39 -0700 Subject: Check permissions on target project in merge request create service. --- app/services/merge_requests/create_service.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb index f431c5d5534..9651b16462c 100644 --- a/app/services/merge_requests/create_service.rb +++ b/app/services/merge_requests/create_service.rb @@ -1,11 +1,17 @@ module MergeRequests class CreateService < MergeRequests::BaseService def execute + # @project is used to determine whether the user can set the merge request's + # assignee, milestone and labels. Whether they can depends on their + # permissions on the target project. + source_project = @project + @project = Project.find(params[:target_project_id]) if params[:target_project_id] + filter_params label_params = params[:label_ids] merge_request = MergeRequest.new(params.except(:label_ids)) - merge_request.source_project = project - merge_request.target_project ||= project + merge_request.source_project = source_project + merge_request.target_project ||= source_project merge_request.author = current_user if merge_request.save -- cgit v1.2.1 From 69708dab9f6e1c265dd2bf80eafc39bf68c356e0 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 21 Aug 2015 10:14:45 -0700 Subject: Block blocked users from replying to threads by email. --- app/workers/email_receiver_worker.rb | 2 ++ lib/gitlab/email/receiver.rb | 3 +++ spec/lib/gitlab/email/receiver_spec.rb | 10 ++++++++++ 3 files changed, 15 insertions(+) diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb index 8f6c27ce4af..a588a1f45ee 100644 --- a/app/workers/email_receiver_worker.rb +++ b/app/workers/email_receiver_worker.rb @@ -31,6 +31,8 @@ class EmailReceiverWorker reason = "The email was marked as 'auto generated', which we can't accept. Please create your comment through the web interface." when Gitlab::Email::Receiver::UserNotFoundError reason = "We couldn't figure out what user corresponds to the email. Please create your comment through the web interface." + when Gitlab::Email::Receiver::UserBlockedError + reason = "Your account has been blocked. If you believe this is in error, contact a staff member." when Gitlab::Email::Receiver::UserNotAuthorizedError reason = "You are not allowed to respond to the thread you are replying to. If you believe this is in error, contact a staff member." when Gitlab::Email::Receiver::NoteableNotFoundError diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index 17b8339edcd..355fbd27898 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -8,6 +8,7 @@ module Gitlab class EmptyEmailError < ProcessingError; end class AutoGeneratedEmailError < ProcessingError; end class UserNotFoundError < ProcessingError; end + class UserBlockedError < ProcessingError; end class UserNotAuthorizedError < ProcessingError; end class NoteableNotFoundError < ProcessingError; end class InvalidNoteError < ProcessingError; end @@ -27,6 +28,8 @@ module Gitlab raise UserNotFoundError unless author + raise UserBlockedError if author.blocked? + project = sent_notification.project raise UserNotAuthorizedError unless project && author.can?(:create_note, project) diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb index af44c1242ed..1cc80f35f98 100644 --- a/spec/lib/gitlab/email/receiver_spec.rb +++ b/spec/lib/gitlab/email/receiver_spec.rb @@ -58,6 +58,16 @@ describe Gitlab::Email::Receiver do end end + context "when the user has been blocked" do + before do + user.block + end + + it "raises a UserBlockedError" do + expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::UserBlockedError) + end + end + context "when the user is not authorized to create a note" do before do project.update_attribute(:visibility_level, Project::PRIVATE) -- cgit v1.2.1 From 3715a0f29b6758b3075abb6f28ee24c4eb8ed427 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 20 Aug 2015 21:33:00 -0700 Subject: Fix bug preventing invite by e-mail This was broken in 70f5291808 as a result of introducing the ability to filter by current user. Closes #2320 --- app/assets/javascripts/users_select.js.coffee | 2 ++ app/controllers/autocomplete_controller.rb | 10 ++++++++-- app/helpers/selects_helper.rb | 4 +++- app/views/shared/issuable/_context.html.haml | 2 +- app/views/shared/issuable/_filter.html.haml | 6 +++--- app/views/shared/issuable/_form.html.haml | 3 ++- features/admin/groups.feature | 6 ++++++ features/steps/admin/groups.rb | 15 +++++++++++++++ spec/controllers/autocomplete_controller_spec.rb | 2 +- 9 files changed, 41 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee index aeeed9ca3cc..9157562a5c5 100644 --- a/app/assets/javascripts/users_select.js.coffee +++ b/app/assets/javascripts/users_select.js.coffee @@ -6,6 +6,7 @@ class @UsersSelect $('.ajax-users-select').each (i, select) => @projectId = $(select).data('project-id') @groupId = $(select).data('group-id') + @showCurrentUser = $(select).data('current-user') showNullUser = $(select).data('null-user') showAnyUser = $(select).data('any-user') showEmailUser = $(select).data('email-user') @@ -108,6 +109,7 @@ class @UsersSelect active: true project_id: @projectId group_id: @groupId + current_user: @showCurrentUser dataType: "json" ).done (users) -> callback(users) diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb index 5c3ca8e23c9..904d26a39f4 100644 --- a/app/controllers/autocomplete_controller.rb +++ b/app/controllers/autocomplete_controller.rb @@ -33,8 +33,14 @@ class AutocompleteController < ApplicationController @users = @users.search(params[:search]) if params[:search].present? @users = @users.active @users = @users.page(params[:page]).per(PER_PAGE) - # Always include current user if available to filter by "Me" - @users = User.find(@users.pluck(:id) + [current_user.id]).uniq if current_user + + unless params[:search].present? + # Include current user if available to filter by "Me" + if params[:current_user] && current_user + @users = [*@users, current_user].uniq + end + end + render json: @users, only: [:name, :username, :id], methods: [:avatar_url] end diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb index 2b99a398049..12fce8db701 100644 --- a/app/helpers/selects_helper.rb +++ b/app/helpers/selects_helper.rb @@ -10,6 +10,7 @@ module SelectsHelper any_user = opts[:any_user] || false email_user = opts[:email_user] || false first_user = opts[:first_user] && current_user ? current_user.username : false + current_user = opts[:current_user] || false project = opts[:project] || @project html = { @@ -18,7 +19,8 @@ module SelectsHelper 'data-null-user' => null_user, 'data-any-user' => any_user, 'data-email-user' => email_user, - 'data-first-user' => first_user + 'data-first-user' => first_user, + 'data-current-user' => current_user } unless opts[:scope] == :all diff --git a/app/views/shared/issuable/_context.html.haml b/app/views/shared/issuable/_context.html.haml index 19e8c31975b..cba18c14568 100644 --- a/app/views/shared/issuable/_context.html.haml +++ b/app/views/shared/issuable/_context.html.haml @@ -9,7 +9,7 @@ none .issuable-context-selectbox - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) - = users_select_tag("#{issuable.class.table_name.singularize}[assignee_id]", placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: issuable.assignee_id, project: @target_project, null_user: true) + = users_select_tag("#{issuable.class.table_name.singularize}[assignee_id]", placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: issuable.assignee_id, project: @target_project, null_user: true, current_user: true) %div.prepend-top-20.clearfix .issuable-context-title diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index 0e8da8de723..8801f213fd1 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -36,11 +36,11 @@ .issues-other-filters .filter-item.inline = users_select_tag(:assignee_id, selected: params[:assignee_id], - placeholder: 'Assignee', class: 'trigger-submit', any_user: true, null_user: true, first_user: true) + placeholder: 'Assignee', class: 'trigger-submit', any_user: true, null_user: true, first_user: true, current_user: true) .filter-item.inline = users_select_tag(:author_id, selected: params[:author_id], - placeholder: 'Author', class: 'trigger-submit', any_user: true, first_user: true) + placeholder: 'Author', class: 'trigger-submit', any_user: true, first_user: true, current_user: true) .filter-item.inline.milestone-filter = select_tag('milestone_title', projects_milestones_options, @@ -60,7 +60,7 @@ .issues_bulk_update.hide = form_tag bulk_update_namespace_project_issues_path(@project.namespace, @project), method: :post do = select_tag('update[state_event]', options_for_select([['Open', 'reopen'], ['Closed', 'close']]), prompt: "Status", class: 'form-control') - = users_select_tag('update[assignee_id]', placeholder: 'Assignee', null_user: true) + = users_select_tag('update[assignee_id]', placeholder: 'Assignee', null_user: true, current_user: true) = select_tag('update[milestone_id]', bulk_update_milestone_options, prompt: "Milestone") = hidden_field_tag 'update[issues_ids]', [] = hidden_field_tag :state_event, params[:state_event] diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 3489bf3f191..8a4ae6987fa 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -47,7 +47,8 @@ .col-sm-10 = users_select_tag("#{issuable.class.model_name.param_key}[assignee_id]", placeholder: 'Select a user', class: 'custom-form-control', null_user: true, - selected: issuable.assignee_id, project: @target_project || @project) + selected: issuable.assignee_id, project: @target_project || @project, + current_user: true)   = link_to 'Assign to me', '#', class: 'btn assign-to-me-link' .form-group diff --git a/features/admin/groups.feature b/features/admin/groups.feature index aa365a6ea1a..973918086a3 100644 --- a/features/admin/groups.feature +++ b/features/admin/groups.feature @@ -27,3 +27,9 @@ Feature: Admin Groups When I visit admin group page And I remove user "John Doe" from group Then I should not see "John Doe" in team list + + @javascript + Scenario: Invite user to a group by e-mail + When I visit admin group page + When I select user "johndoe@gitlab.com" from user list as "Reporter" + Then I should see "johndoe@gitlab.com" in team list in every project as "Reporter" diff --git a/features/steps/admin/groups.rb b/features/steps/admin/groups.rb index 83a3f48abe3..d27634858a2 100644 --- a/features/steps/admin/groups.rb +++ b/features/steps/admin/groups.rb @@ -44,6 +44,14 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps click_button "Add users to group" end + When 'I select user "johndoe@gitlab.com" from user list as "Reporter"' do + select2('johndoe@gitlab.com', from: "#user_ids", multiple: true) + page.within "#new_project_member" do + select "Reporter", from: "access_level" + end + click_button "Add users to group" + end + step 'I should see "John Doe" in team list in every project as "Reporter"' do page.within ".group-users-list" do expect(page).to have_content "John Doe" @@ -51,6 +59,13 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps end end + step 'I should see "johndoe@gitlab.com" in team list in every project as "Reporter"' do + page.within ".group-users-list" do + expect(page).to have_content "johndoe@gitlab.com (invited)" + expect(page).to have_content "Reporter" + end + end + step 'I should be all groups' do Group.all.each do |group| expect(page).to have_content group.name diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb index 3521d690259..aa8d6cb807f 100644 --- a/spec/controllers/autocomplete_controller_spec.rb +++ b/spec/controllers/autocomplete_controller_spec.rb @@ -74,7 +74,7 @@ describe AutocompleteController do describe 'GET #users with project ID' do before do - get(:users, project_id: project.id) + get(:users, project_id: project.id, current_user: true) end it { expect(body).to be_kind_of(Array) } -- cgit v1.2.1 From 577df6b8ca4532545ac2cad11a693dc976160b36 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 21 Aug 2015 14:26:57 -0700 Subject: List the current user first in issuable dropdowns --- app/views/shared/issuable/_filter.html.haml | 2 +- app/views/shared/issuable/_form.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index 8801f213fd1..bcaa48c7a12 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -60,7 +60,7 @@ .issues_bulk_update.hide = form_tag bulk_update_namespace_project_issues_path(@project.namespace, @project), method: :post do = select_tag('update[state_event]', options_for_select([['Open', 'reopen'], ['Closed', 'close']]), prompt: "Status", class: 'form-control') - = users_select_tag('update[assignee_id]', placeholder: 'Assignee', null_user: true, current_user: true) + = users_select_tag('update[assignee_id]', placeholder: 'Assignee', null_user: true, first_user: true, current_user: true) = select_tag('update[milestone_id]', bulk_update_milestone_options, prompt: "Milestone") = hidden_field_tag 'update[issues_ids]', [] = hidden_field_tag :state_event, params[:state_event] diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 8a4ae6987fa..61df138ad1e 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -48,7 +48,7 @@ = users_select_tag("#{issuable.class.model_name.param_key}[assignee_id]", placeholder: 'Select a user', class: 'custom-form-control', null_user: true, selected: issuable.assignee_id, project: @target_project || @project, - current_user: true) + first_user: true, current_user: true)   = link_to 'Assign to me', '#', class: 'btn assign-to-me-link' .form-group -- cgit v1.2.1 From 50baa1fdd1be1090c0d8ae6f7346d82f07369657 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 21 Aug 2015 15:03:16 -0700 Subject: Add development section to doc. --- doc/reply_by_email/README.md | 4 ++-- lib/tasks/gitlab/check.rake | 28 ++++++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/doc/reply_by_email/README.md b/doc/reply_by_email/README.md index 1a0ce79bc5b..358e48abccc 100644 --- a/doc/reply_by_email/README.md +++ b/doc/reply_by_email/README.md @@ -87,13 +87,13 @@ In this example, we'll use the Gmail address `gitlab-replies@gmail.com`. If you' mail_room_enabled=true ``` -6. Restart GitLab +6. Restart GitLab: ```sh sudo service gitlab restart ``` -7. Check if everything is configured correctly +7. Check if everything is configured correctly: ```sh sudo bundle exec rake gitlab:reply_by_email:check RAILS_ENV=production diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index 3fbd429ef8d..a48e23e63a8 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -589,8 +589,13 @@ namespace :gitlab do check_address_formatted_correctly check_mail_room_config_exists check_imap_authentication - check_initd_configured_correctly - check_mail_room_running + + if Rails.env.production? + check_initd_configured_correctly + check_mail_room_running + else + check_foreman_configured_correctly + end else puts 'Reply by email is disabled in config/gitlab.yml' end @@ -635,6 +640,25 @@ namespace :gitlab do end end + def check_foreman_configured_correctly + print "Foreman configured correctly? ... " + + path = Rails.root.join("Procfile") + + if File.exist?(path) && File.read(path) =~ /mail_room:/ + puts "yes".green + else + puts "no".red + try_fixing_it( + "Enable mail_room in your Procfile." + ) + for_more_information( + "doc/reply_by_email/README.md" + ) + fix_and_rerun + end + end + def check_mail_room_running print "MailRoom running? ... " -- cgit v1.2.1 From 0366655311f3de0806eb1ad84abfffb881b5c609 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 21 Aug 2015 15:06:04 -0700 Subject: Fix check task for development. --- lib/tasks/gitlab/check.rake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index a48e23e63a8..6f7a13159fc 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -589,7 +589,7 @@ namespace :gitlab do check_address_formatted_correctly check_mail_room_config_exists check_imap_authentication - + if Rails.env.production? check_initd_configured_correctly check_mail_room_running @@ -645,7 +645,7 @@ namespace :gitlab do path = Rails.root.join("Procfile") - if File.exist?(path) && File.read(path) =~ /mail_room:/ + if File.exist?(path) && File.read(path) =~ /^mail_room:/ puts "yes".green else puts "no".red -- cgit v1.2.1 From 3abb356dd215235765f89c78c98655dc62688b77 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 21 Aug 2015 15:13:22 -0700 Subject: Add development documentation for real. --- doc/reply_by_email/README.md | 72 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/doc/reply_by_email/README.md b/doc/reply_by_email/README.md index 358e48abccc..91eea956e52 100644 --- a/doc/reply_by_email/README.md +++ b/doc/reply_by_email/README.md @@ -101,8 +101,76 @@ In this example, we'll use the Gmail address `gitlab-replies@gmail.com`. If you' 8. Reply by email should now be working. -Note: If you're running GitLab in development mode and using `foreman`, make sure to also uncomment the `mail_room` line in your `Procfile`. - ### Omnibus package installations TODO + +### Development + +1. Go to the GitLab installation directory. + +1. Find the `reply_by_email` section in `config/gitlab.yml`, enable the feature and enter the email address including a placeholder for the `reply_key`: + + ```yaml + reply_by_email: + enabled: true + address: "gitlab-replies+%{reply_key}@gmail.com" + ``` + + As mentioned, the part after `+` is ignored, and this will end up in the mailbox for `gitlab-replies@gmail.com`. + +2. Find `config/mail_room.yml.example` and copy it to `config/mail_room.yml`: + + ```sh + sudo cp config/mail_room.yml.example config/mail_room.yml + ``` + +3. Uncomment the configuration options in `config/mail_room.yml` and fill in the details for your specific IMAP server and email account: + + ```yaml + :mailboxes: + - + # IMAP server host + :host: "imap.gmail.com" + # IMAP server port + :port: 993 + # Whether the IMAP server uses SSL + :ssl: true + # Email account username. Usually the full email address. + :email: "gitlab-replies@gmail.com" + # Email account password + :password: "[REDACTED]" + # The name of the mailbox where incoming mail will end up. Usually "inbox". + :name: "inbox" + # Always "sidekiq". + :delivery_method: sidekiq + :delivery_options: + # The URL to the Redis server used by Sidekiq. Should match the URL in config/resque.yml. + :redis_url: redis://localhost:6379 + # Always "resque:gitlab". + :namespace: resque:gitlab + # Always "incoming_email". + :queue: incoming_email + # Always "EmailReceiverWorker" + :worker: EmailReceiverWorker + ``` + +4. Uncomment the `mail_room` line in your `Procfile`: + + ```yaml + mail_room: bundle exec mail_room -q -c config/mail_room.yml + ``` + +6. Restart GitLab: + + ```sh + bundle exec foreman start + ``` + +7. Check if everything is configured correctly: + + ```sh + bundle exec rake gitlab:reply_by_email:check RAILS_ENV=development + ``` + +8. Reply by email should now be working. -- cgit v1.2.1 From 15fc7bd6139f0b429c05c055b4cfab561c926e08 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 21 Aug 2015 16:09:55 -0700 Subject: No HTML-only email please --- lib/gitlab/email/html_cleaner.rb | 135 ----------------------------- lib/gitlab/email/reply_parser.rb | 24 ++--- spec/fixtures/emails/dutch.eml | 2 +- spec/fixtures/emails/html_only.eml | 93 -------------------- spec/fixtures/emails/plaintext_only.eml | 42 +++++++++ spec/lib/gitlab/email/reply_parser_spec.rb | 19 +++- 6 files changed, 65 insertions(+), 250 deletions(-) delete mode 100644 lib/gitlab/email/html_cleaner.rb delete mode 100644 spec/fixtures/emails/html_only.eml create mode 100644 spec/fixtures/emails/plaintext_only.eml diff --git a/lib/gitlab/email/html_cleaner.rb b/lib/gitlab/email/html_cleaner.rb deleted file mode 100644 index e1ae9eee56c..00000000000 --- a/lib/gitlab/email/html_cleaner.rb +++ /dev/null @@ -1,135 +0,0 @@ -# Taken mostly from Discourse's Email::HtmlCleaner -module Gitlab - module Email - # HtmlCleaner cleans up the extremely dirty HTML that many email clients - # generate by stripping out any excess divs or spans, removing styling in - # the process (which also makes the html more suitable to be parsed as - # Markdown). - class HtmlCleaner - # Elements to hoist all children out of - HTML_HOIST_ELEMENTS = %w(div span font table tbody th tr td) - # Node types to always delete - HTML_DELETE_ELEMENT_TYPES = [ - Nokogiri::XML::Node::DTD_NODE, - Nokogiri::XML::Node::COMMENT_NODE, - ] - - # Private variables: - # @doc - nokogiri document - # @out - same as @doc, but only if trimming has occured - def initialize(html) - if html.is_a?(String) - @doc = Nokogiri::HTML(html) - else - @doc = html - end - end - - class << self - # HtmlCleaner.trim(inp, opts={}) - # - # Arguments: - # inp - Either a HTML string or a Nokogiri document. - # Options: - # :return => :doc, :string - # Specify the desired return type. - # Defaults to the type of the input. - # A value of :string is equivalent to calling get_document_text() - # on the returned document. - def trim(inp, opts={}) - cleaner = HtmlCleaner.new(inp) - - opts[:return] ||= (inp.is_a?(String) ? :string : :doc) - - if opts[:return] == :string - cleaner.output_html - else - cleaner.output_document - end - end - - # HtmlCleaner.get_document_text(doc) - # - # Get the body portion of the document, including html, as a string. - def get_document_text(doc) - body = doc.xpath('//body') - if body - body.inner_html - else - doc.inner_html - end - end - end - - def output_document - @out ||= begin - doc = @doc - trim_process_node doc - add_newlines doc - doc - end - end - - def output_html - HtmlCleaner.get_document_text(output_document) - end - - private - - def add_newlines(doc) - # Replace
tags with a markdown \n - doc.xpath('//br').each do |br| - br.replace(new_linebreak_node doc, 2) - end - # Surround

tags with newlines, to help with line-wise postprocessing - # and ensure markdown paragraphs - doc.xpath('//p').each do |p| - p.before(new_linebreak_node doc) - p.after(new_linebreak_node doc, 2) - end - end - - def new_linebreak_node(doc, count=1) - Nokogiri::XML::Text.new("\n" * count, doc) - end - - def trim_process_node(node) - if should_hoist?(node) - hoisted = trim_hoist_element node - hoisted.each { |child| trim_process_node child } - elsif should_delete?(node) - node.remove - else - if children = node.children - children.each { |child| trim_process_node child } - end - end - - node - end - - def trim_hoist_element(element) - hoisted = [] - element.children.each do |child| - element.before(child) - hoisted << child - end - element.remove - hoisted - end - - def should_hoist?(node) - return false unless node.element? - HTML_HOIST_ELEMENTS.include? node.name - end - - def should_delete?(node) - return true if HTML_DELETE_ELEMENT_TYPES.include? node.type - return true if node.element? && node.name == 'head' - return true if node.text? && node.text.strip.blank? - - false - end - end - end -end diff --git a/lib/gitlab/email/reply_parser.rb b/lib/gitlab/email/reply_parser.rb index 6e768e46a71..6ed36b51f12 100644 --- a/lib/gitlab/email/reply_parser.rb +++ b/lib/gitlab/email/reply_parser.rb @@ -23,31 +23,19 @@ module Gitlab private def select_body(message) - html = nil - text = nil - - if message.multipart? - html = fix_charset(message.html_part) - text = fix_charset(message.text_part) - elsif message.content_type =~ /text\/html/ - html = fix_charset(message) - end + text = message.text_part if message.multipart? + text ||= message if message.content_type !~ /text\/html/ - # prefer plain text - return text if text + return "" unless text - if html - body = HtmlCleaner.new(html).output_html - else - body = fix_charset(message) - end + text = fix_charset(text) # Certain trigger phrases that means we didn't parse correctly - if body =~ /(Content\-Type\:|multipart\/alternative|text\/plain)/ + if text =~ /(Content\-Type\:|multipart\/alternative|text\/plain)/ return "" end - body + text end # Force encoding to UTF-8 on a Mail::Message or Mail::Part diff --git a/spec/fixtures/emails/dutch.eml b/spec/fixtures/emails/dutch.eml index 7be08dc4938..3142bf30c3b 100644 --- a/spec/fixtures/emails/dutch.eml +++ b/spec/fixtures/emails/dutch.eml @@ -17,4 +17,4 @@ Dit is een antwoord in het Nederlands. Op 18 juli 2013 10:23 schreef Sander Datema het volgende: -Dit is de originele post. \ No newline at end of file +Dit is de originele post. diff --git a/spec/fixtures/emails/html_only.eml b/spec/fixtures/emails/html_only.eml deleted file mode 100644 index 561b8db2c79..00000000000 --- a/spec/fixtures/emails/html_only.eml +++ /dev/null @@ -1,93 +0,0 @@ - -Delivered-To: walter@breakingbad.com -Received: by 10.64.13.41 with SMTP id m9csp29769iec; - Thu, 20 Jun 2013 08:53:22 -0700 (PDT) -X-Received: by 10.252.23.9 with SMTP id p9mr4055675lag.4.1371743601980; - Thu, 20 Jun 2013 08:53:21 -0700 (PDT) -Received: from mail-la0-x229.google.com (mail-la0-x229.google.com [2a00:1450:4010:c03::229]) - by mx.google.com with ESMTPS id u4si430203lae.48.2013.06.20.08.53.20 - for - (version=TLSv1 cipher=ECDHE-RSA-RC4-SHA bits=128/128); - Thu, 20 Jun 2013 08:53:21 -0700 (PDT) -X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; - d=google.com; s=20120113; - h=x-forwarded-to:x-forwarded-for:delivered-to:x-return-path - :content-type:mime-version:content-transfer-encoding:x-mailer - :message-id:date:subject:from:in-reply-to:to:resent-date:resent-from - :resent-to:resent-subject:resent-message-id:resent-user-agent - :x-scanned-by:x-gm-message-state; - bh=9O67r74ofh9WkEaKTRB/frQ3MKOtQlbCac2mz0/MiyY=; - b=YVAo2/JDMP53RxDmqDEKNcEMtggtfaVyq2DoseZ6vBAfB7G6NtHC9ZEkRs4oGhk6LU - fnyAPe0wnz5d9WINoMAuuTRIhplLxzcqysduSnAJAQ2qqR7mFBnlj9wJeVEKltNwmUME - nPwxsf8go20VBzrZCtECPedcLi60wbl32NCXVn0qwt2LvKiy6ktSS5Xgb4zY8i4dfXAP - 6Y5gu32boooWIb9DkH1TJkn3C0RrEugNlw/DUnXrnkFefgxWF3pt/zcoW/wYRyikOdx+ - smBClgR9my6QmsS2KsQrMvWJZUva7fddTiZ6FC22e4hW+8Wha0RaZOZu5O7hjg6G4/1g - IEyg== -X-Received: by 10.112.55.9 with SMTP id n9mr5916187lbp.5.1371743600857; - Thu, 20 Jun 2013 08:53:20 -0700 (PDT) -X-Forwarded-To: walter@breakingbad.com -X-Forwarded-For: walter@breakingbad.com -Delivered-To: walter@breakingbad.com -Content-Type: text/html; charset="us-ascii" -MIME-Version: 1.0 -Content-Transfer-Encoding: quoted-printable -X-Mailer: BlackBerry Email (10.1.0.1720) -Message-ID: <20130619231548.6307981.74194.2379@breakingbad.com> -Date: Wed, 19 Jun 2013 19:15:48 -0400 -Subject: Re: [Discourse Meta] [PM] re: Regarding your post in "Site - Customization not working" -From: aaron@breakingbad.com -In-Reply-To: <51c238655a394_5f4e3ce6690667bd@tiefighter2.mail> -To: reply+20c1b0a8bd1a63c0163cc7e7641ca06b@appmail.adventuretime.ooo -ReSent-Date: Thu, 20 Jun 2013 11:53:08 -0400 (EDT) -ReSent-From: Aaron -ReSent-Subject: Re: [Discourse Meta] [PM] re: Regarding your post in "Site - Customization not working" -X-Gm-Message-State: ALoCoQl1BtN83rAX7At808XAPv1yCqUK3Du2IvK7eCyY3jsI77u4e5cak28307pYYHAo1JlO/Eu9 - -

The EC2 instance - I've seen that th= -ere tends to be odd and unrecommended settings on the Bitnami installs that= - I've checked out.
= - = -

= - = -
= - = - = -
= -From: Grizzly B via Discourse Meta
Sent: Wednesday, J= -une 19, 2013 19:02
To: aaron@breakingbad.com
= -Reply To: Grizzly B via Discourse Meta
Subject: [Disc= -ourse Meta] [PM] re: Regarding your post in "Site Customization
not wor= -king"

Grizzly B just sent you a private message

- -

Log in to our EC2 instance -or- log into a new Digital Ocean instanc= -e?

- -

Please visit this link to respond: http://= -meta.discourse.org/t/regarding-your-post-in-site-customization-not-working/= -7641/5

- -

To unsubscribe from these emails, visit your user preferences.

-
diff --git a/spec/fixtures/emails/plaintext_only.eml b/spec/fixtures/emails/plaintext_only.eml new file mode 100644 index 00000000000..1bfaec771dc --- /dev/null +++ b/spec/fixtures/emails/plaintext_only.eml @@ -0,0 +1,42 @@ +Delivered-To: reply@discourse.org +Return-Path: +MIME-Version: 1.0 +From: +To: + =?utf-8?Q?Discourse_Meta?= + +Subject: + =?utf-8?Q?Re:_[Discourse_Meta]_[Lounge]_Testing_default_email_replies?= +Importance: Normal +Date: Fri, 28 Nov 2014 21:29:10 +0000 +In-Reply-To: +References: + , +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: base64 + +IyMjIHJlcGx5IGZyb20gZGVmYXVsdCBtYWlsIGNsaWVudCBpbiBXaW5kb3dzIDguMSBNZXRybw0K +DQoNClRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWlj +ayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gg +anVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0 +aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cu +IFRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBi +cm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVt +cHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUg +bGF6eSBkb2cuDQoNCg0KVGhpcyBpcyBhICoqYm9sZCoqIHdvcmQgaW4gTWFya2Rvd24NCg0KDQpU +aGlzIGlzIGEgbGluayBodHRwOi8vZXhhbXBsZS5jb20NCiANCg0KDQoNCg0KDQpGcm9tOiBBcnBp +dCBKYWxhbg0KU2VudDog4oCORnJpZGF54oCOLCDigI5Ob3ZlbWJlcuKAjiDigI4yOOKAjiwg4oCO +MjAxNCDigI4xMuKAjjrigI4zNeKAjiDigI5QTQ0KVG86IGplZmYgYXR3b29kDQoNCg0KDQoNCg0K +DQogdGVjaEFQSg0KTm92ZW1iZXIgMjggDQoNClRlc3QgcmVwbHkuDQoNCkZpcnN0IHBhcmFncmFw +aC4NCg0KU2Vjb25kIHBhcmFncmFwaC4NCg0KDQoNClRvIHJlc3BvbmQsIHJlcGx5IHRvIHRoaXMg +ZW1haWwgb3IgdmlzaXQgaHR0cHM6Ly9tZXRhLmRpc2NvdXJzZS5vcmcvdC90ZXN0aW5nLWRlZmF1 +bHQtZW1haWwtcmVwbGllcy8yMjYzOC8zIGluIHlvdXIgYnJvd3Nlci4NCg0KDQoNClByZXZpb3Vz +IFJlcGxpZXMNCg0KIGNvZGluZ2hvcnJvcg0KTm92ZW1iZXIgMjggDQoNCldlJ3JlIHRlc3Rpbmcg +dGhlIGxhdGVzdCBHaXRIdWIgZW1haWwgcHJvY2Vzc2luZyBsaWJyYXJ5IHdoaWNoIHdlIGFyZSBp +bnRlZ3JhdGluZyBub3cuDQoNCmh0dHBzOi8vZ2l0aHViLmNvbS9naXRodWIvZW1haWxfcmVwbHlf +cGFyc2VyDQoNCkdvIGFoZWFkIGFuZCByZXBseSB0byB0aGlzIHRvcGljIGFuZCBJJ2xsIHJlcGx5 +IGZyb20gdmFyaW91cyBlbWFpbCBjbGllbnRzIGZvciB0ZXN0aW5nLg0KDQoNCg0KDQoNClRvIHJl +c3BvbmQsIHJlcGx5IHRvIHRoaXMgZW1haWwgb3IgdmlzaXQgaHR0cHM6Ly9tZXRhLmRpc2NvdXJz +ZS5vcmcvdC90ZXN0aW5nLWRlZmF1bHQtZW1haWwtcmVwbGllcy8yMjYzOC8zIGluIHlvdXIgYnJv +d3Nlci4NCg0KDQpUbyB1bnN1YnNjcmliZSBmcm9tIHRoZXNlIGVtYWlscywgdmlzaXQgeW91ciB1 +c2VyIHByZWZlcmVuY2VzLg== diff --git a/spec/lib/gitlab/email/reply_parser_spec.rb b/spec/lib/gitlab/email/reply_parser_spec.rb index a94c92ad53c..7cae1da8050 100644 --- a/spec/lib/gitlab/email/reply_parser_spec.rb +++ b/spec/lib/gitlab/email/reply_parser_spec.rb @@ -19,9 +19,22 @@ describe Gitlab::Email::ReplyParser do expect(test_parse_body(fixture_file("emails/no_content_reply.eml"))).to eq("") end - it "can parse the html section" do - expect(test_parse_body(fixture_file("emails/html_only.eml"))).to eq("The EC2 instance - I've seen that there tends to be odd and " + - "unrecommended settings on the Bitnami installs that I've checked out.") + it "properly renders plaintext-only email" do + expect(test_parse_body(fixture_file("emails/plaintext_only.eml"))). + to eq( + <<-BODY.strip_heredoc.chomp + ### reply from default mail client in Windows 8.1 Metro + + + The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. + + + This is a **bold** word in Markdown + + + This is a link http://example.com + BODY + ) end it "supports a Dutch reply" do -- cgit v1.2.1 From a9e409179bb6be53e1fb45e930739fc73dbb77ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Fri, 21 Aug 2015 15:05:43 +0200 Subject: Create cross-reference for closing references on commits pushed to non-default branches. I also revamped the tests on "closing reference commits" (= "Fixes #xxxx" for example). Now, there are two different contexts: - when the commits with "closing reference" are pushed to the default branch, - when the commits with "closing reference" are pushed to a non-default branch. Closes gitlab-org/gitlab-ce#2190. --- CHANGELOG | 1 + app/services/git_push_service.rb | 15 ++++++--- spec/services/git_push_service_spec.rb | 57 ++++++++++++++++++++-------------- 3 files changed, 45 insertions(+), 28 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index dd5162a53db..fe0992b5a0c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,7 @@ v 8.0.0 (unreleased) - Allow displaying of archived projects in the admin interface (Artem Sidorenko) - Allow configuration of import sources for new projects (Artem Sidorenko) - Search for comments should be case insensetive + - Create cross-reference for closing references on commits pushed to non-default branches (Maël Valais) v 7.14.0 (unreleased) - Fix bug where non-project members of the target project could set labels on new merge requests. diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index 81535450ac1..0a73244774a 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -78,24 +78,29 @@ class GitPushService # For push with 1k commits it prevents 900+ requests in database author = nil + # Keep track of the issues that will be actually closed because they are on a default branch. + # Hence, when creating cross-reference notes, the not-closed issues (on non-default branches) + # will also have cross-reference. + actually_closed_issues = [] + if issues_to_close.present? && is_default_branch author ||= commit_user(commit) - + actually_closed_issues = issues_to_close issues_to_close.each do |issue| Issues::CloseService.new(project, author, {}).execute(issue, commit) end end if project.default_issues_tracker? - create_cross_reference_notes(commit, issues_to_close) + create_cross_reference_notes(commit, actually_closed_issues) end end end def create_cross_reference_notes(commit, issues_to_close) - # Create cross-reference notes for any other references. Omit any issues that were referenced in an - # issue-closing phrase, or have already been mentioned from this commit (probably from this commit - # being pushed to a different branch). + # Create cross-reference notes for any other references than those given in issues_to_close. + # Omit any issues that were referenced in an issue-closing phrase, or have already been + # mentioned from this commit (probably from this commit being pushed to a different branch). refs = commit.references(project, user) - issues_to_close refs.reject! { |r| commit.has_mentioned?(r) } diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index 62cef9db534..c483060fd73 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -197,7 +197,7 @@ describe GitPushService do end end - describe "closing issues from pushed commits" do + describe "closing issues from pushed commits containing a closing reference" do let(:issue) { create :issue, project: project } let(:other_issue) { create :issue, project: project } let(:commit_author) { create :user } @@ -215,36 +215,47 @@ describe GitPushService do and_return([closing_commit]) end - it "closes issues with commit messages" do - service.execute(project, user, @oldrev, @newrev, @ref) - - expect(Issue.find(issue.id)).to be_closed - end + context "to default branches" do + it "closes issues" do + service.execute(project, user, @oldrev, @newrev, @ref) + expect(Issue.find(issue.id)).to be_closed + end - it "doesn't create cross-reference notes for a closing reference" do - expect do + it "adds a note indicating that the issue is now closed" do + expect(SystemNoteService).to receive(:change_status).with(issue, project, commit_author, "closed", closing_commit) service.execute(project, user, @oldrev, @newrev, @ref) - end.not_to change { Note.where(project_id: project.id, system: true, commit_id: closing_commit.id).count } - end + end - it "doesn't close issues when pushed to non-default branches" do - allow(project).to receive(:default_branch).and_return('durf') + it "doesn't create additional cross-reference notes" do + expect(SystemNoteService).not_to receive(:cross_reference) + service.execute(project, user, @oldrev, @newrev, @ref) + end - # The push still shouldn't create cross-reference notes. - expect do - service.execute(project, user, @oldrev, @newrev, 'refs/heads/hurf') - end.not_to change { Note.where(project_id: project.id, system: true).count } + it "doesn't close issues when external issue tracker is in use" do + allow(project).to receive(:default_issues_tracker?).and_return(false) - expect(Issue.find(issue.id)).to be_opened + # The push still shouldn't create cross-reference notes. + expect do + service.execute(project, user, @oldrev, @newrev, 'refs/heads/hurf') + end.not_to change { Note.where(project_id: project.id, system: true).count } + end end - it "doesn't close issues when external issue tracker is in use" do - allow(project).to receive(:default_issues_tracker?).and_return(false) + context "to non-default branches" do + before do + # Make sure the "default" branch is different + allow(project).to receive(:default_branch).and_return('not-master') + end + + it "creates cross-reference notes" do + expect(SystemNoteService).to receive(:cross_reference).with(issue, closing_commit, commit_author) + service.execute(project, user, @oldrev, @newrev, @ref) + end - # The push still shouldn't create cross-reference notes. - expect do - service.execute(project, user, @oldrev, @newrev, 'refs/heads/hurf') - end.not_to change { Note.where(project_id: project.id, system: true).count } + it "doesn't close issues" do + service.execute(project, user, @oldrev, @newrev, @ref) + expect(Issue.find(issue.id)).to be_opened + end end end -- cgit v1.2.1 From cddf03f7179aae5bb7568b2c3def54b19eed1f66 Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Sat, 22 Aug 2015 19:11:02 +0000 Subject: Specify the branch name of the blog post to prevent the use of spaces. --- doc/release/monthly.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/release/monthly.md b/doc/release/monthly.md index 12d6a84b68e..c1ed9e3b80e 100644 --- a/doc/release/monthly.md +++ b/doc/release/monthly.md @@ -1,8 +1,9 @@ # Monthly Release -NOTE: This is a guide used by the GitLab B.V. developers. +NOTE: This is a guide used by the GitLab the company to release GitLab. +As an end user you do not need to use this guide. -It starts 7 working days before the release. +The process starts 7 working days before the release. The release manager doesn't have to perform all the work but must ensure someone is assigned. The current release manager must schedule the appointment of the next release manager. The new release manager should create overall issue to track the progress. @@ -164,7 +165,7 @@ Tweet about the RC release: 1. Create a merge request on [GitLab.com](https://gitlab.com/gitlab-com/www-gitlab-com/tree/master) 1. Assign to one reviewer who will fix spelling issues by editing the branch (either with a git client or by using the online editor) 1. Comment to the reviewer: '@person Please mention the whole team as soon as you are done (3 workdays before release at the latest)' -1. Create a complete copy of the [release blog template](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/doc/release_blog_template.md) for the release after this. +1. Create a new merge request with complete copy of the [release blog template](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/doc/release_blog_template.md) for the next release using the branch name `release-x-x-x`. ## Create CE, EE, CI stable versions -- cgit v1.2.1 From 97cc91d21d28a6482dc6ab040db31598a786f56c Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 23 Aug 2015 05:44:49 -0700 Subject: 7.14.0 is released --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index fe0992b5a0c..44ffb56d5fd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,7 +11,7 @@ v 8.0.0 (unreleased) - Search for comments should be case insensetive - Create cross-reference for closing references on commits pushed to non-default branches (Maël Valais) -v 7.14.0 (unreleased) +v 7.14.0 - Fix bug where non-project members of the target project could set labels on new merge requests. - Update default robots.txt rules to disallow crawling of irrelevant pages (Ben Bodenmiller) - Fix redirection after sign in when using auto_sign_in_with_provider -- cgit v1.2.1 From ed1d4fa477789659f9343593bf06d50e70750561 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 7 Aug 2015 00:06:20 -0700 Subject: Remove user OAuth tokens stored in database for Bitbucket, GitHub, and GitLab and request them each session. Pass these tokens to the project import data. This prevents the need to encrypt these tokens and clear them in case they expire or get revoked. For example, if you deleted and re-created OAuth2 keys for Bitbucket, you would get an Error 500 with no way to recover: ``` Started GET "/import/bitbucket/status" for x.x.x.x at 2015-08-07 05:24:10 +0000 Processing by Import::BitbucketController#status as HTML Completed 500 Internal Server Error in 607ms (ActiveRecord: 2.3ms) NameError (uninitialized constant Import::BitbucketController::Unauthorized): app/controllers/import/bitbucket_controller.rb:77:in `rescue in go_to_bitbucket_for_permissions' app/controllers/import/bitbucket_controller.rb:74:in `go_to_bitbucket_for_permissions' app/controllers/import/bitbucket_controller.rb:86:in `bitbucket_unauthorized' ``` Closes #1871 --- CHANGELOG | 1 + app/controllers/import/bitbucket_controller.rb | 23 ++++++++++++------ app/controllers/import/github_controller.rb | 16 ++++++++----- app/controllers/import/gitlab_controller.rb | 16 ++++++++----- app/workers/repository_import_worker.rb | 3 ++- ...0150814065925_remove_oauth_tokens_from_users.rb | 8 +++++++ db/schema.rb | 4 ---- lib/gitlab/bitbucket_import/importer.rb | 15 +++++++----- lib/gitlab/bitbucket_import/key_adder.rb | 7 +++--- lib/gitlab/bitbucket_import/key_deleter.rb | 7 ++++-- lib/gitlab/bitbucket_import/project_creator.rb | 12 ++++++---- lib/gitlab/github_import/importer.rb | 4 +++- lib/gitlab/github_import/project_creator.rb | 13 ++++++---- lib/gitlab/gitlab_import/importer.rb | 14 ++++++----- lib/gitlab/gitlab_import/project_creator.rb | 12 ++++++---- .../import/bitbucket_controller_spec.rb | 28 ++++++++++++++-------- spec/controllers/import/github_controller_spec.rb | 21 +++++++++++----- spec/controllers/import/gitlab_controller_spec.rb | 22 +++++++++++------ .../bitbucket_import/project_creator_spec.rb | 7 ++++-- .../gitlab/github_import/project_creator_spec.rb | 6 +++-- .../gitlab/gitlab_import/project_creator_spec.rb | 6 +++-- 21 files changed, 162 insertions(+), 83 deletions(-) create mode 100644 db/migrate/20150814065925_remove_oauth_tokens_from_users.rb diff --git a/CHANGELOG b/CHANGELOG index 44ffb56d5fd..7eb6ab81dd9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.0.0 (unreleased) + - Remove user OAuth tokens from the database and request new tokens each session (Stan Hu) - Only show recent push event if the branch still exists or a recent merge request has not been created (Stan Hu) - Remove satellites - Better performance for web editor (switched from satellites to rugged) diff --git a/app/controllers/import/bitbucket_controller.rb b/app/controllers/import/bitbucket_controller.rb index 4e6c0b66634..f84f85a7df8 100644 --- a/app/controllers/import/bitbucket_controller.rb +++ b/app/controllers/import/bitbucket_controller.rb @@ -13,10 +13,9 @@ class Import::BitbucketController < Import::BaseController access_token = client.get_token(request_token, params[:oauth_verifier], callback_import_bitbucket_url) - current_user.bitbucket_access_token = access_token.token - current_user.bitbucket_access_token_secret = access_token.secret + session[:bitbucket_access_token] = access_token.token + session[:bitbucket_access_token_secret] = access_token.secret - current_user.save redirect_to status_import_bitbucket_url end @@ -46,19 +45,20 @@ class Import::BitbucketController < Import::BaseController namespace = get_or_create_namespace || (render and return) - unless Gitlab::BitbucketImport::KeyAdder.new(repo, current_user).execute + unless Gitlab::BitbucketImport::KeyAdder.new(repo, current_user, access_params).execute @access_denied = true render return end - @project = Gitlab::BitbucketImport::ProjectCreator.new(repo, namespace, current_user).execute + @project = Gitlab::BitbucketImport::ProjectCreator.new(repo, namespace, current_user, access_params).execute end private def client - @client ||= Gitlab::BitbucketImport::Client.new(current_user.bitbucket_access_token, current_user.bitbucket_access_token_secret) + @client ||= Gitlab::BitbucketImport::Client.new(session[:bitbucket_access_token], + session[:bitbucket_access_token_secret]) end def verify_bitbucket_import_enabled @@ -66,7 +66,7 @@ class Import::BitbucketController < Import::BaseController end def bitbucket_auth - if current_user.bitbucket_access_token.blank? + if session[:bitbucket_access_token].blank? go_to_bitbucket_for_permissions end end @@ -81,4 +81,13 @@ class Import::BitbucketController < Import::BaseController def bitbucket_unauthorized go_to_bitbucket_for_permissions end + + private + + def access_params + { + bitbucket_access_token: session[:bitbucket_access_token], + bitbucket_access_token_secret: session[:bitbucket_access_token_secret] + } + end end diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb index b9f99c1b88a..f21fbd9ecca 100644 --- a/app/controllers/import/github_controller.rb +++ b/app/controllers/import/github_controller.rb @@ -5,9 +5,7 @@ class Import::GithubController < Import::BaseController rescue_from Octokit::Unauthorized, with: :github_unauthorized def callback - token = client.get_token(params[:code]) - current_user.github_access_token = token - current_user.save + session[:github_access_token] = client.get_token(params[:code]) redirect_to status_import_github_url end @@ -39,13 +37,13 @@ class Import::GithubController < Import::BaseController namespace = get_or_create_namespace || (render and return) - @project = Gitlab::GithubImport::ProjectCreator.new(repo, namespace, current_user).execute + @project = Gitlab::GithubImport::ProjectCreator.new(repo, namespace, current_user, access_params).execute end private def client - @client ||= Gitlab::GithubImport::Client.new(current_user.github_access_token) + @client ||= Gitlab::GithubImport::Client.new(session[:github_access_token]) end def verify_github_import_enabled @@ -53,7 +51,7 @@ class Import::GithubController < Import::BaseController end def github_auth - if current_user.github_access_token.blank? + if session[:github_access_token].blank? go_to_github_for_permissions end end @@ -65,4 +63,10 @@ class Import::GithubController < Import::BaseController def github_unauthorized go_to_github_for_permissions end + + private + + def access_params + { github_access_token: session[:github_access_token] } + end end diff --git a/app/controllers/import/gitlab_controller.rb b/app/controllers/import/gitlab_controller.rb index 1b8962d8924..27af19f5f61 100644 --- a/app/controllers/import/gitlab_controller.rb +++ b/app/controllers/import/gitlab_controller.rb @@ -5,9 +5,7 @@ class Import::GitlabController < Import::BaseController rescue_from OAuth2::Error, with: :gitlab_unauthorized def callback - token = client.get_token(params[:code], callback_import_gitlab_url) - current_user.gitlab_access_token = token - current_user.save + session[:gitlab_access_token] = client.get_token(params[:code], callback_import_gitlab_url) redirect_to status_import_gitlab_url end @@ -36,13 +34,13 @@ class Import::GitlabController < Import::BaseController namespace = get_or_create_namespace || (render and return) - @project = Gitlab::GitlabImport::ProjectCreator.new(repo, namespace, current_user).execute + @project = Gitlab::GitlabImport::ProjectCreator.new(repo, namespace, current_user, access_params).execute end private def client - @client ||= Gitlab::GitlabImport::Client.new(current_user.gitlab_access_token) + @client ||= Gitlab::GitlabImport::Client.new(session[:gitlab_access_token]) end def verify_gitlab_import_enabled @@ -50,7 +48,7 @@ class Import::GitlabController < Import::BaseController end def gitlab_auth - if current_user.gitlab_access_token.blank? + if session[:gitlab_access_token].blank? go_to_gitlab_for_permissions end end @@ -62,4 +60,10 @@ class Import::GitlabController < Import::BaseController def gitlab_unauthorized go_to_gitlab_for_permissions end + + private + + def access_params + { gitlab_access_token: session[:gitlab_access_token] } + end end diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb index b546f8777e1..f2ba2e15e7b 100644 --- a/app/workers/repository_import_worker.rb +++ b/app/workers/repository_import_worker.rb @@ -25,9 +25,10 @@ class RepositoryImportWorker end return project.import_fail unless data_import_result + Gitlab::BitbucketImport::KeyDeleter.new(project).execute if project.import_type == 'bitbucket' + project.import_finish project.save ProjectCacheWorker.perform_async(project.id) - Gitlab::BitbucketImport::KeyDeleter.new(project).execute if project.import_type == 'bitbucket' end end diff --git a/db/migrate/20150814065925_remove_oauth_tokens_from_users.rb b/db/migrate/20150814065925_remove_oauth_tokens_from_users.rb new file mode 100644 index 00000000000..de2078a9268 --- /dev/null +++ b/db/migrate/20150814065925_remove_oauth_tokens_from_users.rb @@ -0,0 +1,8 @@ +class RemoveOauthTokensFromUsers < ActiveRecord::Migration + def change + remove_column :users, :github_access_token, :string + remove_column :users, :gitlab_access_token, :string + remove_column :users, :bitbucket_access_token, :string + remove_column :users, :bitbucket_access_token_secret, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 2b9a3e7f011..108c48bf321 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -526,13 +526,9 @@ ActiveRecord::Schema.define(version: 20150818213832) do t.string "unconfirmed_email" t.boolean "hide_no_ssh_key", default: false t.string "website_url", default: "", null: false - t.string "github_access_token" - t.string "gitlab_access_token" t.string "notification_email" t.boolean "hide_no_password", default: false t.boolean "password_automatically_set", default: false - t.string "bitbucket_access_token" - t.string "bitbucket_access_token_secret" t.string "location" t.string "encrypted_otp_secret" t.string "encrypted_otp_secret_iv" diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb index 42c93707caa..d8a7d29f1bf 100644 --- a/lib/gitlab/bitbucket_import/importer.rb +++ b/lib/gitlab/bitbucket_import/importer.rb @@ -5,7 +5,10 @@ module Gitlab def initialize(project) @project = project - @client = Client.new(project.creator.bitbucket_access_token, project.creator.bitbucket_access_token_secret) + import_data = project.import_data.try(:data) + bb_session = import_data["bb_session"] if import_data + @client = Client.new(bb_session["bitbucket_access_token"], + bb_session["bitbucket_access_token_secret"]) @formatter = Gitlab::ImportFormatter.new end @@ -16,12 +19,12 @@ module Gitlab #Issues && Comments issues = client.issues(project_identifier) - + issues["issues"].each do |issue| body = @formatter.author_line(issue["reported_by"]["username"], issue["content"]) - + comments = client.issue_comments(project_identifier, issue["local_id"]) - + if comments.any? body += @formatter.comments_header end @@ -31,13 +34,13 @@ module Gitlab end project.issues.create!( - description: body, + description: body, title: issue["title"], state: %w(resolved invalid duplicate wontfix).include?(issue["status"]) ? 'closed' : 'opened', author_id: gl_user_id(project, issue["reported_by"]["username"]) ) end - + true end diff --git a/lib/gitlab/bitbucket_import/key_adder.rb b/lib/gitlab/bitbucket_import/key_adder.rb index 9931aa7e029..0b63f025d0a 100644 --- a/lib/gitlab/bitbucket_import/key_adder.rb +++ b/lib/gitlab/bitbucket_import/key_adder.rb @@ -3,14 +3,15 @@ module Gitlab class KeyAdder attr_reader :repo, :current_user, :client - def initialize(repo, current_user) + def initialize(repo, current_user, access_params) @repo, @current_user = repo, current_user - @client = Client.new(current_user.bitbucket_access_token, current_user.bitbucket_access_token_secret) + @client = Client.new(access_params[:bitbucket_access_token], + access_params[:bitbucket_access_token_secret]) end def execute return false unless BitbucketImport.public_key.present? - + project_identifier = "#{repo["owner"]}/#{repo["slug"]}" client.add_deploy_key(project_identifier, BitbucketImport.public_key) diff --git a/lib/gitlab/bitbucket_import/key_deleter.rb b/lib/gitlab/bitbucket_import/key_deleter.rb index 1a24a86fc37..f4dd393ad29 100644 --- a/lib/gitlab/bitbucket_import/key_deleter.rb +++ b/lib/gitlab/bitbucket_import/key_deleter.rb @@ -6,12 +6,15 @@ module Gitlab def initialize(project) @project = project @current_user = project.creator - @client = Client.new(current_user.bitbucket_access_token, current_user.bitbucket_access_token_secret) + import_data = project.import_data.try(:data) + bb_session = import_data["bb_session"] if import_data + @client = Client.new(bb_session["bitbucket_access_token"], + bb_session["bitbucket_access_token_secret"]) end def execute return false unless BitbucketImport.public_key.present? - + client.delete_deploy_key(project.import_source, BitbucketImport.public_key) true diff --git a/lib/gitlab/bitbucket_import/project_creator.rb b/lib/gitlab/bitbucket_import/project_creator.rb index 54420e62c90..35e34d033e0 100644 --- a/lib/gitlab/bitbucket_import/project_creator.rb +++ b/lib/gitlab/bitbucket_import/project_creator.rb @@ -1,16 +1,17 @@ module Gitlab module BitbucketImport class ProjectCreator - attr_reader :repo, :namespace, :current_user + attr_reader :repo, :namespace, :current_user, :session_data - def initialize(repo, namespace, current_user) + def initialize(repo, namespace, current_user, session_data) @repo = repo @namespace = namespace @current_user = current_user + @session_data = session_data end def execute - ::Projects::CreateService.new(current_user, + project = ::Projects::CreateService.new(current_user, name: repo["name"], path: repo["slug"], description: repo["description"], @@ -18,8 +19,11 @@ module Gitlab visibility_level: repo["is_private"] ? Gitlab::VisibilityLevel::PRIVATE : Gitlab::VisibilityLevel::PUBLIC, import_type: "bitbucket", import_source: "#{repo["owner"]}/#{repo["slug"]}", - import_url: "ssh://git@bitbucket.org/#{repo["owner"]}/#{repo["slug"]}.git" + import_url: "ssh://git@bitbucket.org/#{repo["owner"]}/#{repo["slug"]}.git", ).execute + + project.create_import_data(data: { "bb_session" => session_data } ) + project end end end diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb index 98039a76dcd..8c106a61735 100644 --- a/lib/gitlab/github_import/importer.rb +++ b/lib/gitlab/github_import/importer.rb @@ -5,7 +5,9 @@ module Gitlab def initialize(project) @project = project - @client = Client.new(project.creator.github_access_token) + import_data = project.import_data.try(:data) + github_session = import_data["github_session"] if import_data + @client = Client.new(github_session["github_access_token"]) @formatter = Gitlab::ImportFormatter.new end diff --git a/lib/gitlab/github_import/project_creator.rb b/lib/gitlab/github_import/project_creator.rb index 2723eec933e..8c27ebd1ce8 100644 --- a/lib/gitlab/github_import/project_creator.rb +++ b/lib/gitlab/github_import/project_creator.rb @@ -1,16 +1,18 @@ module Gitlab module GithubImport class ProjectCreator - attr_reader :repo, :namespace, :current_user + attr_reader :repo, :namespace, :current_user, :session_data - def initialize(repo, namespace, current_user) + def initialize(repo, namespace, current_user, session_data) @repo = repo @namespace = namespace @current_user = current_user + @session_data = session_data end def execute - ::Projects::CreateService.new(current_user, + project = ::Projects::CreateService.new( + current_user, name: repo.name, path: repo.name, description: repo.description, @@ -18,8 +20,11 @@ module Gitlab visibility_level: repo.private ? Gitlab::VisibilityLevel::PRIVATE : Gitlab::VisibilityLevel::PUBLIC, import_type: "github", import_source: repo.full_name, - import_url: repo.clone_url.sub("https://", "https://#{current_user.github_access_token}@") + import_url: repo.clone_url.sub("https://", "https://#{@session_data[:github_access_token]}@") ).execute + + project.create_import_data(data: { "github_session" => session_data } ) + project end end end diff --git a/lib/gitlab/gitlab_import/importer.rb b/lib/gitlab/gitlab_import/importer.rb index c5304a0699b..50594d2b24f 100644 --- a/lib/gitlab/gitlab_import/importer.rb +++ b/lib/gitlab/gitlab_import/importer.rb @@ -5,7 +5,9 @@ module Gitlab def initialize(project) @project = project - @client = Client.new(project.creator.gitlab_access_token) + import_data = project.import_data.try(:data) + gitlab_session = import_data["gitlab_session"] if import_data + @client = Client.new(gitlab_session["gitlab_access_token"]) @formatter = Gitlab::ImportFormatter.new end @@ -14,12 +16,12 @@ module Gitlab #Issues && Comments issues = client.issues(project_identifier) - + issues.each do |issue| body = @formatter.author_line(issue["author"]["name"], issue["description"]) - + comments = client.issue_comments(project_identifier, issue["id"]) - + if comments.any? body += @formatter.comments_header end @@ -29,13 +31,13 @@ module Gitlab end project.issues.create!( - description: body, + description: body, title: issue["title"], state: issue["state"], author_id: gl_user_id(project, issue["author"]["id"]) ) end - + true end diff --git a/lib/gitlab/gitlab_import/project_creator.rb b/lib/gitlab/gitlab_import/project_creator.rb index f0d7141bf56..d9452de6a50 100644 --- a/lib/gitlab/gitlab_import/project_creator.rb +++ b/lib/gitlab/gitlab_import/project_creator.rb @@ -1,16 +1,17 @@ module Gitlab module GitlabImport class ProjectCreator - attr_reader :repo, :namespace, :current_user + attr_reader :repo, :namespace, :current_user, :session_data - def initialize(repo, namespace, current_user) + def initialize(repo, namespace, current_user, session_data) @repo = repo @namespace = namespace @current_user = current_user + @session_data = session_data end def execute - ::Projects::CreateService.new(current_user, + project = ::Projects::CreateService.new(current_user, name: repo["name"], path: repo["path"], description: repo["description"], @@ -18,8 +19,11 @@ module Gitlab visibility_level: repo["visibility_level"], import_type: "gitlab", import_source: repo["path_with_namespace"], - import_url: repo["http_url_to_repo"].sub("://", "://oauth2:#{current_user.gitlab_access_token}@") + import_url: repo["http_url_to_repo"].sub("://", "://oauth2:#{@session_data[:gitlab_access_token]}@") ).execute + + project.create_import_data(data: { "gitlab_session" => session_data } ) + project end end end diff --git a/spec/controllers/import/bitbucket_controller_spec.rb b/spec/controllers/import/bitbucket_controller_spec.rb index 89e595121a7..81c03c9059b 100644 --- a/spec/controllers/import/bitbucket_controller_spec.rb +++ b/spec/controllers/import/bitbucket_controller_spec.rb @@ -4,7 +4,15 @@ require_relative 'import_spec_helper' describe Import::BitbucketController do include ImportSpecHelper - let(:user) { create(:user, bitbucket_access_token: 'asd123', bitbucket_access_token_secret: "sekret") } + let(:user) { create(:user) } + let(:token) { "asdasd12345" } + let(:secret) { "sekrettt" } + let(:access_params) { { bitbucket_access_token: token, bitbucket_access_token_secret: secret } } + + def assign_session_tokens + session[:bitbucket_access_token] = token + session[:bitbucket_access_token_secret] = secret + end before do sign_in(user) @@ -17,8 +25,6 @@ describe Import::BitbucketController do end it "updates access token" do - token = "asdasd12345" - secret = "sekrettt" access_token = double(token: token, secret: secret) allow_any_instance_of(Gitlab::BitbucketImport::Client). to receive(:get_token).and_return(access_token) @@ -26,8 +32,8 @@ describe Import::BitbucketController do get :callback - expect(user.reload.bitbucket_access_token).to eq(token) - expect(user.reload.bitbucket_access_token_secret).to eq(secret) + expect(session[:bitbucket_access_token]).to eq(token) + expect(session[:bitbucket_access_token_secret]).to eq(secret) expect(controller).to redirect_to(status_import_bitbucket_url) end end @@ -35,6 +41,7 @@ describe Import::BitbucketController do describe "GET status" do before do @repo = OpenStruct.new(slug: 'vim', owner: 'asd') + assign_session_tokens end it "assigns variables" do @@ -73,17 +80,18 @@ describe Import::BitbucketController do before do allow(Gitlab::BitbucketImport::KeyAdder). - to receive(:new).with(bitbucket_repo, user). + to receive(:new).with(bitbucket_repo, user, access_params). and_return(double(execute: true)) stub_client(user: bitbucket_user, project: bitbucket_repo) + assign_session_tokens end context "when the repository owner is the Bitbucket user" do context "when the Bitbucket user and GitLab user's usernames match" do it "takes the current user's namespace" do expect(Gitlab::BitbucketImport::ProjectCreator). - to receive(:new).with(bitbucket_repo, user.namespace, user). + to receive(:new).with(bitbucket_repo, user.namespace, user, access_params). and_return(double(execute: true)) post :create, format: :js @@ -95,7 +103,7 @@ describe Import::BitbucketController do it "takes the current user's namespace" do expect(Gitlab::BitbucketImport::ProjectCreator). - to receive(:new).with(bitbucket_repo, user.namespace, user). + to receive(:new).with(bitbucket_repo, user.namespace, user, access_params). and_return(double(execute: true)) post :create, format: :js @@ -116,7 +124,7 @@ describe Import::BitbucketController do context "when the namespace is owned by the GitLab user" do it "takes the existing namespace" do expect(Gitlab::BitbucketImport::ProjectCreator). - to receive(:new).with(bitbucket_repo, existing_namespace, user). + to receive(:new).with(bitbucket_repo, existing_namespace, user, access_params). and_return(double(execute: true)) post :create, format: :js @@ -150,7 +158,7 @@ describe Import::BitbucketController do it "takes the new namespace" do expect(Gitlab::BitbucketImport::ProjectCreator). - to receive(:new).with(bitbucket_repo, an_instance_of(Group), user). + to receive(:new).with(bitbucket_repo, an_instance_of(Group), user, access_params). and_return(double(execute: true)) post :create, format: :js diff --git a/spec/controllers/import/github_controller_spec.rb b/spec/controllers/import/github_controller_spec.rb index 0bc14059a35..766be578f7f 100644 --- a/spec/controllers/import/github_controller_spec.rb +++ b/spec/controllers/import/github_controller_spec.rb @@ -4,7 +4,13 @@ require_relative 'import_spec_helper' describe Import::GithubController do include ImportSpecHelper - let(:user) { create(:user, github_access_token: 'asd123') } + let(:user) { create(:user) } + let(:token) { "asdasd12345" } + let(:access_params) { { github_access_token: token } } + + def assign_session_token + session[:github_access_token] = token + end before do sign_in(user) @@ -20,7 +26,7 @@ describe Import::GithubController do get :callback - expect(user.reload.github_access_token).to eq(token) + expect(session[:github_access_token]).to eq(token) expect(controller).to redirect_to(status_import_github_url) end end @@ -30,6 +36,7 @@ describe Import::GithubController do @repo = OpenStruct.new(login: 'vim', full_name: 'asd/vim') @org = OpenStruct.new(login: 'company') @org_repo = OpenStruct.new(login: 'company', full_name: 'company/repo') + assign_session_token end it "assigns variables" do @@ -66,13 +73,14 @@ describe Import::GithubController do before do stub_client(user: github_user, repo: github_repo) + assign_session_token end context "when the repository owner is the GitHub user" do context "when the GitHub user and GitLab user's usernames match" do it "takes the current user's namespace" do expect(Gitlab::GithubImport::ProjectCreator). - to receive(:new).with(github_repo, user.namespace, user). + to receive(:new).with(github_repo, user.namespace, user, access_params). and_return(double(execute: true)) post :create, format: :js @@ -84,7 +92,7 @@ describe Import::GithubController do it "takes the current user's namespace" do expect(Gitlab::GithubImport::ProjectCreator). - to receive(:new).with(github_repo, user.namespace, user). + to receive(:new).with(github_repo, user.namespace, user, access_params). and_return(double(execute: true)) post :create, format: :js @@ -97,6 +105,7 @@ describe Import::GithubController do before do github_repo.owner = OpenStruct.new(login: other_username) + assign_session_token end context "when a namespace with the GitHub user's username already exists" do @@ -105,7 +114,7 @@ describe Import::GithubController do context "when the namespace is owned by the GitLab user" do it "takes the existing namespace" do expect(Gitlab::GithubImport::ProjectCreator). - to receive(:new).with(github_repo, existing_namespace, user). + to receive(:new).with(github_repo, existing_namespace, user, access_params). and_return(double(execute: true)) post :create, format: :js @@ -139,7 +148,7 @@ describe Import::GithubController do it "takes the new namespace" do expect(Gitlab::GithubImport::ProjectCreator). - to receive(:new).with(github_repo, an_instance_of(Group), user). + to receive(:new).with(github_repo, an_instance_of(Group), user, access_params). and_return(double(execute: true)) post :create, format: :js diff --git a/spec/controllers/import/gitlab_controller_spec.rb b/spec/controllers/import/gitlab_controller_spec.rb index 4bc67c86703..198d006af76 100644 --- a/spec/controllers/import/gitlab_controller_spec.rb +++ b/spec/controllers/import/gitlab_controller_spec.rb @@ -4,7 +4,13 @@ require_relative 'import_spec_helper' describe Import::GitlabController do include ImportSpecHelper - let(:user) { create(:user, gitlab_access_token: 'asd123') } + let(:user) { create(:user) } + let(:token) { "asdasd12345" } + let(:access_params) { { gitlab_access_token: token } } + + def assign_session_token + session[:gitlab_access_token] = token + end before do sign_in(user) @@ -13,14 +19,13 @@ describe Import::GitlabController do describe "GET callback" do it "updates access token" do - token = "asdasd12345" allow_any_instance_of(Gitlab::GitlabImport::Client). to receive(:get_token).and_return(token) stub_omniauth_provider('gitlab') get :callback - expect(user.reload.gitlab_access_token).to eq(token) + expect(session[:gitlab_access_token]).to eq(token) expect(controller).to redirect_to(status_import_gitlab_url) end end @@ -28,6 +33,7 @@ describe Import::GitlabController do describe "GET status" do before do @repo = OpenStruct.new(path: 'vim', path_with_namespace: 'asd/vim') + assign_session_token end it "assigns variables" do @@ -67,13 +73,14 @@ describe Import::GitlabController do before do stub_client(user: gitlab_user, project: gitlab_repo) + assign_session_token end context "when the repository owner is the GitLab.com user" do context "when the GitLab.com user and GitLab server user's usernames match" do it "takes the current user's namespace" do expect(Gitlab::GitlabImport::ProjectCreator). - to receive(:new).with(gitlab_repo, user.namespace, user). + to receive(:new).with(gitlab_repo, user.namespace, user, access_params). and_return(double(execute: true)) post :create, format: :js @@ -85,7 +92,7 @@ describe Import::GitlabController do it "takes the current user's namespace" do expect(Gitlab::GitlabImport::ProjectCreator). - to receive(:new).with(gitlab_repo, user.namespace, user). + to receive(:new).with(gitlab_repo, user.namespace, user, access_params). and_return(double(execute: true)) post :create, format: :js @@ -98,6 +105,7 @@ describe Import::GitlabController do before do gitlab_repo["namespace"]["path"] = other_username + assign_session_token end context "when a namespace with the GitLab.com user's username already exists" do @@ -106,7 +114,7 @@ describe Import::GitlabController do context "when the namespace is owned by the GitLab server user" do it "takes the existing namespace" do expect(Gitlab::GitlabImport::ProjectCreator). - to receive(:new).with(gitlab_repo, existing_namespace, user). + to receive(:new).with(gitlab_repo, existing_namespace, user, access_params). and_return(double(execute: true)) post :create, format: :js @@ -140,7 +148,7 @@ describe Import::GitlabController do it "takes the new namespace" do expect(Gitlab::GitlabImport::ProjectCreator). - to receive(:new).with(gitlab_repo, an_instance_of(Group), user). + to receive(:new).with(gitlab_repo, an_instance_of(Group), user, access_params). and_return(double(execute: true)) post :create, format: :js diff --git a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb index f8958c9bab8..0e826a319e0 100644 --- a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::BitbucketImport::ProjectCreator do - let(:user) { create(:user, bitbucket_access_token: "asdffg", bitbucket_access_token_secret: "sekret") } + let(:user) { create(:user) } let(:repo) do { name: 'Vim', @@ -11,6 +11,9 @@ describe Gitlab::BitbucketImport::ProjectCreator do }.with_indifferent_access end let(:namespace){ create(:group, owner: user) } + let(:token) { "asdasd12345" } + let(:secret) { "sekrettt" } + let(:access_params) { { bitbucket_access_token: token, bitbucket_access_token_secret: secret } } before do namespace.add_owner(user) @@ -19,7 +22,7 @@ describe Gitlab::BitbucketImport::ProjectCreator do it 'creates project' do allow_any_instance_of(Project).to receive(:add_import_job) - project_creator = Gitlab::BitbucketImport::ProjectCreator.new(repo, namespace, user) + project_creator = Gitlab::BitbucketImport::ProjectCreator.new(repo, namespace, user, access_params) project = project_creator.execute expect(project.import_url).to eq("ssh://git@bitbucket.org/asd/vim.git") diff --git a/spec/lib/gitlab/github_import/project_creator_spec.rb b/spec/lib/gitlab/github_import/project_creator_spec.rb index 4fe7bd3b77d..ca61d3c5234 100644 --- a/spec/lib/gitlab/github_import/project_creator_spec.rb +++ b/spec/lib/gitlab/github_import/project_creator_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::GithubImport::ProjectCreator do - let(:user) { create(:user, github_access_token: "asdffg") } + let(:user) { create(:user) } let(:repo) do OpenStruct.new( login: 'vim', @@ -13,6 +13,8 @@ describe Gitlab::GithubImport::ProjectCreator do ) end let(:namespace){ create(:group, owner: user) } + let(:token) { "asdffg" } + let(:access_params) { { github_access_token: token } } before do namespace.add_owner(user) @@ -21,7 +23,7 @@ describe Gitlab::GithubImport::ProjectCreator do it 'creates project' do allow_any_instance_of(Project).to receive(:add_import_job) - project_creator = Gitlab::GithubImport::ProjectCreator.new(repo, namespace, user) + project_creator = Gitlab::GithubImport::ProjectCreator.new(repo, namespace, user, access_params) project = project_creator.execute expect(project.import_url).to eq("https://asdffg@gitlab.com/asd/vim.git") diff --git a/spec/lib/gitlab/gitlab_import/project_creator_spec.rb b/spec/lib/gitlab/gitlab_import/project_creator_spec.rb index 938d08396fd..2d8923d14bb 100644 --- a/spec/lib/gitlab/gitlab_import/project_creator_spec.rb +++ b/spec/lib/gitlab/gitlab_import/project_creator_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::GitlabImport::ProjectCreator do - let(:user) { create(:user, gitlab_access_token: "asdffg") } + let(:user) { create(:user) } let(:repo) do { name: 'vim', @@ -13,6 +13,8 @@ describe Gitlab::GitlabImport::ProjectCreator do }.with_indifferent_access end let(:namespace){ create(:group, owner: user) } + let(:token) { "asdffg" } + let(:access_params) { { gitlab_access_token: token } } before do namespace.add_owner(user) @@ -21,7 +23,7 @@ describe Gitlab::GitlabImport::ProjectCreator do it 'creates project' do allow_any_instance_of(Project).to receive(:add_import_job) - project_creator = Gitlab::GitlabImport::ProjectCreator.new(repo, namespace, user) + project_creator = Gitlab::GitlabImport::ProjectCreator.new(repo, namespace, user, access_params) project = project_creator.execute expect(project.import_url).to eq("https://oauth2:asdffg@gitlab.com/asd/vim.git") -- cgit v1.2.1 From 4016a4577c6f41354e9261e08fe94d917bc9edc2 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 23 Aug 2015 13:31:02 -0700 Subject: Only include base URL in OmniAuth full_host parameter Closes #2335 --- CHANGELOG | 1 + config/initializers/1_settings.rb | 26 ++++++++++++++++++-------- config/initializers/7_omniauth.rb | 2 +- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 44ffb56d5fd..ae7a8dba9d4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.0.0 (unreleased) + - Only include base URL in OmniAuth full_host parameter (Stan Hu) - Only show recent push event if the branch still exists or a recent merge request has not been created (Stan Hu) - Remove satellites - Better performance for web editor (switched from satellites to rugged) diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index bd26ac1da20..c47e5dab27c 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -8,7 +8,7 @@ class Settings < Settingslogic def gitlab_on_standard_port? gitlab.port.to_i == (gitlab.https ? 443 : 80) end - + # get host without www, thanks to http://stackoverflow.com/a/6674363/1233435 def get_host_without_www(url) url = URI.encode(url) @@ -32,14 +32,12 @@ class Settings < Settingslogic end end + def build_base_gitlab_url + base_gitlab_url.join('') + end + def build_gitlab_url - custom_port = gitlab_on_standard_port? ? nil : ":#{gitlab.port}" - [ gitlab.protocol, - "://", - gitlab.host, - custom_port, - gitlab.relative_url_root - ].join('') + (base_gitlab_url + [gitlab.relative_url_root]).join('') end # check that values in `current` (string or integer) is a contant in `modul`. @@ -64,6 +62,17 @@ class Settings < Settingslogic end value end + + private + + def base_gitlab_url + custom_port = gitlab_on_standard_port? ? nil : ":#{gitlab.port}" + [ gitlab.protocol, + "://", + gitlab.host, + custom_port + ] + end end end @@ -123,6 +132,7 @@ Settings.gitlab['email_enabled'] ||= true if Settings.gitlab['email_enabled'].ni 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['base_url'] ||= Settings.send(:build_base_gitlab_url) Settings.gitlab['url'] ||= Settings.send(:build_gitlab_url) Settings.gitlab['user'] ||= 'git' Settings.gitlab['user_home'] ||= begin diff --git a/config/initializers/7_omniauth.rb b/config/initializers/7_omniauth.rb index 7f73546ac89..70ed10e8275 100644 --- a/config/initializers/7_omniauth.rb +++ b/config/initializers/7_omniauth.rb @@ -11,7 +11,7 @@ if Gitlab::LDAP::Config.enabled? end end -OmniAuth.config.full_host = Settings.gitlab['url'] +OmniAuth.config.full_host = Settings.gitlab['base_url'] OmniAuth.config.allowed_request_methods = [:post] #In case of auto sign-in, the GET method is used (users don't get to click on a button) OmniAuth.config.allowed_request_methods << :get if Gitlab.config.omniauth.auto_sign_in_with_provider.present? -- cgit v1.2.1 From 6318a7631131074d5d65775f92097b9c1e3f04d6 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 23 Aug 2015 16:52:29 -0700 Subject: Upgrade browser gem to 1.0.0 to avoid warning in IE11 compatibilty mode Closes #2271 --- CHANGELOG | 1 + Gemfile | 2 +- Gemfile.lock | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 7eb6ab81dd9..99df3c7a838 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.0.0 (unreleased) + - Upgrade browser gem to 1.0.0 to avoid warning in IE11 compatibilty mode (Stan Hu) - Remove user OAuth tokens from the database and request new tokens each session (Stan Hu) - Only show recent push event if the branch still exists or a recent merge request has not been created (Stan Hu) - Remove satellites diff --git a/Gemfile b/Gemfile index 3aa3c72e088..fa54a72f025 100644 --- a/Gemfile +++ b/Gemfile @@ -34,7 +34,7 @@ gem 'rqrcode-rails3' gem 'attr_encrypted', '1.3.4' # Browser detection -gem "browser", '~> 0.8.0' +gem "browser", '~> 1.0.0' # Extracting information from a git repository # Provide access to Gitlab::Git library diff --git a/Gemfile.lock b/Gemfile.lock index 5278fe243a8..a7cfe6b7511 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -76,7 +76,7 @@ GEM ruby_parser (~> 3.5.0) sass (~> 3.0) terminal-table (~> 1.4) - browser (0.8.0) + browser (1.0.0) builder (3.2.2) byebug (3.2.0) columnize (~> 0.8) @@ -755,7 +755,7 @@ DEPENDENCIES binding_of_caller bootstrap-sass (~> 3.0) brakeman - browser (~> 0.8.0) + browser (~> 1.0.0) byebug cal-heatmap-rails (~> 0.0.1) capybara (~> 2.4.0) -- cgit v1.2.1 From 56527b63e8a09e0fe4967eabf08638d853e6b2b5 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 13 Aug 2015 17:48:21 +0300 Subject: Ability to search milestones --- CHANGELOG | 1 + app/controllers/search_controller.rb | 4 ++-- app/models/milestone.rb | 9 ++++++++- app/views/search/_category.html.haml | 14 ++++++++++++++ app/views/search/results/_milestone.html.haml | 9 +++++++++ features/search.feature | 15 +++++++++++++++ features/steps/search.rb | 11 +++++++++++ lib/gitlab/search_results.rb | 14 +++++++++++++- 8 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 app/views/search/results/_milestone.html.haml diff --git a/CHANGELOG b/CHANGELOG index 7eb6ab81dd9..7ec1dabcf95 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,6 +11,7 @@ v 8.0.0 (unreleased) - Allow configuration of import sources for new projects (Artem Sidorenko) - Search for comments should be case insensetive - Create cross-reference for closing references on commits pushed to non-default branches (Maël Valais) + - Ability to search milestones v 7.14.0 - Fix bug where non-project members of the target project could set labels on new merge requests. diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 4e2ea6c5710..eb0408a95e5 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -23,7 +23,7 @@ class SearchController < ApplicationController @search_results = if @project - unless %w(blobs notes issues merge_requests wiki_blobs). + unless %w(blobs notes issues merge_requests milestones wiki_blobs). include?(@scope) @scope = 'blobs' end @@ -36,7 +36,7 @@ class SearchController < ApplicationController Search::SnippetService.new(current_user, params).execute else - unless %w(projects issues merge_requests).include?(@scope) + unless %w(projects issues merge_requests milestones).include?(@scope) @scope = 'projects' end Search::GlobalService.new(current_user, params).execute diff --git a/app/models/milestone.rb b/app/models/milestone.rb index d28f3c8d3f9..c6aff6f709f 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -47,6 +47,13 @@ class Milestone < ActiveRecord::Base state :active end + class << self + def search(query) + query = "%#{query}%" + where("title like ? or description like ?", query, query) + end + end + def expired? if due_date due_date.past? @@ -54,7 +61,7 @@ class Milestone < ActiveRecord::Base false end end - + def open_items_count self.issues.opened.count + self.merge_requests.opened.count end diff --git a/app/views/search/_category.html.haml b/app/views/search/_category.html.haml index 154332cb9a9..a75cd7bd809 100644 --- a/app/views/search/_category.html.haml +++ b/app/views/search/_category.html.haml @@ -21,6 +21,13 @@ Merge requests %span.badge = @search_results.merge_requests_count + %li{class: ("active" if @scope == 'milestones')} + = link_to search_filter_path(scope: 'milestones') do + = icon('clock-o fw') + %span + Milestones + %span.badge + = @search_results.milestones_count %li{class: ("active" if @scope == 'notes')} = link_to search_filter_path(scope: 'notes') do = icon('comments fw') @@ -74,4 +81,11 @@ Merge requests %span.badge = @search_results.merge_requests_count + %li{class: ("active" if @scope == 'milestones')} + = link_to search_filter_path(scope: 'milestones') do + = icon('clock-o fw') + %span + Milestones + %span.badge + = @search_results.milestones_count diff --git a/app/views/search/results/_milestone.html.haml b/app/views/search/results/_milestone.html.haml new file mode 100644 index 00000000000..e0b18733d74 --- /dev/null +++ b/app/views/search/results/_milestone.html.haml @@ -0,0 +1,9 @@ +.search-result-row + %h4 + = link_to [milestone.project.namespace.becomes(Namespace), milestone.project, milestone] do + %span.term.str-truncated= milestone.title + + - if milestone.description.present? + .description.term + = preserve do + = search_md_sanitize(markdown(milestone.description)) \ No newline at end of file diff --git a/features/search.feature b/features/search.feature index 1608e824671..a9234c1a611 100644 --- a/features/search.feature +++ b/features/search.feature @@ -23,6 +23,13 @@ Feature: Search Then I should see "Foo" link in the search results And I should not see "Bar" link in the search results + Scenario: I should see milestones I am looking for + And project has milestones + When I search for "Foo" + When I click "Milestones" link + Then I should see "Foo" link in the search results + And I should not see "Bar" link in the search results + Scenario: I should see project code I am looking for When I click project "Shop" link And I search for "rspec" @@ -44,6 +51,14 @@ Feature: Search Then I should see "Foo" link in the search results And I should not see "Bar" link in the search results + Scenario: I should see project milestones + And project has milestones + When I click project "Shop" link + And I search for "Foo" + And I click "Milestones" link + Then I should see "Foo" link in the search results + And I should not see "Bar" link in the search results + Scenario: I should see Wiki blobs And project has Wiki content When I click project "Shop" link diff --git a/features/steps/search.rb b/features/steps/search.rb index 87893aa0205..79273cbad9a 100644 --- a/features/steps/search.rb +++ b/features/steps/search.rb @@ -41,6 +41,12 @@ class Spinach::Features::Search < Spinach::FeatureSteps end end + step 'I click "Milestones" link' do + page.within '.search-filter' do + click_link 'Milestones' + end + end + step 'I click "Wiki" link' do page.within '.search-filter' do click_link 'Wiki' @@ -72,6 +78,11 @@ class Spinach::Features::Search < Spinach::FeatureSteps create(:merge_request, :simple, title: "Bar", source_project: project, target_project: project) end + step 'project has milestones' do + create(:milestone, title: "Foo", project: project) + create(:milestone, title: "Bar", project: project) + end + step 'I should see "Foo" link in the search results' do page.within('.results') do find(:css, '.search-results').should have_link 'Foo' diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb index 06245374bc8..2ab2d4af797 100644 --- a/lib/gitlab/search_results.rb +++ b/lib/gitlab/search_results.rb @@ -19,13 +19,15 @@ module Gitlab issues.page(page).per(per_page) when 'merge_requests' merge_requests.page(page).per(per_page) + when 'milestones' + milestones.page(page).per(per_page) else Kaminari.paginate_array([]).page(page).per(per_page) end end def total_count - @total_count ||= projects_count + issues_count + merge_requests_count + @total_count ||= projects_count + issues_count + merge_requests_count + milestones_count end def projects_count @@ -40,6 +42,10 @@ module Gitlab @merge_requests_count ||= merge_requests.count end + def milestones_count + @milestones_count ||= milestones.count + end + def empty? total_count.zero? end @@ -60,6 +66,12 @@ module Gitlab issues.order('updated_at DESC') end + def milestones + milestones = Milestone.where(project_id: limit_project_ids) + milestones = milestones.search(query) + milestones.order('updated_at DESC') + end + def merge_requests merge_requests = MergeRequest.in_projects(limit_project_ids) if query =~ /[#!](\d+)\z/ -- cgit v1.2.1 From 19f478e00d1508728d0170ac60defed0535a0789 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 24 Aug 2015 07:46:06 -0700 Subject: Fix Error 500 in API when accessing a group that has an avatar Closes #2340 --- CHANGELOG | 1 + app/models/group.rb | 1 + spec/requests/api/groups_spec.rb | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 7eb6ab81dd9..72b7317a9c4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.0.0 (unreleased) + - Fix Error 500 in API when accessing a group that has an avatar (Stan Hu) - Remove user OAuth tokens from the database and request new tokens each session (Stan Hu) - Only show recent push event if the branch still exists or a recent merge request has not been created (Stan Hu) - Remove satellites diff --git a/app/models/group.rb b/app/models/group.rb index 4ff610f8e9d..9cd146bb73b 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -17,6 +17,7 @@ require 'carrierwave/orm/activerecord' require 'file_size_validator' class Group < Namespace + include Gitlab::ConfigHelper include Referable has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember' diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 1d5b4f6f36b..13cced81875 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -7,7 +7,8 @@ describe API::API, api: true do let(:user2) { create(:user) } let(:user3) { create(:user) } let(:admin) { create(:admin) } - let!(:group1) { create(:group) } + let(:avatar_file_path) { File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif') } + let!(:group1) { create(:group, avatar: File.open(avatar_file_path)) } let!(:group2) { create(:group) } before do -- cgit v1.2.1 From 208888038375d48cc91cf481a3f8509d63d8e1ad Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 24 Aug 2015 10:57:35 -0700 Subject: Ignore empty incoming messages. --- app/mailers/email_rejection_mailer.rb | 2 ++ app/workers/email_receiver_worker.rb | 2 ++ config/mail_room.yml.example | 2 ++ doc/reply_by_email/README.md | 4 ++++ 4 files changed, 10 insertions(+) diff --git a/app/mailers/email_rejection_mailer.rb b/app/mailers/email_rejection_mailer.rb index 89aceda82d1..883f1c73ad4 100644 --- a/app/mailers/email_rejection_mailer.rb +++ b/app/mailers/email_rejection_mailer.rb @@ -3,6 +3,8 @@ class EmailRejectionMailer < BaseMailer @reason = reason @original_message = Mail::Message.new(original_raw) + return unless @original_message.from + headers = { to: @original_message.from, subject: "[Rejected] #{@original_message.subject}" diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb index a588a1f45ee..8cfb96ef376 100644 --- a/app/workers/email_receiver_worker.rb +++ b/app/workers/email_receiver_worker.rb @@ -18,6 +18,8 @@ class EmailReceiverWorker def handle_failure(raw, e) Rails.logger.warn("Email can not be processed: #{e}\n\n#{raw}") + return unless raw.present? + can_retry = false reason = nil diff --git a/config/mail_room.yml.example b/config/mail_room.yml.example index 28366eb7394..dd8edfc42eb 100644 --- a/config/mail_room.yml.example +++ b/config/mail_room.yml.example @@ -14,6 +14,8 @@ # :name: "inbox" # # Always "sidekiq". # :delivery_method: sidekiq + # # Always true. + # :delete_after_delivery: true # :delivery_options: # # The URL to the Redis server used by Sidekiq. Should match the URL in config/resque.yml. # :redis_url: redis://localhost:6379 diff --git a/doc/reply_by_email/README.md b/doc/reply_by_email/README.md index 91eea956e52..5d36f5121d1 100644 --- a/doc/reply_by_email/README.md +++ b/doc/reply_by_email/README.md @@ -59,6 +59,8 @@ In this example, we'll use the Gmail address `gitlab-replies@gmail.com`. If you' :name: "inbox" # Always "sidekiq". :delivery_method: sidekiq + # Always true. + :delete_after_delivery: true :delivery_options: # The URL to the Redis server used by Sidekiq. Should match the URL in config/resque.yml. :redis_url: redis://localhost:6379 @@ -144,6 +146,8 @@ TODO :name: "inbox" # Always "sidekiq". :delivery_method: sidekiq + # Always true. + :delete_after_delivery: true :delivery_options: # The URL to the Redis server used by Sidekiq. Should match the URL in config/resque.yml. :redis_url: redis://localhost:6379 -- cgit v1.2.1 From eeac2aafbc444b1fe5ada430b15058b30628b364 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 24 Aug 2015 12:07:14 -0700 Subject: Move full_host fix changelog entry to 7.14.1 [ci skip] --- CHANGELOG | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index f22c337b726..5137879d318 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,7 +3,6 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.0.0 (unreleased) - Fix Error 500 in API when accessing a group that has an avatar (Stan Hu) - Remove user OAuth tokens from the database and request new tokens each session (Stan Hu) - - Only include base URL in OmniAuth full_host parameter (Stan Hu) - Only show recent push event if the branch still exists or a recent merge request has not been created (Stan Hu) - Remove satellites - Better performance for web editor (switched from satellites to rugged) @@ -15,6 +14,9 @@ v 8.0.0 (unreleased) - Create cross-reference for closing references on commits pushed to non-default branches (Maël Valais) - Ability to search milestones +v 7.14.1 (unreleased) + - Only include base URL in OmniAuth full_host parameter (Stan Hu) + v 7.14.0 - Fix bug where non-project members of the target project could set labels on new merge requests. - Update default robots.txt rules to disallow crawling of irrelevant pages (Ben Bodenmiller) -- cgit v1.2.1 From 451873963a4c41e0edeb066545cafcb9a0df9c19 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 24 Aug 2015 12:27:02 -0700 Subject: Move changelog entry to 7.14.1 [ci skip] --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 5137879d318..5940d586d88 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,6 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.0.0 (unreleased) - - Fix Error 500 in API when accessing a group that has an avatar (Stan Hu) - Remove user OAuth tokens from the database and request new tokens each session (Stan Hu) - Only show recent push event if the branch still exists or a recent merge request has not been created (Stan Hu) - Remove satellites @@ -16,6 +15,7 @@ v 8.0.0 (unreleased) v 7.14.1 (unreleased) - Only include base URL in OmniAuth full_host parameter (Stan Hu) + - Fix Error 500 in API when accessing a group that has an avatar (Stan Hu) v 7.14.0 - Fix bug where non-project members of the target project could set labels on new merge requests. -- cgit v1.2.1 From 646c1f0324650c5736e85db0deb55dceb943fa7a Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 24 Aug 2015 18:56:01 -0700 Subject: Re-enable the "links with adjacent text" UserReferenceFilter spec --- spec/lib/gitlab/markdown/user_reference_filter_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb index a5405e14a73..02d923b036c 100644 --- a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb @@ -121,7 +121,6 @@ module Gitlab::Markdown end it 'links with adjacent text' do - skip "TODO (rspeicher): Re-enable when usernames can't end in periods." doc = filter("Mention me (#{reference}.)") expect(doc.to_html).to match(/\(#{reference}<\/a>\.\)/) end -- cgit v1.2.1 From 3bee4a6712a654abeba4ce07eb17235c3240b487 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 24 Aug 2015 23:35:26 -0700 Subject: Fix "Reload with full diff" URL button in compare branch view This button worked when viewing merge requests because the JavaScript stripped the .html in the Ajax request. However, it left the .html suffix in the compare branch view. --- CHANGELOG | 1 + app/views/projects/diffs/_warning.html.haml | 2 +- features/project/commits/commits.feature | 1 + features/steps/project/commits/commits.rb | 6 ++++++ 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 7eb6ab81dd9..4e165362692 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.0.0 (unreleased) + - Fix "Reload with full diff" URL button in compare branch view (Stan Hu) - Remove user OAuth tokens from the database and request new tokens each session (Stan Hu) - Only show recent push event if the branch still exists or a recent merge request has not been created (Stan Hu) - Remove satellites diff --git a/app/views/projects/diffs/_warning.html.haml b/app/views/projects/diffs/_warning.html.haml index caed0e69dc8..f99bc9a85eb 100644 --- a/app/views/projects/diffs/_warning.html.haml +++ b/app/views/projects/diffs/_warning.html.haml @@ -3,7 +3,7 @@ Too many changes to show. .pull-right - unless diff_hard_limit_enabled? - = link_to "Reload with full diff", url_for(params.merge(force_show_diff: true, format: :html)), class: "btn btn-sm btn-warning" + = link_to "Reload with full diff", url_for(params.merge(force_show_diff: true, format: nil)), class: "btn btn-sm btn-warning" - if current_controller?(:commit) or current_controller?(:merge_requests) - if current_controller?(:commit) diff --git a/features/project/commits/commits.feature b/features/project/commits/commits.feature index c4b206edc95..3ebc8a39aae 100644 --- a/features/project/commits/commits.feature +++ b/features/project/commits/commits.feature @@ -41,6 +41,7 @@ Feature: Project Commits Scenario: I browse big commit Given I visit big commit page Then I see big commit warning + And I see "Reload with full diff" link Scenario: I browse a commit with an image Given I visit a commit with an image that changed diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb index e6330ec457e..a8532cc18d8 100644 --- a/features/steps/project/commits/commits.rb +++ b/features/steps/project/commits/commits.rb @@ -79,6 +79,12 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps expect(page).to have_content "Too many changes" end + step 'I see "Reload with full diff" link' do + link = find_link('Reload with full diff') + expect(link[:href]).to end_with('?force_show_diff=true') + expect(link[:href]).not_to include('.html') + end + step 'I visit a commit with an image that changed' do visit namespace_project_commit_path(@project.namespace, @project, sample_image_commit.id) end -- cgit v1.2.1 From e02b7bf4435d0ff151239ac790849994066c0bc1 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 25 Aug 2015 10:52:10 +0200 Subject: Improve abuse reports management * Link to user profile instead of user admin page * One button for remove user and report * Remove user and report with page reloading Signed-off-by: Dmitriy Zaporozhets --- app/assets/javascripts/application.js.coffee | 6 ++++++ app/controllers/admin/abuse_reports_controller.rb | 9 +++++++-- app/views/admin/abuse_reports/_abuse_report.html.haml | 11 +++++++---- app/views/admin/abuse_reports/index.html.haml | 2 +- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index bb0a0c51fd4..c263912b7ea 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -116,6 +116,12 @@ $ -> $('.remove-row').bind 'ajax:success', -> $(this).closest('li').fadeOut() + $('.js-remove-tr').bind 'ajax:before', -> + $(this).hide() + + $('.js-remove-tr').bind 'ajax:success', -> + $(this).closest('tr').fadeOut() + # Initialize select2 selects $('select.select2').select2(width: 'resolve', dropdownAutoWidth: true) diff --git a/app/controllers/admin/abuse_reports_controller.rb b/app/controllers/admin/abuse_reports_controller.rb index 34f37bca4ad..38a5a9fca08 100644 --- a/app/controllers/admin/abuse_reports_controller.rb +++ b/app/controllers/admin/abuse_reports_controller.rb @@ -4,8 +4,13 @@ class Admin::AbuseReportsController < Admin::ApplicationController end def destroy - AbuseReport.find(params[:id]).destroy + abuse_report = AbuseReport.find(params[:id]) - redirect_to admin_abuse_reports_path, notice: 'Report was removed' + if params[:remove_user] + abuse_report.user.destroy + end + + abuse_report.destroy + render nothing: true end end diff --git a/app/views/admin/abuse_reports/_abuse_report.html.haml b/app/views/admin/abuse_reports/_abuse_report.html.haml index 4449721ae38..785ce4b759e 100644 --- a/app/views/admin/abuse_reports/_abuse_report.html.haml +++ b/app/views/admin/abuse_reports/_abuse_report.html.haml @@ -3,7 +3,7 @@ %tr %td - if reporter - = link_to reporter.name, [:admin, reporter] + = link_to reporter.name, reporter - else (removed) %td @@ -12,12 +12,15 @@ = abuse_report.message %td - if user - = link_to user.name, [:admin, user] + = link_to user.name, user - else (removed) %td - if user - = link_to 'Block', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-xs btn-warning" - = link_to 'Remove user', [:admin, user], data: { confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-xs btn-remove" + = link_to 'Remove user & report', admin_abuse_report_path(abuse_report, remove_user: true), + data: { confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?" }, remote: true, method: :delete, class: "btn btn-xs btn-remove js-remove-tr" + %td + - if user + = link_to 'Block user', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-xs btn-warning" = link_to 'Remove report', [:admin, abuse_report], method: :delete, class: "btn btn-xs btn-close" diff --git a/app/views/admin/abuse_reports/index.html.haml b/app/views/admin/abuse_reports/index.html.haml index 4a25848f156..2e8746146d1 100644 --- a/app/views/admin/abuse_reports/index.html.haml +++ b/app/views/admin/abuse_reports/index.html.haml @@ -9,7 +9,7 @@ %th Reported at %th Message %th User - %th + %th Primary action %th = render @abuse_reports = paginate @abuse_reports -- cgit v1.2.1 From 13f8fc97843d3bb0f8c498835f740e44b1306395 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 25 Aug 2015 10:57:56 +0200 Subject: Fix remove report button Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 1 + app/views/admin/abuse_reports/_abuse_report.html.haml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 5940d586d88..40b8ccb62f4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,7 @@ v 8.0.0 (unreleased) - Search for comments should be case insensetive - Create cross-reference for closing references on commits pushed to non-default branches (Maël Valais) - Ability to search milestones + - Improve abuse reports management from admin area v 7.14.1 (unreleased) - Only include base URL in OmniAuth full_host parameter (Stan Hu) diff --git a/app/views/admin/abuse_reports/_abuse_report.html.haml b/app/views/admin/abuse_reports/_abuse_report.html.haml index 785ce4b759e..d3afc658cd6 100644 --- a/app/views/admin/abuse_reports/_abuse_report.html.haml +++ b/app/views/admin/abuse_reports/_abuse_report.html.haml @@ -22,5 +22,5 @@ %td - if user - = link_to 'Block user', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-xs btn-warning" - = link_to 'Remove report', [:admin, abuse_report], method: :delete, class: "btn btn-xs btn-close" + = link_to 'Block user', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-xs" + = link_to 'Remove report', [:admin, abuse_report], remote: true, method: :delete, class: "btn btn-xs btn-close js-remove-tr" -- cgit v1.2.1 From 8f68c38746639d8714ff5aa12fe9a0fd362f4419 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 25 Aug 2015 14:13:04 +0200 Subject: Dashboard activity as separate page Signed-off-by: Dmitriy Zaporozhets --- app/assets/javascripts/dispatcher.js.coffee | 1 + app/controllers/dashboard_controller.rb | 22 +++++++++++++++------- app/views/dashboard/activity.html.haml | 6 ++++++ app/views/dashboard/show.html.haml | 9 +-------- app/views/layouts/nav/_dashboard.html.haml | 7 ++++++- config/routes.rb | 1 + 6 files changed, 30 insertions(+), 16 deletions(-) create mode 100644 app/views/dashboard/activity.html.haml diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index 81e73799271..539041c2862 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -51,6 +51,7 @@ class Dispatcher MergeRequests.init() when 'dashboard:show', 'root:show' new Dashboard() + when 'dashboard:activity' new Activities() when 'dashboard:projects:starred' new Activities() diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index d2f0c43929f..d745131694b 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -1,6 +1,6 @@ class DashboardController < Dashboard::ApplicationController before_action :load_projects - before_action :event_filter, only: :show + before_action :event_filter, only: :activity respond_to :html @@ -10,13 +10,8 @@ class DashboardController < Dashboard::ApplicationController respond_to do |format| format.html - - format.json do - load_events - pager_json("events/_events", @events.count) - end - format.atom do + event_filter load_events render layout: false end @@ -40,6 +35,19 @@ class DashboardController < Dashboard::ApplicationController end end + def activity + @last_push = current_user.recent_push + + respond_to do |format| + format.html + + format.json do + load_events + pager_json("events/_events", @events.count) + end + end + end + protected def load_projects diff --git a/app/views/dashboard/activity.html.haml b/app/views/dashboard/activity.html.haml new file mode 100644 index 00000000000..7a5a093add5 --- /dev/null +++ b/app/views/dashboard/activity.html.haml @@ -0,0 +1,6 @@ += content_for :meta_tags do + - if current_user + = auto_discovery_link_tag(:atom, dashboard_url(format: :atom, private_token: current_user.private_token), title: "All activity") + +%section.activities + = render 'activities' diff --git a/app/views/dashboard/show.html.haml b/app/views/dashboard/show.html.haml index a3a32b6932f..1b6ea3d5963 100644 --- a/app/views/dashboard/show.html.haml +++ b/app/views/dashboard/show.html.haml @@ -5,13 +5,6 @@ = render 'dashboard/projects_head' - if @projects.any? - = render 'shared/show_aside' - - .dashboard.row - %section.activities.col-md-8 - = render 'activities' - %aside.col-md-4 - = render 'sidebar' - + = render 'sidebar' - else = render "zero_authorized_projects" diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index 8f010196d1a..154fd418399 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -1,9 +1,14 @@ %ul.nav.nav-sidebar = nav_link(path: ['dashboard#show', 'root#show', 'projects#trending', 'projects#starred', 'projects#index'], html_options: {class: 'home'}) do = link_to (current_user ? root_path : explore_root_path), title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do - = icon('dashboard fw') + = icon('home fw') %span Projects + = nav_link(path: 'dashboard#activity') do + = link_to activity_dashboard_path, title: 'Activity', data: {placement: 'right'} do + = icon('dashboard fw') + %span + Activity = nav_link(controller: :groups) do = link_to (current_user ? dashboard_groups_path : explore_groups_path), title: 'Groups', data: {placement: 'right'} do = icon('group fw') diff --git a/config/routes.rb b/config/routes.rb index d7307a61ede..8ba439f08b8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -261,6 +261,7 @@ Gitlab::Application.routes.draw do member do get :issues get :merge_requests + get :activity end scope module: :dashboard do -- cgit v1.2.1 From c11e43145f87436062f3840d16a9fcf97286265a Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 25 Aug 2015 15:23:25 +0200 Subject: Make some visual improvements to dashboard page after we moved activity Signed-off-by: Dmitriy Zaporozhets --- app/assets/javascripts/projects_list.js.coffee | 2 +- app/assets/stylesheets/pages/dashboard.scss | 38 +------------------------- app/assets/stylesheets/pages/projects.scss | 31 +++++++++++++++++++++ app/views/dashboard/_projects.html.haml | 9 ++++-- app/views/dashboard/show.html.haml | 2 +- app/views/groups/_projects.html.haml | 2 +- app/views/shared/_project.html.haml | 6 +++- 7 files changed, 46 insertions(+), 44 deletions(-) diff --git a/app/assets/javascripts/projects_list.js.coffee b/app/assets/javascripts/projects_list.js.coffee index c0e36d1ccc5..db5faf71faf 100644 --- a/app/assets/javascripts/projects_list.js.coffee +++ b/app/assets/javascripts/projects_list.js.coffee @@ -8,7 +8,7 @@ class @ProjectsList $(".projects-list-filter").keyup -> terms = $(this).val() - uiBox = $(this).closest('.panel') + uiBox = $(this).closest('.projects-list-holder') if terms == "" || terms == undefined uiBox.find(".projects-list li").show() else diff --git a/app/assets/stylesheets/pages/dashboard.scss b/app/assets/stylesheets/pages/dashboard.scss index 9a3b543ad10..c1103a1c2e6 100644 --- a/app/assets/stylesheets/pages/dashboard.scss +++ b/app/assets/stylesheets/pages/dashboard.scss @@ -23,41 +23,6 @@ } } -.project-row, .group-row { - padding: 0 !important; - font-size: 14px; - line-height: 24px; - - a { - display: block; - padding: 8px 15px; - } - - .project-name, .group-name { - font-weight: 500; - } - - .arrow { - float: right; - margin: 0; - font-size: 20px; - } - - .last-activity { - float: right; - font-size: 12px; - color: #AAA; - display: block; - .date { - color: #777; - } - } -} - -.project-description { - overflow: hidden; -} - .project-access-icon { margin-left: 10px; float: left; @@ -73,10 +38,9 @@ float: left; .avatar { - margin-top: -8px; - margin-left: -15px; @include border-radius(0px); } + .identicon { line-height: 40px; } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 29d3dbc25eb..1e138651d52 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -316,3 +316,34 @@ table.table.protected-branches-list tr.no-border { pre.light-well { border-color: #f1f1f1; } + +.projects-search-form { + max-width: 600px; + margin: 0 auto; + margin-bottom: 20px; + + input { + border-color: #BBB; + } +} + +.project-row { + .project-full-name { + font-weight: bold; + font-size: 15px; + } + + .project-description { + color: #888; + font-size: 13px; + + p { + margin-bottom: 0; + color: #888; + } + } +} + +.my-projects .project-row { + padding: 10px 0; +} diff --git a/app/views/dashboard/_projects.html.haml b/app/views/dashboard/_projects.html.haml index d676576067c..dc83d5343f2 100644 --- a/app/views/dashboard/_projects.html.haml +++ b/app/views/dashboard/_projects.html.haml @@ -1,5 +1,5 @@ -.panel.panel-default - .panel-heading.clearfix +.projects-list-holder + .projects-search-form .input-group = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control' - if current_user.can_create_project? @@ -7,4 +7,7 @@ = link_to new_project_path, class: 'btn btn-success' do New project - = render 'shared/projects_list', projects: @projects, projects_limit: 20 + %ul.projects-list.bordered-list.my-projects + - @projects.each do |project| + %li.project-row + = render partial: 'shared/project', locals: { project: project, avatar: true, stars: true } diff --git a/app/views/dashboard/show.html.haml b/app/views/dashboard/show.html.haml index 1b6ea3d5963..44372b921cf 100644 --- a/app/views/dashboard/show.html.haml +++ b/app/views/dashboard/show.html.haml @@ -5,6 +5,6 @@ = render 'dashboard/projects_head' - if @projects.any? - = render 'sidebar' + = render 'projects' - else = render "zero_authorized_projects" diff --git a/app/views/groups/_projects.html.haml b/app/views/groups/_projects.html.haml index 4f8aec1c67e..2ae51a1c8c0 100644 --- a/app/views/groups/_projects.html.haml +++ b/app/views/groups/_projects.html.haml @@ -1,4 +1,4 @@ -.panel.panel-default +.panel.panel-default.projects-list-holder .panel-heading.clearfix .input-group = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control' diff --git a/app/views/shared/_project.html.haml b/app/views/shared/_project.html.haml index 6bd61455d21..15df97b1333 100644 --- a/app/views/shared/_project.html.haml +++ b/app/views/shared/_project.html.haml @@ -3,7 +3,7 @@ - if avatar .dash-project-avatar = project_icon(project, alt: '', class: 'avatar project-avatar s40') - %span.str-truncated + %span.str-truncated.project-full-name %span.namespace-name - if project.namespace = project.namespace.human_name @@ -14,3 +14,7 @@ %span.pull-right.light %i.fa.fa-star = project.star_count + - if project.description.present? + .project-description + .str-truncated + = markdown(project.description, pipeline: :description) -- cgit v1.2.1 From 7569010bc3918af0098e00eb56fce1a18d8ca72e Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 25 Aug 2015 16:36:54 +0200 Subject: Fix tests and last push widget Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 1 + app/views/dashboard/projects/starred.html.haml | 2 +- app/views/dashboard/show.html.haml | 3 +++ app/views/events/_event_last_push.html.haml | 2 +- features/dashboard/dashboard.feature | 8 ++++++-- features/dashboard/event_filters.feature | 8 ++++---- features/steps/shared/paths.rb | 4 ++++ 7 files changed, 20 insertions(+), 8 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 35b5803d341..cf3ce803e0b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ v 8.0.0 (unreleased) - Create cross-reference for closing references on commits pushed to non-default branches (Maël Valais) - Ability to search milestones - Gracefully handle SMTP user input errors (e.g. incorrect email addresses) to prevent Sidekiq retries (Stan Hu) + - Move dashboard activity to separate page v 7.14.1 (unreleased) - Only include base URL in OmniAuth full_host parameter (Stan Hu) diff --git a/app/views/dashboard/projects/starred.html.haml b/app/views/dashboard/projects/starred.html.haml index 98b8cde4766..027387028b9 100644 --- a/app/views/dashboard/projects/starred.html.haml +++ b/app/views/dashboard/projects/starred.html.haml @@ -8,7 +8,7 @@ %section.activities.col-md-8 = render 'dashboard/activities' %aside.col-md-4 - .panel.panel-default + .panel.panel-default.projects-list-holder .panel-heading.clearfix .input-group = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control' diff --git a/app/views/dashboard/show.html.haml b/app/views/dashboard/show.html.haml index 44372b921cf..4cf2feb9aa6 100644 --- a/app/views/dashboard/show.html.haml +++ b/app/views/dashboard/show.html.haml @@ -4,6 +4,9 @@ = render 'dashboard/projects_head' +- if @last_push + = render "events/event_last_push", event: @last_push + - if @projects.any? = render 'projects' - else diff --git a/app/views/events/_event_last_push.html.haml b/app/views/events/_event_last_push.html.haml index 501412642db..6a0c6cba41b 100644 --- a/app/views/events/_event_last_push.html.haml +++ b/app/views/events/_event_last_push.html.haml @@ -9,6 +9,6 @@ #{time_ago_with_tooltip(event.created_at)} .pull-right - = link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-create btn-sm" do + = link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-info btn-sm" do Create Merge Request %hr diff --git a/features/dashboard/dashboard.feature b/features/dashboard/dashboard.feature index 1959d327082..392d4235eff 100644 --- a/features/dashboard/dashboard.feature +++ b/features/dashboard/dashboard.feature @@ -10,6 +10,10 @@ Feature: Dashboard Scenario: I should see projects list Then I should see "New Project" link Then I should see "Shop" project link + + @javascript + Scenario: I should see activity list + And I visit dashboard activity page Then I should see project "Shop" activity feed Scenario: I should see groups list @@ -26,12 +30,12 @@ Feature: Dashboard @javascript Scenario: I should see User joined Project event Given user with name "John Doe" joined project "Shop" - When I visit dashboard page + When I visit dashboard activity page Then I should see "John Doe joined project Shop" event @javascript Scenario: I should see User left Project event Given user with name "John Doe" joined project "Shop" And user with name "John Doe" left project "Shop" - When I visit dashboard page + When I visit dashboard activity page Then I should see "John Doe left project Shop" event diff --git a/features/dashboard/event_filters.feature b/features/dashboard/event_filters.feature index ec5680caba6..96399ea21a6 100644 --- a/features/dashboard/event_filters.feature +++ b/features/dashboard/event_filters.feature @@ -6,7 +6,7 @@ Feature: Event Filters And this project has push event And this project has new member event And this project has merge request event - And I visit dashboard page + And I visit dashboard activity page @javascript Scenario: I should see all events @@ -16,7 +16,7 @@ Feature: Event Filters @javascript Scenario: I should see only pushed events - When I click "push" event filter + When I click "push" event filter Then I should see push event And I should not see new member event And I should not see merge request event @@ -38,11 +38,11 @@ Feature: Event Filters @javascript Scenario: I should see only selected events while page reloaded When I click "push" event filter - And I visit dashboard page + And I visit dashboard activity page Then I should see push event And I should not see new member event When I click "team" event filter - And I visit dashboard page + And I visit dashboard activity page Then I should see push event And I should see new member event And I should not see merge request event diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb index ca8fbb49101..b4deccb6520 100644 --- a/features/steps/shared/paths.rb +++ b/features/steps/shared/paths.rb @@ -71,6 +71,10 @@ module SharedPaths visit dashboard_path end + step 'I visit dashboard activity page' do + visit activity_dashboard_path + end + step 'I visit dashboard projects page' do visit projects_dashboard_path end -- cgit v1.2.1 From 4a4d9fe16e371c4c5d4423a285758abe1d04d84d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Rosen=C3=B6gger?= <123haynes@gmail.com> Date: Tue, 25 Aug 2015 17:58:29 +0200 Subject: UI Improvement for the project page This commit improves the positioning of the dropdown menu on the project page. --- CHANGELOG | 1 + app/assets/stylesheets/pages/projects.scss | 4 ++++ app/views/projects/buttons/_dropdown.html.haml | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index a66e898652c..8048ca13733 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.0.0 (unreleased) + - Improve dropdown positioning on the project home page (Hannes Rosenögger) - Upgrade browser gem to 1.0.0 to avoid warning in IE11 compatibilty mode (Stan Hu) - Fix "Reload with full diff" URL button in compare branch view (Stan Hu) - Remove user OAuth tokens from the database and request new tokens each session (Stan Hu) diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 29d3dbc25eb..505b93b3bc5 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -30,6 +30,10 @@ } } + .project-home-dropdown { + margin: 11px 3px 0; + } + .project-home-desc { h1 { margin: 0; diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml index cade930c8cc..bc7625e8989 100644 --- a/app/views/projects/buttons/_dropdown.html.haml +++ b/app/views/projects/buttons/_dropdown.html.haml @@ -2,7 +2,7 @@ %span.dropdown %a.dropdown-toggle.btn.btn-new{href: '#', "data-toggle" => "dropdown"} = icon('plus') - %ul.dropdown-menu + %ul.dropdown-menu.dropdown-menu-right.project-home-dropdown - if can?(current_user, :create_issue, @project) %li = link_to url_for_new_issue do -- cgit v1.2.1 From 084da5253b699b0734e17839f6ffebd4dc414320 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 25 Aug 2015 18:20:06 +0200 Subject: Re-use project partial for rendering explore projects Signed-off-by: Dmitriy Zaporozhets --- app/views/explore/projects/_project.html.haml | 24 ------------------------ app/views/explore/projects/_projects.html.haml | 6 ++++++ app/views/explore/projects/index.html.haml | 9 ++------- app/views/explore/projects/starred.html.haml | 5 +---- app/views/explore/projects/trending.html.haml | 4 +--- app/views/layouts/nav/_dashboard.html.haml | 2 +- 6 files changed, 11 insertions(+), 39 deletions(-) delete mode 100644 app/views/explore/projects/_project.html.haml create mode 100644 app/views/explore/projects/_projects.html.haml diff --git a/app/views/explore/projects/_project.html.haml b/app/views/explore/projects/_project.html.haml deleted file mode 100644 index 1e8a89e3661..00000000000 --- a/app/views/explore/projects/_project.html.haml +++ /dev/null @@ -1,24 +0,0 @@ -%li - %h4.project-title - .project-access-icon - = visibility_level_icon(project.visibility_level) - = link_to project.name_with_namespace, [project.namespace.becomes(Namespace), project] - %span.pull-right - %i.fa.fa-star - = project.star_count - - .project-info - - if project.description.present? - .project-description.str-truncated - = markdown(project.description, pipeline: :description) - - .repo-info - - unless project.empty_repo? - = link_to pluralize(round_commit_count(project), 'commit'), namespace_project_commits_path(project.namespace, project, project.default_branch) - · - = link_to pluralize(project.repository.branch_names.count, 'branch'), namespace_project_branches_path(project.namespace, project) - · - = link_to pluralize(project.repository.tag_names.count, 'tag'), namespace_project_tags_path(project.namespace, project) - - else - %i.fa.fa-exclamation-triangle - Empty repository diff --git a/app/views/explore/projects/_projects.html.haml b/app/views/explore/projects/_projects.html.haml new file mode 100644 index 00000000000..22cc541115c --- /dev/null +++ b/app/views/explore/projects/_projects.html.haml @@ -0,0 +1,6 @@ +%ul.projects-list.bordered-list.my-projects.public-projects + - projects.each do |project| + %li.project-row + = render partial: 'shared/project', locals: { project: project, avatar: true, stars: true } +- unless projects.present? + .nothing-here-block No such projects diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml index 4956081e1ed..0cfdf5cfd15 100644 --- a/app/views/explore/projects/index.html.haml +++ b/app/views/explore/projects/index.html.haml @@ -4,10 +4,5 @@ .clearfix = render 'filter' %br -.public-projects - %ul.bordered-list.top-list - = render @projects - - unless @projects.present? - .nothing-here-block No public projects - - = paginate @projects, theme: "gitlab" += render 'projects', projects: @projects += paginate @projects, theme: "gitlab" diff --git a/app/views/explore/projects/starred.html.haml b/app/views/explore/projects/starred.html.haml index fdccbe5692f..4a9fcae4bed 100644 --- a/app/views/explore/projects/starred.html.haml +++ b/app/views/explore/projects/starred.html.haml @@ -7,8 +7,5 @@ See most starred projects .pull-right = render 'explore/projects/dropdown' - .public-projects - %ul.bordered-list - = render @starred_projects - + = render 'projects', projects: @starred_projects = paginate @starred_projects, theme: 'gitlab' diff --git a/app/views/explore/projects/trending.html.haml b/app/views/explore/projects/trending.html.haml index 98a4174b426..4c7e7d44733 100644 --- a/app/views/explore/projects/trending.html.haml +++ b/app/views/explore/projects/trending.html.haml @@ -13,6 +13,4 @@ See most discussed projects for last month .pull-right = render 'explore/projects/dropdown' - .public-projects - %ul.bordered-list - = render @trending_projects + = render 'projects', projects: @trending_projects diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index 154fd418399..d620c022273 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -34,7 +34,7 @@ %span.count= current_user.assigned_merge_requests.opened.count = nav_link(controller: :snippets) do = link_to (current_user ? user_snippets_path(current_user) : snippets_path), title: 'Your snippets', data: {placement: 'right'} do - = icon('dashboard fw') + = icon('clipboard fw') %span Snippets - if current_user -- cgit v1.2.1 From ad85f1f560224a3f276e57dcf638a6e41eb231eb Mon Sep 17 00:00:00 2001 From: Josef Kufner Date: Wed, 13 May 2015 12:51:47 +0200 Subject: Change plots to bar graphs --- app/views/projects/graphs/commits.html.haml | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/app/views/projects/graphs/commits.html.haml b/app/views/projects/graphs/commits.html.haml index 141acbdcf72..a357736bf52 100644 --- a/app/views/projects/graphs/commits.html.haml +++ b/app/views/projects/graphs/commits.html.haml @@ -50,39 +50,42 @@ datasets : [{ fillColor : "rgba(220,220,220,0.5)", strokeColor : "rgba(220,220,220,1)", - pointColor : "rgba(220,220,220,1)", - pointStrokeColor : "#EEE", + barStrokeWidth: 1, + barValueSpacing: 1, + barDatasetSpacing: 1, data : #{@commits_per_time.values.to_json} }] } ctx = $("#hour-chart").get(0).getContext("2d"); - new Chart(ctx).Line(data,{"scaleOverlay": true, responsive: true, pointHitDetectionRadius: 2}) + new Chart(ctx).Bar(data,{"scaleOverlay": true, responsive: true, pointHitDetectionRadius: 2}) data = { labels : #{@commits_per_week_days.keys.to_json}, datasets : [{ fillColor : "rgba(220,220,220,0.5)", strokeColor : "rgba(220,220,220,1)", - pointColor : "rgba(220,220,220,1)", - pointStrokeColor : "#EEE", + barStrokeWidth: 1, + barValueSpacing: 1, + barDatasetSpacing: 1, data : #{@commits_per_week_days.values.to_json} }] } ctx = $("#weekday-chart").get(0).getContext("2d"); - new Chart(ctx).Line(data,{"scaleOverlay": true, responsive: true, pointHitDetectionRadius: 2}) + new Chart(ctx).Bar(data,{"scaleOverlay": true, responsive: true, pointHitDetectionRadius: 2}) data = { labels : #{@commits_per_month.keys.to_json}, datasets : [{ fillColor : "rgba(220,220,220,0.5)", strokeColor : "rgba(220,220,220,1)", - pointColor : "rgba(220,220,220,1)", - pointStrokeColor : "#EEE", + barStrokeWidth: 1, + barValueSpacing: 1, + barDatasetSpacing: 1, data : #{@commits_per_month.values.to_json} }] } ctx = $("#month-chart").get(0).getContext("2d"); - new Chart(ctx).Line(data, {"scaleOverlay": true, responsive: true, pointHitDetectionRadius: 2}) + new Chart(ctx).Bar(data, {"scaleOverlay": true, responsive: true, pointHitDetectionRadius: 2}) -- cgit v1.2.1 From fa65db9aa876289bcb5974edfdd15f181c9a1ac5 Mon Sep 17 00:00:00 2001 From: Josef Kufner Date: Mon, 24 Aug 2015 23:51:50 +0200 Subject: Add changelog entry --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 5222ab1e3e1..3ccb096214a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -306,6 +306,7 @@ v 7.11.0 - Protect OmniAuth request phase against CSRF. - Don't send notifications to mentioned users that don't have access to the project in question. - Add search issues/MR by number + - Change plots to bar graphs in commit statistics screen - Move snippets UI to fluid layout - Improve UI for sidebar. Increase separation between navigation and content - Improve new project command options (Ben Bodenmiller) -- cgit v1.2.1 From 02e7b00a54ebfebc51acab0a7f7e15adc93f4bb0 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 25 Aug 2015 12:03:58 -0700 Subject: Fix init script for Reply by email --- lib/support/init.d/gitlab | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab index 41a2f254db6..457bd31e23b 100755 --- a/lib/support/init.d/gitlab +++ b/lib/support/init.d/gitlab @@ -288,7 +288,7 @@ print_status() { fi if [ "$mail_room_enabled" = true ]; then if [ "$mail_room_status" = "0" ]; then - echo "The GitLab MailRoom email processor with pid $spid is running." + echo "The GitLab MailRoom email processor with pid $mpid is running." else printf "The GitLab MailRoom email processor is \033[31mnot running\033[0m.\n" fi -- cgit v1.2.1 From 4344b8d2d4367b19c6849c3cab0d02d17ddf2304 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 25 Aug 2015 14:31:33 -0700 Subject: Add Gitlab::ColorSchemes module Very similar to Gitlab::Theme, this contains all of the definitions for our syntax highlighting schemes. --- app/helpers/preferences_helper.rb | 25 +---------- app/views/profiles/preferences/show.html.haml | 8 ++-- lib/gitlab/color_schemes.rb | 62 +++++++++++++++++++++++++++ spec/lib/gitlab/color_schemes_spec.rb | 45 +++++++++++++++++++ spec/lib/gitlab/themes_spec.rb | 3 -- 5 files changed, 113 insertions(+), 30 deletions(-) create mode 100644 lib/gitlab/color_schemes.rb create mode 100644 spec/lib/gitlab/color_schemes_spec.rb diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb index ea774e28ecf..b294ddc9d72 100644 --- a/app/helpers/preferences_helper.rb +++ b/app/helpers/preferences_helper.rb @@ -1,25 +1,5 @@ # Helper methods for per-User preferences module PreferencesHelper - COLOR_SCHEMES = { - 1 => 'white', - 2 => 'dark', - 3 => 'solarized-light', - 4 => 'solarized-dark', - 5 => 'monokai', - } - COLOR_SCHEMES.default = 'white' - - # Helper method to access the COLOR_SCHEMES - # - # The keys are the `color_scheme_ids` - # The values are the `name` of the scheme. - # - # The preview images are `name-scheme-preview.png` - # The stylesheets should use the css class `.name` - def color_schemes - COLOR_SCHEMES.freeze - end - # Maps `dashboard` values to more user-friendly option text DASHBOARD_CHOICES = { projects: 'Your Projects (default)', @@ -50,12 +30,11 @@ module PreferencesHelper end def user_application_theme - theme = Gitlab::Themes.by_id(current_user.try(:theme_id)) - theme.css_class + Gitlab::Themes.by_id(current_user.try(:theme_id)).css_class end def user_color_scheme_class - COLOR_SCHEMES[current_user.try(:color_scheme_id)] if defined?(current_user) + Gitlab::ColorSchemes.by_id(current_user.try(:color_scheme_id)).css_class end def prefer_readme? diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index 1134317ee06..aa0361a0a1b 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -22,11 +22,11 @@ .panel-heading Syntax highlighting theme .panel-body - - color_schemes.each do |color_scheme_id, color_scheme| + - Gitlab::ColorSchemes.each do |scheme| = label_tag do - .preview= image_tag "#{color_scheme}-scheme-preview.png" - = f.radio_button :color_scheme_id, color_scheme_id - = color_scheme.tr('-_', ' ').titleize + .preview= image_tag "#{scheme.css_class}-scheme-preview.png" + = f.radio_button :color_scheme_id, scheme.id + = scheme.name .panel.panel-default .panel-heading diff --git a/lib/gitlab/color_schemes.rb b/lib/gitlab/color_schemes.rb new file mode 100644 index 00000000000..763853ab1cb --- /dev/null +++ b/lib/gitlab/color_schemes.rb @@ -0,0 +1,62 @@ +module Gitlab + # Module containing GitLab's syntax color scheme definitions and helper + # methods for accessing them. + module ColorSchemes + # Struct class representing a single Scheme + Scheme = Struct.new(:id, :name, :css_class) + + SCHEMES = [ + Scheme.new(1, 'White', 'white'), + Scheme.new(2, 'Dark', 'dark'), + Scheme.new(3, 'Solarized Light', 'solarized-light'), + Scheme.new(4, 'Solarized Dark', 'solarized-dark'), + Scheme.new(5, 'Monokai', 'monokai') + ].freeze + + # Convenience method to get a space-separated String of all the color scheme + # classes that might be applied to a code block. + # + # Returns a String + def self.body_classes + SCHEMES.collect(&:css_class).uniq.join(' ') + end + + # Get a Scheme by its ID + # + # If the ID is invalid, returns the default Scheme. + # + # id - Integer ID + # + # Returns a Scheme + def self.by_id(id) + SCHEMES.detect { |s| s.id == id } || default + end + + # Get the default Scheme + # + # Returns a Scheme + def self.default + by_id(1) + end + + # Iterate through each Scheme + # + # Yields the Scheme object + def self.each(&block) + SCHEMES.each(&block) + end + + # Get the Scheme for the specified user, or the default + # + # user - User record + # + # Returns a Scheme + def self.for_user(user) + if user + by_id(user.color_scheme_id) + else + default + end + end + end +end diff --git a/spec/lib/gitlab/color_schemes_spec.rb b/spec/lib/gitlab/color_schemes_spec.rb new file mode 100644 index 00000000000..c7be45dbcd3 --- /dev/null +++ b/spec/lib/gitlab/color_schemes_spec.rb @@ -0,0 +1,45 @@ +require 'spec_helper' + +describe Gitlab::ColorSchemes do + describe '.body_classes' do + it 'returns a space-separated list of class names' do + css = described_class.body_classes + + expect(css).to include('white') + expect(css).to include(' solarized-light ') + expect(css).to include(' monokai') + end + end + + describe '.by_id' do + it 'returns a scheme by its ID' do + expect(described_class.by_id(1).name).to eq 'White' + expect(described_class.by_id(4).name).to eq 'Solarized Dark' + end + end + + describe '.default' do + it 'returns the default scheme' do + expect(described_class.default.id).to eq 1 + end + end + + describe '.each' do + it 'passes the block to the SCHEMES Array' do + ids = [] + described_class.each { |scheme| ids << scheme.id } + expect(ids).not_to be_empty + end + end + + describe '.for_user' do + it 'returns default when user is nil' do + expect(described_class.for_user(nil).id).to eq 1 + end + + it "returns user's preferred color scheme" do + user = double(color_scheme_id: 5) + expect(described_class.for_user(user).id).to eq 5 + end + end +end diff --git a/spec/lib/gitlab/themes_spec.rb b/spec/lib/gitlab/themes_spec.rb index 9c6c3fd8104..e554458e41c 100644 --- a/spec/lib/gitlab/themes_spec.rb +++ b/spec/lib/gitlab/themes_spec.rb @@ -43,9 +43,6 @@ describe Gitlab::Themes do ids = [] described_class.each { |theme| ids << theme.id } expect(ids).not_to be_empty - - # TODO (rspeicher): RSpec 3.x - # expect(described_class.each).to yield_with_arg(described_class::Theme) end end end -- cgit v1.2.1 From 59180c4f5a553938ee79968e7983aee6ce584ff5 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 25 Aug 2015 15:23:41 -0700 Subject: Add highlight_word style definition to each color scheme Now we don't need to override the user color scheme on search results. They'll look good with any of them. --- app/assets/stylesheets/generic/common.scss | 4 ---- app/assets/stylesheets/highlight/dark.scss | 6 ++++++ app/assets/stylesheets/highlight/monokai.scss | 6 ++++++ app/assets/stylesheets/highlight/solarized_dark.scss | 5 +++++ app/assets/stylesheets/highlight/solarized_light.scss | 5 +++++ app/assets/stylesheets/highlight/white.scss | 5 +++++ 6 files changed, 27 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/generic/common.scss b/app/assets/stylesheets/generic/common.scss index bf5c7a8d75e..7281b26179e 100644 --- a/app/assets/stylesheets/generic/common.scss +++ b/app/assets/stylesheets/generic/common.scss @@ -132,10 +132,6 @@ p.time { text-shadow: none; } -.highlight_word { - background: #fafe3d; -} - .thin_area{ height: 150px; } diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss index c8cb18ec35f..8323a8598ec 100644 --- a/app/assets/stylesheets/highlight/dark.scss +++ b/app/assets/stylesheets/highlight/dark.scss @@ -21,6 +21,12 @@ pre.code.highlight.dark, background-color: #557 !important; } + // Search result highlight + span.highlight_word { + background: #ffe792; + color: #000000; + } + .hll { background-color: #373b41 } .c { color: #969896 } /* Comment */ .err { color: #cc6666 } /* Error */ diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss index 001e8b31020..e8381674336 100644 --- a/app/assets/stylesheets/highlight/monokai.scss +++ b/app/assets/stylesheets/highlight/monokai.scss @@ -21,6 +21,12 @@ pre.code.monokai, background-color: #49483e !important; } + // Search result highlight + span.highlight_word { + background: #ffe792; + color: #000000; + } + .hll { background-color: #49483e } .c { color: #75715e } /* Comment */ .err { color: #960050; background-color: #1e0010 } /* Error */ diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss index f5b827e7c02..bd41480aefb 100644 --- a/app/assets/stylesheets/highlight/solarized_dark.scss +++ b/app/assets/stylesheets/highlight/solarized_dark.scss @@ -21,6 +21,11 @@ pre.code.highlight.solarized-dark, background-color: #174652 !important; } + // Search result highlight + span.highlight_word { + background: #094554; + } + /* Solarized Dark For use with Jekyll and Pygments diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss index 6b44c00c305..4cc62863870 100644 --- a/app/assets/stylesheets/highlight/solarized_light.scss +++ b/app/assets/stylesheets/highlight/solarized_light.scss @@ -21,6 +21,11 @@ pre.code.highlight.solarized-light, background-color: #ddd8c5 !important; } + // Search result highlight + span.highlight_word { + background: #eee8d5; + } + /* Solarized Light For use with Jekyll and Pygments diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss index a52ffc971d1..e0edfb80b42 100644 --- a/app/assets/stylesheets/highlight/white.scss +++ b/app/assets/stylesheets/highlight/white.scss @@ -21,6 +21,11 @@ pre.code.highlight.white, background-color: #f8eec7 !important; } + // Search result highlight + span.highlight_word { + background: #fafe3d; + } + .hll { background-color: #f8f8f8 } .c { color: #999988; font-style: italic; } .err { color: #a61717; background-color: #e3d2d2; } -- cgit v1.2.1 From 2c3e42e4a44e2f40e521cbafc8144e5d7c366b76 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 25 Aug 2015 15:24:53 -0700 Subject: Remove user_color_scheme_class Instead of rendering this value server-side, we use Javascript and Gon to apply the user's color scheme (or the default) to any syntax highlighted code blocks. This will make it easier to cache these blocks in the future because they're no longer state-dependent. --- app/assets/javascripts/syntax_highlight.coffee | 9 +++++++++ app/controllers/application_controller.rb | 9 +++++---- app/helpers/gitlab_markdown_helper.rb | 2 +- app/helpers/preferences_helper.rb | 4 ---- app/views/search/results/_blob.html.haml | 2 +- app/views/search/results/_snippet_blob.html.haml | 2 +- app/views/search/results/_wiki_blob.html.haml | 2 +- app/views/shared/_file_highlight.html.haml | 2 +- lib/redcarpet/render/gitlab_html.rb | 5 ++--- spec/features/markdown_spec.rb | 4 ---- spec/helpers/preferences_helper_spec.rb | 21 --------------------- 11 files changed, 21 insertions(+), 41 deletions(-) create mode 100644 app/assets/javascripts/syntax_highlight.coffee diff --git a/app/assets/javascripts/syntax_highlight.coffee b/app/assets/javascripts/syntax_highlight.coffee new file mode 100644 index 00000000000..510f15d1b49 --- /dev/null +++ b/app/assets/javascripts/syntax_highlight.coffee @@ -0,0 +1,9 @@ +# Applies a syntax highlighting color scheme CSS class to any element with the +# `js-syntax-highlight` class +# +# ### Example Markup +# +#
+# +$(document).on 'ready page:load', -> + $('.js-syntax-highlight').addClass(gon.user_color_scheme) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 12d439b0b31..b51a9600c18 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -190,11 +190,12 @@ class ApplicationController < ActionController::Base end def add_gon_variables + gon.api_version = API::API.version + gon.default_avatar_url = URI::join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s gon.default_issues_tracker = Project.new.default_issue_tracker.to_param - gon.api_version = API::API.version - gon.relative_url_root = Gitlab.config.gitlab.relative_url_root - gon.default_avatar_url = URI::join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s - gon.max_file_size = current_application_settings.max_attachment_size; + gon.max_file_size = current_application_settings.max_attachment_size + gon.relative_url_root = Gitlab.config.gitlab.relative_url_root + gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class if current_user gon.current_user_id = current_user.id diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index eb3f72a307d..114730eb948 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -58,7 +58,7 @@ module GitlabMarkdownHelper @options = options # see https://github.com/vmg/redcarpet#darling-i-packed-you-a-couple-renderers-for-lunch - rend = Redcarpet::Render::GitlabHTML.new(self, user_color_scheme_class, options) + rend = Redcarpet::Render::GitlabHTML.new(self, options) # see https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use @markdown = Redcarpet::Markdown.new(rend, MARKDOWN_OPTIONS) diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb index b294ddc9d72..51a6b3fa997 100644 --- a/app/helpers/preferences_helper.rb +++ b/app/helpers/preferences_helper.rb @@ -33,10 +33,6 @@ module PreferencesHelper Gitlab::Themes.by_id(current_user.try(:theme_id)).css_class end - def user_color_scheme_class - Gitlab::ColorSchemes.by_id(current_user.try(:color_scheme_id)).css_class - end - def prefer_readme? !current_user || current_user.project_view == 'readme' diff --git a/app/views/search/results/_blob.html.haml b/app/views/search/results/_blob.html.haml index 58f58eff54d..0fe8a3b490a 100644 --- a/app/views/search/results/_blob.html.haml +++ b/app/views/search/results/_blob.html.haml @@ -7,4 +7,4 @@ %strong = blob.filename .file-content.code.term - = render 'shared/file_highlight', blob: blob, first_line_number: blob.startline, user_color_scheme_class: 'white' + = render 'shared/file_highlight', blob: blob, first_line_number: blob.startline diff --git a/app/views/search/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml index 95099853918..19a7bfefef5 100644 --- a/app/views/search/results/_snippet_blob.html.haml +++ b/app/views/search/results/_snippet_blob.html.haml @@ -23,7 +23,7 @@ .nothing-here-block Empty file - else .file-content.code - %div.highlighted-data{class: user_color_scheme_class} + %div.highlighted-data.js-syntax-highlight .line-numbers - snippet_blob[:snippet_chunks].each do |snippet| - unless snippet[:data].empty? diff --git a/app/views/search/results/_wiki_blob.html.haml b/app/views/search/results/_wiki_blob.html.haml index c03438eb952..f5859481d46 100644 --- a/app/views/search/results/_wiki_blob.html.haml +++ b/app/views/search/results/_wiki_blob.html.haml @@ -7,4 +7,4 @@ %strong = wiki_blob.filename .file-content.code.term - = render 'shared/file_highlight', blob: wiki_blob, first_line_number: wiki_blob.startline, user_color_scheme_class: 'white' + = render 'shared/file_highlight', blob: wiki_blob, first_line_number: wiki_blob.startline diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml index d6a2e177da1..7b1c624d769 100644 --- a/app/views/shared/_file_highlight.html.haml +++ b/app/views/shared/_file_highlight.html.haml @@ -1,4 +1,4 @@ -.file-content.code{class: user_color_scheme_class} +.file-content.code.js-syntax-highlight .line-numbers - if blob.data.present? - blob.data.lines.each_index do |index| diff --git a/lib/redcarpet/render/gitlab_html.rb b/lib/redcarpet/render/gitlab_html.rb index f57b56cbdf0..9cb8e91d6e3 100644 --- a/lib/redcarpet/render/gitlab_html.rb +++ b/lib/redcarpet/render/gitlab_html.rb @@ -4,9 +4,8 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML attr_reader :template alias_method :h, :template - def initialize(template, color_scheme, options = {}) + def initialize(template, options = {}) @template = template - @color_scheme = color_scheme @options = options.dup @options.reverse_merge!( @@ -35,7 +34,7 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML end formatter = Rouge::Formatters::HTMLGitlab.new( - cssclass: "code highlight #{@color_scheme} #{lexer.tag}" + cssclass: "code highlight js-syntax-highlight #{lexer.tag}" ) formatter.format(lexer.lex(code)) end diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb index 3da4dfc2b23..d32bd4adb52 100644 --- a/spec/features/markdown_spec.rb +++ b/spec/features/markdown_spec.rb @@ -224,8 +224,4 @@ describe 'GitLab Markdown', feature: true do def current_user @feat.user end - - def user_color_scheme_class - :white - end end diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb index d814b562113..b4dee272bfd 100644 --- a/spec/helpers/preferences_helper_spec.rb +++ b/spec/helpers/preferences_helper_spec.rb @@ -48,25 +48,4 @@ describe PreferencesHelper do ] end end - - describe 'user_color_scheme_class' do - context 'with current_user is nil' do - it 'should return a string' do - allow(self).to receive(:current_user).and_return(nil) - expect(user_color_scheme_class).to be_kind_of(String) - end - end - - context 'with a current_user' do - (1..5).each do |color_scheme_id| - context "with color_scheme_id == #{color_scheme_id}" do - it 'should return a string' do - current_user = double(color_scheme_id: color_scheme_id) - allow(self).to receive(:current_user).and_return(current_user) - expect(user_color_scheme_class).to be_kind_of(String) - end - end - end - end - end end -- cgit v1.2.1 From c401543d02487888a71966518fa8cb4f17de1397 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 25 Aug 2015 15:57:55 -0700 Subject: Fix failing spec --- spec/features/markdown_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb index d32bd4adb52..4fe019f8342 100644 --- a/spec/features/markdown_spec.rb +++ b/spec/features/markdown_spec.rb @@ -64,8 +64,8 @@ describe 'GitLab Markdown', feature: true do it 'parses fenced code blocks' do aggregate_failures do - expect(doc).to have_selector('pre.code.highlight.white.c') - expect(doc).to have_selector('pre.code.highlight.white.python') + expect(doc).to have_selector('pre.code.highlight.js-syntax-highlight.c') + expect(doc).to have_selector('pre.code.highlight.js-syntax-highlight.python') end end -- cgit v1.2.1 From d6a145d4b0b8d2baa63f2c1699ba44ea203a4ae8 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 25 Aug 2015 16:26:20 -0700 Subject: Fix two more failing specs --- spec/helpers/events_helper_spec.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/spec/helpers/events_helper_spec.rb b/spec/helpers/events_helper_spec.rb index da58ab98462..e68a5ec29ab 100644 --- a/spec/helpers/events_helper_spec.rb +++ b/spec/helpers/events_helper_spec.rb @@ -28,8 +28,7 @@ describe EventsHelper do it 'should display the first line of a code block' do input = "```\nCode block\nwith two lines\n```" - expected = '
' \
-               'Code block...
' + expected = %r{Code block\.\.\.} expect(event_note(input)).to match(expected) end @@ -55,7 +54,7 @@ describe EventsHelper do it 'should preserve code color scheme' do input = "```ruby\ndef test\n 'hello world'\nend\n```" - expected = '
' \
+    expected = '
' \
       "def test\n" \
       "  \'hello world\'\n" \
       "end" \
-- 
cgit v1.2.1


From 5ddf38e1881b515149738647e42f2d0a96d8df14 Mon Sep 17 00:00:00 2001
From: Dmitriy Zaporozhets 
Date: Wed, 26 Aug 2015 10:21:51 +0200
Subject: Improve project name/description look on project page

Signed-off-by: Dmitriy Zaporozhets 
---
 app/assets/stylesheets/pages/projects.scss | 3 +++
 app/views/projects/_home_panel.html.haml   | 2 +-
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 1e138651d52..6e40cbdd02f 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -35,9 +35,12 @@
       margin: 0;
       margin-bottom: 10px;
       font-size: 26px;
+      font-weight: bold;
     }
 
     p {
+      font-size: 18px;
+      color: #666;
       display: inline;
     }
   }
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index bec40ec27a5..b93036e78e6 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -2,7 +2,7 @@
 .project-home-panel.clearfix{:class => ("empty-project" if empty_repo)}
   .project-identicon-holder
     = project_icon(@project, alt: '', class: 'project-avatar avatar s90')
-  .project-home-desc.lead
+  .project-home-desc
     %h1= @project.name
     - if @project.description.present?
       = markdown(@project.description, pipeline: :description)
-- 
cgit v1.2.1


From 735978388f62787dc69c97cd267359693fde6415 Mon Sep 17 00:00:00 2001
From: Dmitriy Zaporozhets 
Date: Wed, 26 Aug 2015 10:36:08 +0200
Subject: Increase width of sidebar for several pages

Signed-off-by: Dmitriy Zaporozhets 
---
 app/views/dashboard/projects/starred.html.haml | 4 ++--
 app/views/groups/show.html.haml                | 4 ++--
 app/views/users/show.html.haml                 | 4 ++--
 3 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/app/views/dashboard/projects/starred.html.haml b/app/views/dashboard/projects/starred.html.haml
index 027387028b9..6dcfd497ed2 100644
--- a/app/views/dashboard/projects/starred.html.haml
+++ b/app/views/dashboard/projects/starred.html.haml
@@ -5,9 +5,9 @@
   = render 'shared/show_aside'
 
   .dashboard.row
-    %section.activities.col-md-8
+    %section.activities.col-md-7
       = render 'dashboard/activities'
-    %aside.col-md-4
+    %aside.col-md-5
       .panel.panel-default.projects-list-holder
         .panel-heading.clearfix
           .input-group
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index d31dae7d648..0577f4ec142 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -17,7 +17,7 @@
   = render 'shared/show_aside'
 
   .row
-    %section.activities.col-md-8
+    %section.activities.col-md-7
       .hidden-xs
         - if current_user
           = render "events/event_last_push", event: @last_push
@@ -33,5 +33,5 @@
 
       .content_list
       = spinner
-    %aside.side.col-md-4
+    %aside.side.col-md-5
       = render "projects", projects: @projects
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 64b7f25ad37..aa4e8722fb1 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -7,7 +7,7 @@
 = render 'shared/show_aside'
 
 .row
-  %section.col-md-8
+  %section.col-md-7
     .header-with-avatar
       = link_to avatar_icon(@user.email, 400), target: '_blank' do
         = image_tag avatar_icon(@user.email, 90), class: "avatar avatar-tile s90", alt: ''
@@ -59,7 +59,7 @@
 
     .content_list
     = spinner
-  %aside.col-md-4
+  %aside.col-md-5
     = render 'profile', user: @user
     = render 'projects', projects: @projects, contributed_projects: @contributed_projects
 
-- 
cgit v1.2.1


From 0276e9032a5c31548844e76e913d9c50039cf054 Mon Sep 17 00:00:00 2001
From: Dmitriy Zaporozhets 
Date: Wed, 26 Aug 2015 11:51:28 +0200
Subject: Limit content width for big screens except certain pages

Signed-off-by: Dmitriy Zaporozhets 
---
 app/assets/stylesheets/base/layout.scss           |  5 +++++
 app/assets/stylesheets/base/variables.scss        |  2 +-
 app/assets/stylesheets/pages/issuable.scss        |  6 ------
 app/assets/stylesheets/pages/tree.scss            |  1 -
 app/helpers/page_layout_helper.rb                 |  8 ++++++++
 app/views/layouts/_page.html.haml                 |  2 +-
 app/views/layouts/header/_default.html.haml       | 12 ------------
 app/views/projects/deploy_keys/index.html.haml    |  1 +
 app/views/projects/diffs/_diffs.html.haml         |  3 +++
 app/views/projects/merge_requests/_show.html.haml |  3 +++
 10 files changed, 22 insertions(+), 21 deletions(-)

diff --git a/app/assets/stylesheets/base/layout.scss b/app/assets/stylesheets/base/layout.scss
index 690d89a5c16..734b95e26c0 100644
--- a/app/assets/stylesheets/base/layout.scss
+++ b/app/assets/stylesheets/base/layout.scss
@@ -20,3 +20,8 @@ html {
 .navless-container {
   margin-top: 30px;
 }
+
+
+.container-limited {
+  max-width: $fixed-layout-width;
+}
diff --git a/app/assets/stylesheets/base/variables.scss b/app/assets/stylesheets/base/variables.scss
index cb439a0e0bf..26d0a1e5363 100644
--- a/app/assets/stylesheets/base/variables.scss
+++ b/app/assets/stylesheets/base/variables.scss
@@ -13,7 +13,7 @@ $code_line_height: 1.5;
 $border-color: #E5E5E5;
 $background-color: #f5f5f5;
 $header-height: 50px;
-$readable-width: 1100px;
+$fixed-layout-width: 1200px;
 
 
 /*
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 3f617e72b02..586e7b5f8da 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -45,9 +45,3 @@
 
   .btn { font-size: 13px; }
 }
-
-.issuable-details {
-  .description {
-    max-width: $readable-width;
-  }
-}
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index 5f1a3db4fb6..81e2aa7bb9c 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -117,7 +117,6 @@
 
 .readme-holder {
   margin: 0 auto;
-  max-width: $readable-width;
 
   .readme-file-title {
     font-size: 14px;
diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb
index 01b6a63552c..8473d6d75d0 100644
--- a/app/helpers/page_layout_helper.rb
+++ b/app/helpers/page_layout_helper.rb
@@ -23,4 +23,12 @@ module PageLayoutHelper
       @sidebar
     end
   end
+
+  def fluid_layout(enabled = false)
+    if @fluid_layout.nil?
+      @fluid_layout = enabled
+    else
+      @fluid_layout
+    end
+  end
 end
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index 96e15783a36..3d58f993cac 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -13,7 +13,7 @@
         .username
           = current_user.username
   .content-wrapper
-    .container-fluid
+    %div{ class: fluid_layout ? "container-fluid" : "container-fluid container-limited" }
       .content
         = render "layouts/flash"
         .clearfix
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 12ddbe6f1b7..6086f36f07e 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -17,15 +17,6 @@
           %li.visible-sm.visible-xs
             = link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom'} do
               = icon('search')
-          -#%li.hidden-xs
-            = link_to help_path, title: 'Help', data: {toggle: 'tooltip', placement: 'bottom'} do
-              = icon('question-circle fw')
-          -#%li
-            = link_to explore_root_path, title: 'Explore', data: {toggle: 'tooltip', placement: 'bottom'} do
-              = icon('globe fw')
-          -#%li
-            = link_to user_snippets_path(current_user), title: 'Your snippets', data: {toggle: 'tooltip', placement: 'bottom'} do
-              = icon('clipboard fw')
           - if current_user.is_admin?
             %li
               = link_to admin_root_path, title: 'Admin area', data: {toggle: 'tooltip', placement: 'bottom'} do
@@ -34,9 +25,6 @@
             %li.hidden-xs
               = link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom'} do
                 = icon('plus fw')
-          -#%li
-            = link_to profile_path, title: 'Profile settings', data: {toggle: 'tooltip', placement: 'bottom'} do
-              = icon('cog fw')
           %li
             = link_to destroy_user_session_path, class: 'logout', method: :delete, title: 'Sign out', data: {toggle: 'tooltip', placement: 'bottom'} do
               = icon('sign-out')
diff --git a/app/views/projects/deploy_keys/index.html.haml b/app/views/projects/deploy_keys/index.html.haml
index 2e9c5dc08c8..8e24c778b7c 100644
--- a/app/views/projects/deploy_keys/index.html.haml
+++ b/app/views/projects/deploy_keys/index.html.haml
@@ -1,4 +1,5 @@
 - page_title "Deploy Keys"
+
 %h3.page-title
   Deploy keys allow read-only access to the repository
 
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index 52c1e03040c..30943f49bba 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -1,3 +1,6 @@
+- if params[:view] == 'parallel'
+  - fluid_layout true
+
 .prepend-top-20.append-bottom-20
   .pull-right
     .btn-group
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 007f6c6a787..ec1838eb489 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -1,4 +1,7 @@
 - page_title "#{@merge_request.title} (##{@merge_request.iid})", "Merge Requests"
+- if params[:view] == 'parallel'
+  - fluid_layout true
+
 .merge-request{'data-url' => merge_request_path(@merge_request)}
   .merge-request-details.issuable-details
     = render "projects/merge_requests/show/mr_title"
-- 
cgit v1.2.1


From 2fa560d658ec7c49df97daadae443568bc72bdce Mon Sep 17 00:00:00 2001
From: Dmitriy Zaporozhets 
Date: Wed, 26 Aug 2015 13:15:02 +0200
Subject: Align header with content container

Signed-off-by: Dmitriy Zaporozhets 
---
 CHANGELOG                                       |  1 +
 app/assets/stylesheets/generic/header.scss      | 66 +++----------------------
 app/assets/stylesheets/generic/sidebar.scss     | 43 ++++++++++++++++
 app/assets/stylesheets/themes/gitlab-theme.scss | 26 +++++-----
 app/views/layouts/_page.html.haml               |  5 ++
 app/views/layouts/header/_default.html.haml     |  7 +--
 6 files changed, 67 insertions(+), 81 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index 5222ab1e3e1..5ab0b5c3faf 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -17,6 +17,7 @@ v 8.0.0 (unreleased)
   - Gracefully handle SMTP user input errors (e.g. incorrect email addresses) to prevent Sidekiq retries (Stan Hu)
   - Improve abuse reports management from admin area
   - Move dashboard activity to separate page
+  - Limit content width to 1200px for most of pages to improve readability on big screens
 
 v 7.14.1 (unreleased)
   - Only include base URL in OmniAuth full_host parameter (Stan Hu)
diff --git a/app/assets/stylesheets/generic/header.scss b/app/assets/stylesheets/generic/header.scss
index 31e2ad86691..6a29b32e196 100644
--- a/app/assets/stylesheets/generic/header.scss
+++ b/app/assets/stylesheets/generic/header.scss
@@ -20,16 +20,16 @@ header {
   }
 
   &.navbar-gitlab {
+    padding: 0 20px;
     z-index: 100;
     margin-bottom: 0;
     min-height: $header-height;
     border: none;
-    width: 100%;
+    border-bottom: 1px solid #EEE;
 
-    .container {
+    .container-fluid {
       background: #FFF;
       width: 100% !important;
-      padding: 0;
       filter: none;
 
       .nav > li > a {
@@ -64,55 +64,11 @@ header {
     }
   }
 
-  .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: ($header-height - 36 ) / 2 8px;
-      overflow: hidden;
-
-      img {
-        width: 36px;
-        height: 36px;
-        float: left;
-      }
-
-      .gitlab-text-container {
-        width: 230px;
-
-        h3 {
-          width: 158px;
-          float: left;
-          margin: 0;
-          margin-left: 14px;
-          font-size: 18px;
-          line-height: $header-height - 14;
-          font-weight: normal;
-        }
-      }
-    }
-
-    &:hover {
-      background-color: #EEE;
-    }
-  }
-
   .header-content {
-    border-bottom: 1px solid #EEE;
-    padding-right: 35px;
     height: $header-height;
 
     .title {
       margin: 0;
-      padding: 0 15px 0 35px;
       overflow: hidden;
       font-size: 18px;
       line-height: $header-height;
@@ -168,15 +124,7 @@ header {
 }
 
 @mixin collapsed-header {
-  .header-logo {
-    width: $sidebar_collapsed_width;
-  }
-
-  .header-content {
-    .title {
-      margin-left: 30px;
-    }
-  }
+  margin-left: $sidebar_collapsed_width;
 }
 
 @media (max-width: $screen-md-max) {
@@ -191,16 +139,14 @@ header {
   }
 
   .header-expanded {
+    margin-left: $sidebar_width;
   }
 }
 
 @media (max-width: $screen-xs-max) {
-  header .container {
+  header .container-fluid {
     font-size: 18px;
 
-    .title {
-    }
-
     .navbar-nav {
       margin: 0px;
       float: none !important;
diff --git a/app/assets/stylesheets/generic/sidebar.scss b/app/assets/stylesheets/generic/sidebar.scss
index b96664d30db..320bdb1c765 100644
--- a/app/assets/stylesheets/generic/sidebar.scss
+++ b/app/assets/stylesheets/generic/sidebar.scss
@@ -188,3 +188,46 @@
     width: $sidebar_width - 2 * 10px;
   }
 }
+
+.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: ($header-height - 36 ) / 2 8px;
+      overflow: hidden;
+
+      img {
+        width: 36px;
+        height: 36px;
+        float: left;
+      }
+
+      .gitlab-text-container {
+        width: 230px;
+
+        h3 {
+          width: 158px;
+          float: left;
+          margin: 0;
+          margin-left: 14px;
+          font-size: 18px;
+          line-height: $header-height - 14;
+          font-weight: normal;
+        }
+      }
+    }
+
+    &:hover {
+      background-color: #EEE;
+    }
+  }
+}
diff --git a/app/assets/stylesheets/themes/gitlab-theme.scss b/app/assets/stylesheets/themes/gitlab-theme.scss
index 3589cb88d03..77b62c3153f 100644
--- a/app/assets/stylesheets/themes/gitlab-theme.scss
+++ b/app/assets/stylesheets/themes/gitlab-theme.scss
@@ -7,27 +7,23 @@
  * $color-dark   -
  */
 @mixin gitlab-theme($color-light, $color, $color-darker, $color-dark) {
-  header {
-    &.navbar-gitlab {
-      .header-logo {
-        background-color: $color-darker;
-        border-color: $color-darker;
+  .page-with-sidebar {
+    .header-logo {
+      background-color: $color-darker;
+      border-color: $color-darker;
 
-        a {
-          color: $color-light;
-        }
+      a {
+        color: $color-light;
+      }
 
-        &:hover {
-          background-color: $color-dark;
-          a {
-            color: #FFF;
-          }
+      &:hover {
+        background-color: $color-dark;
+        a {
+          color: #FFF;
         }
       }
     }
-  }
 
-  .page-with-sidebar {
     .collapse-nav a {
       color: #FFF;
       background: $color;
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index 3d58f993cac..0104d7198df 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -1,6 +1,11 @@
 .page-with-sidebar{ class: nav_sidebar_class }
   = render "layouts/broadcast"
   .sidebar-wrapper.nicescroll
+    .header-logo
+      = link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home', data: {toggle: 'tooltip', placement: 'bottom'} do
+        = brand_header_logo
+        .gitlab-text-container
+          %h3 GitLab
     - if defined?(sidebar) && sidebar
       = render "layouts/nav/#{sidebar}"
     - elsif current_user
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 6086f36f07e..0b630b55c70 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -1,10 +1,5 @@
 %header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class }
-  .container
-    .header-logo
-      = link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home', data: {toggle: 'tooltip', placement: 'bottom'} do
-        = brand_header_logo
-        .gitlab-text-container
-          %h3 GitLab
+  %div{ class: fluid_layout ? "container-fluid" : "container-fluid container-limited" }
     .header-content
       %button.navbar-toggle{type: 'button'}
         %span.sr-only Toggle navigation
-- 
cgit v1.2.1


From bafffb2d14e1924154d5b7c74c7b3cbcf8c898fd Mon Sep 17 00:00:00 2001
From: Valery Sizov 
Date: Tue, 11 Aug 2015 09:59:40 +0300
Subject: Enable SSL verification for Webhooks

---
 CHANGELOG                                                |  1 +
 app/controllers/admin/hooks_controller.rb                |  2 +-
 app/controllers/projects/hooks_controller.rb             |  3 ++-
 app/controllers/projects/services_controller.rb          |  2 +-
 app/models/hooks/web_hook.rb                             |  5 +++--
 app/models/project_services/buildkite_service.rb         |  9 +++++++--
 app/models/project_services/gitlab_ci_service.rb         |  6 ++++--
 app/views/admin/hooks/index.html.haml                    |  8 ++++++++
 app/views/projects/hooks/index.html.haml                 |  8 ++++++++
 db/migrate/20150824002011_add_enable_ssl_verification.rb |  5 +++++
 db/schema.rb                                             | 15 ++++++++-------
 features/admin/hooks.feature                             |  9 +++++++++
 features/project/hooks.feature                           |  5 +++++
 features/steps/admin/hooks.rb                            | 15 +++++++++++++++
 features/steps/project/hooks.rb                          | 13 +++++++++++++
 15 files changed, 90 insertions(+), 16 deletions(-)
 create mode 100644 db/migrate/20150824002011_add_enable_ssl_verification.rb
 create mode 100644 features/admin/hooks.feature
 create mode 100644 features/steps/admin/hooks.rb

diff --git a/CHANGELOG b/CHANGELOG
index 7ec1dabcf95..2c1b1a53fe9 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -12,6 +12,7 @@ v 8.0.0 (unreleased)
   - Search for comments should be case insensetive
   - Create cross-reference for closing references on commits pushed to non-default branches (Maël Valais)
   - Ability to search milestones
+  - Ability to enable SSL verification for Webhooks
 
 v 7.14.0
   - Fix bug where non-project members of the target project could set labels on new merge requests.
diff --git a/app/controllers/admin/hooks_controller.rb b/app/controllers/admin/hooks_controller.rb
index 690096bdbcf..d670386f8c6 100644
--- a/app/controllers/admin/hooks_controller.rb
+++ b/app/controllers/admin/hooks_controller.rb
@@ -39,6 +39,6 @@ class Admin::HooksController < Admin::ApplicationController
   end
 
   def hook_params
-    params.require(:hook).permit(:url)
+    params.require(:hook).permit(:url, :enable_ssl_verification)
   end
 end
diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb
index 76062446c92..4e5b4125f5a 100644
--- a/app/controllers/projects/hooks_controller.rb
+++ b/app/controllers/projects/hooks_controller.rb
@@ -53,6 +53,7 @@ class Projects::HooksController < Projects::ApplicationController
   end
 
   def hook_params
-    params.require(:hook).permit(:url, :push_events, :issues_events, :merge_requests_events, :tag_push_events, :note_events)
+    params.require(:hook).permit(:url, :push_events, :issues_events,
+      :merge_requests_events, :tag_push_events, :note_events, :enable_ssl_verification)
   end
 end
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
index 01105532479..b0cf5866d41 100644
--- a/app/controllers/projects/services_controller.rb
+++ b/app/controllers/projects/services_controller.rb
@@ -8,7 +8,7 @@ class Projects::ServicesController < Projects::ApplicationController
                     :push_events, :issues_events, :merge_requests_events, :tag_push_events,
                     :note_events, :send_from_committer_email, :disable_diffs, :external_wiki_url,
                     :notify, :color,
-                    :server_host, :server_port, :default_irc_uri]
+                    :server_host, :server_port, :default_irc_uri, :enable_ssl_verification]
   # Authorize
   before_action :authorize_admin_project!
   before_action :service, only: [:edit, :update, :test]
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index 46fb85336e5..9a8251bdad5 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -25,6 +25,7 @@ class WebHook < ActiveRecord::Base
   default_value_for :note_events, false
   default_value_for :merge_requests_events, false
   default_value_for :tag_push_events, false
+  default_value_for :enable_ssl_verification, false
 
   # HTTParty timeout
   default_timeout Gitlab.config.gitlab.webhook_timeout
@@ -41,7 +42,7 @@ class WebHook < ActiveRecord::Base
                      "Content-Type" => "application/json",
                      "X-Gitlab-Event" => hook_name.singularize.titleize
                    },
-                   verify: false)
+                   verify: enable_ssl_verification)
     else
       post_url = url.gsub("#{parsed_url.userinfo}@", "")
       auth = {
@@ -54,7 +55,7 @@ class WebHook < ActiveRecord::Base
                      "Content-Type" => "application/json",
                      "X-Gitlab-Event" => hook_name.singularize.titleize
                    },
-                   verify: false,
+                   verify: enable_ssl_verification,
                    basic_auth: auth)
     end
   rescue SocketError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e
diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb
index a714bc82246..9e5da6f45d2 100644
--- a/app/models/project_services/buildkite_service.rb
+++ b/app/models/project_services/buildkite_service.rb
@@ -23,7 +23,7 @@ require "addressable/uri"
 class BuildkiteService < CiService
   ENDPOINT = "https://buildkite.com"
 
-  prop_accessor :project_url, :token
+  prop_accessor :project_url, :token, :enable_ssl_verification
 
   validates :project_url, presence: true, if: :activated?
   validates :token, presence: true, if: :activated?
@@ -37,6 +37,7 @@ class BuildkiteService < CiService
   def compose_service_hook
     hook = service_hook || build_service_hook
     hook.url = webhook_url
+    hook.enable_ssl_verification = enable_ssl_verification
     hook.save
   end
 
@@ -96,7 +97,11 @@ class BuildkiteService < CiService
 
       { type: 'text',
         name: 'project_url',
-        placeholder: "#{ENDPOINT}/example/project" }
+        placeholder: "#{ENDPOINT}/example/project" },
+      
+      { type: 'checkbox',
+        name: 'enable_ssl_verification',
+        title: "Enable SSL verification" }
     ]
   end
 
diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb
index ecdcd48ae60..acbbc9935b6 100644
--- a/app/models/project_services/gitlab_ci_service.rb
+++ b/app/models/project_services/gitlab_ci_service.rb
@@ -21,7 +21,7 @@
 class GitlabCiService < CiService
   API_PREFIX = "api/v1"
 
-  prop_accessor :project_url, :token
+  prop_accessor :project_url, :token, :enable_ssl_verification
   validates :project_url,
     presence: true,
     format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }, if: :activated?
@@ -34,6 +34,7 @@ class GitlabCiService < CiService
   def compose_service_hook
     hook = service_hook || build_service_hook
     hook.url = [project_url, "/build", "?token=#{token}"].join("")
+    hook.enable_ssl_verification = enable_ssl_verification
     hook.save
   end
 
@@ -136,7 +137,8 @@ class GitlabCiService < CiService
   def fields
     [
       { type: 'text', name: 'token', placeholder: 'GitLab CI project specific token' },
-      { type: 'text', name: 'project_url', placeholder: 'http://ci.gitlabhq.com/projects/3' }
+      { type: 'text', name: 'project_url', placeholder: 'http://ci.gitlabhq.com/projects/3' },
+      { type: 'checkbox', name: 'enable_ssl_verification', title: "Enable SSL verification" }
     ]
   end
 
diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml
index e74e1e85f41..b120f4dea67 100644
--- a/app/views/admin/hooks/index.html.haml
+++ b/app/views/admin/hooks/index.html.haml
@@ -18,6 +18,13 @@
     = f.label :url, "URL:", class: 'control-label'
     .col-sm-10
       = f.text_field :url, class: "form-control"
+  .form-group
+    = f.label :enable_ssl_verification, "SSL verification", class: 'control-label checkbox'
+    .col-sm-10
+      .checkbox
+        = f.label :enable_ssl_verification do
+          = f.check_box :enable_ssl_verification
+          %strong Enable SSL verification
   .form-actions
     = f.submit "Add System Hook", class: "btn btn-create"
 %hr
@@ -32,6 +39,7 @@
           .list-item-name
             = link_to admin_hook_path(hook) do
               %strong= hook.url
+            %p SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}
 
           .pull-right
             = link_to 'Test Hook', admin_hook_test_path(hook), class: "btn btn-sm"
diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml
index eadbf61fdd4..85dbfd67862 100644
--- a/app/views/projects/hooks/index.html.haml
+++ b/app/views/projects/hooks/index.html.haml
@@ -55,6 +55,13 @@
             %strong Merge Request events
           %p.light
             This url will be triggered when a merge request is created
+  .form-group
+    = f.label :enable_ssl_verification, "SSL verification", class: 'control-label checkbox'
+    .col-sm-10
+      .checkbox
+        = f.label :enable_ssl_verification do
+          = f.check_box :enable_ssl_verification
+          %strong Enable SSL verification
   .form-actions
     = f.submit "Add Web Hook", class: "btn btn-create"
 
@@ -74,3 +81,4 @@
             - %w(push_events tag_push_events issues_events note_events merge_requests_events).each do |trigger|
               - if hook.send(trigger)
                 %span.label.label-gray= trigger.titleize
+            SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}
diff --git a/db/migrate/20150824002011_add_enable_ssl_verification.rb b/db/migrate/20150824002011_add_enable_ssl_verification.rb
new file mode 100644
index 00000000000..093c068fbde
--- /dev/null
+++ b/db/migrate/20150824002011_add_enable_ssl_verification.rb
@@ -0,0 +1,5 @@
+class AddEnableSslVerification < ActiveRecord::Migration
+  def change
+    add_column :web_hooks, :enable_ssl_verification, :boolean, default: false
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 108c48bf321..7ee1c6e2146 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 20150818213832) do
+ActiveRecord::Schema.define(version: 20150824002011) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
@@ -566,13 +566,14 @@ ActiveRecord::Schema.define(version: 20150818213832) do
     t.integer  "project_id"
     t.datetime "created_at"
     t.datetime "updated_at"
-    t.string   "type",                  default: "ProjectHook"
+    t.string   "type",                    default: "ProjectHook"
     t.integer  "service_id"
-    t.boolean  "push_events",           default: true,          null: false
-    t.boolean  "issues_events",         default: false,         null: false
-    t.boolean  "merge_requests_events", default: false,         null: false
-    t.boolean  "tag_push_events",       default: false
-    t.boolean  "note_events",           default: false,         null: false
+    t.boolean  "push_events",             default: true,          null: false
+    t.boolean  "issues_events",           default: false,         null: false
+    t.boolean  "merge_requests_events",   default: false,         null: false
+    t.boolean  "tag_push_events",         default: false
+    t.boolean  "note_events",             default: false,         null: false
+    t.boolean  "enable_ssl_verification", default: false
   end
 
   add_index "web_hooks", ["created_at", "id"], name: "index_web_hooks_on_created_at_and_id", using: :btree
diff --git a/features/admin/hooks.feature b/features/admin/hooks.feature
new file mode 100644
index 00000000000..5ca332d9f1c
--- /dev/null
+++ b/features/admin/hooks.feature
@@ -0,0 +1,9 @@
+@admin
+Feature: Admin Hooks
+  Background:
+    Given I sign in as an admin
+
+  Scenario: On Admin Hooks
+    Given I visit admin hooks page
+    Then I submit the form with enabled SSL verification
+    And I see new hook with enabled SSL verification
\ No newline at end of file
diff --git a/features/project/hooks.feature b/features/project/hooks.feature
index 1a60846a23e..627738004c4 100644
--- a/features/project/hooks.feature
+++ b/features/project/hooks.feature
@@ -13,6 +13,11 @@ Feature: Project Hooks
     When I submit new hook
     Then I should see newly created hook
 
+  Scenario: I add new hook with SSL verification enabled
+    Given I visit project hooks page
+    When I submit new hook with SSL verification enabled
+    Then I should see newly created hook with SSL verification enabled
+
   Scenario: I test hook
     Given project has hook
     And I visit project hooks page
diff --git a/features/steps/admin/hooks.rb b/features/steps/admin/hooks.rb
new file mode 100644
index 00000000000..541e25fcb70
--- /dev/null
+++ b/features/steps/admin/hooks.rb
@@ -0,0 +1,15 @@
+class Spinach::Features::AdminHooks < Spinach::FeatureSteps
+  include SharedAuthentication
+  include SharedPaths
+  include SharedAdmin
+
+  step "I submit the form with enabled SSL verification" do
+    fill_in 'hook_url', with: 'http://google.com'
+    check "Enable SSL verification"
+    click_on "Add System Hook"
+  end
+
+  step "I see new hook with enabled SSL verification" do
+    expect(page).to have_content "SSL Verification: enabled"
+  end
+end
diff --git a/features/steps/project/hooks.rb b/features/steps/project/hooks.rb
index 04e3bf78ede..df4a23a3716 100644
--- a/features/steps/project/hooks.rb
+++ b/features/steps/project/hooks.rb
@@ -28,11 +28,24 @@ class Spinach::Features::ProjectHooks < Spinach::FeatureSteps
     expect { click_button "Add Web Hook" }.to change(ProjectHook, :count).by(1)
   end
 
+  step 'I submit new hook with SSL verification enabled' do
+    @url = FFaker::Internet.uri("http")
+    fill_in "hook_url", with: @url
+    check "hook_enable_ssl_verification"
+    expect { click_button "Add Web Hook" }.to change(ProjectHook, :count).by(1)
+  end
+
   step 'I should see newly created hook' do
     expect(current_path).to eq namespace_project_hooks_path(current_project.namespace, current_project)
     expect(page).to have_content(@url)
   end
 
+  step 'I should see newly created hook with SSL verification enabled' do
+    expect(current_path).to eq namespace_project_hooks_path(current_project.namespace, current_project)
+    expect(page).to have_content(@url)
+    expect(page).to have_content("SSL Verification: enabled")
+  end
+
   step 'I click test hook button' do
     stub_request(:post, @hook.url).to_return(status: 200)
     click_link 'Test Hook'
-- 
cgit v1.2.1


From 99b2be7625fe7849aa0559356beef86c1840fb65 Mon Sep 17 00:00:00 2001
From: Valery Sizov 
Date: Wed, 26 Aug 2015 16:39:55 +0300
Subject: Improve perofrmance of git blame

---
 CHANGELOG                               | 1 +
 app/views/projects/blame/show.html.haml | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG b/CHANGELOG
index be99941b4cd..dc1d88bf61e 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -18,6 +18,7 @@ v 8.0.0 (unreleased)
   - Gracefully handle SMTP user input errors (e.g. incorrect email addresses) to prevent Sidekiq retries (Stan Hu)
   - Improve abuse reports management from admin area
   - Move dashboard activity to separate page
+  - Improve performance of git blame
 
 v 7.14.1 (unreleased)
   - Only include base URL in OmniAuth full_host parameter (Stan Hu)
diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml
index a3ff7ce2f1f..c1ec42aefca 100644
--- a/app/views/projects/blame/show.html.haml
+++ b/app/views/projects/blame/show.html.haml
@@ -27,7 +27,7 @@
                 .light
                   = commit_author_link(commit, avatar: false)
                   authored
-                  #{time_ago_with_tooltip(commit.committed_date)}
+                  #{time_ago_with_tooltip(commit.committed_date, skip_js: true)}
             %td.lines.blame-numbers
               %pre
                 - line_count = blame_group[:lines].count
-- 
cgit v1.2.1


From 468d29251da81bb3a16380b4465becf35db2ba23 Mon Sep 17 00:00:00 2001
From: Dmitriy Zaporozhets 
Date: Wed, 26 Aug 2015 16:00:37 +0200
Subject: Fix header for anonymous users too

Signed-off-by: Dmitriy Zaporozhets 
---
 app/views/layouts/header/_public.html.haml | 7 +------
 1 file changed, 1 insertion(+), 6 deletions(-)

diff --git a/app/views/layouts/header/_public.html.haml b/app/views/layouts/header/_public.html.haml
index 15c2e292be3..af4b9ba58f6 100644
--- a/app/views/layouts/header/_public.html.haml
+++ b/app/views/layouts/header/_public.html.haml
@@ -1,10 +1,5 @@
 %header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class }
-  .container
-    .header-logo
-      = link_to explore_root_path, class: "home" do
-        = brand_header_logo
-        .gitlab-text-container
-          %h3 GitLab
+  %div{ class: fluid_layout ? "container-fluid" : "container-fluid container-limited" }
     .header-content
       - unless current_controller?('sessions')
         .pull-right
-- 
cgit v1.2.1


From c1fe98e0b9dbe40d564dc5ddad23e14dd0e6e462 Mon Sep 17 00:00:00 2001
From: Stan Hu 
Date: Wed, 26 Aug 2015 07:38:33 -0700
Subject: Prevent too many redirects error when home page URL set to
 external_urll

Many users naively set the home page URL setting to external_url
(e.g. https://mydomain.com). When an unauthenticated user signs in, this
causes endless redirections. For example, this is occuring:

1. Unauthenticated user attempts to access https://mydomain.com/dashboard
2. Application redirects to the home page URL: https://mydomain.com
3. Repeat step 2

In step 3, ApplicationController should have redirected the user to
https://mydomain.com/users/sign_in. Disabling the redirection if home
page URL is the same as external_url prevents users from messing up.

Closes https://github.com/gitlabhq/gitlabhq/issues/8843
Closes #2057
---
 CHANGELOG                                 | 1 +
 app/controllers/application_controller.rb | 4 +++-
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG b/CHANGELOG
index a93558fb10d..92793b6be24 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 Please view this file on the master branch, on stable branches it's out of date.
 
 v 8.0.0 (unreleased)
+  - Prevent too many redirects upon login when home page URL is set to external_url (Stan Hu)
   - Improve dropdown positioning on the project home page (Hannes Rosenögger)
   - Upgrade browser gem to 1.0.0 to avoid warning in IE11 compatibilty mode (Stan Hu)
   - Fix "Reload with full diff" URL button in compare branch view (Stan Hu)
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 12d439b0b31..ef1170e16da 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -55,7 +55,9 @@ class ApplicationController < ActionController::Base
 
   def authenticate_user!(*args)
     # If user is not signed-in and tries to access root_path - redirect him to landing page
-    if current_application_settings.home_page_url.present?
+    # Don't redirect to the default URL to prevent endless redirections
+    if current_application_settings.home_page_url.present? &&
+        current_application_settings.home_page_url.chomp('/') != Gitlab.config.gitlab['url'].chomp('/')
       if current_user.nil? && root_path == request.path
         redirect_to current_application_settings.home_page_url and return
       end
-- 
cgit v1.2.1


From 5ea15fc7b556d1e5e3a778d2f100b903904e8c6a Mon Sep 17 00:00:00 2001
From: Stan Hu 
Date: Wed, 26 Aug 2015 07:53:26 -0700
Subject: Update CHANGELOG for released 7.14.1

[ci skip]
---
 CHANGELOG | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index dc1d88bf61e..6743b93f58c 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -3,7 +3,6 @@ Please view this file on the master branch, on stable branches it's out of date.
 v 8.0.0 (unreleased)
   - Improve dropdown positioning on the project home page (Hannes Rosenögger)
   - Upgrade browser gem to 1.0.0 to avoid warning in IE11 compatibilty mode (Stan Hu)
-  - Fix "Reload with full diff" URL button in compare branch view (Stan Hu)
   - Remove user OAuth tokens from the database and request new tokens each session (Stan Hu)
   - Only show recent push event if the branch still exists or a recent merge request has not been created (Stan Hu)
   - Remove satellites
@@ -16,11 +15,12 @@ v 8.0.0 (unreleased)
   - Create cross-reference for closing references on commits pushed to non-default branches (Maël Valais)
   - Ability to search milestones
   - Gracefully handle SMTP user input errors (e.g. incorrect email addresses) to prevent Sidekiq retries (Stan Hu)
-  - Improve abuse reports management from admin area
   - Move dashboard activity to separate page
   - Improve performance of git blame
 
-v 7.14.1 (unreleased)
+v 7.14.1
+  - Improve abuse reports management from admin area
+  - Fix "Reload with full diff" URL button in compare branch view (Stan Hu)
   - Only include base URL in OmniAuth full_host parameter (Stan Hu)
   - Fix Error 500 in API when accessing a group that has an avatar (Stan Hu)
 
-- 
cgit v1.2.1


From bc683a590402b6ce017a613616623b65b6823ec4 Mon Sep 17 00:00:00 2001
From: Douwe Maan 
Date: Wed, 26 Aug 2015 08:58:46 -0700
Subject: Update mail_room

---
 Gemfile      | 2 +-
 Gemfile.lock | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/Gemfile b/Gemfile
index fa54a72f025..4696a961e82 100644
--- a/Gemfile
+++ b/Gemfile
@@ -273,6 +273,6 @@ gem "newrelic_rpm"
 
 gem 'octokit', '3.7.0'
 
-gem "mail_room", "~> 0.4.0"
+gem "mail_room", "~> 0.4.1"
 
 gem 'email_reply_parser'
diff --git a/Gemfile.lock b/Gemfile.lock
index a7cfe6b7511..814249c7ff6 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -372,7 +372,7 @@ GEM
       systemu (~> 2.6.2)
     mail (2.6.3)
       mime-types (>= 1.16, < 3)
-    mail_room (0.4.0)
+    mail_room (0.4.1)
     method_source (0.8.2)
     mime-types (1.25.1)
     mimemagic (0.3.0)
@@ -808,7 +808,7 @@ DEPENDENCIES
   jquery-ui-rails
   kaminari (~> 0.15.1)
   letter_opener
-  mail_room (~> 0.4.0)
+  mail_room (~> 0.4.1)
   minitest (~> 5.3.0)
   mousetrap-rails
   mysql2
-- 
cgit v1.2.1


From 6d43308b5a5ff7663e1782fd6f5493f48c0f7e2a Mon Sep 17 00:00:00 2001
From: Robert Speicher 
Date: Wed, 26 Aug 2015 11:30:11 -0700
Subject: Add `Gitlab::Themes.for_user`

---
 lib/gitlab/themes.rb | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/lib/gitlab/themes.rb b/lib/gitlab/themes.rb
index 5209df92795..37a36b9599b 100644
--- a/lib/gitlab/themes.rb
+++ b/lib/gitlab/themes.rb
@@ -51,6 +51,19 @@ module Gitlab
       THEMES.each(&block)
     end
 
+    # Get the Theme for the specified user, or the default
+    #
+    # user - User record
+    #
+    # Returns a Theme
+    def self.for_user(user)
+      if user
+        by_id(user.theme_id)
+      else
+        default
+      end
+    end
+
     private
 
     def self.default_id
-- 
cgit v1.2.1


From 2d72efcd9fb63f4bb0c6aed5a472a2eca2d6abac Mon Sep 17 00:00:00 2001
From: Robert Speicher 
Date: Wed, 26 Aug 2015 11:30:38 -0700
Subject: Add `count` to Themes and ColorSchemes

---
 lib/gitlab/color_schemes.rb | 5 +++++
 lib/gitlab/themes.rb        | 5 +++++
 2 files changed, 10 insertions(+)

diff --git a/lib/gitlab/color_schemes.rb b/lib/gitlab/color_schemes.rb
index 763853ab1cb..9c4664df903 100644
--- a/lib/gitlab/color_schemes.rb
+++ b/lib/gitlab/color_schemes.rb
@@ -32,6 +32,11 @@ module Gitlab
       SCHEMES.detect { |s| s.id == id } || default
     end
 
+    # Returns the number of defined Schemes
+    def self.count
+      SCHEMES.size
+    end
+
     # Get the default Scheme
     #
     # Returns a Scheme
diff --git a/lib/gitlab/themes.rb b/lib/gitlab/themes.rb
index 37a36b9599b..83f91de810c 100644
--- a/lib/gitlab/themes.rb
+++ b/lib/gitlab/themes.rb
@@ -37,6 +37,11 @@ module Gitlab
       THEMES.detect { |t| t.id == id } || default
     end
 
+    # Returns the number of defined Themes
+    def self.count
+      THEMES.size
+    end
+
     # Get the default Theme
     #
     # Returns a Theme
-- 
cgit v1.2.1


From 7a81dc65d9013dff4d8bf36cf78e027693703990 Mon Sep 17 00:00:00 2001
From: Robert Speicher 
Date: Wed, 26 Aug 2015 11:33:44 -0700
Subject: Re-add user_color_scheme helper

Update PreferencesHelper specs
---
 app/helpers/preferences_helper.rb       |  6 ++-
 spec/helpers/preferences_helper_spec.rb | 77 +++++++++++++++++++++++----------
 2 files changed, 59 insertions(+), 24 deletions(-)

diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb
index 51a6b3fa997..7f1b6a69926 100644
--- a/app/helpers/preferences_helper.rb
+++ b/app/helpers/preferences_helper.rb
@@ -30,7 +30,11 @@ module PreferencesHelper
   end
 
   def user_application_theme
-    Gitlab::Themes.by_id(current_user.try(:theme_id)).css_class
+    Gitlab::Themes.for_user(current_user).css_class
+  end
+
+  def user_color_scheme
+    Gitlab::ColorSchemes.for_user(current_user).css_class
   end
 
   def prefer_readme?
diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb
index b4dee272bfd..06f69262b71 100644
--- a/spec/helpers/preferences_helper_spec.rb
+++ b/spec/helpers/preferences_helper_spec.rb
@@ -1,51 +1,82 @@
 require 'spec_helper'
 
 describe PreferencesHelper do
+  describe 'dashboard_choices' do
+    it 'raises an exception when defined choices may be missing' do
+      expect(User).to receive(:dashboards).and_return(foo: 'foo')
+      expect { helper.dashboard_choices }.to raise_error(RuntimeError)
+    end
+
+    it 'raises an exception when defined choices may be using the wrong key' do
+      expect(User).to receive(:dashboards).and_return(foo: 'foo', bar: 'bar')
+      expect { helper.dashboard_choices }.to raise_error(KeyError)
+    end
+
+    it 'provides better option descriptions' do
+      expect(helper.dashboard_choices).to match_array [
+        ['Your Projects (default)', 'projects'],
+        ['Starred Projects',        'stars']
+      ]
+    end
+  end
+
   describe 'user_application_theme' do
     context 'with a user' do
       it "returns user's theme's css_class" do
-        user = double('user', theme_id: 3)
-        allow(self).to receive(:current_user).and_return(user)
-        expect(user_application_theme).to eq 'ui_green'
+        stub_user(theme_id: 3)
+
+        expect(helper.user_application_theme).to eq 'ui_green'
       end
 
       it 'returns the default when id is invalid' do
-        user = double('user', theme_id: Gitlab::Themes::THEMES.size + 5)
+        stub_user(theme_id: Gitlab::Themes.count + 5)
 
         allow(Gitlab.config.gitlab).to receive(:default_theme).and_return(2)
-        allow(self).to receive(:current_user).and_return(user)
 
-        expect(user_application_theme).to eq 'ui_charcoal'
+        expect(helper.user_application_theme).to eq 'ui_charcoal'
       end
     end
 
     context 'without a user' do
-      before do
-        allow(self).to receive(:current_user).and_return(nil)
-      end
-
       it 'returns the default theme' do
-        expect(user_application_theme).to eq Gitlab::Themes.default.css_class
+        stub_user
+
+        expect(helper.user_application_theme).to eq Gitlab::Themes.default.css_class
       end
     end
   end
 
-  describe 'dashboard_choices' do
-    it 'raises an exception when defined choices may be missing' do
-      expect(User).to receive(:dashboards).and_return(foo: 'foo')
-      expect { dashboard_choices }.to raise_error(RuntimeError)
+  describe 'user_color_scheme' do
+    context 'with a user' do
+      it "returns user's scheme's css_class" do
+        allow(helper).to receive(:current_user).
+          and_return(double(color_scheme_id: 3))
+
+        expect(helper.user_color_scheme).to eq 'solarized-light'
+      end
+
+      it 'returns the default when id is invalid' do
+        allow(helper).to receive(:current_user).
+          and_return(double(color_scheme_id: Gitlab::ColorSchemes.count + 5))
+      end
     end
 
-    it 'raises an exception when defined choices may be using the wrong key' do
-      expect(User).to receive(:dashboards).and_return(foo: 'foo', bar: 'bar')
-      expect { dashboard_choices }.to raise_error(KeyError)
+    context 'without a user' do
+      it 'returns the default theme' do
+        stub_user
+
+        expect(helper.user_color_scheme).
+          to eq Gitlab::ColorSchemes.default.css_class
+      end
     end
+  end
 
-    it 'provides better option descriptions' do
-      expect(dashboard_choices).to match_array [
-        ['Your Projects (default)', 'projects'],
-        ['Starred Projects',        'stars']
-      ]
+  def stub_user(messages = {})
+    if messages.empty?
+      allow(helper).to receive(:current_user).and_return(nil)
+    else
+      allow(helper).to receive(:current_user).
+        and_return(double('user', messages))
     end
   end
 end
-- 
cgit v1.2.1


From 54771100eb8693c7fa06ea31041b0bd15f78dcc7 Mon Sep 17 00:00:00 2001
From: Robert Speicher 
Date: Wed, 26 Aug 2015 11:34:28 -0700
Subject: Add user_color_scheme class to file_highlight

Prevents an unstyled flash that occurs when we were only applying the
syntax highlighting class in Javascript. Now for these uncached blobs
the server can add the syntax class, and for (possibly) cached fenced
code blocks, the Javascript will add it as needed.
---
 app/views/shared/_file_highlight.html.haml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml
index 7b1c624d769..57c3aff3e18 100644
--- a/app/views/shared/_file_highlight.html.haml
+++ b/app/views/shared/_file_highlight.html.haml
@@ -1,4 +1,4 @@
-.file-content.code.js-syntax-highlight
+.file-content.code.js-syntax-highlight{ class: user_color_scheme }
   .line-numbers
     - if blob.data.present?
       - blob.data.lines.each_index do |index|
-- 
cgit v1.2.1


From ce0a0feff45b47b6c03b9ab01d815b651840a7b2 Mon Sep 17 00:00:00 2001
From: Robert Speicher 
Date: Wed, 26 Aug 2015 11:43:25 -0700
Subject: Make snippet_blob highlight server-side as well

---
 app/views/search/results/_snippet_blob.html.haml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/views/search/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml
index 19a7bfefef5..9a4f9fb9485 100644
--- a/app/views/search/results/_snippet_blob.html.haml
+++ b/app/views/search/results/_snippet_blob.html.haml
@@ -23,7 +23,7 @@
                 .nothing-here-block Empty file
       - else
         .file-content.code
-          %div.highlighted-data.js-syntax-highlight
+          %div.highlighted-data{ class: user_color_scheme }
             .line-numbers
               - snippet_blob[:snippet_chunks].each do |snippet|
                 - unless snippet[:data].empty?
-- 
cgit v1.2.1


From c27c81331183dd6c878b4a74df7e62e37b006a04 Mon Sep 17 00:00:00 2001
From: Dmitriy Zaporozhets 
Date: Wed, 26 Aug 2015 22:43:04 +0200
Subject: Improve trending projects finder

Signed-off-by: Dmitriy Zaporozhets 
---
 app/finders/trending_projects_finder.rb | 18 +++++++++++++-----
 1 file changed, 13 insertions(+), 5 deletions(-)

diff --git a/app/finders/trending_projects_finder.rb b/app/finders/trending_projects_finder.rb
index a79bd47d986..f3f4d461efa 100644
--- a/app/finders/trending_projects_finder.rb
+++ b/app/finders/trending_projects_finder.rb
@@ -2,13 +2,21 @@ class TrendingProjectsFinder
   def execute(current_user, start_date = nil)
     start_date ||= Date.today - 1.month
 
-    projects = projects_for(current_user)
-
     # Determine trending projects based on comments count
     # for period of time - ex. month
-    projects.joins(:notes).where('notes.created_at > ?', start_date).
-      select("projects.*, count(notes.id) as ncount").
-      group("projects.id").reorder("ncount DESC")
+    trending_project_ids = Note.
+      select("notes.project_id, count(notes.project_id) as pcount").
+      where('notes.created_at > ?', start_date).
+      group("project_id").
+      reorder("pcount DESC").
+      map(&:project_id)
+
+    sql_order_ids = trending_project_ids.reverse.
+      map { |project_id| "id = #{project_id}" }.join(", ")
+
+    # Get list of projects that user allowed to see
+    projects = projects_for(current_user)
+    projects.where(id: trending_project_ids).reorder(sql_order_ids)
   end
 
   private
-- 
cgit v1.2.1


From b54358b45785639acc49e4b065020d2089040bd9 Mon Sep 17 00:00:00 2001
From: Dmitriy Zaporozhets 
Date: Wed, 26 Aug 2015 22:44:02 +0200
Subject: Refactor project list rendering

Signed-off-by: Dmitriy Zaporozhets 
---
 app/assets/javascripts/dispatcher.js.coffee    |  4 ---
 app/assets/stylesheets/base/mixins.scss        | 37 ++++++++++++++++++++++++++
 app/assets/stylesheets/generic/lists.scss      | 20 ++------------
 app/assets/stylesheets/pages/projects.scss     | 35 +++++++++++++++---------
 app/views/dashboard/_projects.html.haml        |  5 +---
 app/views/dashboard/projects/starred.html.haml |  3 +--
 app/views/explore/projects/_projects.html.haml | 12 ++++-----
 app/views/groups/_projects.html.haml           |  2 +-
 app/views/search/_results.html.haml            |  6 ++++-
 app/views/search/results/_project.html.haml    |  6 -----
 app/views/shared/_project.html.haml            | 20 --------------
 app/views/shared/_projects_list.html.haml      | 17 ------------
 app/views/shared/projects/_list.html.haml      | 19 +++++++++++++
 app/views/shared/projects/_project.html.haml   | 23 ++++++++++++++++
 app/views/users/_projects.html.haml            |  4 +--
 15 files changed, 119 insertions(+), 94 deletions(-)
 delete mode 100644 app/views/search/results/_project.html.haml
 delete mode 100644 app/views/shared/_project.html.haml
 delete mode 100644 app/views/shared/_projects_list.html.haml
 create mode 100644 app/views/shared/projects/_list.html.haml
 create mode 100644 app/views/shared/projects/_project.html.haml

diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index 539041c2862..5bf0b302179 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -55,7 +55,6 @@ class Dispatcher
         new Activities()
       when 'dashboard:projects:starred'
         new Activities()
-        new ProjectsList()
       when 'projects:commit:show'
         new Commit()
         new Diff()
@@ -70,7 +69,6 @@ class Dispatcher
       when 'groups:show'
         new Activities()
         shortcut_handler = new ShortcutsNavigation()
-        new ProjectsList()
       when 'groups:group_members:index'
         new GroupMembers()
         new UsersSelect()
@@ -96,8 +94,6 @@ class Dispatcher
       when 'users:show'
         new User()
         new Activities()
-      when 'admin:users:show'
-        new ProjectsList()
 
     switch path.first()
       when 'admin'
diff --git a/app/assets/stylesheets/base/mixins.scss b/app/assets/stylesheets/base/mixins.scss
index 7beef1845ef..3974befa95c 100644
--- a/app/assets/stylesheets/base/mixins.scss
+++ b/app/assets/stylesheets/base/mixins.scss
@@ -157,3 +157,40 @@
   white-space: nowrap;
   max-width: $max_width;
 }
+
+/*
+ * Base mixin for lists in GitLab
+ */
+@mixin basic-list {
+  margin: 5px 0px;
+  padding: 0px;
+
+  li {
+    padding: 10px 0;
+    border-bottom: 1px solid #EEE;
+    overflow: hidden;
+    display: block;
+    margin: 0px;
+
+    &:last-child {
+      border:none
+    }
+
+    &.active {
+      background: #f9f9f9;
+      a {
+        font-weight: bold;
+      }
+    }
+
+    &.hide {
+      display: none;
+    }
+
+    &.light {
+      a {
+        color: #777;
+      }
+    }
+  }
+}
diff --git a/app/assets/stylesheets/generic/lists.scss b/app/assets/stylesheets/generic/lists.scss
index c502d953c75..4b7ff84de2b 100644
--- a/app/assets/stylesheets/generic/lists.scss
+++ b/app/assets/stylesheets/generic/lists.scss
@@ -93,28 +93,12 @@ ol, ul {
 
 /** light list with border-bottom between li **/
 ul.bordered-list {
-  margin: 5px 0px;
-  padding: 0px;
-  li {
-    padding: 5px 0;
-    border-bottom: 1px solid #EEE;
-    overflow: hidden;
-    display: block;
-    margin: 0px;
-    &:last-child { border:none }
-    &.active {
-      background: #f9f9f9;
-      a { font-weight: bold; }
-    }
-
-    &.light {
-      a { color: #777; }
-    }
-  }
+  @include basic-list;
 
   &.top-list {
     li:first-child {
       padding-top: 0;
+
       h4, h5 {
         margin-top: 0;
       }
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 4d065c3bdf6..8339c6f5e6b 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -334,23 +334,32 @@ pre.light-well {
   }
 }
 
-.project-row {
-  .project-full-name {
-    font-weight: bold;
-    font-size: 15px;
-  }
-
-  .project-description {
-    color: #888;
-    font-size: 13px;
+/*
+ * Projects list rendered on dashboard and user page
+ */
+.projects-list {
+  @include basic-list;
+
+  .project-row {
+    .project-full-name {
+      @include str-truncated;
+      font-weight: bold;
+      font-size: 15px;
+    }
 
-    p {
-      margin-bottom: 0;
+    .project-description {
       color: #888;
+      font-size: 13px;
+
+      p {
+        @include str-truncated;
+        margin-bottom: 0;
+        color: #888;
+      }
     }
   }
 }
 
-.my-projects .project-row {
-  padding: 10px 0;
+.panel .projects-list li {
+  padding: 10px 15px;
 }
diff --git a/app/views/dashboard/_projects.html.haml b/app/views/dashboard/_projects.html.haml
index dc83d5343f2..ef9b9ce756a 100644
--- a/app/views/dashboard/_projects.html.haml
+++ b/app/views/dashboard/_projects.html.haml
@@ -7,7 +7,4 @@
           = link_to new_project_path, class: 'btn btn-success' do
             New project
 
-  %ul.projects-list.bordered-list.my-projects
-    - @projects.each do |project|
-      %li.project-row
-        = render partial: 'shared/project', locals: { project: project, avatar: true, stars: true }
+  = render 'shared/projects/list', projects: @projects
diff --git a/app/views/dashboard/projects/starred.html.haml b/app/views/dashboard/projects/starred.html.haml
index 6dcfd497ed2..19f3975e530 100644
--- a/app/views/dashboard/projects/starred.html.haml
+++ b/app/views/dashboard/projects/starred.html.haml
@@ -17,8 +17,7 @@
                 = link_to new_project_path, class: 'btn btn-success' do
                   New project
 
-        = render 'shared/projects_list', projects: @projects,
-          projects_limit: 20, stars: true, avatar: false
+        = render 'shared/projects/list', projects: @projects, projects_limit: 20
 
 - else
   %h3 You don't have starred projects yet
diff --git a/app/views/explore/projects/_projects.html.haml b/app/views/explore/projects/_projects.html.haml
index 22cc541115c..669079e9521 100644
--- a/app/views/explore/projects/_projects.html.haml
+++ b/app/views/explore/projects/_projects.html.haml
@@ -1,6 +1,6 @@
-%ul.projects-list.bordered-list.my-projects.public-projects
-  - projects.each do |project|
-    %li.project-row
-      = render partial: 'shared/project', locals: { project: project, avatar: true, stars: true }
-- unless projects.present?
-  .nothing-here-block No such projects
+- if projects.any?
+  .public-projects
+    = render 'shared/projects/list', projects: projects
+- else
+  .nothing-here-block
+    No such projects
diff --git a/app/views/groups/_projects.html.haml b/app/views/groups/_projects.html.haml
index 2ae51a1c8c0..b2e32ced5e0 100644
--- a/app/views/groups/_projects.html.haml
+++ b/app/views/groups/_projects.html.haml
@@ -7,4 +7,4 @@
           = link_to new_project_path(namespace_id: @group.id), class: 'btn btn-success' do
             New project
 
-  = render 'shared/projects_list', projects: @projects, projects_limit: 20
+  = render 'shared/projects/list', projects: @projects, projects_limit: 20
diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml
index 741c780ad96..0dc1dd53628 100644
--- a/app/views/search/_results.html.haml
+++ b/app/views/search/_results.html.haml
@@ -14,7 +14,11 @@
   %br
   .results.prepend-top-10
     .search-results
-      = render partial: "search/results/#{@scope.singularize}", collection: @objects
+      - if @scope == 'projects'
+        .term
+          = render 'shared/projects/list', projects: @objects
+      - else
+        = render partial: "search/results/#{@scope.singularize}", collection: @objects
       = paginate @objects, theme: 'gitlab'
 
 :javascript
diff --git a/app/views/search/results/_project.html.haml b/app/views/search/results/_project.html.haml
deleted file mode 100644
index 195cf06c8ea..00000000000
--- a/app/views/search/results/_project.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-.search-result-row
-  %h4
-    = link_to [project.namespace.becomes(Namespace), project] do
-      %span.term= project.name_with_namespace
-  - if project.description.present?
-    %span.light.term= project.description
diff --git a/app/views/shared/_project.html.haml b/app/views/shared/_project.html.haml
deleted file mode 100644
index 15df97b1333..00000000000
--- a/app/views/shared/_project.html.haml
+++ /dev/null
@@ -1,20 +0,0 @@
-= cache [project.namespace, project, controller.controller_name, controller.action_name] do
-  = link_to project_path(project), class: dom_class(project) do
-    - if avatar
-      .dash-project-avatar
-        = project_icon(project, alt: '', class: 'avatar project-avatar s40')
-    %span.str-truncated.project-full-name
-      %span.namespace-name
-        - if project.namespace
-          = project.namespace.human_name
-          \/
-      %span.project-name.filter-title
-        = project.name
-    - if stars
-      %span.pull-right.light
-        %i.fa.fa-star
-        = project.star_count
-  - if project.description.present?
-    .project-description
-      .str-truncated
-        = markdown(project.description, pipeline: :description)
diff --git a/app/views/shared/_projects_list.html.haml b/app/views/shared/_projects_list.html.haml
deleted file mode 100644
index 4c58092af44..00000000000
--- a/app/views/shared/_projects_list.html.haml
+++ /dev/null
@@ -1,17 +0,0 @@
-- projects_limit = 20 unless local_assigns[:projects_limit]
-- avatar = true unless local_assigns[:avatar] == false
-- stars = false unless local_assigns[:stars] == true
-%ul.well-list.projects-list
-  - projects.each_with_index do |project, i|
-    %li{class: (i >= projects_limit) ? 'project-row hide' : 'project-row'}
-      = render "shared/project", project: project, avatar: avatar, stars: stars
-  - if projects.blank?
-    %li
-      .nothing-here-block There are no projects here.
-  - if projects.count > projects_limit
-    %li.bottom
-      %span.light
-        #{projects_limit} of #{pluralize(projects.count, 'project')} displayed.
-      %span
-        = link_to '#', class: 'js-expand' do
-          Show all
diff --git a/app/views/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml
new file mode 100644
index 00000000000..021e3b689a1
--- /dev/null
+++ b/app/views/shared/projects/_list.html.haml
@@ -0,0 +1,19 @@
+- projects_limit = 20 unless local_assigns[:projects_limit]
+- avatar = true unless local_assigns[:avatar] == false
+- stars = true unless local_assigns[:stars] == false
+
+%ul.projects-list
+  - projects.each_with_index do |project, i|
+    - css_class = (i >= projects_limit) ? 'hide' : nil
+    = render "shared/projects/project", project: project,
+      avatar: avatar, stars: stars, css_class: css_class
+
+  - if projects.count > projects_limit
+    %li.bottom.center
+      .light
+        #{projects_limit} of #{pluralize(projects.count, 'project')} displayed.
+        = link_to '#', class: 'js-expand' do
+          Show all
+
+:coffeescript
+  new ProjectsList()
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
new file mode 100644
index 00000000000..4bfdf4d55ff
--- /dev/null
+++ b/app/views/shared/projects/_project.html.haml
@@ -0,0 +1,23 @@
+- avatar = true unless local_assigns[:avatar] == false
+- stars = true unless local_assigns[:stars] == false
+- css_class = nil unless local_assigns[:css_class]
+%li.project-row{ class: css_class }
+  = cache [project.namespace, project, controller.controller_name, controller.action_name, 'v2'] do
+    = link_to project_path(project), class: dom_class(project) do
+      - if avatar
+        .dash-project-avatar
+          = project_icon(project, alt: '', class: 'avatar project-avatar s40')
+      %span.project-full-name
+        %span.namespace-name
+          - if project.namespace
+            = project.namespace.human_name
+            \/
+        %span.project-name.filter-title
+          = project.name
+      - if stars
+        %span.pull-right.light
+          %i.fa.fa-star
+          = project.star_count
+    - if project.description.present?
+      .project-description
+        = markdown(project.description, pipeline: :description)
diff --git a/app/views/users/_projects.html.haml b/app/views/users/_projects.html.haml
index 297fa537394..a126a858ea8 100644
--- a/app/views/users/_projects.html.haml
+++ b/app/views/users/_projects.html.haml
@@ -1,13 +1,13 @@
 - if local_assigns.has_key?(:contributed_projects) && contributed_projects.present?
   .panel.panel-default.contributed-projects
     .panel-heading Projects contributed to
-    = render 'shared/projects_list',
+    = render 'shared/projects/list',
       projects: contributed_projects.sort_by(&:star_count).reverse,
       projects_limit: 5, stars: true, avatar: false
 
 - if local_assigns.has_key?(:projects) && projects.present?
   .panel.panel-default
     .panel-heading Personal projects
-    = render 'shared/projects_list',
+    = render 'shared/projects/list',
       projects: projects.sort_by(&:star_count).reverse,
       projects_limit: 10, stars: true, avatar: false
-- 
cgit v1.2.1


From 174e4424a5eb6828705ecf5ed25979a5120dd735 Mon Sep 17 00:00:00 2001
From: Dmitriy Zaporozhets 
Date: Wed, 26 Aug 2015 22:52:52 +0200
Subject: Remove unused css

Signed-off-by: Dmitriy Zaporozhets 
---
 app/assets/stylesheets/pages/projects.scss | 72 ------------------------------
 1 file changed, 72 deletions(-)

diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 8339c6f5e6b..488dded549e 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -162,78 +162,6 @@ ul.nav.nav-projects-tabs {
   margin: 0px;
 }
 
-.my-projects,
-.public-projects {
-  li {
-    .project-info {
-      margin-bottom: 10px;
-      overflow: hidden;
-    }
-
-    .access-icon {
-      color: #AAA;
-      margin-left: 10px;
-      i {
-        color: #AAA;
-      }
-    }
-  }
-}
-
-.public-clone {
-  background: #EEE;
-  color: #777;
-  padding: 6px 10px;
-  margin: 1px;
-  font-weight: normal;
-}
-
-.public-projects .repo-info {
-  color: #777;
-
-  a {
-    color: #777;
-  }
-}
-
-.project-side {
-  .project-fork-icon {
-    float: left;
-    font-size: 26px;
-    margin-right: 10px;
-    line-height: 1.5;
-  }
-
-  .panel {
-    @include border-radius(3px);
-
-    .panel-heading, .panel-footer {
-      font-weight: normal;
-      background-color: transparent;
-      color: #666;
-      border-color: #EEE;
-    }
-
-    .actions {
-      margin-top: 10px;
-    }
-
-    .nav-pills a {
-      padding: 10px;
-      font-weight: bold;
-      color: $gl-link-color;
-    }
-
-    .nav {
-      margin-bottom: 15px;
-    }
-  }
-
-  .ci-status-image {
-    max-height: 22px;
-  }
-}
-
 .transfer-project .select2-container {
   min-width: 200px;
 }
-- 
cgit v1.2.1


From e0b4222fa67f25c330d16b660010b604383ba7fa Mon Sep 17 00:00:00 2001
From: Dmitriy Zaporozhets 
Date: Wed, 26 Aug 2015 23:12:24 +0200
Subject: Small improvements to center top menu

Signed-off-by: Dmitriy Zaporozhets 
---
 app/assets/stylesheets/generic/common.scss | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/app/assets/stylesheets/generic/common.scss b/app/assets/stylesheets/generic/common.scss
index bf5c7a8d75e..c63fe404aa2 100644
--- a/app/assets/stylesheets/generic/common.scss
+++ b/app/assets/stylesheets/generic/common.scss
@@ -375,7 +375,6 @@ table {
 }
 
 .center-top-menu {
-  border-bottom: 1px solid #EEE;
   list-style: none;
   text-align: center;
   padding-bottom: 15px;
@@ -385,7 +384,7 @@ table {
     display: inline-block;
 
     a {
-      padding: 10px;
+      padding: 15px;
     }
 
     &.active a {
-- 
cgit v1.2.1


From 5ea5e8f5d6fa6721af10839b7c81a87fc49c7843 Mon Sep 17 00:00:00 2001
From: Dmitriy Zaporozhets 
Date: Wed, 26 Aug 2015 23:41:56 +0200
Subject: Standartize how we render group rows

Signed-off-by: Dmitriy Zaporozhets 
---
 app/assets/stylesheets/generic/common.scss |  1 +
 app/views/dashboard/groups/index.html.haml | 31 ++++--------------------------
 app/views/explore/groups/index.html.haml   | 12 +-----------
 app/views/shared/groups/_group.html.haml   | 24 +++++++++++++++++++++++
 4 files changed, 30 insertions(+), 38 deletions(-)
 create mode 100644 app/views/shared/groups/_group.html.haml

diff --git a/app/assets/stylesheets/generic/common.scss b/app/assets/stylesheets/generic/common.scss
index c63fe404aa2..2674fde41ae 100644
--- a/app/assets/stylesheets/generic/common.scss
+++ b/app/assets/stylesheets/generic/common.scss
@@ -377,6 +377,7 @@ table {
 .center-top-menu {
   list-style: none;
   text-align: center;
+  margin-top: 5px;
   padding-bottom: 15px;
   margin-bottom: 15px;
 
diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml
index 0860fe3c761..fbe523b4b66 100644
--- a/app/views/dashboard/groups/index.html.haml
+++ b/app/views/dashboard/groups/index.html.haml
@@ -8,32 +8,9 @@
       = link_to new_group_path, class: "btn btn-new btn-sm" do
         %i.fa.fa-plus
         New Group
-.panel.panel-default
-  .panel-heading
-    %strong Groups
-    (#{@group_members.count})
-  %ul.well-list
-    - @group_members.each do |group_member|
-      - group = group_member.group
-      %li
-        .pull-right.hidden-xs
-          - if can?(current_user, :admin_group, group)
-            = link_to edit_group_path(group), class: "btn-sm btn btn-grouped" do
-              %i.fa.fa-cogs
-              Settings
-
-          = link_to leave_group_group_members_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-sm btn btn-grouped", title: 'Leave this group' do
-            %i.fa.fa-sign-out
-            Leave
-
-        = image_tag group_icon(group), class: "avatar s40 avatar-tile hidden-xs"
-        = link_to group, class: 'group-name' do
-          %strong= group.name
-
-        as
-        %strong #{group_member.human_access}
-
-        %div.light
-          #{pluralize(group.projects.count, "project")}, #{pluralize(group.users.count, "user")}
+%ul.bordered-list
+  - @group_members.each do |group_member|
+    - group = group_member.group
+    = render 'shared/groups/group', group: group, group_member: group_member
 
 = paginate @group_members
diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml
index 7dcefd330a1..80acb914365 100644
--- a/app/views/explore/groups/index.html.haml
+++ b/app/views/explore/groups/index.html.haml
@@ -32,17 +32,7 @@
 
 %ul.bordered-list
   - @groups.each do |group|
-    %li
-      .clearfix
-        %h4
-          = link_to group_path(id: group.path) do
-            = group.name
-      .clearfix
-        %p
-          = truncate group.description, length: 150
-      .clearfix
-        %p.light
-          #{pluralize(group.members.size, 'member')}, #{pluralize(group.projects.count, 'project')}
+    = render 'shared/groups/group', group: group
   - unless @groups.present?
     .nothing-here-block No public groups
 
diff --git a/app/views/shared/groups/_group.html.haml b/app/views/shared/groups/_group.html.haml
new file mode 100644
index 00000000000..229ae359bc5
--- /dev/null
+++ b/app/views/shared/groups/_group.html.haml
@@ -0,0 +1,24 @@
+- group_member = local_assigns[:group_member]
+%li
+  - if group_member
+    .pull-right.hidden-xs
+      - if can?(current_user, :admin_group, group)
+        = link_to edit_group_path(group), class: "btn-sm btn btn-grouped" do
+          %i.fa.fa-cogs
+          Settings
+
+      = link_to leave_group_group_members_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-sm btn btn-grouped", title: 'Leave this group' do
+        %i.fa.fa-sign-out
+        Leave
+
+  = image_tag group_icon(group), class: "avatar s40 avatar-tile hidden-xs"
+  = link_to group, class: 'group-name' do
+    %strong= group.name
+
+  - if group_member
+    as
+    %strong #{group_member.human_access}
+
+  %div.light
+    #{pluralize(group.projects.count, "project")}, #{pluralize(group.users.count, "user")}
+
-- 
cgit v1.2.1


From daa90e1182c1991b088f4feed1a633c35bdde0f9 Mon Sep 17 00:00:00 2001
From: Dmitriy Zaporozhets 
Date: Wed, 26 Aug 2015 23:59:52 +0200
Subject: Fix 500 error when try to create project snippet without content

Signed-off-by: Dmitriy Zaporozhets 
---
 app/controllers/projects/snippets_controller.rb | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
index 64306637423..b07a2a8db2f 100644
--- a/app/controllers/projects/snippets_controller.rb
+++ b/app/controllers/projects/snippets_controller.rb
@@ -30,9 +30,14 @@ class Projects::SnippetsController < Projects::ApplicationController
   def create
     @snippet = CreateSnippetService.new(@project, current_user,
                                         snippet_params).execute
-    respond_with(@snippet,
-                 location: namespace_project_snippet_path(@project.namespace,
-                                                          @project, @snippet))
+
+    if @snippet.valid?
+      respond_with(@snippet,
+                   location: namespace_project_snippet_path(@project.namespace,
+                                                            @project, @snippet))
+    else
+      render :new
+    end
   end
 
   def edit
-- 
cgit v1.2.1


From 4ac7f5c949c664523e9a6e474d522f7d093249a2 Mon Sep 17 00:00:00 2001
From: Dmitriy Zaporozhets 
Date: Thu, 27 Aug 2015 00:00:06 +0200
Subject: Restyle snippets rendering

Signed-off-by: Dmitriy Zaporozhets 
---
 app/assets/stylesheets/pages/snippets.scss     | 24 ++++++++++++++++++++++++
 app/views/projects/snippets/_snippet.html.haml | 15 ---------------
 app/views/projects/snippets/index.html.haml    |  3 +--
 app/views/shared/snippets/_snippet.html.haml   | 21 +++++++++++++++++++++
 app/views/snippets/_snippet.html.haml          | 23 -----------------------
 app/views/snippets/_snippets.html.haml         |  2 +-
 6 files changed, 47 insertions(+), 41 deletions(-)
 delete mode 100644 app/views/projects/snippets/_snippet.html.haml
 create mode 100644 app/views/shared/snippets/_snippet.html.haml
 delete mode 100644 app/views/snippets/_snippet.html.haml

diff --git a/app/assets/stylesheets/pages/snippets.scss b/app/assets/stylesheets/pages/snippets.scss
index d79591d9915..a3d7aba054d 100644
--- a/app/assets/stylesheets/pages/snippets.scss
+++ b/app/assets/stylesheets/pages/snippets.scss
@@ -6,3 +6,27 @@
 .snippet-form-holder .file-holder .file-title {
   padding: 2px;
 }
+
+
+.snippet-row {
+  .snippet-title {
+    font-size: 15px;
+    font-weight: bold;
+    line-height: 20px;
+    margin-bottom: 2px;
+
+    .monospace {
+      font-weight: normal;
+    }
+  }
+
+  .snippet-info {
+    color: #888;
+    font-size: 13px;
+    line-height: 24px;
+
+    a {
+      color: #888;
+    }
+  }
+}
diff --git a/app/views/projects/snippets/_snippet.html.haml b/app/views/projects/snippets/_snippet.html.haml
deleted file mode 100644
index b2c35edc44c..00000000000
--- a/app/views/projects/snippets/_snippet.html.haml
+++ /dev/null
@@ -1,15 +0,0 @@
-%li
-  %h4.snippet-title
-    = link_to reliable_snippet_path(snippet) do
-      = truncate(snippet.title, length: 60)
-    %span.cgray.monospace.tiny.pull-right
-      = snippet.file_name
-
-  .snippet-info
-    = "##{snippet.id}"
-    %span
-      by
-      = image_tag avatar_icon(snippet.author_email), class: "avatar avatar-inline s16"
-      = snippet.author_name
-      %span.light
-        #{time_ago_with_tooltip(snippet.created_at)}
diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml
index 30081673ffc..45d4de6a385 100644
--- a/app/views/projects/snippets/index.html.haml
+++ b/app/views/projects/snippets/index.html.haml
@@ -8,9 +8,8 @@
 %p.light
   Share code pastes with others out of git repository
 
-%hr
 %ul.bordered-list
-  = render partial: "projects/snippets/snippet", collection: @snippets
+  = render partial: "shared/snippets/snippet", collection: @snippets
   - if @snippets.empty?
     %li
       .nothing-here-block Nothing here.
diff --git a/app/views/shared/snippets/_snippet.html.haml b/app/views/shared/snippets/_snippet.html.haml
new file mode 100644
index 00000000000..69a713ad9aa
--- /dev/null
+++ b/app/views/shared/snippets/_snippet.html.haml
@@ -0,0 +1,21 @@
+%li.snippet-row
+  .snippet-title
+    = link_to reliable_snippet_path(snippet) do
+      = truncate(snippet.title, length: 60)
+      - if snippet.private?
+        %span.label.label-gray
+          %i.fa.fa-lock
+          private
+    %span.monospace.pull-right
+      = snippet.file_name
+
+  %small.pull-right.cgray
+    - if snippet.project_id?
+      = link_to snippet.project.name_with_namespace, namespace_project_path(snippet.project.namespace, snippet.project)
+
+  .snippet-info
+    = link_to user_snippets_path(snippet.author) do
+      = image_tag avatar_icon(snippet.author_email), class: "avatar s24", alt: ''
+      = snippet.author_name
+    authored #{time_ago_with_tooltip(snippet.created_at)}
+
diff --git a/app/views/snippets/_snippet.html.haml b/app/views/snippets/_snippet.html.haml
deleted file mode 100644
index 5bb28664349..00000000000
--- a/app/views/snippets/_snippet.html.haml
+++ /dev/null
@@ -1,23 +0,0 @@
-%li
-  %h4.snippet-title
-    = link_to reliable_snippet_path(snippet) do
-      = truncate(snippet.title, length: 60)
-      - if snippet.private?
-        %span.label.label-gray
-          %i.fa.fa-lock
-          private
-    %span.cgray.monospace.tiny.pull-right
-      = snippet.file_name
-
-  %small.pull-right.cgray
-    - if snippet.project_id?
-      = link_to snippet.project.name_with_namespace, namespace_project_path(snippet.project.namespace, snippet.project)
-
-  .snippet-info
-    = "##{snippet.id}"
-    %span
-      by
-      = link_to user_snippets_path(snippet.author) do
-        = image_tag avatar_icon(snippet.author_email), class: "avatar avatar-inline s16", alt: ''
-        = snippet.author_name
-      %span.light #{time_ago_with_tooltip(snippet.created_at)}
diff --git a/app/views/snippets/_snippets.html.haml b/app/views/snippets/_snippets.html.haml
index 40df42b6cf5..d9aa4dd1d2e 100644
--- a/app/views/snippets/_snippets.html.haml
+++ b/app/views/snippets/_snippets.html.haml
@@ -1,5 +1,5 @@
 %ul.bordered-list
-  = render partial: 'snippet', collection: @snippets
+  = render partial: 'shared/snippets/snippet', collection: @snippets
   - if @snippets.empty?
     %li
       .nothing-here-block Nothing here.
-- 
cgit v1.2.1


From 3d02ead9246c7112d5a32ee87da774e47927c84e Mon Sep 17 00:00:00 2001
From: Dmitriy Zaporozhets 
Date: Thu, 27 Aug 2015 00:06:48 +0200
Subject: Use same snippets icon everywhere. Fix list style for bordered-list

Signed-off-by: Dmitriy Zaporozhets 
---
 app/assets/stylesheets/base/mixins.scss  | 1 +
 app/views/layouts/nav/_project.html.haml | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/app/assets/stylesheets/base/mixins.scss b/app/assets/stylesheets/base/mixins.scss
index 3974befa95c..05f5bd79f91 100644
--- a/app/assets/stylesheets/base/mixins.scss
+++ b/app/assets/stylesheets/base/mixins.scss
@@ -164,6 +164,7 @@
 @mixin basic-list {
   margin: 5px 0px;
   padding: 0px;
+  list-style: none;
 
   li {
     padding: 10px 0;
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index d17d1c5fbd4..5e7b902622b 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -100,7 +100,7 @@
   - if project_nav_tab? :snippets
     = nav_link(controller: :snippets) do
       = link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets', data: {placement: 'right'} do
-        = icon('file-text-o fw')
+        = icon('clipboard fw')
         %span
           Snippets
 
-- 
cgit v1.2.1


From dcdf214fd15244fd41cc22cfc068c0b6ee421069 Mon Sep 17 00:00:00 2001
From: Dmitriy Zaporozhets 
Date: Thu, 27 Aug 2015 00:22:36 +0200
Subject: More compact search page

Signed-off-by: Dmitriy Zaporozhets 
---
 app/assets/stylesheets/pages/search.scss | 6 +++---
 app/views/search/_category.html.haml     | 2 +-
 app/views/search/show.html.haml          | 3 +--
 3 files changed, 5 insertions(+), 6 deletions(-)

diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index bdaa17ac339..eee0e342c31 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -1,7 +1,7 @@
 .search-results {
   .search-result-row {
-    border-bottom: 1px solid #EEE;
-    padding-bottom: 10px;
-    margin-bottom: 10px;
+    border-bottom: 1px solid #DDD;
+    padding-bottom: 15px;
+    margin-bottom: 15px;
   }
 }
diff --git a/app/views/search/_category.html.haml b/app/views/search/_category.html.haml
index a75cd7bd809..d637abfa76b 100644
--- a/app/views/search/_category.html.haml
+++ b/app/views/search/_category.html.haml
@@ -1,4 +1,4 @@
-%ul.nav.nav-pills.search-filter
+%ul.nav.nav-tabs.search-filter
   - if @project
     %li{class: ("active" if @scope == 'blobs')}
       = link_to search_filter_path(scope: 'blobs') do
diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml
index 60f9e9ac9de..35b23ceb9d7 100644
--- a/app/views/search/show.html.haml
+++ b/app/views/search/show.html.haml
@@ -1,7 +1,6 @@
 - page_title @search_term
 = render 'search/form'
-%hr
+%br
 - if @search_term
   = render 'search/category'
-  %hr
   = render 'search/results'
-- 
cgit v1.2.1


From e06fe6b7e1d02784992cb9c413f65504ad63ce14 Mon Sep 17 00:00:00 2001
From: Dmitriy Zaporozhets 
Date: Thu, 27 Aug 2015 00:50:17 +0200
Subject: Make search bar on search page more noticeable

Signed-off-by: Dmitriy Zaporozhets 
---
 app/assets/stylesheets/pages/search.scss | 12 ++++++++++++
 app/views/search/_filter.html.haml       |  6 ++----
 app/views/search/_form.html.haml         | 12 +++++++-----
 app/views/search/_results.html.haml      |  3 +--
 app/views/search/show.html.haml          |  1 -
 5 files changed, 22 insertions(+), 12 deletions(-)

diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index eee0e342c31..3aaa96da609 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -5,3 +5,15 @@
     margin-bottom: 15px;
   }
 }
+
+.search-holder {
+  max-width: 600px;
+  margin: 0 auto;
+  margin-bottom: 20px;
+
+  input {
+    border-color: #BBB;
+    font-weight: bold;
+  }
+}
+
diff --git a/app/views/search/_filter.html.haml b/app/views/search/_filter.html.haml
index e2d0cab9e79..ec478a5963d 100644
--- a/app/views/search/_filter.html.haml
+++ b/app/views/search/_filter.html.haml
@@ -1,6 +1,5 @@
 .dropdown.inline
-  %button.dropdown-toggle.btn.btn{type: 'button', 'data-toggle' => 'dropdown'}
-    %i.fa.fa-tags
+  %button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'}
     %span.light Group:
     - if @group.present?
       %strong= @group.name
@@ -17,8 +16,7 @@
           = group.name
 
 .dropdown.inline.prepend-left-10.project-filter
-  %button.dropdown-toggle.btn.btn{type: 'button', 'data-toggle' => 'dropdown'}
-    %i.fa.fa-tags
+  %button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'}
     %span.light Project:
     - if @project.present?
       %strong= @project.name_with_namespace
diff --git a/app/views/search/_form.html.haml b/app/views/search/_form.html.haml
index 5ee70be1ad6..3938c545cad 100644
--- a/app/views/search/_form.html.haml
+++ b/app/views/search/_form.html.haml
@@ -1,12 +1,14 @@
-= form_tag search_path, method: :get, class: 'form-inline' do |f|
+= form_tag search_path, method: :get do |f|
   = hidden_field_tag :project_id, params[:project_id]
   = hidden_field_tag :group_id, params[:group_id]
   = hidden_field_tag :snippets, params[:snippets]
   = hidden_field_tag :scope, params[:scope]
+
   .search-holder.clearfix
-    .form-group
+    .input-group
       = search_field_tag :search, params[:search], placeholder: "Search for projects, issues etc", class: "form-control search-text-input", id: "dashboard_search", autofocus: true
-      = button_tag 'Search', class: "btn btn-primary"
+      %span.input-group-btn
+        = button_tag 'Search', class: "btn btn-primary"
     - unless params[:snippets].eql? 'true'
-      .pull-right
-        = render 'filter'
+      %br
+      = render 'filter'
diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml
index 0dc1dd53628..2a38c98dcfc 100644
--- a/app/views/search/_results.html.haml
+++ b/app/views/search/_results.html.haml
@@ -1,7 +1,7 @@
 - if @search_results.empty?
   = render partial: "search/results/empty"
 - else
-  .light
+  %p.light
     Search results for
     %code
       = @search_term
@@ -11,7 +11,6 @@
       - elsif @group
         in group #{link_to @group.name, @group}
 
-  %br
   .results.prepend-top-10
     .search-results
       - if @scope == 'projects'
diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml
index 35b23ceb9d7..f4f3dcfc29f 100644
--- a/app/views/search/show.html.haml
+++ b/app/views/search/show.html.haml
@@ -1,6 +1,5 @@
 - page_title @search_term
 = render 'search/form'
-%br
 - if @search_term
   = render 'search/category'
   = render 'search/results'
-- 
cgit v1.2.1


From 6a889572fa8c149729e12e9386c1982e660a766e Mon Sep 17 00:00:00 2001
From: Dmitriy Zaporozhets 
Date: Thu, 27 Aug 2015 00:53:00 +0200
Subject: Add changelog items

Signed-off-by: Dmitriy Zaporozhets 
---
 CHANGELOG | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/CHANGELOG b/CHANGELOG
index 03b12ff1331..7603bda6d36 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -20,6 +20,9 @@ v 8.0.0 (unreleased)
   - Move dashboard activity to separate page
   - Improve performance of git blame
   - Limit content width to 1200px for most of pages to improve readability on big screens
+  - Fix 500 error when submit project snippet without body
+  - Improve search page usability
+  - Bring more UI consistency in way how projects, snippets and groups lists are rendered
 
 v 7.14.1 (unreleased)
   - Only include base URL in OmniAuth full_host parameter (Stan Hu)
-- 
cgit v1.2.1


From 397ebc5fc1fd424fc745535793fadbff880ce547 Mon Sep 17 00:00:00 2001
From: Sytse Sijbrandij 
Date: Wed, 26 Aug 2015 19:29:37 -0700
Subject: Doesn't refer to participating in watch.

---
 app/views/profiles/notifications/show.html.haml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index 9480a19f5b2..db7fa2eabe3 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -48,7 +48,7 @@
           = f.radio_button :notification_level, Notification::N_WATCH
           .level-title
             Watch
-          %p You will receive all notifications from projects in which you participate
+          %p You will receive notifications for any activity
 
   .form-actions
     = f.submit 'Save changes', class: "btn btn-create"
-- 
cgit v1.2.1


From ff30b407545b2b873ffbe9d5d3d603e550013cf3 Mon Sep 17 00:00:00 2001
From: Valery Sizov 
Date: Thu, 27 Aug 2015 11:21:57 +0300
Subject: Make all profile public

---
 CHANGELOG                           | 1 +
 app/controllers/users_controller.rb | 4 ----
 app/models/user.rb                  | 4 ----
 app/views/profiles/show.html.haml   | 5 -----
 features/user.feature               | 5 -----
 5 files changed, 1 insertion(+), 18 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index 6ed1716f8af..d366e2a6f57 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -19,6 +19,7 @@ v 8.0.0 (unreleased)
   - Move dashboard activity to separate page
   - Improve performance of git blame
   - Limit content width to 1200px for most of pages to improve readability on big screens
+  - Make all profiles public
 
 v 7.14.1
   - Improve abuse reports management from admin area
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 2bb5c338cf6..1484356a7f4 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -51,10 +51,6 @@ class UsersController < ApplicationController
 
   def set_user
     @user = User.find_by_username!(params[:username])
-
-    unless current_user || @user.public_profile?
-      return authenticate_user!
-    end
   end
 
   def authorized_projects_ids
diff --git a/app/models/user.rb b/app/models/user.rb
index f70761074c5..b4e779370a0 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -637,10 +637,6 @@ class User < ActiveRecord::Base
     email.start_with?('temp-email-for-oauth')
   end
 
-  def public_profile?
-    authorized_projects.public_only.any?
-  end
-
   def avatar_url(size = nil)
     if avatar.present?
       [gitlab_config.url, avatar.url].join
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index 9fdeddfcc7a..c519e52e596 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -100,11 +100,6 @@
               %hr
               = link_to 'Remove avatar', profile_avatar_path, data: { confirm: "Avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar"
 
-      - if @user.public_profile?
-        .alert.alert-info
-          %h4 Public profile
-          %p Your profile is publicly visible because you joined public project(s)
-
 
   .row
     .col-md-7
diff --git a/features/user.feature b/features/user.feature
index 69618e929c4..35eae842e77 100644
--- a/features/user.feature
+++ b/features/user.feature
@@ -14,11 +14,6 @@ Feature: User
     And I should not see project "Internal"
     And I should see project "Community"
 
-  Scenario: I visit user "John Doe" page while not signed in when he is not authorized to a public project
-    Given "John Doe" owns internal project "Internal"
-    When I visit user "John Doe" page
-    Then I should be redirected to sign in page
-
   # Signed in as someone else
 
   Scenario: I visit user "John Doe" page while signed in as someone else when he owns a public project
-- 
cgit v1.2.1


From 1081a322f1b846fcddb3cb77f068e2e818f9d73f Mon Sep 17 00:00:00 2001
From: Jacob Vosmaer 
Date: Thu, 27 Aug 2015 16:35:00 +0200
Subject: Properly daemonize the mail_room process

The old invocation only worked by accident because we have a '&'
somewhere in the init script for expediency. When ran from a terminal,
the mail_room daemon process ended up in the session of the terminal.
This commit adds a small wrapper that tries to do the textbook
daemonization steps (double fork, setsid etc.) while also taking
care that the pidfile is written before the 'start' process exits.
---
 bin/daemon_with_pidfile | 33 +++++++++++++++++++++++++++++++++
 bin/mail_room           |  4 +---
 2 files changed, 34 insertions(+), 3 deletions(-)
 create mode 100755 bin/daemon_with_pidfile

diff --git a/bin/daemon_with_pidfile b/bin/daemon_with_pidfile
new file mode 100755
index 00000000000..f138c27a0e2
--- /dev/null
+++ b/bin/daemon_with_pidfile
@@ -0,0 +1,33 @@
+#!/usr/bin/env ruby
+# daemon_with_pidfile
+#
+# Daemonize, write a pidfile, and exec the remainder of the command line.
+
+def main(pidfile, cmd)
+  if middle_pid = Process.fork
+    # outer process
+    # Do not exit the outer process before the middle process finishes
+    Process.waitpid(middle_pid)
+    exit $?.exitstatus
+  end
+
+  if final_pid = Process.fork
+    # middle process
+    open(pidfile, 'w') { |f| f.puts final_pid }
+    exit
+  end
+
+  # Standard daemon things: become session leader, ignore SIGHUP, close stdin.
+  Signal.trap("HUP", "IGNORE")
+  Process.setsid
+  IO.new(0).close
+
+  exec(*cmd)
+end
+
+if ARGV.count < 2
+  abort "Usage: #$0 pidfile command [args...]"
+end
+
+pidfile = ARGV.shift
+main(pidfile, ARGV)
diff --git a/bin/mail_room b/bin/mail_room
index f4f1a170c04..74a84f5b2b4 100755
--- a/bin/mail_room
+++ b/bin/mail_room
@@ -19,9 +19,7 @@ get_mail_room_pid()
 
 start()
 {
-  bundle exec mail_room -q -c $mail_room_config >> $mail_room_logfile 2>&1 &
-  PID=$!
-  echo $PID > $mail_room_pidfile
+  bin/daemon_with_pidfile $mail_room_pidfile bundle exec mail_room -q -c $mail_room_config >> $mail_room_logfile 2>&1
 }
 
 stop()
-- 
cgit v1.2.1


From ef6c06c0f3341d9508693930af4979659d6a4b63 Mon Sep 17 00:00:00 2001
From: Stan Hu 
Date: Thu, 27 Aug 2015 13:07:14 -0700
Subject: Upgrade gitlab_git to 7.2.15 to fix `git blame` errors with
 ISO-encoded files

Closes https://github.com/gitlabhq/gitlabhq/issues/9577
---
 CHANGELOG    | 1 +
 Gemfile      | 2 +-
 Gemfile.lock | 4 ++--
 3 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index 386ce7ca6d4..638a6aac036 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 Please view this file on the master branch, on stable branches it's out of date.
 
 v 8.0.0 (unreleased)
+  - Upgrade gitlab_git to 7.2.15 to fix `git blame` errors with ISO-encoded files (Stan Hu)
   - Prevent too many redirects upon login when home page URL is set to external_url (Stan Hu)
   - Improve dropdown positioning on the project home page (Hannes Rosenögger)
   - Upgrade browser gem to 1.0.0 to avoid warning in IE11 compatibilty mode (Stan Hu)
diff --git a/Gemfile b/Gemfile
index 4696a961e82..e1b9ede17ba 100644
--- a/Gemfile
+++ b/Gemfile
@@ -38,7 +38,7 @@ gem "browser", '~> 1.0.0'
 
 # Extracting information from a git repository
 # Provide access to Gitlab::Git library
-gem "gitlab_git", '~> 7.2.14'
+gem "gitlab_git", '~> 7.2.15'
 
 # Ruby/Rack Git Smart-HTTP Server Handler
 # GitLab fork with a lot of changes (improved thread-safety, better memory usage etc)
diff --git a/Gemfile.lock b/Gemfile.lock
index 814249c7ff6..ff01ad10145 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -276,7 +276,7 @@ GEM
       mime-types (~> 1.19)
     gitlab_emoji (0.1.0)
       gemojione (~> 2.0)
-    gitlab_git (7.2.14)
+    gitlab_git (7.2.15)
       activesupport (~> 4.0)
       charlock_holmes (~> 0.6)
       gitlab-linguist (~> 3.0)
@@ -790,7 +790,7 @@ DEPENDENCIES
   gitlab-grack (~> 2.0.2)
   gitlab-linguist (~> 3.0.1)
   gitlab_emoji (~> 0.1)
-  gitlab_git (~> 7.2.14)
+  gitlab_git (~> 7.2.15)
   gitlab_meta (= 7.0)
   gitlab_omniauth-ldap (= 1.2.1)
   gollum-lib (~> 4.0.2)
-- 
cgit v1.2.1


From 23aee0ca8ad3de5697f770696f3e55fbfdba2be8 Mon Sep 17 00:00:00 2001
From: Eric Maziade 
Date: Wed, 26 Aug 2015 22:28:24 -0400
Subject: fixed connection detection so settings can be read from the database

---
 lib/gitlab/current_settings.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index 1a2a50a14d0..7ad3ed8728f 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -4,7 +4,7 @@ module Gitlab
       key = :current_application_settings
 
       RequestStore.store[key] ||= begin
-        if ActiveRecord::Base.connected? && ActiveRecord::Base.connection.table_exists?('application_settings')
+        if ActiveRecord::Base.connection.active? && ActiveRecord::Base.connection.table_exists?('application_settings')
           ApplicationSetting.current || ApplicationSetting.create_from_defaults
         else
           fake_application_settings
-- 
cgit v1.2.1