diff --git a/.gitignore b/.gitignore
index 3e30fb8cf77f62ecf6c9862c501a7566aabb71a7..8a68bb3e4f04795686eb11f188a22b2bbf303658 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/CHANGELOG b/CHANGELOG
index 53c7463ae76ff757f192bd1d2ee91c0e10bf4076..4946c683904c9f94ffdd6623f4ccb0d9c1b670b2 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,11 @@
 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)
+  - 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)
@@ -9,8 +14,26 @@ 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
-
-v 7.14.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
+  - 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
+  - Make all profiles public
+
+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)
+  - 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.
   - 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)
@@ -294,6 +317,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)
diff --git a/Gemfile b/Gemfile
index 8f65a274baabe018e3f46b8c0cdb23f9bbd941b1..e1b9ede17baae4138b771878df8762507ba0413b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -34,11 +34,11 @@ 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
-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)
@@ -272,3 +272,7 @@ end
 gem "newrelic_rpm"
 
 gem 'octokit', '3.7.0'
+
+gem "mail_room", "~> 0.4.1"
+
+gem 'email_reply_parser'
diff --git a/Gemfile.lock b/Gemfile.lock
index f0c661fa9c53dbddb8b6adff65e3847a81c83038..ff01ad10145adecd2f590aa1aa2f50007948b676 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)
@@ -156,6 +156,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)
@@ -275,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)
@@ -371,6 +372,7 @@ GEM
       systemu (~> 2.6.2)
     mail (2.6.3)
       mime-types (>= 1.16, < 3)
+    mail_room (0.4.1)
     method_source (0.8.2)
     mime-types (1.25.1)
     mimemagic (0.3.0)
@@ -753,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)
@@ -773,6 +775,7 @@ DEPENDENCIES
   diffy (~> 3.0.3)
   doorkeeper (= 2.1.3)
   dropzonejs-rails
+  email_reply_parser
   email_spec (~> 1.6.0)
   enumerize
   factory_girl_rails
@@ -787,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)
@@ -805,6 +808,7 @@ DEPENDENCIES
   jquery-ui-rails
   kaminari (~> 0.15.1)
   letter_opener
+  mail_room (~> 0.4.1)
   minitest (~> 5.3.0)
   mousetrap-rails
   mysql2
diff --git a/Procfile b/Procfile
index 799b92729fa95aa90cc480ef9a01d7e6ae3b00db..18fd9eb3d9269071377de1dd8103b8bc8d7d9601 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 -q -c config/mail_room.yml
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index bb0a0c51fd43588655ceed9dcaa0ce6f71ffa775..c263912b7ea5e454c58051d0da965d910dc43362 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/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index 81e737992714bc63866aa7c3d5a51a3218fae2de..5bf0b302179f4cda666bf23647a33b90c1450e86 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -51,10 +51,10 @@ class Dispatcher
         MergeRequests.init()
       when 'dashboard:show', 'root:show'
         new Dashboard()
+      when 'dashboard:activity'
         new Activities()
       when 'dashboard:projects:starred'
         new Activities()
-        new ProjectsList()
       when 'projects:commit:show'
         new Commit()
         new Diff()
@@ -69,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()
@@ -95,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/javascripts/projects_list.js.coffee b/app/assets/javascripts/projects_list.js.coffee
index c0e36d1ccc5ce47b9c8ee4b2944d36829c9373ee..db5faf71faf79461144bdd260d9bb3adbf380c0b 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/javascripts/syntax_highlight.coffee b/app/assets/javascripts/syntax_highlight.coffee
new file mode 100644
index 0000000000000000000000000000000000000000..510f15d1b49eb19e15ebee3492bd42c49795119a
--- /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
+#
+#   <div class="js-syntax-highlight"></div>
+#
+$(document).on 'ready page:load', ->
+  $('.js-syntax-highlight').addClass(gon.user_color_scheme)
diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee
index aeeed9ca3ccaa55299ba3023d28b5b05ec57e2ab..9157562a5c58854269743befbd4073205aa3f049 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/assets/stylesheets/base/layout.scss b/app/assets/stylesheets/base/layout.scss
index 690d89a5c16e335ec54c46bead71147640403c0c..734b95e26c0844b05b46cca67403835529c16ddf 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/mixins.scss b/app/assets/stylesheets/base/mixins.scss
index 7beef1845ef7ec41a70a1d42abf04a713bf55911..05f5bd79f91e96613f69188d9df8bf7a7961775b 100644
--- a/app/assets/stylesheets/base/mixins.scss
+++ b/app/assets/stylesheets/base/mixins.scss
@@ -157,3 +157,41 @@
   white-space: nowrap;
   max-width: $max_width;
 }
+
+/*
+ * Base mixin for lists in GitLab
+ */
+@mixin basic-list {
+  margin: 5px 0px;
+  padding: 0px;
+  list-style: none;
+
+  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/base/variables.scss b/app/assets/stylesheets/base/variables.scss
index cb439a0e0bf0ae34eeac5609940768d59a43d698..26d0a1e53638a0aa39c184831d7446d309a1f6bd 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/generic/common.scss b/app/assets/stylesheets/generic/common.scss
index bf5c7a8d75ebbeb7cd099dc5794e33906fb0a85f..e5902597c4d5b8e0b36f2c516101742332e2c7fa 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;
 }
@@ -375,9 +371,9 @@ table {
 }
 
 .center-top-menu {
-  border-bottom: 1px solid #EEE;
   list-style: none;
   text-align: center;
+  margin-top: 5px;
   padding-bottom: 15px;
   margin-bottom: 15px;
 
@@ -385,7 +381,7 @@ table {
     display: inline-block;
 
     a {
-      padding: 10px;
+      padding: 15px;
     }
 
     &.active a {
diff --git a/app/assets/stylesheets/generic/header.scss b/app/assets/stylesheets/generic/header.scss
index 31e2ad866912c5cd349f5f5a5e2fd0da468bb99a..6a29b32e1960af12d3183161a90affd1d2f2b05d 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/lists.scss b/app/assets/stylesheets/generic/lists.scss
index c502d953c7573c0fc9b20c78f9e98fbd9e5f270a..4b7ff84de2b31f9462272f59250324aa69a5b9c2 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/generic/sidebar.scss b/app/assets/stylesheets/generic/sidebar.scss
index b96664d30db6a967fa3de37fb34389c90e1d46ee..320bdb1c7657d148ec641c07033a16853a6530ab 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/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss
index c8cb18ec35ff9b866cd455e6add77c9c3ed14177..8323a8598ec7d546e363f7640e5d753f943a7df4 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 001e8b31020dda641c6e076f9a4a7d93b7c438cc..e83816743363941298c580cf677fb962cc4485a6 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 f5b827e7c02e591c014a75c2b291a8293c9d3b08..bd41480aefb4706d6f7dc5ced47e501ec15c1611 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 6b44c00c305e3cfa618c26454496106c67ed37df..4cc62863870e701867d3bd67cd212c4dd44c0854 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 a52ffc971d1a4c9f47b2a8b2afc3cd70175c2d98..e0edfb80b42f39df7ff1cb388fb61d43e6d0c0cb 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; }
diff --git a/app/assets/stylesheets/pages/dashboard.scss b/app/assets/stylesheets/pages/dashboard.scss
index 9a3b543ad105798b5380267c6a749ae83ee7e2bb..c1103a1c2e648813bbdaabe1de64a77df9f9e458 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/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 3f617e72b025cf148e2b6f202a2091db38961ed5..586e7b5f8dab8edf8aac4cb1ab054730f705f8e0 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/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 29d3dbc25eb2838ca9ae154cf3f7fbfd808e2289..488dded549edb8729c735eaf38fae2c9f0b541f2 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -30,14 +30,21 @@
     }
   }
 
+  .project-home-dropdown {
+    margin: 11px 3px 0;
+  }
+
   .project-home-desc {
     h1 {
       margin: 0;
       margin-bottom: 10px;
       font-size: 26px;
+      font-weight: bold;
     }
 
     p {
+      font-size: 18px;
+      color: #666;
       display: inline;
     }
   }
@@ -155,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;
 }
@@ -316,3 +251,43 @@ 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;
+  }
+}
+
+/*
+ * 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;
+    }
+
+    .project-description {
+      color: #888;
+      font-size: 13px;
+
+      p {
+        @include str-truncated;
+        margin-bottom: 0;
+        color: #888;
+      }
+    }
+  }
+}
+
+.panel .projects-list li {
+  padding: 10px 15px;
+}
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index bdaa17ac33926839777bcfdf84fe8d2c565bff1e..3aaa96da609172170cb7dc5907cf751e77d57cf1 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -1,7 +1,19 @@
 .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;
   }
 }
+
+.search-holder {
+  max-width: 600px;
+  margin: 0 auto;
+  margin-bottom: 20px;
+
+  input {
+    border-color: #BBB;
+    font-weight: bold;
+  }
+}
+
diff --git a/app/assets/stylesheets/pages/snippets.scss b/app/assets/stylesheets/pages/snippets.scss
index d79591d99156ffbfef51dec3772452777c6d53f8..a3d7aba054d21d654efe5152023b67910bb772d6 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/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index 5f1a3db4fb6857fc1e42f4faee6057d86362d893..81e2aa7bb9c0947bba7578c836afdf692e2a7d2d 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/assets/stylesheets/themes/gitlab-theme.scss b/app/assets/stylesheets/themes/gitlab-theme.scss
index 3589cb88d031b06dc2f948227ea7ae70cda200c7..77b62c3153fb2c2fdf45b1295e6a9484d2e91cc6 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/controllers/admin/abuse_reports_controller.rb b/app/controllers/admin/abuse_reports_controller.rb
index 34f37bca4adaee9d9a4f5fab0a3f59a4fab09020..38a5a9fca08e36f30c77ee9c9b9cfd858297a250 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/controllers/admin/hooks_controller.rb b/app/controllers/admin/hooks_controller.rb
index 690096bdbcf5ff05ae66d0ad4e61afcf61556313..d670386f8c62afdca3ef174da86df8dae1de4cba 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/application_controller.rb b/app/controllers/application_controller.rb
index 12d439b0b312b4775d13319a8aebcd4106b3241f..cb1cf13d34dfd398a9bf859be637fd8beac76864 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
@@ -190,11 +192,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/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb
index 5c3ca8e23c9c0a2cdb7fe8fe019b25d13f45bb94..904d26a39f43e84b6cfe455cf36abbddcd9b5ed1 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/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index d2f0c43929f9230a75b553cd05cddb655f31ec2c..d745131694b5175a43bb8234591ad6252671ff5d 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/controllers/import/bitbucket_controller.rb b/app/controllers/import/bitbucket_controller.rb
index 4e6c0b6663460d99b94220f41d5c549204f13d28..f84f85a7df842a522d7131b88cd269054cf78518 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 b9f99c1b88ad75f46f74772bd1dc7bcf0e0effb5..f21fbd9eccac0f02b41fbf40530d5c7364b68ff8 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 1b8962d89244b223b87a7de46e7e34e0a8f4d774..27af19f5f616f89f19580fd03f95f6bd49faa573 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/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb
index 76062446c92765be86eb2c2dc6846651505f097d..4e5b4125f5a9cbaa654066ad9e1ae745c7cfeb2e 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 0110553247959d85b490f4efb5705cbbe1c4514f..b0cf5866d41033b9367d10675cc9b91f83eaa350 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/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
index 643066374230cc5038b21410659073d0af473517..b07a2a8db2f7d121a5f8ad50a1c8d1433725d5f3 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
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index 4e2ea6c5710ff14cca8c75d7f5f764d7b3410cc3..eb0408a95e58c3b64349ebdcf8d2633b0303082f 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/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 2bb5c338cf60fc119b3083a8e4eb08d902a79a3c..1484356a7f49ec79472bc61ca17104afc4796a58 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/finders/trending_projects_finder.rb b/app/finders/trending_projects_finder.rb
index a79bd47d9867de5379cd985b16cf3a0683e1fb74..f3f4d461efa3680a19b6cc04bb894d339a3a92ad 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
diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb
index eb3f72a307d5bd0ace12af2dbceae43ef37a9bc3..114730eb9489aab6cba68b5f6e2fb1cc48694879 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/icons_helper.rb b/app/helpers/icons_helper.rb
index 30b17a736a74f64f6b128cf3a50e6c58ab870896..1cf5b96481a952ba4d35108bd4c5ac8a2b66f0a5 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')
diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb
index 01b6a63552c1c00123becdcc5a98c19ee9e7374b..8473d6d75d07bd59cb975d1201889aebd9c78f95 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/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb
index ea774e28ecf37a3325d74c11ce5c977686f98d9c..7f1b6a6992676d532bcc9b35b42d1c8234ba392f 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.for_user(current_user).css_class
   end
 
-  def user_color_scheme_class
-    COLOR_SCHEMES[current_user.try(:color_scheme_id)] if defined?(current_user)
+  def user_color_scheme
+    Gitlab::ColorSchemes.for_user(current_user).css_class
   end
 
   def prefer_readme?
diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb
index 2b99a398049e12bb06057fe1aa0b7b25d169c11e..12fce8db7013c34b70fc7626144caaee363c4ffc 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/mailers/base_mailer.rb b/app/mailers/base_mailer.rb
new file mode 100644
index 0000000000000000000000000000000000000000..aedb0889185cf27e2393d0fb6abb9e2d34bc5a03
--- /dev/null
+++ b/app/mailers/base_mailer.rb
@@ -0,0 +1,32 @@
+class BaseMailer < 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 can?
+    Ability.abilities.allowed?(current_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
new file mode 100644
index 0000000000000000000000000000000000000000..883f1c73ad43a5751d1688b2f03d17ca91519363
--- /dev/null
+++ b/app/mailers/email_rejection_mailer.rb
@@ -0,0 +1,21 @@
+class EmailRejectionMailer < BaseMailer
+  def rejection(reason, original_raw, can_retry = false)
+    @reason = reason
+    @original_message = Mail::Message.new(original_raw)
+
+    return unless @original_message.from
+
+    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
+end
diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb
index 687bac3aa31a009de8219bf58624626bee70c60b..2c035fbb70bdce3d02c8a6649f07a5e863ce3385 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})"))
+
+      SentNotification.record(@issue, recipient_id, reply_key)
     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})"))
+
+      SentNotification.record(@issue, recipient_id, reply_key)
     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})"))
+
+      SentNotification.record(@issue, recipient_id, reply_key)
     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})"))
+
+      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 512a8f7ea6be5034077a1944b066415c61a66b0d..7923fb770d0427885b69524fe75cdab46f281703 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})"))
+
+      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)
@@ -23,6 +25,8 @@ module Emails
                          from: sender(updated_by_user_id),
                          to: recipient(recipient_id),
                          subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
+
+      SentNotification.record(@merge_request, recipient_id, reply_key)
     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})"))
+
+      SentNotification.record(@merge_request, recipient_id, reply_key)
     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})"))
+
+      SentNotification.record(@merge_request, recipient_id, reply_key)
     end
 
     def merge_request_status_email(recipient_id, merge_request_id, status, updated_by_user_id)
@@ -58,52 +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}"))
-    end
-  end
+                         subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
 
-  # 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 => <Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
-  #   => target project => <Project id: 2, name: "My Ror", path: "ruby_on_rails", ...>
-  #   => 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 => <Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
-  #   => target project => <Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
-  #   => 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}"
+      SentNotification.record(@merge_request, recipient_id, reply_key)
     end
-    subject << " | " + extra.join(' | ') if extra.present?
-    subject
   end
-
 end
diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb
index ff251209e01e2d6c4f82c61167080f183e378c7c..63d4aca61af5e011a2f85987b2e15ea9993aeb65 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})"))
+
+      SentNotification.record(@commit, recipient_id, reply_key)
     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})"))
+
+      SentNotification.record(@issue, recipient_id, reply_key)
     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})"))
+
+      SentNotification.record(@merge_request, recipient_id, reply_key)
     end
   end
 end
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 79fb48b00d36a4f8984e2967ba9ebbfc2a6b43c5..5717c89e61d5c800de8244f096bc6135870f3eed 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: Gitlab.config.gitlab.email_reply_to
-
-  # 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,
@@ -48,13 +35,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
-
   def can_send_from_user_email?(sender)
     sender_domain = sender.email.split("@").last
     self.class.allowed_email_domains.include?(sender_domain)
@@ -85,14 +65,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
@@ -126,14 +98,37 @@ class Notify < ActionMailer::Base
     "<#{model_name}_#{model.id}@#{Gitlab.config.gitlab.host}>"
   end
 
+  def mail_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
+
+    if reply_key
+      headers['X-GitLab-Reply-Key'] = 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)
+  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 = {}, &block)
+  def mail_new_thread(model, headers = {})
     headers['Message-ID'] = message_id(model)
-    headers['X-GitLab-Project'] = "#{@project.name} | " if @project
-    mail(headers, &block)
+
+    mail_thread(model, headers)
   end
 
   # Send an email that responds to an existing conversation thread,
@@ -144,19 +139,17 @@ 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
+    headers[:subject].prepend('Re: ') if headers[:subject]
 
-    mail(headers, &block)
+    mail_thread(model, headers)
   end
 
-  def can?
-    Ability.abilities.allowed?(user, action, subject)
+  def reply_key
+    @reply_key ||= Gitlab::ReplyByEmail.reply_key
   end
 end
diff --git a/app/models/group.rb b/app/models/group.rb
index 4ff610f8e9d7d55c69006ec412503f27c890b2ce..9cd146bb73bf18e718beab0f34594b7ca7a50c8c 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/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index 46fb85336e537f0c092217e246bf24b9798c7ac0..9a8251bdad510d28be28f2bc5c5e091a60a41e8c 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/milestone.rb b/app/models/milestone.rb
index d28f3c8d3f908756cfa196c2a76cbe3da1317f8a..c6aff6f709f348fdff95537f18d14113856233cb 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/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb
index a714bc82246d778742b7497d657750b8b79272eb..9e5da6f45d2444e44e7b3356363617ff22ff5ec3 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 ecdcd48ae602476d8670fae8352fc7ed14cd718c..acbbc9935b62705d6d0be647388bdff9b2ed69c4 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/models/sent_notification.rb b/app/models/sent_notification.rb
new file mode 100644
index 0000000000000000000000000000000000000000..460ca40be3f260a62e7aafc63f38c13f96d0230d
--- /dev/null
+++ b/app/models/sent_notification.rb
@@ -0,0 +1,50 @@
+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, unless: :for_commit?
+  validates :commit_id, presence: true, if: :for_commit?
+
+  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?
+    noteable_type == "Commit"
+  end
+
+  def noteable
+    if for_commit?
+      project.commit(commit_id) rescue nil
+    else
+      super
+    end
+  end
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index 1f0735a7e7bd289085c703f8edaa7c2fbc018758..48e0e6ed48bb6aba4e1af7219ce3a511dd52258c 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/services/git_push_service.rb b/app/services/git_push_service.rb
index 81535450ac15f6cc0974f3d6452b7b74c3034197..0a73244774a20c22ea070c97215a6f4854d4e4a6 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/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb
index f431c5d55343eee2d6931f77ed75bf2ed6262de3..9651b16462cb61b2225111e1b1c6c6a99ac0b98d 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
diff --git a/app/services/projects/upload_service.rb b/app/services/projects/upload_service.rb
index 992a7a7a1dc7f213abe3ff0c6fd481378b9f08ea..279550d6f4a2e9e484711ad0b65a3e2249a0f5ed 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/app/views/admin/abuse_reports/_abuse_report.html.haml b/app/views/admin/abuse_reports/_abuse_report.html.haml
index 4449721ae380cba8b35b001da8c9b875f6e4a5d7..d3afc658cd62d3641e7b21be6ff14acdf84e594b 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
-    = link_to 'Remove report', [:admin, abuse_report], method: :delete, class: "btn btn-xs btn-close"
+    - 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"
+    = link_to 'Remove report', [:admin, abuse_report], remote: true, method: :delete, class: "btn btn-xs btn-close js-remove-tr"
diff --git a/app/views/admin/abuse_reports/index.html.haml b/app/views/admin/abuse_reports/index.html.haml
index 4a25848f1566583462e29c9a0b082711d0e3300a..2e8746146d15f8b8690b53a6086c602bb8f43ee1 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
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 3732ff847b93261012037e9b6e08789f01953320..54191aadda6c7f4c232f278dbd4895ffc5f95c7e 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
diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml
index e74e1e85f4165251dc1f8f7d1cf988c01584397c..b120f4dea67b675ea92c6d84a3405ac60eaa6b5f 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/dashboard/_projects.html.haml b/app/views/dashboard/_projects.html.haml
index d676576067cf0c486aaf925dc36bd847e013463f..ef9b9ce756acb29a1a7a644633bdd9b8d9d52e83 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,4 @@
           = link_to new_project_path, class: 'btn btn-success' do
             New project
 
-  = render 'shared/projects_list', projects: @projects, projects_limit: 20
+  = render 'shared/projects/list', projects: @projects
diff --git a/app/views/dashboard/activity.html.haml b/app/views/dashboard/activity.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..7a5a093add50737839e1a2c3971a44f1f5cdfb75
--- /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/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml
index 0860fe3c761e577c53f5fa4c2b8870d680f14793..fbe523b4b66423dd3ba9ebd8e1a796d40446782a 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/dashboard/projects/starred.html.haml b/app/views/dashboard/projects/starred.html.haml
index 98b8cde47666d6e5479539099e2222269a2a6de2..19f3975e530f1924d95060d2d6bb7a571757117f 100644
--- a/app/views/dashboard/projects/starred.html.haml
+++ b/app/views/dashboard/projects/starred.html.haml
@@ -5,10 +5,10 @@
   = render 'shared/show_aside'
 
   .dashboard.row
-    %section.activities.col-md-8
+    %section.activities.col-md-7
       = render 'dashboard/activities'
-    %aside.col-md-4
-      .panel.panel-default
+    %aside.col-md-5
+      .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'
@@ -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/dashboard/show.html.haml b/app/views/dashboard/show.html.haml
index a3a32b6932f5afb31b594253dca2b73f0f4b10ac..4cf2feb9aa60048ae4cb5f16a505531ec24949fd 100644
--- a/app/views/dashboard/show.html.haml
+++ b/app/views/dashboard/show.html.haml
@@ -4,14 +4,10 @@
 
 = 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'
+- if @last_push
+  = render "events/event_last_push", event: @last_push
 
+- if @projects.any?
+  = render 'projects'
 - else
   = render "zero_authorized_projects"
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 0000000000000000000000000000000000000000..7f7d841fe21c1b8eb400e336215f16a62160a1e0
--- /dev/null
+++ b/app/views/email_rejection_mailer/rejection.html.haml
@@ -0,0 +1,4 @@
+%p
+  Unfortunately, your email message to GitLab could not be processed.
+
+= markdown @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 0000000000000000000000000000000000000000..6693e6f90e8b9b869e0529b01bbaad1a6d9eb7a0
--- /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/views/events/_event_last_push.html.haml b/app/views/events/_event_last_push.html.haml
index 501412642db283c6d3976e1f3b3c92102920b8d4..6a0c6cba41bd73012bf1b5545a74d0bd00e4eeef 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/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml
index 7dcefd330a14930befe7c74ad0c4993712b5f6e9..80acb91436599f5e3205744bf35beb8013f9d301 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/explore/projects/_project.html.haml b/app/views/explore/projects/_project.html.haml
deleted file mode 100644
index 1e8a89e3661b7f85dc61f353435a6f1d7f166561..0000000000000000000000000000000000000000
--- 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)
-        &middot;
-        = link_to pluralize(project.repository.branch_names.count, 'branch'), namespace_project_branches_path(project.namespace, project)
-        &middot;
-        = 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 0000000000000000000000000000000000000000..669079e9521733e02500e87d32c8b81b6c87f766
--- /dev/null
+++ b/app/views/explore/projects/_projects.html.haml
@@ -0,0 +1,6 @@
+- if projects.any?
+  .public-projects
+    = render 'shared/projects/list', projects: projects
+- else
+  .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 4956081e1ed5d17616d6c726ce75a8ab2d70b435..0cfdf5cfd15a7bf2d494605cc8b1226f80e1b782 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 fdccbe5692fd2dfec02e8ab26fe96ae5d0d86b13..4a9fcae4bed75d90726be7ca20472446846e6fb9 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 98a4174b426aaf5601ee4ba809ddb896f5b2a82c..4c7e7d44733224b140bc34309605e30d87fcffdd 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/groups/_projects.html.haml b/app/views/groups/_projects.html.haml
index 4f8aec1c67e9a4f767febb7cc0f7c5313edef0b9..b2e32ced5e0f614581019282bda595c050de6469 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'
@@ -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/groups/show.html.haml b/app/views/groups/show.html.haml
index d31dae7d648642069666137dd32c38c81eb1a37b..0577f4ec14239c44ed893a82eb8496c04371c195 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/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index 96e15783a369af776c0d737f8413906d9ffc1fa4..0104d7198df4d251e1c403ffce407d302df8489f 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
@@ -13,7 +18,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 12ddbe6f1b72f90a24c7dba2c6f9d7039ae5959a..0b630b55c7016c6434da87cce25773acb28b2b21 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
@@ -17,15 +12,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 +20,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/layouts/header/_public.html.haml b/app/views/layouts/header/_public.html.haml
index 15c2e292be38fb69b3f3112780b41adab7862d1c..af4b9ba58f6783627c41ab2c3367258c66c01126 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
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index 8f010196d1a2acdc03be0631a13484726b123857..d620c022273679eb7158498d01d51b876a13373f 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')
@@ -29,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
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index d17d1c5fbd48cb5b5f3ede5fe8b2ff3168f04a9b..5e7b902622bf65bceceeb8b93d07841acd2d6c7c 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
 
diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml
index c8662a15adb9f3dd8bbe5828ad6dfb96fe3e97ba..ec209c38eeddbeea91628f00a5f642d0cf26dff1 100644
--- a/app/views/layouts/notify.html.haml
+++ b/app/views/layouts/notify.html.haml
@@ -36,7 +36,11 @@
         &mdash;
         %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.
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index 9480a19f5b216e523556630ff0dae24baa12946d..db7fa2eabe3f7f7f1c4b9d6439a05aec0dc960e5 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"
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index 1134317ee062e1816801356c3c21e1be3df9c199..aa0361a0a1be58c9300c5b8e8161c54aa4e6018a 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/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index 9fdeddfcc7a697146b89e142d92745a8e5d33398..c519e52e596d305e21936cbd1ab3f37debf1dad6 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/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index bec40ec27a5ae648957c570f09f26a7a84a95590..b93036e78e646e9c433edf2afae91e0728272c1a 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)
diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml
index a3ff7ce2f1ffdb666d1d97143af8caba2f4b0a80..c1ec42aefca4ad4e6751dc1b08c3c2b848bf4f0f 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
diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml
index cade930c8cc8da9004bd6756b60d1418922cc34d..bc7625e8989dc6bb52517ea7173862966f6e47a0 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
diff --git a/app/views/projects/deploy_keys/index.html.haml b/app/views/projects/deploy_keys/index.html.haml
index 2e9c5dc08c8f06d307aa8a18a3e0f6343b4f4026..8e24c778b7c0e4c0dff6016e2702b1880a603dab 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 52c1e03040c0cafa1d8dab669937d261d2fae636..30943f49bba54d996b84cf16057dbcb9b1de4807 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/diffs/_warning.html.haml b/app/views/projects/diffs/_warning.html.haml
index caed0e69dc883aee701b1574ddb034ce6bda9f34..f99bc9a85eb7aca109f57e670b68358021bac3b7 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/app/views/projects/graphs/commits.html.haml b/app/views/projects/graphs/commits.html.haml
index 141acbdcf722478d55368bc37f059ba3c0966e17..a357736bf528d5107e5b1f507ce046982b6f2722 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})
diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml
index eadbf61fdd4ed32f75995b2997a2601f4bc1c39f..85dbfd6786258ffc7dbf9b4daa554cccc5a1abbc 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/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 007f6c6a7878ee810820ec635fd1c6cd62b6e029..ec1838eb489a58470306520c17efd72ec254fda1 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"
diff --git a/app/views/projects/snippets/_snippet.html.haml b/app/views/projects/snippets/_snippet.html.haml
deleted file mode 100644
index b2c35edc44c653e011712dbbb54e26040affeb9a..0000000000000000000000000000000000000000
--- 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 30081673ffc76b4c2850a98b8358cf327b4bfa4c..45d4de6a385b4cc0b17d50a208218df97d901f23 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/search/_category.html.haml b/app/views/search/_category.html.haml
index 154332cb9a9d8623391174eebdf5db2ce006483f..d637abfa76b90588840b1e98c209e0c7521b7fd4 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
@@ -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/_filter.html.haml b/app/views/search/_filter.html.haml
index e2d0cab9e79bfe18132054e8aa9ae538ba00c53a..ec478a5963d15d75fb75cc6f7caf9b80b891611c 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 5ee70be1ad6b44ee8fa894db5ed2b80f8e4592e2..3938c545caddd0bbbc7948b22026c83fbc3fc835 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 741c780ad96921da5c45bd48ed063c7c673c2a2a..2a38c98dcfcc17dc205f6c49d26b4f69c8a05452 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,10 +11,13 @@
       - elsif @group
         in group #{link_to @group.name, @group}
 
-  %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/_blob.html.haml b/app/views/search/results/_blob.html.haml
index 58f58eff54d048db97352ba8b494604c450d89ff..0fe8a3b490a8293d6b8ea0afbf08031ea87e7db7 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/_milestone.html.haml b/app/views/search/results/_milestone.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..e0b18733d742e6319cb25d900eb0384cff15e1e5
--- /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/app/views/search/results/_project.html.haml b/app/views/search/results/_project.html.haml
deleted file mode 100644
index 195cf06c8ea73fa1b208d7f7556ddef491985ae8..0000000000000000000000000000000000000000
--- 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/search/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml
index 95099853918d22129c5fae2866c46a1a75a722c0..9a4f9fb9485433c382bfdce23f6ea87be3572f78 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{ class: user_color_scheme }
             .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 c03438eb9525705759a0199609b231e928841eb6..f5859481d465edd97ac8b6c9713fd529da555f6c 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/search/show.html.haml b/app/views/search/show.html.haml
index 60f9e9ac9de07b3a20ab866f96f83d9066fe097c..f4f3dcfc29ff5ba1ebe68709264f50e7adaa4ac9 100644
--- a/app/views/search/show.html.haml
+++ b/app/views/search/show.html.haml
@@ -1,7 +1,5 @@
 - page_title @search_term
 = render 'search/form'
-%hr
 - if @search_term
   = render 'search/category'
-  %hr
   = render 'search/results'
diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml
index d6a2e177da120c24ea3addc34fb087372d47eccb..57c3aff3e1892480a7f6d1906d5d85f26def4733 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{ class: user_color_scheme }
   .line-numbers
     - if blob.data.present?
       - blob.data.lines.each_index do |index|
diff --git a/app/views/shared/_project.html.haml b/app/views/shared/_project.html.haml
deleted file mode 100644
index 6bd61455d2170ba8cd01b1c0d9d34988c8665b77..0000000000000000000000000000000000000000
--- a/app/views/shared/_project.html.haml
+++ /dev/null
@@ -1,16 +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
-      %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
diff --git a/app/views/shared/_projects_list.html.haml b/app/views/shared/_projects_list.html.haml
deleted file mode 100644
index 4c58092af44ca17c60a75dd231dc87e5556bd179..0000000000000000000000000000000000000000
--- 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/groups/_group.html.haml b/app/views/shared/groups/_group.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..229ae359bc597bce882dd18b41c2fb0cbcdafd2e
--- /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")}
+
diff --git a/app/views/shared/issuable/_context.html.haml b/app/views/shared/issuable/_context.html.haml
index 19e8c31975bfad9e739876cfeb58f0eb7d8465f6..cba18c14568710fddff4b99341cbdd1a07b7991a 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 0e8da8de723736c556887ff54cf0c13ae8611d00..bcaa48c7a12ecba4d1293a95e9fccf9e0902923f 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, 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 3489bf3f1913edd4a1098800a186976ec9e95688..09327d645f32d2b7e135ea1edd45857f8e5ebea5 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
@@ -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,
+            first_user: true, current_user: true)
         &nbsp;
         = link_to 'Assign to me', '#', class: 'btn assign-to-me-link'
   .form-group
diff --git a/app/views/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..021e3b689a15dacaa597f9b95db5a66a63650dd7
--- /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 0000000000000000000000000000000000000000..4bfdf4d55ff889fa41f594770efb8113b62d1179
--- /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/snippets/_snippet.html.haml b/app/views/shared/snippets/_snippet.html.haml
similarity index 53%
rename from app/views/snippets/_snippet.html.haml
rename to app/views/shared/snippets/_snippet.html.haml
index 5bb28664349416608c23b1c75db98a4ee406f63e..69a713ad9aacf4b20cae64fbfaca0bbc10e09779 100644
--- a/app/views/snippets/_snippet.html.haml
+++ b/app/views/shared/snippets/_snippet.html.haml
@@ -1,12 +1,12 @@
-%li
-  %h4.snippet-title
+%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.cgray.monospace.tiny.pull-right
+    %span.monospace.pull-right
       = snippet.file_name
 
   %small.pull-right.cgray
@@ -14,10 +14,8 @@
       = 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)}
+    = 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/_snippets.html.haml b/app/views/snippets/_snippets.html.haml
index 40df42b6cf5805d73d67165769621f8528105eec..d9aa4dd1d2eb22db73e91a8f8e98cad288474197 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.
diff --git a/app/views/users/_projects.html.haml b/app/views/users/_projects.html.haml
index 297fa53739407271eb9303bb51c9ce827ce59d45..a126a858ea85f0ce96e785dc2ebc0297a161d38f 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
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 64b7f25ad37a18ec7712048914b194b3202204c6..aa4e8722fb1fe2092fabdcb28fb1882c84e15cc5 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
 
diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8cfb96ef37645b102727ea4850c36cb2663c401a
--- /dev/null
+++ b/app/workers/email_receiver_worker.rb
@@ -0,0 +1,51 @@
+class EmailReceiverWorker
+  include Sidekiq::Worker
+
+  sidekiq_options queue: :incoming_email
+
+  def perform(raw)
+    return unless Gitlab::ReplyByEmail.enabled?
+
+    begin
+      Gitlab::Email::Receiver.new(raw).execute
+    rescue => e
+      handle_failure(raw, e)
+    end
+  end
+
+  private
+
+  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
+
+    case e
+    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
+      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::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::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
+      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::InvalidNoteError
+      can_retry = true
+      reason = e.message
+    else
+      return
+    end
+
+    EmailRejectionMailer.delay.rejection(reason, raw, can_retry)
+  end
+end
diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb
index 1d21addece6df43d48a79a6c0e5d478c47e06ef3..916a99bb273b8aadb85a6ff45541d4209d2dabc3 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/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb
index b546f8777e158405b5459338f0944ab2b677e42f..f2ba2e15e7b2f7e7ef4f3e028ee13d311aed8792 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/bin/background_jobs b/bin/background_jobs
index a041a4b0433b292aa3785d089b5e909a4e1400ea..a4895cf6586b9710a35e622fd0347e1068cea15d 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/daemon_with_pidfile b/bin/daemon_with_pidfile
new file mode 100755
index 0000000000000000000000000000000000000000..f138c27a0e2b85374a5f874a633163b864e04eb1
--- /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
new file mode 100755
index 0000000000000000000000000000000000000000..74a84f5b2b477d64441989633a3b68963a4d1db5
--- /dev/null
+++ b/bin/mail_room
@@ -0,0 +1,50 @@
+#!/bin/sh
+
+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()
+{
+  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()
+{
+  bin/daemon_with_pidfile $mail_room_pidfile bundle exec mail_room -q -c $mail_room_config >> $mail_room_logfile 2>&1
+}
+
+stop()
+{
+  get_mail_room_pid
+  kill -TERM $mail_room_pid
+}
+
+restart()
+{
+  stop
+  start
+}
+
+case "$1" in
+  start)
+    start
+    ;;
+  stop)
+    stop
+    ;;
+  restart)
+    restart
+    ;;
+  *)
+    echo "Usage: $0 {start|stop|restart}"
+    ;;
+esac
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 56770335ddce76a025d85916040bb4e04517c94d..c7b60a1d4b111bf3726096bdd10f3ce07a35bd9f 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -94,6 +94,13 @@ production: &base
     # The default is 'tmp/repositories' relative to the root of the Rails app.
     # 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"
+
   ## 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 ef6e074c1084e7ec03dc5431674bea371a72e36f..c47e5dab27c7ac3c757aa06c792e1c488694ebc2 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
@@ -150,6 +160,12 @@ Settings.gitlab['repository_downloads_path'] = File.absolute_path(Settings.gitla
 Settings.gitlab['restricted_signup_domains'] ||= []
 Settings.gitlab['import_sources'] ||= ['github','bitbucket','gitlab','gitorious','google_code','git']
 
+#
+# Reply by email
+#
+Settings['reply_by_email'] ||= Settingslogic.new({})
+Settings.reply_by_email['enabled'] = false if Settings.reply_by_email['enabled'].nil?
+
 #
 # Gravatar
 #
diff --git a/config/initializers/7_omniauth.rb b/config/initializers/7_omniauth.rb
index 7f73546ac89ab13de781b567d4a97e40b6ea219b..70ed10e8275ef461c703d98064f2b31cf3fb643f 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?
diff --git a/config/mail_room.yml.example b/config/mail_room.yml.example
new file mode 100644
index 0000000000000000000000000000000000000000..dd8edfc42eb027c7c1740d740497ea6709d4c8ed
--- /dev/null
+++ b/config/mail_room.yml.example
@@ -0,0 +1,27 @@
+: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: "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
+    # # 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
+    #   # Always "resque:gitlab".
+    #   :namespace: resque:gitlab
+    #   # Always "incoming_email".
+    #   :queue: incoming_email
+    #   # Always "EmailReceiverWorker"
+    #   :worker: EmailReceiverWorker
diff --git a/config/routes.rb b/config/routes.rb
index d7307a61ede75414c336cdff3e778c8e4cbcb912..8ba439f08b80a4974c13471b5fc9a11f203ed284 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
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 0000000000000000000000000000000000000000..de2078a9268578b8569be64125eeb40ad538441a
--- /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/migrate/20150818213832_add_sent_notifications.rb b/db/migrate/20150818213832_add_sent_notifications.rb
new file mode 100644
index 0000000000000000000000000000000000000000..43e8d6a1a82e2d620e41ba6b14326424e383ec0f
--- /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/migrate/20150824002011_add_enable_ssl_verification.rb b/db/migrate/20150824002011_add_enable_ssl_verification.rb
new file mode 100644
index 0000000000000000000000000000000000000000..093c068fbde7ecee87d1e7cc978cacfebfc37794
--- /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 7a0c92bddcda8bb7382e18a0eba7f7db76960c75..7ee1c6e214681a647f3de09cbd89d7f9946e7085 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: 20150817163600) do
+ActiveRecord::Schema.define(version: 20150824002011) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
@@ -405,6 +405,17 @@ ActiveRecord::Schema.define(version: 20150817163600) 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"
@@ -515,13 +526,9 @@ ActiveRecord::Schema.define(version: 20150817163600) 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"
@@ -559,13 +566,14 @@ ActiveRecord::Schema.define(version: 20150817163600) 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/doc/README.md b/doc/README.md
index 0524fda3ed6cd63bc5acf904ff340abeb857f4c0..337c4e6a62d39300d417aa56f76e94969f3c4435 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/install/installation.md b/doc/install/installation.md
index 0b95c4da82d551c863ebe6365ead842c7d546f35..73e36fa7e51df31fa0a16699e76d67ae5ae893b3 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -366,7 +366,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/release/monthly.md b/doc/release/monthly.md
index 12d6a84b68e5ec4ac7c62174a50beddf6f7e06be..c1ed9e3b80e599d6693cb720c700868a9cc10b14 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
 
diff --git a/doc/reply_by_email/README.md b/doc/reply_by_email/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..5d36f5121d1d8b6ad7f35e6d83d3763cd533ada5
--- /dev/null
+++ b/doc/reply_by_email/README.md
@@ -0,0 +1,180 @@
+# 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. 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:
+      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:
+
+    ```sh
+    sudo editor config/mail_room.yml
+    ```
+
+    ```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
+        # 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
+          # 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
+    sudo editor /etc/default/gitlab
+    ```
+    
+    ```sh
+    mail_room_enabled=true
+    ```
+
+6. Restart GitLab:
+    
+    ```sh
+    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.
+
+### 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
+        # 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
+          # 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.
diff --git a/doc/workflow/importing/README.md b/doc/workflow/importing/README.md
index cd98d1b985245819a29a000947ab4e7b3c5d00ad..5cde90993d2fa600b8fa54be8a1efd53f7a84c44 100644
--- a/doc/workflow/importing/README.md
+++ b/doc/workflow/importing/README.md
@@ -8,5 +8,5 @@
 ### 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. 
diff --git a/features/admin/groups.feature b/features/admin/groups.feature
index aa365a6ea1ab14f3115545ada55d0c655376ffd7..973918086a3827db0ba74085e09b77b5d606390c 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/admin/hooks.feature b/features/admin/hooks.feature
new file mode 100644
index 0000000000000000000000000000000000000000..5ca332d9f1cfe031041697267cfa703759205c2a
--- /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/dashboard/dashboard.feature b/features/dashboard/dashboard.feature
index 1959d3270823f22209e0db4f15afada303362343..392d4235eff87aa7985795e1d83be3d78624e470 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 ec5680caba68c0726eea82b09f97645eddc081ef..96399ea21a6080badfc273489bc73a86ee678e63 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/project/commits/commits.feature b/features/project/commits/commits.feature
index c4b206edc95a30468157c41359e4687dbbd748df..3ebc8a39aaed4a6d17130c0229a9722de92f0628 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/project/hooks.feature b/features/project/hooks.feature
index 1a60846a23e6eb79e020324699fbb6c9c76709d1..627738004c4afcbd2df16758fc5ba32094c4efaf 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/search.feature b/features/search.feature
index 1608e8246717a178fdf6f291670062ed8576b53f..a9234c1a611bd254f578b5199a561860594a0d04 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/admin/groups.rb b/features/steps/admin/groups.rb
index 83a3f48abe385534f90817d0533de01210e6963b..d27634858a2f2328379a97399aa14df770ae0ad5 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/features/steps/admin/hooks.rb b/features/steps/admin/hooks.rb
new file mode 100644
index 0000000000000000000000000000000000000000..541e25fcb703fdb955926c082d7807841a4ad8d7
--- /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/commits/commits.rb b/features/steps/project/commits/commits.rb
index e6330ec457e802f6661592e4376786f5ff77be6c..a8532cc18d857c13d3efb7142aa95328883b4a1a 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
diff --git a/features/steps/project/hooks.rb b/features/steps/project/hooks.rb
index 04e3bf78ede53b0080c55dac3653cb49a4a28e37..df4a23a37169a5759a18e842980100b214518110 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'
diff --git a/features/steps/search.rb b/features/steps/search.rb
index 87893aa02057b6715325efe463be8e20cedab151..79273cbad9a8f4ac6dd1e24c38070659dfc8eb58 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/features/steps/shared/paths.rb b/features/steps/shared/paths.rb
index ca8fbb49101b32e43ba7af3a5cdcb893cd419a2b..b4deccb65209334822b91affd968bf0bc1e7290d 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
diff --git a/features/user.feature b/features/user.feature
index 69618e929c4cee95581e22170438d837fa5ae6df..35eae842e77184cc231bacfbf1d6c3ae69c4bb69 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
diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb
index 42c93707caa69cba20af39ff39af6112b90bdbc1..d8a7d29f1bf1488a082f6c0dfed972fae325beec 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 9931aa7e029996fbb95f63ac13f11d94db79569c..0b63f025d0ad45900d8c4816e4703ab9200935c5 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 1a24a86fc37c0f00d8900633e11fd0efa261566c..f4dd393ad29f04c9ee43b6fa7319b4f59a2adf82 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 54420e62c9091a1df425d2f60a7d61b8ae85b48c..35e34d033e0ccc8a31b501f8412b5a58b0231917 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/color_schemes.rb b/lib/gitlab/color_schemes.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9c4664df90309010711378c524600930d704d62f
--- /dev/null
+++ b/lib/gitlab/color_schemes.rb
@@ -0,0 +1,67 @@
+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
+
+    # Returns the number of defined Schemes
+    def self.count
+      SCHEMES.size
+    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/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index 1a2a50a14d0072a80231bc2814d495ba8d357037..7ad3ed8728fd9773fad7d63bdc2fe5f0bbd57b53 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
diff --git a/lib/gitlab/email/attachment_uploader.rb b/lib/gitlab/email/attachment_uploader.rb
new file mode 100644
index 0000000000000000000000000000000000000000..32cece8316b319922a19bcbaf11edb61dcab7159
--- /dev/null
+++ b/lib/gitlab/email/attachment_uploader.rb
@@ -0,0 +1,35 @@
+module Gitlab
+  module Email
+    class 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/receiver.rb b/lib/gitlab/email/receiver.rb
new file mode 100644
index 0000000000000000000000000000000000000000..355fbd278981e013595d869d53c40ecb040d74ee
--- /dev/null
+++ b/lib/gitlab/email/receiver.rb
@@ -0,0 +1,106 @@
+# Inspired in great part by Discourse's Email::Receiver
+module Gitlab
+  module Email
+    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 UserBlockedError < ProcessingError; end
+      class UserNotAuthorizedError < ProcessingError; end
+      class NoteableNotFoundError < ProcessingError; end
+      class InvalidNoteError < ProcessingError; end
+
+      def initialize(raw)
+        @raw = raw
+      end
+
+      def execute
+        raise EmptyEmailError if @raw.blank?
+
+        raise SentNotificationNotFoundError unless sent_notification
+
+        raise AutoGeneratedEmailError if message.header.to_s =~ /auto-(generated|replied)/
+
+        author = sent_notification.recipient
+
+        raise UserNotFoundError unless author
+
+        raise UserBlockedError if author.blocked?
+
+        project = sent_notification.project
+
+        raise UserNotAuthorizedError unless project && 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 InvalidNoteError, message
+        end
+      end
+
+      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|
+          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 = Email::AttachmentUploader.new(message).execute(sent_notification.project)
+
+        attachments.each do |link|
+          text = "[#{link[:alt]}](#{link[:url]})"
+          text.prepend("!") if link[:is_image]
+
+          reply << "\n\n#{text}"
+        end
+
+        reply
+      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 0000000000000000000000000000000000000000..6ed36b51f12d9bca142229d25f0e28e083c5b6c7
--- /dev/null
+++ b/lib/gitlab/email/reply_parser.rb
@@ -0,0 +1,79 @@
+# 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)
+        text    = message.text_part if message.multipart?
+        text  ||= message           if message.content_type !~ /text\/html/
+
+        return "" unless text
+
+        text = fix_charset(text)
+
+        # Certain trigger phrases that means we didn't parse correctly
+        if text =~ /(Content\-Type\:|multipart\/alternative|text\/plain)/
+          return ""
+        end
+
+        text
+      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/github_import/importer.rb b/lib/gitlab/github_import/importer.rb
index 98039a76dcdbfb40d5363a839e13ac3214853df7..8c106a61735207727cfed851fe2e53f97a792474 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 2723eec933eaea9a28b1b4afe2b277188d3f4452..8c27ebd1ce8fa503528a9911558a6bfe1a7e53f9 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 c5304a0699b9e1c424040e1a36b75fee3165507f..50594d2b24f0b421871571e97cbb4d82b258779f 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 f0d7141bf566ff163fcd243dd523590e98e38fbf..d9452de6a5093b99990a79f44fb29ce1986d6098 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/lib/gitlab/markdown/autolink_filter.rb b/lib/gitlab/markdown/autolink_filter.rb
index 4e14a048cfb3dc96e3cb9c4ca7024b1347499de3..541f1d88ffc356f86cfb9ae97e77d007833ff6bb 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/lib/gitlab/reply_by_email.rb b/lib/gitlab/reply_by_email.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c3fe6778f0630165714c1d6b10c554adae38e06f
--- /dev/null
+++ b/lib/gitlab/reply_by_email.rb
@@ -0,0 +1,49 @@
+module Gitlab
+  module ReplyByEmail
+    class << self
+      def enabled?
+        config.enabled && address_formatted_correctly?
+      end
+
+      def address_formatted_correctly?
+        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)
+        regex = address_regex
+        return unless regex
+
+        match = address.match(regex)
+        return unless match
+
+        match[1]
+      end
+
+      private
+
+      def config
+        Gitlab.config.reply_by_email
+      end
+
+      def address_regex
+        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
diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb
index 06245374bc8b969c76fc2c2d2aec9c556080419a..2ab2d4af797d5b09451315cb2a38c753cd4d9390 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/
diff --git a/lib/gitlab/themes.rb b/lib/gitlab/themes.rb
index 5209df92795d21ef91bfde1ac94b3d4cca0ea767..83f91de810cdb0a22b23c14c1a304b9257016617 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
@@ -51,6 +56,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
diff --git a/lib/redcarpet/render/gitlab_html.rb b/lib/redcarpet/render/gitlab_html.rb
index f57b56cbdf005a3ea9ca1986ae04aad03eb9e499..9cb8e91d6e350077fb0785cdaec7ccc197bb0ce7 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/lib/support/init.d/gitlab b/lib/support/init.d/gitlab
index a3455728a94050cb43465f085cb9e4d17edecc00..457bd31e23bd265c545e5811e575d6a3af838b29 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 ] || [ ! -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
@@ -111,7 +120,15 @@ check_status(){
   else
     sidekiq_status="-1"
   fi
-  if [ $web_status = 0 -a $sidekiq_status = 0 ]; then
+  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 ] && [ $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
@@ -125,26 +142,33 @@ 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" ] && [ "$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" ] && [ "$sidekiq_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; }; then
     echo "GitLab is not running."
     exit
   fi
@@ -154,12 +178,14 @@ 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
-    echo -n "Starting GitLab Unicorn"
-  elif [ "$sidekiq_status" != "0" ]; then
-    echo -n "Starting GitLab Sidekiq"
+  if [ "$web_status" != "0" ]; then
+    echo "Starting GitLab Unicorn"
+  fi
+  if [ "$sidekiq_status" != "0" ]; then
+    echo "Starting GitLab Sidekiq"
+  fi
+  if [ "$mail_room_enabled" = true ] && [ "$mail_room_status" != "0" ]; then
+    echo "Starting GitLab MailRoom"
   fi
 
   # Then check if the service is running. If it is: don't start again.
@@ -179,22 +205,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 $mpid, 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 "Shutting down GitLab Unicorn"
+  fi
+  if [ "$sidekiq_status" = "0" ]; then
+    echo "Shutting down GitLab Sidekiq"
+  fi
+  if [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; then
+    echo "Shutting down GitLab MailRoom"
   fi
 
   # If the Unicorn web server is running, tell it to stop;
@@ -205,13 +242,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" ] || [ "$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" ]; then
+    if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; }; then
       printf "\n"
       break
     fi
@@ -220,7 +261,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 +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" ]; then
+  if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; }; then
     echo "GitLab is not running."
     return
   fi
@@ -242,7 +286,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 $mpid is running."
+    else
+        printf "The GitLab MailRoom email processor is \033[31mnot running\033[0m.\n"
+    fi
+  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
 }
@@ -257,9 +308,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 +324,7 @@ reload_gitlab(){
 ## Restarts Sidekiq and Unicorn.
 restart_gitlab(){
   check_status
-  if [ "$web_status" = "0" -o "$sidekiq_status" = "0" ]; then
+  if [ "$web_status" = "0" ] || [ "$sidekiq_status" = "0" ] || { [ "$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 cf7f4198cbf604db1580d5ff24a5eb784fb3c406..fd70cb7cc742a6ec812842a6dcbf2edb537894c5 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"
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index 60aa50e8751496a2b4760f61180c5fe9a7538de5..2b9688c1b40a625451aed662d82a8a0a7b2a6b5b 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}
 
@@ -629,6 +630,174 @@ 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
+
+        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
+
+      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_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? ... "
+
+      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_running?
+        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_running?
+      ps_ux, _ = Gitlab::Popen.popen(%W(ps ux))
+      ps_ux.include?("mail_room")
+    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.
diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb
index 3521d690259a72919955fa36491171cb9bbc4199..aa8d6cb807f968c4b8af91a540fc7d010d0d3996 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) }
diff --git a/spec/controllers/import/bitbucket_controller_spec.rb b/spec/controllers/import/bitbucket_controller_spec.rb
index 89e595121a731afe8b3370abd9757ad77a318575..81c03c9059b5c08fafbafd1204ae030a285cf2e2 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 0bc14059a350fb0ac82db681336340d889596306..766be578f7f34be0ba9b01e47f5f232efe378105 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 4bc67c8670380cc2f031d64d34baa701949e1b67..198d006af76ccec6125d283a08f0f129a739d0e9 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/features/markdown_spec.rb b/spec/features/markdown_spec.rb
index 3da4dfc2b23d6aaf683db4d73f00c3db8fd34c5a..4fe019f83423c142803e2709b52fa7528d3abe28 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
 
@@ -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/fixtures/emails/android_gmail.eml b/spec/fixtures/emails/android_gmail.eml
new file mode 100644
index 0000000000000000000000000000000000000000..21c5dde23460ae2e799c20d59ae22cbe2638e5ba
--- /dev/null
+++ b/spec/fixtures/emails/android_gmail.eml
@@ -0,0 +1,177 @@
+Delivered-To: reply@discourse.org
+Return-Path: <walter.white@googlemail.com>
+MIME-Version: 1.0
+In-Reply-To: <topic/22638/86406@meta.discourse.org>
+References: <topic/22638@meta.discourse.org>
+  <topic/22638/86406@meta.discourse.org>
+Date: Fri, 28 Nov 2014 12:53:21 -0800
+Subject: Re: [Discourse Meta] [Lounge] Testing default email replies
+From: Walter White <walter.white@googlemail.com>
+To: Discourse Meta <reply@discourse.org>
+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" <info@discourse.org> wrote:
+
+>     techAPJ <https://meta.discourse.org/users/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
+> <https://meta.discourse.org/users/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
+> <https://meta.discourse.org/my/preferences>.
+>
+
+--089e0149cfa485c6630508f173df
+Content-Type: text/html; charset=UTF-8
+Content-Transfer-Encoding: quoted-printable
+
+<p dir=3D"ltr">### this is a reply from Android 5 gmail</p>
+<p dir=3D"ltr">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. </p>
+<p dir=3D"ltr">This is **bold** in Markdown.</p>
+<p dir=3D"ltr">This is a link to <a href=3D"http://example.com">http://exam=
+ple.com</a></p>
+<div class=3D"gmail_quote">On Nov 28, 2014 12:36 PM, &quot;Arpit Jalan&quot=
+; &lt;<a href=3D"mailto:info@discourse.org">info@discourse.org</a>&gt; wrot=
+e:<br type=3D"attribution"><blockquote class=3D"gmail_quote" style=3D"margi=
+n:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div>
+
+<table style=3D"margin-bottom:25px" cellspacing=3D"0" cellpadding=3D"0" bor=
+der=3D"0">
+  <tbody>
+    <tr>
+      <td style=3D"vertical-align:top;width:55px">
+        <img src=3D"https://meta-discourse.global.ssl.fastly.net/user_avata=
+r/meta.discourse.org/techapj/45/3281.png" title=3D"techAPJ" style=3D"max-wi=
+dth:100%" width=3D"45" height=3D"45">
+      </td>
+      <td>
+        <a href=3D"https://meta.discourse.org/users/techapj" style=3D"text-=
+decoration:none;font-weight:bold;color:#006699;font-size:13px;font-family:&=
+#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif;color:#3b5998;text-d=
+ecoration:none;font-weight:bold" target=3D"_blank">techAPJ</a><br>
+        <span style=3D"text-align:right;color:#999999;padding-right:5px;fon=
+t-family:&#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif;font-size:=
+11px">November 28</span>
+      </td>
+    </tr>
+    <tr>
+      <td style=3D"padding-top:5px" colspan=3D"2">
+<p style=3D"margin-top:0;border:0">Test reply.</p>
+
+<p style=3D"margin-top:0;border:0">First paragraph.</p>
+
+<p style=3D"margin-top:0;border:0">Second paragraph.</p>
+</td>
+    </tr>
+  </tbody>
+</table>
+
+
+  <div style=3D"color:#666">
+    <p>To respond, reply to this email or visit <a href=3D"https://meta.dis=
+course.org/t/testing-default-email-replies/22638/3" style=3D"text-decoratio=
+n:none;font-weight:bold;color:#006699;color:#666" target=3D"_blank">https:/=
+/meta.discourse.org/t/testing-default-email-replies/22638/3</a> in your bro=
+wser.</p>
+  </div>
+  <hr style=3D"background-color:#ddd;min-height:1px;border:1px;background-c=
+olor:#ddd;min-height:1px;border:1px">
+  <h4>Previous Replies</h4>
+
+  <table style=3D"margin-bottom:25px" cellspacing=3D"0" cellpadding=3D"0" b=
+order=3D"0">
+  <tbody>
+    <tr>
+      <td style=3D"vertical-align:top;width:55px">
+        <img src=3D"https://meta-discourse.global.ssl.fastly.net/user_avata=
+r/meta.discourse.org/codinghorror/45/5297.png" title=3D"codinghorror" style=
+=3D"max-width:100%" width=3D"45" height=3D"45">
+      </td>
+      <td>
+        <a href=3D"https://meta.discourse.org/users/codinghorror" style=3D"=
+text-decoration:none;font-weight:bold;color:#006699;font-size:13px;font-fam=
+ily:&#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif;color:#3b5998;t=
+ext-decoration:none;font-weight:bold" target=3D"_blank">codinghorror</a><br=
+>
+        <span style=3D"text-align:right;color:#999999;padding-right:5px;fon=
+t-family:&#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif;font-size:=
+11px">November 28</span>
+      </td>
+    </tr>
+    <tr>
+      <td style=3D"padding-top:5px" colspan=3D"2">
+<p style=3D"margin-top:0;border:0">We&#39;re testing the latest GitHub emai=
+l processing library which we are integrating now.</p>
+
+<p style=3D"margin-top:0;border:0"><a href=3D"https://github.com/github/ema=
+il_reply_parser" style=3D"text-decoration:none;font-weight:bold;color:#0066=
+99" target=3D"_blank">https://github.com/github/email_reply_parser</a></p>
+
+<p style=3D"margin-top:0;border:0">Go ahead and reply to this topic and I&#=
+39;ll reply from various email clients for testing.</p>
+</td>
+    </tr>
+  </tbody>
+</table>
+
+
+<hr style=3D"background-color:#ddd;min-height:1px;border:1px;background-col=
+or:#ddd;min-height:1px;border:1px">
+
+<div style=3D"color:#666">
+<p>To respond, reply to this email or visit <a href=3D"https://meta.discour=
+se.org/t/testing-default-email-replies/22638/3" style=3D"text-decoration:no=
+ne;font-weight:bold;color:#006699;color:#666" target=3D"_blank">https://met=
+a.discourse.org/t/testing-default-email-replies/22638/3</a> in your browser=
+.</p>
+</div>
+<div style=3D"color:#666">
+<p>To unsubscribe from these emails, visit your <a href=3D"https://meta.dis=
+course.org/my/preferences" style=3D"text-decoration:none;font-weight:bold;c=
+olor:#006699;color:#666" target=3D"_blank">user preferences</a>.</p>
+</div>
+</div>
+</blockquote></div>
+
+--089e0149cfa485c6630508f173df--
diff --git a/spec/fixtures/emails/attachment.eml b/spec/fixtures/emails/attachment.eml
new file mode 100644
index 0000000000000000000000000000000000000000..f25c3d1a4498cc01ed66517c024adca785737e56
--- /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 <FROM>
+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 <TO>
+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 <info@discourse.org> wrote:
+
+>    Neil <http://meta.discourse.org/users/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 <http://users/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 <http://meta.discourse.org/users/neil>
+> November 19
+>
+> Looks like a bug when deleting a spammer. I'll look at it.
+>     riking <http://meta.discourse.org/users/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 <http://meta.discourse.org/users/codinghorror>
+> November 19
+>
+> Hmm, that's interesting -- can you have a look @eviltrout<http://users/eviltrout>?
+> I can't even find that topic by name.
+>     riking <http://meta.discourse.org/users/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
+> <//cdn.discourse.org/uploads/meta_discourse/2158/50b8b49557cb249e.png>
+>
+> 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<http://meta.discourse.org/user_preferences>
+> .
+>
+
+--047d7b45041e19c67b04eb9f3de6
+Content-Type: text/html; charset=ISO-8859-1
+Content-Transfer-Encoding: quoted-printable
+
+<div dir=3D"ltr">here is an image attachment</div><div class=3D"gmail_extra=
+"><br><br><div class=3D"gmail_quote">On Tue, Nov 19, 2013 at 5:11 PM, Neil =
+<span dir=3D"ltr">&lt;<a href=3D"mailto:info@discourse.org" target=3D"_blan=
+k">info@discourse.org</a>&gt;</span> wrote:<br>
+<blockquote class=3D"gmail_quote" style=3D"margin:0 0 0 .8ex;border-left:1p=
+x #ccc solid;padding-left:1ex"><div>
+<table style=3D"margin-bottom:25px;max-width:761px" cellspacing=3D"0" cellp=
+adding=3D"0" border=3D"0"><tbody>
+<tr>
+<td style=3D"vertical-align:top;width:55px">
+        <img src=3D"http://www.gravatar.com/avatar/42776c4982dff1fa45ee8248=
+532f8ad0.png?s=3D45&amp;r=3Dpg&amp;d=3Didenticon" title=3D"Neil" style=3D"m=
+ax-width:694px" width=3D"45" height=3D"45">
+</td>
+      <td>
+        <a href=3D"http://meta.discourse.org/users/neil" style=3D"font-size=
+:13px;font-family:&#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif;c=
+olor:#3b5998;text-decoration:none;font-weight:bold" target=3D"_blank">Neil<=
+/a><br>
+<span style=3D"text-align:right;color:#999999;padding-right:5px;font-family=
+:&#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif;font-size:11px">No=
+vember 19</span>
+      </td>
+    </tr>
+<tr>
+<td style=3D"padding-top:5px" colspan=3D"2">
+<p style=3D"margin-top:0">Actually, deleting a spammer does what it&#39;s s=
+upposed to. It does mark the topic as deleted.</p>
+
+<p style=3D"margin-top:0">That topic has id 11002, and you&#39;re right tha=
+t the user was deleted.</p>
+
+<p style=3D"margin-top:0"><a href=3D"http://users/eviltrout" target=3D"_bla=
+nk">@eviltrout</a> Any idea why it showed up in suggested topics? </p>
+</td>
+    </tr>
+</tbody></table>
+<div style=3D"color:#666">
+    <p>To respond, reply to this email or visit <a href=3D"http://meta.disc=
+ourse.org/t/spam-post-pops-back-up-in-suggested-topics/11005/5" style=3D"co=
+lor:#666" target=3D"_blank">http://meta.discourse.org/t/spam-post-pops-back=
+-up-in-suggested-topics/11005/5</a> in your browser.</p>
+
+  </div>
+  <hr style=3D"background-color:#ddd;min-height:1px;border:1px">
+<h4>Previous Replies</h4>
+
+  <table style=3D"margin-bottom:25px;max-width:761px" cellspacing=3D"0" cel=
+lpadding=3D"0" border=3D"0"><tbody>
+<tr>
+<td style=3D"vertical-align:top;width:55px">
+        <img src=3D"http://www.gravatar.com/avatar/42776c4982dff1fa45ee8248=
+532f8ad0.png?s=3D45&amp;r=3Dpg&amp;d=3Didenticon" title=3D"Neil" style=3D"m=
+ax-width:694px" width=3D"45" height=3D"45">
+</td>
+      <td>
+        <a href=3D"http://meta.discourse.org/users/neil" style=3D"font-size=
+:13px;font-family:&#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif;c=
+olor:#3b5998;text-decoration:none;font-weight:bold" target=3D"_blank">Neil<=
+/a><br>
+<span style=3D"text-align:right;color:#999999;padding-right:5px;font-family=
+:&#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif;font-size:11px">No=
+vember 19</span>
+      </td>
+    </tr>
+<tr>
+<td style=3D"padding-top:5px" colspan=3D"2"><p style=3D"margin-top:0">Looks=
+ like a bug when deleting a spammer. I&#39;ll look at it.</p></td>
+    </tr>
+</tbody></table>
+<table style=3D"margin-bottom:25px;max-width:761px" cellspacing=3D"0" cellp=
+adding=3D"0" border=3D"0"><tbody>
+<tr>
+<td style=3D"vertical-align:top;width:55px">
+        <img src=3D"http://www.gravatar.com/avatar/5120fc4e345db0d1a9648882=
+72073819.png?s=3D45&amp;r=3Dpg&amp;d=3Didenticon" title=3D"riking" style=3D=
+"max-width:694px" width=3D"45" height=3D"45">
+</td>
+      <td>
+        <a href=3D"http://meta.discourse.org/users/riking" style=3D"font-si=
+ze:13px;font-family:&#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif=
+;color:#3b5998;text-decoration:none;font-weight:bold" target=3D"_blank">rik=
+ing</a><br>
+<span style=3D"text-align:right;color:#999999;padding-right:5px;font-family=
+:&#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif;font-size:11px">No=
+vember 19</span>
+      </td>
+    </tr>
+<tr>
+<td style=3D"padding-top:5px" colspan=3D"2">
+<p style=3D"margin-top:0"><u></u></p><div>
+<div></div>
+<img width=3D"20" height=3D"20" src=3D"http://www.gravatar.com/avatar/51d62=
+3f33f8b83095db84ff35e15dbe8.png?s=3D40&amp;r=3Dpg&amp;d=3Didenticon" style=
+=3D"max-width:694px">codinghorror:</div>
+<blockquote><p style=3D"margin-top:0">I can&#39;t even find that topic by n=
+ame.</p></blockquote><u></u><p></p>
+
+<p style=3D"margin-top:0">In that case, I&#39;m fairly certain someone used=
+ the &#39;Delete Spammer&#39; function on the user, which would explain you=
+r inability to find it - it&#39;s gone.</p>
+
+<p style=3D"margin-top:0">I&#39;m raising this because, well, it&#39;s gone=
+ and shouldn&#39;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.</p>
+</td>
+    </tr>
+</tbody></table>
+<table style=3D"margin-bottom:25px;max-width:761px" cellspacing=3D"0" cellp=
+adding=3D"0" border=3D"0"><tbody>
+<tr>
+<td style=3D"vertical-align:top;width:55px">
+        <img src=3D"http://www.gravatar.com/avatar/51d623f33f8b83095db84ff3=
+5e15dbe8.png?s=3D45&amp;r=3Dpg&amp;d=3Didenticon" title=3D"codinghorror" st=
+yle=3D"max-width:694px" width=3D"45" height=3D"45">
+</td>
+      <td>
+        <a href=3D"http://meta.discourse.org/users/codinghorror" style=3D"f=
+ont-size:13px;font-family:&#39;lucida grande&#39;,tahoma,verdana,arial,sans=
+-serif;color:#3b5998;text-decoration:none;font-weight:bold" target=3D"_blan=
+k">codinghorror</a><br>
+<span style=3D"text-align:right;color:#999999;padding-right:5px;font-family=
+:&#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif;font-size:11px">No=
+vember 19</span>
+      </td>
+    </tr>
+<tr>
+<td style=3D"padding-top:5px" colspan=3D"2"><p style=3D"margin-top:0">Hmm, =
+that&#39;s interesting -- can you have a look <a href=3D"http://users/evilt=
+rout" target=3D"_blank">@eviltrout</a>? I can&#39;t even find that topic by=
+ name. </p>
+</td>
+    </tr>
+</tbody></table>
+<table style=3D"margin-bottom:25px;max-width:761px" cellspacing=3D"0" cellp=
+adding=3D"0" border=3D"0"><tbody>
+<tr>
+<td style=3D"vertical-align:top;width:55px">
+        <img src=3D"http://www.gravatar.com/avatar/5120fc4e345db0d1a9648882=
+72073819.png?s=3D45&amp;r=3Dpg&amp;d=3Didenticon" title=3D"riking" style=3D=
+"max-width:694px" width=3D"45" height=3D"45">
+</td>
+      <td>
+        <a href=3D"http://meta.discourse.org/users/riking" style=3D"font-si=
+ze:13px;font-family:&#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif=
+;color:#3b5998;text-decoration:none;font-weight:bold" target=3D"_blank">rik=
+ing</a><br>
+<span style=3D"text-align:right;color:#999999;padding-right:5px;font-family=
+:&#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif;font-size:11px">No=
+vember 19</span>
+      </td>
+    </tr>
+<tr>
+<td style=3D"padding-top:5px" colspan=3D"2">
+<p style=3D"margin-top:0">I&#39;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:</p>
+
+<p style=3D"margin-top:0"></p>
+<div><a href=3D"//cdn.discourse.org/uploads/meta_discourse/2158/50b8b49557c=
+b249e.png" target=3D"_blank"><img src=3D"http://cdn.discourse.org/uploads/m=
+eta_discourse/_optimized/ab1/c92/acd2c33402_584x134.png" width=3D"584" heig=
+ht=3D"134" style=3D"max-width:694px"><div>
+
+<span>Pasted image</span><span>1125x220 27.7 KB</span><span></span>
+</div></a></div>
+
+<p style=3D"margin-top:0">We may want to recheck the suppression on these.<=
+/p>
+</td>
+    </tr>
+</tbody></table>
+<hr style=3D"background-color:#ddd;min-height:1px;border:1px">
+<div style=3D"color:#666">
+<p>To respond, reply to this email or visit <a href=3D"http://meta.discours=
+e.org/t/spam-post-pops-back-up-in-suggested-topics/11005/5" style=3D"color:=
+#666" target=3D"_blank">http://meta.discourse.org/t/spam-post-pops-back-up-=
+in-suggested-topics/11005/5</a> in your browser.</p>
+
+</div>
+<div style=3D"color:#666">
+<p>To unsubscribe from these emails, visit your <a href=3D"http://meta.disc=
+ourse.org/user_preferences" style=3D"color:#666" target=3D"_blank">user pre=
+ferences</a>.</p>
+</div>
+</div></blockquote></div><br></div>
+
+--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 0000000000000000000000000000000000000000..7999c8d78b7da5d36b637aa0c34b2e4e0efac16c
--- /dev/null
+++ b/spec/fixtures/emails/auto_reply.eml
@@ -0,0 +1,21 @@
+Return-Path: <jake@adventuretime.ooo>
+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 <reply+59d8df8370b7e95c5a49fbf86aeb2c93@discourse.example.com>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <reply+59d8df8370b7e95c5a49fbf86aeb2c93@discourse.example.com>; 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 <jake@adventuretime.ooo>
+To: reply+636ca428858779856c226bb145ef4fad@appmail.adventuretime.ooo
+Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+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/dutch.eml b/spec/fixtures/emails/dutch.eml
new file mode 100644
index 0000000000000000000000000000000000000000..3142bf30c3b436eb734f350c63244b930486b048
--- /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: <walter.white@googlemail.com>
+References: <topic/5043@discourse.org> <51efeb9b36c34_66dc2dfce6811866@discourse.mail>
+From: Walter White <walter.white@googlemail.com>
+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 <reply+cd480e301683c9902891f15968bf07a5@appmail.adventuretime.ooo>
+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.
diff --git a/spec/fixtures/emails/gmail_web.eml b/spec/fixtures/emails/gmail_web.eml
new file mode 100644
index 0000000000000000000000000000000000000000..8bb83835711d6552f338daf8bd93a3922ce742a0
--- /dev/null
+++ b/spec/fixtures/emails/gmail_web.eml
@@ -0,0 +1,181 @@
+Delivered-To: reply@discourse.org
+Return-Path: <walter.white@googlemail.com>
+MIME-Version: 1.0
+In-Reply-To: <topic/22638/86406@meta.discourse.org>
+References: <topic/22638@meta.discourse.org>
+  <topic/22638/86406@meta.discourse.org>
+Date: Fri, 28 Nov 2014 12:36:49 -0800
+Subject: Re: [Discourse Meta] [Lounge] Testing default email replies
+From: Walter White <walter.white@googlemail.com>
+To: Discourse Meta <reply@discourse.org>
+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 <info@discourse.org> wrote:
+
+>     techAPJ <https://meta.discourse.org/users/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
+> <https://meta.discourse.org/users/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
+> <https://meta.discourse.org/my/preferences>.
+>
+
+--001a11c2e04e6544f30508f138ba
+Content-Type: text/html; charset=UTF-8
+Content-Transfer-Encoding: quoted-printable
+
+<div dir=3D"ltr"><div>### This is a reply from standard GMail in Google Chr=
+ome.</div><div><br></div><div>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</div><div><br></div><div>Here&#39;s some **bold** text=
+ in Markdown.</div><div><br></div><div>Here&#39;s a link <a href=3D"http://=
+example.com">http://example.com</a></div></div><div class=3D"gmail_extra"><=
+br><div class=3D"gmail_quote">On Fri, Nov 28, 2014 at 12:35 PM, Arpit Jalan=
+ <span dir=3D"ltr">&lt;<a href=3D"mailto:info@discourse.org" target=3D"_bla=
+nk">info@discourse.org</a>&gt;</span> wrote:<br><blockquote class=3D"gmail_=
+quote" style=3D"margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1=
+ex"><div>
+
+<table style=3D"margin-bottom:25px" cellspacing=3D"0" cellpadding=3D"0" bor=
+der=3D"0">
+  <tbody>
+    <tr>
+      <td style=3D"vertical-align:top;width:55px">
+        <img src=3D"https://meta-discourse.global.ssl.fastly.net/user_avata=
+r/meta.discourse.org/techapj/45/3281.png" title=3D"techAPJ" style=3D"max-wi=
+dth:100%" width=3D"45" height=3D"45">
+      </td>
+      <td>
+        <a href=3D"https://meta.discourse.org/users/techapj" style=3D"text-=
+decoration:none;font-weight:bold;color:#006699;font-size:13px;font-family:&=
+#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif;color:#3b5998;text-d=
+ecoration:none;font-weight:bold" target=3D"_blank">techAPJ</a><br>
+        <span style=3D"text-align:right;color:#999999;padding-right:5px;fon=
+t-family:&#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif;font-size:=
+11px">November 28</span>
+      </td>
+    </tr>
+    <tr>
+      <td style=3D"padding-top:5px" colspan=3D"2">
+<p style=3D"margin-top:0;border:0">Test reply.</p>
+
+<p style=3D"margin-top:0;border:0">First paragraph.</p>
+
+<p style=3D"margin-top:0;border:0">Second paragraph.</p>
+</td>
+    </tr>
+  </tbody>
+</table>
+
+
+  <div style=3D"color:#666">
+    <p>To respond, reply to this email or visit <a href=3D"https://meta.dis=
+course.org/t/testing-default-email-replies/22638/3" style=3D"text-decoratio=
+n:none;font-weight:bold;color:#006699;color:#666" target=3D"_blank">https:/=
+/meta.discourse.org/t/testing-default-email-replies/22638/3</a> in your bro=
+wser.</p>
+  </div>
+  <hr style=3D"background-color:#ddd;min-height:1px;border:1px;background-c=
+olor:#ddd;min-height:1px;border:1px">
+  <h4>Previous Replies</h4>
+
+  <table style=3D"margin-bottom:25px" cellspacing=3D"0" cellpadding=3D"0" b=
+order=3D"0">
+  <tbody>
+    <tr>
+      <td style=3D"vertical-align:top;width:55px">
+        <img src=3D"https://meta-discourse.global.ssl.fastly.net/user_avata=
+r/meta.discourse.org/codinghorror/45/5297.png" title=3D"codinghorror" style=
+=3D"max-width:100%" width=3D"45" height=3D"45">
+      </td>
+      <td>
+        <a href=3D"https://meta.discourse.org/users/codinghorror" style=3D"=
+text-decoration:none;font-weight:bold;color:#006699;font-size:13px;font-fam=
+ily:&#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif;color:#3b5998;t=
+ext-decoration:none;font-weight:bold" target=3D"_blank">codinghorror</a><br=
+>
+        <span style=3D"text-align:right;color:#999999;padding-right:5px;fon=
+t-family:&#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif;font-size:=
+11px">November 28</span>
+      </td>
+    </tr>
+    <tr>
+      <td style=3D"padding-top:5px" colspan=3D"2">
+<p style=3D"margin-top:0;border:0">We&#39;re testing the latest GitHub emai=
+l processing library which we are integrating now.</p>
+
+<p style=3D"margin-top:0;border:0"><a href=3D"https://github.com/github/ema=
+il_reply_parser" style=3D"text-decoration:none;font-weight:bold;color:#0066=
+99" target=3D"_blank">https://github.com/github/email_reply_parser</a></p>
+
+<p style=3D"margin-top:0;border:0">Go ahead and reply to this topic and I&#=
+39;ll reply from various email clients for testing.</p>
+</td>
+    </tr>
+  </tbody>
+</table>
+
+
+<hr style=3D"background-color:#ddd;min-height:1px;border:1px;background-col=
+or:#ddd;min-height:1px;border:1px">
+
+<div style=3D"color:#666">
+<p>To respond, reply to this email or visit <a href=3D"https://meta.discour=
+se.org/t/testing-default-email-replies/22638/3" style=3D"text-decoration:no=
+ne;font-weight:bold;color:#006699;color:#666" target=3D"_blank">https://met=
+a.discourse.org/t/testing-default-email-replies/22638/3</a> in your browser=
+.</p>
+</div>
+<div style=3D"color:#666">
+<p>To unsubscribe from these emails, visit your <a href=3D"https://meta.dis=
+course.org/my/preferences" style=3D"text-decoration:none;font-weight:bold;c=
+olor:#006699;color:#666" target=3D"_blank">user preferences</a>.</p>
+</div>
+</div>
+</blockquote></div><br></div>
+
+--001a11c2e04e6544f30508f138ba--
diff --git a/spec/fixtures/emails/html_paragraphs.eml b/spec/fixtures/emails/html_paragraphs.eml
new file mode 100644
index 0000000000000000000000000000000000000000..3fe37fb8b176d28ab0a46286201b020b93549750
--- /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: <topic/35@discourse.techapj.com>
+  <5434c8b52bb3a_623ff09fec70f049749@discourse-app.mail>
+Date: Wed, 8 Oct 2014 10:47:17 +0530
+Delivered-To: arpit@techapj.com
+Message-ID: <CAOJeqne=SJ_LwN4sb-0Y95ejc2OpreVhdmcPn0TnmwSvTCYzzQ@mail.gmail.com>
+Subject: Re: [Discourse] [Meta] Welcome to techAPJ's Discourse!
+From: Arpit Jalan <arpit@techapj.com>
+To: Discourse <mail+e1c7f2a380e33840aeb654f075490bad@arpitjalan.com>
+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 <info@unconfigured.discourse.org>
+wrote:
+
+>     ajalan
+> <http://mandrillapp.com/track/click/30081177/discourse.techapj.com?p=3Dey=
+JzIjoiVXgxTTZ3eHpuRWF2QXVoZGRJZVN5MWI0WnhrIiwidiI6MSwicCI6IntcInVcIjozMDA4M=
+TE3NyxcInZcIjoxLFwidXJsXCI6XCJodHRwOlxcXC9cXFwvZGlzY291cnNlLnRlY2hhcGouY29t=
+XFxcL3VzZXJzXFxcL2FqYWxhblwiLFwiaWRcIjpcIjgyNWI5MDYzZWNmMDRkMjk5OTE4Nzk1MmU=
+5YjY2YjE3XCIsXCJ1cmxfaWRzXCI6W1wiNzA3MTNjNTg4MDI3YWQyM2RiM2QwOTVhOGQwYmY4ZT=
+YyMzNjYThiMFwiXX0ifQ>
+> 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
+> <http://mandrillapp.com/track/click/30081177/discourse.techapj.com?p=3Dey=
+JzIjoid1IyWnVqVGRPU2RwLUlFR0Q5QnI1a203eVNjIiwidiI6MSwicCI6IntcInVcIjozMDA4M=
+TE3NyxcInZcIjoxLFwidXJsXCI6XCJodHRwOlxcXC9cXFwvZGlzY291cnNlLnRlY2hhcGouY29t=
+XFxcL3RcXFwvd2VsY29tZS10by10ZWNoYXBqcy1kaXNjb3Vyc2VcXFwvMzVcXFwvMlwiLFwiaWR=
+cIjpcIjgyNWI5MDYzZWNmMDRkMjk5OTE4Nzk1MmU5YjY2YjE3XCIsXCJ1cmxfaWRzXCI6W1wiY2=
+RkYzFlZjc5OThhNzE1ODA4Yjg0MGFlNzVlZmNiYmYzYmViODk4Y1wiXX0ifQ>
+> in your browser.
+>  ------------------------------
+> Previous Replies    techAPJ
+> <http://mandrillapp.com/track/click/30081177/discourse.techapj.com?p=3Dey=
+JzIjoia2x3LUxac2RSX25uWEFYYWcwVDVha3pIY3RjIiwidiI6MSwicCI6IntcInVcIjozMDA4M=
+TE3NyxcInZcIjoxLFwidXJsXCI6XCJodHRwOlxcXC9cXFwvZGlzY291cnNlLnRlY2hhcGouY29t=
+XFxcL3VzZXJzXFxcL3RlY2hhcGpcIixcImlkXCI6XCI4MjViOTA2M2VjZjA0ZDI5OTkxODc5NTJ=
+lOWI2NmIxN1wiLFwidXJsX2lkc1wiOltcIjk2ZjAyMzVhNmM2NzIyNmU1NjhhMzU1NDE1OTAxNz=
+AyYTkxNjM1NzJcIl19In0>
+> 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
+> <http://mandrillapp.com/track/click/30081177/discourse.techapj.com?p=3Dey=
+JzIjoid1IyWnVqVGRPU2RwLUlFR0Q5QnI1a203eVNjIiwidiI6MSwicCI6IntcInVcIjozMDA4M=
+TE3NyxcInZcIjoxLFwidXJsXCI6XCJodHRwOlxcXC9cXFwvZGlzY291cnNlLnRlY2hhcGouY29t=
+XFxcL3RcXFwvd2VsY29tZS10by10ZWNoYXBqcy1kaXNjb3Vyc2VcXFwvMzVcXFwvMlwiLFwiaWR=
+cIjpcIjgyNWI5MDYzZWNmMDRkMjk5OTE4Nzk1MmU5YjY2YjE3XCIsXCJ1cmxfaWRzXCI6W1wiY2=
+RkYzFlZjc5OThhNzE1ODA4Yjg0MGFlNzVlZmNiYmYzYmViODk4Y1wiXX0ifQ>
+> in your browser.
+>
+> To unsubscribe from these emails, visit your user preferences
+> <http://mandrillapp.com/track/click/30081177/discourse.techapj.com?p=3Dey=
+JzIjoiVTNudkpobl9lUUl0cmdsVVRrcm5iaHpyN0JZIiwidiI6MSwicCI6IntcInVcIjozMDA4M=
+TE3NyxcInZcIjoxLFwidXJsXCI6XCJodHRwOlxcXC9cXFwvZGlzY291cnNlLnRlY2hhcGouY29t=
+XFxcL215XFxcL3ByZWZlcmVuY2VzXCIsXCJpZFwiOlwiODI1YjkwNjNlY2YwNGQyOTk5MTg3OTU=
+yZTliNjZiMTdcIixcInVybF9pZHNcIjpbXCI0OTIyMmMyZDgyNzUwMmQyMGZjYzU4MTZkNjhmYT=
+k3NzFkY2YzZDllXCJdfSJ9>
+> .
+>
+
+--001a114119d8f4e46e0504e26d5b
+Content-Type: text/html; charset=UTF-8
+Content-Transfer-Encoding: quoted-printable
+
+<div dir=3D"ltr">Awesome!<div><br></div><div>Pleasure to have you here!</di=
+v><div><br></div><div>:boom:</div></div><div class=3D"gmail_extra"><br><div=
+ class=3D"gmail_quote">On Wed, Oct 8, 2014 at 10:46 AM, ajalan <span dir=3D=
+"ltr">&lt;<a href=3D"mailto:info@unconfigured.discourse.org" target=3D"_bla=
+nk">info@unconfigured.discourse.org</a>&gt;</span> wrote:<br><blockquote cl=
+ass=3D"gmail_quote" style=3D"margin:0 0 0 .8ex;border-left:1px #ccc solid;p=
+adding-left:1ex"><div>
+
+<table style=3D"margin-bottom:25px;max-width:761px" cellspacing=3D"0" cellp=
+adding=3D"0" border=3D"0">
+  <tbody>
+    <tr>
+      <td style=3D"vertical-align:top;width:55px">
+        <img src=3D"http://discourse.techapj.com/user_avatar/discourse.tech=
+apj.com/ajalan/45/35.png" title=3D"ajalan" style=3D"max-width:694px" width=
+=3D"45" height=3D"45">
+      </td>
+      <td>
+        <a href=3D"http://mandrillapp.com/track/click/30081177/discourse.te=
+chapj.com?p=3DeyJzIjoiVXgxTTZ3eHpuRWF2QXVoZGRJZVN5MWI0WnhrIiwidiI6MSwicCI6I=
+ntcInVcIjozMDA4MTE3NyxcInZcIjoxLFwidXJsXCI6XCJodHRwOlxcXC9cXFwvZGlzY291cnNl=
+LnRlY2hhcGouY29tXFxcL3VzZXJzXFxcL2FqYWxhblwiLFwiaWRcIjpcIjgyNWI5MDYzZWNmMDR=
+kMjk5OTE4Nzk1MmU5YjY2YjE3XCIsXCJ1cmxfaWRzXCI6W1wiNzA3MTNjNTg4MDI3YWQyM2RiM2=
+QwOTVhOGQwYmY4ZTYyMzNjYThiMFwiXX0ifQ" style=3D"font-size:13px;font-family:&=
+#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif;color:#3b5998;text-d=
+ecoration:none;font-weight:bold;text-decoration:none;font-weight:bold;color=
+:#006699" target=3D"_blank">ajalan</a><br>
+        <span style=3D"text-align:right;color:#999999;padding-right:5px;fon=
+t-family:&#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif;font-size:=
+11px">October 8</span>
+      </td>
+    </tr>
+    <tr>
+      <td style=3D"padding-top:5px" colspan=3D"2"><p style=3D"margin-top:0;=
+border:0">Nice to be here! Thanks! <img src=3D"http://discourse.techapj.com=
+/plugins/emoji/images/smile.png" title=3D":smile:" alt=3D"smile" width=3D"2=
+0" height=3D"20"></p></td>
+    </tr>
+  </tbody>
+</table>
+
+
+  <div style=3D"color:#666">
+    <p>To respond, reply to this email or visit <a href=3D"http://mandrilla=
+pp.com/track/click/30081177/discourse.techapj.com?p=3DeyJzIjoid1IyWnVqVGRPU=
+2RwLUlFR0Q5QnI1a203eVNjIiwidiI6MSwicCI6IntcInVcIjozMDA4MTE3NyxcInZcIjoxLFwi=
+dXJsXCI6XCJodHRwOlxcXC9cXFwvZGlzY291cnNlLnRlY2hhcGouY29tXFxcL3RcXFwvd2VsY29=
+tZS10by10ZWNoYXBqcy1kaXNjb3Vyc2VcXFwvMzVcXFwvMlwiLFwiaWRcIjpcIjgyNWI5MDYzZW=
+NmMDRkMjk5OTE4Nzk1MmU5YjY2YjE3XCIsXCJ1cmxfaWRzXCI6W1wiY2RkYzFlZjc5OThhNzE1O=
+DA4Yjg0MGFlNzVlZmNiYmYzYmViODk4Y1wiXX0ifQ" style=3D"color:#666;text-decorat=
+ion:none;font-weight:bold;color:#006699" target=3D"_blank">http://discourse=
+.techapj.com/t/welcome-to-techapjs-discourse/35/2</a> in your browser.</p>
+  </div>
+  <hr style=3D"background-color:#ddd;min-height:1px;border:1px;background-c=
+olor:#ddd;min-height:1px;border:1px">
+  <h4>Previous Replies</h4>
+
+  <table style=3D"margin-bottom:25px;max-width:761px" cellspacing=3D"0" cel=
+lpadding=3D"0" border=3D"0">
+  <tbody>
+    <tr>
+      <td style=3D"vertical-align:top;width:55px">
+        <img src=3D"http://discourse.techapj.com/user_avatar/discourse.tech=
+apj.com/techapj/45/34.png" title=3D"techAPJ" style=3D"max-width:694px" widt=
+h=3D"45" height=3D"45">
+      </td>
+      <td>
+        <a href=3D"http://mandrillapp.com/track/click/30081177/discourse.te=
+chapj.com?p=3DeyJzIjoia2x3LUxac2RSX25uWEFYYWcwVDVha3pIY3RjIiwidiI6MSwicCI6I=
+ntcInVcIjozMDA4MTE3NyxcInZcIjoxLFwidXJsXCI6XCJodHRwOlxcXC9cXFwvZGlzY291cnNl=
+LnRlY2hhcGouY29tXFxcL3VzZXJzXFxcL3RlY2hhcGpcIixcImlkXCI6XCI4MjViOTA2M2VjZjA=
+0ZDI5OTkxODc5NTJlOWI2NmIxN1wiLFwidXJsX2lkc1wiOltcIjk2ZjAyMzVhNmM2NzIyNmU1Nj=
+hhMzU1NDE1OTAxNzAyYTkxNjM1NzJcIl19In0" style=3D"font-size:13px;font-family:=
+&#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif;color:#3b5998;text-=
+decoration:none;font-weight:bold;text-decoration:none;font-weight:bold;colo=
+r:#006699" target=3D"_blank">techAPJ</a><br>
+        <span style=3D"text-align:right;color:#999999;padding-right:5px;fon=
+t-family:&#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif;font-size:=
+11px">October 8</span>
+      </td>
+    </tr>
+    <tr>
+      <td style=3D"padding-top:5px" colspan=3D"2"><p style=3D"margin-top:0;=
+border:0">Welcome to techAPJ&#39;s Discourse!</p></td>
+    </tr>
+  </tbody>
+</table>
+
+
+<hr style=3D"background-color:#ddd;min-height:1px;border:1px;background-col=
+or:#ddd;min-height:1px;border:1px">
+
+<div style=3D"color:#666">
+<p>To respond, reply to this email or visit <a href=3D"http://mandrillapp.c=
+om/track/click/30081177/discourse.techapj.com?p=3DeyJzIjoid1IyWnVqVGRPU2RwL=
+UlFR0Q5QnI1a203eVNjIiwidiI6MSwicCI6IntcInVcIjozMDA4MTE3NyxcInZcIjoxLFwidXJs=
+XCI6XCJodHRwOlxcXC9cXFwvZGlzY291cnNlLnRlY2hhcGouY29tXFxcL3RcXFwvd2VsY29tZS1=
+0by10ZWNoYXBqcy1kaXNjb3Vyc2VcXFwvMzVcXFwvMlwiLFwiaWRcIjpcIjgyNWI5MDYzZWNmMD=
+RkMjk5OTE4Nzk1MmU5YjY2YjE3XCIsXCJ1cmxfaWRzXCI6W1wiY2RkYzFlZjc5OThhNzE1ODA4Y=
+jg0MGFlNzVlZmNiYmYzYmViODk4Y1wiXX0ifQ" style=3D"color:#666;text-decoration:=
+none;font-weight:bold;color:#006699" target=3D"_blank">http://discourse.tec=
+hapj.com/t/welcome-to-techapjs-discourse/35/2</a> in your browser.</p>
+</div><span class=3D"">
+<div style=3D"color:#666">
+<p>To unsubscribe from these emails, visit your <a href=3D"http://mandrilla=
+pp.com/track/click/30081177/discourse.techapj.com?p=3DeyJzIjoiVTNudkpobl9lU=
+Ul0cmdsVVRrcm5iaHpyN0JZIiwidiI6MSwicCI6IntcInVcIjozMDA4MTE3NyxcInZcIjoxLFwi=
+dXJsXCI6XCJodHRwOlxcXC9cXFwvZGlzY291cnNlLnRlY2hhcGouY29tXFxcL215XFxcL3ByZWZ=
+lcmVuY2VzXCIsXCJpZFwiOlwiODI1YjkwNjNlY2YwNGQyOTk5MTg3OTUyZTliNjZiMTdcIixcIn=
+VybF9pZHNcIjpbXCI0OTIyMmMyZDgyNzUwMmQyMGZjYzU4MTZkNjhmYTk3NzFkY2YzZDllXCJdf=
+SJ9" style=3D"color:#666;text-decoration:none;font-weight:bold;color:#00669=
+9" target=3D"_blank">user preferences</a>.</p>
+</div>
+</span></div>
+
+<img src=3D"http://mandrillapp.com/track/open.php?u=3D30081177&amp;id=3D825=
+b9063ecf04d2999187952e9b66b17" height=3D"1" width=3D"1"></blockquote></div>=
+<br></div>
+
+--001a114119d8f4e46e0504e26d5b--
diff --git a/spec/fixtures/emails/inline_reply.eml b/spec/fixtures/emails/inline_reply.eml
new file mode 100644
index 0000000000000000000000000000000000000000..39625a225da9fcfacfece749e28561c7267afec6
--- /dev/null
+++ b/spec/fixtures/emails/inline_reply.eml
@@ -0,0 +1,60 @@
+
+MIME-Version: 1.0
+In-Reply-To: <reply@discourse-app.mail>
+References: <topic/36@discourse.techapj.com>
+  <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 <walter.white@googlemail.com>
+To: Discourse <reply@mail.com>
+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 <info@unconfigured.discourse.org>
+wrote:
+
+>     techAPJ <https://meta.discourse.org/users/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
+> <https://meta.discourse.org/users/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
+> <https://meta.discourse.org/my/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 0000000000000000000000000000000000000000..8d4d58feb1685bbe2bcfedbdc0d57a6d87ad7e95
--- /dev/null
+++ b/spec/fixtures/emails/ios_default.eml
@@ -0,0 +1,136 @@
+Delivered-To: reply@discourse.org
+Return-Path: <walter.white@googlemail.com>
+From: Walter White <walter.white@googlemail.com>
+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: <topic/22638@meta.discourse.org> <topic/22638/86406@meta.discourse.org>
+In-Reply-To: <topic/22638/86406@meta.discourse.org>
+To: Discourse Meta <reply@discourse.org>
+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 <info@discourse.org> 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
+
+<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body dir="auto"><div>### this is a reply from iOS default mail</div><div><br></div><div>The quick brown fox jumps over the lazy dog.&nbsp;<span style="background-color: rgba(255, 255, 255, 0);">The quick brown fox jumps over the lazy dog.&nbsp;The quick brown fox jumps over the lazy dog.&nbsp;The quick brown fox jumps over the lazy dog.&nbsp;The quick brown fox jumps over the lazy dog.&nbsp;The quick brown fox jumps over the lazy dog.&nbsp;The quick brown fox jumps over the lazy dog.&nbsp;</span></div><div><br></div><div>Here's some **bold** markdown text.</div><div><br></div><div>Here's a link <a href="http://example.com">http://example.com</a><br><br></div><div><br>On Nov 28, 2014, at 12:35 PM, Arpit Jalan &lt;<a href="mailto:info@discourse.org">info@discourse.org</a>&gt; wrote:<br><br></div><blockquote type="cite"><div><div>
+
+<table style="margin-bottom:25px;" cellspacing="0" cellpadding="0" border="0">
+  <tbody>
+    <tr>
+      <td style="vertical-align:top;width:55px;">
+        <img src="https://meta-discourse.global.ssl.fastly.net/user_avatar/meta.discourse.org/techapj/45/3281.png" title="techAPJ" style="max-width:100%;" width="45" height="45">
+      </td>
+      <td>
+        <a href="https://meta.discourse.org/users/techapj" target="_blank" style="text-decoration: none; font-weight: bold; color: #006699;; font-size:13px;font-family:'lucida grande',tahoma,verdana,arial,sans-serif;color:#3b5998;text-decoration:none;font-weight:bold">techAPJ</a><br>
+        <span style="text-align:right;color:#999999;padding-right:5px;font-family:'lucida grande',tahoma,verdana,arial,sans-serif;font-size:11px">November 28</span>
+      </td>
+    </tr>
+    <tr>
+      <td style="padding-top:5px;" colspan="2">
+<p style="margin-top:0; border: 0;">Test reply.</p>
+
+<p style="margin-top:0; border: 0;">First paragraph.</p>
+
+<p style="margin-top:0; border: 0;">Second paragraph.</p>
+</td>
+    </tr>
+  </tbody>
+</table>
+
+
+  <div style="color:#666;">
+    <p>To respond, reply to this email or visit <a href="https://meta.discourse.org/t/testing-default-email-replies/22638/3" style="text-decoration: none; font-weight: bold; color: #006699;; color:#666;">https://meta.discourse.org/t/testing-default-email-replies/22638/3</a> in your browser.</p>
+  </div>
+  <hr style="background-color: #ddd; height: 1px; border: 1px;; background-color: #ddd; height: 1px; border: 1px;">
+  <h4>Previous Replies</h4>
+
+  <table style="margin-bottom:25px;" cellspacing="0" cellpadding="0" border="0">
+  <tbody>
+    <tr>
+      <td style="vertical-align:top;width:55px;">
+        <img src="https://meta-discourse.global.ssl.fastly.net/user_avatar/meta.discourse.org/codinghorror/45/5297.png" title="codinghorror" style="max-width:100%;" width="45" height="45">
+      </td>
+      <td>
+        <a href="https://meta.discourse.org/users/codinghorror" target="_blank" style="text-decoration: none; font-weight: bold; color: #006699;; font-size:13px;font-family:'lucida grande',tahoma,verdana,arial,sans-serif;color:#3b5998;text-decoration:none;font-weight:bold">codinghorror</a><br>
+        <span style="text-align:right;color:#999999;padding-right:5px;font-family:'lucida grande',tahoma,verdana,arial,sans-serif;font-size:11px">November 28</span>
+      </td>
+    </tr>
+    <tr>
+      <td style="padding-top:5px;" colspan="2">
+<p style="margin-top:0; border: 0;">We're testing the latest GitHub email processing library which we are integrating now.</p>
+
+<p style="margin-top:0; border: 0;"><a href="https://github.com/github/email_reply_parser" target="_blank" style="text-decoration: none; font-weight: bold; color: #006699;">https://github.com/github/email_reply_parser</a></p>
+
+<p style="margin-top:0; border: 0;">Go ahead and reply to this topic and I'll reply from various email clients for testing.</p>
+</td>
+    </tr>
+  </tbody>
+</table>
+
+
+<hr style="background-color: #ddd; height: 1px; border: 1px;; background-color: #ddd; height: 1px; border: 1px;">
+
+<div style="color:#666;">
+<p>To respond, reply to this email or visit <a href="https://meta.discourse.org/t/testing-default-email-replies/22638/3" style="text-decoration: none; font-weight: bold; color: #006699;; color:#666;">https://meta.discourse.org/t/testing-default-email-replies/22638/3</a> in your browser.</p>
+</div>
+<div style="color:#666;">
+<p>To unsubscribe from these emails, visit your <a href="https://meta.discourse.org/my/preferences" style="text-decoration: none; font-weight: bold; color: #006699;; color:#666;">user preferences</a>.</p>
+</div>
+</div>
+</div></blockquote></body></html>
+--Apple-Mail-B41C7F8E-3639-49B0-A5D5-440E125A7105--
diff --git a/spec/fixtures/emails/newlines.eml b/spec/fixtures/emails/newlines.eml
new file mode 100644
index 0000000000000000000000000000000000000000..cf03b9d18bcc966fd60979391c1dec0cbaaa78eb
--- /dev/null
+++ b/spec/fixtures/emails/newlines.eml
@@ -0,0 +1,84 @@
+In-Reply-To: <test@discourse-app.mail>
+Date: Wed, 8 Oct 2014 10:36:19 +0530
+Delivered-To: walter.white@googlemail.com
+Subject: Re: [Discourse] Welcome to Discourse
+From: Walter White <walter.white@googlemail.com>
+To: Discourse <mail@arpitjalan.com>
+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 <info@unconfigured.discourse.org>
+wrote:
+
+>     ajalan
+> <http://mandrillapp.com/track/click/30081177/discourse.techapj.com?p=3Dey=
+JzIjoiMGM3a1pGT250VG5sb242RVNTdFdjS1FUSHdzIiwidiI6MSwicCI6IntcInVcIjozMDA4M=
+TE3NyxcInZcIjoxLFwidXJsXCI6XCJodHRwOlxcXC9cXFwvZGlzY291cnNlLnRlY2hhcGouY29t=
+XFxcL3VzZXJzXFxcL2FqYWxhblwiLFwiaWRcIjpcImQxOWYxYjQ5NTdkODRkMGNhZWY1NDEzZGN=
+hODA4YTRhXCIsXCJ1cmxfaWRzXCI6W1wiNzA3MTNjNTg4MDI3YWQyM2RiM2QwOTVhOGQwYmY4ZT=
+YyMzNjYThiMFwiXX0ifQ>
+> 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
+> <http://mandrillapp.com/track/click/30081177/discourse.techapj.com?p=3Dey=
+JzIjoibzNWaXFDRDdxSFNCbVRkUmdONlRJVW1ENU8wIiwidiI6MSwicCI6IntcInVcIjozMDA4M=
+TE3NyxcInZcIjoxLFwidXJsXCI6XCJodHRwOlxcXC9cXFwvZGlzY291cnNlLnRlY2hhcGouY29t=
+XFxcL3RcXFwvd2VsY29tZS10by1kaXNjb3Vyc2VcXFwvOFxcXC8yXCIsXCJpZFwiOlwiZDE5ZjF=
+iNDk1N2Q4NGQwY2FlZjU0MTNkY2E4MDhhNGFcIixcInVybF9pZHNcIjpbXCIwYmFkNjE2NDJkNm=
+M2NzJhNGU0ZjYzMGU2ZDA5M2I3MzU3NzQ4MzYxXCJdfSJ9>
+> in your browser.
+>  ------------------------------
+> Previous Replies    system
+> <http://mandrillapp.com/track/click/30081177/discourse.techapj.com?p=3Dey=
+JzIjoicjFZQm8ySTJjUEtNclpvekZ5ZmFqYmdpTVFNIiwidiI6MSwicCI6IntcInVcIjozMDA4M=
+TE3NyxcInZcIjoxLFwidXJsXCI6XCJodHRwOlxcXC9cXFwvZGlzY291cnNlLnRlY2hhcGouY29t=
+XFxcL3VzZXJzXFxcL3N5c3RlbVwiLFwiaWRcIjpcImQxOWYxYjQ5NTdkODRkMGNhZWY1NDEzZGN=
+hODA4YTRhXCIsXCJ1cmxfaWRzXCI6W1wiMTcxNWU2OTE1M2UzMjk4YmM2Y2NhMWEyM2E5N2ViMW=
+U5N2IwMWYyNFwiXX0ifQ>
+> 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
+> <http://mandrillapp.com/track/click/30081177/discourse.techapj.com?p=3Dey=
+JzIjoibzNWaXFDRDdxSFNCbVRkUmdONlRJVW1ENU8wIiwidiI6MSwicCI6IntcInVcIjozMDA4M=
+TE3NyxcInZcIjoxLFwidXJsXCI6XCJodHRwOlxcXC9cXFwvZGlzY291cnNlLnRlY2hhcGouY29t=
+XFxcL3RcXFwvd2VsY29tZS10by1kaXNjb3Vyc2VcXFwvOFxcXC8yXCIsXCJpZFwiOlwiZDE5ZjF=
+iNDk1N2Q4NGQwY2FlZjU0MTNkY2E4MDhhNGFcIixcInVybF9pZHNcIjpbXCIwYmFkNjE2NDJkNm=
+M2NzJhNGU0ZjYzMGU2ZDA5M2I3MzU3NzQ4MzYxXCJdfSJ9>
+> in your browser.
+>
+> To unsubscribe from these emails, visit your user preferences
+> <http://mandrillapp.com/track/click/30081177/discourse.techapj.com?p=3Dey=
+JzIjoiaFdWSWtiRGIybjJOeWc0VHRrenAzbnhraU93IiwidiI6MSwicCI6IntcInVcIjozMDA4M=
+TE3NyxcInZcIjoxLFwidXJsXCI6XCJodHRwOlxcXC9cXFwvZGlzY291cnNlLnRlY2hhcGouY29t=
+XFxcL215XFxcL3ByZWZlcmVuY2VzXCIsXCJpZFwiOlwiZDE5ZjFiNDk1N2Q4NGQwY2FlZjU0MTN=
+kY2E4MDhhNGFcIixcInVybF9pZHNcIjpbXCI0OTIyMmMyZDgyNzUwMmQyMGZjYzU4MTZkNjhmYT=
+k3NzFkY2YzZDllXCJdfSJ9>
+> .
+>
+
+--bcaec554078cc3d0c10504e24661
diff --git a/spec/fixtures/emails/no_content_reply.eml b/spec/fixtures/emails/no_content_reply.eml
new file mode 100644
index 0000000000000000000000000000000000000000..95eb2055ce69c8151f2a7b9e282784f800d1939b
--- /dev/null
+++ b/spec/fixtures/emails/no_content_reply.eml
@@ -0,0 +1,34 @@
+Return-Path: <jake@adventuretime.ooo>
+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 <reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo>; 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 <jake@adventuretime.ooo>
+To: reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo
+Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+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
+<reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo> 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 0000000000000000000000000000000000000000..feb59bd27bb38a37b69379bd3a06468fe00b347b
--- /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: <topic/18058@meta.discourse.org>
+	<540f16d4c08d9_4a3f9ff6d61890391c@tiefighter4-meta.mail>
+Date: Tue, 9 Sep 2014 16:18:19 -0700
+Delivered-To: kanepyork@gmail.com
+Message-ID: <CABeNrKXxfb8YJUWxO5L_oPTGrFsiZfQOpWudk+44Mh=yuUEHNQ@mail.gmail.com>
+Subject: Re: [Discourse Meta] Badge icons - where to find them?
+From: Kane York <jake@adventuretime.ooo>
+To: Discourse Meta <reply+8305e3604ae4d1485dc12b6af6a8446c@appmail.adventuretime.ooo>
+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 <info@discourse.org> wrote:
+
+>     gordon_ryan <https://meta.discourse.org/users/gordon_ryan>
+> September 9
+>
+> @riking <https://meta.discourse.org/users/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 <https://meta.discourse.org/users/riking>
+> July 25
+>
+> Check out the "HTML Head" section in the "Content" tab of the admin panel=
+.
+>      meglio <https://meta.discourse.org/users/meglio>
+> July 25
+>
+> How will it load the related custom font?
+>      riking <https://meta.discourse.org/users/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 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
+> <https://meta.discourse.org/my/preferences>.
+>
+
+--001a11c34c389e728f0502aa26a0
+Content-Type: text/html; charset=UTF-8
+Content-Transfer-Encoding: quoted-printable
+
+<div dir=3D"ltr"><span style=3D"font-family:arial,sans-serif;font-size:13px=
+">Sure, all you need to do is frobnicate the foobar and you&#39;ll be all s=
+et!</span><br><div class=3D"gmail_extra"><br clear=3D"all"><div><br>=
+<br><div class=3D"gmail_quote">On Tue, Sep 9, 2014 at 8:03 AM, gordon_ryan =
+<span dir=3D"ltr">&lt;<a href=3D"mailto:info@discourse.org" target=3D"_blan=
+k">info@discourse.org</a>&gt;</span> wrote:<br><blockquote class=3D"gmail_q=
+uote" style=3D"margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1e=
+x"><div>
+
+
+<table style=3D"margin-bottom:25px;max-width:761px" cellspacing=3D"0" cellp=
+adding=3D"0" border=3D"0">
+  <tbody>
+    <tr>
+      <td style=3D"vertical-align:top;width:55px">
+        <img src=3D"https://meta-discourse.global.ssl.fastly.net/user_avata=
+r/meta.discourse.org/gordon_ryan/45/34017.png" title=3D"gordon_ryan" style=
+=3D"max-width:694px" width=3D"45" height=3D"45">
+      </td>
+      <td>
+        <a href=3D"https://meta.discourse.org/users/gordon_ryan" style=3D"f=
+ont-size:13px;font-family:&#39;lucida grande&#39;,tahoma,verdana,arial,sans=
+-serif;color:#3b5998;text-decoration:none;font-weight:bold;text-decoration:=
+none;font-weight:bold;color:#006699" target=3D"_blank">gordon_ryan</a><br>
+        <span style=3D"text-align:right;color:#999999;padding-right:5px;fon=
+t-family:&#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif;font-size:=
+11px">September 9</span>
+      </td>
+    </tr>
+    <tr>
+      <td style=3D"padding-top:5px" colspan=3D"2">
+<p style=3D"margin-top:0;border:0"><a href=3D"https://meta.discourse.org/us=
+ers/riking" style=3D"text-decoration:none;font-weight:bold;color:#006699" t=
+arget=3D"_blank">@riking</a>- willing to step by step of the custom icon me=
+thod for an admittedly ignorant admin? Seriously confused.</p>
+
+<p style=3D"margin-top:0;border:0">Or anyone else who knows how to do this =
+<img src=3D"https://meta-discourse.global.ssl.fastly.net/plugins/emoji/imag=
+es/smiley.png" title=3D":smiley:" alt=3D"smiley" width=3D"20" height=3D"20"=
+></p>
+</td>
+    </tr>
+  </tbody>
+</table>
+
+
+  <div style=3D"color:#666">
+    <p>To respond, reply to this email or visit <a href=3D"https://meta.dis=
+course.org/t/badge-icons-where-to-find-them/18058/9" style=3D"color:#666;te=
+xt-decoration:none;font-weight:bold;color:#006699" target=3D"_blank">https:=
+//meta.discourse.org/t/badge-icons-where-to-find-them/18058/9</a> in your b=
+rowser.</p>
+  </div>
+  <hr style=3D"background-color:#ddd;min-height:1px;border:1px;background-c=
+olor:#ddd;min-height:1px;border:1px">
+  <h4>Previous Replies</h4>
+
+  <table style=3D"margin-bottom:25px;max-width:761px" cellspacing=3D"0" cel=
+lpadding=3D"0" border=3D"0">
+  <tbody>
+    <tr>
+      <td style=3D"vertical-align:top;width:55px">
+        <img src=3D"https://meta-discourse.global.ssl.fastly.net/user_avata=
+r/meta.discourse.org/riking/45/9779.png" title=3D"riking" style=3D"max-widt=
+h:694px" width=3D"45" height=3D"45">
+      </td>
+      <td>
+        <a href=3D"https://meta.discourse.org/users/riking" style=3D"font-s=
+ize:13px;font-family:&#39;lucida grande&#39;,tahoma,verdana,arial,sans-seri=
+f;color:#3b5998;text-decoration:none;font-weight:bold;text-decoration:none;=
+font-weight:bold;color:#006699" target=3D"_blank">riking</a><br>
+        <span style=3D"text-align:right;color:#999999;padding-right:5px;fon=
+t-family:&#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif;font-size:=
+11px">July 25</span>
+      </td>
+    </tr>
+    <tr>
+      <td style=3D"padding-top:5px" colspan=3D"2"><p style=3D"margin-top:0;=
+border:0">Check out the &quot;HTML Head&quot; section in the &quot;Content&=
+quot; tab of the admin panel.</p></td>
+    </tr>
+  </tbody>
+</table>
+
+  <table style=3D"margin-bottom:25px;max-width:761px" cellspacing=3D"0" cel=
+lpadding=3D"0" border=3D"0">
+  <tbody>
+    <tr>
+      <td style=3D"vertical-align:top;width:55px">
+        <img src=3D"https://meta-discourse.global.ssl.fastly.net/user_avata=
+r/meta.discourse.org/meglio/45/33480.png" title=3D"meglio" style=3D"max-wid=
+th:694px" width=3D"45" height=3D"45">
+      </td>
+      <td>
+        <a href=3D"https://meta.discourse.org/users/meglio" style=3D"font-s=
+ize:13px;font-family:&#39;lucida grande&#39;,tahoma,verdana,arial,sans-seri=
+f;color:#3b5998;text-decoration:none;font-weight:bold;text-decoration:none;=
+font-weight:bold;color:#006699" target=3D"_blank">meglio</a><br>
+        <span style=3D"text-align:right;color:#999999;padding-right:5px;fon=
+t-family:&#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif;font-size:=
+11px">July 25</span>
+      </td>
+    </tr>
+    <tr>
+      <td style=3D"padding-top:5px" colspan=3D"2"><p style=3D"margin-top:0;=
+border:0">How will it load the related custom font?</p></td>
+    </tr>
+  </tbody>
+</table>
+
+  <table style=3D"margin-bottom:25px;max-width:761px" cellspacing=3D"0" cel=
+lpadding=3D"0" border=3D"0">
+  <tbody>
+    <tr>
+      <td style=3D"vertical-align:top;width:55px">
+        <img src=3D"https://meta-discourse.global.ssl.fastly.net/user_avata=
+r/meta.discourse.org/riking/45/9779.png" title=3D"riking" style=3D"max-widt=
+h:694px" width=3D"45" height=3D"45">
+      </td>
+      <td>
+        <a href=3D"https://meta.discourse.org/users/riking" style=3D"font-s=
+ize:13px;font-family:&#39;lucida grande&#39;,tahoma,verdana,arial,sans-seri=
+f;color:#3b5998;text-decoration:none;font-weight:bold;text-decoration:none;=
+font-weight:bold;color:#006699" target=3D"_blank">riking</a><br>
+        <span style=3D"text-align:right;color:#999999;padding-right:5px;fon=
+t-family:&#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif;font-size:=
+11px">July 25</span>
+      </td>
+    </tr>
+    <tr>
+      <td style=3D"padding-top:5px" colspan=3D"2">
+<p style=3D"margin-top:0;border:0">Here&#39;s an example of the styles that=
+ FA applies. I&#39;ll use <code style=3D"background-color:#f1f1ff;padding:2=
+px 5px">&lt;i class=3D&quot;fa fa-heart&quot;&gt;&lt;/i&gt;</code> as the e=
+xample.</p>
+
+<p style=3D"margin-top:0;border:0"></p>
+<pre style=3D"word-wrap:break-word;max-width:694px"><code style=3D"backgrou=
+nd-color:#f1f1ff;padding:2px 5px;display:block;background-color:#f1f1ff;pad=
+ding:5px">.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: &quot;\f004&quot;;
+}</code></pre>
+
+<p style=3D"margin-top:0;border:0">So you could do this in your site styles=
+heet:</p>
+
+<p style=3D"margin-top:0;border:0"></p>
+<pre style=3D"word-wrap:break-word;max-width:694px"><code style=3D"backgrou=
+nd-color:#f1f1ff;padding:2px 5px;display:block;background-color:#f1f1ff;pad=
+ding:5px">.fa-custom-burger:before {
+  content: &quot;\01f354&quot;;
+  font-family: inherit;
+}</code></pre>
+
+<p style=3D"margin-top:0;border:0">And get =F0=9F=8D=94 as your badge icon =
+when you enter <code style=3D"background-color:#f1f1ff;padding:2px 5px">cus=
+tom-burger</code>.</p>
+</td>
+    </tr>
+  </tbody>
+</table>
+
+
+<hr style=3D"background-color:#ddd;min-height:1px;border:1px;background-col=
+or:#ddd;min-height:1px;border:1px">
+
+<div style=3D"color:#666">
+<p>To respond, reply to this email or visit <a href=3D"https://meta.discour=
+se.org/t/badge-icons-where-to-find-them/18058/9" style=3D"color:#666;text-d=
+ecoration:none;font-weight:bold;color:#006699" target=3D"_blank">https://me=
+ta.discourse.org/t/badge-icons-where-to-find-them/18058/9</a> in your brows=
+er.</p>
+</div>
+<div style=3D"color:#666">
+<p>To unsubscribe from these emails, visit your <a href=3D"https://meta.dis=
+course.org/my/preferences" style=3D"color:#666;text-decoration:none;font-we=
+ight:bold;color:#006699" target=3D"_blank">user preferences</a>.</p>
+</div>
+</div>
+</blockquote></div><br></div></div>
+
+--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 0000000000000000000000000000000000000000..fb1f590a30eddf1f948470edc839a94c4ef47897
--- /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: <topic/35@discourse.techapj.com>
+  <5434c8b52bb3a_623ff09fec70f049749@discourse-app.mail>
+Date: Wed, 8 Oct 2014 10:47:17 +0530
+Delivered-To: arpit@techapj.com
+Message-ID: <CAOJeqne=SJ_LwN4sb-0Y95ejc2OpreVhdmcPn0TnmwSvTCYzzQ@mail.gmail.com>
+Subject: Re: [Discourse] [Meta] Welcome to techAPJ's Discourse!
+From: Arpit Jalan <arpit@techapj.com>
+To: Discourse <mail+e1c7f2a380e33840aeb654f075490bad@arpitjalan.com>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.eml b/spec/fixtures/emails/paragraphs.eml
new file mode 100644
index 0000000000000000000000000000000000000000..2d5b5283f7eb0fbd085d58797abfd2d0bc41a9bc
--- /dev/null
+++ b/spec/fixtures/emails/paragraphs.eml
@@ -0,0 +1,42 @@
+Return-Path: <jake@adventuretime.ooo>
+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 <reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo>; 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 <jake@adventuretime.ooo>
+To: reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo
+Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+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
+<reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo> 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/plaintext_only.eml b/spec/fixtures/emails/plaintext_only.eml
new file mode 100644
index 0000000000000000000000000000000000000000..1bfaec771dc2e3d45708666079f5cf42e34de6c5
--- /dev/null
+++ b/spec/fixtures/emails/plaintext_only.eml
@@ -0,0 +1,42 @@
+Delivered-To: reply@discourse.org
+Return-Path: <walter.white@googlemail.com>
+MIME-Version: 1.0
+From: <walter.white@googlemail.com>
+To:
+  =?utf-8?Q?Discourse_Meta?=
+   <reply@discourse.org>
+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: <topic/22638/86406@meta.discourse.org>
+References:
+ <topic/22638@meta.discourse.org>,<topic/22638/86406@meta.discourse.org>
+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/fixtures/emails/valid_reply.eml b/spec/fixtures/emails/valid_reply.eml
new file mode 100644
index 0000000000000000000000000000000000000000..1e696389954ca0b2ca63fe4a0f4dfd5853f5e61a
--- /dev/null
+++ b/spec/fixtures/emails/valid_reply.eml
@@ -0,0 +1,40 @@
+Return-Path: <jake@adventuretime.ooo>
+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 <reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo>; 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 <jake@adventuretime.ooo>
+To: reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo
+Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+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
+<reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo> 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/windows_8_metro.eml b/spec/fixtures/emails/windows_8_metro.eml
new file mode 100644
index 0000000000000000000000000000000000000000..67d204af56278f2d9503966a0aac33f84d8cedd2
--- /dev/null
+++ b/spec/fixtures/emails/windows_8_metro.eml
@@ -0,0 +1,173 @@
+Delivered-To: reply@discourse.org
+Return-Path: <walter.white@googlemail.com>
+MIME-Version: 1.0
+From: <walter.white@googlemail.com>
+To:
+  =?utf-8?Q?Discourse_Meta?=
+   <reply@discourse.org>
+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: <topic/22638/86406@meta.discourse.org>
+References:
+ <topic/22638@meta.discourse.org>,<topic/22638/86406@meta.discourse.org>
+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 0000000000000000000000000000000000000000..491e078fb5b7057e2e617f45d8a5c496bc50babc
--- /dev/null
+++ b/spec/fixtures/emails/wrong_reply_key.eml
@@ -0,0 +1,40 @@
+Return-Path: <jake@adventuretime.ooo>
+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 <reply+59d8df8370b7e95c5a49fbf86aeb2c93@discourse.example.com>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <reply+59d8df8370b7e95c5a49fbf86aeb2c93@discourse.example.com>; 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 <jake@adventuretime.ooo>
+To: reply+QQd8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo
+Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+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
+<reply+59d8df8370b7e95c5a49fbf86aeb2c93@discourse.example.com> 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/helpers/events_helper_spec.rb b/spec/helpers/events_helper_spec.rb
index da58ab98462bdb27eaaee0c1520a2254330b2d3e..e68a5ec29ab63b6fdbcdad4255d74be2e0e7dddd 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 = '<pre class="code highlight white plaintext"><code>' \
-               'Code block...</code></pre>'
+    expected = %r{<pre.+><code>Code block\.\.\.</code></pre>}
 
     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 = '<pre class="code highlight white ruby">' \
+    expected = '<pre class="code highlight js-syntax-highlight ruby">' \
       "<code><span class=\"k\">def</span> <span class=\"nf\">test</span>\n" \
       "  <span class=\"s1\">\'hello world\'</span>\n" \
       "<span class=\"k\">end</span>" \
diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb
index d814b562113d733c8e71812b634e02b1fd102ff4..06f69262b71a2773d0879f9b68bf68d6d5e5badb 100644
--- a/spec/helpers/preferences_helper_spec.rb
+++ b/spec/helpers/preferences_helper_spec.rb
@@ -1,72 +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)
-    end
+  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))
 
-    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)
-    end
+        expect(helper.user_color_scheme).to eq 'solarized-light'
+      end
 
-    it 'provides better option descriptions' do
-      expect(dashboard_choices).to match_array [
-        ['Your Projects (default)', 'projects'],
-        ['Starred Projects',        'stars']
-      ]
+      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
-  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)
+    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
 
-    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
+  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
diff --git a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb
index f8958c9bab8e4b514972ea2cc2038799677cef8d..0e826a319e04e382d897e558a0a7085e097a2a79 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/color_schemes_spec.rb b/spec/lib/gitlab/color_schemes_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c7be45dbcd3f84368cd7de04e48a6222e7894061
--- /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/email/attachment_uploader_spec.rb b/spec/lib/gitlab/email/attachment_uploader_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e8208e15e29ead60bb92b74ea82da81661dc40bf
--- /dev/null
+++ b/spec/lib/gitlab/email/attachment_uploader_spec.rb
@@ -0,0 +1,20 @@
+require "spec_helper"
+
+describe Gitlab::Email::AttachmentUploader do
+  describe "#execute" do
+    let(:project) { build(:project) }
+    let(:message_raw) { fixture_file("emails/attachment.eml") }
+    let(:message) { Mail::Message.new(message_raw) }
+
+    it "uploads all attachments and returns their links" 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/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1cc80f35f98c102ad5760a8ed60c1092e5a08df1
--- /dev/null
+++ b/spec/lib/gitlab/email/receiver_spec.rb
@@ -0,0 +1,138 @@
+require "spec_helper"
+
+describe Gitlab::Email::Receiver do
+  before do
+    stub_reply_by_email_setting(enabled: true, address: "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/wrong_reply_key.eml') }
+
+    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 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)
+    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
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 0000000000000000000000000000000000000000..7cae1da8050053015c868791c5f1380ecdf724c3
--- /dev/null
+++ b/spec/lib/gitlab/email/reply_parser_spec.rb
@@ -0,0 +1,210 @@
+require "spec_helper"
+
+# Inspired in great part by Discourse's Email::Receiver
+describe Gitlab::Email::ReplyParser do
+  describe '#execute' 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 "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
+      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(
+          <<-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.
+
+            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(
+          <<-BODY.strip_heredoc.chomp
+            Awesome!
+
+            Pleasure to have you here!
+
+            :boom:
+          BODY
+        )
+    end
+
+    it "handles newlines" do
+      expect(test_parse_body(fixture_file("emails/newlines.eml"))).
+        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(
+          <<-BODY.strip_heredoc.chomp
+            On Wed, Oct 8, 2014 at 11:12 AM, techAPJ <info@unconfigured.discourse.org> wrote:
+
+            >     techAPJ <https://meta.discourse.org/users/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
+            > <https://meta.discourse.org/users/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
+            > <https://meta.discourse.org/my/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(
+          <<-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.
+
+            Here's some **bold** text in Markdown.
+
+            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(
+          <<-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.
+
+            Here's some **bold** markdown text.
+
+            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(
+          <<-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.
+
+            This is **bold** in Markdown.
+
+            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(
+          <<-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 "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/github_import/project_creator_spec.rb b/spec/lib/gitlab/github_import/project_creator_spec.rb
index 4fe7bd3b77d470a7291701153ad26f92d9e59a14..ca61d3c5234e93902ff70c74155a935d671d6cea 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 938d08396fd6a3af23716038ae7edc2ccdda8db1..2d8923d14bb94309436fe65b92c49793accd2884 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")
diff --git a/spec/lib/gitlab/google_code_import/client_spec.rb b/spec/lib/gitlab/google_code_import/client_spec.rb
index 6aa4428f36758d2d3e79f555916e51628a4ff462..37985c062b4a06858c6911433b59df08f2f80cfd 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 f49cbb7f532ae79cd44a6718bc9eba4e87d5144b..65ad7524cc2b6fa8339a2069e3c22c1b16de4025 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/lib/gitlab/markdown/autolink_filter_spec.rb b/spec/lib/gitlab/markdown/autolink_filter_spec.rb
index 982be0782c93953dac9d5578fc250856494c9836..26332ba5217f38f18d1639557bb03fccfdb1eef0 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 &lt;&lt;&lt;#{link}&gt;&gt;&gt;")
+
+        expect(doc.at_css('a')['href']).to eq link
+        expect(doc.text).to eq "See <<<#{link}>>>"
       end
 
       it 'accepts link_attr options' do
diff --git a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
index a5405e14a73603a5211f7ec6b19b0ad49d4240e4..02d923b036cce0ae896a0214062c08ad9c69459f 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(/\(<a.+>#{reference}<\/a>\.\)/)
     end
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 0000000000000000000000000000000000000000..a678c7e1a76db0cebdca67dd38863e749ff9384e
--- /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
+        stub_reply_by_email_setting(enabled: true)
+      end
+
+      context "when the address is valid" do
+        before do
+          stub_reply_by_email_setting(address: "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
+          stub_reply_by_email_setting(address: "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
+        stub_reply_by_email_setting(enabled: 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
+      stub_reply_by_email_setting(address: "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
+      stub_reply_by_email_setting(address: "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
diff --git a/spec/lib/gitlab/themes_spec.rb b/spec/lib/gitlab/themes_spec.rb
index 9c6c3fd8104003fb6369b9fd14ca65d70a8622bc..e554458e41ca55fc6c5e3349879812e3a1a1f0b5 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
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 1d5b4f6f36b215a7cb7aefd2a9d6e6d122569e89..13cced81875c59f7680a92082e72c9c89542a56d 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
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index 62cef9db5344f1754c156cf4ca551287d44b0598..c483060fd73994235eb2c069bb936b79ce78ad60 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
 
diff --git a/spec/services/projects/upload_service_spec.rb b/spec/services/projects/upload_service_spec.rb
index 7aa26857649aacf5bc5c6413de7218175fc060fb..fa4ff6b01ad9993cfcc6fa2d1ed112e2dc08f347 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
diff --git a/spec/support/fixture_helpers.rb b/spec/support/fixture_helpers.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a05c9d18002e4a19e0bf9efa709daa04d44ee57a
--- /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 c59df4e84d6ab9d99cb23135da2cd2af375eec98..39a643914604076f99dfc11350ad8bed6c737325 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 = File.read(Rails.root.join('spec/fixtures/markdown.md.erb'))
+    ERB.new(markdown).result(binding)
   end
 end
diff --git a/spec/support/stub_configuration.rb b/spec/support/stub_configuration.rb
index e4004ec8f79b88eb43298b6b9b54319ce18e68bd..ef3a120d44a2a96154dae9cec75969059c3e4821 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
diff --git a/spec/workers/email_receiver_worker_spec.rb b/spec/workers/email_receiver_worker_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e8f1bd2fa2fc18c96fd8086b40bd948e7d97d860
--- /dev/null
+++ b/spec/workers/email_receiver_worker_spec.rb
@@ -0,0 +1,45 @@
+require "spec_helper"
+
+describe EmailReceiverWorker do
+  let(:raw_message) { fixture_file('emails/valid_reply.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(["jake@adventuretime.ooo"])
+        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
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 0000000000000000000000000000000000000000..3600c771075293c0413caa9ed753b24a9625261b
--- /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