Commit fe86c8df authored by Douwe Maan's avatar Douwe Maan

Merge branch 'master' into joelkoglin/gitlab-ce-feature_fix_ldap_auth_issue_993

parents d92f4280 a429eb4d
...@@ -25,6 +25,7 @@ config/initializers/rack_attack.rb ...@@ -25,6 +25,7 @@ config/initializers/rack_attack.rb
config/initializers/smtp_settings.rb config/initializers/smtp_settings.rb
config/resque.yml config/resque.yml
config/unicorn.rb config/unicorn.rb
config/mail_room.yml
coverage/* coverage/*
db/*.sqlite3 db/*.sqlite3
db/*.sqlite3-journal db/*.sqlite3-journal
......
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.0.0 (unreleased) 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) - Only show recent push event if the branch still exists or a recent merge request has not been created (Stan Hu)
- Remove satellites - Remove satellites
- Better performance for web editor (switched from satellites to rugged) - Better performance for web editor (switched from satellites to rugged)
...@@ -9,8 +14,26 @@ v 8.0.0 (unreleased) ...@@ -9,8 +14,26 @@ v 8.0.0 (unreleased)
- Allow displaying of archived projects in the admin interface (Artem Sidorenko) - Allow displaying of archived projects in the admin interface (Artem Sidorenko)
- Allow configuration of import sources for new projects (Artem Sidorenko) - Allow configuration of import sources for new projects (Artem Sidorenko)
- Search for comments should be case insensetive - Search for comments should be case insensetive
- Create cross-reference for closing references on commits pushed to non-default branches (Maël Valais)
v 7.14.0 (unreleased) - 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) - 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 - 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) - Upgrade gitlab_git to 7.2.14 to ignore CRLFs in .gitmodules (Stan Hu)
...@@ -294,6 +317,7 @@ v 7.11.0 ...@@ -294,6 +317,7 @@ v 7.11.0
- Protect OmniAuth request phase against CSRF. - Protect OmniAuth request phase against CSRF.
- Don't send notifications to mentioned users that don't have access to the project in question. - Don't send notifications to mentioned users that don't have access to the project in question.
- Add search issues/MR by number - Add search issues/MR by number
- Change plots to bar graphs in commit statistics screen
- Move snippets UI to fluid layout - Move snippets UI to fluid layout
- Improve UI for sidebar. Increase separation between navigation and content - Improve UI for sidebar. Increase separation between navigation and content
- Improve new project command options (Ben Bodenmiller) - Improve new project command options (Ben Bodenmiller)
......
...@@ -34,11 +34,11 @@ gem 'rqrcode-rails3' ...@@ -34,11 +34,11 @@ gem 'rqrcode-rails3'
gem 'attr_encrypted', '1.3.4' gem 'attr_encrypted', '1.3.4'
# Browser detection # Browser detection
gem "browser", '~> 0.8.0' gem "browser", '~> 1.0.0'
# Extracting information from a git repository # Extracting information from a git repository
# Provide access to Gitlab::Git library # 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 # Ruby/Rack Git Smart-HTTP Server Handler
# GitLab fork with a lot of changes (improved thread-safety, better memory usage etc) # GitLab fork with a lot of changes (improved thread-safety, better memory usage etc)
...@@ -272,3 +272,7 @@ end ...@@ -272,3 +272,7 @@ end
gem "newrelic_rpm" gem "newrelic_rpm"
gem 'octokit', '3.7.0' gem 'octokit', '3.7.0'
gem "mail_room", "~> 0.4.1"
gem 'email_reply_parser'
...@@ -76,7 +76,7 @@ GEM ...@@ -76,7 +76,7 @@ GEM
ruby_parser (~> 3.5.0) ruby_parser (~> 3.5.0)
sass (~> 3.0) sass (~> 3.0)
terminal-table (~> 1.4) terminal-table (~> 1.4)
browser (0.8.0) browser (1.0.0)
builder (3.2.2) builder (3.2.2)
byebug (3.2.0) byebug (3.2.0)
columnize (~> 0.8) columnize (~> 0.8)
...@@ -156,6 +156,7 @@ GEM ...@@ -156,6 +156,7 @@ GEM
dotenv (0.9.0) dotenv (0.9.0)
dropzonejs-rails (0.7.1) dropzonejs-rails (0.7.1)
rails (> 3.1) rails (> 3.1)
email_reply_parser (0.5.8)
email_spec (1.6.0) email_spec (1.6.0)
launchy (~> 2.1) launchy (~> 2.1)
mail (~> 2.2) mail (~> 2.2)
...@@ -275,7 +276,7 @@ GEM ...@@ -275,7 +276,7 @@ GEM
mime-types (~> 1.19) mime-types (~> 1.19)
gitlab_emoji (0.1.0) gitlab_emoji (0.1.0)
gemojione (~> 2.0) gemojione (~> 2.0)
gitlab_git (7.2.14) gitlab_git (7.2.15)
activesupport (~> 4.0) activesupport (~> 4.0)
charlock_holmes (~> 0.6) charlock_holmes (~> 0.6)
gitlab-linguist (~> 3.0) gitlab-linguist (~> 3.0)
...@@ -371,6 +372,7 @@ GEM ...@@ -371,6 +372,7 @@ GEM
systemu (~> 2.6.2) systemu (~> 2.6.2)
mail (2.6.3) mail (2.6.3)
mime-types (>= 1.16, < 3) mime-types (>= 1.16, < 3)
mail_room (0.4.1)
method_source (0.8.2) method_source (0.8.2)
mime-types (1.25.1) mime-types (1.25.1)
mimemagic (0.3.0) mimemagic (0.3.0)
...@@ -753,7 +755,7 @@ DEPENDENCIES ...@@ -753,7 +755,7 @@ DEPENDENCIES
binding_of_caller binding_of_caller
bootstrap-sass (~> 3.0) bootstrap-sass (~> 3.0)
brakeman brakeman
browser (~> 0.8.0) browser (~> 1.0.0)
byebug byebug
cal-heatmap-rails (~> 0.0.1) cal-heatmap-rails (~> 0.0.1)
capybara (~> 2.4.0) capybara (~> 2.4.0)
...@@ -773,6 +775,7 @@ DEPENDENCIES ...@@ -773,6 +775,7 @@ DEPENDENCIES
diffy (~> 3.0.3) diffy (~> 3.0.3)
doorkeeper (= 2.1.3) doorkeeper (= 2.1.3)
dropzonejs-rails dropzonejs-rails
email_reply_parser
email_spec (~> 1.6.0) email_spec (~> 1.6.0)
enumerize enumerize
factory_girl_rails factory_girl_rails
...@@ -787,7 +790,7 @@ DEPENDENCIES ...@@ -787,7 +790,7 @@ DEPENDENCIES
gitlab-grack (~> 2.0.2) gitlab-grack (~> 2.0.2)
gitlab-linguist (~> 3.0.1) gitlab-linguist (~> 3.0.1)
gitlab_emoji (~> 0.1) gitlab_emoji (~> 0.1)
gitlab_git (~> 7.2.14) gitlab_git (~> 7.2.15)
gitlab_meta (= 7.0) gitlab_meta (= 7.0)
gitlab_omniauth-ldap (= 1.2.1) gitlab_omniauth-ldap (= 1.2.1)
gollum-lib (~> 4.0.2) gollum-lib (~> 4.0.2)
...@@ -805,6 +808,7 @@ DEPENDENCIES ...@@ -805,6 +808,7 @@ DEPENDENCIES
jquery-ui-rails jquery-ui-rails
kaminari (~> 0.15.1) kaminari (~> 0.15.1)
letter_opener letter_opener
mail_room (~> 0.4.1)
minitest (~> 5.3.0) minitest (~> 5.3.0)
mousetrap-rails mousetrap-rails
mysql2 mysql2
......
web: bundle exec unicorn_rails -p ${PORT:="3000"} -E ${RAILS_ENV:="development"} -c ${UNICORN_CONFIG:="config/unicorn.rb"} web: bundle exec unicorn_rails -p ${PORT:="3000"} -E ${RAILS_ENV:="development"} -c ${UNICORN_CONFIG:="config/unicorn.rb"}
worker: bundle exec sidekiq -q post_receive -q mailer -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q 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
...@@ -116,6 +116,12 @@ $ -> ...@@ -116,6 +116,12 @@ $ ->
$('.remove-row').bind 'ajax:success', -> $('.remove-row').bind 'ajax:success', ->
$(this).closest('li').fadeOut() $(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 # Initialize select2 selects
$('select.select2').select2(width: 'resolve', dropdownAutoWidth: true) $('select.select2').select2(width: 'resolve', dropdownAutoWidth: true)
......
...@@ -51,10 +51,10 @@ class Dispatcher ...@@ -51,10 +51,10 @@ class Dispatcher
MergeRequests.init() MergeRequests.init()
when 'dashboard:show', 'root:show' when 'dashboard:show', 'root:show'
new Dashboard() new Dashboard()
when 'dashboard:activity'
new Activities() new Activities()
when 'dashboard:projects:starred' when 'dashboard:projects:starred'
new Activities() new Activities()
new ProjectsList()
when 'projects:commit:show' when 'projects:commit:show'
new Commit() new Commit()
new Diff() new Diff()
...@@ -69,7 +69,6 @@ class Dispatcher ...@@ -69,7 +69,6 @@ class Dispatcher
when 'groups:show' when 'groups:show'
new Activities() new Activities()
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
new ProjectsList()
when 'groups:group_members:index' when 'groups:group_members:index'
new GroupMembers() new GroupMembers()
new UsersSelect() new UsersSelect()
...@@ -95,8 +94,6 @@ class Dispatcher ...@@ -95,8 +94,6 @@ class Dispatcher
when 'users:show' when 'users:show'
new User() new User()
new Activities() new Activities()
when 'admin:users:show'
new ProjectsList()
switch path.first() switch path.first()
when 'admin' when 'admin'
......
...@@ -8,7 +8,7 @@ class @ProjectsList ...@@ -8,7 +8,7 @@ class @ProjectsList
$(".projects-list-filter").keyup -> $(".projects-list-filter").keyup ->
terms = $(this).val() terms = $(this).val()
uiBox = $(this).closest('.panel') uiBox = $(this).closest('.projects-list-holder')
if terms == "" || terms == undefined if terms == "" || terms == undefined
uiBox.find(".projects-list li").show() uiBox.find(".projects-list li").show()
else else
......
# 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)
...@@ -6,6 +6,7 @@ class @UsersSelect ...@@ -6,6 +6,7 @@ class @UsersSelect
$('.ajax-users-select').each (i, select) => $('.ajax-users-select').each (i, select) =>
@projectId = $(select).data('project-id') @projectId = $(select).data('project-id')
@groupId = $(select).data('group-id') @groupId = $(select).data('group-id')
@showCurrentUser = $(select).data('current-user')
showNullUser = $(select).data('null-user') showNullUser = $(select).data('null-user')
showAnyUser = $(select).data('any-user') showAnyUser = $(select).data('any-user')
showEmailUser = $(select).data('email-user') showEmailUser = $(select).data('email-user')
...@@ -108,6 +109,7 @@ class @UsersSelect ...@@ -108,6 +109,7 @@ class @UsersSelect
active: true active: true
project_id: @projectId project_id: @projectId
group_id: @groupId group_id: @groupId
current_user: @showCurrentUser
dataType: "json" dataType: "json"
).done (users) -> ).done (users) ->
callback(users) callback(users)
......
...@@ -20,3 +20,8 @@ html { ...@@ -20,3 +20,8 @@ html {
.navless-container { .navless-container {
margin-top: 30px; margin-top: 30px;
} }
.container-limited {
max-width: $fixed-layout-width;
}
...@@ -157,3 +157,41 @@ ...@@ -157,3 +157,41 @@
white-space: nowrap; white-space: nowrap;
max-width: $max_width; 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;
}
}
}
}
...@@ -13,7 +13,7 @@ $code_line_height: 1.5; ...@@ -13,7 +13,7 @@ $code_line_height: 1.5;
$border-color: #E5E5E5; $border-color: #E5E5E5;
$background-color: #f5f5f5; $background-color: #f5f5f5;
$header-height: 50px; $header-height: 50px;
$readable-width: 1100px; $fixed-layout-width: 1200px;
/* /*
......
...@@ -132,10 +132,6 @@ p.time { ...@@ -132,10 +132,6 @@ p.time {
text-shadow: none; text-shadow: none;
} }
.highlight_word {
background: #fafe3d;
}
.thin_area{ .thin_area{
height: 150px; height: 150px;
} }
...@@ -375,9 +371,9 @@ table { ...@@ -375,9 +371,9 @@ table {
} }
.center-top-menu { .center-top-menu {
border-bottom: 1px solid #EEE;
list-style: none; list-style: none;
text-align: center; text-align: center;
margin-top: 5px;
padding-bottom: 15px; padding-bottom: 15px;
margin-bottom: 15px; margin-bottom: 15px;
...@@ -385,7 +381,7 @@ table { ...@@ -385,7 +381,7 @@ table {
display: inline-block; display: inline-block;
a { a {
padding: 10px; padding: 15px;
} }
&.active a { &.active a {
......
...@@ -20,16 +20,16 @@ header { ...@@ -20,16 +20,16 @@ header {
} }
&.navbar-gitlab { &.navbar-gitlab {
padding: 0 20px;
z-index: 100; z-index: 100;
margin-bottom: 0; margin-bottom: 0;
min-height: $header-height; min-height: $header-height;
border: none; border: none;
width: 100%; border-bottom: 1px solid #EEE;
.container { .container-fluid {
background: #FFF; background: #FFF;
width: 100% !important; width: 100% !important;
padding: 0;
filter: none; filter: none;
.nav > li > a { .nav > li > a {
...@@ -64,55 +64,11 @@ header { ...@@ -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 { .header-content {
border-bottom: 1px solid #EEE;
padding-right: 35px;
height: $header-height; height: $header-height;
.title { .title {
margin: 0; margin: 0;
padding: 0 15px 0 35px;
overflow: hidden; overflow: hidden;
font-size: 18px; font-size: 18px;
line-height: $header-height; line-height: $header-height;
...@@ -168,15 +124,7 @@ header { ...@@ -168,15 +124,7 @@ header {
} }
@mixin collapsed-header { @mixin collapsed-header {
.header-logo { margin-left: $sidebar_collapsed_width;
width: $sidebar_collapsed_width;
}
.header-content {
.title {
margin-left: 30px;
}
}
} }
@media (max-width: $screen-md-max) { @media (max-width: $screen-md-max) {
...@@ -191,16 +139,14 @@ header { ...@@ -191,16 +139,14 @@ header {
} }
.header-expanded { .header-expanded {
margin-left: $sidebar_width;
} }
} }
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
header .container { header .container-fluid {
font-size: 18px; font-size: 18px;
.title {
}
.navbar-nav { .navbar-nav {
margin: 0px; margin: 0px;
float: none !important; float: none !important;
......
...@@ -93,28 +93,12 @@ ol, ul { ...@@ -93,28 +93,12 @@ ol, ul {
/** light list with border-bottom between li **/ /** light list with border-bottom between li **/
ul.bordered-list { ul.bordered-list {
margin: 5px 0px; @include basic-list;
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; }
}
}
&.top-list { &.top-list {
li:first-child { li:first-child {
padding-top: 0; padding-top: 0;
h4, h5 { h4, h5 {
margin-top: 0; margin-top: 0;
} }
......
...@@ -188,3 +188,46 @@ ...@@ -188,3 +188,46 @@
width: $sidebar_width - 2 * 10px; 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;
}
}
}
...@@ -21,6 +21,12 @@ pre.code.highlight.dark, ...@@ -21,6 +21,12 @@ pre.code.highlight.dark,
background-color: #557 !important; background-color: #557 !important;
} }
// Search result highlight
span.highlight_word {
background: #ffe792;
color: #000000;
}
.hll { background-color: #373b41 } .hll { background-color: #373b41 }
.c { color: #969896 } /* Comment */ .c { color: #969896 } /* Comment */
.err { color: #cc6666 } /* Error */ .err { color: #cc6666 } /* Error */
......
...@@ -21,6 +21,12 @@ pre.code.monokai, ...@@ -21,6 +21,12 @@ pre.code.monokai,
background-color: #49483e !important; background-color: #49483e !important;
} }
// Search result highlight
span.highlight_word {
background: #ffe792;
color: #000000;
}
.hll { background-color: #49483e } .hll { background-color: #49483e }
.c { color: #75715e } /* Comment */ .c { color: #75715e } /* Comment */
.err { color: #960050; background-color: #1e0010 } /* Error */ .err { color: #960050; background-color: #1e0010 } /* Error */
......
...@@ -21,6 +21,11 @@ pre.code.highlight.solarized-dark, ...@@ -21,6 +21,11 @@ pre.code.highlight.solarized-dark,
background-color: #174652 !important; background-color: #174652 !important;
} }
// Search result highlight
span.highlight_word {
background: #094554;
}
/* Solarized Dark /* Solarized Dark
For use with Jekyll and Pygments For use with Jekyll and Pygments
......
...@@ -21,6 +21,11 @@ pre.code.highlight.solarized-light, ...@@ -21,6 +21,11 @@ pre.code.highlight.solarized-light,
background-color: #ddd8c5 !important; background-color: #ddd8c5 !important;
} }
// Search result highlight
span.highlight_word {
background: #eee8d5;
}
/* Solarized Light /* Solarized Light
For use with Jekyll and Pygments For use with Jekyll and Pygments
......
...@@ -21,6 +21,11 @@ pre.code.highlight.white, ...@@ -21,6 +21,11 @@ pre.code.highlight.white,
background-color: #f8eec7 !important; background-color: #f8eec7 !important;
} }
// Search result highlight
span.highlight_word {
background: #fafe3d;
}
.hll { background-color: #f8f8f8 } .hll { background-color: #f8f8f8 }
.c { color: #999988; font-style: italic; } .c { color: #999988; font-style: italic; }
.err { color: #a61717; background-color: #e3d2d2; } .err { color: #a61717; background-color: #e3d2d2; }
......
...@@ -23,41 +23,6 @@ ...@@ -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 { .project-access-icon {
margin-left: 10px; margin-left: 10px;
float: left; float: left;
...@@ -73,10 +38,9 @@ ...@@ -73,10 +38,9 @@
float: left; float: left;
.avatar { .avatar {
margin-top: -8px;
margin-left: -15px;
@include border-radius(0px); @include border-radius(0px);
} }
.identicon { .identicon {
line-height: 40px; line-height: 40px;
} }
......
...@@ -45,9 +45,3 @@ ...@@ -45,9 +45,3 @@
.btn { font-size: 13px; } .btn { font-size: 13px; }
} }
.issuable-details {
.description {
max-width: $readable-width;
}
}
...@@ -30,14 +30,21 @@ ...@@ -30,14 +30,21 @@
} }
} }
.project-home-dropdown {
margin: 11px 3px 0;
}
.project-home-desc { .project-home-desc {
h1 { h1 {
margin: 0; margin: 0;
margin-bottom: 10px; margin-bottom: 10px;
font-size: 26px; font-size: 26px;
font-weight: bold;
} }
p { p {
font-size: 18px;
color: #666;
display: inline; display: inline;
} }
} }
...@@ -155,78 +162,6 @@ ul.nav.nav-projects-tabs { ...@@ -155,78 +162,6 @@ ul.nav.nav-projects-tabs {
margin: 0px; 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 { .transfer-project .select2-container {
min-width: 200px; min-width: 200px;
} }
...@@ -316,3 +251,43 @@ table.table.protected-branches-list tr.no-border { ...@@ -316,3 +251,43 @@ table.table.protected-branches-list tr.no-border {
pre.light-well { pre.light-well {
border-color: #f1f1f1; 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;
}
.search-results { .search-results {
.search-result-row { .search-result-row {
border-bottom: 1px solid #EEE; border-bottom: 1px solid #DDD;
padding-bottom: 10px; padding-bottom: 15px;
margin-bottom: 10px; margin-bottom: 15px;
} }
} }
.search-holder {
max-width: 600px;
margin: 0 auto;
margin-bottom: 20px;
input {
border-color: #BBB;
font-weight: bold;
}
}
...@@ -6,3 +6,27 @@ ...@@ -6,3 +6,27 @@
.snippet-form-holder .file-holder .file-title { .snippet-form-holder .file-holder .file-title {
padding: 2px; 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;
}
}
}
...@@ -117,7 +117,6 @@ ...@@ -117,7 +117,6 @@
.readme-holder { .readme-holder {
margin: 0 auto; margin: 0 auto;
max-width: $readable-width;
.readme-file-title { .readme-file-title {
font-size: 14px; font-size: 14px;
......
...@@ -7,27 +7,23 @@ ...@@ -7,27 +7,23 @@
* $color-dark - * $color-dark -
*/ */
@mixin gitlab-theme($color-light, $color, $color-darker, $color-dark) { @mixin gitlab-theme($color-light, $color, $color-darker, $color-dark) {
header { .page-with-sidebar {
&.navbar-gitlab { .header-logo {
.header-logo { background-color: $color-darker;
background-color: $color-darker; border-color: $color-darker;
border-color: $color-darker;
a { a {
color: $color-light; color: $color-light;
} }
&:hover { &:hover {
background-color: $color-dark; background-color: $color-dark;
a { a {
color: #FFF; color: #FFF;
}
} }
} }
} }
}
.page-with-sidebar {
.collapse-nav a { .collapse-nav a {
color: #FFF; color: #FFF;
background: $color; background: $color;
......
...@@ -4,8 +4,13 @@ class Admin::AbuseReportsController < Admin::ApplicationController ...@@ -4,8 +4,13 @@ class Admin::AbuseReportsController < Admin::ApplicationController
end end
def destroy 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
end end
...@@ -39,6 +39,6 @@ class Admin::HooksController < Admin::ApplicationController ...@@ -39,6 +39,6 @@ class Admin::HooksController < Admin::ApplicationController
end end
def hook_params def hook_params
params.require(:hook).permit(:url) params.require(:hook).permit(:url, :enable_ssl_verification)
end end
end end
...@@ -55,7 +55,9 @@ class ApplicationController < ActionController::Base ...@@ -55,7 +55,9 @@ class ApplicationController < ActionController::Base
def authenticate_user!(*args) def authenticate_user!(*args)
# If user is not signed-in and tries to access root_path - redirect him to landing page # 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 if current_user.nil? && root_path == request.path
redirect_to current_application_settings.home_page_url and return redirect_to current_application_settings.home_page_url and return
end end
...@@ -190,11 +192,12 @@ class ApplicationController < ActionController::Base ...@@ -190,11 +192,12 @@ class ApplicationController < ActionController::Base
end end
def add_gon_variables 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.default_issues_tracker = Project.new.default_issue_tracker.to_param
gon.api_version = API::API.version gon.max_file_size = current_application_settings.max_attachment_size
gon.relative_url_root = Gitlab.config.gitlab.relative_url_root 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.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class
gon.max_file_size = current_application_settings.max_attachment_size;
if current_user if current_user
gon.current_user_id = current_user.id gon.current_user_id = current_user.id
......
...@@ -33,8 +33,14 @@ class AutocompleteController < ApplicationController ...@@ -33,8 +33,14 @@ class AutocompleteController < ApplicationController
@users = @users.search(params[:search]) if params[:search].present? @users = @users.search(params[:search]) if params[:search].present?
@users = @users.active @users = @users.active
@users = @users.page(params[:page]).per(PER_PAGE) @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] render json: @users, only: [:name, :username, :id], methods: [:avatar_url]
end end
......
class DashboardController < Dashboard::ApplicationController class DashboardController < Dashboard::ApplicationController
before_action :load_projects before_action :load_projects
before_action :event_filter, only: :show before_action :event_filter, only: :activity
respond_to :html respond_to :html
...@@ -10,13 +10,8 @@ class DashboardController < Dashboard::ApplicationController ...@@ -10,13 +10,8 @@ class DashboardController < Dashboard::ApplicationController
respond_to do |format| respond_to do |format|
format.html format.html
format.json do
load_events
pager_json("events/_events", @events.count)
end
format.atom do format.atom do
event_filter
load_events load_events
render layout: false render layout: false
end end
...@@ -40,6 +35,19 @@ class DashboardController < Dashboard::ApplicationController ...@@ -40,6 +35,19 @@ class DashboardController < Dashboard::ApplicationController
end end
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 protected
def load_projects def load_projects
......
...@@ -13,10 +13,9 @@ class Import::BitbucketController < Import::BaseController ...@@ -13,10 +13,9 @@ class Import::BitbucketController < Import::BaseController
access_token = client.get_token(request_token, params[:oauth_verifier], callback_import_bitbucket_url) access_token = client.get_token(request_token, params[:oauth_verifier], callback_import_bitbucket_url)
current_user.bitbucket_access_token = access_token.token session[:bitbucket_access_token] = access_token.token
current_user.bitbucket_access_token_secret = access_token.secret session[:bitbucket_access_token_secret] = access_token.secret
current_user.save
redirect_to status_import_bitbucket_url redirect_to status_import_bitbucket_url
end end
...@@ -46,19 +45,20 @@ class Import::BitbucketController < Import::BaseController ...@@ -46,19 +45,20 @@ class Import::BitbucketController < Import::BaseController
namespace = get_or_create_namespace || (render and return) 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 @access_denied = true
render render
return return
end end
@project = Gitlab::BitbucketImport::ProjectCreator.new(repo, namespace, current_user).execute @project = Gitlab::BitbucketImport::ProjectCreator.new(repo, namespace, current_user, access_params).execute
end end
private private
def client 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 end
def verify_bitbucket_import_enabled def verify_bitbucket_import_enabled
...@@ -66,7 +66,7 @@ class Import::BitbucketController < Import::BaseController ...@@ -66,7 +66,7 @@ class Import::BitbucketController < Import::BaseController
end end
def bitbucket_auth def bitbucket_auth
if current_user.bitbucket_access_token.blank? if session[:bitbucket_access_token].blank?
go_to_bitbucket_for_permissions go_to_bitbucket_for_permissions
end end
end end
...@@ -81,4 +81,13 @@ class Import::BitbucketController < Import::BaseController ...@@ -81,4 +81,13 @@ class Import::BitbucketController < Import::BaseController
def bitbucket_unauthorized def bitbucket_unauthorized
go_to_bitbucket_for_permissions go_to_bitbucket_for_permissions
end end
private
def access_params
{
bitbucket_access_token: session[:bitbucket_access_token],
bitbucket_access_token_secret: session[:bitbucket_access_token_secret]
}
end
end end
...@@ -5,9 +5,7 @@ class Import::GithubController < Import::BaseController ...@@ -5,9 +5,7 @@ class Import::GithubController < Import::BaseController
rescue_from Octokit::Unauthorized, with: :github_unauthorized rescue_from Octokit::Unauthorized, with: :github_unauthorized
def callback def callback
token = client.get_token(params[:code]) session[:github_access_token] = client.get_token(params[:code])
current_user.github_access_token = token
current_user.save
redirect_to status_import_github_url redirect_to status_import_github_url
end end
...@@ -39,13 +37,13 @@ class Import::GithubController < Import::BaseController ...@@ -39,13 +37,13 @@ class Import::GithubController < Import::BaseController
namespace = get_or_create_namespace || (render and return) 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 end
private private
def client def client
@client ||= Gitlab::GithubImport::Client.new(current_user.github_access_token) @client ||= Gitlab::GithubImport::Client.new(session[:github_access_token])
end end
def verify_github_import_enabled def verify_github_import_enabled
...@@ -53,7 +51,7 @@ class Import::GithubController < Import::BaseController ...@@ -53,7 +51,7 @@ class Import::GithubController < Import::BaseController
end end
def github_auth def github_auth
if current_user.github_access_token.blank? if session[:github_access_token].blank?
go_to_github_for_permissions go_to_github_for_permissions
end end
end end
...@@ -65,4 +63,10 @@ class Import::GithubController < Import::BaseController ...@@ -65,4 +63,10 @@ class Import::GithubController < Import::BaseController
def github_unauthorized def github_unauthorized
go_to_github_for_permissions go_to_github_for_permissions
end end
private
def access_params
{ github_access_token: session[:github_access_token] }
end
end end
...@@ -5,9 +5,7 @@ class Import::GitlabController < Import::BaseController ...@@ -5,9 +5,7 @@ class Import::GitlabController < Import::BaseController
rescue_from OAuth2::Error, with: :gitlab_unauthorized rescue_from OAuth2::Error, with: :gitlab_unauthorized
def callback def callback
token = client.get_token(params[:code], callback_import_gitlab_url) session[:gitlab_access_token] = client.get_token(params[:code], callback_import_gitlab_url)
current_user.gitlab_access_token = token
current_user.save
redirect_to status_import_gitlab_url redirect_to status_import_gitlab_url
end end
...@@ -36,13 +34,13 @@ class Import::GitlabController < Import::BaseController ...@@ -36,13 +34,13 @@ class Import::GitlabController < Import::BaseController
namespace = get_or_create_namespace || (render and return) 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 end
private private
def client def client
@client ||= Gitlab::GitlabImport::Client.new(current_user.gitlab_access_token) @client ||= Gitlab::GitlabImport::Client.new(session[:gitlab_access_token])
end end
def verify_gitlab_import_enabled def verify_gitlab_import_enabled
...@@ -50,7 +48,7 @@ class Import::GitlabController < Import::BaseController ...@@ -50,7 +48,7 @@ class Import::GitlabController < Import::BaseController
end end
def gitlab_auth def gitlab_auth
if current_user.gitlab_access_token.blank? if session[:gitlab_access_token].blank?
go_to_gitlab_for_permissions go_to_gitlab_for_permissions
end end
end end
...@@ -62,4 +60,10 @@ class Import::GitlabController < Import::BaseController ...@@ -62,4 +60,10 @@ class Import::GitlabController < Import::BaseController
def gitlab_unauthorized def gitlab_unauthorized
go_to_gitlab_for_permissions go_to_gitlab_for_permissions
end end
private
def access_params
{ gitlab_access_token: session[:gitlab_access_token] }
end
end end
...@@ -53,6 +53,7 @@ class Projects::HooksController < Projects::ApplicationController ...@@ -53,6 +53,7 @@ class Projects::HooksController < Projects::ApplicationController
end end
def hook_params 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
end end
...@@ -8,7 +8,7 @@ class Projects::ServicesController < Projects::ApplicationController ...@@ -8,7 +8,7 @@ class Projects::ServicesController < Projects::ApplicationController
:push_events, :issues_events, :merge_requests_events, :tag_push_events, :push_events, :issues_events, :merge_requests_events, :tag_push_events,
:note_events, :send_from_committer_email, :disable_diffs, :external_wiki_url, :note_events, :send_from_committer_email, :disable_diffs, :external_wiki_url,
:notify, :color, :notify, :color,
:server_host, :server_port, :default_irc_uri] :server_host, :server_port, :default_irc_uri, :enable_ssl_verification]
# Authorize # Authorize
before_action :authorize_admin_project! before_action :authorize_admin_project!
before_action :service, only: [:edit, :update, :test] before_action :service, only: [:edit, :update, :test]
......
...@@ -30,9 +30,14 @@ class Projects::SnippetsController < Projects::ApplicationController ...@@ -30,9 +30,14 @@ class Projects::SnippetsController < Projects::ApplicationController
def create def create
@snippet = CreateSnippetService.new(@project, current_user, @snippet = CreateSnippetService.new(@project, current_user,
snippet_params).execute snippet_params).execute
respond_with(@snippet,
location: namespace_project_snippet_path(@project.namespace, if @snippet.valid?
@project, @snippet)) respond_with(@snippet,
location: namespace_project_snippet_path(@project.namespace,
@project, @snippet))
else
render :new
end
end end
def edit def edit
......
...@@ -23,7 +23,7 @@ class SearchController < ApplicationController ...@@ -23,7 +23,7 @@ class SearchController < ApplicationController
@search_results = @search_results =
if @project if @project
unless %w(blobs notes issues merge_requests wiki_blobs). unless %w(blobs notes issues merge_requests milestones wiki_blobs).
include?(@scope) include?(@scope)
@scope = 'blobs' @scope = 'blobs'
end end
...@@ -36,7 +36,7 @@ class SearchController < ApplicationController ...@@ -36,7 +36,7 @@ class SearchController < ApplicationController
Search::SnippetService.new(current_user, params).execute Search::SnippetService.new(current_user, params).execute
else else
unless %w(projects issues merge_requests).include?(@scope) unless %w(projects issues merge_requests milestones).include?(@scope)
@scope = 'projects' @scope = 'projects'
end end
Search::GlobalService.new(current_user, params).execute Search::GlobalService.new(current_user, params).execute
......
...@@ -51,10 +51,6 @@ class UsersController < ApplicationController ...@@ -51,10 +51,6 @@ class UsersController < ApplicationController
def set_user def set_user
@user = User.find_by_username!(params[:username]) @user = User.find_by_username!(params[:username])
unless current_user || @user.public_profile?
return authenticate_user!
end
end end
def authorized_projects_ids def authorized_projects_ids
......
...@@ -2,13 +2,21 @@ class TrendingProjectsFinder ...@@ -2,13 +2,21 @@ class TrendingProjectsFinder
def execute(current_user, start_date = nil) def execute(current_user, start_date = nil)
start_date ||= Date.today - 1.month start_date ||= Date.today - 1.month
projects = projects_for(current_user)
# Determine trending projects based on comments count # Determine trending projects based on comments count
# for period of time - ex. month # for period of time - ex. month
projects.joins(:notes).where('notes.created_at > ?', start_date). trending_project_ids = Note.
select("projects.*, count(notes.id) as ncount"). select("notes.project_id, count(notes.project_id) as pcount").
group("projects.id").reorder("ncount DESC") 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 end
private private
......
...@@ -58,7 +58,7 @@ module GitlabMarkdownHelper ...@@ -58,7 +58,7 @@ module GitlabMarkdownHelper
@options = options @options = options
# see https://github.com/vmg/redcarpet#darling-i-packed-you-a-couple-renderers-for-lunch # 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 # see https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use
@markdown = Redcarpet::Markdown.new(rend, MARKDOWN_OPTIONS) @markdown = Redcarpet::Markdown.new(rend, MARKDOWN_OPTIONS)
......
...@@ -20,7 +20,7 @@ module IconsHelper ...@@ -20,7 +20,7 @@ module IconsHelper
end end
def boolean_to_icon(value) def boolean_to_icon(value)
if value.to_s == "true" if value
icon('circle', class: 'cgreen') icon('circle', class: 'cgreen')
else else
icon('power-off', class: 'clgray') icon('power-off', class: 'clgray')
......
...@@ -23,4 +23,12 @@ module PageLayoutHelper ...@@ -23,4 +23,12 @@ module PageLayoutHelper
@sidebar @sidebar
end end
end end
def fluid_layout(enabled = false)
if @fluid_layout.nil?
@fluid_layout = enabled
else
@fluid_layout
end
end
end end
# Helper methods for per-User preferences # Helper methods for per-User preferences
module PreferencesHelper 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 # Maps `dashboard` values to more user-friendly option text
DASHBOARD_CHOICES = { DASHBOARD_CHOICES = {
projects: 'Your Projects (default)', projects: 'Your Projects (default)',
...@@ -50,12 +30,11 @@ module PreferencesHelper ...@@ -50,12 +30,11 @@ module PreferencesHelper
end end
def user_application_theme def user_application_theme
theme = Gitlab::Themes.by_id(current_user.try(:theme_id)) Gitlab::Themes.for_user(current_user).css_class
theme.css_class
end end
def user_color_scheme_class def user_color_scheme
COLOR_SCHEMES[current_user.try(:color_scheme_id)] if defined?(current_user) Gitlab::ColorSchemes.for_user(current_user).css_class
end end
def prefer_readme? def prefer_readme?
......
...@@ -10,6 +10,7 @@ module SelectsHelper ...@@ -10,6 +10,7 @@ module SelectsHelper
any_user = opts[:any_user] || false any_user = opts[:any_user] || false
email_user = opts[:email_user] || false email_user = opts[:email_user] || false
first_user = opts[:first_user] && current_user ? current_user.username : false first_user = opts[:first_user] && current_user ? current_user.username : false
current_user = opts[:current_user] || false
project = opts[:project] || @project project = opts[:project] || @project
html = { html = {
...@@ -18,7 +19,8 @@ module SelectsHelper ...@@ -18,7 +19,8 @@ module SelectsHelper
'data-null-user' => null_user, 'data-null-user' => null_user,
'data-any-user' => any_user, 'data-any-user' => any_user,
'data-email-user' => email_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 unless opts[:scope] == :all
......
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
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
...@@ -8,6 +8,8 @@ module Emails ...@@ -8,6 +8,8 @@ module Emails
from: sender(@issue.author_id), from: sender(@issue.author_id),
to: recipient(recipient_id), to: recipient(recipient_id),
subject: subject("#{@issue.title} (##{@issue.iid})")) subject: subject("#{@issue.title} (##{@issue.iid})"))
SentNotification.record(@issue, recipient_id, reply_key)
end end
def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id, updated_by_user_id) def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id, updated_by_user_id)
...@@ -19,6 +21,8 @@ module Emails ...@@ -19,6 +21,8 @@ module Emails
from: sender(updated_by_user_id), from: sender(updated_by_user_id),
to: recipient(recipient_id), to: recipient(recipient_id),
subject: subject("#{@issue.title} (##{@issue.iid})")) subject: subject("#{@issue.title} (##{@issue.iid})"))
SentNotification.record(@issue, recipient_id, reply_key)
end end
def closed_issue_email(recipient_id, issue_id, updated_by_user_id) def closed_issue_email(recipient_id, issue_id, updated_by_user_id)
...@@ -30,6 +34,8 @@ module Emails ...@@ -30,6 +34,8 @@ module Emails
from: sender(updated_by_user_id), from: sender(updated_by_user_id),
to: recipient(recipient_id), to: recipient(recipient_id),
subject: subject("#{@issue.title} (##{@issue.iid})")) subject: subject("#{@issue.title} (##{@issue.iid})"))
SentNotification.record(@issue, recipient_id, reply_key)
end end
def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id) def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id)
...@@ -42,6 +48,8 @@ module Emails ...@@ -42,6 +48,8 @@ module Emails
from: sender(updated_by_user_id), from: sender(updated_by_user_id),
to: recipient(recipient_id), to: recipient(recipient_id),
subject: subject("#{@issue.title} (##{@issue.iid})")) subject: subject("#{@issue.title} (##{@issue.iid})"))
SentNotification.record(@issue, recipient_id, reply_key)
end end
end end
end end
...@@ -10,6 +10,8 @@ module Emails ...@@ -10,6 +10,8 @@ module Emails
from: sender(@merge_request.author_id), from: sender(@merge_request.author_id),
to: recipient(recipient_id), to: recipient(recipient_id),
subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
SentNotification.record(@merge_request, recipient_id, reply_key)
end end
def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id, updated_by_user_id) def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id, updated_by_user_id)
...@@ -23,6 +25,8 @@ module Emails ...@@ -23,6 +25,8 @@ module Emails
from: sender(updated_by_user_id), from: sender(updated_by_user_id),
to: recipient(recipient_id), to: recipient(recipient_id),
subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
SentNotification.record(@merge_request, recipient_id, reply_key)
end end
def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id) def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id)
...@@ -36,6 +40,8 @@ module Emails ...@@ -36,6 +40,8 @@ module Emails
from: sender(updated_by_user_id), from: sender(updated_by_user_id),
to: recipient(recipient_id), to: recipient(recipient_id),
subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
SentNotification.record(@merge_request, recipient_id, reply_key)
end end
def merged_merge_request_email(recipient_id, merge_request_id, updated_by_user_id) def merged_merge_request_email(recipient_id, merge_request_id, updated_by_user_id)
...@@ -48,6 +54,8 @@ module Emails ...@@ -48,6 +54,8 @@ module Emails
from: sender(updated_by_user_id), from: sender(updated_by_user_id),
to: recipient(recipient_id), to: recipient(recipient_id),
subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
SentNotification.record(@merge_request, recipient_id, reply_key)
end end
def merge_request_status_email(recipient_id, merge_request_id, status, updated_by_user_id) def merge_request_status_email(recipient_id, merge_request_id, status, updated_by_user_id)
...@@ -58,52 +66,12 @@ module Emails ...@@ -58,52 +66,12 @@ module Emails
@target_url = namespace_project_merge_request_url(@project.namespace, @target_url = namespace_project_merge_request_url(@project.namespace,
@project, @project,
@merge_request) @merge_request)
set_reference("merge_request_#{merge_request_id}")
mail_answer_thread(@merge_request, mail_answer_thread(@merge_request,
from: sender(updated_by_user_id), from: sender(updated_by_user_id),
to: recipient(recipient_id), to: recipient(recipient_id),
subject: subject("#{@merge_request.title} (##{@merge_request.iid}) #{@mr_status}")) subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
end
end
# Over rides default behaviour to show source/target SentNotification.record(@merge_request, recipient_id, reply_key)
# 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}"
end end
subject << " | " + extra.join(' | ') if extra.present?
subject
end end
end end
...@@ -11,6 +11,8 @@ module Emails ...@@ -11,6 +11,8 @@ module Emails
from: sender(@note.author_id), from: sender(@note.author_id),
to: recipient(recipient_id), to: recipient(recipient_id),
subject: subject("#{@commit.title} (#{@commit.short_id})")) subject: subject("#{@commit.title} (#{@commit.short_id})"))
SentNotification.record(@commit, recipient_id, reply_key)
end end
def note_issue_email(recipient_id, note_id) def note_issue_email(recipient_id, note_id)
...@@ -24,6 +26,8 @@ module Emails ...@@ -24,6 +26,8 @@ module Emails
from: sender(@note.author_id), from: sender(@note.author_id),
to: recipient(recipient_id), to: recipient(recipient_id),
subject: subject("#{@issue.title} (##{@issue.iid})")) subject: subject("#{@issue.title} (##{@issue.iid})"))
SentNotification.record(@issue, recipient_id, reply_key)
end end
def note_merge_request_email(recipient_id, note_id) def note_merge_request_email(recipient_id, note_id)
...@@ -38,6 +42,8 @@ module Emails ...@@ -38,6 +42,8 @@ module Emails
from: sender(@note.author_id), from: sender(@note.author_id),
to: recipient(recipient_id), to: recipient(recipient_id),
subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
SentNotification.record(@merge_request, recipient_id, reply_key)
end end
end end
end end
class Notify < ActionMailer::Base class Notify < BaseMailer
include ActionDispatch::Routing::PolymorphicRoutes include ActionDispatch::Routing::PolymorphicRoutes
include Emails::Issues include Emails::Issues
...@@ -8,22 +8,9 @@ class Notify < ActionMailer::Base ...@@ -8,22 +8,9 @@ class Notify < ActionMailer::Base
include Emails::Profile include Emails::Profile
include Emails::Groups include Emails::Groups
add_template_helper ApplicationHelper
add_template_helper GitlabMarkdownHelper
add_template_helper MergeRequestsHelper add_template_helper MergeRequestsHelper
add_template_helper EmailsHelper 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) def test_email(recipient_email, subject, body)
mail(to: recipient_email, mail(to: recipient_email,
subject: subject, subject: subject,
...@@ -48,13 +35,6 @@ class Notify < ActionMailer::Base ...@@ -48,13 +35,6 @@ class Notify < ActionMailer::Base
private 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) def can_send_from_user_email?(sender)
sender_domain = sender.email.split("@").last sender_domain = sender.email.split("@").last
self.class.allowed_email_domains.include?(sender_domain) self.class.allowed_email_domains.include?(sender_domain)
...@@ -85,14 +65,6 @@ class Notify < ActionMailer::Base ...@@ -85,14 +65,6 @@ class Notify < ActionMailer::Base
@current_user.notification_email @current_user.notification_email
end 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 # Formats arguments into a String suitable for use as an email subject
# #
# extra - Extra Strings to be inserted into the subject # extra - Extra Strings to be inserted into the subject
...@@ -126,14 +98,37 @@ class Notify < ActionMailer::Base ...@@ -126,14 +98,37 @@ class Notify < ActionMailer::Base
"<#{model_name}_#{model.id}@#{Gitlab.config.gitlab.host}>" "<#{model_name}_#{model.id}@#{Gitlab.config.gitlab.host}>"
end 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, # Send an email that starts a new conversation thread,
# with headers suitable for grouping by thread in email clients. # with headers suitable for grouping by thread in email clients.
# #
# See: mail_answer_thread # See: mail_answer_thread
def mail_new_thread(model, headers = {}, &block) def mail_new_thread(model, headers = {})
headers['Message-ID'] = message_id(model) headers['Message-ID'] = message_id(model)
headers['X-GitLab-Project'] = "#{@project.name} | " if @project
mail(headers, &block) mail_thread(model, headers)
end end
# Send an email that responds to an existing conversation thread, # Send an email that responds to an existing conversation thread,
...@@ -144,19 +139,17 @@ class Notify < ActionMailer::Base ...@@ -144,19 +139,17 @@ class Notify < ActionMailer::Base
# * have a subject that begin by 'Re: ' # * have a subject that begin by 'Re: '
# * have a 'In-Reply-To' or 'References' header that references the original 'Message-ID' # * 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['In-Reply-To'] = message_id(model)
headers['References'] = message_id(model) headers['References'] = message_id(model)
headers['X-GitLab-Project'] = "#{@project.name} | " if @project
if headers[:subject] headers[:subject].prepend('Re: ') if headers[:subject]
headers[:subject].prepend('Re: ')
end
mail(headers, &block) mail_thread(model, headers)
end end
def can? def reply_key
Ability.abilities.allowed?(user, action, subject) @reply_key ||= Gitlab::ReplyByEmail.reply_key
end end
end end
...@@ -17,6 +17,7 @@ require 'carrierwave/orm/activerecord' ...@@ -17,6 +17,7 @@ require 'carrierwave/orm/activerecord'
require 'file_size_validator' require 'file_size_validator'
class Group < Namespace class Group < Namespace
include Gitlab::ConfigHelper
include Referable include Referable
has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember' has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember'
......
...@@ -25,6 +25,7 @@ class WebHook < ActiveRecord::Base ...@@ -25,6 +25,7 @@ class WebHook < ActiveRecord::Base
default_value_for :note_events, false default_value_for :note_events, false
default_value_for :merge_requests_events, false default_value_for :merge_requests_events, false
default_value_for :tag_push_events, false default_value_for :tag_push_events, false
default_value_for :enable_ssl_verification, false
# HTTParty timeout # HTTParty timeout
default_timeout Gitlab.config.gitlab.webhook_timeout default_timeout Gitlab.config.gitlab.webhook_timeout
...@@ -41,7 +42,7 @@ class WebHook < ActiveRecord::Base ...@@ -41,7 +42,7 @@ class WebHook < ActiveRecord::Base
"Content-Type" => "application/json", "Content-Type" => "application/json",
"X-Gitlab-Event" => hook_name.singularize.titleize "X-Gitlab-Event" => hook_name.singularize.titleize
}, },
verify: false) verify: enable_ssl_verification)
else else
post_url = url.gsub("#{parsed_url.userinfo}@", "") post_url = url.gsub("#{parsed_url.userinfo}@", "")
auth = { auth = {
...@@ -54,7 +55,7 @@ class WebHook < ActiveRecord::Base ...@@ -54,7 +55,7 @@ class WebHook < ActiveRecord::Base
"Content-Type" => "application/json", "Content-Type" => "application/json",
"X-Gitlab-Event" => hook_name.singularize.titleize "X-Gitlab-Event" => hook_name.singularize.titleize
}, },
verify: false, verify: enable_ssl_verification,
basic_auth: auth) basic_auth: auth)
end end
rescue SocketError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e rescue SocketError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e
......
...@@ -47,6 +47,13 @@ class Milestone < ActiveRecord::Base ...@@ -47,6 +47,13 @@ class Milestone < ActiveRecord::Base
state :active state :active
end end
class << self
def search(query)
query = "%#{query}%"
where("title like ? or description like ?", query, query)
end
end
def expired? def expired?
if due_date if due_date
due_date.past? due_date.past?
...@@ -54,7 +61,7 @@ class Milestone < ActiveRecord::Base ...@@ -54,7 +61,7 @@ class Milestone < ActiveRecord::Base
false false
end end
end end
def open_items_count def open_items_count
self.issues.opened.count + self.merge_requests.opened.count self.issues.opened.count + self.merge_requests.opened.count
end end
......
...@@ -23,7 +23,7 @@ require "addressable/uri" ...@@ -23,7 +23,7 @@ require "addressable/uri"
class BuildkiteService < CiService class BuildkiteService < CiService
ENDPOINT = "https://buildkite.com" 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 :project_url, presence: true, if: :activated?
validates :token, presence: true, if: :activated? validates :token, presence: true, if: :activated?
...@@ -37,6 +37,7 @@ class BuildkiteService < CiService ...@@ -37,6 +37,7 @@ class BuildkiteService < CiService
def compose_service_hook def compose_service_hook
hook = service_hook || build_service_hook hook = service_hook || build_service_hook
hook.url = webhook_url hook.url = webhook_url
hook.enable_ssl_verification = enable_ssl_verification
hook.save hook.save
end end
...@@ -96,7 +97,11 @@ class BuildkiteService < CiService ...@@ -96,7 +97,11 @@ class BuildkiteService < CiService
{ type: 'text', { type: 'text',
name: 'project_url', name: 'project_url',
placeholder: "#{ENDPOINT}/example/project" } placeholder: "#{ENDPOINT}/example/project" },
{ type: 'checkbox',
name: 'enable_ssl_verification',
title: "Enable SSL verification" }
] ]
end end
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
class GitlabCiService < CiService class GitlabCiService < CiService
API_PREFIX = "api/v1" API_PREFIX = "api/v1"
prop_accessor :project_url, :token prop_accessor :project_url, :token, :enable_ssl_verification
validates :project_url, validates :project_url,
presence: true, presence: true,
format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }, if: :activated? format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }, if: :activated?
...@@ -34,6 +34,7 @@ class GitlabCiService < CiService ...@@ -34,6 +34,7 @@ class GitlabCiService < CiService
def compose_service_hook def compose_service_hook
hook = service_hook || build_service_hook hook = service_hook || build_service_hook
hook.url = [project_url, "/build", "?token=#{token}"].join("") hook.url = [project_url, "/build", "?token=#{token}"].join("")
hook.enable_ssl_verification = enable_ssl_verification
hook.save hook.save
end end
...@@ -136,7 +137,8 @@ class GitlabCiService < CiService ...@@ -136,7 +137,8 @@ class GitlabCiService < CiService
def fields def fields
[ [
{ type: 'text', name: 'token', placeholder: 'GitLab CI project specific token' }, { 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 end
......
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
...@@ -637,10 +637,6 @@ class User < ActiveRecord::Base ...@@ -637,10 +637,6 @@ class User < ActiveRecord::Base
email.start_with?('temp-email-for-oauth') email.start_with?('temp-email-for-oauth')
end end
def public_profile?
authorized_projects.public_only.any?
end
def avatar_url(size = nil) def avatar_url(size = nil)
if avatar.present? if avatar.present?
[gitlab_config.url, avatar.url].join [gitlab_config.url, avatar.url].join
......
...@@ -78,24 +78,29 @@ class GitPushService ...@@ -78,24 +78,29 @@ class GitPushService
# For push with 1k commits it prevents 900+ requests in database # For push with 1k commits it prevents 900+ requests in database
author = nil 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 if issues_to_close.present? && is_default_branch
author ||= commit_user(commit) author ||= commit_user(commit)
actually_closed_issues = issues_to_close
issues_to_close.each do |issue| issues_to_close.each do |issue|
Issues::CloseService.new(project, author, {}).execute(issue, commit) Issues::CloseService.new(project, author, {}).execute(issue, commit)
end end
end end
if project.default_issues_tracker? if project.default_issues_tracker?
create_cross_reference_notes(commit, issues_to_close) create_cross_reference_notes(commit, actually_closed_issues)
end end
end end
end end
def create_cross_reference_notes(commit, issues_to_close) 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 # Create cross-reference notes for any other references than those given in issues_to_close.
# issue-closing phrase, or have already been mentioned from this commit (probably from this commit # Omit any issues that were referenced in an issue-closing phrase, or have already been
# being pushed to a different branch). # mentioned from this commit (probably from this commit being pushed to a different branch).
refs = commit.references(project, user) - issues_to_close refs = commit.references(project, user) - issues_to_close
refs.reject! { |r| commit.has_mentioned?(r) } refs.reject! { |r| commit.has_mentioned?(r) }
......
module MergeRequests module MergeRequests
class CreateService < MergeRequests::BaseService class CreateService < MergeRequests::BaseService
def execute 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 filter_params
label_params = params[:label_ids] label_params = params[:label_ids]
merge_request = MergeRequest.new(params.except(:label_ids)) merge_request = MergeRequest.new(params.except(:label_ids))
merge_request.source_project = project merge_request.source_project = source_project
merge_request.target_project ||= project merge_request.target_project ||= source_project
merge_request.author = current_user merge_request.author = current_user
if merge_request.save if merge_request.save
......
...@@ -13,9 +13,9 @@ module Projects ...@@ -13,9 +13,9 @@ module Projects
filename = uploader.image? ? uploader.file.basename : uploader.file.filename filename = uploader.image? ? uploader.file.basename : uploader.file.filename
{ {
'alt' => filename, alt: filename,
'url' => uploader.secure_url, url: uploader.secure_url,
'is_image' => uploader.image? is_image: uploader.image?
} }
end end
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
%tr %tr
%td %td
- if reporter - if reporter
= link_to reporter.name, [:admin, reporter] = link_to reporter.name, reporter
- else - else
(removed) (removed)
%td %td
...@@ -12,12 +12,15 @@ ...@@ -12,12 +12,15 @@
= abuse_report.message = abuse_report.message
%td %td
- if user - if user
= link_to user.name, [:admin, user] = link_to user.name, user
- else - else
(removed) (removed)
%td %td
- if user - 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 & report', admin_abuse_report_path(abuse_report, remove_user: true),
= 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" 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 %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"
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
%th Reported at %th Reported at
%th Message %th Message
%th User %th User
%th %th Primary action
%th %th
= render @abuse_reports = render @abuse_reports
= paginate @abuse_reports = paginate @abuse_reports
......
...@@ -55,6 +55,10 @@ ...@@ -55,6 +55,10 @@
OmniAuth OmniAuth
%span.light.pull-right %span.light.pull-right
= boolean_to_icon Gitlab.config.omniauth.enabled = boolean_to_icon Gitlab.config.omniauth.enabled
%p
Reply by email
%span.light.pull-right
= boolean_to_icon Gitlab::ReplyByEmail.enabled?
.col-md-4 .col-md-4
%h4 %h4
Components Components
......
...@@ -18,6 +18,13 @@ ...@@ -18,6 +18,13 @@
= f.label :url, "URL:", class: 'control-label' = f.label :url, "URL:", class: 'control-label'
.col-sm-10 .col-sm-10
= f.text_field :url, class: "form-control" = 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 .form-actions
= f.submit "Add System Hook", class: "btn btn-create" = f.submit "Add System Hook", class: "btn btn-create"
%hr %hr
...@@ -32,6 +39,7 @@ ...@@ -32,6 +39,7 @@
.list-item-name .list-item-name
= link_to admin_hook_path(hook) do = link_to admin_hook_path(hook) do
%strong= hook.url %strong= hook.url
%p SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}
.pull-right .pull-right
= link_to 'Test Hook', admin_hook_test_path(hook), class: "btn btn-sm" = link_to 'Test Hook', admin_hook_test_path(hook), class: "btn btn-sm"
......
.panel.panel-default .projects-list-holder
.panel-heading.clearfix .projects-search-form
.input-group .input-group
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control' = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control'
- if current_user.can_create_project? - if current_user.can_create_project?
...@@ -7,4 +7,4 @@ ...@@ -7,4 +7,4 @@
= link_to new_project_path, class: 'btn btn-success' do = link_to new_project_path, class: 'btn btn-success' do
New project New project
= render 'shared/projects_list', projects: @projects, projects_limit: 20 = render 'shared/projects/list', projects: @projects
= 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'
...@@ -8,32 +8,9 @@ ...@@ -8,32 +8,9 @@
= link_to new_group_path, class: "btn btn-new btn-sm" do = link_to new_group_path, class: "btn btn-new btn-sm" do
%i.fa.fa-plus %i.fa.fa-plus
New Group New Group
.panel.panel-default %ul.bordered-list
.panel-heading - @group_members.each do |group_member|
%strong Groups - group = group_member.group
(#{@group_members.count}) = render 'shared/groups/group', group: group, group_member: group_member
%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")}
= paginate @group_members = paginate @group_members
...@@ -5,10 +5,10 @@ ...@@ -5,10 +5,10 @@
= render 'shared/show_aside' = render 'shared/show_aside'
.dashboard.row .dashboard.row
%section.activities.col-md-8 %section.activities.col-md-7
= render 'dashboard/activities' = render 'dashboard/activities'
%aside.col-md-4 %aside.col-md-5
.panel.panel-default .panel.panel-default.projects-list-holder
.panel-heading.clearfix .panel-heading.clearfix
.input-group .input-group
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control' = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control'
...@@ -17,8 +17,7 @@ ...@@ -17,8 +17,7 @@
= link_to new_project_path, class: 'btn btn-success' do = link_to new_project_path, class: 'btn btn-success' do
New project New project
= render 'shared/projects_list', projects: @projects, = render 'shared/projects/list', projects: @projects, projects_limit: 20
projects_limit: 20, stars: true, avatar: false
- else - else
%h3 You don't have starred projects yet %h3 You don't have starred projects yet
......
...@@ -4,14 +4,10 @@ ...@@ -4,14 +4,10 @@
= render 'dashboard/projects_head' = render 'dashboard/projects_head'
- if @projects.any? - if @last_push
= render 'shared/show_aside' = render "events/event_last_push", event: @last_push
.dashboard.row
%section.activities.col-md-8
= render 'activities'
%aside.col-md-4
= render 'sidebar'
- if @projects.any?
= render 'projects'
- else - else
= render "zero_authorized_projects" = render "zero_authorized_projects"
%p
Unfortunately, your email message to GitLab could not be processed.
= markdown @reason
Unfortunately, your email message to GitLab could not be processed.
= @reason
...@@ -9,6 +9,6 @@ ...@@ -9,6 +9,6 @@
#{time_ago_with_tooltip(event.created_at)} #{time_ago_with_tooltip(event.created_at)}
.pull-right .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 Create Merge Request
%hr %hr
...@@ -32,17 +32,7 @@ ...@@ -32,17 +32,7 @@
%ul.bordered-list %ul.bordered-list
- @groups.each do |group| - @groups.each do |group|
%li = render 'shared/groups/group', group: group
.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')}
- unless @groups.present? - unless @groups.present?
.nothing-here-block No public groups .nothing-here-block No public groups
......
%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
- if projects.any?
.public-projects
= render 'shared/projects/list', projects: projects
- else
.nothing-here-block
No such projects
...@@ -4,10 +4,5 @@ ...@@ -4,10 +4,5 @@
.clearfix .clearfix
= render 'filter' = render 'filter'
%br %br
.public-projects = render 'projects', projects: @projects
%ul.bordered-list.top-list = paginate @projects, theme: "gitlab"
= render @projects
- unless @projects.present?
.nothing-here-block No public projects
= paginate @projects, theme: "gitlab"
...@@ -7,8 +7,5 @@ ...@@ -7,8 +7,5 @@
See most starred projects See most starred projects
.pull-right .pull-right
= render 'explore/projects/dropdown' = render 'explore/projects/dropdown'
.public-projects = render 'projects', projects: @starred_projects
%ul.bordered-list
= render @starred_projects
= paginate @starred_projects, theme: 'gitlab' = paginate @starred_projects, theme: 'gitlab'
...@@ -13,6 +13,4 @@ ...@@ -13,6 +13,4 @@
See most discussed projects for last month See most discussed projects for last month
.pull-right .pull-right
= render 'explore/projects/dropdown' = render 'explore/projects/dropdown'
.public-projects = render 'projects', projects: @trending_projects
%ul.bordered-list
= render @trending_projects
.panel.panel-default .panel.panel-default.projects-list-holder
.panel-heading.clearfix .panel-heading.clearfix
.input-group .input-group
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control' = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control'
...@@ -7,4 +7,4 @@ ...@@ -7,4 +7,4 @@
= link_to new_project_path(namespace_id: @group.id), class: 'btn btn-success' do = link_to new_project_path(namespace_id: @group.id), class: 'btn btn-success' do
New project New project
= render 'shared/projects_list', projects: @projects, projects_limit: 20 = render 'shared/projects/list', projects: @projects, projects_limit: 20
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
= render 'shared/show_aside' = render 'shared/show_aside'
.row .row
%section.activities.col-md-8 %section.activities.col-md-7
.hidden-xs .hidden-xs
- if current_user - if current_user
= render "events/event_last_push", event: @last_push = render "events/event_last_push", event: @last_push
...@@ -33,5 +33,5 @@ ...@@ -33,5 +33,5 @@
.content_list .content_list
= spinner = spinner
%aside.side.col-md-4 %aside.side.col-md-5
= render "projects", projects: @projects = render "projects", projects: @projects
.page-with-sidebar{ class: nav_sidebar_class } .page-with-sidebar{ class: nav_sidebar_class }
= render "layouts/broadcast" = render "layouts/broadcast"
.sidebar-wrapper.nicescroll .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 - if defined?(sidebar) && sidebar
= render "layouts/nav/#{sidebar}" = render "layouts/nav/#{sidebar}"
- elsif current_user - elsif current_user
...@@ -13,7 +18,7 @@ ...@@ -13,7 +18,7 @@
.username .username
= current_user.username = current_user.username
.content-wrapper .content-wrapper
.container-fluid %div{ class: fluid_layout ? "container-fluid" : "container-fluid container-limited" }
.content .content
= render "layouts/flash" = render "layouts/flash"
.clearfix .clearfix
......
%header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class } %header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class }
.container %div{ class: fluid_layout ? "container-fluid" : "container-fluid container-limited" }
.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
.header-content .header-content
%button.navbar-toggle{type: 'button'} %button.navbar-toggle{type: 'button'}
%span.sr-only Toggle navigation %span.sr-only Toggle navigation
...@@ -17,15 +12,6 @@ ...@@ -17,15 +12,6 @@
%li.visible-sm.visible-xs %li.visible-sm.visible-xs
= link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom'} do = link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom'} do
= icon('search') = 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? - if current_user.is_admin?
%li %li
= link_to admin_root_path, title: 'Admin area', data: {toggle: 'tooltip', placement: 'bottom'} do = link_to admin_root_path, title: 'Admin area', data: {toggle: 'tooltip', placement: 'bottom'} do
...@@ -34,9 +20,6 @@ ...@@ -34,9 +20,6 @@
%li.hidden-xs %li.hidden-xs
= link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom'} do = link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom'} do
= icon('plus fw') = icon('plus fw')
-#%li
= link_to profile_path, title: 'Profile settings', data: {toggle: 'tooltip', placement: 'bottom'} do
= icon('cog fw')
%li %li
= link_to destroy_user_session_path, class: 'logout', method: :delete, title: 'Sign out', data: {toggle: 'tooltip', placement: 'bottom'} do = link_to destroy_user_session_path, class: 'logout', method: :delete, title: 'Sign out', data: {toggle: 'tooltip', placement: 'bottom'} do
= icon('sign-out') = icon('sign-out')
......
%header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class } %header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class }
.container %div{ class: fluid_layout ? "container-fluid" : "container-fluid container-limited" }
.header-logo
= link_to explore_root_path, class: "home" do
= brand_header_logo
.gitlab-text-container
%h3 GitLab
.header-content .header-content
- unless current_controller?('sessions') - unless current_controller?('sessions')
.pull-right .pull-right
......
%ul.nav.nav-sidebar %ul.nav.nav-sidebar
= nav_link(path: ['dashboard#show', 'root#show', 'projects#trending', 'projects#starred', 'projects#index'], html_options: {class: 'home'}) do = 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 = 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 %span
Projects 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 = nav_link(controller: :groups) do
= link_to (current_user ? dashboard_groups_path : explore_groups_path), title: 'Groups', data: {placement: 'right'} do = link_to (current_user ? dashboard_groups_path : explore_groups_path), title: 'Groups', data: {placement: 'right'} do
= icon('group fw') = icon('group fw')
...@@ -29,7 +34,7 @@ ...@@ -29,7 +34,7 @@
%span.count= current_user.assigned_merge_requests.opened.count %span.count= current_user.assigned_merge_requests.opened.count
= nav_link(controller: :snippets) do = nav_link(controller: :snippets) do
= link_to (current_user ? user_snippets_path(current_user) : snippets_path), title: 'Your snippets', data: {placement: 'right'} 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 %span
Snippets Snippets
- if current_user - if current_user
......
...@@ -100,7 +100,7 @@ ...@@ -100,7 +100,7 @@
- if project_nav_tab? :snippets - if project_nav_tab? :snippets
= nav_link(controller: :snippets) do = nav_link(controller: :snippets) do
= link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets', data: {placement: 'right'} 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 %span
Snippets Snippets
......
...@@ -36,7 +36,11 @@ ...@@ -36,7 +36,11 @@
&mdash; &mdash;
%br %br
- if @target_url - 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 = email_action @target_url
- if @project && !@disable_footer - 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. 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.
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
= f.radio_button :notification_level, Notification::N_WATCH = f.radio_button :notification_level, Notification::N_WATCH
.level-title .level-title
Watch Watch
%p You will receive all notifications from projects in which you participate %p You will receive notifications for any activity
.form-actions .form-actions
= f.submit 'Save changes', class: "btn btn-create" = f.submit 'Save changes', class: "btn btn-create"
......
...@@ -22,11 +22,11 @@ ...@@ -22,11 +22,11 @@
.panel-heading .panel-heading
Syntax highlighting theme Syntax highlighting theme
.panel-body .panel-body
- color_schemes.each do |color_scheme_id, color_scheme| - Gitlab::ColorSchemes.each do |scheme|
= label_tag do = label_tag do
.preview= image_tag "#{color_scheme}-scheme-preview.png" .preview= image_tag "#{scheme.css_class}-scheme-preview.png"
= f.radio_button :color_scheme_id, color_scheme_id = f.radio_button :color_scheme_id, scheme.id
= color_scheme.tr('-_', ' ').titleize = scheme.name
.panel.panel-default .panel.panel-default
.panel-heading .panel-heading
......
...@@ -100,11 +100,6 @@ ...@@ -100,11 +100,6 @@
%hr %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" = 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 .row
.col-md-7 .col-md-7
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
.project-home-panel.clearfix{:class => ("empty-project" if empty_repo)} .project-home-panel.clearfix{:class => ("empty-project" if empty_repo)}
.project-identicon-holder .project-identicon-holder
= project_icon(@project, alt: '', class: 'project-avatar avatar s90') = project_icon(@project, alt: '', class: 'project-avatar avatar s90')
.project-home-desc.lead .project-home-desc
%h1= @project.name %h1= @project.name
- if @project.description.present? - if @project.description.present?
= markdown(@project.description, pipeline: :description) = markdown(@project.description, pipeline: :description)
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
.light .light
= commit_author_link(commit, avatar: false) = commit_author_link(commit, avatar: false)
authored authored
#{time_ago_with_tooltip(commit.committed_date)} #{time_ago_with_tooltip(commit.committed_date, skip_js: true)}
%td.lines.blame-numbers %td.lines.blame-numbers
%pre %pre
- line_count = blame_group[:lines].count - line_count = blame_group[:lines].count
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
%span.dropdown %span.dropdown
%a.dropdown-toggle.btn.btn-new{href: '#', "data-toggle" => "dropdown"} %a.dropdown-toggle.btn.btn-new{href: '#', "data-toggle" => "dropdown"}
= icon('plus') = icon('plus')
%ul.dropdown-menu %ul.dropdown-menu.dropdown-menu-right.project-home-dropdown
- if can?(current_user, :create_issue, @project) - if can?(current_user, :create_issue, @project)
%li %li
= link_to url_for_new_issue do = link_to url_for_new_issue do
......
- page_title "Deploy Keys" - page_title "Deploy Keys"
%h3.page-title %h3.page-title
Deploy keys allow read-only access to the repository Deploy keys allow read-only access to the repository
......
- if params[:view] == 'parallel'
- fluid_layout true
.prepend-top-20.append-bottom-20 .prepend-top-20.append-bottom-20
.pull-right .pull-right
.btn-group .btn-group
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
Too many changes to show. Too many changes to show.
.pull-right .pull-right
- unless diff_hard_limit_enabled? - 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) or current_controller?(:merge_requests)
- if current_controller?(:commit) - if current_controller?(:commit)
......
...@@ -50,39 +50,42 @@ ...@@ -50,39 +50,42 @@
datasets : [{ datasets : [{
fillColor : "rgba(220,220,220,0.5)", fillColor : "rgba(220,220,220,0.5)",
strokeColor : "rgba(220,220,220,1)", strokeColor : "rgba(220,220,220,1)",
pointColor : "rgba(220,220,220,1)", barStrokeWidth: 1,
pointStrokeColor : "#EEE", barValueSpacing: 1,
barDatasetSpacing: 1,
data : #{@commits_per_time.values.to_json} data : #{@commits_per_time.values.to_json}
}] }]
} }
ctx = $("#hour-chart").get(0).getContext("2d"); 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 = { data = {
labels : #{@commits_per_week_days.keys.to_json}, labels : #{@commits_per_week_days.keys.to_json},
datasets : [{ datasets : [{
fillColor : "rgba(220,220,220,0.5)", fillColor : "rgba(220,220,220,0.5)",
strokeColor : "rgba(220,220,220,1)", strokeColor : "rgba(220,220,220,1)",
pointColor : "rgba(220,220,220,1)", barStrokeWidth: 1,
pointStrokeColor : "#EEE", barValueSpacing: 1,
barDatasetSpacing: 1,
data : #{@commits_per_week_days.values.to_json} data : #{@commits_per_week_days.values.to_json}
}] }]
} }
ctx = $("#weekday-chart").get(0).getContext("2d"); 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 = { data = {
labels : #{@commits_per_month.keys.to_json}, labels : #{@commits_per_month.keys.to_json},
datasets : [{ datasets : [{
fillColor : "rgba(220,220,220,0.5)", fillColor : "rgba(220,220,220,0.5)",
strokeColor : "rgba(220,220,220,1)", strokeColor : "rgba(220,220,220,1)",
pointColor : "rgba(220,220,220,1)", barStrokeWidth: 1,
pointStrokeColor : "#EEE", barValueSpacing: 1,
barDatasetSpacing: 1,
data : #{@commits_per_month.values.to_json} data : #{@commits_per_month.values.to_json}
}] }]
} }
ctx = $("#month-chart").get(0).getContext("2d"); 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})
...@@ -55,6 +55,13 @@ ...@@ -55,6 +55,13 @@
%strong Merge Request events %strong Merge Request events
%p.light %p.light
This url will be triggered when a merge request is created 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 .form-actions
= f.submit "Add Web Hook", class: "btn btn-create" = f.submit "Add Web Hook", class: "btn btn-create"
...@@ -74,3 +81,4 @@ ...@@ -74,3 +81,4 @@
- %w(push_events tag_push_events issues_events note_events merge_requests_events).each do |trigger| - %w(push_events tag_push_events issues_events note_events merge_requests_events).each do |trigger|
- if hook.send(trigger) - if hook.send(trigger)
%span.label.label-gray= trigger.titleize %span.label.label-gray= trigger.titleize
SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}
- page_title "#{@merge_request.title} (##{@merge_request.iid})", "Merge Requests" - 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{'data-url' => merge_request_path(@merge_request)}
.merge-request-details.issuable-details .merge-request-details.issuable-details
= render "projects/merge_requests/show/mr_title" = render "projects/merge_requests/show/mr_title"
......
%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)}
...@@ -8,9 +8,8 @@ ...@@ -8,9 +8,8 @@
%p.light %p.light
Share code pastes with others out of git repository Share code pastes with others out of git repository
%hr
%ul.bordered-list %ul.bordered-list
= render partial: "projects/snippets/snippet", collection: @snippets = render partial: "shared/snippets/snippet", collection: @snippets
- if @snippets.empty? - if @snippets.empty?
%li %li
.nothing-here-block Nothing here. .nothing-here-block Nothing here.
%ul.nav.nav-pills.search-filter %ul.nav.nav-tabs.search-filter
- if @project - if @project
%li{class: ("active" if @scope == 'blobs')} %li{class: ("active" if @scope == 'blobs')}
= link_to search_filter_path(scope: 'blobs') do = link_to search_filter_path(scope: 'blobs') do
...@@ -21,6 +21,13 @@ ...@@ -21,6 +21,13 @@
Merge requests Merge requests
%span.badge %span.badge
= @search_results.merge_requests_count = @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')} %li{class: ("active" if @scope == 'notes')}
= link_to search_filter_path(scope: 'notes') do = link_to search_filter_path(scope: 'notes') do
= icon('comments fw') = icon('comments fw')
...@@ -74,4 +81,11 @@ ...@@ -74,4 +81,11 @@
Merge requests Merge requests
%span.badge %span.badge
= @search_results.merge_requests_count = @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
.dropdown.inline .dropdown.inline
%button.dropdown-toggle.btn.btn{type: 'button', 'data-toggle' => 'dropdown'} %button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'}
%i.fa.fa-tags
%span.light Group: %span.light Group:
- if @group.present? - if @group.present?
%strong= @group.name %strong= @group.name
...@@ -17,8 +16,7 @@ ...@@ -17,8 +16,7 @@
= group.name = group.name
.dropdown.inline.prepend-left-10.project-filter .dropdown.inline.prepend-left-10.project-filter
%button.dropdown-toggle.btn.btn{type: 'button', 'data-toggle' => 'dropdown'} %button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'}
%i.fa.fa-tags
%span.light Project: %span.light Project:
- if @project.present? - if @project.present?
%strong= @project.name_with_namespace %strong= @project.name_with_namespace
......
= 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 :project_id, params[:project_id]
= hidden_field_tag :group_id, params[:group_id] = hidden_field_tag :group_id, params[:group_id]
= hidden_field_tag :snippets, params[:snippets] = hidden_field_tag :snippets, params[:snippets]
= hidden_field_tag :scope, params[:scope] = hidden_field_tag :scope, params[:scope]
.search-holder.clearfix .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 = 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' - unless params[:snippets].eql? 'true'
.pull-right %br
= render 'filter' = render 'filter'
- if @search_results.empty? - if @search_results.empty?
= render partial: "search/results/empty" = render partial: "search/results/empty"
- else - else
.light %p.light
Search results for Search results for
%code %code
= @search_term = @search_term
...@@ -11,10 +11,13 @@ ...@@ -11,10 +11,13 @@
- elsif @group - elsif @group
in group #{link_to @group.name, @group} in group #{link_to @group.name, @group}
%br
.results.prepend-top-10 .results.prepend-top-10
.search-results .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' = paginate @objects, theme: 'gitlab'
:javascript :javascript
......
...@@ -7,4 +7,4 @@ ...@@ -7,4 +7,4 @@
%strong %strong
= blob.filename = blob.filename
.file-content.code.term .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
.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
.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
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
.nothing-here-block Empty file .nothing-here-block Empty file
- else - else
.file-content.code .file-content.code
%div.highlighted-data{class: user_color_scheme_class} %div.highlighted-data{ class: user_color_scheme }
.line-numbers .line-numbers
- snippet_blob[:snippet_chunks].each do |snippet| - snippet_blob[:snippet_chunks].each do |snippet|
- unless snippet[:data].empty? - unless snippet[:data].empty?
......
...@@ -7,4 +7,4 @@ ...@@ -7,4 +7,4 @@
%strong %strong
= wiki_blob.filename = wiki_blob.filename
.file-content.code.term .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
- page_title @search_term - page_title @search_term
= render 'search/form' = render 'search/form'
%hr
- if @search_term - if @search_term
= render 'search/category' = render 'search/category'
%hr
= render 'search/results' = render 'search/results'
.file-content.code{class: user_color_scheme_class} .file-content.code.js-syntax-highlight{ class: user_color_scheme }
.line-numbers .line-numbers
- if blob.data.present? - if blob.data.present?
- blob.data.lines.each_index do |index| - blob.data.lines.each_index do |index|
......
= 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
- 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")}
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
none none
.issuable-context-selectbox .issuable-context-selectbox
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) - 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 %div.prepend-top-20.clearfix
.issuable-context-title .issuable-context-title
......
...@@ -36,11 +36,11 @@ ...@@ -36,11 +36,11 @@
.issues-other-filters .issues-other-filters
.filter-item.inline .filter-item.inline
= users_select_tag(:assignee_id, selected: params[:assignee_id], = 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 .filter-item.inline
= users_select_tag(:author_id, selected: params[:author_id], = 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 .filter-item.inline.milestone-filter
= select_tag('milestone_title', projects_milestones_options, = select_tag('milestone_title', projects_milestones_options,
...@@ -60,7 +60,7 @@ ...@@ -60,7 +60,7 @@
.issues_bulk_update.hide .issues_bulk_update.hide
= form_tag bulk_update_namespace_project_issues_path(@project.namespace, @project), method: :post do = 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') = 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") = select_tag('update[milestone_id]', bulk_update_milestone_options, prompt: "Milestone")
= hidden_field_tag 'update[issues_ids]', [] = hidden_field_tag 'update[issues_ids]', []
= hidden_field_tag :state_event, params[:state_event] = hidden_field_tag :state_event, params[:state_event]
......
...@@ -38,7 +38,7 @@ ...@@ -38,7 +38,7 @@
.clearfix .clearfix
.error-alert .error-alert
%hr %hr
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) - if can?(current_user, :"admin_#{issuable.to_ability_name}", issuable.project)
.form-group .form-group
.issue-assignee .issue-assignee
= f.label :assignee_id, class: 'control-label' do = f.label :assignee_id, class: 'control-label' do
...@@ -47,7 +47,8 @@ ...@@ -47,7 +47,8 @@
.col-sm-10 .col-sm-10
= users_select_tag("#{issuable.class.model_name.param_key}[assignee_id]", = users_select_tag("#{issuable.class.model_name.param_key}[assignee_id]",
placeholder: 'Select a user', class: 'custom-form-control', null_user: true, 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; &nbsp;
= link_to 'Assign to me', '#', class: 'btn assign-to-me-link' = link_to 'Assign to me', '#', class: 'btn assign-to-me-link'
.form-group .form-group
......
- projects_limit = 20 unless local_assigns[:projects_limit] - projects_limit = 20 unless local_assigns[:projects_limit]
- avatar = true unless local_assigns[:avatar] == false - avatar = true unless local_assigns[:avatar] == false
- stars = false unless local_assigns[:stars] == true - stars = true unless local_assigns[:stars] == false
%ul.well-list.projects-list
%ul.projects-list
- projects.each_with_index do |project, i| - projects.each_with_index do |project, i|
%li{class: (i >= projects_limit) ? 'project-row hide' : 'project-row'} - css_class = (i >= projects_limit) ? 'hide' : nil
= render "shared/project", project: project, avatar: avatar, stars: stars = render "shared/projects/project", project: project,
- if projects.blank? avatar: avatar, stars: stars, css_class: css_class
%li
.nothing-here-block There are no projects here.
- if projects.count > projects_limit - if projects.count > projects_limit
%li.bottom %li.bottom.center
%span.light .light
#{projects_limit} of #{pluralize(projects.count, 'project')} displayed. #{projects_limit} of #{pluralize(projects.count, 'project')} displayed.
%span
= link_to '#', class: 'js-expand' do = link_to '#', class: 'js-expand' do
Show all Show all
:coffeescript
new ProjectsList()
- 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)
%li %li.snippet-row
%h4.snippet-title .snippet-title
= link_to reliable_snippet_path(snippet) do = link_to reliable_snippet_path(snippet) do
= truncate(snippet.title, length: 60) = truncate(snippet.title, length: 60)
- if snippet.private? - if snippet.private?
%span.label.label-gray %span.label.label-gray
%i.fa.fa-lock %i.fa.fa-lock
private private
%span.cgray.monospace.tiny.pull-right %span.monospace.pull-right
= snippet.file_name = snippet.file_name
%small.pull-right.cgray %small.pull-right.cgray
...@@ -14,10 +14,8 @@ ...@@ -14,10 +14,8 @@
= link_to snippet.project.name_with_namespace, namespace_project_path(snippet.project.namespace, snippet.project) = link_to snippet.project.name_with_namespace, namespace_project_path(snippet.project.namespace, snippet.project)
.snippet-info .snippet-info
= "##{snippet.id}" = link_to user_snippets_path(snippet.author) do
%span = image_tag avatar_icon(snippet.author_email), class: "avatar s24", alt: ''
by = snippet.author_name
= link_to user_snippets_path(snippet.author) do authored #{time_ago_with_tooltip(snippet.created_at)}
= 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)}
%ul.bordered-list %ul.bordered-list
= render partial: 'snippet', collection: @snippets = render partial: 'shared/snippets/snippet', collection: @snippets
- if @snippets.empty? - if @snippets.empty?
%li %li
.nothing-here-block Nothing here. .nothing-here-block Nothing here.
......
- if local_assigns.has_key?(:contributed_projects) && contributed_projects.present? - if local_assigns.has_key?(:contributed_projects) && contributed_projects.present?
.panel.panel-default.contributed-projects .panel.panel-default.contributed-projects
.panel-heading Projects contributed to .panel-heading Projects contributed to
= render 'shared/projects_list', = render 'shared/projects/list',
projects: contributed_projects.sort_by(&:star_count).reverse, projects: contributed_projects.sort_by(&:star_count).reverse,
projects_limit: 5, stars: true, avatar: false projects_limit: 5, stars: true, avatar: false
- if local_assigns.has_key?(:projects) && projects.present? - if local_assigns.has_key?(:projects) && projects.present?
.panel.panel-default .panel.panel-default
.panel-heading Personal projects .panel-heading Personal projects
= render 'shared/projects_list', = render 'shared/projects/list',
projects: projects.sort_by(&:star_count).reverse, projects: projects.sort_by(&:star_count).reverse,
projects_limit: 10, stars: true, avatar: false projects_limit: 10, stars: true, avatar: false
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
= render 'shared/show_aside' = render 'shared/show_aside'
.row .row
%section.col-md-8 %section.col-md-7
.header-with-avatar .header-with-avatar
= link_to avatar_icon(@user.email, 400), target: '_blank' do = link_to avatar_icon(@user.email, 400), target: '_blank' do
= image_tag avatar_icon(@user.email, 90), class: "avatar avatar-tile s90", alt: '' = image_tag avatar_icon(@user.email, 90), class: "avatar avatar-tile s90", alt: ''
...@@ -59,7 +59,7 @@ ...@@ -59,7 +59,7 @@
.content_list .content_list
= spinner = spinner
%aside.col-md-4 %aside.col-md-5
= render 'profile', user: @user = render 'profile', user: @user
= render 'projects', projects: @projects, contributed_projects: @contributed_projects = render 'projects', projects: @projects, contributed_projects: @contributed_projects
......
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
...@@ -4,7 +4,7 @@ class EmailsOnPushWorker ...@@ -4,7 +4,7 @@ class EmailsOnPushWorker
def perform(project_id, recipients, push_data, options = {}) def perform(project_id, recipients, push_data, options = {})
options.symbolize_keys! options.symbolize_keys!
options.reverse_merge!( options.reverse_merge!(
send_from_committer_email: false, send_from_committer_email: false,
disable_diffs: false disable_diffs: false
) )
send_from_committer_email = options[:send_from_committer_email] send_from_committer_email = options[:send_from_committer_email]
...@@ -16,9 +16,9 @@ class EmailsOnPushWorker ...@@ -16,9 +16,9 @@ class EmailsOnPushWorker
ref = push_data["ref"] ref = push_data["ref"]
author_id = push_data["user_id"] author_id = push_data["user_id"]
action = action =
if Gitlab::Git.blank_ref?(before_sha) if Gitlab::Git.blank_ref?(before_sha)
:create :create
elsif Gitlab::Git.blank_ref?(after_sha) elsif Gitlab::Git.blank_ref?(after_sha)
:delete :delete
else else
...@@ -42,17 +42,22 @@ class EmailsOnPushWorker ...@@ -42,17 +42,22 @@ class EmailsOnPushWorker
end end
recipients.split(" ").each do |recipient| recipients.split(" ").each do |recipient|
Notify.repository_push_email( begin
project_id, Notify.repository_push_email(
recipient, project_id,
author_id: author_id, recipient,
ref: ref, author_id: author_id,
action: action, ref: ref,
compare: compare, action: action,
reverse_compare: reverse_compare, compare: compare,
send_from_committer_email: send_from_committer_email, reverse_compare: reverse_compare,
disable_diffs: disable_diffs send_from_committer_email: send_from_committer_email,
).deliver 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 end
ensure ensure
compare = nil compare = nil
......
...@@ -25,9 +25,10 @@ class RepositoryImportWorker ...@@ -25,9 +25,10 @@ class RepositoryImportWorker
end end
return project.import_fail unless data_import_result return project.import_fail unless data_import_result
Gitlab::BitbucketImport::KeyDeleter.new(project).execute if project.import_type == 'bitbucket'
project.import_finish project.import_finish
project.save project.save
ProjectCacheWorker.perform_async(project.id) ProjectCacheWorker.perform_async(project.id)
Gitlab::BitbucketImport::KeyDeleter.new(project).execute if project.import_type == 'bitbucket'
end end
end end
...@@ -37,7 +37,7 @@ start_no_deamonize() ...@@ -37,7 +37,7 @@ start_no_deamonize()
start_sidekiq() 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() load_ok()
......
#!/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)
#!/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
...@@ -94,6 +94,13 @@ production: &base ...@@ -94,6 +94,13 @@ production: &base
# The default is 'tmp/repositories' relative to the root of the Rails app. # The default is 'tmp/repositories' relative to the root of the Rails app.
# repository_downloads_path: tmp/repositories # 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 ## Gravatar
## For Libravatar see: http://doc.gitlab.com/ce/customization/libravatar.html ## For Libravatar see: http://doc.gitlab.com/ce/customization/libravatar.html
gravatar: gravatar:
......
...@@ -8,7 +8,7 @@ class Settings < Settingslogic ...@@ -8,7 +8,7 @@ class Settings < Settingslogic
def gitlab_on_standard_port? def gitlab_on_standard_port?
gitlab.port.to_i == (gitlab.https ? 443 : 80) gitlab.port.to_i == (gitlab.https ? 443 : 80)
end end
# get host without www, thanks to http://stackoverflow.com/a/6674363/1233435 # get host without www, thanks to http://stackoverflow.com/a/6674363/1233435
def get_host_without_www(url) def get_host_without_www(url)
url = URI.encode(url) url = URI.encode(url)
...@@ -32,14 +32,12 @@ class Settings < Settingslogic ...@@ -32,14 +32,12 @@ class Settings < Settingslogic
end end
end end
def build_base_gitlab_url
base_gitlab_url.join('')
end
def build_gitlab_url def build_gitlab_url
custom_port = gitlab_on_standard_port? ? nil : ":#{gitlab.port}" (base_gitlab_url + [gitlab.relative_url_root]).join('')
[ gitlab.protocol,
"://",
gitlab.host,
custom_port,
gitlab.relative_url_root
].join('')
end end
# check that values in `current` (string or integer) is a contant in `modul`. # check that values in `current` (string or integer) is a contant in `modul`.
...@@ -64,6 +62,17 @@ class Settings < Settingslogic ...@@ -64,6 +62,17 @@ class Settings < Settingslogic
end end
value value
end end
private
def base_gitlab_url
custom_port = gitlab_on_standard_port? ? nil : ":#{gitlab.port}"
[ gitlab.protocol,
"://",
gitlab.host,
custom_port
]
end
end end
end end
...@@ -123,6 +132,7 @@ Settings.gitlab['email_enabled'] ||= true if Settings.gitlab['email_enabled'].ni ...@@ -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_from'] ||= "gitlab@#{Settings.gitlab.host}"
Settings.gitlab['email_display_name'] ||= "GitLab" Settings.gitlab['email_display_name'] ||= "GitLab"
Settings.gitlab['email_reply_to'] ||= "noreply@#{Settings.gitlab.host}" 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['url'] ||= Settings.send(:build_gitlab_url)
Settings.gitlab['user'] ||= 'git' Settings.gitlab['user'] ||= 'git'
Settings.gitlab['user_home'] ||= begin Settings.gitlab['user_home'] ||= begin
...@@ -150,6 +160,12 @@ Settings.gitlab['repository_downloads_path'] = File.absolute_path(Settings.gitla ...@@ -150,6 +160,12 @@ Settings.gitlab['repository_downloads_path'] = File.absolute_path(Settings.gitla
Settings.gitlab['restricted_signup_domains'] ||= [] Settings.gitlab['restricted_signup_domains'] ||= []
Settings.gitlab['import_sources'] ||= ['github','bitbucket','gitlab','gitorious','google_code','git'] 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 # Gravatar
# #
......
...@@ -11,7 +11,7 @@ if Gitlab::LDAP::Config.enabled? ...@@ -11,7 +11,7 @@ if Gitlab::LDAP::Config.enabled?
end end
end end
OmniAuth.config.full_host = Settings.gitlab['url'] OmniAuth.config.full_host = Settings.gitlab['base_url']
OmniAuth.config.allowed_request_methods = [:post] 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) #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? OmniAuth.config.allowed_request_methods << :get if Gitlab.config.omniauth.auto_sign_in_with_provider.present?
......
: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
...@@ -261,6 +261,7 @@ Gitlab::Application.routes.draw do ...@@ -261,6 +261,7 @@ Gitlab::Application.routes.draw do
member do member do
get :issues get :issues
get :merge_requests get :merge_requests
get :activity
end end
scope module: :dashboard do scope module: :dashboard do
......
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
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
class AddEnableSslVerification < ActiveRecord::Migration
def change
add_column :web_hooks, :enable_ssl_verification, :boolean, default: false
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # 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 # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -405,6 +405,17 @@ ActiveRecord::Schema.define(version: 20150817163600) do ...@@ -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 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| create_table "services", force: true do |t|
t.string "type" t.string "type"
t.string "title" t.string "title"
...@@ -515,13 +526,9 @@ ActiveRecord::Schema.define(version: 20150817163600) do ...@@ -515,13 +526,9 @@ ActiveRecord::Schema.define(version: 20150817163600) do
t.string "unconfirmed_email" t.string "unconfirmed_email"
t.boolean "hide_no_ssh_key", default: false t.boolean "hide_no_ssh_key", default: false
t.string "website_url", default: "", null: false t.string "website_url", default: "", null: false
t.string "github_access_token"
t.string "gitlab_access_token"
t.string "notification_email" t.string "notification_email"
t.boolean "hide_no_password", default: false t.boolean "hide_no_password", default: false
t.boolean "password_automatically_set", 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 "location"
t.string "encrypted_otp_secret" t.string "encrypted_otp_secret"
t.string "encrypted_otp_secret_iv" t.string "encrypted_otp_secret_iv"
...@@ -559,13 +566,14 @@ ActiveRecord::Schema.define(version: 20150817163600) do ...@@ -559,13 +566,14 @@ ActiveRecord::Schema.define(version: 20150817163600) do
t.integer "project_id" t.integer "project_id"
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
t.string "type", default: "ProjectHook" t.string "type", default: "ProjectHook"
t.integer "service_id" t.integer "service_id"
t.boolean "push_events", default: true, null: false t.boolean "push_events", default: true, null: false
t.boolean "issues_events", default: false, null: false t.boolean "issues_events", default: false, null: false
t.boolean "merge_requests_events", default: false, null: false t.boolean "merge_requests_events", default: false, null: false
t.boolean "tag_push_events", default: false t.boolean "tag_push_events", default: false
t.boolean "note_events", default: false, null: false t.boolean "note_events", default: false, null: false
t.boolean "enable_ssl_verification", default: false
end end
add_index "web_hooks", ["created_at", "id"], name: "index_web_hooks_on_created_at_and_id", using: :btree add_index "web_hooks", ["created_at", "id"], name: "index_web_hooks_on_created_at_and_id", using: :btree
......
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
- [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed. - [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. - [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. - [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 ## Contributor documentation
......
...@@ -366,7 +366,7 @@ Make sure to edit the config file to match your setup: ...@@ -366,7 +366,7 @@ Make sure to edit the config file to match your setup:
# domain name of your host serving GitLab. # domain name of your host serving GitLab.
# If using Ubuntu default nginx install: # If using Ubuntu default nginx install:
# either remove the default_server from the listen line # 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 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. **Note:** If you want to use HTTPS, replace the `gitlab` Nginx config with `gitlab-ssl`. See [Using HTTPS](#using-https) for HTTPS configuration details.
......
# Monthly Release # 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 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 current release manager must schedule the appointment of the next release manager.
The new release manager should create overall issue to track the progress. The new release manager should create overall issue to track the progress.
...@@ -164,7 +165,7 @@ Tweet about the RC release: ...@@ -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. 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. 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. 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 ## Create CE, EE, CI stable versions
......
# 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.
...@@ -8,5 +8,5 @@ ...@@ -8,5 +8,5 @@
### Note ### 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. * 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. * You can import any Git repository via HTTP from the New Project page.
If the repository is too large, it can timeout. We have a soft limit of 10GB. If the repository is too large, it can timeout.
...@@ -27,3 +27,9 @@ Feature: Admin Groups ...@@ -27,3 +27,9 @@ Feature: Admin Groups
When I visit admin group page When I visit admin group page
And I remove user "John Doe" from group And I remove user "John Doe" from group
Then I should not see "John Doe" in team list 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"
@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
...@@ -10,6 +10,10 @@ Feature: Dashboard ...@@ -10,6 +10,10 @@ Feature: Dashboard
Scenario: I should see projects list Scenario: I should see projects list
Then I should see "New Project" link Then I should see "New Project" link
Then I should see "Shop" 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 Then I should see project "Shop" activity feed
Scenario: I should see groups list Scenario: I should see groups list
...@@ -26,12 +30,12 @@ Feature: Dashboard ...@@ -26,12 +30,12 @@ Feature: Dashboard
@javascript @javascript
Scenario: I should see User joined Project event Scenario: I should see User joined Project event
Given user with name "John Doe" joined project "Shop" 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 Then I should see "John Doe joined project Shop" event
@javascript @javascript
Scenario: I should see User left Project event Scenario: I should see User left Project event
Given user with name "John Doe" joined project "Shop" Given user with name "John Doe" joined project "Shop"
And user with name "John Doe" left 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 Then I should see "John Doe left project Shop" event
...@@ -6,7 +6,7 @@ Feature: Event Filters ...@@ -6,7 +6,7 @@ Feature: Event Filters
And this project has push event And this project has push event
And this project has new member event And this project has new member event
And this project has merge request event And this project has merge request event
And I visit dashboard page And I visit dashboard activity page
@javascript @javascript
Scenario: I should see all events Scenario: I should see all events
...@@ -16,7 +16,7 @@ Feature: Event Filters ...@@ -16,7 +16,7 @@ Feature: Event Filters
@javascript @javascript
Scenario: I should see only pushed events 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 Then I should see push event
And I should not see new member event And I should not see new member event
And I should not see merge request event And I should not see merge request event
...@@ -38,11 +38,11 @@ Feature: Event Filters ...@@ -38,11 +38,11 @@ Feature: Event Filters
@javascript @javascript
Scenario: I should see only selected events while page reloaded Scenario: I should see only selected events while page reloaded
When I click "push" event filter When I click "push" event filter
And I visit dashboard page And I visit dashboard activity page
Then I should see push event Then I should see push event
And I should not see new member event And I should not see new member event
When I click "team" event filter When I click "team" event filter
And I visit dashboard page And I visit dashboard activity page
Then I should see push event Then I should see push event
And I should see new member event And I should see new member event
And I should not see merge request event And I should not see merge request event
......
...@@ -41,6 +41,7 @@ Feature: Project Commits ...@@ -41,6 +41,7 @@ Feature: Project Commits
Scenario: I browse big commit Scenario: I browse big commit
Given I visit big commit page Given I visit big commit page
Then I see big commit warning Then I see big commit warning
And I see "Reload with full diff" link
Scenario: I browse a commit with an image Scenario: I browse a commit with an image
Given I visit a commit with an image that changed Given I visit a commit with an image that changed
......
...@@ -13,6 +13,11 @@ Feature: Project Hooks ...@@ -13,6 +13,11 @@ Feature: Project Hooks
When I submit new hook When I submit new hook
Then I should see newly created 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 Scenario: I test hook
Given project has hook Given project has hook
And I visit project hooks page And I visit project hooks page
......
...@@ -23,6 +23,13 @@ Feature: Search ...@@ -23,6 +23,13 @@ Feature: Search
Then I should see "Foo" link in the search results Then I should see "Foo" link in the search results
And I should not see "Bar" 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 Scenario: I should see project code I am looking for
When I click project "Shop" link When I click project "Shop" link
And I search for "rspec" And I search for "rspec"
...@@ -44,6 +51,14 @@ Feature: Search ...@@ -44,6 +51,14 @@ Feature: Search
Then I should see "Foo" link in the search results Then I should see "Foo" link in the search results
And I should not see "Bar" 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 Scenario: I should see Wiki blobs
And project has Wiki content And project has Wiki content
When I click project "Shop" link When I click project "Shop" link
......
...@@ -44,6 +44,14 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps ...@@ -44,6 +44,14 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps
click_button "Add users to group" click_button "Add users to group"
end 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 step 'I should see "John Doe" in team list in every project as "Reporter"' do
page.within ".group-users-list" do page.within ".group-users-list" do
expect(page).to have_content "John Doe" expect(page).to have_content "John Doe"
...@@ -51,6 +59,13 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps ...@@ -51,6 +59,13 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps
end end
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 step 'I should be all groups' do
Group.all.each do |group| Group.all.each do |group|
expect(page).to have_content group.name expect(page).to have_content group.name
......
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
...@@ -79,6 +79,12 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps ...@@ -79,6 +79,12 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
expect(page).to have_content "Too many changes" expect(page).to have_content "Too many changes"
end 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 step 'I visit a commit with an image that changed' do
visit namespace_project_commit_path(@project.namespace, @project, sample_image_commit.id) visit namespace_project_commit_path(@project.namespace, @project, sample_image_commit.id)
end end
......
...@@ -28,11 +28,24 @@ class Spinach::Features::ProjectHooks < Spinach::FeatureSteps ...@@ -28,11 +28,24 @@ class Spinach::Features::ProjectHooks < Spinach::FeatureSteps
expect { click_button "Add Web Hook" }.to change(ProjectHook, :count).by(1) expect { click_button "Add Web Hook" }.to change(ProjectHook, :count).by(1)
end 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 step 'I should see newly created hook' do
expect(current_path).to eq namespace_project_hooks_path(current_project.namespace, current_project) 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(@url)
end 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 step 'I click test hook button' do
stub_request(:post, @hook.url).to_return(status: 200) stub_request(:post, @hook.url).to_return(status: 200)
click_link 'Test Hook' click_link 'Test Hook'
......
...@@ -41,6 +41,12 @@ class Spinach::Features::Search < Spinach::FeatureSteps ...@@ -41,6 +41,12 @@ class Spinach::Features::Search < Spinach::FeatureSteps
end end
end end
step 'I click "Milestones" link' do
page.within '.search-filter' do
click_link 'Milestones'
end
end
step 'I click "Wiki" link' do step 'I click "Wiki" link' do
page.within '.search-filter' do page.within '.search-filter' do
click_link 'Wiki' click_link 'Wiki'
...@@ -72,6 +78,11 @@ class Spinach::Features::Search < Spinach::FeatureSteps ...@@ -72,6 +78,11 @@ class Spinach::Features::Search < Spinach::FeatureSteps
create(:merge_request, :simple, title: "Bar", source_project: project, target_project: project) create(:merge_request, :simple, title: "Bar", source_project: project, target_project: project)
end 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 step 'I should see "Foo" link in the search results' do
page.within('.results') do page.within('.results') do
find(:css, '.search-results').should have_link 'Foo' find(:css, '.search-results').should have_link 'Foo'
......
...@@ -71,6 +71,10 @@ module SharedPaths ...@@ -71,6 +71,10 @@ module SharedPaths
visit dashboard_path visit dashboard_path
end end
step 'I visit dashboard activity page' do
visit activity_dashboard_path
end
step 'I visit dashboard projects page' do step 'I visit dashboard projects page' do
visit projects_dashboard_path visit projects_dashboard_path
end end
......
...@@ -14,11 +14,6 @@ Feature: User ...@@ -14,11 +14,6 @@ Feature: User
And I should not see project "Internal" And I should not see project "Internal"
And I should see project "Community" 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 # Signed in as someone else
Scenario: I visit user "John Doe" page while signed in as someone else when he owns a public project Scenario: I visit user "John Doe" page while signed in as someone else when he owns a public project
......
...@@ -5,7 +5,10 @@ module Gitlab ...@@ -5,7 +5,10 @@ module Gitlab
def initialize(project) def initialize(project)
@project = 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 @formatter = Gitlab::ImportFormatter.new
end end
...@@ -16,12 +19,12 @@ module Gitlab ...@@ -16,12 +19,12 @@ module Gitlab
#Issues && Comments #Issues && Comments
issues = client.issues(project_identifier) issues = client.issues(project_identifier)
issues["issues"].each do |issue| issues["issues"].each do |issue|
body = @formatter.author_line(issue["reported_by"]["username"], issue["content"]) body = @formatter.author_line(issue["reported_by"]["username"], issue["content"])
comments = client.issue_comments(project_identifier, issue["local_id"]) comments = client.issue_comments(project_identifier, issue["local_id"])
if comments.any? if comments.any?
body += @formatter.comments_header body += @formatter.comments_header
end end
...@@ -31,13 +34,13 @@ module Gitlab ...@@ -31,13 +34,13 @@ module Gitlab
end end
project.issues.create!( project.issues.create!(
description: body, description: body,
title: issue["title"], title: issue["title"],
state: %w(resolved invalid duplicate wontfix).include?(issue["status"]) ? 'closed' : 'opened', state: %w(resolved invalid duplicate wontfix).include?(issue["status"]) ? 'closed' : 'opened',
author_id: gl_user_id(project, issue["reported_by"]["username"]) author_id: gl_user_id(project, issue["reported_by"]["username"])
) )
end end
true true
end end
......
...@@ -3,14 +3,15 @@ module Gitlab ...@@ -3,14 +3,15 @@ module Gitlab
class KeyAdder class KeyAdder
attr_reader :repo, :current_user, :client attr_reader :repo, :current_user, :client
def initialize(repo, current_user) def initialize(repo, current_user, access_params)
@repo, @current_user = repo, current_user @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 end
def execute def execute
return false unless BitbucketImport.public_key.present? return false unless BitbucketImport.public_key.present?
project_identifier = "#{repo["owner"]}/#{repo["slug"]}" project_identifier = "#{repo["owner"]}/#{repo["slug"]}"
client.add_deploy_key(project_identifier, BitbucketImport.public_key) client.add_deploy_key(project_identifier, BitbucketImport.public_key)
......
...@@ -6,12 +6,15 @@ module Gitlab ...@@ -6,12 +6,15 @@ module Gitlab
def initialize(project) def initialize(project)
@project = project @project = project
@current_user = project.creator @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 end
def execute def execute
return false unless BitbucketImport.public_key.present? return false unless BitbucketImport.public_key.present?
client.delete_deploy_key(project.import_source, BitbucketImport.public_key) client.delete_deploy_key(project.import_source, BitbucketImport.public_key)
true true
......
module Gitlab module Gitlab
module BitbucketImport module BitbucketImport
class ProjectCreator 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 @repo = repo
@namespace = namespace @namespace = namespace
@current_user = current_user @current_user = current_user
@session_data = session_data
end end
def execute def execute
::Projects::CreateService.new(current_user, project = ::Projects::CreateService.new(current_user,
name: repo["name"], name: repo["name"],
path: repo["slug"], path: repo["slug"],
description: repo["description"], description: repo["description"],
...@@ -18,8 +19,11 @@ module Gitlab ...@@ -18,8 +19,11 @@ module Gitlab
visibility_level: repo["is_private"] ? Gitlab::VisibilityLevel::PRIVATE : Gitlab::VisibilityLevel::PUBLIC, visibility_level: repo["is_private"] ? Gitlab::VisibilityLevel::PRIVATE : Gitlab::VisibilityLevel::PUBLIC,
import_type: "bitbucket", import_type: "bitbucket",
import_source: "#{repo["owner"]}/#{repo["slug"]}", 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 ).execute
project.create_import_data(data: { "bb_session" => session_data } )
project
end end
end end
end end
......
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
...@@ -4,7 +4,7 @@ module Gitlab ...@@ -4,7 +4,7 @@ module Gitlab
key = :current_application_settings key = :current_application_settings
RequestStore.store[key] ||= begin 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 ApplicationSetting.current || ApplicationSetting.create_from_defaults
else else
fake_application_settings fake_application_settings
......
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
# 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
# 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
...@@ -5,7 +5,9 @@ module Gitlab ...@@ -5,7 +5,9 @@ module Gitlab
def initialize(project) def initialize(project)
@project = 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 @formatter = Gitlab::ImportFormatter.new
end end
......
module Gitlab module Gitlab
module GithubImport module GithubImport
class ProjectCreator 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 @repo = repo
@namespace = namespace @namespace = namespace
@current_user = current_user @current_user = current_user
@session_data = session_data
end end
def execute def execute
::Projects::CreateService.new(current_user, project = ::Projects::CreateService.new(
current_user,
name: repo.name, name: repo.name,
path: repo.name, path: repo.name,
description: repo.description, description: repo.description,
...@@ -18,8 +20,11 @@ module Gitlab ...@@ -18,8 +20,11 @@ module Gitlab
visibility_level: repo.private ? Gitlab::VisibilityLevel::PRIVATE : Gitlab::VisibilityLevel::PUBLIC, visibility_level: repo.private ? Gitlab::VisibilityLevel::PRIVATE : Gitlab::VisibilityLevel::PUBLIC,
import_type: "github", import_type: "github",
import_source: repo.full_name, 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 ).execute
project.create_import_data(data: { "github_session" => session_data } )
project
end end
end end
end end
......
...@@ -5,7 +5,9 @@ module Gitlab ...@@ -5,7 +5,9 @@ module Gitlab
def initialize(project) def initialize(project)
@project = 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 @formatter = Gitlab::ImportFormatter.new
end end
...@@ -14,12 +16,12 @@ module Gitlab ...@@ -14,12 +16,12 @@ module Gitlab
#Issues && Comments #Issues && Comments
issues = client.issues(project_identifier) issues = client.issues(project_identifier)
issues.each do |issue| issues.each do |issue|
body = @formatter.author_line(issue["author"]["name"], issue["description"]) body = @formatter.author_line(issue["author"]["name"], issue["description"])
comments = client.issue_comments(project_identifier, issue["id"]) comments = client.issue_comments(project_identifier, issue["id"])
if comments.any? if comments.any?
body += @formatter.comments_header body += @formatter.comments_header
end end
...@@ -29,13 +31,13 @@ module Gitlab ...@@ -29,13 +31,13 @@ module Gitlab
end end
project.issues.create!( project.issues.create!(
description: body, description: body,
title: issue["title"], title: issue["title"],
state: issue["state"], state: issue["state"],
author_id: gl_user_id(project, issue["author"]["id"]) author_id: gl_user_id(project, issue["author"]["id"])
) )
end end
true true
end end
......
module Gitlab module Gitlab
module GitlabImport module GitlabImport
class ProjectCreator 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 @repo = repo
@namespace = namespace @namespace = namespace
@current_user = current_user @current_user = current_user
@session_data = session_data
end end
def execute def execute
::Projects::CreateService.new(current_user, project = ::Projects::CreateService.new(current_user,
name: repo["name"], name: repo["name"],
path: repo["path"], path: repo["path"],
description: repo["description"], description: repo["description"],
...@@ -18,8 +19,11 @@ module Gitlab ...@@ -18,8 +19,11 @@ module Gitlab
visibility_level: repo["visibility_level"], visibility_level: repo["visibility_level"],
import_type: "gitlab", import_type: "gitlab",
import_source: repo["path_with_namespace"], 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 ).execute
project.create_import_data(data: { "gitlab_session" => session_data } )
project
end end
end end
end end
......
...@@ -87,8 +87,14 @@ module Gitlab ...@@ -87,8 +87,14 @@ module Gitlab
def autolink_filter(text) def autolink_filter(text)
text.gsub(LINK_PATTERN) do |match| 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) options = link_options.merge(href: match)
content_tag(:a, match, options) content_tag(:a, match, options) + dropped
end end
end end
......
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
...@@ -19,13 +19,15 @@ module Gitlab ...@@ -19,13 +19,15 @@ module Gitlab
issues.page(page).per(per_page) issues.page(page).per(per_page)
when 'merge_requests' when 'merge_requests'
merge_requests.page(page).per(per_page) merge_requests.page(page).per(per_page)
when 'milestones'
milestones.page(page).per(per_page)
else else
Kaminari.paginate_array([]).page(page).per(per_page) Kaminari.paginate_array([]).page(page).per(per_page)
end end
end end
def total_count def total_count
@total_count ||= projects_count + issues_count + merge_requests_count @total_count ||= projects_count + issues_count + merge_requests_count + milestones_count
end end
def projects_count def projects_count
...@@ -40,6 +42,10 @@ module Gitlab ...@@ -40,6 +42,10 @@ module Gitlab
@merge_requests_count ||= merge_requests.count @merge_requests_count ||= merge_requests.count
end end
def milestones_count
@milestones_count ||= milestones.count
end
def empty? def empty?
total_count.zero? total_count.zero?
end end
...@@ -60,6 +66,12 @@ module Gitlab ...@@ -60,6 +66,12 @@ module Gitlab
issues.order('updated_at DESC') issues.order('updated_at DESC')
end end
def milestones
milestones = Milestone.where(project_id: limit_project_ids)
milestones = milestones.search(query)
milestones.order('updated_at DESC')
end
def merge_requests def merge_requests
merge_requests = MergeRequest.in_projects(limit_project_ids) merge_requests = MergeRequest.in_projects(limit_project_ids)
if query =~ /[#!](\d+)\z/ if query =~ /[#!](\d+)\z/
......
...@@ -37,6 +37,11 @@ module Gitlab ...@@ -37,6 +37,11 @@ module Gitlab
THEMES.detect { |t| t.id == id } || default THEMES.detect { |t| t.id == id } || default
end end
# Returns the number of defined Themes
def self.count
THEMES.size
end
# Get the default Theme # Get the default Theme
# #
# Returns a Theme # Returns a Theme
...@@ -51,6 +56,19 @@ module Gitlab ...@@ -51,6 +56,19 @@ module Gitlab
THEMES.each(&block) THEMES.each(&block)
end 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 private
def self.default_id def self.default_id
......
...@@ -4,9 +4,8 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML ...@@ -4,9 +4,8 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML
attr_reader :template attr_reader :template
alias_method :h, :template alias_method :h, :template
def initialize(template, color_scheme, options = {}) def initialize(template, options = {})
@template = template @template = template
@color_scheme = color_scheme
@options = options.dup @options = options.dup
@options.reverse_merge!( @options.reverse_merge!(
...@@ -35,7 +34,7 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML ...@@ -35,7 +34,7 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML
end end
formatter = Rouge::Formatters::HTMLGitlab.new( 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)) formatter.format(lexer.lex(code))
end end
......
...@@ -35,6 +35,8 @@ pid_path="$app_root/tmp/pids" ...@@ -35,6 +35,8 @@ pid_path="$app_root/tmp/pids"
socket_path="$app_root/tmp/sockets" socket_path="$app_root/tmp/sockets"
web_server_pid_path="$pid_path/unicorn.pid" web_server_pid_path="$pid_path/unicorn.pid"
sidekiq_pid_path="$pid_path/sidekiq.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" shell_path="/bin/bash"
# Read configuration variable file if it is present # Read configuration variable file if it is present
...@@ -70,13 +72,20 @@ check_pids(){ ...@@ -70,13 +72,20 @@ check_pids(){
else else
spid=0 spid=0
fi 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. ## Called when we have started the two processes and are waiting for their pid files.
wait_for_pids(){ wait_for_pids(){
# We are sleeping a bit here mostly because sidekiq is slow at writing it's pid # We are sleeping a bit here mostly because sidekiq is slow at writing it's pid
i=0; 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; sleep 0.1;
i=$((i+1)) i=$((i+1))
if [ $((i%10)) = 0 ]; then if [ $((i%10)) = 0 ]; then
...@@ -111,7 +120,15 @@ check_status(){ ...@@ -111,7 +120,15 @@ check_status(){
else else
sidekiq_status="-1" sidekiq_status="-1"
fi 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 gitlab_status=0
else else
# http://refspecs.linuxbase.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html # http://refspecs.linuxbase.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html
...@@ -125,26 +142,33 @@ check_stale_pids(){ ...@@ -125,26 +142,33 @@ check_stale_pids(){
check_status check_status
# If there is a pid it is something else than 0, the service is running if # If there is a pid it is something else than 0, the service is running if
# *_status is == 0. # *_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." 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 if ! rm "$web_server_pid_path"; then
echo "Unable to remove stale pid, exiting." echo "Unable to remove stale pid, exiting."
exit 1 exit 1
fi fi
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." 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 if ! rm "$sidekiq_pid_path"; then
echo "Unable to remove stale pid, exiting" echo "Unable to remove stale pid, exiting"
exit 1 exit 1
fi fi
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. ## If no parts of the service is running, bail out.
exit_if_not_running(){ exit_if_not_running(){
check_stale_pids 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." echo "GitLab is not running."
exit exit
fi fi
...@@ -154,12 +178,14 @@ exit_if_not_running(){ ...@@ -154,12 +178,14 @@ exit_if_not_running(){
start_gitlab() { start_gitlab() {
check_stale_pids check_stale_pids
if [ "$web_status" != "0" -a "$sidekiq_status" != "0" ]; then if [ "$web_status" != "0" ]; then
echo -n "Starting both the GitLab Unicorn and Sidekiq" echo "Starting GitLab Unicorn"
elif [ "$web_status" != "0" ]; then fi
echo -n "Starting GitLab Unicorn" if [ "$sidekiq_status" != "0" ]; then
elif [ "$sidekiq_status" != "0" ]; then echo "Starting GitLab Sidekiq"
echo -n "Starting GitLab Sidekiq" fi
if [ "$mail_room_enabled" = true ] && [ "$mail_room_status" != "0" ]; then
echo "Starting GitLab MailRoom"
fi fi
# Then check if the service is running. If it is: don't start again. # Then check if the service is running. If it is: don't start again.
...@@ -179,22 +205,33 @@ start_gitlab() { ...@@ -179,22 +205,33 @@ start_gitlab() {
RAILS_ENV=$RAILS_ENV bin/background_jobs start & RAILS_ENV=$RAILS_ENV bin/background_jobs start &
fi 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 the pids to be planted
wait_for_pids wait_for_pids
# Finally check the status to tell wether or not GitLab is running # Finally check the status to tell wether or not GitLab is running
print_status 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() { stop_gitlab() {
exit_if_not_running exit_if_not_running
if [ "$web_status" = "0" -a "$sidekiq_status" = "0" ]; then if [ "$web_status" = "0" ]; then
echo -n "Shutting down both Unicorn and Sidekiq" echo "Shutting down GitLab Unicorn"
elif [ "$web_status" = "0" ]; then fi
echo -n "Shutting down Unicorn" if [ "$sidekiq_status" = "0" ]; then
elif [ "$sidekiq_status" = "0" ]; then echo "Shutting down GitLab Sidekiq"
echo -n "Shutting down Sidekiq" fi
if [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; then
echo "Shutting down GitLab MailRoom"
fi fi
# If the Unicorn web server is running, tell it to stop; # If the Unicorn web server is running, tell it to stop;
...@@ -205,13 +242,17 @@ stop_gitlab() { ...@@ -205,13 +242,17 @@ stop_gitlab() {
if [ "$sidekiq_status" = "0" ]; then if [ "$sidekiq_status" = "0" ]; then
RAILS_ENV=$RAILS_ENV bin/background_jobs stop RAILS_ENV=$RAILS_ENV bin/background_jobs stop
fi 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. # 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 sleep 1
check_status check_status
printf "." 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" printf "\n"
break break
fi fi
...@@ -220,7 +261,10 @@ stop_gitlab() { ...@@ -220,7 +261,10 @@ stop_gitlab() {
sleep 1 sleep 1
# Cleaning up unused pids # Cleaning up unused pids
rm "$web_server_pid_path" 2>/dev/null 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 print_status
} }
...@@ -228,7 +272,7 @@ stop_gitlab() { ...@@ -228,7 +272,7 @@ stop_gitlab() {
## Prints the status of GitLab and it's components. ## Prints the status of GitLab and it's components.
print_status() { print_status() {
check_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." echo "GitLab is not running."
return return
fi fi
...@@ -242,7 +286,14 @@ print_status() { ...@@ -242,7 +286,14 @@ print_status() {
else else
printf "The GitLab Sidekiq job dispatcher is \033[31mnot running\033[0m.\n" printf "The GitLab Sidekiq job dispatcher is \033[31mnot running\033[0m.\n"
fi 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" printf "GitLab and all its components are \033[32mup and running\033[0m.\n"
fi fi
} }
...@@ -257,9 +308,15 @@ reload_gitlab(){ ...@@ -257,9 +308,15 @@ reload_gitlab(){
printf "Reloading GitLab Unicorn configuration... " printf "Reloading GitLab Unicorn configuration... "
RAILS_ENV=$RAILS_ENV bin/web reload RAILS_ENV=$RAILS_ENV bin/web reload
echo "Done." echo "Done."
echo "Restarting GitLab Sidekiq since it isn't capable of reloading its config..." echo "Restarting GitLab Sidekiq since it isn't capable of reloading its config..."
RAILS_ENV=$RAILS_ENV bin/background_jobs restart 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 wait_for_pids
print_status print_status
} }
...@@ -267,7 +324,7 @@ reload_gitlab(){ ...@@ -267,7 +324,7 @@ reload_gitlab(){
## Restarts Sidekiq and Unicorn. ## Restarts Sidekiq and Unicorn.
restart_gitlab(){ restart_gitlab(){
check_status 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 stop_gitlab
fi fi
start_gitlab start_gitlab
......
...@@ -30,6 +30,15 @@ web_server_pid_path="$pid_path/unicorn.pid" ...@@ -30,6 +30,15 @@ web_server_pid_path="$pid_path/unicorn.pid"
# The default is "$pid_path/sidekiq.pid" # The default is "$pid_path/sidekiq.pid"
sidekiq_pid_path="$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_path defines the path of shell for "$app_user" in case you are using
# shell other than "bash" # shell other than "bash"
# The default is "/bin/bash" # The default is "/bin/bash"
......
...@@ -2,6 +2,7 @@ namespace :gitlab do ...@@ -2,6 +2,7 @@ namespace :gitlab do
desc "GitLab | Check the configuration of GitLab and its environment" desc "GitLab | Check the configuration of GitLab and its environment"
task check: %w{gitlab:gitlab_shell:check task check: %w{gitlab:gitlab_shell:check
gitlab:sidekiq:check gitlab:sidekiq:check
gitlab:reply_by_email:check
gitlab:ldap:check gitlab:ldap:check
gitlab:app:check} gitlab:app:check}
...@@ -629,6 +630,174 @@ namespace :gitlab do ...@@ -629,6 +630,174 @@ namespace :gitlab do
end end
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 namespace :ldap do
task :check, [:limit] => :environment do |t, args| task :check, [:limit] => :environment do |t, args|
# Only show up to 100 results because LDAP directories can be very big. # Only show up to 100 results because LDAP directories can be very big.
......
...@@ -74,7 +74,7 @@ describe AutocompleteController do ...@@ -74,7 +74,7 @@ describe AutocompleteController do
describe 'GET #users with project ID' do describe 'GET #users with project ID' do
before do before do
get(:users, project_id: project.id) get(:users, project_id: project.id, current_user: true)
end end
it { expect(body).to be_kind_of(Array) } it { expect(body).to be_kind_of(Array) }
......
...@@ -4,7 +4,15 @@ require_relative 'import_spec_helper' ...@@ -4,7 +4,15 @@ require_relative 'import_spec_helper'
describe Import::BitbucketController do describe Import::BitbucketController do
include ImportSpecHelper 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 before do
sign_in(user) sign_in(user)
...@@ -17,8 +25,6 @@ describe Import::BitbucketController do ...@@ -17,8 +25,6 @@ describe Import::BitbucketController do
end end
it "updates access token" do it "updates access token" do
token = "asdasd12345"
secret = "sekrettt"
access_token = double(token: token, secret: secret) access_token = double(token: token, secret: secret)
allow_any_instance_of(Gitlab::BitbucketImport::Client). allow_any_instance_of(Gitlab::BitbucketImport::Client).
to receive(:get_token).and_return(access_token) to receive(:get_token).and_return(access_token)
...@@ -26,8 +32,8 @@ describe Import::BitbucketController do ...@@ -26,8 +32,8 @@ describe Import::BitbucketController do
get :callback get :callback
expect(user.reload.bitbucket_access_token).to eq(token) expect(session[:bitbucket_access_token]).to eq(token)
expect(user.reload.bitbucket_access_token_secret).to eq(secret) expect(session[:bitbucket_access_token_secret]).to eq(secret)
expect(controller).to redirect_to(status_import_bitbucket_url) expect(controller).to redirect_to(status_import_bitbucket_url)
end end
end end
...@@ -35,6 +41,7 @@ describe Import::BitbucketController do ...@@ -35,6 +41,7 @@ describe Import::BitbucketController do
describe "GET status" do describe "GET status" do
before do before do
@repo = OpenStruct.new(slug: 'vim', owner: 'asd') @repo = OpenStruct.new(slug: 'vim', owner: 'asd')
assign_session_tokens
end end
it "assigns variables" do it "assigns variables" do
...@@ -73,17 +80,18 @@ describe Import::BitbucketController do ...@@ -73,17 +80,18 @@ describe Import::BitbucketController do
before do before do
allow(Gitlab::BitbucketImport::KeyAdder). 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)) and_return(double(execute: true))
stub_client(user: bitbucket_user, project: bitbucket_repo) stub_client(user: bitbucket_user, project: bitbucket_repo)
assign_session_tokens
end end
context "when the repository owner is the Bitbucket user" do context "when the repository owner is the Bitbucket user" do
context "when the Bitbucket user and GitLab user's usernames match" do context "when the Bitbucket user and GitLab user's usernames match" do
it "takes the current user's namespace" do it "takes the current user's namespace" do
expect(Gitlab::BitbucketImport::ProjectCreator). 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)) and_return(double(execute: true))
post :create, format: :js post :create, format: :js
...@@ -95,7 +103,7 @@ describe Import::BitbucketController do ...@@ -95,7 +103,7 @@ describe Import::BitbucketController do
it "takes the current user's namespace" do it "takes the current user's namespace" do
expect(Gitlab::BitbucketImport::ProjectCreator). 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)) and_return(double(execute: true))
post :create, format: :js post :create, format: :js
...@@ -116,7 +124,7 @@ describe Import::BitbucketController do ...@@ -116,7 +124,7 @@ describe Import::BitbucketController do
context "when the namespace is owned by the GitLab user" do context "when the namespace is owned by the GitLab user" do
it "takes the existing namespace" do it "takes the existing namespace" do
expect(Gitlab::BitbucketImport::ProjectCreator). 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)) and_return(double(execute: true))
post :create, format: :js post :create, format: :js
...@@ -150,7 +158,7 @@ describe Import::BitbucketController do ...@@ -150,7 +158,7 @@ describe Import::BitbucketController do
it "takes the new namespace" do it "takes the new namespace" do
expect(Gitlab::BitbucketImport::ProjectCreator). 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)) and_return(double(execute: true))
post :create, format: :js post :create, format: :js
......
...@@ -4,7 +4,13 @@ require_relative 'import_spec_helper' ...@@ -4,7 +4,13 @@ require_relative 'import_spec_helper'
describe Import::GithubController do describe Import::GithubController do
include ImportSpecHelper 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 before do
sign_in(user) sign_in(user)
...@@ -20,7 +26,7 @@ describe Import::GithubController do ...@@ -20,7 +26,7 @@ describe Import::GithubController do
get :callback 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) expect(controller).to redirect_to(status_import_github_url)
end end
end end
...@@ -30,6 +36,7 @@ describe Import::GithubController do ...@@ -30,6 +36,7 @@ describe Import::GithubController do
@repo = OpenStruct.new(login: 'vim', full_name: 'asd/vim') @repo = OpenStruct.new(login: 'vim', full_name: 'asd/vim')
@org = OpenStruct.new(login: 'company') @org = OpenStruct.new(login: 'company')
@org_repo = OpenStruct.new(login: 'company', full_name: 'company/repo') @org_repo = OpenStruct.new(login: 'company', full_name: 'company/repo')
assign_session_token
end end
it "assigns variables" do it "assigns variables" do
...@@ -66,13 +73,14 @@ describe Import::GithubController do ...@@ -66,13 +73,14 @@ describe Import::GithubController do
before do before do
stub_client(user: github_user, repo: github_repo) stub_client(user: github_user, repo: github_repo)
assign_session_token
end end
context "when the repository owner is the GitHub user" do context "when the repository owner is the GitHub user" do
context "when the GitHub user and GitLab user's usernames match" do context "when the GitHub user and GitLab user's usernames match" do
it "takes the current user's namespace" do it "takes the current user's namespace" do
expect(Gitlab::GithubImport::ProjectCreator). 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)) and_return(double(execute: true))
post :create, format: :js post :create, format: :js
...@@ -84,7 +92,7 @@ describe Import::GithubController do ...@@ -84,7 +92,7 @@ describe Import::GithubController do
it "takes the current user's namespace" do it "takes the current user's namespace" do
expect(Gitlab::GithubImport::ProjectCreator). 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)) and_return(double(execute: true))
post :create, format: :js post :create, format: :js
...@@ -97,6 +105,7 @@ describe Import::GithubController do ...@@ -97,6 +105,7 @@ describe Import::GithubController do
before do before do
github_repo.owner = OpenStruct.new(login: other_username) github_repo.owner = OpenStruct.new(login: other_username)
assign_session_token
end end
context "when a namespace with the GitHub user's username already exists" do context "when a namespace with the GitHub user's username already exists" do
...@@ -105,7 +114,7 @@ describe Import::GithubController do ...@@ -105,7 +114,7 @@ describe Import::GithubController do
context "when the namespace is owned by the GitLab user" do context "when the namespace is owned by the GitLab user" do
it "takes the existing namespace" do it "takes the existing namespace" do
expect(Gitlab::GithubImport::ProjectCreator). 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)) and_return(double(execute: true))
post :create, format: :js post :create, format: :js
...@@ -139,7 +148,7 @@ describe Import::GithubController do ...@@ -139,7 +148,7 @@ describe Import::GithubController do
it "takes the new namespace" do it "takes the new namespace" do
expect(Gitlab::GithubImport::ProjectCreator). 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)) and_return(double(execute: true))
post :create, format: :js post :create, format: :js
......
...@@ -4,7 +4,13 @@ require_relative 'import_spec_helper' ...@@ -4,7 +4,13 @@ require_relative 'import_spec_helper'
describe Import::GitlabController do describe Import::GitlabController do
include ImportSpecHelper 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 before do
sign_in(user) sign_in(user)
...@@ -13,14 +19,13 @@ describe Import::GitlabController do ...@@ -13,14 +19,13 @@ describe Import::GitlabController do
describe "GET callback" do describe "GET callback" do
it "updates access token" do it "updates access token" do
token = "asdasd12345"
allow_any_instance_of(Gitlab::GitlabImport::Client). allow_any_instance_of(Gitlab::GitlabImport::Client).
to receive(:get_token).and_return(token) to receive(:get_token).and_return(token)
stub_omniauth_provider('gitlab') stub_omniauth_provider('gitlab')
get :callback 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) expect(controller).to redirect_to(status_import_gitlab_url)
end end
end end
...@@ -28,6 +33,7 @@ describe Import::GitlabController do ...@@ -28,6 +33,7 @@ describe Import::GitlabController do
describe "GET status" do describe "GET status" do
before do before do
@repo = OpenStruct.new(path: 'vim', path_with_namespace: 'asd/vim') @repo = OpenStruct.new(path: 'vim', path_with_namespace: 'asd/vim')
assign_session_token
end end
it "assigns variables" do it "assigns variables" do
...@@ -67,13 +73,14 @@ describe Import::GitlabController do ...@@ -67,13 +73,14 @@ describe Import::GitlabController do
before do before do
stub_client(user: gitlab_user, project: gitlab_repo) stub_client(user: gitlab_user, project: gitlab_repo)
assign_session_token
end end
context "when the repository owner is the GitLab.com user" do 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 context "when the GitLab.com user and GitLab server user's usernames match" do
it "takes the current user's namespace" do it "takes the current user's namespace" do
expect(Gitlab::GitlabImport::ProjectCreator). 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)) and_return(double(execute: true))
post :create, format: :js post :create, format: :js
...@@ -85,7 +92,7 @@ describe Import::GitlabController do ...@@ -85,7 +92,7 @@ describe Import::GitlabController do
it "takes the current user's namespace" do it "takes the current user's namespace" do
expect(Gitlab::GitlabImport::ProjectCreator). 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)) and_return(double(execute: true))
post :create, format: :js post :create, format: :js
...@@ -98,6 +105,7 @@ describe Import::GitlabController do ...@@ -98,6 +105,7 @@ describe Import::GitlabController do
before do before do
gitlab_repo["namespace"]["path"] = other_username gitlab_repo["namespace"]["path"] = other_username
assign_session_token
end end
context "when a namespace with the GitLab.com user's username already exists" do context "when a namespace with the GitLab.com user's username already exists" do
...@@ -106,7 +114,7 @@ describe Import::GitlabController do ...@@ -106,7 +114,7 @@ describe Import::GitlabController do
context "when the namespace is owned by the GitLab server user" do context "when the namespace is owned by the GitLab server user" do
it "takes the existing namespace" do it "takes the existing namespace" do
expect(Gitlab::GitlabImport::ProjectCreator). 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)) and_return(double(execute: true))
post :create, format: :js post :create, format: :js
...@@ -140,7 +148,7 @@ describe Import::GitlabController do ...@@ -140,7 +148,7 @@ describe Import::GitlabController do
it "takes the new namespace" do it "takes the new namespace" do
expect(Gitlab::GitlabImport::ProjectCreator). 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)) and_return(double(execute: true))
post :create, format: :js post :create, format: :js
......
...@@ -64,8 +64,8 @@ describe 'GitLab Markdown', feature: true do ...@@ -64,8 +64,8 @@ describe 'GitLab Markdown', feature: true do
it 'parses fenced code blocks' do it 'parses fenced code blocks' do
aggregate_failures do aggregate_failures do
expect(doc).to have_selector('pre.code.highlight.white.c') expect(doc).to have_selector('pre.code.highlight.js-syntax-highlight.c')
expect(doc).to have_selector('pre.code.highlight.white.python') expect(doc).to have_selector('pre.code.highlight.js-syntax-highlight.python')
end end
end end
...@@ -224,8 +224,4 @@ describe 'GitLab Markdown', feature: true do ...@@ -224,8 +224,4 @@ describe 'GitLab Markdown', feature: true do
def current_user def current_user
@feat.user @feat.user
end end
def user_color_scheme_class
:white
end
end end
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--
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--
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
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.
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--
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--
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--
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--
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
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
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
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_--
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).
>
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==
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
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_--
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
...@@ -28,8 +28,7 @@ describe EventsHelper do ...@@ -28,8 +28,7 @@ describe EventsHelper do
it 'should display the first line of a code block' do it 'should display the first line of a code block' do
input = "```\nCode block\nwith two lines\n```" input = "```\nCode block\nwith two lines\n```"
expected = '<pre class="code highlight white plaintext"><code>' \ expected = %r{<pre.+><code>Code block\.\.\.</code></pre>}
'Code block...</code></pre>'
expect(event_note(input)).to match(expected) expect(event_note(input)).to match(expected)
end end
...@@ -55,7 +54,7 @@ describe EventsHelper do ...@@ -55,7 +54,7 @@ describe EventsHelper do
it 'should preserve code color scheme' do it 'should preserve code color scheme' do
input = "```ruby\ndef test\n 'hello world'\nend\n```" 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" \ "<code><span class=\"k\">def</span> <span class=\"nf\">test</span>\n" \
" <span class=\"s1\">\'hello world\'</span>\n" \ " <span class=\"s1\">\'hello world\'</span>\n" \
"<span class=\"k\">end</span>" \ "<span class=\"k\">end</span>" \
......
require 'spec_helper' require 'spec_helper'
describe PreferencesHelper do 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 describe 'user_application_theme' do
context 'with a user' do context 'with a user' do
it "returns user's theme's css_class" do it "returns user's theme's css_class" do
user = double('user', theme_id: 3) stub_user(theme_id: 3)
allow(self).to receive(:current_user).and_return(user)
expect(user_application_theme).to eq 'ui_green' expect(helper.user_application_theme).to eq 'ui_green'
end end
it 'returns the default when id is invalid' do 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(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
end end
context 'without a user' do context 'without a user' do
before do
allow(self).to receive(:current_user).and_return(nil)
end
it 'returns the default theme' do 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 end
end end
describe 'dashboard_choices' do describe 'user_color_scheme' do
it 'raises an exception when defined choices may be missing' do context 'with a user' do
expect(User).to receive(:dashboards).and_return(foo: 'foo') it "returns user's scheme's css_class" do
expect { dashboard_choices }.to raise_error(RuntimeError) allow(helper).to receive(:current_user).
end and_return(double(color_scheme_id: 3))
it 'raises an exception when defined choices may be using the wrong key' do expect(helper.user_color_scheme).to eq 'solarized-light'
expect(User).to receive(:dashboards).and_return(foo: 'foo', bar: 'bar') end
expect { dashboard_choices }.to raise_error(KeyError)
end
it 'provides better option descriptions' do it 'returns the default when id is invalid' do
expect(dashboard_choices).to match_array [ allow(helper).to receive(:current_user).
['Your Projects (default)', 'projects'], and_return(double(color_scheme_id: Gitlab::ColorSchemes.count + 5))
['Starred Projects', 'stars'] end
]
end end
end
describe 'user_color_scheme_class' do context 'without a user' do
context 'with current_user is nil' do it 'returns the default theme' do
it 'should return a string' do stub_user
allow(self).to receive(:current_user).and_return(nil)
expect(user_color_scheme_class).to be_kind_of(String) expect(helper.user_color_scheme).
to eq Gitlab::ColorSchemes.default.css_class
end end
end end
end
context 'with a current_user' do def stub_user(messages = {})
(1..5).each do |color_scheme_id| if messages.empty?
context "with color_scheme_id == #{color_scheme_id}" do allow(helper).to receive(:current_user).and_return(nil)
it 'should return a string' do else
current_user = double(color_scheme_id: color_scheme_id) allow(helper).to receive(:current_user).
allow(self).to receive(:current_user).and_return(current_user) and_return(double('user', messages))
expect(user_color_scheme_class).to be_kind_of(String)
end
end
end
end end
end end
end end
require 'spec_helper' require 'spec_helper'
describe Gitlab::BitbucketImport::ProjectCreator do 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 let(:repo) do
{ {
name: 'Vim', name: 'Vim',
...@@ -11,6 +11,9 @@ describe Gitlab::BitbucketImport::ProjectCreator do ...@@ -11,6 +11,9 @@ describe Gitlab::BitbucketImport::ProjectCreator do
}.with_indifferent_access }.with_indifferent_access
end end
let(:namespace){ create(:group, owner: user) } 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 before do
namespace.add_owner(user) namespace.add_owner(user)
...@@ -19,7 +22,7 @@ describe Gitlab::BitbucketImport::ProjectCreator do ...@@ -19,7 +22,7 @@ describe Gitlab::BitbucketImport::ProjectCreator do
it 'creates project' do it 'creates project' do
allow_any_instance_of(Project).to receive(:add_import_job) 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 project = project_creator.execute
expect(project.import_url).to eq("ssh://git@bitbucket.org/asd/vim.git") expect(project.import_url).to eq("ssh://git@bitbucket.org/asd/vim.git")
......
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
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
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
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
require 'spec_helper' require 'spec_helper'
describe Gitlab::GithubImport::ProjectCreator do describe Gitlab::GithubImport::ProjectCreator do
let(:user) { create(:user, github_access_token: "asdffg") } let(:user) { create(:user) }
let(:repo) do let(:repo) do
OpenStruct.new( OpenStruct.new(
login: 'vim', login: 'vim',
...@@ -13,6 +13,8 @@ describe Gitlab::GithubImport::ProjectCreator do ...@@ -13,6 +13,8 @@ describe Gitlab::GithubImport::ProjectCreator do
) )
end end
let(:namespace){ create(:group, owner: user) } let(:namespace){ create(:group, owner: user) }
let(:token) { "asdffg" }
let(:access_params) { { github_access_token: token } }
before do before do
namespace.add_owner(user) namespace.add_owner(user)
...@@ -21,7 +23,7 @@ describe Gitlab::GithubImport::ProjectCreator do ...@@ -21,7 +23,7 @@ describe Gitlab::GithubImport::ProjectCreator do
it 'creates project' do it 'creates project' do
allow_any_instance_of(Project).to receive(:add_import_job) 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 project = project_creator.execute
expect(project.import_url).to eq("https://asdffg@gitlab.com/asd/vim.git") expect(project.import_url).to eq("https://asdffg@gitlab.com/asd/vim.git")
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::GitlabImport::ProjectCreator do describe Gitlab::GitlabImport::ProjectCreator do
let(:user) { create(:user, gitlab_access_token: "asdffg") } let(:user) { create(:user) }
let(:repo) do let(:repo) do
{ {
name: 'vim', name: 'vim',
...@@ -13,6 +13,8 @@ describe Gitlab::GitlabImport::ProjectCreator do ...@@ -13,6 +13,8 @@ describe Gitlab::GitlabImport::ProjectCreator do
}.with_indifferent_access }.with_indifferent_access
end end
let(:namespace){ create(:group, owner: user) } let(:namespace){ create(:group, owner: user) }
let(:token) { "asdffg" }
let(:access_params) { { gitlab_access_token: token } }
before do before do
namespace.add_owner(user) namespace.add_owner(user)
...@@ -21,7 +23,7 @@ describe Gitlab::GitlabImport::ProjectCreator do ...@@ -21,7 +23,7 @@ describe Gitlab::GitlabImport::ProjectCreator do
it 'creates project' do it 'creates project' do
allow_any_instance_of(Project).to receive(:add_import_job) 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 project = project_creator.execute
expect(project.import_url).to eq("https://oauth2:asdffg@gitlab.com/asd/vim.git") expect(project.import_url).to eq("https://oauth2:asdffg@gitlab.com/asd/vim.git")
......
require "spec_helper" require "spec_helper"
describe Gitlab::GoogleCodeImport::Client do 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) } subject { described_class.new(raw_data) }
describe "#valid?" do describe "#valid?" do
......
...@@ -2,7 +2,7 @@ require "spec_helper" ...@@ -2,7 +2,7 @@ require "spec_helper"
describe Gitlab::GoogleCodeImport::Importer do describe Gitlab::GoogleCodeImport::Importer do
let(:mapped_user) { create(:user, username: "thilo123") } 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(:client) { Gitlab::GoogleCodeImport::Client.new(raw_data) }
let(:import_data) do let(:import_data) do
{ {
......
...@@ -86,6 +86,16 @@ module Gitlab::Markdown ...@@ -86,6 +86,16 @@ module Gitlab::Markdown
doc = filter("See #{link}, ok?") doc = filter("See #{link}, ok?")
expect(doc.at_css('a').text).to eq link 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 end
it 'accepts link_attr options' do it 'accepts link_attr options' do
......
...@@ -121,7 +121,6 @@ module Gitlab::Markdown ...@@ -121,7 +121,6 @@ module Gitlab::Markdown
end end
it 'links with adjacent text' do it 'links with adjacent text' do
skip "TODO (rspeicher): Re-enable when usernames can't end in periods."
doc = filter("Mention me (#{reference}.)") doc = filter("Mention me (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{reference}<\/a>\.\)/) expect(doc.to_html).to match(/\(<a.+>#{reference}<\/a>\.\)/)
end end
......
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
...@@ -43,9 +43,6 @@ describe Gitlab::Themes do ...@@ -43,9 +43,6 @@ describe Gitlab::Themes do
ids = [] ids = []
described_class.each { |theme| ids << theme.id } described_class.each { |theme| ids << theme.id }
expect(ids).not_to be_empty 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 end
end end
...@@ -7,7 +7,8 @@ describe API::API, api: true do ...@@ -7,7 +7,8 @@ describe API::API, api: true do
let(:user2) { create(:user) } let(:user2) { create(:user) }
let(:user3) { create(:user) } let(:user3) { create(:user) }
let(:admin) { create(:admin) } 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) } let!(:group2) { create(:group) }
before do before do
......
...@@ -197,7 +197,7 @@ describe GitPushService do ...@@ -197,7 +197,7 @@ describe GitPushService do
end end
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(:issue) { create :issue, project: project }
let(:other_issue) { create :issue, project: project } let(:other_issue) { create :issue, project: project }
let(:commit_author) { create :user } let(:commit_author) { create :user }
...@@ -215,36 +215,47 @@ describe GitPushService do ...@@ -215,36 +215,47 @@ describe GitPushService do
and_return([closing_commit]) and_return([closing_commit])
end end
it "closes issues with commit messages" do context "to default branches" do
service.execute(project, user, @oldrev, @newrev, @ref) it "closes issues" do
service.execute(project, user, @oldrev, @newrev, @ref)
expect(Issue.find(issue.id)).to be_closed expect(Issue.find(issue.id)).to be_closed
end end
it "doesn't create cross-reference notes for a closing reference" do it "adds a note indicating that the issue is now closed" do
expect do expect(SystemNoteService).to receive(:change_status).with(issue, project, commit_author, "closed", closing_commit)
service.execute(project, user, @oldrev, @newrev, @ref) 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 it "doesn't create additional cross-reference notes" do
allow(project).to receive(:default_branch).and_return('durf') expect(SystemNoteService).not_to receive(:cross_reference)
service.execute(project, user, @oldrev, @newrev, @ref)
end
# The push still shouldn't create cross-reference notes. it "doesn't close issues when external issue tracker is in use" do
expect do allow(project).to receive(:default_issues_tracker?).and_return(false)
service.execute(project, user, @oldrev, @newrev, 'refs/heads/hurf')
end.not_to change { Note.where(project_id: project.id, system: true).count }
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 end
it "doesn't close issues when external issue tracker is in use" do context "to non-default branches" do
allow(project).to receive(:default_issues_tracker?).and_return(false) 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. it "doesn't close issues" do
expect do service.execute(project, user, @oldrev, @newrev, @ref)
service.execute(project, user, @oldrev, @newrev, 'refs/heads/hurf') expect(Issue.find(issue.id)).to be_opened
end.not_to change { Note.where(project_id: project.id, system: true).count } end
end end
end end
......
...@@ -13,13 +13,13 @@ describe Projects::UploadService do ...@@ -13,13 +13,13 @@ describe Projects::UploadService do
@link_to_file = upload_file(@project.repository, gif) @link_to_file = upload_file(@project.repository, gif)
end end
it { expect(@link_to_file).to have_key('alt') } 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(:url) }
it { expect(@link_to_file).to have_key('is_image') } 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).to have_value('banana_sample') }
it { expect(@link_to_file['is_image']).to equal(true) } 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("/#{@project.path_with_namespace}") }
it { expect(@link_to_file['url']).to match('banana_sample.gif') } it { expect(@link_to_file[:url]).to match('banana_sample.gif') }
end end
context 'for valid png file' do context 'for valid png file' do
...@@ -29,13 +29,13 @@ describe Projects::UploadService do ...@@ -29,13 +29,13 @@ describe Projects::UploadService do
@link_to_file = upload_file(@project.repository, png) @link_to_file = upload_file(@project.repository, png)
end end
it { expect(@link_to_file).to have_key('alt') } 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(:url) }
it { expect(@link_to_file).to have_value('dk') } it { expect(@link_to_file).to have_value('dk') }
it { expect(@link_to_file).to have_key('is_image') } 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[: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("/#{@project.path_with_namespace}") }
it { expect(@link_to_file['url']).to match('dk.png') } it { expect(@link_to_file[:url]).to match('dk.png') }
end end
context 'for valid jpg file' do context 'for valid jpg file' do
...@@ -44,13 +44,13 @@ describe Projects::UploadService do ...@@ -44,13 +44,13 @@ describe Projects::UploadService do
@link_to_file = upload_file(@project.repository, jpg) @link_to_file = upload_file(@project.repository, jpg)
end end
it { expect(@link_to_file).to have_key('alt') } 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(:url) }
it { expect(@link_to_file).to have_key('is_image') } 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).to have_value('rails_sample') }
it { expect(@link_to_file['is_image']).to equal(true) } 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("/#{@project.path_with_namespace}") }
it { expect(@link_to_file['url']).to match('rails_sample.jpg') } it { expect(@link_to_file[:url]).to match('rails_sample.jpg') }
end end
context 'for txt file' do context 'for txt file' do
...@@ -59,13 +59,13 @@ describe Projects::UploadService do ...@@ -59,13 +59,13 @@ describe Projects::UploadService do
@link_to_file = upload_file(@project.repository, txt) @link_to_file = upload_file(@project.repository, txt)
end end
it { expect(@link_to_file).to have_key('alt') } 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(:url) }
it { expect(@link_to_file).to have_key('is_image') } 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).to have_value('doc_sample.txt') }
it { expect(@link_to_file['is_image']).to equal(false) } 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("/#{@project.path_with_namespace}") }
it { expect(@link_to_file['url']).to match('doc_sample.txt') } it { expect(@link_to_file[:url]).to match('doc_sample.txt') }
end end
context 'for too large a file' do context 'for too large a file' do
......
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
...@@ -100,7 +100,7 @@ class MarkdownFeature ...@@ -100,7 +100,7 @@ class MarkdownFeature
end end
def raw_markdown def raw_markdown
fixture = Rails.root.join('spec/fixtures/markdown.md.erb') markdown = File.read(Rails.root.join('spec/fixtures/markdown.md.erb'))
ERB.new(File.read(fixture)).result(binding) ERB.new(markdown).result(binding)
end end
end end
...@@ -17,6 +17,10 @@ module StubConfiguration ...@@ -17,6 +17,10 @@ module StubConfiguration
allow(Gitlab.config.gravatar).to receive_messages(messages) allow(Gitlab.config.gravatar).to receive_messages(messages)
end end
def stub_reply_by_email_setting(messages)
allow(Gitlab.config.reply_by_email).to receive_messages(messages)
end
private private
# Modifies stubbed messages to also stub possible predicate versions # Modifies stubbed messages to also stub possible predicate versions
......
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
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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment