Commit c3ed268a authored by James Lopez's avatar James Lopez

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into fix/cross-reference-notes-forks

parents 9b925d79 9afcacb3
...@@ -13,9 +13,12 @@ v 8.5.0 (unreleased) ...@@ -13,9 +13,12 @@ v 8.5.0 (unreleased)
set it up set it up
- Fix diff comments loaded by AJAX to load comment with diff in discussion tab - Fix diff comments loaded by AJAX to load comment with diff in discussion tab
- Whitelist raw "abbr" elements when parsing Markdown (Benedict Etzel) - Whitelist raw "abbr" elements when parsing Markdown (Benedict Etzel)
- Fix label links for a merge request pointing to issues list
- Don't vendor minified JS - Don't vendor minified JS
- Display 404 error on group not found - Display 404 error on group not found
- Track project import failure - Track project import failure
- Support Two-factor Authentication for LDAP users
- Display database type and version in Administration dashboard
- Fix visibility level text in admin area (Zeger-Jan van de Weg) - Fix visibility level text in admin area (Zeger-Jan van de Weg)
- Warn admin during OAuth of granting admin rights (Zeger-Jan van de Weg) - Warn admin during OAuth of granting admin rights (Zeger-Jan van de Weg)
- Update the ExternalIssue regex pattern (Blake Hitchcock) - Update the ExternalIssue regex pattern (Blake Hitchcock)
...@@ -24,6 +27,7 @@ v 8.5.0 (unreleased) ...@@ -24,6 +27,7 @@ v 8.5.0 (unreleased)
- Fix API to keep request parameters in Link header (Michael Potthoff) - Fix API to keep request parameters in Link header (Michael Potthoff)
- Deprecate API "merge_request/:merge_request_id/comments". Use "merge_requests/:merge_request_id/notes" instead - Deprecate API "merge_request/:merge_request_id/comments". Use "merge_requests/:merge_request_id/notes" instead
- Deprecate API "merge_request/:merge_request_id/...". Use "merge_requests/:merge_request_id/..." instead - Deprecate API "merge_request/:merge_request_id/...". Use "merge_requests/:merge_request_id/..." instead
- Prevent parse error when name of project ends with .atom and prevent path issues
- Mark inline difference between old and new paths when a file is renamed - Mark inline difference between old and new paths when a file is renamed
- Support Akismet spam checking for creation of issues via API (Stan Hu) - Support Akismet spam checking for creation of issues via API (Stan Hu)
- Improve UI consistency between projects and groups lists - Improve UI consistency between projects and groups lists
......
...@@ -215,4 +215,76 @@ $ -> ...@@ -215,4 +215,76 @@ $ ->
$this = $(this) $this = $(this)
$this.attr 'value', $this.val() $this.attr 'value', $this.val()
$(document).on 'breakpoint:change', (e, breakpoint) ->
if breakpoint is 'sm' or breakpoint is 'xs'
$gutterIcon = $('.gutter-toggle').find('i')
if $gutterIcon.hasClass('fa-angle-double-right')
$gutterIcon.closest('a').trigger('click')
$(document).on 'click', 'aside .gutter-toggle', (e) ->
e.preventDefault()
$this = $(this)
$thisIcon = $this.find 'i'
if $thisIcon.hasClass('fa-angle-double-right')
$thisIcon.removeClass('fa-angle-double-right')
.addClass('fa-angle-double-left')
$this
.closest('aside')
.removeClass('right-sidebar-expanded')
.addClass('right-sidebar-collapsed')
$('.page-with-sidebar')
.removeClass('right-sidebar-expanded')
.addClass('right-sidebar-collapsed')
else
$thisIcon.removeClass('fa-angle-double-left')
.addClass('fa-angle-double-right')
$this
.closest('aside')
.removeClass('right-sidebar-collapsed')
.addClass('right-sidebar-expanded')
$('.page-with-sidebar')
.removeClass('right-sidebar-collapsed')
.addClass('right-sidebar-expanded')
$.cookie("collapsed_gutter",
$('.right-sidebar')
.hasClass('right-sidebar-collapsed'), { path: '/' })
bootstrapBreakpoint = undefined;
checkBootstrapBreakpoints = ->
if $('.device-xs').is(':visible')
bootstrapBreakpoint = "xs"
else if $('.device-sm').is(':visible')
bootstrapBreakpoint = "sm"
else if $('.device-md').is(':visible')
bootstrapBreakpoint = "md"
else if $('.device-lg').is(':visible')
bootstrapBreakpoint = "lg"
setBootstrapBreakpoints = ->
if $('.device-xs').length
return
$("body")
.append('<div class="device-xs visible-xs"></div>'+
'<div class="device-sm visible-sm"></div>'+
'<div class="device-md visible-md"></div>'+
'<div class="device-lg visible-lg"></div>')
checkBootstrapBreakpoints()
fitSidebarForSize = ->
oldBootstrapBreakpoint = bootstrapBreakpoint
checkBootstrapBreakpoints()
if bootstrapBreakpoint != oldBootstrapBreakpoint
$(document).trigger('breakpoint:change', [bootstrapBreakpoint])
checkInitialSidebarSize = ->
if bootstrapBreakpoint is "xs" or "sm"
$(document).trigger('breakpoint:change', [bootstrapBreakpoint])
$(window).on "resize", (e) ->
fitSidebarForSize()
setBootstrapBreakpoints()
checkInitialSidebarSize()
new Aside() new Aside()
...@@ -10,19 +10,7 @@ class @IssuableContext ...@@ -10,19 +10,7 @@ class @IssuableContext
$(".issuable-sidebar .inline-update").on "change", ".js-assignee", -> $(".issuable-sidebar .inline-update").on "change", ".js-assignee", ->
$(this).submit() $(this).submit()
$('.issuable-details').waitForImages -> $(document).on "click",".edit-link", (e) ->
$('.issuable-affix').on 'affix.bs.affix', ->
$(@).width($(@).outerWidth())
.on 'affixed-top.bs.affix affixed-bottom.bs.affix', ->
$(@).width('')
$('.issuable-affix').affix offset:
top: ->
@top = ($('.issuable-affix').offset().top - 70)
bottom: ->
@bottom = $('.footer').outerHeight(true)
$(".edit-link").click (e) ->
block = $(@).parents('.block') block = $(@).parents('.block')
block.find('.selectbox').show() block.find('.selectbox').show()
block.find('.value').hide() block.find('.value').hide()
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
&.s26 { width: 26px; height: 26px; margin-right: 8px; } &.s26 { width: 26px; height: 26px; margin-right: 8px; }
&.s32 { width: 32px; height: 32px; margin-right: 10px; } &.s32 { width: 32px; height: 32px; margin-right: 10px; }
&.s36 { width: 36px; height: 36px; margin-right: 10px; } &.s36 { width: 36px; height: 36px; margin-right: 10px; }
&.s40 { width: 40px; height: 40px; margin-right: 10px; }
&.s46 { width: 46px; height: 46px; margin-right: 15px; } &.s46 { width: 46px; height: 46px; margin-right: 15px; }
&.s48 { width: 48px; height: 48px; margin-right: 10px; } &.s48 { width: 48px; height: 48px; margin-right: 10px; }
&.s60 { width: 60px; height: 60px; margin-right: 12px; } &.s60 { width: 60px; height: 60px; margin-right: 12px; }
...@@ -40,7 +41,8 @@ ...@@ -40,7 +41,8 @@
&.s16 { font-size: 12px; line-height: 1.33; } &.s16 { font-size: 12px; line-height: 1.33; }
&.s24 { font-size: 14px; line-height: 1.8; } &.s24 { font-size: 14px; line-height: 1.8; }
&.s26 { font-size: 20px; line-height: 1.33; } &.s26 { font-size: 20px; line-height: 1.33; }
&.s32 { font-size: 22px; line-height: 32px; } &.s32 { font-size: 20px; line-height: 32px; }
&.s40 { font-size: 16px; line-height: 40px; }
&.s60 { font-size: 32px; line-height: 60px; } &.s60 { font-size: 32px; line-height: 60px; }
&.s90 { font-size: 36px; line-height: 90px; } &.s90 { font-size: 36px; line-height: 90px; }
&.s110 { font-size: 40px; line-height: 112px; font-weight: 300; } &.s110 { font-size: 40px; line-height: 112px; font-weight: 300; }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
@include border-radius(3px); @include border-radius(3px);
font-size: $gl-font-size; font-size: $gl-font-size;
font-weight: 500; font-weight: 500;
padding: $gl-vert-padding $gl-padding; padding: $gl-vert-padding $gl-btn-padding;
&:focus, &:focus,
&:active { &:active {
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
display: block; display: block;
float: left; float: left;
padding: 0 $gl-padding; padding: 0 $gl-btn-padding;
font-weight: normal; font-weight: normal;
margin-right: 10px; margin-right: 10px;
font-size: $gl-font-size; font-size: $gl-font-size;
......
...@@ -109,7 +109,6 @@ ul.content-list { ...@@ -109,7 +109,6 @@ ul.content-list {
padding: 0; padding: 0;
> li { > li {
padding: $gl-padding 0;
border-color: $table-border-color; border-color: $table-border-color;
color: $gl-gray; color: $gl-gray;
......
...@@ -116,7 +116,7 @@ ...@@ -116,7 +116,7 @@
display: none; display: none;
} }
aside { aside:not(.right-sidebar){
display: none; display: none;
} }
......
...@@ -200,6 +200,14 @@ ...@@ -200,6 +200,14 @@
} }
} }
@mixin expanded-gutter {
padding-right: $gutter_width;
}
@mixin collapsed-gutter {
padding-right: $sidebar_collapsed_width;
}
@mixin collapsed-sidebar { @mixin collapsed-sidebar {
padding-left: $sidebar_collapsed_width; padding-left: $sidebar_collapsed_width;
...@@ -266,6 +274,7 @@ ...@@ -266,6 +274,7 @@
background: #f2f6f7; background: #f2f6f7;
} }
// page is small enough
@media (max-width: $screen-md-max) { @media (max-width: $screen-md-max) {
.page-sidebar-collapsed { .page-sidebar-collapsed {
@include collapsed-sidebar; @include collapsed-sidebar;
...@@ -275,12 +284,32 @@ ...@@ -275,12 +284,32 @@
@include collapsed-sidebar; @include collapsed-sidebar;
} }
.page-gutter {
&.right-sidebar-collapsed {
@include collapsed-gutter;
}
&.right-sidebar-expanded {
@include expanded-gutter;
}
}
.collapse-nav { .collapse-nav {
display: none; display: none;
} }
} }
// page is large enough
@media(min-width: $screen-md-max) { @media(min-width: $screen-md-max) {
.page-gutter {
&.right-sidebar-collapsed {
@include collapsed-gutter;
}
&.right-sidebar-expanded {
@include expanded-gutter;
}
}
.page-sidebar-collapsed { .page-sidebar-collapsed {
@include collapsed-sidebar; @include collapsed-sidebar;
} }
...@@ -288,4 +317,4 @@ ...@@ -288,4 +317,4 @@
.page-sidebar-expanded { .page-sidebar-expanded {
@include expanded-sidebar; @include expanded-sidebar;
} }
} }
\ No newline at end of file
...@@ -12,6 +12,9 @@ $gl-font-size: 15px; ...@@ -12,6 +12,9 @@ $gl-font-size: 15px;
$list-font-size: 15px; $list-font-size: 15px;
$sidebar_collapsed_width: 62px; $sidebar_collapsed_width: 62px;
$sidebar_width: 230px; $sidebar_width: 230px;
$gutter_collapsed_width: 62px;
$gutter_width: 312px;
$gutter_inner_width: 280px;
$avatar_radius: 50%; $avatar_radius: 50%;
$code_font_size: 13px; $code_font_size: 13px;
$code_line_height: 1.5; $code_line_height: 1.5;
...@@ -22,9 +25,10 @@ $header-height: 58px; ...@@ -22,9 +25,10 @@ $header-height: 58px;
$fixed-layout-width: 1280px; $fixed-layout-width: 1280px;
$gl-gray: #5a5a5a; $gl-gray: #5a5a5a;
$gl-padding: 16px; $gl-padding: 16px;
$gl-btn-padding: 10px;
$gl-vert-padding: 6px; $gl-vert-padding: 6px;
$gl-padding-top:10px; $gl-padding-top:10px;
$gl-avatar-size: 46px; $gl-avatar-size: 40px;
$secondary-text: #7f8fa4; $secondary-text: #7f8fa4;
$error-exclamation-point: #E62958; $error-exclamation-point: #E62958;
...@@ -36,11 +40,12 @@ $white-light: #FFFFFF; ...@@ -36,11 +40,12 @@ $white-light: #FFFFFF;
$white-normal: #ededed; $white-normal: #ededed;
$white-dark: #ededed; $white-dark: #ededed;
$gray-light: #f7f7f7; $gray-light: #faf9f9;
$gray-normal: #ededed; $gray-normal: #f5f5f5;
$gray-dark: #ededed; $gray-dark: #ededed;
$gray-darkest: #c9c9c9;
$green-light: #31AF64; $green-light: #38ae67;
$green-normal: #2FAA60; $green-normal: #2FAA60;
$green-dark: #2CA05B; $green-dark: #2CA05B;
...@@ -52,7 +57,7 @@ $blue-medium-light: #3498CB; ...@@ -52,7 +57,7 @@ $blue-medium-light: #3498CB;
$blue-medium: #2F8EBF; $blue-medium: #2F8EBF;
$blue-medium-dark: #2D86B4; $blue-medium-dark: #2D86B4;
$orange-light: #FC6443; $orange-light: rgba(252, 109, 38, 0.80);
$orange-normal: #E75E40; $orange-normal: #E75E40;
$orange-dark: #CE5237; $orange-dark: #CE5237;
...@@ -64,8 +69,8 @@ $border-white-light: #F1F2F4; ...@@ -64,8 +69,8 @@ $border-white-light: #F1F2F4;
$border-white-normal: #D6DAE2; $border-white-normal: #D6DAE2;
$border-white-dark: #C6CACF; $border-white-dark: #C6CACF;
$border-gray-light: #d1d1d1; $border-gray-light: rgba(0, 0, 0, 0.06);
$border-gray-normal: #D6DAE2; $border-gray-normal: rgba(0, 0, 0, 0.10);;
$border-gray-dark: #C6CACF; $border-gray-dark: #C6CACF;
$border-green-light: #2FAA60; $border-green-light: #2FAA60;
...@@ -76,7 +81,7 @@ $border-blue-light: #2D9FD8; ...@@ -76,7 +81,7 @@ $border-blue-light: #2D9FD8;
$border-blue-normal: #2897CE; $border-blue-normal: #2897CE;
$border-blue-dark: #258DC1; $border-blue-dark: #258DC1;
$border-orange-light: #ED5C3D; $border-orange-light: #fc6d26;
$border-orange-normal: #CE5237; $border-orange-normal: #CE5237;
$border-orange-dark: #C14E35; $border-orange-dark: #C14E35;
......
...@@ -40,10 +40,6 @@ ...@@ -40,10 +40,6 @@
.avatar { .avatar {
@include border-radius(50%); @include border-radius(50%);
} }
.identicon {
line-height: 46px;
}
} }
.dash-project-access-icon { .dash-project-access-icon {
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
*/ */
.event-item { .event-item {
font-size: $gl-font-size; font-size: $gl-font-size;
padding: $gl-padding 0 $gl-padding ($gl-avatar-size + 15px); padding: $gl-padding-top 0 $gl-padding-top ($gl-avatar-size + $gl-padding-top);
border-bottom: 1px solid $table-border-color; border-bottom: 1px solid $table-border-color;
color: #7f8fa4; color: #7f8fa4;
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
.event-title, .event-title,
.event-item-timestamp { .event-item-timestamp {
line-height: 44px; line-height: 40px;
} }
} }
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
} }
.avatar { .avatar {
margin-left: -($gl-avatar-size + 15px); margin-left: -($gl-avatar-size + $gl-padding-top);
} }
.event-title { .event-title {
...@@ -41,7 +41,6 @@ ...@@ -41,7 +41,6 @@
margin-right: 174px; margin-right: 174px;
.event-note { .event-note {
margin-top: 5px;
word-wrap: break-word; word-wrap: break-word;
.md { .md {
...@@ -98,8 +97,6 @@ ...@@ -98,8 +97,6 @@
&:last-child { border:none } &:last-child { border:none }
.event_commits { .event_commits {
margin-top: 9px;
li { li {
&.commit { &.commit {
background: transparent; background: transparent;
......
...@@ -42,8 +42,6 @@ ...@@ -42,8 +42,6 @@
.issuable-details { .issuable-details {
section { section {
border-right: 1px solid $border-white-light;
.issuable-discussion { .issuable-discussion {
margin-right: 1px; margin-right: 1px;
} }
...@@ -73,11 +71,35 @@ ...@@ -73,11 +71,35 @@
.block { .block {
@include clearfix; @include clearfix;
padding: $gl-padding 0; padding: $gl-padding 0;
border-bottom: 1px solid #F0F0F0; border-bottom: 1px solid $border-gray-light;
// This prevents the mess when resizing the sidebar
// of elements repositioning themselves..
width: $gutter_inner_width;
overflow-x: hidden;
// --
&:first-child {
padding-top: 5px;
}
&:last-child { &:last-child {
border: none; border: none;
} }
span {
margin-top: 7px;
display: inline-block;
}
.issuable-count {
}
.gutter-toggle {
margin-left: 20px;
border-left: 1px solid $border-gray-light;
padding-left: 10px;
}
} }
.title { .title {
...@@ -133,3 +155,92 @@ ...@@ -133,3 +155,92 @@
margin-right: 2px; margin-right: 2px;
} }
} }
.right-sidebar {
position: fixed;
top: 58px;
right: 0;
height: 100%;
transition-duration: .3s;
background: $gray-light;
overflow: scroll;
padding: 10px 20px;
&.right-sidebar-expanded {
width: $gutter_width;
hr {
display: none;
}
}
.subscribe-button {
span {
margin-top: 0;
}
}
&.right-sidebar-collapsed {
width: $sidebar_collapsed_width;
padding-top: 0;
overflow-x: hidden;
hr {
margin: 0;
color: $gray-normal;
border-color: $gray-normal;
width: 62px;
margin-left: -20px
}
.block {
border-bottom: none;
padding: 15px 0 0 0;
}
}
.btn {
background: $gray-normal;
border: 1px solid $border-gray-normal;
}
&.right-sidebar-collapsed {
.issuable-count,
.issuable-nav,
.assignee > *,
.milestone > *,
.labels > *,
.participants > *,
.light > *,
.project-reference > * {
display: none;
}
.gutter-toggle {
margin-left: -$gutter_inner_width + 4;
}
.sidebar-collapsed-icon {
display: block;
float: left;
width: 62px;
text-align: center;
margin-left: -19px;
padding-bottom: 10px;
color: #999999;
span {
display: block;
margin-top: 0;
}
}
}
&.right-sidebar-expanded {
.sidebar-collapsed-icon {
display: none;
}
}
}
\ No newline at end of file
...@@ -65,10 +65,6 @@ form.edit-issue { ...@@ -65,10 +65,6 @@ form.edit-issue {
width: 3em; width: 3em;
} }
.merge-request-info {
padding-left: 5px;
}
.merge-request-status { .merge-request-status {
color: $gl-gray; color: $gl-gray;
font-size: 15px; font-size: 15px;
...@@ -143,4 +139,4 @@ form.edit-issue { ...@@ -143,4 +139,4 @@ form.edit-issue {
.issue-closed-by-widget { .issue-closed-by-widget {
color: $secondary-text; color: $secondary-text;
margin-left: 52px; margin-left: 52px;
} }
\ No newline at end of file
...@@ -391,12 +391,11 @@ pre.light-well { ...@@ -391,12 +391,11 @@ pre.light-well {
@include basic-list; @include basic-list;
.project-row { .project-row {
padding: $gl-padding 0;
border-color: $table-border-color; border-color: $table-border-color;
&.no-description { &.no-description {
.project { .project {
line-height: 44px; line-height: 40px;
} }
} }
...@@ -409,7 +408,7 @@ pre.light-well { ...@@ -409,7 +408,7 @@ pre.light-well {
.project-controls { .project-controls {
float: right; float: right;
color: $gl-gray; color: $gl-gray;
line-height: 45px; line-height: 40px;
color: #7f8fa4; color: #7f8fa4;
a:hover { a:hover {
......
class OmniauthCallbacksController < Devise::OmniauthCallbacksController class OmniauthCallbacksController < Devise::OmniauthCallbacksController
include AuthenticatesWithTwoFactor
protect_from_forgery except: [:kerberos, :saml, :cas3] protect_from_forgery except: [:kerberos, :saml, :cas3]
...@@ -29,8 +30,12 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -29,8 +30,12 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
# Do additional LDAP checks for the user filter and EE features # Do additional LDAP checks for the user filter and EE features
if ldap_user.allowed? if ldap_user.allowed?
log_audit_event(@user, with: :ldap) if @user.two_factor_enabled?
sign_in_and_redirect(@user) prompt_for_two_factor(@user)
else
log_audit_event(@user, with: :ldap)
sign_in_and_redirect(@user)
end
else else
flash[:alert] = "Access denied for your LDAP account." flash[:alert] = "Access denied for your LDAP account."
redirect_to new_user_session_path redirect_to new_user_session_path
......
...@@ -169,18 +169,6 @@ module ApplicationHelper ...@@ -169,18 +169,6 @@ module ApplicationHelper
Gitlab.config.extra Gitlab.config.extra
end end
def search_placeholder
if @project && @project.persisted?
'Search'
elsif @snippet || @snippets || @show_snippets
'Search snippets'
elsif @group && @group.persisted?
'Search in this group'
else
'Search'
end
end
# Render a `time` element with Javascript-based relative date and tooltip # Render a `time` element with Javascript-based relative date and tooltip
# #
# time - Time object # time - Time object
...@@ -293,6 +281,76 @@ module ApplicationHelper ...@@ -293,6 +281,76 @@ module ApplicationHelper
end end
end end
def issuable_link_next(project,issuable)
if project.nil?
nil
elsif current_controller?(:issues)
namespace_project_issue_path(project.namespace, project, next_issuable_for(project, issuable.id).try(:iid))
elsif current_controller?(:merge_requests)
namespace_project_merge_request_path(project.namespace, project, next_issuable_for(project, issuable.id).try(:iid))
end
end
def issuable_link_prev(project,issuable)
if project.nil?
nil
elsif current_controller?(:issues)
namespace_project_issue_path(project.namespace, project, prev_issuable_for(project, issuable.id).try(:iid))
elsif current_controller?(:merge_requests)
namespace_project_merge_request_path(project.namespace, project, prev_issuable_for(project, issuable.id).try(:iid))
end
end
def issuable_count(entity, project)
if project.nil?
0
elsif current_controller?(:issues)
project.issues.send(entity).count
elsif current_controller?(:merge_requests)
project.merge_requests.send(entity).count
end
end
def next_issuable_for(project, id)
if project.nil?
nil
elsif current_controller?(:issues)
project.issues.where("id > ?", id).last
elsif current_controller?(:merge_requests)
project.merge_requests.where("id > ?", id).last
end
end
def has_next_issuable?(project, id)
if project.nil?
nil
elsif current_controller?(:issues)
project.issues.where("id > ?", id).last
elsif current_controller?(:merge_requests)
project.merge_requests.where("id > ?", id).last
end
end
def prev_issuable_for(project, id)
if project.nil?
nil
elsif current_controller?(:issues)
project.issues.where("id < ?", id).first
elsif current_controller?(:merge_requests)
project.merge_requests.where("id < ?", id).first
end
end
def has_prev_issuable?(project, id)
if project.nil?
nil
elsif current_controller?(:issues)
project.issues.where("id < ?", id).first
elsif current_controller?(:merge_requests)
project.merge_requests.where("id < ?", id).first
end
end
def state_filters_text_for(entity, project) def state_filters_text_for(entity, project)
titles = { titles = {
opened: "Open" opened: "Open"
......
...@@ -7,6 +7,8 @@ module LabelsHelper ...@@ -7,6 +7,8 @@ module LabelsHelper
# project - Project object which will be used as the context for the label's # project - Project object which will be used as the context for the label's
# link. If omitted, defaults to `@project`, or the label's own # link. If omitted, defaults to `@project`, or the label's own
# project. # project.
# type - The type of item the link will point to (:issue or
# :merge_request). If omitted, defaults to :issue.
# block - An optional block that will be passed to `link_to`, forming the # block - An optional block that will be passed to `link_to`, forming the
# body of the link element. If omitted, defaults to # body of the link element. If omitted, defaults to
# `render_colored_label`. # `render_colored_label`.
...@@ -23,14 +25,19 @@ module LabelsHelper ...@@ -23,14 +25,19 @@ module LabelsHelper
# # Force the generated link to use a provided project # # Force the generated link to use a provided project
# link_to_label(label, project: Project.last) # link_to_label(label, project: Project.last)
# #
# # Force the generated link to point to merge requests instead of issues
# link_to_label(label, type: :merge_request)
#
# # Customize link body with a block # # Customize link body with a block
# link_to_label(label) { "My Custom Label Text" } # link_to_label(label) { "My Custom Label Text" }
# #
# Returns a String # Returns a String
def link_to_label(label, project: nil, &block) def link_to_label(label, project: nil, type: :issue, &block)
project ||= @project || label.project project ||= @project || label.project
link = namespace_project_issues_path(project.namespace, project, link = send("namespace_project_#{type.to_s.pluralize}_path",
label_name: label.name) project.namespace,
project,
label_name: label.name)
if block_given? if block_given?
link_to link, &block link_to link, &block
......
...@@ -3,6 +3,18 @@ module NavHelper ...@@ -3,6 +3,18 @@ module NavHelper
cookies[:collapsed_nav] == 'true' cookies[:collapsed_nav] == 'true'
end end
def sidebar_gutter_collapsed_class
if cookies[:collapsed_gutter] == 'true'
"right-sidebar-collapsed"
else
"right-sidebar-expanded"
end
end
def sidebar_gutter_collapsed?
cookies[:collapsed_gutter] == 'true'
end
def nav_sidebar_class def nav_sidebar_class
if nav_menu_collapsed? if nav_menu_collapsed?
"sidebar-collapsed" "sidebar-collapsed"
...@@ -19,6 +31,17 @@ module NavHelper ...@@ -19,6 +31,17 @@ module NavHelper
end end
end end
def page_gutter_class
if current_path?('merge_requests#show') || current_path?('issues#show')
if cookies[:collapsed_gutter] == 'true'
"page-gutter right-sidebar-collapsed"
else
"page-gutter right-sidebar-expanded"
end
end
end
def nav_header_class def nav_header_class
if nav_menu_collapsed? if nav_menu_collapsed?
"header-collapsed" "header-collapsed"
......
...@@ -20,6 +20,12 @@ module ProjectsHelper ...@@ -20,6 +20,12 @@ module ProjectsHelper
end end
end end
def link_to_member_avatar(author, opts = {})
default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name" }
opts = default_opts.merge(opts)
image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar]
end
def link_to_member(project, author, opts = {}) def link_to_member(project, author, opts = {})
default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name" } default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name" }
opts = default_opts.merge(opts) opts = default_opts.merge(opts)
......
...@@ -92,6 +92,11 @@ ...@@ -92,6 +92,11 @@
Rails Rails
%span.pull-right %span.pull-right
#{Rails::VERSION::STRING} #{Rails::VERSION::STRING}
%p
= Gitlab::Database.adapter_name
%span.pull-right
= Gitlab::Database.version
%hr %hr
.row .row
.col-sm-4 .col-sm-4
......
...@@ -3,8 +3,8 @@ ...@@ -3,8 +3,8 @@
.event-item-timestamp .event-item-timestamp
#{time_ago_with_tooltip(event.created_at)} #{time_ago_with_tooltip(event.created_at)}
= cache [event, current_application_settings, "v2.1"] do = cache [event, current_application_settings, "v2.2"] do
= image_tag avatar_icon(event.author_email, 46), class: "avatar s46", alt:'' = image_tag avatar_icon(event.author_email, 40), class: "avatar s40", alt:''
- if event.created_project? - if event.created_project?
= render "events/event/created_project", event: event = render "events/event/created_project", event: event
- elsif event.push? - elsif event.push?
......
...@@ -40,10 +40,6 @@ ...@@ -40,10 +40,6 @@
%td.shortcut %td.shortcut
.key enter .key enter
%td Open Selection %td Open Selection
%tr
%td.shortcut
.key t
%td Go to finding file
%tbody %tbody
%tr %tr
%th %th
...@@ -161,6 +157,10 @@ ...@@ -161,6 +157,10 @@
.key s .key s
%td %td
Go to snippets Go to snippets
%tr
%td.shortcut
.key t
%td Go to finding file
.col-lg-4 .col-lg-4
%table.shortcut-mappings %table.shortcut-mappings
%tbody{ class: 'hidden-shortcut network', style: 'display:none' } %tbody{ class: 'hidden-shortcut network', style: 'display:none' }
......
.page-with-sidebar{ class: page_sidebar_class } .page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" }
= render "layouts/broadcast" = render "layouts/broadcast"
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class } .sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
.header-logo .header-logo
......
.search .search
= form_tag search_path, method: :get, class: 'navbar-form pull-left' do |f| = form_tag search_path, method: :get, class: 'navbar-form pull-left' do |f|
= search_field_tag "search", nil, placeholder: search_placeholder, class: "search-input form-control", spellcheck: false = search_field_tag "search", nil, placeholder: 'search', class: "search-input form-control", spellcheck: false
= hidden_field_tag :group_id, @group.try(:id) = hidden_field_tag :group_id, @group.try(:id)
- if @project && @project.persisted? - if @project && @project.persisted?
= hidden_field_tag :project_id, @project.id = hidden_field_tag :project_id, @project.id
......
...@@ -31,34 +31,33 @@ ...@@ -31,34 +31,33 @@
- else - else
= f.submit 'Generate', class: "btn btn-default" = f.submit 'Generate', class: "btn btn-default"
- unless current_user.ldap_user? .panel.panel-default
.panel.panel-default .panel-heading
.panel-heading Two-factor Authentication
Two-factor Authentication .panel-body
.panel-body - if current_user.two_factor_enabled?
- if current_user.two_factor_enabled? .pull-right
.pull-right = link_to 'Disable Two-factor Authentication', profile_two_factor_auth_path, method: :delete, class: 'btn btn-close btn-sm',
= link_to 'Disable Two-factor Authentication', profile_two_factor_auth_path, method: :delete, class: 'btn btn-close btn-sm', data: { confirm: 'Are you sure?' }
data: { confirm: 'Are you sure?' } %p.text-success
%p.text-success %strong
%strong Two-factor Authentication is enabled
Two-factor Authentication is enabled %p
%p If you lose your recovery codes you can
If you lose your recovery codes you can %strong
%strong = succeed ',' do
= succeed ',' do = link_to 'generate new ones', codes_profile_two_factor_auth_path, method: :post, data: { confirm: 'Are you sure?' }
= link_to 'generate new ones', codes_profile_two_factor_auth_path, method: :post, data: { confirm: 'Are you sure?' } invalidating all previous codes.
invalidating all previous codes.
- else - else
%p %p
Increase your account's security by enabling two-factor authentication (2FA). Increase your account's security by enabling two-factor authentication (2FA).
%p %p
Each time you log in you’ll be required to provide your username and Each time you log in you’ll be required to provide your username and
password as usual, plus a randomly-generated code from your phone. password as usual, plus a randomly-generated code from your phone.
.form-actions .form-actions
= link_to 'Enable Two-factor Authentication', new_profile_two_factor_auth_path, class: 'btn btn-success' = link_to 'Enable Two-factor Authentication', new_profile_two_factor_auth_path, class: 'btn btn-success'
- if button_based_providers.any? - if button_based_providers.any?
.panel.panel-default .panel.panel-default
......
...@@ -54,11 +54,8 @@ ...@@ -54,11 +54,8 @@
= render 'votes/votes_block', votable: @issue = render 'votes/votes_block', votable: @issue
.row .row
%section.col-md-9 %section.col-md-12
.issuable-discussion .issuable-discussion
= render 'projects/issues/discussion' = render 'projects/issues/discussion'
%aside.col-md-3 = render 'shared/issuable/sidebar', issuable: @issue
= render 'shared/issuable/sidebar', issuable: @issue \ No newline at end of file
= render 'shared/show_aside'
$('.issuable-sidebar').html("#{escape_javascript(render 'shared/issuable/sidebar', issuable: @issue)}"); $('aside.right-sidebar')[0].outerHTML = "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @issue)}";
$('.issuable-sidebar').parent().effect('highlight') $('aside.right-sidebar').effect('highlight');
new Issue(); new Issue();
\ No newline at end of file
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
- if merge_request.labels.any? - if merge_request.labels.any?
&nbsp; &nbsp;
- merge_request.labels.each do |label| - merge_request.labels.each do |label|
= link_to_label(label, project: merge_request.project) = link_to_label(label, project: merge_request.project, type: 'merge_request')
- if merge_request.tasks? - if merge_request.tasks?
&nbsp; &nbsp;
%span.task-status %span.task-status
......
...@@ -70,12 +70,9 @@ ...@@ -70,12 +70,9 @@
= render 'votes/votes_block', votable: @merge_request = render 'votes/votes_block', votable: @merge_request
.row .row
%section.col-md-9 %section.col-md-12
.issuable-discussion .issuable-discussion
= render "projects/merge_requests/discussion" = render "projects/merge_requests/discussion"
%aside.col-md-3
= render 'shared/issuable/sidebar', issuable: @merge_request
= render 'shared/show_aside'
#commits.commits.tab-pane #commits.commits.tab-pane
- # This tab is always loaded via AJAX - # This tab is always loaded via AJAX
...@@ -87,6 +84,8 @@ ...@@ -87,6 +84,8 @@
.mr-loading-status .mr-loading-status
= spinner = spinner
= render 'shared/issuable/sidebar', issuable: @merge_request
:javascript :javascript
var merge_request; var merge_request;
......
$('.issuable-sidebar').html("#{escape_javascript(render 'shared/issuable/sidebar', issuable: @merge_request)}"); $('aside.right-sidebar')[0].outerHTML= "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @merge_request)}";
$('.issuable-sidebar').parent().effect('highlight') $('aside.right-sidebar').effect('highlight')
merge_request = new MergeRequest(); merge_request = new MergeRequest();
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
= icon('users') = icon('users')
= number_with_delimiter(group.users.count) = number_with_delimiter(group.users.count)
= image_tag group_icon(group), class: "avatar s46 hidden-xs" = image_tag group_icon(group), class: "avatar s40 hidden-xs"
= link_to group, class: 'group-name' do = link_to group, class: 'group-name' do
%span.item-title= group.name %span.item-title= group.name
......
.block.participants .block.participants
.sidebar-collapsed-icon
= icon('users')
%span
= participants.count
.title .title
= pluralize participants.count, "participant" = pluralize participants.count, "participant"
- participants.each do |participant| - participants.each do |participant|
......
.issuable-sidebar.issuable-affix %aside.right-sidebar{ class: sidebar_gutter_collapsed_class }
= form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f| .issuable-sidebar
.block.assignee .block
.title %span.issuable-count.pull-left
%label = issuable.iid
Assignee of
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) = issuable_count(:all, @project)
.pull-right %span.pull-right
= link_to 'Edit', '#', class: 'edit-link' %a.gutter-toggle{href: '#'}
.value - if sidebar_gutter_collapsed?
- if issuable.assignee = icon('angle-double-left')
%strong= link_to_member(@project, issuable.assignee, size: 24) - else
- if issuable.instance_of?(MergeRequest) && !issuable.can_be_merged_by?(issuable.assignee) = icon('angle-double-right')
%a.pull-right.cannot-be-merged{href: '#', data: {toggle: 'tooltip'}, title: 'Not allowed to merge'} .issuable-nav.pull-right.btn-group{role: 'group', "aria-label" => '...'}
= icon('exclamation-triangle') - if has_prev_issuable?(@project, issuable.id)
= link_to 'Prev', issuable_link_prev(@project, issuable), class: 'btn btn-default'
- else - else
.light None %a.btn.btn-default.disabled{href: '#'}
Prev
- if has_next_issuable?(@project, issuable.id)
= link_to 'Next', issuable_link_next(@project, issuable), class: 'btn btn-default'
- else
%a.btn.btn-default.disabled{href: '#'}
Next
.selectbox = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f|
= users_select_tag("#{issuable.class.table_name.singularize}[assignee_id]", placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: issuable.assignee_id, project: @target_project, null_user: true, current_user: true, first_user: true) .block.assignee
.sidebar-collapsed-icon
- if issuable.assignee
= link_to_member_avatar(issuable.assignee, size: 24)
- else
= icon('user')
.title
%label
Assignee
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
.pull-right
= link_to 'Edit', '#', class: 'edit-link'
.value
- if issuable.assignee
%strong= link_to_member(@project, issuable.assignee, size: 24)
- if issuable.instance_of?(MergeRequest) && !issuable.can_be_merged_by?(issuable.assignee)
%a.pull-right.cannot-be-merged{href: '#', data: {toggle: 'tooltip'}, title: 'Not allowed to merge'}
= icon('exclamation-triangle')
- else
.light None
.block.milestone .selectbox
.title = users_select_tag("#{issuable.class.table_name.singularize}[assignee_id]", placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: issuable.assignee_id, project: @target_project, null_user: true, current_user: true, first_user: true)
%label
Milestone
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
.pull-right
= link_to 'Edit', '#', class: 'edit-link'
.value
- if issuable.milestone
%span.back-to-milestone
= link_to namespace_project_milestone_path(@project.namespace, @project, issuable.milestone) do
%strong
= icon('clock-o')
= issuable.milestone.title
- else
.light None
.selectbox
= f.select(:milestone_id, milestone_options(issuable), { include_blank: true }, { class: 'select2 select2-compact js-select2 js-milestone', data: { placeholder: 'Select milestone' }})
= hidden_field_tag :issuable_context
= f.submit class: 'btn hide'
- if issuable.project.labels.any? .block.milestone
.block .sidebar-collapsed-icon
= icon('balance-scale')
%span
- if issuable.milestone
= issuable.milestone.title
- else
No
.title .title
%label Labels %label
Milestone
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
.pull-right .pull-right
= link_to 'Edit', '#', class: 'edit-link' = link_to 'Edit', '#', class: 'edit-link'
.value.issuable-show-labels .value
- if issuable.labels.any? - if issuable.milestone
- issuable.labels.each do |label| %span.back-to-milestone
= link_to_label(label) = link_to namespace_project_milestone_path(@project.namespace, @project, issuable.milestone) do
%strong
= icon('clock-o')
= issuable.milestone.title
- else - else
.light None .light None
.selectbox .selectbox
= f.collection_select :label_ids, issuable.project.labels.all, :id, :name, = f.select(:milestone_id, milestone_options(issuable), { include_blank: true }, { class: 'select2 select2-compact js-select2 js-milestone', data: { placeholder: 'Select milestone' }})
{ selected: issuable.label_ids }, multiple: true, class: 'select2 js-select2', data: { placeholder: "Select labels" } = hidden_field_tag :issuable_context
= f.submit class: 'btn hide'
= render "shared/issuable/participants", participants: issuable.participants(current_user) - if issuable.project.labels.any?
.block.labels
.sidebar-collapsed-icon
= icon('tags')
%span
= issuable.labels.count
.title
%label Labels
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
.pull-right
= link_to 'Edit', '#', class: 'edit-link'
.value.issuable-show-labels
- if issuable.labels.any?
- issuable.labels.each do |label|
= link_to_label(label, type: issuable.to_ability_name)
- else
.light None
.selectbox
= f.collection_select :label_ids, issuable.project.labels.all, :id, :name,
{ selected: issuable.label_ids }, multiple: true, class: 'select2 js-select2', data: { placeholder: "Select labels" }
- if current_user = render "shared/issuable/participants", participants: issuable.participants(current_user)
- subscribed = issuable.subscribed?(current_user) %hr
.block.light - if current_user
.title - subscribed = issuable.subscribed?(current_user)
%label.light Notifications .block.light
- subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed' .sidebar-collapsed-icon
%button.btn.btn-block.btn-gray.subscribe-button{:type => 'button'} = icon('rss')
%span= subscribed ? 'Unsubscribe' : 'Subscribe' .title
.subscription-status{data: {status: subscribtion_status}} %label.light Notifications
.unsubscribed{class: ( 'hidden' if subscribed )} - subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed'
You're not receiving notifications from this thread. %button.btn.btn-block.btn-gray.subscribe-button{:type => 'button'}
.subscribed{class: ( 'hidden' unless subscribed )} %span= subscribed ? 'Unsubscribe' : 'Subscribe'
You're receiving notifications because you're subscribed to this thread. .subscription-status{data: {status: subscribtion_status}}
.unsubscribed{class: ( 'hidden' if subscribed )}
You're not receiving notifications from this thread.
.subscribed{class: ( 'hidden' unless subscribed )}
You're receiving notifications because you're subscribed to this thread.
- project_ref = cross_project_reference(@project, issuable) - project_ref = cross_project_reference(@project, issuable)
.block .block.project-reference
.title .sidebar-collapsed-icon
.cross-project-reference = icon('clipboard')
%span .title
Reference: .cross-project-reference
%cite{title: project_ref} %span
= project_ref Reference:
= clipboard_button(clipboard_text: project_ref) %cite{title: project_ref}
= project_ref
= clipboard_button(clipboard_text: project_ref)
:javascript :javascript
new Subscription("#{toggle_subscription_path(issuable)}"); new Subscription("#{toggle_subscription_path(issuable)}");
new IssuableContext(); new IssuableContext();
\ No newline at end of file
...@@ -16,9 +16,9 @@ ...@@ -16,9 +16,9 @@
- if avatar - if avatar
.dash-project-avatar .dash-project-avatar
- if use_creator_avatar - if use_creator_avatar
= image_tag avatar_icon(project.creator.email, 46), class: "avatar s46", alt:'' = image_tag avatar_icon(project.creator.email, 40), class: "avatar s40", alt:''
- else - else
= project_icon(project, alt: '', class: 'avatar project-avatar s46') = project_icon(project, alt: '', class: 'avatar project-avatar s40')
%span.project-full-name %span.project-full-name
%span.namespace-name %span.namespace-name
- if project.namespace && !skip_namespace - if project.namespace && !skip_namespace
......
class RemoveDotAtomPathEndingOfProjects < ActiveRecord::Migration
include Gitlab::ShellAdapter
class ProjectPath
attr_reader :old_path, :id, :namespace_path
def initialize(old_path, id, namespace_path, namespace_id)
@old_path = old_path
@id = id
@namespace_path = namespace_path
@namespace_id = namespace_id
end
def clean_path
@_clean_path ||= PathCleaner.clean(@old_path, @namespace_id)
end
end
class PathCleaner
def initialize(path, namespace_id)
@namespace_id = namespace_id
@path = path
end
def self.clean(*args)
new(*args).clean
end
def clean
path = cleaned_path
count = 0
while path_exists?(path)
path = "#{cleaned_path}#{count}"
count += 1
end
path
end
private
def cleaned_path
@_cleaned_path ||= @path.gsub(/\.atom\z/, '-atom')
end
def path_exists?(path)
Project.find_by_path_and_namespace_id(path, @namespace_id)
end
end
def projects_with_dot_atom
select_all("SELECT p.id, p.path, n.path as namespace_path, n.id as namespace_id FROM projects p inner join namespaces n on n.id = p.namespace_id WHERE lower(p.path) LIKE '%.atom'")
end
def up
projects_with_dot_atom.each do |project|
project_path = ProjectPath.new(project['path'], project['id'], project['namespace_path'], project['namespace_id'])
clean_path(project_path) if rename_project_repo(project_path)
end
end
private
def clean_path(project_path)
execute "UPDATE projects SET path = #{sanitize(project_path.clean_path)} WHERE id = #{project_path.id}"
end
def rename_project_repo(project_path)
old_path_with_namespace = File.join(project_path.namespace_path, project_path.old_path)
new_path_with_namespace = File.join(project_path.namespace_path, project_path.clean_path)
gitlab_shell.mv_repository("#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki")
gitlab_shell.mv_repository(old_path_with_namespace, new_path_with_namespace)
rescue
false
end
def sanitize(value)
ActiveRecord::Base.connection.quote(value)
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160128233227) do ActiveRecord::Schema.define(version: 20160129135155) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
......
# Adding deploy keys to multiple projects # Adding deploy keys to multiple projects
If you want to easily add the same deploy key to multiple projects in the same group, this can be achieved quite easily with the API. If you want to easily add the same deploy key to multiple projects in the same
group, this can be achieved quite easily with the API.
First, find the ID of the projects you're interested in, by either listing all projects: First, find the ID of the projects you're interested in, by either listing all
projects:
``` ```
curl --header 'PRIVATE-TOKEN: abcdef' https://gitlab.com/api/v3/projects curl -H 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' https://gitlab.example.com/api/v3/projects
``` ```
Or finding the id of a group and then listing all projects in that group: Or finding the ID of a group and then listing all projects in that group:
``` ```
curl --header 'PRIVATE-TOKEN: abcdef' https://gitlab.com/api/v3/groups curl -H 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' https://gitlab.example.com/api/v3/groups
# For group 1234: # For group 1234:
curl --header 'PRIVATE-TOKEN: abcdef' https://gitlab.com/api/v3/groups/1234 curl -H 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' https://gitlab.example.com/api/v3/groups/1234
``` ```
With those IDs, add the same deploy key to all: With those IDs, add the same deploy key to all:
``` ```
for project_id in 321 456 987; do for project_id in 321 456 987; do
curl -X POST --data '{"title": "my key", "key": "ssh-rsa AAAA..."}' --header 'PRIVATE-TOKEN: abcdef' https://gitlab.com/api/v3/projects/${project_id}/keys curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -H "Content-Type: application/json" \
--data '{"title": "my key", "key": "ssh-rsa AAAA..."}' https://gitlab.example.com/api/v3/projects/${project_id}/keys
done done
``` ```
# Session # Session
Login to get private token You can login with both GitLab and LDAP credentials in order to obtain the
private token.
``` ```
POST /session POST /session
``` ```
Parameters: | Attribute | Type | Required | Description |
| ---------- | ------- | -------- | -------- |
| `login` | string | yes | The username of the user|
| `email` | string | yes if login is not provided | The email of the user |
| `password` | string | yes | The password of the user |
- `login` (required) - The login of user ```bash
- `email` (required if login missing) - The email of user curl -X POST "https://gitlab.example.com/api/v3/session?login=john_smith&password=strongpassw0rd"
- `password` (required) - Valid password ```
**You can login with both GitLab and LDAP credentials now**
Example response:
```json ```json
{ {
"id": 1,
"username": "john_smith",
"email": "john@example.com",
"name": "John Smith", "name": "John Smith",
"private_token": "dd34asd13as", "username": "john_smith",
"blocked": false, "id": 32,
"created_at": "2012-05-23T08:00:58Z", "state": "active",
"avatar_url": null,
"created_at": "2015-01-29T21:07:19.440Z",
"is_admin": true,
"bio": null, "bio": null,
"skype": "", "skype": "",
"linkedin": "", "linkedin": "",
"twitter": "", "twitter": "",
"website_url": "", "website_url": "",
"dark_scheme": false, "email": "john@example.com",
"theme_id": 1, "theme_id": 1,
"is_admin": false, "color_scheme_id": 1,
"projects_limit": 10,
"current_sign_in_at": "2015-07-07T07:10:58.392Z",
"identities": [],
"can_create_group": true, "can_create_group": true,
"can_create_team": true, "can_create_project": true,
"can_create_project": true "two_factor_enabled": false,
"private_token": "9koXpg98eAheJpvBs5tK"
} }
``` ```
...@@ -16,7 +16,7 @@ Here, `%{issue_ref}` is a complex regular expression defined inside GitLab, that ...@@ -16,7 +16,7 @@ Here, `%{issue_ref}` is a complex regular expression defined inside GitLab, that
For example: For example:
``` ```
git commit -m "Awesome commit message (Fix #20, Fixes #21 and Closes group/otherproject#2). This commit is also related to #17 and fixes #18, #19 and https://gitlab.example.com/group/otherproject/issues/23." git commit -m "Awesome commit message (Fix #20, Fixes #21 and Closes group/otherproject#22). This commit is also related to #17 and fixes #18, #19 and https://gitlab.example.com/group/otherproject/issues/23."
``` ```
will close `#18`, `#19`, `#20`, and `#21` in the project this commit is pushed to, as well as `#22` and `#23` in group/otherproject. `#17` won't be closed as it does not match the pattern. It also works with multiline commit messages. will close `#18`, `#19`, `#20`, and `#21` in the project this commit is pushed to, as well as `#22` and `#23` in group/otherproject. `#17` won't be closed as it does not match the pattern. It also works with multiline commit messages.
......
...@@ -47,7 +47,7 @@ module Gitlab ...@@ -47,7 +47,7 @@ module Gitlab
# new_path - new project path with namespace # new_path - new project path with namespace
# #
# Ex. # Ex.
# mv_repository("gitlab/gitlab-ci", "randx/gitlab-ci-new.git") # mv_repository("gitlab/gitlab-ci", "randx/gitlab-ci-new")
# #
def mv_repository(path, new_path) def mv_repository(path, new_path)
Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'mv-project', Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'mv-project',
......
module Gitlab module Gitlab
module Database module Database
def self.adapter_name
connection.adapter_name
end
def self.mysql? def self.mysql?
ActiveRecord::Base.connection.adapter_name.downcase == 'mysql2' adapter_name.downcase == 'mysql2'
end end
def self.postgresql? def self.postgresql?
ActiveRecord::Base.connection.adapter_name.downcase == 'postgresql' adapter_name.downcase == 'postgresql'
end
def self.version
database_version.match(/\A(?:PostgreSQL |)([^\s]+).*\z/)[1]
end end
def true_value def true_value
case ActiveRecord::Base.connection.adapter_name.downcase if self.class.postgresql?
when 'postgresql'
"'t'" "'t'"
else else
1 1
...@@ -18,12 +25,31 @@ module Gitlab ...@@ -18,12 +25,31 @@ module Gitlab
end end
def false_value def false_value
case ActiveRecord::Base.connection.adapter_name.downcase if self.class.postgresql?
when 'postgresql'
"'f'" "'f'"
else else
0 0
end end
end end
private
def self.connection
ActiveRecord::Base.connection
end
def self.database_version
row = connection.execute("SELECT VERSION()").first
if postgresql?
row['version']
else
row.first
end
end
def connection
self.class.connection
end
end end
end end
...@@ -34,12 +34,12 @@ module Gitlab ...@@ -34,12 +34,12 @@ module Gitlab
def project_path_regex def project_path_regex
@project_path_regex ||= /\A[a-zA-Z0-9_.][a-zA-Z0-9_\-\.]*(?<!\.git)\z/.freeze @project_path_regex ||= /\A[a-zA-Z0-9_.][a-zA-Z0-9_\-\.]*(?<!\.git|\.atom)\z/.freeze
end end
def project_path_regex_message def project_path_regex_message
"can contain only letters, digits, '_', '-' and '.'. " \ "can contain only letters, digits, '_', '-' and '.'. " \
"Cannot start with '-' or end in '.git'" \ "Cannot start with '-', end in '.git' or end in '.atom'" \
end end
......
...@@ -86,6 +86,14 @@ describe ProjectsController do ...@@ -86,6 +86,14 @@ describe ProjectsController do
end end
end end
end end
context "when the url contains .atom" do
let(:public_project_with_dot_atom) { build(:project, :public, name: 'my.atom', path: 'my.atom') }
it 'expect an error creating the project' do
expect(public_project_with_dot_atom).not_to be_valid
end
end
end end
describe "#destroy" do describe "#destroy" do
......
...@@ -3,7 +3,7 @@ require 'spec_helper' ...@@ -3,7 +3,7 @@ require 'spec_helper'
describe LabelsHelper do describe LabelsHelper do
describe 'link_to_label' do describe 'link_to_label' do
let(:project) { create(:empty_project) } let(:project) { create(:empty_project) }
let(:label) { create(:label, project: project) } let(:label) { create(:label, project: project) }
context 'with @project set' do context 'with @project set' do
before do before do
...@@ -11,34 +11,31 @@ describe LabelsHelper do ...@@ -11,34 +11,31 @@ describe LabelsHelper do
end end
it 'uses the instance variable' do it 'uses the instance variable' do
expect(label).not_to receive(:project) expect(link_to_label(label)).to match %r{<a href="/#{@project.to_reference}/issues\?label_name=#{label.name}">.*</a>}
link_to_label(label)
end end
end end
context 'without @project set' do context 'without @project set' do
it "uses the label's project" do it "uses the label's project" do
expect(label).to receive(:project).and_return(project) expect(link_to_label(label)).to match %r{<a href="/#{label.project.to_reference}/issues\?label_name=#{label.name}">.*</a>}
link_to_label(label)
end end
end end
context 'with a named project argument' do context 'with a project argument' do
it 'uses the provided project' do let(:another_project) { double('project', namespace: 'foo3', to_param: 'bar3') }
arg = double('project')
expect(arg).to receive(:namespace).and_return('foo')
expect(arg).to receive(:to_param).and_return('foo')
link_to_label(label, project: arg) it 'links to merge requests page' do
expect(link_to_label(label, project: another_project)).to match %r{<a href="/foo3/bar3/issues\?label_name=#{label.name}">.*</a>}
end end
end
it 'takes precedence over other types' do context 'with a type argument' do
@project = project ['issue', :issue, 'merge_request', :merge_request].each do |type|
expect(@project).not_to receive(:namespace) context "set to #{type}" do
expect(label).not_to receive(:project) it 'links to correct page' do
expect(link_to_label(label, type: type)).to match %r{<a href="/#{label.project.to_reference}/#{type.to_s.pluralize}\?label_name=#{label.name}">.*</a>}
arg = double('project', namespace: 'foo', to_param: 'foo') end
link_to_label(label, project: arg) end
end end
end end
......
...@@ -42,9 +42,9 @@ describe SearchHelper do ...@@ -42,9 +42,9 @@ describe SearchHelper do
expect(search_autocomplete_opts(project.name).size).to eq(1) expect(search_autocomplete_opts(project.name).size).to eq(1)
end end
it "includes the public group" do it "should not include the public group" do
group = create(:group) group = create(:group)
expect(search_autocomplete_opts(group.name).size).to eq(1) expect(search_autocomplete_opts(group.name).size).to eq(0)
end end
context "with a current project" do context "with a current project" do
......
...@@ -14,4 +14,24 @@ describe Gitlab::Database, lib: true do ...@@ -14,4 +14,24 @@ describe Gitlab::Database, lib: true do
it { is_expected.to satisfy { |val| val == true || val == false } } it { is_expected.to satisfy { |val| val == true || val == false } }
end end
describe '.version' do
context "on mysql" do
it "extracts the version number" do
allow(described_class).to receive(:database_version).
and_return("5.7.12-standard")
expect(described_class.version).to eq '5.7.12-standard'
end
end
context "on postgresql" do
it "extracts the version number" do
allow(described_class).to receive(:database_version).
and_return("PostgreSQL 9.4.4 on x86_64-apple-darwin14.3.0")
expect(described_class.version).to eq '9.4.4'
end
end
end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment