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
config/initializers/smtp_settings.rb
config/resque.yml
config/unicorn.rb
config/mail_room.yml
coverage/*
db/*.sqlite3
db/*.sqlite3-journal
......
Please view this file on the master branch, on stable branches it's out of date.
v 8.0.0 (unreleased)
- Improve dropdown positioning on the project home page (Hannes Rosenögger)
- Upgrade browser gem to 1.0.0 to avoid warning in IE11 compatibilty mode (Stan Hu)
- Fix "Reload with full diff" URL button in compare branch view (Stan Hu)
- Remove user OAuth tokens from the database and request new tokens each session (Stan Hu)
- Only show recent push event if the branch still exists or a recent merge request has not been created (Stan Hu)
- Remove satellites
- Better performance for web editor (switched from satellites to rugged)
- Faster merge
- Ability to fetch merge requests from refs/merge-requests/:id
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)
- 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)
......@@ -295,6 +317,7 @@ v 7.11.0
- Protect OmniAuth request phase against CSRF.
- Don't send notifications to mentioned users that don't have access to the project in question.
- Add search issues/MR by number
- Change plots to bar graphs in commit statistics screen
- Move snippets UI to fluid layout
- Improve UI for sidebar. Increase separation between navigation and content
- Improve new project command options (Ben Bodenmiller)
......
......@@ -35,7 +35,7 @@ gem 'rqrcode-rails3'
gem 'attr_encrypted', '1.3.4'
# Browser detection
gem "browser", '~> 0.8.0'
gem "browser", '~> 1.0.0'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
......@@ -276,3 +276,7 @@ end
gem "newrelic_rpm"
gem 'octokit', '3.7.0'
gem "mail_room", "~> 0.4.0"
gem 'email_reply_parser'
......@@ -76,7 +76,7 @@ GEM
ruby_parser (~> 3.5.0)
sass (~> 3.0)
terminal-table (~> 1.4)
browser (0.8.0)
browser (1.0.0)
builder (3.2.2)
byebug (3.2.0)
columnize (~> 0.8)
......@@ -156,6 +156,7 @@ GEM
dotenv (0.9.0)
dropzonejs-rails (0.7.1)
rails (> 3.1)
email_reply_parser (0.5.8)
email_spec (1.6.0)
launchy (~> 2.1)
mail (~> 2.2)
......@@ -374,6 +375,7 @@ GEM
systemu (~> 2.6.2)
mail (2.6.3)
mime-types (>= 1.16, < 3)
mail_room (0.4.0)
method_source (0.8.2)
mime-types (1.25.1)
mimemagic (0.3.0)
......@@ -756,7 +758,7 @@ DEPENDENCIES
binding_of_caller
bootstrap-sass (~> 3.0)
brakeman
browser (~> 0.8.0)
browser (~> 1.0.0)
byebug
cal-heatmap-rails (~> 0.0.1)
capybara (~> 2.4.0)
......@@ -776,6 +778,7 @@ DEPENDENCIES
diffy (~> 3.0.3)
doorkeeper (= 2.1.3)
dropzonejs-rails
email_reply_parser
email_spec (~> 1.6.0)
enumerize
factory_girl_rails
......@@ -810,6 +813,7 @@ DEPENDENCIES
jquery-ui-rails
kaminari (~> 0.15.1)
letter_opener
mail_room (~> 0.4.0)
minitest (~> 5.3.0)
mousetrap-rails
mysql2
......
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 @@ $ ->
$('.remove-row').bind 'ajax:success', ->
$(this).closest('li').fadeOut()
$('.js-remove-tr').bind 'ajax:before', ->
$(this).hide()
$('.js-remove-tr').bind 'ajax:success', ->
$(this).closest('tr').fadeOut()
# Initialize select2 selects
$('select.select2').select2(width: 'resolve', dropdownAutoWidth: true)
......
......@@ -119,6 +119,7 @@ class @ImageFile
requestImageInfo: (img, callback) ->
domImg = img.get(0)
if domImg
if domImg.complete
callback.call(this, domImg.naturalWidth, domImg.naturalHeight)
else
......
......@@ -51,6 +51,7 @@ class Dispatcher
MergeRequests.init()
when 'dashboard:show', 'root:show'
new Dashboard()
when 'dashboard:activity'
new Activities()
when 'dashboard:projects:starred'
new Activities()
......
......@@ -8,7 +8,7 @@ class @ProjectsList
$(".projects-list-filter").keyup ->
terms = $(this).val()
uiBox = $(this).closest('.panel')
uiBox = $(this).closest('.projects-list-holder')
if terms == "" || terms == undefined
uiBox.find(".projects-list li").show()
else
......
......@@ -7,6 +7,7 @@ class @UsersSelect
@skipLdap = $(select).hasClass('skip_ldap')
@projectId = $(select).data('project-id')
@groupId = $(select).data('group-id')
@showCurrentUser = $(select).data('current-user')
showNullUser = $(select).data('null-user')
showAnyUser = $(select).data('any-user')
showEmailUser = $(select).data('email-user')
......@@ -110,6 +111,7 @@ class @UsersSelect
project_id: @projectId
group_id: @groupId
skip_ldap: @skipLdap
current_user: @showCurrentUser
dataType: "json"
).done (users) ->
callback(users)
......
......@@ -20,3 +20,8 @@ html {
.navless-container {
margin-top: 30px;
}
.container-limited {
max-width: $fixed-layout-width;
}
......@@ -13,7 +13,7 @@ $code_line_height: 1.5;
$border-color: #E5E5E5;
$background-color: #f5f5f5;
$header-height: 50px;
$readable-width: 1100px;
$fixed-layout-width: 1200px;
/*
......
......@@ -382,3 +382,23 @@ table {
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 {
}
&.navbar-gitlab {
padding: 0 20px;
z-index: 100;
margin-bottom: 0;
min-height: $header-height;
border: none;
width: 100%;
border-bottom: 1px solid #EEE;
.container {
.container-fluid {
background: #FFF;
width: 100% !important;
padding: 0;
filter: none;
.nav > li > a {
......@@ -64,55 +64,11 @@ header {
}
}
.header-logo {
border-bottom: 1px solid transparent;
float: left;
height: $header-height;
width: $sidebar_width;
overflow: hidden;
transition-duration: .3s;
a {
float: left;
height: $header-height;
width: 100%;
padding: ($header-height - 36 ) / 2 8px;
overflow: hidden;
img {
width: 36px;
height: 36px;
float: left;
}
.gitlab-text-container {
width: 230px;
h3 {
width: 158px;
float: left;
margin: 0;
margin-left: 14px;
font-size: 18px;
line-height: $header-height - 14;
font-weight: normal;
}
}
}
&:hover {
background-color: #EEE;
}
}
.header-content {
border-bottom: 1px solid #EEE;
padding-right: 35px;
height: $header-height;
.title {
margin: 0;
padding: 0 15px 0 35px;
overflow: hidden;
font-size: 18px;
line-height: $header-height;
......@@ -168,15 +124,7 @@ header {
}
@mixin collapsed-header {
.header-logo {
width: $sidebar_collapsed_width;
}
.header-content {
.title {
margin-left: 30px;
}
}
margin-left: $sidebar_collapsed_width;
}
@media (max-width: $screen-md-max) {
......@@ -191,16 +139,14 @@ header {
}
.header-expanded {
margin-left: $sidebar_width;
}
}
@media (max-width: $screen-xs-max) {
header .container {
header .container-fluid {
font-size: 18px;
.title {
}
.navbar-nav {
margin: 0px;
float: none !important;
......
......@@ -188,3 +188,46 @@
width: $sidebar_width - 2 * 10px;
}
}
.sidebar-wrapper {
.header-logo {
border-bottom: 1px solid transparent;
float: left;
height: $header-height;
width: $sidebar_width;
overflow: hidden;
transition-duration: .3s;
a {
float: left;
height: $header-height;
width: 100%;
padding: ($header-height - 36 ) / 2 8px;
overflow: hidden;
img {
width: 36px;
height: 36px;
float: left;
}
.gitlab-text-container {
width: 230px;
h3 {
width: 158px;
float: left;
margin: 0;
margin-left: 14px;
font-size: 18px;
line-height: $header-height - 14;
font-weight: normal;
}
}
}
&:hover {
background-color: #EEE;
}
}
}
......@@ -23,41 +23,6 @@
}
}
.project-row, .group-row {
padding: 0 !important;
font-size: 14px;
line-height: 24px;
a {
display: block;
padding: 8px 15px;
}
.project-name, .group-name {
font-weight: 500;
}
.arrow {
float: right;
margin: 0;
font-size: 20px;
}
.last-activity {
float: right;
font-size: 12px;
color: #AAA;
display: block;
.date {
color: #777;
}
}
}
.project-description {
overflow: hidden;
}
.project-access-icon {
margin-left: 10px;
float: left;
......@@ -73,10 +38,9 @@
float: left;
.avatar {
margin-top: -8px;
margin-left: -15px;
@include border-radius(0px);
}
.identicon {
line-height: 40px;
}
......
......@@ -6,3 +6,11 @@
font-size: 30px;
}
}
.explore-trending-block {
.lead {
line-height: 32px;
font-size: 18px;
margin-top: 10px;
}
}
......@@ -45,9 +45,3 @@
.btn { font-size: 13px; }
}
.issuable-details {
.description {
max-width: $readable-width;
}
}
......@@ -30,14 +30,21 @@
}
}
.project-home-dropdown {
margin: 11px 3px 0;
}
.project-home-desc {
h1 {
margin: 0;
margin-bottom: 10px;
font-size: 26px;
font-weight: bold;
}
p {
font-size: 18px;
color: #666;
display: inline;
}
}
......@@ -316,3 +323,34 @@ table.table.protected-branches-list tr.no-border {
pre.light-well {
border-color: #f1f1f1;
}
.projects-search-form {
max-width: 600px;
margin: 0 auto;
margin-bottom: 20px;
input {
border-color: #BBB;
}
}
.project-row {
.project-full-name {
font-weight: bold;
font-size: 15px;
}
.project-description {
color: #888;
font-size: 13px;
p {
margin-bottom: 0;
color: #888;
}
}
}
.my-projects .project-row {
padding: 10px 0;
}
......@@ -117,7 +117,6 @@
.readme-holder {
margin: 0 auto;
max-width: $readable-width;
.readme-file-title {
font-size: 14px;
......
......@@ -7,8 +7,7 @@
* $color-dark -
*/
@mixin gitlab-theme($color-light, $color, $color-darker, $color-dark) {
header {
&.navbar-gitlab {
.page-with-sidebar {
.header-logo {
background-color: $color-darker;
border-color: $color-darker;
......@@ -24,10 +23,7 @@
}
}
}
}
}
.page-with-sidebar {
.collapse-nav a {
color: #FFF;
background: $color;
......
......@@ -4,8 +4,13 @@ class Admin::AbuseReportsController < Admin::ApplicationController
end
def destroy
AbuseReport.find(params[:id]).destroy
abuse_report = AbuseReport.find(params[:id])
redirect_to admin_abuse_reports_path, notice: 'Report was removed'
if params[:remove_user]
abuse_report.user.destroy
end
abuse_report.destroy
render nothing: true
end
end
......@@ -29,6 +29,15 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
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(
:default_projects_limit,
:default_branch_protection,
......@@ -48,6 +57,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:version_check_enabled,
:user_oauth_applications,
restricted_visibility_levels: [],
import_sources: []
)
end
end
......@@ -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.with_push if params[:with_push].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.sort(@sort = params[:sort])
@projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]).per(PER_PAGE)
......
......@@ -20,7 +20,7 @@ class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
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|
log_exception(exception)
......@@ -304,15 +304,43 @@ class ApplicationController < ActionController::Base
@issuable_finder.execute
end
def import_sources_enabled?
!current_application_settings.import_sources.empty?
end
def github_import_enabled?
current_application_settings.import_sources.include?('github')
end
def github_import_configured?
Gitlab::OAuth::Provider.enabled?(:github)
end
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)
end
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?
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
......@@ -34,8 +34,14 @@ class AutocompleteController < ApplicationController
@users = @users.search(params[:search]) if params[:search].present?
@users = @users.active
@users = @users.page(params[:page]).per(PER_PAGE)
# Always include current user if available to filter by "Me"
@users = User.find(@users.pluck(:id) + [current_user.id]).uniq if current_user
unless params[:search].present?
# Include current user if available to filter by "Me"
if params[:current_user] && current_user
@users = [*@users, current_user].uniq
end
end
render json: @users, only: [:name, :username, :id], methods: [:avatar_url]
end
......
class DashboardController < Dashboard::ApplicationController
before_action :load_projects
before_action :event_filter, only: :show
before_action :event_filter, only: :activity
respond_to :html
......@@ -10,13 +10,8 @@ class DashboardController < Dashboard::ApplicationController
respond_to do |format|
format.html
format.json do
load_events
pager_json("events/_events", @events.count)
end
format.atom do
event_filter
load_events
render layout: false
end
......@@ -40,6 +35,19 @@ class DashboardController < Dashboard::ApplicationController
end
end
def activity
@last_push = current_user.recent_push
respond_to do |format|
format.html
format.json do
load_events
pager_json("events/_events", @events.count)
end
end
end
protected
def load_projects
......
......@@ -7,6 +7,7 @@ class Explore::ProjectsController < Explore::ApplicationController
@tags = @projects.tags_on(:tags)
@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.non_archived
@projects = @projects.search(params[:search]) if params[:search].present?
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.includes(:namespace).page(params[:page]).per(PER_PAGE)
......@@ -14,6 +15,7 @@ class Explore::ProjectsController < Explore::ApplicationController
def trending
@trending_projects = TrendingProjectsFinder.new.execute(current_user)
@trending_projects = @trending_projects.non_archived
@trending_projects = @trending_projects.page(params[:page]).per(PER_PAGE)
end
......
......@@ -13,10 +13,9 @@ class Import::BitbucketController < Import::BaseController
access_token = client.get_token(request_token, params[:oauth_verifier], callback_import_bitbucket_url)
current_user.bitbucket_access_token = access_token.token
current_user.bitbucket_access_token_secret = access_token.secret
session[:bitbucket_access_token] = access_token.token
session[:bitbucket_access_token_secret] = access_token.secret
current_user.save
redirect_to status_import_bitbucket_url
end
......@@ -46,19 +45,20 @@ class Import::BitbucketController < Import::BaseController
namespace = get_or_create_namespace || (render and return)
unless Gitlab::BitbucketImport::KeyAdder.new(repo, current_user).execute
unless Gitlab::BitbucketImport::KeyAdder.new(repo, current_user, access_params).execute
@access_denied = true
render
return
end
@project = Gitlab::BitbucketImport::ProjectCreator.new(repo, namespace, current_user).execute
@project = Gitlab::BitbucketImport::ProjectCreator.new(repo, namespace, current_user, access_params).execute
end
private
def client
@client ||= Gitlab::BitbucketImport::Client.new(current_user.bitbucket_access_token, current_user.bitbucket_access_token_secret)
@client ||= Gitlab::BitbucketImport::Client.new(session[:bitbucket_access_token],
session[:bitbucket_access_token_secret])
end
def verify_bitbucket_import_enabled
......@@ -66,7 +66,7 @@ class Import::BitbucketController < Import::BaseController
end
def bitbucket_auth
if current_user.bitbucket_access_token.blank?
if session[:bitbucket_access_token].blank?
go_to_bitbucket_for_permissions
end
end
......@@ -81,4 +81,13 @@ class Import::BitbucketController < Import::BaseController
def bitbucket_unauthorized
go_to_bitbucket_for_permissions
end
private
def access_params
{
bitbucket_access_token: session[:bitbucket_access_token],
bitbucket_access_token_secret: session[:bitbucket_access_token_secret]
}
end
end
......@@ -5,9 +5,7 @@ class Import::GithubController < Import::BaseController
rescue_from Octokit::Unauthorized, with: :github_unauthorized
def callback
token = client.get_token(params[:code])
current_user.github_access_token = token
current_user.save
session[:github_access_token] = client.get_token(params[:code])
redirect_to status_import_github_url
end
......@@ -39,13 +37,13 @@ class Import::GithubController < Import::BaseController
namespace = get_or_create_namespace || (render and return)
@project = Gitlab::GithubImport::ProjectCreator.new(repo, namespace, current_user).execute
@project = Gitlab::GithubImport::ProjectCreator.new(repo, namespace, current_user, access_params).execute
end
private
def client
@client ||= Gitlab::GithubImport::Client.new(current_user.github_access_token)
@client ||= Gitlab::GithubImport::Client.new(session[:github_access_token])
end
def verify_github_import_enabled
......@@ -53,7 +51,7 @@ class Import::GithubController < Import::BaseController
end
def github_auth
if current_user.github_access_token.blank?
if session[:github_access_token].blank?
go_to_github_for_permissions
end
end
......@@ -65,4 +63,10 @@ class Import::GithubController < Import::BaseController
def github_unauthorized
go_to_github_for_permissions
end
private
def access_params
{ github_access_token: session[:github_access_token] }
end
end
......@@ -5,9 +5,7 @@ class Import::GitlabController < Import::BaseController
rescue_from OAuth2::Error, with: :gitlab_unauthorized
def callback
token = client.get_token(params[:code], callback_import_gitlab_url)
current_user.gitlab_access_token = token
current_user.save
session[:gitlab_access_token] = client.get_token(params[:code], callback_import_gitlab_url)
redirect_to status_import_gitlab_url
end
......@@ -36,13 +34,13 @@ class Import::GitlabController < Import::BaseController
namespace = get_or_create_namespace || (render and return)
@project = Gitlab::GitlabImport::ProjectCreator.new(repo, namespace, current_user).execute
@project = Gitlab::GitlabImport::ProjectCreator.new(repo, namespace, current_user, access_params).execute
end
private
def client
@client ||= Gitlab::GitlabImport::Client.new(current_user.gitlab_access_token)
@client ||= Gitlab::GitlabImport::Client.new(session[:gitlab_access_token])
end
def verify_gitlab_import_enabled
......@@ -50,7 +48,7 @@ class Import::GitlabController < Import::BaseController
end
def gitlab_auth
if current_user.gitlab_access_token.blank?
if session[:gitlab_access_token].blank?
go_to_gitlab_for_permissions
end
end
......@@ -62,4 +60,10 @@ class Import::GitlabController < Import::BaseController
def gitlab_unauthorized
go_to_gitlab_for_permissions
end
private
def access_params
{ gitlab_access_token: session[:gitlab_access_token] }
end
end
class Import::GitoriousController < Import::BaseController
before_action :verify_gitorious_import_enabled
def new
redirect_to client.authorize_url(callback_import_gitorious_url)
......@@ -40,4 +41,8 @@ class Import::GitoriousController < Import::BaseController
@client ||= Gitlab::GitoriousImport::Client.new(session[:gitorious_repos])
end
def verify_gitorious_import_enabled
not_found! unless gitorious_import_enabled?
end
end
class Import::GoogleCodeController < Import::BaseController
before_action :verify_google_code_import_enabled
before_action :user_map, only: [:new_user_map, :create_user_map]
def new
......@@ -104,6 +105,10 @@ class Import::GoogleCodeController < Import::BaseController
@client ||= Gitlab::GoogleCodeImport::Client.new(session[:google_code_dump])
end
def verify_google_code_import_enabled
not_found! unless google_code_import_enabled?
end
def user_map
@user_map ||= begin
user_map = client.user_map
......
......@@ -8,6 +8,28 @@ class Projects::BlameController < Projects::ApplicationController
def show
@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
......@@ -23,7 +23,7 @@ class SearchController < ApplicationController
@search_results =
if @project
unless %w(blobs notes issues merge_requests wiki_blobs).
unless %w(blobs notes issues merge_requests milestones wiki_blobs).
include?(@scope)
@scope = 'blobs'
end
......@@ -36,7 +36,7 @@ class SearchController < ApplicationController
Search::SnippetService.new(current_user, params).execute
else
unless %w(projects issues merge_requests).include?(@scope)
unless %w(projects issues merge_requests milestones).include?(@scope)
@scope = 'projects'
end
Search::GlobalService.new(current_user, params).execute
......
......@@ -43,4 +43,21 @@ module ApplicationSettingsHelper
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
......@@ -10,7 +10,7 @@ module ExploreHelper
options = exist_opts.merge(options)
path = request.path
path = explore_projects_path
path << "?#{options.to_param}"
path
end
......
......@@ -20,7 +20,7 @@ module IconsHelper
end
def boolean_to_icon(value)
if value.to_s == "true"
if value
icon('circle', class: 'cgreen')
else
icon('power-off', class: 'clgray')
......
......@@ -23,4 +23,12 @@ module PageLayoutHelper
@sidebar
end
end
def fluid_layout(enabled = false)
if @fluid_layout.nil?
@fluid_layout = enabled
else
@fluid_layout
end
end
end
......@@ -11,6 +11,7 @@ module SelectsHelper
any_user = opts[:any_user] || false
email_user = opts[:email_user] || false
first_user = opts[:first_user] && current_user ? current_user.username : false
current_user = opts[:current_user] || false
project = opts[:project] || @project
html = {
......@@ -19,7 +20,8 @@ module SelectsHelper
'data-null-user' => null_user,
'data-any-user' => any_user,
'data-email-user' => email_user,
'data-first-user' => first_user
'data-first-user' => first_user,
'data-current-user' => current_user
}
unless opts[:scope] == :all
......
......@@ -67,6 +67,14 @@ module TabHelper
path.any? do |single_path|
current_path?(single_path)
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
c = options.delete(:controller)
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
from: sender(@issue.author_id),
to: recipient(recipient_id),
subject: subject("#{@issue.title} (##{@issue.iid})"))
SentNotification.record(@issue, recipient_id, reply_key)
end
def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id, updated_by_user_id)
......@@ -19,6 +21,8 @@ module Emails
from: sender(updated_by_user_id),
to: recipient(recipient_id),
subject: subject("#{@issue.title} (##{@issue.iid})"))
SentNotification.record(@issue, recipient_id, reply_key)
end
def closed_issue_email(recipient_id, issue_id, updated_by_user_id)
......@@ -30,6 +34,8 @@ module Emails
from: sender(updated_by_user_id),
to: recipient(recipient_id),
subject: subject("#{@issue.title} (##{@issue.iid})"))
SentNotification.record(@issue, recipient_id, reply_key)
end
def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id)
......@@ -42,6 +48,8 @@ module Emails
from: sender(updated_by_user_id),
to: recipient(recipient_id),
subject: subject("#{@issue.title} (##{@issue.iid})"))
SentNotification.record(@issue, recipient_id, reply_key)
end
end
end
......@@ -10,6 +10,8 @@ module Emails
from: sender(@merge_request.author_id),
to: recipient(recipient_id),
subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
SentNotification.record(@merge_request, recipient_id, reply_key)
end
def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id, updated_by_user_id)
......@@ -23,6 +25,8 @@ module Emails
from: sender(updated_by_user_id),
to: recipient(recipient_id),
subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
SentNotification.record(@merge_request, recipient_id, reply_key)
end
def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id)
......@@ -36,6 +40,8 @@ module Emails
from: sender(updated_by_user_id),
to: recipient(recipient_id),
subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
SentNotification.record(@merge_request, recipient_id, reply_key)
end
def merged_merge_request_email(recipient_id, merge_request_id, updated_by_user_id)
......@@ -48,6 +54,8 @@ module Emails
from: sender(updated_by_user_id),
to: recipient(recipient_id),
subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
SentNotification.record(@merge_request, recipient_id, reply_key)
end
def merge_request_status_email(recipient_id, merge_request_id, status, updated_by_user_id)
......@@ -58,52 +66,12 @@ module Emails
@target_url = namespace_project_merge_request_url(@project.namespace,
@project,
@merge_request)
set_reference("merge_request_#{merge_request_id}")
mail_answer_thread(@merge_request,
from: sender(updated_by_user_id),
to: recipient(recipient_id),
subject: subject("#{@merge_request.title} (##{@merge_request.iid}) #{@mr_status}"))
end
end
subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
# Over rides default behaviour to show source/target
# Formats arguments into a String suitable for use as an email subject
#
# extra - Extra Strings to be inserted into the subject
#
# Examples
#
# >> subject('Lorem ipsum')
# => "GitLab Merge Request | Lorem ipsum"
#
# # Automatically inserts Project name:
# Forked MR
# => source project => <Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
# => target project => <Project id: 2, name: "My Ror", path: "ruby_on_rails", ...>
# => source branch => source
# => target branch => target
# >> subject('Lorem ipsum')
# => "GitLab Merge Request | Ruby on Rails:source >> My Ror:target | Lorem ipsum "
#
# Non Forked MR
# => source project => <Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
# => target project => <Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
# => source branch => source
# => target branch => target
# >> subject('Lorem ipsum')
# => "GitLab Merge Request | Ruby on Rails | source >> target | Lorem ipsum "
# # Accepts multiple arguments
# >> subject('Lorem ipsum', 'Dolor sit amet')
# => "GitLab Merge Request | Lorem ipsum | Dolor sit amet"
def subject(*extra)
subject = "Merge Request | "
if @merge_request.for_fork?
subject << "#{@merge_request.source_project.name_with_namespace}:#{merge_request.source_branch} >> #{@merge_request.target_project.name_with_namespace}:#{merge_request.target_branch}"
else
subject << "#{@merge_request.source_project.name_with_namespace} | #{merge_request.source_branch} >> #{merge_request.target_branch}"
SentNotification.record(@merge_request, recipient_id, reply_key)
end
subject << " | " + extra.join(' | ') if extra.present?
subject
end
end
......@@ -11,6 +11,8 @@ module Emails
from: sender(@note.author_id),
to: recipient(recipient_id),
subject: subject("#{@commit.title} (#{@commit.short_id})"))
SentNotification.record(@commit, recipient_id, reply_key)
end
def note_issue_email(recipient_id, note_id)
......@@ -24,6 +26,8 @@ module Emails
from: sender(@note.author_id),
to: recipient(recipient_id),
subject: subject("#{@issue.title} (##{@issue.iid})"))
SentNotification.record(@issue, recipient_id, reply_key)
end
def note_merge_request_email(recipient_id, note_id)
......@@ -38,6 +42,8 @@ module Emails
from: sender(@note.author_id),
to: recipient(recipient_id),
subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
SentNotification.record(@merge_request, recipient_id, reply_key)
end
end
end
class Notify < ActionMailer::Base
class Notify < BaseMailer
include ActionDispatch::Routing::PolymorphicRoutes
include Emails::AdminNotification
......@@ -9,22 +9,9 @@ class Notify < ActionMailer::Base
include Emails::Profile
include Emails::Groups
add_template_helper ApplicationHelper
add_template_helper GitlabMarkdownHelper
add_template_helper MergeRequestsHelper
add_template_helper EmailsHelper
attr_accessor :current_user
helper_method :current_user, :can?
default from: Proc.new { default_sender_address.format }
default reply_to: Gitlab.config.gitlab.email_reply_to
# Just send email with 2 seconds delay
def self.delay
delay_for(2.seconds)
end
def test_email(recipient_email, subject, body)
mail(to: recipient_email,
subject: subject,
......@@ -49,13 +36,6 @@ class Notify < ActionMailer::Base
private
# The default email address to send emails from
def default_sender_address
address = Mail::Address.new(Gitlab.config.gitlab.email_from)
address.display_name = Gitlab.config.gitlab.email_display_name
address
end
def can_send_from_user_email?(sender)
sender_domain = sender.email.split("@").last
self.class.allowed_email_domains.include?(sender_domain)
......@@ -86,14 +66,6 @@ class Notify < ActionMailer::Base
@current_user.notification_email
end
# Set the References header field
#
# local_part - The local part of the referenced message ID
#
def set_reference(local_part)
headers["References"] = "<#{local_part}@#{Gitlab.config.gitlab.host}>"
end
# Formats arguments into a String suitable for use as an email subject
#
# extra - Extra Strings to be inserted into the subject
......@@ -127,14 +99,37 @@ class Notify < ActionMailer::Base
"<#{model_name}_#{model.id}@#{Gitlab.config.gitlab.host}>"
end
def mail_thread(model, headers = {})
if @project
headers['X-GitLab-Project'] = @project.name
headers['X-GitLab-Project-Id'] = @project.id
headers['X-GitLab-Project-Path'] = @project.path_with_namespace
end
headers["X-GitLab-#{model.class.name}-ID"] = model.id
if reply_key
headers['X-GitLab-Reply-Key'] = reply_key
address = Mail::Address.new(Gitlab::ReplyByEmail.reply_address(reply_key))
address.display_name = @project.name_with_namespace
headers['Reply-To'] = address
@reply_by_email = true
end
mail(headers)
end
# Send an email that starts a new conversation thread,
# with headers suitable for grouping by thread in email clients.
#
# See: mail_answer_thread
def mail_new_thread(model, headers = {}, &block)
def mail_new_thread(model, headers = {})
headers['Message-ID'] = message_id(model)
headers['X-GitLab-Project'] = "#{@project.name} | " if @project
mail(headers, &block)
mail_thread(model, headers)
end
# Send an email that responds to an existing conversation thread,
......@@ -145,19 +140,17 @@ class Notify < ActionMailer::Base
# * have a subject that begin by 'Re: '
# * have a 'In-Reply-To' or 'References' header that references the original 'Message-ID'
#
def mail_answer_thread(model, headers = {}, &block)
def mail_answer_thread(model, headers = {})
headers['Message-ID'] = SecureRandom.hex
headers['In-Reply-To'] = message_id(model)
headers['References'] = message_id(model)
headers['X-GitLab-Project'] = "#{@project.name} | " if @project
if headers[:subject]
headers[:subject].prepend('Re: ')
end
headers[:subject].prepend('Re: ') if headers[:subject]
mail(headers, &block)
mail_thread(model, headers)
end
def can?
Ability.abilities.allowed?(user, action, subject)
def reply_key
@reply_key ||= Gitlab::ReplyByEmail.reply_key
end
end
......@@ -23,10 +23,12 @@
# user_oauth_applications :boolean default(TRUE)
# after_sign_out_path :string(255)
# session_expire_delay :integer default(10080), not null
# import_sources :text
#
class ApplicationSetting < ActiveRecord::Base
serialize :restricted_visibility_levels
serialize :import_sources
serialize :restricted_signup_domains, Array
attr_accessor :restricted_signup_domains_raw
......@@ -53,6 +55,16 @@ class ApplicationSetting < ActiveRecord::Base
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
ApplicationSetting.last
end
......@@ -71,7 +83,8 @@ class ApplicationSetting < ActiveRecord::Base
session_expire_delay: Settings.gitlab['session_expire_delay'],
default_project_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
......
......@@ -17,6 +17,7 @@ require 'carrierwave/orm/activerecord'
require 'file_size_validator'
class Group < Namespace
include Gitlab::ConfigHelper
include Referable
has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember'
......
......@@ -47,6 +47,13 @@ class Milestone < ActiveRecord::Base
state :active
end
class << self
def search(query)
query = "%#{query}%"
where("title like ? or description like ?", query, query)
end
end
def expired?
if due_date
due_date.past?
......
......@@ -90,7 +90,7 @@ class Note < ActiveRecord::Base
end
def search(query)
where("note like :query", query: "%#{query}%")
where("LOWER(note) like :query", query: "%#{query.downcase}%")
end
end
......
......@@ -222,7 +222,7 @@ class Project < ActiveRecord::Base
end
def search(query)
joins(:namespace).where('projects.archived = ?', false).
joins(:namespace).
where('LOWER(projects.name) LIKE :query OR
LOWER(projects.path) 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
events = recent_events.code_push.where("created_at > ?", Time.now - 2.hours)
events = events.where(project_id: project_id) if project_id
# Take only latest one
events = events.recent.limit(1).first
# Use the latest event that has not been pushed or merged recently
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
def projects_sorted_by_activity
......
......@@ -78,24 +78,29 @@ class GitPushService
# For push with 1k commits it prevents 900+ requests in database
author = nil
# Keep track of the issues that will be actually closed because they are on a default branch.
# Hence, when creating cross-reference notes, the not-closed issues (on non-default branches)
# will also have cross-reference.
actually_closed_issues = []
if issues_to_close.present? && is_default_branch
author ||= commit_user(commit)
actually_closed_issues = issues_to_close
issues_to_close.each do |issue|
Issues::CloseService.new(project, author, {}).execute(issue, commit)
end
end
if project.reference_issue_tracker?
create_cross_reference_notes(commit, issues_to_close)
create_cross_reference_notes(commit, actually_closed_issues)
end
end
end
def create_cross_reference_notes(commit, issues_to_close)
# Create cross-reference notes for any other references. Omit any issues that were referenced in an
# issue-closing phrase, or have already been mentioned from this commit (probably from this commit
# being pushed to a different branch).
# Create cross-reference notes for any other references than those given in issues_to_close.
# Omit any issues that were referenced in an issue-closing phrase, or have already been
# mentioned from this commit (probably from this commit being pushed to a different branch).
refs = commit.references(project, user) - issues_to_close
refs.reject! { |r| commit.has_mentioned?(r) }
......
module MergeRequests
class CreateService < MergeRequests::BaseService
def execute
# @project is used to determine whether the user can set the merge request's
# assignee, milestone and labels. Whether they can depends on their
# permissions on the target project.
source_project = @project
@project = Project.find(params[:target_project_id]) if params[:target_project_id]
filter_params
label_params = params[:label_ids]
merge_request = MergeRequest.new(params.except(:label_ids))
merge_request.source_project = project
merge_request.target_project ||= project
merge_request.source_project = source_project
merge_request.target_project ||= source_project
merge_request.author = current_user
if merge_request.save
......
......@@ -13,9 +13,9 @@ module Projects
filename = uploader.image? ? uploader.file.basename : uploader.file.filename
{
'alt' => filename,
'url' => uploader.secure_url,
'is_image' => uploader.image?
alt: filename,
url: uploader.secure_url,
is_image: uploader.image?
}
end
......
......@@ -3,7 +3,7 @@
%tr
%td
- if reporter
= link_to reporter.name, [:admin, reporter]
= link_to reporter.name, reporter
- else
(removed)
%td
......@@ -12,12 +12,15 @@
= abuse_report.message
%td
- if user
= link_to user.name, [:admin, user]
= link_to user.name, user
- else
(removed)
%td
- if user
= link_to 'Block', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-xs btn-warning"
= link_to 'Remove user', [:admin, user], data: { confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-xs btn-remove"
= link_to 'Remove user & report', admin_abuse_report_path(abuse_report, remove_user: true),
data: { confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?" }, remote: true, method: :delete, class: "btn btn-xs btn-remove js-remove-tr"
%td
= link_to 'Remove report', [:admin, abuse_report], method: :delete, class: "btn btn-xs btn-close"
- if user
= link_to 'Block user', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-xs"
= link_to 'Remove report', [:admin, abuse_report], remote: true, method: :delete, class: "btn btn-xs btn-close js-remove-tr"
......@@ -9,7 +9,7 @@
%th Reported at
%th Message
%th User
%th
%th Primary action
%th
= render @abuse_reports
= paginate @abuse_reports
......
......@@ -27,6 +27,20 @@
- restricted_level_checkboxes('restricted-visibility-help').each do |level|
= level
%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
.col-sm-offset-2.col-sm-10
.checkbox
......
......@@ -55,6 +55,10 @@
OmniAuth
%span.light.pull-right
= boolean_to_icon Gitlab.config.omniauth.enabled
%p
Reply by email
%span.light.pull-right
= boolean_to_icon Gitlab::ReplyByEmail.enabled?
.col-md-4
%h4
Components
......
......@@ -23,6 +23,10 @@
= label_tag :abandoned do
= check_box_tag :abandoned, 1, params[:abandoned]
%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
%strong Visibility level:
......@@ -73,6 +77,8 @@
= visibility_level_icon(project.visibility_level)
= link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project]
.pull-right
- if project.archived
%span.label.label-warning archived
%span.label.label-gray
= repository_size(project)
= 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
.panel-heading.clearfix
.projects-list-holder
.projects-search-form
.input-group
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control'
- if current_user.can_create_project?
......@@ -7,4 +7,7 @@
= link_to new_project_path, class: 'btn btn-success' do
New project
= render 'shared/projects_list', projects: @projects, projects_limit: 20
%ul.projects-list.bordered-list.my-projects
- @projects.each do |project|
%li.project-row
= render partial: 'shared/project', locals: { project: project, avatar: true, stars: true }
%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"
%h3.page-title
Group Membership
= render 'dashboard/groups_head'
.slead
Group members have access to all group projects.
- if current_user.can_create_group?
%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
New Group
%p.light
Group members have access to all group projects.
%hr
.panel.panel-default
.panel-heading
%strong Groups
......
- page_title "Starred Projects"
= render 'dashboard/projects_head'
- if @projects.any?
= render 'shared/show_aside'
.dashboard.row
%section.activities.col-md-8
%section.activities.col-md-7
= render 'dashboard/activities'
%aside.col-md-4
.panel.panel-default
%aside.col-md-5
.panel.panel-default.projects-list-holder
.panel-heading.clearfix
.input-group
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control'
......
......@@ -2,14 +2,12 @@
- if current_user
= auto_discovery_link_tag(:atom, dashboard_url(format: :atom, private_token: current_user.private_token), title: "All activity")
- if @projects.any?
= render 'shared/show_aside'
= render 'dashboard/projects_head'
.dashboard.row
%section.activities.col-md-8
= render 'activities'
%aside.col-md-4
= render 'sidebar'
- if @last_push
= render "events/event_last_push", event: @last_push
- if @projects.any?
= render 'projects'
- else
= render "zero_authorized_projects"
%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 @@
#{time_ago_with_tooltip(event.created_at)}
.pull-right
= link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-create btn-sm" do
= link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-info btn-sm" do
Create Merge Request
%hr
- page_title "Groups"
.clearfix
- if current_user
= render 'dashboard/groups_head'
.clearfix.append-bottom-10
.pull-left
= form_tag explore_groups_path, method: :get, class: 'form-inline form-tiny' do |f|
= hidden_field_tag :sort, @sort
......@@ -28,15 +30,12 @@
= link_to explore_groups_path(sort: sort_value_oldest_updated) do
= sort_title_oldest_updated
%hr
%ul.bordered-list
- @groups.each do |group|
%li
.clearfix
%h4
= link_to group_path(id: group.path) do
%i.fa.fa-users
= group.name
.clearfix
%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 @@
= link_to explore_projects_filter_path(tag: tag.name) do
%i.fa.fa-tag
= tag.name
.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
= render 'explore/projects/dropdown'
%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"
- if current_user
= render 'dashboard/projects_head'
.clearfix
= render 'filter'
%hr
.public-projects
%ul.bordered-list.top-list
= render @projects
- unless @projects.present?
.nothing-here-block No public projects
= paginate @projects, theme: "gitlab"
%br
= render 'projects', projects: @projects
= paginate @projects, theme: "gitlab"
- page_title "Starred Projects"
- if current_user
= render 'dashboard/projects_head'
.explore-trending-block
%p.lead
.lead
%i.fa.fa-star
See most starred projects
%hr
.public-projects
%ul.bordered-list
= render @starred_projects
.pull-right
= render 'explore/projects/dropdown'
= render 'projects', projects: @starred_projects
= paginate @starred_projects, theme: 'gitlab'
- page_title "Trending Projects"
- if current_user
= render 'dashboard/projects_head'
.explore-title
%h3
Explore GitLab
......@@ -6,10 +8,9 @@
Discover projects and groups. Share your projects with others
%hr
.explore-trending-block
%p.lead
.lead
%i.fa.fa-comments-o
See most discussed projects for last month
%hr
.public-projects
%ul.bordered-list
= render @trending_projects
.pull-right
= render 'explore/projects/dropdown'
= render 'projects', projects: @trending_projects
.panel.panel-default
.panel.panel-default.projects-list-holder
.panel-heading.clearfix
.input-group
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control'
......
......@@ -12,11 +12,14 @@
- @projects.each do |project|
%li
.list-item-name
%span{ class: visibility_level_color(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
= 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 '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"
......
......@@ -17,7 +17,7 @@
= render 'shared/show_aside'
.row
%section.activities.col-md-8
%section.activities.col-md-7
.hidden-xs
- if current_user
= render "events/event_last_push", event: @last_push
......@@ -33,7 +33,7 @@
.content_list
= spinner
%aside.side.col-md-4
%aside.side.col-md-5
= render "projects", projects: @projects
%br
= render "shared_projects", projects: @shared_projects
.page-with-sidebar{ class: nav_sidebar_class }
= render "layouts/broadcast"
.sidebar-wrapper.nicescroll
.header-logo
= link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home', data: {toggle: 'tooltip', placement: 'bottom'} do
= brand_header_logo
.gitlab-text-container
%h3 GitLab
- if defined?(sidebar) && sidebar
= render "layouts/nav/#{sidebar}"
- elsif current_user
......@@ -13,7 +18,7 @@
.username
= current_user.username
.content-wrapper
.container-fluid
%div{ class: fluid_layout ? "container-fluid" : "container-fluid container-limited" }
.content
= render "layouts/flash"
.clearfix
......
- page_title "Explore"
- header_title "Explore GitLab", explore_root_path
- sidebar "explore"
- if current_user
- header_title "Dashboard", root_path
- else
- header_title "Explore GitLab", explore_root_path
- sidebar "dashboard"
= render template: "layouts/application"
%header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class }
.container
.header-logo
= link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home', data: {toggle: 'tooltip', placement: 'bottom'} do
= brand_header_logo
.gitlab-text-container
%h3 GitLab
%div{ class: fluid_layout ? "container-fluid" : "container-fluid container-limited" }
.header-content
%button.navbar-toggle{type: 'button'}
%span.sr-only Toggle navigation
......@@ -17,15 +12,6 @@
%li.visible-sm.visible-xs
= link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom'} do
= icon('search')
%li.hidden-xs
= link_to help_path, title: 'Help', data: {toggle: 'tooltip', placement: 'bottom'} do
= icon('question-circle fw')
%li
= link_to explore_root_path, title: 'Explore', data: {toggle: 'tooltip', placement: 'bottom'} do
= icon('globe fw')
%li
= link_to user_snippets_path(current_user), title: 'Your snippets', data: {toggle: 'tooltip', placement: 'bottom'} do
= icon('clipboard fw')
- if current_user.is_admin?
%li
= link_to admin_root_path, title: 'Admin area', data: {toggle: 'tooltip', placement: 'bottom'} do
......@@ -34,9 +20,6 @@
%li.hidden-xs
= link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom'} do
= icon('plus fw')
%li
= link_to profile_path, title: 'Profile settings', data: {toggle: 'tooltip', placement: 'bottom'} do
= icon('cog fw')
%li
= link_to destroy_user_session_path, class: 'logout', method: :delete, title: 'Sign out', data: {toggle: 'tooltip', placement: 'bottom'} do
= icon('sign-out')
......
%header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class }
.container
.header-logo
= link_to explore_root_path, class: "home" do
= brand_header_logo
.gitlab-text-container
%h3 GitLab
%div{ class: fluid_layout ? "container-fluid" : "container-fluid container-limited" }
.header-content
- unless current_controller?('sessions')
.pull-right
......
%ul.nav.nav-sidebar
= nav_link(path: ['dashboard#show', 'root#show'], html_options: {class: 'home'}) do
= link_to dashboard_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do
= icon('dashboard fw')
= nav_link(path: ['dashboard#show', 'root#show', 'projects#trending', 'projects#starred', 'projects#index'], html_options: {class: 'home'}) do
= link_to (current_user ? root_path : explore_root_path), title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do
= icon('home fw')
%span
Your Projects
= nav_link(path: 'projects#starred') do
= link_to starred_dashboard_projects_path, title: 'Starred Projects', data: {placement: 'right'} do
= icon('star fw')
Projects
= nav_link(path: 'dashboard#activity') do
= link_to activity_dashboard_path, title: 'Activity', data: {placement: 'right'} do
= icon('dashboard fw')
%span
Starred Projects
Activity
= 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')
%span
Groups
- if current_user
= nav_link(controller: :milestones) do
= link_to dashboard_milestones_path, title: 'Milestones', data: {placement: 'right'} do
= icon('clock-o fw')
......@@ -31,6 +32,17 @@
%span
Merge Requests
%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
= link_to help_path, title: 'Help', data: {placement: 'right'} do
= 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 @@
&mdash;
%br
- 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}
= email_action @target_url
- if @project && !@disable_footer
......
- page_title 'Snippets'
- header_title 'Snippets', snippets_path
- sidebar "snippets"
- if current_user
- header_title "Dashboard", root_path
- else
- header_title 'Snippets', snippets_path
- sidebar "dashboard"
= render template: "layouts/application"
%p.slead
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
%ul
......
......@@ -7,7 +7,7 @@
.modal-body
To enable importing projects from Bitbucket,
- if current_user.admin?
you need to
as administrator you need to configure
- else
your GitLab administrator needs to
== #{link_to 'setup OAuth integration', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/bitbucket.md'}.
ask your GitLab administrator to configure
== #{link_to 'OAuth integration', help_page_path("integration", "bitbucket")}.
......@@ -7,7 +7,7 @@
.modal-body
To enable importing projects from GitHub,
- if current_user.admin?
you need to
as administrator you need to configure
- else
your GitLab administrator needs to
== #{link_to 'setup OAuth integration', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/github.md'}.
\ No newline at end of file
ask your Gitlab administrator to configure
== #{link_to 'OAuth integration', help_page_path("integration", "github")}.
......@@ -7,7 +7,7 @@
.modal-body
To enable importing projects from GitLab.com,
- if current_user.admin?
you need to
as administrator you need to configure
- else
your GitLab administrator needs to
== #{link_to 'setup OAuth integration', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/gitlab.md'}.
\ No newline at end of file
ask your GitLab administrator to configure
== #{link_to 'OAuth integration', help_page_path("integration", "gitlab")}.
......@@ -2,7 +2,7 @@
.project-home-panel.clearfix{:class => ("empty-project" if empty_repo)}
.project-identicon-holder
= project_icon(@project, alt: '', class: 'project-avatar avatar s90')
.project-home-desc.lead
.project-home-desc
%h1= @project.name
- if @project.description.present?
= markdown(@project.description, pipeline: :description)
......
......@@ -13,12 +13,11 @@
.file-content.blame.highlight
%table
- current_line = 1
- @blame.each do |raw_commit, line|
- @blame.each do |blame_group|
%tr
%td.blame-commit
.commit
- unless @prev_commit && @prev_commit.sha == raw_commit.sha
- commit = Commit.new(raw_commit, @project)
- commit = Commit.new(blame_group[:commit], @project)
.commit-row-title
%strong
= link_to_gfm truncate(commit.title, length: 35), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "cdark"
......@@ -28,15 +27,18 @@
.light
= commit_author_link(commit, avatar: false)
authored
#{time_ago_with_tooltip(commit.committed_date)}
- @prev_commit = raw_commit
#{time_ago_with_tooltip(commit.committed_date, skip_js: true)}
%td.lines.blame-numbers
%pre
= current_line
- current_line += 1
- line_count = blame_group[:lines].count
- (current_line...(current_line + line_count)).each do |i|
= i
\
- current_line += line_count
%td.lines
%pre{class: 'code highlight white'}
%code
- blame_group[:lines].each do |line|
:erb
<%= highlight(@blob.name, line, nowrap: true, continue: true).html_safe %>
......@@ -2,7 +2,7 @@
%span.dropdown
%a.dropdown-toggle.btn.btn-new{href: '#', "data-toggle" => "dropdown"}
= icon('plus')
%ul.dropdown-menu
%ul.dropdown-menu.dropdown-menu-right.project-home-dropdown
- if can?(current_user, :create_issue, @project)
%li
= link_to url_for_new_issue do
......
- page_title "Deploy Keys"
%h3.page-title
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