Commit 55964cbb authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'ce-to-ee' into 'master'

CE to EE



See merge request !484
parents ea503fbc 243bf651
...@@ -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)
- Improve dropdown positioning on the project home page (Hannes Rosenögger)
- Upgrade browser gem to 1.0.0 to avoid warning in IE11 compatibilty mode (Stan Hu)
- Fix "Reload with full diff" URL button in compare branch view (Stan Hu)
- Remove user OAuth tokens from the database and request new tokens each session (Stan Hu)
- Only show recent push event if the branch still exists or a recent merge request has not been created (Stan Hu)
- Remove satellites - Remove satellites
- Better performance for web editor (switched from satellites to rugged) - Better performance for web editor (switched from satellites to rugged)
- Faster merge - Faster merge
- Ability to fetch merge requests from refs/merge-requests/:id - Ability to fetch merge requests from refs/merge-requests/:id
v 7.14.0 v 7.14.0
- Allow displaying of archived projects in the admin interface (Artem Sidorenko)
- Allow configuration of import sources for new projects (Artem Sidorenko)
- Search for comments should be case insensetive
- Create cross-reference for closing references on commits pushed to non-default branches (Maël Valais)
- Ability to search milestones
- Gracefully handle SMTP user input errors (e.g. incorrect email addresses) to prevent Sidekiq retries (Stan Hu)
- Improve abuse reports management from admin area
- Move dashboard activity to separate page
- Improve performance of git blame
- Limit content width to 1200px for most of pages to improve readability on big screens
v 7.14.1 (unreleased)
- Only include base URL in OmniAuth full_host parameter (Stan Hu)
- Fix Error 500 in API when accessing a group that has an avatar (Stan Hu)
v 7.14.0
- Fix bug where non-project members of the target project could set labels on new merge requests.
- 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)
...@@ -295,6 +317,7 @@ v 7.11.0 ...@@ -295,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)
......
...@@ -35,7 +35,7 @@ gem 'rqrcode-rails3' ...@@ -35,7 +35,7 @@ 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
...@@ -276,3 +276,7 @@ end ...@@ -276,3 +276,7 @@ end
gem "newrelic_rpm" gem "newrelic_rpm"
gem 'octokit', '3.7.0' gem 'octokit', '3.7.0'
gem "mail_room", "~> 0.4.0"
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)
...@@ -374,6 +375,7 @@ GEM ...@@ -374,6 +375,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.0)
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)
...@@ -756,7 +758,7 @@ DEPENDENCIES ...@@ -756,7 +758,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)
...@@ -776,6 +778,7 @@ DEPENDENCIES ...@@ -776,6 +778,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
...@@ -810,6 +813,7 @@ DEPENDENCIES ...@@ -810,6 +813,7 @@ DEPENDENCIES
jquery-ui-rails jquery-ui-rails
kaminari (~> 0.15.1) kaminari (~> 0.15.1)
letter_opener letter_opener
mail_room (~> 0.4.0)
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
...@@ -118,6 +118,12 @@ $ -> ...@@ -118,6 +118,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)
......
...@@ -119,6 +119,7 @@ class @ImageFile ...@@ -119,6 +119,7 @@ class @ImageFile
requestImageInfo: (img, callback) -> requestImageInfo: (img, callback) ->
domImg = img.get(0) domImg = img.get(0)
if domImg
if domImg.complete if domImg.complete
callback.call(this, domImg.naturalWidth, domImg.naturalHeight) callback.call(this, domImg.naturalWidth, domImg.naturalHeight)
else else
......
...@@ -51,6 +51,7 @@ class Dispatcher ...@@ -51,6 +51,7 @@ 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()
......
...@@ -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
......
...@@ -7,6 +7,7 @@ class @UsersSelect ...@@ -7,6 +7,7 @@ class @UsersSelect
@skipLdap = $(select).hasClass('skip_ldap') @skipLdap = $(select).hasClass('skip_ldap')
@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')
...@@ -110,6 +111,7 @@ class @UsersSelect ...@@ -110,6 +111,7 @@ class @UsersSelect
project_id: @projectId project_id: @projectId
group_id: @groupId group_id: @groupId
skip_ldap: @skipLdap skip_ldap: @skipLdap
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;
}
...@@ -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;
/* /*
......
...@@ -382,3 +382,23 @@ table { ...@@ -382,3 +382,23 @@ table {
border-color: #EEE !important; border-color: #EEE !important;
} }
} }
.center-top-menu {
border-bottom: 1px solid #EEE;
list-style: none;
text-align: center;
padding-bottom: 15px;
margin-bottom: 15px;
li {
display: inline-block;
a {
padding: 10px;
}
&.active a {
color: #666;
}
}
}
...@@ -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;
......
...@@ -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;
}
}
}
...@@ -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;
} }
......
...@@ -6,3 +6,11 @@ ...@@ -6,3 +6,11 @@
font-size: 30px; font-size: 30px;
} }
} }
.explore-trending-block {
.lead {
line-height: 32px;
font-size: 18px;
margin-top: 10px;
}
}
...@@ -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;
} }
} }
...@@ -316,3 +323,34 @@ table.table.protected-branches-list tr.no-border { ...@@ -316,3 +323,34 @@ 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;
}
}
.project-row {
.project-full-name {
font-weight: bold;
font-size: 15px;
}
.project-description {
color: #888;
font-size: 13px;
p {
margin-bottom: 0;
color: #888;
}
}
}
.my-projects .project-row {
padding: 10px 0;
}
...@@ -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,8 +7,7 @@ ...@@ -7,8 +7,7 @@
* $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;
...@@ -24,10 +23,7 @@ ...@@ -24,10 +23,7 @@
} }
} }
} }
}
}
.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
...@@ -29,6 +29,15 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -29,6 +29,15 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
end end
end end
import_sources = params[:application_setting][:import_sources]
if import_sources.nil?
params[:application_setting][:import_sources] = []
else
import_sources.map! do |source|
source.to_str
end
end
params.require(:application_setting).permit( params.require(:application_setting).permit(
:default_projects_limit, :default_projects_limit,
:default_branch_protection, :default_branch_protection,
...@@ -48,6 +57,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -48,6 +57,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:version_check_enabled, :version_check_enabled,
:user_oauth_applications, :user_oauth_applications,
restricted_visibility_levels: [], restricted_visibility_levels: [],
import_sources: []
) )
end end
end end
...@@ -9,6 +9,7 @@ class Admin::ProjectsController < Admin::ApplicationController ...@@ -9,6 +9,7 @@ class Admin::ProjectsController < Admin::ApplicationController
@projects = @projects.where("visibility_level IN (?)", params[:visibility_levels]) if params[:visibility_levels].present? @projects = @projects.where("visibility_level IN (?)", params[:visibility_levels]) if params[:visibility_levels].present?
@projects = @projects.with_push if params[:with_push].present? @projects = @projects.with_push if params[:with_push].present?
@projects = @projects.abandoned if params[:abandoned].present? @projects = @projects.abandoned if params[:abandoned].present?
@projects = @projects.non_archived unless params[:with_archived].present?
@projects = @projects.search(params[:name]) if params[:name].present? @projects = @projects.search(params[:name]) if params[:name].present?
@projects = @projects.sort(@sort = params[:sort]) @projects = @projects.sort(@sort = params[:sort])
@projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]).per(PER_PAGE) @projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]).per(PER_PAGE)
......
...@@ -20,7 +20,7 @@ class ApplicationController < ActionController::Base ...@@ -20,7 +20,7 @@ class ApplicationController < ActionController::Base
protect_from_forgery with: :exception protect_from_forgery with: :exception
helper_method :abilities, :can?, :current_application_settings helper_method :abilities, :can?, :current_application_settings
helper_method :github_import_enabled?, :gitlab_import_enabled?, :bitbucket_import_enabled? helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :gitorious_import_enabled?, :google_code_import_enabled?, :git_import_enabled?
rescue_from Encoding::CompatibilityError do |exception| rescue_from Encoding::CompatibilityError do |exception|
log_exception(exception) log_exception(exception)
...@@ -304,15 +304,43 @@ class ApplicationController < ActionController::Base ...@@ -304,15 +304,43 @@ class ApplicationController < ActionController::Base
@issuable_finder.execute @issuable_finder.execute
end end
def import_sources_enabled?
!current_application_settings.import_sources.empty?
end
def github_import_enabled? def github_import_enabled?
current_application_settings.import_sources.include?('github')
end
def github_import_configured?
Gitlab::OAuth::Provider.enabled?(:github) Gitlab::OAuth::Provider.enabled?(:github)
end end
def gitlab_import_enabled? def gitlab_import_enabled?
request.host != 'gitlab.com' && current_application_settings.import_sources.include?('gitlab')
end
def gitlab_import_configured?
Gitlab::OAuth::Provider.enabled?(:gitlab) Gitlab::OAuth::Provider.enabled?(:gitlab)
end end
def bitbucket_import_enabled? def bitbucket_import_enabled?
current_application_settings.import_sources.include?('bitbucket')
end
def bitbucket_import_configured?
Gitlab::OAuth::Provider.enabled?(:bitbucket) && Gitlab::BitbucketImport.public_key.present? Gitlab::OAuth::Provider.enabled?(:bitbucket) && Gitlab::BitbucketImport.public_key.present?
end end
def gitorious_import_enabled?
current_application_settings.import_sources.include?('gitorious')
end
def google_code_import_enabled?
current_application_settings.import_sources.include?('google_code')
end
def git_import_enabled?
current_application_settings.import_sources.include?('git')
end
end end
...@@ -34,8 +34,14 @@ class AutocompleteController < ApplicationController ...@@ -34,8 +34,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
......
...@@ -7,6 +7,7 @@ class Explore::ProjectsController < Explore::ApplicationController ...@@ -7,6 +7,7 @@ class Explore::ProjectsController < Explore::ApplicationController
@tags = @projects.tags_on(:tags) @tags = @projects.tags_on(:tags)
@projects = @projects.tagged_with(params[:tag]) if params[:tag].present? @projects = @projects.tagged_with(params[:tag]) if params[:tag].present?
@projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present? @projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present?
@projects = @projects.non_archived
@projects = @projects.search(params[:search]) if params[:search].present? @projects = @projects.search(params[:search]) if params[:search].present?
@projects = @projects.sort(@sort = params[:sort]) @projects = @projects.sort(@sort = params[:sort])
@projects = @projects.includes(:namespace).page(params[:page]).per(PER_PAGE) @projects = @projects.includes(:namespace).page(params[:page]).per(PER_PAGE)
...@@ -14,6 +15,7 @@ class Explore::ProjectsController < Explore::ApplicationController ...@@ -14,6 +15,7 @@ class Explore::ProjectsController < Explore::ApplicationController
def trending def trending
@trending_projects = TrendingProjectsFinder.new.execute(current_user) @trending_projects = TrendingProjectsFinder.new.execute(current_user)
@trending_projects = @trending_projects.non_archived
@trending_projects = @trending_projects.page(params[:page]).per(PER_PAGE) @trending_projects = @trending_projects.page(params[:page]).per(PER_PAGE)
end end
......
...@@ -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
class Import::GitoriousController < Import::BaseController class Import::GitoriousController < Import::BaseController
before_action :verify_gitorious_import_enabled
def new def new
redirect_to client.authorize_url(callback_import_gitorious_url) redirect_to client.authorize_url(callback_import_gitorious_url)
...@@ -40,4 +41,8 @@ class Import::GitoriousController < Import::BaseController ...@@ -40,4 +41,8 @@ class Import::GitoriousController < Import::BaseController
@client ||= Gitlab::GitoriousImport::Client.new(session[:gitorious_repos]) @client ||= Gitlab::GitoriousImport::Client.new(session[:gitorious_repos])
end end
def verify_gitorious_import_enabled
not_found! unless gitorious_import_enabled?
end
end end
class Import::GoogleCodeController < Import::BaseController class Import::GoogleCodeController < Import::BaseController
before_action :verify_google_code_import_enabled
before_action :user_map, only: [:new_user_map, :create_user_map] before_action :user_map, only: [:new_user_map, :create_user_map]
def new def new
...@@ -104,6 +105,10 @@ class Import::GoogleCodeController < Import::BaseController ...@@ -104,6 +105,10 @@ class Import::GoogleCodeController < Import::BaseController
@client ||= Gitlab::GoogleCodeImport::Client.new(session[:google_code_dump]) @client ||= Gitlab::GoogleCodeImport::Client.new(session[:google_code_dump])
end end
def verify_google_code_import_enabled
not_found! unless google_code_import_enabled?
end
def user_map def user_map
@user_map ||= begin @user_map ||= begin
user_map = client.user_map user_map = client.user_map
......
...@@ -8,6 +8,28 @@ class Projects::BlameController < Projects::ApplicationController ...@@ -8,6 +8,28 @@ class Projects::BlameController < Projects::ApplicationController
def show def show
@blob = @repository.blob_at(@commit.id, @path) @blob = @repository.blob_at(@commit.id, @path)
@blame = Gitlab::Git::Blame.new(@repository, @commit.id, @path) @blame = group_blame_lines
end
def group_blame_lines
blame = Gitlab::Git::Blame.new(@repository, @commit.id, @path)
prev_sha = nil
groups = []
current_group = nil
blame.each do |commit, line|
if prev_sha && prev_sha == commit.sha
current_group[:lines] << line
else
groups << current_group if current_group.present?
current_group = { commit: commit, lines: [line] }
end
prev_sha = commit.sha
end
groups << current_group if current_group.present?
groups
end end
end end
...@@ -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
......
...@@ -43,4 +43,21 @@ module ApplicationSettingsHelper ...@@ -43,4 +43,21 @@ module ApplicationSettingsHelper
end end
end end
end end
# Return a group of checkboxes that use Bootstrap's button plugin for a
# toggle button effect.
def import_sources_checkboxes(help_block_id)
Gitlab::ImportSources.options.map do |name, source|
checked = current_application_settings.import_sources.include?(source)
css_class = 'btn'
css_class += ' active' if checked
checkbox_name = 'application_setting[import_sources][]'
label_tag(checkbox_name, class: css_class) do
check_box_tag(checkbox_name, source, checked,
autocomplete: 'off',
'aria-describedby' => help_block_id) + name
end
end
end
end end
...@@ -10,7 +10,7 @@ module ExploreHelper ...@@ -10,7 +10,7 @@ module ExploreHelper
options = exist_opts.merge(options) options = exist_opts.merge(options)
path = request.path path = explore_projects_path
path << "?#{options.to_param}" path << "?#{options.to_param}"
path path
end end
......
...@@ -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
...@@ -11,6 +11,7 @@ module SelectsHelper ...@@ -11,6 +11,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 = {
...@@ -19,7 +20,8 @@ module SelectsHelper ...@@ -19,7 +20,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
......
...@@ -67,6 +67,14 @@ module TabHelper ...@@ -67,6 +67,14 @@ module TabHelper
path.any? do |single_path| path.any? do |single_path|
current_path?(single_path) current_path?(single_path)
end end
elsif page = options.delete(:page)
unless page.respond_to?(:each)
page = [page]
end
page.any? do |single_page|
current_page?(single_page)
end
else else
c = options.delete(:controller) c = options.delete(:controller)
a = options.delete(:action) a = options.delete(:action)
......
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::AdminNotification include Emails::AdminNotification
...@@ -9,22 +9,9 @@ class Notify < ActionMailer::Base ...@@ -9,22 +9,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,
...@@ -49,13 +36,6 @@ class Notify < ActionMailer::Base ...@@ -49,13 +36,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)
...@@ -86,14 +66,6 @@ class Notify < ActionMailer::Base ...@@ -86,14 +66,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
...@@ -127,14 +99,37 @@ class Notify < ActionMailer::Base ...@@ -127,14 +99,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,
...@@ -145,19 +140,17 @@ class Notify < ActionMailer::Base ...@@ -145,19 +140,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
...@@ -23,10 +23,12 @@ ...@@ -23,10 +23,12 @@
# user_oauth_applications :boolean default(TRUE) # user_oauth_applications :boolean default(TRUE)
# after_sign_out_path :string(255) # after_sign_out_path :string(255)
# session_expire_delay :integer default(10080), not null # session_expire_delay :integer default(10080), not null
# import_sources :text
# #
class ApplicationSetting < ActiveRecord::Base class ApplicationSetting < ActiveRecord::Base
serialize :restricted_visibility_levels serialize :restricted_visibility_levels
serialize :import_sources
serialize :restricted_signup_domains, Array serialize :restricted_signup_domains, Array
attr_accessor :restricted_signup_domains_raw attr_accessor :restricted_signup_domains_raw
...@@ -53,6 +55,16 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -53,6 +55,16 @@ class ApplicationSetting < ActiveRecord::Base
end end
end end
validates_each :import_sources do |record, attr, value|
unless value.nil?
value.each do |source|
unless Gitlab::ImportSources.options.has_value?(source)
record.errors.add(attr, "'#{source}' is not a import source")
end
end
end
end
def self.current def self.current
ApplicationSetting.last ApplicationSetting.last
end end
...@@ -71,7 +83,8 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -71,7 +83,8 @@ class ApplicationSetting < ActiveRecord::Base
session_expire_delay: Settings.gitlab['session_expire_delay'], session_expire_delay: Settings.gitlab['session_expire_delay'],
default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'], default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'], default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
restricted_signup_domains: Settings.gitlab['restricted_signup_domains'] restricted_signup_domains: Settings.gitlab['restricted_signup_domains'],
import_sources: ['github','bitbucket','gitlab','gitorious','google_code','git']
) )
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'
......
...@@ -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?
......
...@@ -90,7 +90,7 @@ class Note < ActiveRecord::Base ...@@ -90,7 +90,7 @@ class Note < ActiveRecord::Base
end end
def search(query) def search(query)
where("note like :query", query: "%#{query}%") where("LOWER(note) like :query", query: "%#{query.downcase}%")
end end
end end
......
...@@ -222,7 +222,7 @@ class Project < ActiveRecord::Base ...@@ -222,7 +222,7 @@ class Project < ActiveRecord::Base
end end
def search(query) def search(query)
joins(:namespace).where('projects.archived = ?', false). joins(:namespace).
where('LOWER(projects.name) LIKE :query OR where('LOWER(projects.name) LIKE :query OR
LOWER(projects.path) LIKE :query OR LOWER(projects.path) LIKE :query OR
LOWER(namespaces.name) LIKE :query OR LOWER(namespaces.name) LIKE :query OR
......
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
...@@ -500,8 +500,19 @@ class User < ActiveRecord::Base ...@@ -500,8 +500,19 @@ class User < ActiveRecord::Base
events = recent_events.code_push.where("created_at > ?", Time.now - 2.hours) events = recent_events.code_push.where("created_at > ?", Time.now - 2.hours)
events = events.where(project_id: project_id) if project_id events = events.where(project_id: project_id) if project_id
# Take only latest one # Use the latest event that has not been pushed or merged recently
events = events.recent.limit(1).first events.recent.find do |event|
project = Project.find_by_id(event.project_id)
next unless project
repo = project.repository
if repo.branch_names.include?(event.branch_name)
merge_requests = MergeRequest.where("created_at >= ?", event.created_at).
where(source_project_id: project.id,
source_branch: event.branch_name)
merge_requests.empty?
end
end
end end
def projects_sorted_by_activity def projects_sorted_by_activity
......
...@@ -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.reference_issue_tracker? if project.reference_issue_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
......
...@@ -27,6 +27,20 @@ ...@@ -27,6 +27,20 @@
- restricted_level_checkboxes('restricted-visibility-help').each do |level| - restricted_level_checkboxes('restricted-visibility-help').each do |level|
= level = level
%span.help-block#restricted-visibility-help Selected levels cannot be used by non-admin users for projects or snippets %span.help-block#restricted-visibility-help Selected levels cannot be used by non-admin users for projects or snippets
.form-group
= f.label :import_sources, class: 'control-label col-sm-2'
.col-sm-10
- data_attrs = { toggle: 'buttons' }
.btn-group{ data: data_attrs }
- import_sources_checkboxes('import-sources-help').each do |source|
= source
%span.help-block#import-sources-help
Enabled sources for code import during project creation. OmniAuth must be configured for GitHub
= link_to "(?)", help_page_path("integration", "github")
, Bitbucket
= link_to "(?)", help_page_path("integration", "bitbucket")
and GitLab.com
= link_to "(?)", help_page_path("integration", "gitlab")
.form-group .form-group
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
.checkbox .checkbox
......
...@@ -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
......
...@@ -23,6 +23,10 @@ ...@@ -23,6 +23,10 @@
= label_tag :abandoned do = label_tag :abandoned do
= check_box_tag :abandoned, 1, params[:abandoned] = check_box_tag :abandoned, 1, params[:abandoned]
%span No activity over 6 month %span No activity over 6 month
.checkbox
= label_tag :with_archived do
= check_box_tag :with_archived, 1, params[:with_archived]
%span Show archived projects
%fieldset %fieldset
%strong Visibility level: %strong Visibility level:
...@@ -73,6 +77,8 @@ ...@@ -73,6 +77,8 @@
= visibility_level_icon(project.visibility_level) = visibility_level_icon(project.visibility_level)
= link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project] = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project]
.pull-right .pull-right
- if project.archived
%span.label.label-warning archived
%span.label.label-gray %span.label.label-gray
= repository_size(project) = repository_size(project)
= link_to 'Edit', edit_namespace_project_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm" = link_to 'Edit', edit_namespace_project_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm"
......
%ul.center-top-menu
= nav_link(page: [dashboard_groups_path]) do
= link_to dashboard_groups_path, title: 'Your groups', data: {placement: 'right'} do
Your Groups
= nav_link(page: [explore_groups_path]) do
= link_to explore_groups_path, title: 'Explore groups', data: {toggle: 'tooltip', placement: 'bottom'} do
Explore Groups
.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,7 @@ ...@@ -7,4 +7,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, projects_limit: 20 %ul.projects-list.bordered-list.my-projects
- @projects.each do |project|
%li.project-row
= render partial: 'shared/project', locals: { project: project, avatar: true, stars: true }
%ul.center-top-menu
= nav_link(path: ['dashboard#show', 'root#show']) do
= link_to dashboard_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do
Your Projects
= nav_link(page: starred_dashboard_projects_path) do
= link_to starred_dashboard_projects_path, title: 'Starred Projects', data: {placement: 'right'} do
Starred Projects
= nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path]) do
= link_to explore_root_path, title: 'Explore', data: {toggle: 'tooltip', placement: 'bottom'} do
Explore 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'
- page_title "Groups" - page_title "Groups"
%h3.page-title = render 'dashboard/groups_head'
Group Membership
.slead
Group members have access to all group projects.
- if current_user.can_create_group? - if current_user.can_create_group?
%span.pull-right.hidden-xs %span.pull-right.hidden-xs
= link_to new_group_path, class: "btn btn-new" 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
%p.light
Group members have access to all group projects.
%hr
.panel.panel-default .panel.panel-default
.panel-heading .panel-heading
%strong Groups %strong Groups
......
- page_title "Starred Projects" - page_title "Starred Projects"
= render 'dashboard/projects_head'
- if @projects.any? - if @projects.any?
= 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'
......
...@@ -2,14 +2,12 @@ ...@@ -2,14 +2,12 @@
- if current_user - if current_user
= auto_discovery_link_tag(:atom, dashboard_url(format: :atom, private_token: current_user.private_token), title: "All activity") = auto_discovery_link_tag(:atom, dashboard_url(format: :atom, private_token: current_user.private_token), title: "All activity")
- if @projects.any? = render 'dashboard/projects_head'
= render 'shared/show_aside'
.dashboard.row - if @last_push
%section.activities.col-md-8 = render "events/event_last_push", event: @last_push
= 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
- page_title "Groups" - page_title "Groups"
.clearfix - if current_user
= render 'dashboard/groups_head'
.clearfix.append-bottom-10
.pull-left .pull-left
= form_tag explore_groups_path, method: :get, class: 'form-inline form-tiny' do |f| = form_tag explore_groups_path, method: :get, class: 'form-inline form-tiny' do |f|
= hidden_field_tag :sort, @sort = hidden_field_tag :sort, @sort
...@@ -28,15 +30,12 @@ ...@@ -28,15 +30,12 @@
= link_to explore_groups_path(sort: sort_value_oldest_updated) do = link_to explore_groups_path(sort: sort_value_oldest_updated) do
= sort_title_oldest_updated = sort_title_oldest_updated
%hr
%ul.bordered-list %ul.bordered-list
- @groups.each do |group| - @groups.each do |group|
%li %li
.clearfix .clearfix
%h4 %h4
= link_to group_path(id: group.path) do = link_to group_path(id: group.path) do
%i.fa.fa-users
= group.name = group.name
.clearfix .clearfix
%p %p
......
.dropdown.inline
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
%span.light sort:
- if @sort.present?
= sort_options_hash[@sort]
- elsif current_page?(trending_explore_projects_path) || current_page?(explore_root_path)
Trending projects
- elsif current_page?(starred_explore_projects_path)
Most stars
- else
= sort_title_recently_created
%b.caret
%ul.dropdown-menu
%li
= link_to trending_explore_projects_path do
Trending projects
= link_to starred_explore_projects_path do
Most stars
= link_to explore_projects_filter_path(sort: sort_value_recently_created) do
= sort_title_recently_created
= link_to explore_projects_filter_path(sort: sort_value_oldest_created) do
= sort_title_oldest_created
= link_to explore_projects_filter_path(sort: sort_value_recently_updated) do
= sort_title_recently_updated
= link_to explore_projects_filter_path(sort: sort_value_oldest_updated) do
= sort_title_oldest_updated
...@@ -46,22 +46,4 @@ ...@@ -46,22 +46,4 @@
= link_to explore_projects_filter_path(tag: tag.name) do = link_to explore_projects_filter_path(tag: tag.name) do
%i.fa.fa-tag %i.fa.fa-tag
= tag.name = tag.name
= render 'explore/projects/dropdown'
.dropdown.inline
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
%span.light sort:
- if @sort.present?
= sort_options_hash[@sort]
- else
= sort_title_recently_created
%b.caret
%ul.dropdown-menu
%li
= link_to explore_projects_filter_path(sort: sort_value_recently_created) do
= sort_title_recently_created
= link_to explore_projects_filter_path(sort: sort_value_oldest_created) do
= sort_title_oldest_created
= link_to explore_projects_filter_path(sort: sort_value_recently_updated) do
= sort_title_recently_updated
= link_to explore_projects_filter_path(sort: sort_value_oldest_updated) do
= sort_title_oldest_updated
%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
%ul.projects-list.bordered-list.my-projects.public-projects
- projects.each do |project|
%li.project-row
= render partial: 'shared/project', locals: { project: project, avatar: true, stars: true }
- unless projects.present?
.nothing-here-block No such projects
- page_title "Projects" - page_title "Projects"
- if current_user
= render 'dashboard/projects_head'
.clearfix .clearfix
= render 'filter' = render 'filter'
%br
%hr = render 'projects', projects: @projects
.public-projects = paginate @projects, theme: "gitlab"
%ul.bordered-list.top-list
= render @projects
- unless @projects.present?
.nothing-here-block No public projects
= paginate @projects, theme: "gitlab"
- page_title "Starred Projects" - page_title "Starred Projects"
- if current_user
= render 'dashboard/projects_head'
.explore-trending-block .explore-trending-block
%p.lead .lead
%i.fa.fa-star %i.fa.fa-star
See most starred projects See most starred projects
%hr .pull-right
.public-projects = render 'explore/projects/dropdown'
%ul.bordered-list = render 'projects', projects: @starred_projects
= render @starred_projects
= paginate @starred_projects, theme: 'gitlab' = paginate @starred_projects, theme: 'gitlab'
- page_title "Trending Projects" - page_title "Trending Projects"
- if current_user
= render 'dashboard/projects_head'
.explore-title .explore-title
%h3 %h3
Explore GitLab Explore GitLab
...@@ -6,10 +8,9 @@ ...@@ -6,10 +8,9 @@
Discover projects and groups. Share your projects with others Discover projects and groups. Share your projects with others
%hr %hr
.explore-trending-block .explore-trending-block
%p.lead .lead
%i.fa.fa-comments-o %i.fa.fa-comments-o
See most discussed projects for last month See most discussed projects for last month
%hr .pull-right
.public-projects = render 'explore/projects/dropdown'
%ul.bordered-list = render 'projects', projects: @trending_projects
= 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'
......
...@@ -12,11 +12,14 @@ ...@@ -12,11 +12,14 @@
- @projects.each do |project| - @projects.each do |project|
%li %li
.list-item-name .list-item-name
%span{ class: visibility_level_color(project.visibility_level) }
= visibility_level_icon(project.visibility_level) = visibility_level_icon(project.visibility_level)
%strong= link_to project.name_with_namespace, namespace_project_path(project.namespace, project) %strong= link_to project.name_with_namespace, project
.pull-right
- if project.archived
%span.label.label-warning archived
%span.label.label-gray %span.label.label-gray
= repository_size(project) = repository_size(project)
.pull-right
= link_to 'Members', namespace_project_project_members_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm" = link_to 'Members', namespace_project_project_members_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm"
= link_to 'Edit', edit_namespace_project_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm" = link_to 'Edit', edit_namespace_project_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm"
= link_to 'Remove', project, data: { confirm: remove_project_message(project)}, method: :delete, class: "btn btn-sm btn-remove" = link_to 'Remove', project, data: { confirm: remove_project_message(project)}, method: :delete, class: "btn btn-sm btn-remove"
......
...@@ -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,7 +33,7 @@ ...@@ -33,7 +33,7 @@
.content_list .content_list
= spinner = spinner
%aside.side.col-md-4 %aside.side.col-md-5
= render "projects", projects: @projects = render "projects", projects: @projects
%br %br
= render "shared_projects", projects: @shared_projects = render "shared_projects", projects: @shared_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
......
- page_title "Explore" - page_title "Explore"
- header_title "Explore GitLab", explore_root_path - if current_user
- sidebar "explore" - header_title "Dashboard", root_path
- else
- header_title "Explore GitLab", explore_root_path
- sidebar "dashboard"
= render template: "layouts/application" = render template: "layouts/application"
%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'], 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 dashboard_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
Your Projects Projects
= nav_link(path: 'projects#starred') do = nav_link(path: 'dashboard#activity') do
= link_to starred_dashboard_projects_path, title: 'Starred Projects', data: {placement: 'right'} do = link_to activity_dashboard_path, title: 'Activity', data: {placement: 'right'} do
= icon('star fw') = icon('dashboard fw')
%span %span
Starred Projects Activity
= nav_link(controller: :groups) do = nav_link(controller: :groups) do
= link_to dashboard_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')
%span %span
Groups Groups
- if current_user
= nav_link(controller: :milestones) do = nav_link(controller: :milestones) do
= link_to dashboard_milestones_path, title: 'Milestones', data: {placement: 'right'} do = link_to dashboard_milestones_path, title: 'Milestones', data: {placement: 'right'} do
= icon('clock-o fw') = icon('clock-o fw')
...@@ -31,6 +32,17 @@ ...@@ -31,6 +32,17 @@
%span %span
Merge Requests Merge Requests
%span.count= current_user.assigned_merge_requests.opened.count %span.count= current_user.assigned_merge_requests.opened.count
= nav_link(controller: :snippets) do
= link_to (current_user ? user_snippets_path(current_user) : snippets_path), title: 'Your snippets', data: {placement: 'right'} do
= icon('clipboard fw')
%span
Snippets
- if current_user
= nav_link(controller: :profile) do
= link_to profile_path, title: 'Profile settings', data: {toggle: 'tooltip', placement: 'bottom'} do
= icon('user fw')
%span
Profile
= nav_link(controller: :help) do = nav_link(controller: :help) do
= link_to help_path, title: 'Help', data: {placement: 'right'} do = link_to help_path, title: 'Help', data: {placement: 'right'} do
= icon('question-circle fw') = icon('question-circle fw')
......
%ul.nav.nav-sidebar
= nav_link(path: 'projects#trending') do
= link_to explore_root_path, title: 'Trending Projects', data: {placement: 'right'} do
= icon('comments fw')
%span Trending Projects
= nav_link(path: 'projects#starred') do
= link_to starred_explore_projects_path, title: 'Most-starred Projects', data: {placement: 'right'} do
= icon('star fw')
%span Most-starred Projects
= nav_link(path: 'projects#index') do
= link_to explore_projects_path, title: 'All Projects', data: {placement: 'right'} do
= icon('bookmark fw')
%span All Projects
= nav_link(controller: :groups) do
= link_to explore_groups_path, title: 'All Groups', data: {placement: 'right'} do
= icon('group fw')
%span All Groups
%ul.nav.nav-sidebar
- if current_user
= nav_link(path: user_snippets_path(current_user), html_options: {class: 'home'}) do
= link_to user_snippets_path(current_user), title: 'Your snippets', data: {placement: 'right'} do
= icon('dashboard fw')
%span
Your Snippets
= nav_link(path: snippets_path) do
= link_to snippets_path, title: 'Discover snippets', data: {placement: 'right'} do
= icon('globe fw')
%span
Discover Snippets
...@@ -36,6 +36,10 @@ ...@@ -36,6 +36,10 @@
&mdash; &mdash;
%br %br
- if @target_url - if @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} #{link_to "View it on GitLab", @target_url}
= email_action @target_url = email_action @target_url
- if @project && !@disable_footer - if @project && !@disable_footer
......
- page_title 'Snippets' - page_title 'Snippets'
- header_title 'Snippets', snippets_path - if current_user
- sidebar "snippets" - header_title "Dashboard", root_path
- else
- header_title 'Snippets', snippets_path
- sidebar "dashboard"
= render template: "layouts/application" = render template: "layouts/application"
%p.slead %p.slead
Should you ever lose your phone, each of these recovery codes can be used one Should you ever lose your phone, each of these recovery codes can be used one
time each to regain access to your account. Please save them in a safe place. time each to regain access to your account. Please save them in a safe place, or you
%b will
lose access to your account.
.codes.well .codes.well
%ul %ul
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
.modal-body .modal-body
To enable importing projects from Bitbucket, To enable importing projects from Bitbucket,
- if current_user.admin? - if current_user.admin?
you need to as administrator you need to configure
- else - else
your GitLab administrator needs to ask your GitLab administrator to configure
== #{link_to 'setup OAuth integration', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/bitbucket.md'}. == #{link_to 'OAuth integration', help_page_path("integration", "bitbucket")}.
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
.modal-body .modal-body
To enable importing projects from GitHub, To enable importing projects from GitHub,
- if current_user.admin? - if current_user.admin?
you need to as administrator you need to configure
- else - else
your GitLab administrator needs to ask your Gitlab administrator to configure
== #{link_to 'setup OAuth integration', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/github.md'}. == #{link_to 'OAuth integration', help_page_path("integration", "github")}.
\ No newline at end of file
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
.modal-body .modal-body
To enable importing projects from GitLab.com, To enable importing projects from GitLab.com,
- if current_user.admin? - if current_user.admin?
you need to as administrator you need to configure
- else - else
your GitLab administrator needs to ask your GitLab administrator to configure
== #{link_to 'setup OAuth integration', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/gitlab.md'}. == #{link_to 'OAuth integration', help_page_path("integration", "gitlab")}.
\ No newline at end of file
...@@ -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)
......
...@@ -13,12 +13,11 @@ ...@@ -13,12 +13,11 @@
.file-content.blame.highlight .file-content.blame.highlight
%table %table
- current_line = 1 - current_line = 1
- @blame.each do |raw_commit, line| - @blame.each do |blame_group|
%tr %tr
%td.blame-commit %td.blame-commit
.commit .commit
- unless @prev_commit && @prev_commit.sha == raw_commit.sha - commit = Commit.new(blame_group[:commit], @project)
- commit = Commit.new(raw_commit, @project)
.commit-row-title .commit-row-title
%strong %strong
= link_to_gfm truncate(commit.title, length: 35), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "cdark" = link_to_gfm truncate(commit.title, length: 35), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "cdark"
...@@ -28,15 +27,18 @@ ...@@ -28,15 +27,18 @@
.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)}
- @prev_commit = raw_commit
%td.lines.blame-numbers %td.lines.blame-numbers
%pre %pre
= current_line - line_count = blame_group[:lines].count
- current_line += 1 - (current_line...(current_line + line_count)).each do |i|
= i
\
- current_line += line_count
%td.lines %td.lines
%pre{class: 'code highlight white'} %pre{class: 'code highlight white'}
%code %code
- blame_group[:lines].each do |line|
:erb :erb
<%= highlight(@blob.name, line, nowrap: true, continue: true).html_safe %> <%= highlight(@blob.name, line, nowrap: true, continue: true).html_safe %>
...@@ -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
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment