Commit e75300df authored by Valery Sizov's avatar Valery Sizov

Merge branch 'ce-upstream' into 'master'

CE upstream

Unresolved conflicts
```
both modified:   app/controllers/omniauth_callbacks_controller.rb
both modified:   app/views/shared/issuable/_sidebar.html.haml

```

See merge request !166
parents 97bc167b 23caf7b6
......@@ -2,26 +2,44 @@ Please view this file on the master branch, on stable branches it's out of date.
v 8.5.0 (unreleased)
- Ensure rake tasks that don't need a DB connection can be run without one
- Update New Relic gem to 3.14.1.311 (Stan Hu)
- Add "visibility" flag to GET /projects api endpoint
- Ignore binary files in code search to prevent Error 500 (Stan Hu)
- Render sanitized SVG images (Stan Hu)
- Support download access by PRIVATE-TOKEN header (Stan Hu)
- Upgrade gitlab_git to 7.2.23 to fix commit message mentions in first branch push
- New UI for pagination
- Don't prevent sign out when 2FA enforcement is enabled and user hasn't yet
set it up
- Fix diff comments loaded by AJAX to load comment with diff in discussion tab
- 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
- Display 404 error on group not found
- 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)
- Warn admin during OAuth of granting admin rights (Zeger-Jan van de Weg)
- Update the ExternalIssue regex pattern (Blake Hitchcock)
- Remember user's inline/side-by-side diff view preference in a cookie (Kirill Katsnelson)
- Optimized performance of finding issues to be closed by a merge request
- Revert "Add IP check against DNSBLs at account sign-up"
- 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/...". 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
- Support Akismet spam checking for creation of issues via API (Stan Hu)
- Improve UI consistency between projects and groups lists
- Add sort dropdown to dashboard projects page
- Fixed logo animation on Safari (Roman Rott)
- Hide remove source branch button when the MR is merged but new commits are pushed (Zeger-Jan van de Weg)
- In seach autocomplete show only groups and projects you are member of
- Fix: init.d script not working on OS X
v 8.4.4
- Update omniauth-saml gem to 1.4.2
v 8.4.3
- Increase lfs_objects size column to 8-byte integer to allow files larger
......@@ -30,6 +48,9 @@ v 8.4.3
- Fix highlighting in blame view
- Update sentry-raven gem to prevent "Not a git repository" console output
when running certain commands
- Add instrumentation to additional Gitlab::Git and Rugged methods for
performance monitoring
- Allow autosize textareas to also be manually resized
v 8.4.2
- Bump required gitlab-workhorse version to bring in a fix for missing
......@@ -177,6 +198,7 @@ v 8.3.0
- Handle and report SSL errors in Web hook test (Stan Hu)
- Bump Redis requirement to 2.8 for Sidekiq 4 (Stan Hu)
- Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera)
- WIP identifier on merge requests no longer requires trailing space
- Add rake tasks for git repository maintainance (Zeger-Jan van de Weg)
- Fix 500 error when update group member permission
- Fix: As an admin, cannot add oneself as a member to a group/project
......
This diff is collapsed.
......@@ -30,15 +30,16 @@ gem 'omniauth-github', '~> 1.1.1'
gem 'omniauth-gitlab', '~> 1.0.0'
gem 'omniauth-google-oauth2', '~> 0.2.0'
gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
gem 'omniauth-saml', '~> 1.4.0'
gem 'omniauth-saml', '~> 1.4.2'
gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-twitter', '~> 1.2.0'
gem 'omniauth_crowd', '~> 2.2.0'
gem 'gssapi', group: :kerberos
gem 'rack-oauth2', '~> 1.2.1'
# reCAPTCHA protection
# Spam and anti-bot protection
gem 'recaptcha', require: 'recaptcha/rails'
gem 'akismet', '~> 2.0'
# Two-factor authentication
gem 'devise-two-factor', '~> 2.0.0'
......@@ -50,7 +51,7 @@ gem "browser", '~> 1.0.0'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
gem "gitlab_git", '~> 7.2.23'
gem "gitlab_git", '~> 8.1'
# LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes
......@@ -309,9 +310,6 @@ group :production do
gem "gitlab_meta", '7.0'
end
gem "newrelic_rpm", '~> 3.9.4.245'
gem 'newrelic-grape'
gem 'octokit', '~> 3.8.0'
gem "mail_room", "~> 0.6.1"
......
......@@ -49,6 +49,7 @@ GEM
addressable (2.3.8)
after_commit_queue (1.3.0)
activerecord (>= 3.0)
akismet (2.0.0)
allocations (1.0.3)
annotate (2.6.10)
activerecord (>= 3.2, <= 4.3)
......@@ -378,7 +379,7 @@ GEM
gitlab-license (0.0.4)
gitlab_emoji (0.2.0)
gemojione (~> 2.1)
gitlab_git (7.2.24)
gitlab_git (8.1.0)
activesupport (~> 4.0)
charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0)
......@@ -502,10 +503,6 @@ GEM
net-ldap (0.12.1)
net-ssh (3.0.1)
netrc (0.11.0)
newrelic-grape (2.1.0)
grape
newrelic_rpm
newrelic_rpm (3.9.4.245)
nokogiri (1.6.7.2)
mini_portile2 (~> 2.0.0.rc2)
nprogress-rails (0.1.6.7)
......@@ -558,9 +555,9 @@ GEM
omniauth-oauth2 (1.3.1)
oauth2 (~> 1.0)
omniauth (~> 1.2)
omniauth-saml (1.4.1)
omniauth-saml (1.4.2)
omniauth (~> 1.1)
ruby-saml (~> 1.0.0)
ruby-saml (~> 1.1, >= 1.1.1)
omniauth-shibboleth (1.2.1)
omniauth (>= 1.0.0)
omniauth-twitter (1.2.1)
......@@ -716,7 +713,7 @@ GEM
ruby-fogbugz (0.2.1)
crack (~> 0.4)
ruby-progressbar (1.7.5)
ruby-saml (1.0.0)
ruby-saml (1.1.1)
nokogiri (>= 1.5.10)
uuid (~> 2.3)
ruby2ruby (2.2.0)
......@@ -908,6 +905,7 @@ DEPENDENCIES
acts-as-taggable-on (~> 3.4)
addressable (~> 2.3.8)
after_commit_queue
akismet (~> 2.0)
allocations (~> 1.0)
annotate (~> 2.6.0)
asana (~> 0.4.0)
......@@ -962,7 +960,7 @@ DEPENDENCIES
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-license (~> 0.0.4)
gitlab_emoji (~> 0.2.0)
gitlab_git (~> 7.2.23)
gitlab_git (~> 8.1)
gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.1.0)
......@@ -991,8 +989,6 @@ DEPENDENCIES
nested_form (~> 0.3.2)
net-ldap
net-ssh (~> 3.0.1)
newrelic-grape
newrelic_rpm (~> 3.9.4.245)
nokogiri (= 1.6.7.2)
nprogress-rails (~> 0.1.6.7)
oauth2 (~> 1.0.0)
......@@ -1006,7 +1002,7 @@ DEPENDENCIES
omniauth-gitlab (~> 1.0.0)
omniauth-google-oauth2 (~> 0.2.0)
omniauth-kerberos (~> 0.3.0)
omniauth-saml (~> 1.4.0)
omniauth-saml (~> 1.4.2)
omniauth-shibboleth (~> 1.2.0)
omniauth-twitter (~> 1.2.0)
omniauth_crowd (~> 2.2.0)
......
......@@ -11,7 +11,7 @@ class @AdminEmailSelect
group_result = Api.groups query.term, skip_ldap, (groups) ->
groups
project_result = Api.projects query.term, (projects) ->
project_result = Api.projects query.term, 'id', (projects) ->
projects
$.when(project_result, group_result).done (projects, groups) ->
......
......@@ -49,7 +49,7 @@
callback(namespaces)
# Return projects list. Filtered by query
projects: (query, callback) ->
projects: (query, order, callback) ->
url = Api.buildUrl(Api.projects_path)
$.ajax(
......@@ -57,6 +57,7 @@
data:
private_token: gon.api_token
search: query
order_by: order
per_page: 20
dataType: "json"
).done (projects) ->
......@@ -96,17 +97,3 @@
dataType: "json"
).done (groups) ->
callback(groups)
# Return projects list. Filtered by query
projects: (query, callback) ->
project_url = Api.buildUrl(Api.projects_path)
project_query = $.ajax(
url: project_url
data:
private_token: gon.api_token
search: query
per_page: 20
dataType: "json"
).done (projects) ->
callback(projects)
......@@ -215,8 +215,89 @@ $ ->
$this.attr 'value', $this.val()
return
$(document).on 'keyup', 'input[type="search"]' , (e) ->
$this = $(this)
$this.attr 'value', $this.val()
$(document)
.off 'keyup', 'input[type="search"]'
.on 'keyup', 'input[type="search"]' , (e) ->
$this = $(this)
$this.attr 'value', $this.val()
$(document)
.off 'breakpoint:change'
.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)
.off 'click', 'aside .gutter-toggle'
.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)
.off "resize"
.on "resize", (e) ->
fitSidebarForSize()
setBootstrapBreakpoints()
checkInitialSidebarSize()
new Aside()
class @Dashboard
constructor: ->
new ProjectsList()
@Dashboard =
init: ->
this.initSearch()
initSearch: ->
@timer = null
$("#project-filter-form-field").on('keyup', ->
clearTimeout(@timer)
@timer = setTimeout(Dashboard.filterResults, 500)
)
filterResults: =>
$('.projects-list-holder').fadeTo(250, 0.5)
form = null
form = $("#project-filter-form")
search = $("#project-filter-form-field").val()
project_filter_url = form.attr('action') + '?' + form.serialize()
$.ajax
type: "GET"
url: form.attr('action')
data: form.serialize()
complete: ->
$('.projects-list-holder').fadeTo(250, 1)
success: (data) ->
$('div.projects-list-holder').replaceWith(data.html)
# Change url so if user reload a page - search results are saved
history.replaceState {page: project_filter_url}, document.title, project_filter_url
dataType: "json"
......@@ -58,7 +58,7 @@ class Dispatcher
shortcut_handler = new ShortcutsNavigation()
MergeRequests.init()
when 'dashboard:show', 'root:show'
new Dashboard()
Dashboard.init()
when 'dashboard:activity'
new Activities()
when 'dashboard:projects:starred'
......
......@@ -65,8 +65,7 @@ class @DropzoneInput
return
success: (header, response) ->
child = $(dropzone[0]).children("textarea")
$(child).val $(child).val() + response.link.markdown + "\n"
pasteText response.link.markdown
return
error: (temp, errorMessage) ->
......@@ -128,6 +127,7 @@ class @DropzoneInput
beforeSelection = $(child).val().substring 0, caretStart
afterSelection = $(child).val().substring caretEnd, textEnd
$(child).val beforeSelection + text + afterSelection
child.get(0).setSelectionRange caretStart + text.length, caretEnd + text.length
form_textarea.trigger "input"
getFilename = (e) ->
......
......@@ -10,19 +10,7 @@ class @IssuableContext
$(".issuable-sidebar .inline-update").on "change", ".js-assignee", ->
$(this).submit()
$('.issuable-details').waitForImages ->
$('.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) ->
$(document).on "click",".edit-link", (e) ->
block = $(@).parents('.block')
block.find('.selectbox').show()
block.find('.value').hide()
......
......@@ -42,3 +42,8 @@ work = ->
$(document).on('page:fetch', start)
$(document).on('page:change', stop)
$ ->
# Make logo clickable
$('#logo').on 'click', ->
$('#js-shortcuts-home').get(0).click()
......@@ -50,3 +50,19 @@ class @Project
$('#notifications-button').empty().append("<i class='fa fa-bell'></i>" + label + "<i class='fa fa-angle-down'></i>")
$(@).parents('ul').find('li.active').removeClass 'active'
$(@).parent().addClass 'active'
@projectSelectDropdown()
projectSelectDropdown: ->
new ProjectSelect()
$('.project-item-select').on 'click', (e) =>
@changeProject $(e.currentTarget).val()
$('.js-projects-dropdown-toggle').on 'click', (e) ->
e.preventDefault()
$('.js-projects-dropdown').select2('open')
changeProject: (url) ->
window.location = url
......@@ -3,6 +3,7 @@ class @ProjectSelect
$('.ajax-project-select').each (i, select) ->
@groupId = $(select).data('group-id')
@includeGroups = $(select).data('include-groups')
@orderBy = $(select).data('order-by') || 'id'
placeholder = "Search for project"
placeholder += " or group" if @includeGroups
......@@ -28,7 +29,7 @@ class @ProjectSelect
if @groupId
Api.groupProjects @groupId, query.term, projectsCallback
else
Api.projects query.term, projectsCallback
Api.projects query.term, @orderBy, projectsCallback
id: (project) ->
project.web_url
......
......@@ -22,5 +22,3 @@ class @ProjectsList
else
$(this).show()
uiBox.find("ul.projects-list li.bottom").hide()
......@@ -16,12 +16,31 @@ class @ShortcutsIssuable extends ShortcutsNavigation
@replyWithSelectedText()
return false
)
Mousetrap.bind('j', =>
@prevIssue()
return false
)
Mousetrap.bind('k', =>
@nextIssue()
return false
)
if isMergeRequest
@enabledHelp.push('.hidden-shortcut.merge_requests')
else
@enabledHelp.push('.hidden-shortcut.issues')
prevIssue: ->
$prevBtn = $('.prev-btn')
if not $prevBtn.hasClass('disabled')
Turbolinks.visit($prevBtn.attr('href'))
nextIssue: ->
$nextBtn = $('.next-btn')
if not $nextBtn.hasClass('disabled')
Turbolinks.visit($nextBtn.attr('href'))
replyWithSelectedText: ->
if window.getSelection
selected = window.getSelection().toString()
......
......@@ -24,6 +24,7 @@
&.s26 { width: 26px; height: 26px; margin-right: 8px; }
&.s32 { width: 32px; height: 32px; 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; }
&.s48 { width: 48px; height: 48px; margin-right: 10px; }
&.s60 { width: 60px; height: 60px; margin-right: 12px; }
......@@ -40,7 +41,8 @@
&.s16 { font-size: 12px; line-height: 1.33; }
&.s24 { font-size: 14px; line-height: 1.8; }
&.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; }
&.s90 { font-size: 36px; line-height: 90px; }
&.s110 { font-size: 40px; line-height: 112px; font-weight: 300; }
......
......@@ -2,7 +2,7 @@
@include border-radius(3px);
font-size: $gl-font-size;
font-weight: 500;
padding: $gl-vert-padding $gl-padding;
padding: $gl-vert-padding $gl-btn-padding;
&:focus,
&:active {
......@@ -82,8 +82,7 @@
&.btn-success,
&.btn-new,
&.btn-create,
&.btn-save,
&.btn-green {
&.btn-save {
@include btn-green;
}
......@@ -159,7 +158,6 @@
.input-group-btn {
.btn {
@include btn-gray;
@include btn-middle;
&:hover {
......@@ -186,8 +184,4 @@
border: 1px solid #c6cacf !important;
background-color: #e4e7ed !important;
}
.btn-green {
@include btn-green
}
}
......@@ -385,11 +385,11 @@ table {
margin-bottom: $gl-padding;
}
.new-project-item-select-holder {
.project-item-select-holder {
display: inline-block;
position: relative;
.new-project-item-select {
.project-item-select {
position: absolute;
top: 0;
right: 0;
......
......@@ -73,7 +73,6 @@ header {
.title {
margin: 0;
overflow: hidden;
font-size: 19px;
line-height: $header-height;
font-weight: normal;
......@@ -88,6 +87,22 @@ header {
text-decoration: underline;
}
}
.dropdown-toggle-caret {
position: relative;
top: -2px;
width: 12px;
line-height: 12px;
margin-left: 5px;
font-size: 10px;
text-align: center;
cursor: pointer;
}
.project-item-select {
right: auto;
left: 0;
}
}
.navbar-collapse {
......
......@@ -9,7 +9,7 @@
display: block;
float: left;
padding: 0 $gl-padding;
padding: 0 $gl-btn-padding;
font-weight: normal;
margin-right: 10px;
font-size: $gl-font-size;
......
......@@ -109,7 +109,6 @@ ul.content-list {
padding: 0;
> li {
padding: $gl-padding 0;
border-color: $table-border-color;
color: $gl-gray;
......
......@@ -116,7 +116,7 @@
display: none;
}
aside {
aside:not(.right-sidebar){
display: none;
}
......
......@@ -37,3 +37,93 @@
}
}
}
.top-area {
@include clearfix;
border-bottom: 1px solid #EEE;
.nav-text {
padding-top: 16px;
padding-bottom: 11px;
display: inline-block;
width: 50%;
line-height: 28px;
/* Small devices (phones, tablets, 768px and lower) */
@media (max-width: $screen-sm-min) {
width: 100%;
}
}
.nav-links {
display: inline-block;
width: 50%;
margin-bottom: 0px;
border-bottom: none;
/* Small devices (phones, tablets, 768px and lower) */
@media (max-width: $screen-sm-min) {
width: 100%;
}
}
.nav-controls {
width: 50%;
display: inline-block;
float: right;
text-align: right;
padding: 11px 0;
margin-bottom: 0px;
> .dropdown {
margin-right: 10px;
display: inline-block;
}
> .btn {
display: inline-block;
}
> form {
display: inline-block;
}
input {
height: 34px;
display: inline-block;
position: relative;
top: 1px;
margin-right: 10px;
/* Medium devices (desktops, 992px and up) */
@media (min-width: $screen-md-min) { width: 200px; }
/* Large devices (large desktops, 1200px and up) */
@media (min-width: $screen-lg-min) { width: 250px; }
&.input-short {
/* Medium devices (desktops, 992px and up) */
@media (min-width: $screen-md-min) { width: 170px; }
/* Large devices (large desktops, 1200px and up) */
@media (min-width: $screen-lg-min) { width: 210px; }
}
}
/* Hide on extra small devices (phones) */
@media (max-width: $screen-xs-max) {
display: none;
}
/* Small devices (tablets, 768px and lower) */
@media (max-width: $screen-sm-max) {
width: 100%;
text-align: left;
input {
width: 300px;
}
}
}
}
......@@ -45,6 +45,19 @@
overflow: hidden;
transition-duration: .3s;
.home {
z-index: 1;
position: absolute;
left: 0px;
}
#logo {
z-index: 2;
position: absolute;
width: 58px;
cursor: pointer;
}
a {
float: left;
height: $header-height;
......@@ -70,7 +83,7 @@
width: 158px;
float: left;
margin: 0;
margin-left: 14px;
margin-left: 50px;
font-size: 19px;
line-height: 41px;
font-weight: normal;
......@@ -200,6 +213,14 @@
}
}
@mixin expanded-gutter {
padding-right: $gutter_width;
}
@mixin collapsed-gutter {
padding-right: $sidebar_collapsed_width;
}
@mixin collapsed-sidebar {
padding-left: $sidebar_collapsed_width;
......@@ -266,6 +287,7 @@
background: #f2f6f7;
}
// page is small enough
@media (max-width: $screen-md-max) {
.page-sidebar-collapsed {
@include collapsed-sidebar;
......@@ -275,12 +297,32 @@
@include collapsed-sidebar;
}
.page-gutter {
&.right-sidebar-collapsed {
@include collapsed-gutter;
}
&.right-sidebar-expanded {
@include expanded-gutter;
}
}
.collapse-nav {
display: none;
}
}
// page is large enough
@media(min-width: $screen-md-max) {
.page-gutter {
&.right-sidebar-collapsed {
@include collapsed-gutter;
}
&.right-sidebar-expanded {
@include expanded-gutter;
}
}
.page-sidebar-collapsed {
@include collapsed-sidebar;
}
......@@ -288,4 +330,4 @@
.page-sidebar-expanded {
@include expanded-sidebar;
}
}
}
\ No newline at end of file
......@@ -12,6 +12,9 @@ $gl-font-size: 15px;
$list-font-size: 15px;
$sidebar_collapsed_width: 62px;
$sidebar_width: 230px;
$gutter_collapsed_width: 62px;
$gutter_width: 290px;
$gutter_inner_width: 258px;
$avatar_radius: 50%;
$code_font_size: 13px;
$code_line_height: 1.5;
......@@ -22,9 +25,10 @@ $header-height: 58px;
$fixed-layout-width: 1280px;
$gl-gray: #5a5a5a;
$gl-padding: 16px;
$gl-btn-padding: 10px;
$gl-vert-padding: 6px;
$gl-padding-top:10px;
$gl-avatar-size: 46px;
$gl-avatar-size: 40px;
$secondary-text: #7f8fa4;
$error-exclamation-point: #E62958;
......@@ -36,11 +40,12 @@ $white-light: #FFFFFF;
$white-normal: #ededed;
$white-dark: #ededed;
$gray-light: #f7f7f7;
$gray-normal: #ededed;
$gray-light: #faf9f9;
$gray-normal: #f5f5f5;
$gray-dark: #ededed;
$gray-darkest: #c9c9c9;
$green-light: #31AF64;
$green-light: #38ae67;
$green-normal: #2FAA60;
$green-dark: #2CA05B;
......@@ -52,7 +57,7 @@ $blue-medium-light: #3498CB;
$blue-medium: #2F8EBF;
$blue-medium-dark: #2D86B4;
$orange-light: #FC6443;
$orange-light: rgba(252, 109, 38, 0.80);
$orange-normal: #E75E40;
$orange-dark: #CE5237;
......@@ -64,8 +69,8 @@ $border-white-light: #F1F2F4;
$border-white-normal: #D6DAE2;
$border-white-dark: #C6CACF;
$border-gray-light: #d1d1d1;
$border-gray-normal: #D6DAE2;
$border-gray-light: rgba(0, 0, 0, 0.06);
$border-gray-normal: rgba(0, 0, 0, 0.10);;
$border-gray-dark: #C6CACF;
$border-green-light: #2FAA60;
......@@ -76,7 +81,7 @@ $border-blue-light: #2D9FD8;
$border-blue-normal: #2897CE;
$border-blue-dark: #258DC1;
$border-orange-light: #ED5C3D;
$border-orange-light: #fc6d26;
$border-orange-normal: #CE5237;
$border-orange-dark: #C14E35;
......
......@@ -40,10 +40,6 @@
.avatar {
@include border-radius(50%);
}
.identicon {
line-height: 46px;
}
}
.dash-project-access-icon {
......
......@@ -4,7 +4,7 @@
*/
.event-item {
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;
color: #7f8fa4;
......@@ -16,7 +16,7 @@
.event-title,
.event-item-timestamp {
line-height: 44px;
line-height: 40px;
}
}
......@@ -25,7 +25,7 @@
}
.avatar {
margin-left: -($gl-avatar-size + 15px);
margin-left: -($gl-avatar-size + $gl-padding-top);
}
.event-title {
......@@ -41,7 +41,6 @@
margin-right: 174px;
.event-note {
margin-top: 5px;
word-wrap: break-word;
.md {
......@@ -98,8 +97,6 @@
&:last-child { border:none }
.event_commits {
margin-top: 9px;
li {
&.commit {
background: transparent;
......
......@@ -6,11 +6,3 @@
font-size: 30px;
}
}
.explore-trending-block {
.lead {
line-height: 32px;
font-size: 18px;
margin-top: 10px;
}
}
......@@ -4,7 +4,7 @@
input[type='search'] {
width: 225px;
vertical-align: bottom;
@media (max-width: $screen-xs-max) {
width: 100px;
vertical-align: bottom;
......@@ -26,3 +26,21 @@
font-weight: 600;
color: #4c4e54;
}
.group-row {
&.no-description {
.group-name {
line-height: 44px;
}
}
.stats {
float: right;
line-height: 44px;
color: $gl-gray;
span {
margin-right: 15px;
}
}
}
......@@ -29,21 +29,8 @@
}
}
.project-issuable-filter {
.controls {
float: right;
margin-top: 11px;
}
.nav-links {
text-align: left;
}
}
.issuable-details {
section {
border-right: 1px solid $border-white-light;
.issuable-discussion {
margin-right: 1px;
}
......@@ -73,11 +60,35 @@
.block {
@include clearfix;
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 {
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 {
......@@ -133,3 +144,98 @@
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;
}
}
}
.detail-page-description {
small {
color: $gray-darkest;
}
}
\ No newline at end of file
......@@ -65,10 +65,6 @@ form.edit-issue {
width: 3em;
}
.merge-request-info {
padding-left: 5px;
}
.merge-request-status {
color: $gl-gray;
font-size: 15px;
......@@ -143,4 +139,4 @@ form.edit-issue {
.issue-closed-by-widget {
color: $secondary-text;
margin-left: 52px;
}
\ No newline at end of file
}
......@@ -281,36 +281,6 @@
margin-top: -1px;
}
.top-area {
border-bottom: 1px solid #EEE;
ul.nav-links {
display: inline-block;
width: 50%;
margin-bottom: 0px;
border-bottom: none;
}
.projects-search-form {
width: 50%;
display: inline-block;
float: right;
padding-top: 11px;
text-align: right;
.btn-green {
margin-left: 10px;
float: right;
}
}
@media (max-width: $screen-xs-max) {
.projects-search-form {
padding-top: 15px;
}
}
}
.fork-namespaces {
.fork-thumbnail {
text-align: center;
......@@ -386,22 +356,6 @@ pre.light-well {
border-color: #f1f1f1;
}
.projects-search-form {
padding: $gl-padding 0;
padding-bottom: 0;
margin-bottom: 0px;
input {
display: inline-block;
width: calc(100% - 151px);
}
.btn {
display: inline-block;
width: 135px;
}
}
.git-empty {
margin: 0 7px 0 7px;
......@@ -437,12 +391,11 @@ pre.light-well {
@include basic-list;
.project-row {
padding: $gl-padding 0;
border-color: $table-border-color;
&.no-description {
.project {
line-height: 44px;
line-height: 40px;
}
}
......@@ -455,12 +408,16 @@ pre.light-well {
.project-controls {
float: right;
color: $gl-gray;
line-height: 45px;
line-height: 40px;
color: #7f8fa4;
a:hover {
text-decoration: none;
}
> span {
margin-left: 10px;
}
}
.project-description {
......@@ -559,52 +516,12 @@ pre.light-well {
}
}
.cannot-be-merged,
.cannot-be-merged,
.cannot-be-merged:hover {
color: #E62958;
margin-top: 2px;
}
/*
* Forks list rendered on Project's forks page
*/
.forks-top-block {
padding: 16px 0;
}
.projects-search-form {
.dropdown-toggle.btn {
margin-top: -3px;
}
&.fork-search-form {
margin: 0;
margin-top: -$gl-padding;
padding-bottom: 0;
input {
/* Small devices (tablets, 768px and up) */
@media (min-width: $screen-sm-min) { width: 180px; }
/* Medium devices (desktops, 992px and up) */
@media (min-width: $screen-md-min) { width: 350px; }
/* Large devices (large desktops, 1200px and up) */
@media (min-width: $screen-lg-min) { width: 400px; }
}
.sort-forks {
width: 160px;
}
.fork-link {
float: right;
margin-left: $gl-padding;
}
}
}
.private-forks-notice .private-fork-icon {
i:nth-child(1) {
color: #2AA056;
......
......@@ -81,6 +81,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:recaptcha_private_key,
:sentry_enabled,
:sentry_dsn,
:akismet_enabled,
:akismet_api_key,
restricted_visibility_levels: [],
import_sources: []
)
......
......@@ -2,7 +2,7 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController
before_action :finder, only: [:edit, :update, :destroy]
def index
@broadcast_messages = BroadcastMessage.reorder("starts_at ASC").page(params[:page])
@broadcast_messages = BroadcastMessage.reorder("ends_at DESC").page(params[:page])
@broadcast_message = BroadcastMessage.new
end
......
class Admin::SpamLogsController < Admin::ApplicationController
def index
@spam_logs = SpamLog.order(id: :desc).page(params[:page])
end
def destroy
spam_log = SpamLog.find(params[:id])
if params[:remove_user]
spam_log.remove_user
redirect_to admin_spam_logs_path, notice: "User #{spam_log.user.username} was successfully removed."
else
spam_log.destroy
render nothing: true
end
end
end
......@@ -60,6 +60,8 @@ class ApplicationController < ActionController::Base
params[:authenticity_token].presence
elsif params[:private_token].presence
params[:private_token].presence
elsif request.headers['PRIVATE-TOKEN'].present?
request.headers['PRIVATE-TOKEN']
end
user = user_token && User.find_by_authentication_token(user_token.to_s)
......@@ -279,9 +281,10 @@ class ApplicationController < ActionController::Base
}
end
def view_to_html_string(partial)
def view_to_html_string(partial, locals = {})
render_to_string(
partial,
locals: locals,
layout: false,
formats: [:html]
)
......
......@@ -3,7 +3,16 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
def index
@projects = current_user.authorized_projects.sorted_by_activity.non_archived
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.includes(:namespace)
terms = params['filter_projects']
if terms.present?
@projects = @projects.search(terms)
end
@projects = @projects.page(params[:page]).per(PER_PAGE)
@last_push = current_user.recent_push
respond_to do |format|
......@@ -13,6 +22,11 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
load_events
render layout: false
end
format.json do
render json: {
html: view_to_html_string("dashboard/projects/_projects", locals: { projects: @projects })
}
end
end
end
......@@ -20,6 +34,14 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
@projects = current_user.starred_projects
@projects = @projects.includes(:namespace, :forked_from_project, :tags)
@projects = @projects.sort(@sort = params[:sort])
terms = params['filter_projects']
if terms.present?
@projects = @projects.search(terms)
end
@projects = @projects.page(params[:page]).per(PER_PAGE)
@last_push = current_user.recent_push
@groups = []
......@@ -27,8 +49,9 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
format.html
format.json do
load_events
pager_json("events/_events", @events.count)
render json: {
html: view_to_html_string("dashboard/projects/_projects", locals: { projects: @projects })
}
end
end
end
......
......@@ -11,14 +11,14 @@ class Explore::ProjectsController < Explore::ApplicationController
end
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)
@projects = TrendingProjectsFinder.new.execute(current_user)
@projects = @projects.non_archived
@projects = @projects.page(params[:page]).per(PER_PAGE)
end
def starred
@starred_projects = ProjectsFinder.new.execute(current_user)
@starred_projects = @starred_projects.reorder('star_count DESC')
@starred_projects = @starred_projects.page(params[:page]).per(PER_PAGE)
@projects = ProjectsFinder.new.execute(current_user)
@projects = @projects.reorder('star_count DESC')
@projects = @projects.page(params[:page]).per(PER_PAGE)
end
end
......@@ -41,6 +41,7 @@ class GroupsController < Groups::ApplicationController
def show
@last_push = current_user.recent_push if current_user
@projects = @projects.includes(:namespace)
@projects = @projects.page(params[:page]).per(PER_PAGE)
@shared_projects = @group.shared_projects
......
......@@ -30,7 +30,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
# Do additional LDAP checks for the user filter and EE features
if ldap_user.allowed?
if @user.otp_required_for_login?
if @user.two_factor_enabled?
prompt_for_two_factor(@user)
else
log_audit_event(@user, with: :ldap)
......
......@@ -28,6 +28,11 @@ class Projects::ApplicationController < ApplicationController
private
def apply_diff_view_cookie!
view = params[:view] || cookies[:diff_view]
cookies.permanent[:diff_view] = params[:view] = view if view
end
def builds_enabled
return render_404 unless @project.builds_enabled?
end
......
......@@ -2,15 +2,13 @@ class Projects::AvatarsController < Projects::ApplicationController
before_action :project
def show
@blob = @project.repository.blob_at_branch('master', @project.avatar_in_git)
@blob = @repository.blob_at_branch('master', @project.avatar_in_git)
if @blob
headers['X-Content-Type-Options'] = 'nosniff'
send_data(
@blob.data,
type: @blob.mime_type,
disposition: 'inline',
filename: @blob.name
)
headers.store(*Gitlab::Workhorse.send_git_blob(@repository, @blob))
headers['Content-Disposition'] = 'inline'
headers['Content-Type'] = @blob.content_type
head :ok # 'render nothing: true' messes up the Content-Type
else
render_404
end
......
......@@ -33,6 +33,7 @@ class Projects::BlobController < Projects::ApplicationController
def edit
@last_commit = Gitlab::Git::Commit.last_for_path(@repository, @ref, @path).sha
blob.load_all_data!(@repository)
end
def update
......@@ -51,6 +52,7 @@ class Projects::BlobController < Projects::ApplicationController
def preview
@content = params[:content]
@blob.load_all_data!(@repository)
diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3', include_diff_info: true)
diff_lines = diffy.diff.scan(/.*\n/)[2..-1]
diff_lines = Gitlab::Diff::Parser.new.parse(diff_lines)
......
......@@ -13,6 +13,8 @@ class Projects::CommitController < Projects::ApplicationController
def show
return git_not_found! unless @commit
apply_diff_view_cookie!
@line_notes = commit.notes.inline
@note = @project.build_commit_note(commit)
@notes = commit.notes.not_inline.fresh
......
......@@ -57,6 +57,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def diffs
apply_diff_view_cookie!
@commit = @merge_request.last_commit
@base_commit = @merge_request.diff_base_commit
......
......@@ -15,7 +15,10 @@ class Projects::RawController < Projects::ApplicationController
if @blob.lfs_pointer?
send_lfs_object
else
stream_data
headers.store(*Gitlab::Workhorse.send_git_blob(@repository, @blob))
headers['Content-Disposition'] = 'inline'
headers['Content-Type'] = get_blob_type
head :ok # 'render nothing: true' messes up the Content-Type
end
else
render_404
......@@ -34,16 +37,6 @@ class Projects::RawController < Projects::ApplicationController
end
end
def stream_data
type = get_blob_type
send_data(
@blob.data,
type: type,
disposition: 'inline'
)
end
def send_lfs_object
lfs_object = find_lfs_object
......
......@@ -4,8 +4,9 @@ class UsersController < ApplicationController
def show
@contributed_projects = contributed_projects.joined(@user).reject(&:forked?)
@projects = PersonalProjectsFinder.new(@user).execute(current_user)
@projects = @projects.page(params[:page]).per(PER_PAGE)
@groups = @user.groups.order_id_desc
......
......@@ -172,18 +172,6 @@ module ApplicationHelper
Gitlab.config.extra
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
#
# time - Time object
......@@ -296,6 +284,76 @@ module ApplicationHelper
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)
titles = {
opened: "Open"
......
......@@ -27,6 +27,10 @@ module ApplicationSettingsHelper
current_application_settings.user_oauth_applications
end
def askimet_enabled?
current_application_settings.akismet_enabled?
end
# Return a group of checkboxes that use Bootstrap's button plugin for a
# toggle button effect.
def restricted_level_checkboxes(help_block_id)
......
......@@ -10,8 +10,19 @@ module ExploreHelper
options = exist_opts.merge(options)
path = explore_projects_path
path = if explore_controller?
explore_projects_path
elsif current_action?(:starred)
starred_dashboard_projects_path
else
dashboard_projects_path
end
path << "?#{options.to_param}"
path
end
def explore_controller?
controller.class.name.split("::").first == "Explore"
end
end
......@@ -7,6 +7,8 @@ module LabelsHelper
# 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
# 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
# body of the link element. If omitted, defaults to
# `render_colored_label`.
......@@ -23,14 +25,19 @@ module LabelsHelper
# # Force the generated link to use a provided project
# 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
# link_to_label(label) { "My Custom Label Text" }
#
# 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
link = namespace_project_issues_path(project.namespace, project,
label_name: label.name)
link = send("namespace_project_#{type.to_s.pluralize}_path",
project.namespace,
project,
label_name: label.name)
if block_given?
link_to link, &block
......
......@@ -3,6 +3,18 @@ module NavHelper
cookies[:collapsed_nav] == 'true'
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
if nav_menu_collapsed?
"sidebar-collapsed"
......@@ -19,6 +31,19 @@ module NavHelper
end
end
def page_gutter_class
if current_path?('merge_requests#show') ||
current_path?('merge_requests#diffs') ||
current_path?('merge_requests#commits') ||
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
if nav_menu_collapsed?
"header-collapsed"
......
......@@ -20,6 +20,12 @@ module ProjectsHelper
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 = {})
default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name" }
opts = default_opts.merge(opts)
......@@ -53,14 +59,23 @@ module ProjectsHelper
link_to(simple_sanitize(owner.name), user_path(owner))
end
project_link = link_to(simple_sanitize(project.name), project_path(project))
project_link = link_to project_path(project), { class: "project-item-select-holder" } do
link_output = simple_sanitize(project.name)
if current_user
link_output += project_select_tag :project_path,
class: "project-item-select js-projects-dropdown",
data: { include_groups: false, order_by: 'last_activity_at' }
end
link_output
end
project_link += icon "chevron-down", class: "dropdown-toggle-caret js-projects-dropdown-toggle" if current_user
full_title = namespace_link + ' / ' + project_link
full_title += ' &middot; '.html_safe + link_to(simple_sanitize(name), url) if name
content_tag :span do
full_title
end
full_title
end
def remove_project_message(project)
......
......@@ -70,7 +70,7 @@ module SearchHelper
# Autocomplete results for the current user's groups
def groups_autocomplete(term, limit = 5)
Group.search(term).limit(limit).map do |group|
current_user.authorized_groups.search(term).limit(limit).map do |group|
{
label: "group: #{search_result_sanitize(group.name)}",
url: group_path(group)
......@@ -80,7 +80,7 @@ module SearchHelper
# Autocomplete results for the current user's projects
def projects_autocomplete(term, limit = 5)
ProjectsFinder.new.execute(current_user).search_by_title(term).
current_user.authorized_projects.search_by_title(term).
sorted_by_stars.non_archived.limit(limit).map do |p|
{
label: "project: #{search_result_sanitize(p.name_with_namespace)}",
......
......@@ -10,7 +10,7 @@ class EmailRejectionMailer < BaseMailer
subject: "[Rejected] #{@original_message.subject}"
}
headers['Message-ID'] = SecureRandom.hex
headers['Message-ID'] = "<#{SecureRandom.hex}@#{Gitlab.config.gitlab.host}>"
headers['In-Reply-To'] = @original_message.message_id
headers['References'] = @original_message.message_id
......
......@@ -89,6 +89,10 @@ class ApplicationSetting < ActiveRecord::Base
presence: true,
if: :sentry_enabled
validates :akismet_api_key,
presence: true,
if: :akismet_enabled
validates_each :restricted_visibility_levels do |record, attr, value|
unless value.nil?
value.each do |level|
......@@ -144,7 +148,9 @@ class ApplicationSetting < ActiveRecord::Base
shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
max_artifacts_size: Settings.artifacts['max_size'],
require_two_factor_authentication: false,
two_factor_grace_period: 48
two_factor_grace_period: 48,
recaptcha_enabled: false,
akismet_enabled: false
)
end
......
......@@ -205,7 +205,11 @@ module Ci
end
def ci_yaml_file
@ci_yaml_file ||= project.repository.blob_at(sha, '.gitlab-ci.yml').data
@ci_yaml_file ||= begin
blob = project.repository.blob_at(sha, '.gitlab-ci.yml')
blob.load_all_data!(project.repository)
blob.data
end
rescue
nil
end
......
......@@ -49,7 +49,7 @@ class Event < ActiveRecord::Base
scope :code_push, -> { where(action: PUSHED) }
scope :in_projects, ->(projects) do
where(project_id: projects.select(:id).reorder(nil)).recent
where(project_id: projects.map(&:id)).recent
end
scope :with_associations, -> { includes(project: :namespace) }
......
......@@ -262,7 +262,7 @@ class MergeRequest < ActiveRecord::Base
end
def work_in_progress?
!!(title =~ /\A\[?WIP\]?:? /i)
!!(title =~ /\A\[?WIP(\]|:| )/i)
end
def mergeable?
......@@ -288,7 +288,8 @@ class MergeRequest < ActiveRecord::Base
def can_remove_source_branch?(current_user)
!source_project.protected_branch?(source_branch) &&
!source_project.root_ref?(source_branch) &&
Ability.abilities.allowed?(current_user, :push_code, source_project)
Ability.abilities.allowed?(current_user, :push_code, source_project) &&
last_commit == source_project.commit(source_branch)
end
def mr_and_commit_notes
......
......@@ -208,8 +208,11 @@ class Repository
cache.fetch(:"diverging_commit_counts_#{branch.name}") do
# Rugged seems to throw a `ReferenceError` when given branch_names rather
# than SHA-1 hashes
number_commits_behind = commits_between(branch.target, root_ref_hash).size
number_commits_ahead = commits_between(root_ref_hash, branch.target).size
number_commits_behind = raw_repository.
count_commits_between(branch.target, root_ref_hash)
number_commits_ahead = raw_repository.
count_commits_between(root_ref_hash, branch.target)
{ behind: number_commits_behind, ahead: number_commits_ahead }
end
......
class SpamLog < ActiveRecord::Base
belongs_to :user
validates :user, presence: true
def remove_user
user.block
user.destroy
end
end
class SpamReport < ActiveRecord::Base
belongs_to :user
validates :user, presence: true
end
......@@ -39,6 +39,8 @@ class Tree
git_repo = repository.raw_repository
@readme = Gitlab::Git::Blob.find(git_repo, sha, readme_path)
@readme.load_all_data!(git_repo)
@readme
end
def trees
......
......@@ -140,6 +140,7 @@ class User < ActiveRecord::Base
has_many :approvals, dependent: :destroy
has_many :approvers, dependent: :destroy
has_one :abuse_report, dependent: :destroy
has_many :spam_logs, dependent: :destroy
has_many :builds, dependent: :nullify, class_name: 'Ci::Build'
......
class CreateSpamLogService < BaseService
def initialize(project, user, params)
super(project, user, params)
end
def execute
spam_params = params.merge({ user_id: @current_user.id,
project_id: @project.id } )
spam_log = SpamLog.new(spam_params)
spam_log.save
spam_log
end
end
......@@ -231,20 +231,37 @@
= f.label :recaptcha_enabled do
= f.check_box :recaptcha_enabled
Enable reCAPTCHA
%span.help-block#recaptcha_help_block Helps preventing bots from creating accounts
%span.help-block#recaptcha_help_block Helps prevent bots from creating accounts
.form-group
= f.label :recaptcha_site_key, 'reCAPTCHA Site Key', class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :recaptcha_site_key, class: 'form-control'
.help-block
Generate site and private keys here:
%a{ href: 'http://www.google.com/recaptcha', target: '_blank'} http://www.google.com/recaptcha
Generate site and private keys at
%a{ href: 'http://www.google.com/recaptcha', target: 'blank'} http://www.google.com/recaptcha
.form-group
= f.label :recaptcha_private_key, 'reCAPTCHA Private Key', class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :recaptcha_private_key, class: 'form-control'
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :akismet_enabled do
= f.check_box :akismet_enabled
Enable Akismet
%span.help-block#akismet_help_block Helps prevent bots from creating issues
.form-group
= f.label :akismet_api_key, 'Akismet API Key', class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :akismet_api_key, class: 'form-control'
.help-block
Generate API key at
%a{ href: 'http://www.akismet.com', target: 'blank'} http://www.akismet.com
%fieldset
%legend Error Reporting and Logging
%p
......
......@@ -34,4 +34,4 @@
= link_to icon('pencil-square-o'), edit_admin_broadcast_message_path(message), title: 'Edit', class: 'btn btn-xs'
= link_to icon('times'), admin_broadcast_message_path(message), method: :delete, remote: true, title: 'Remove', class: 'js-remove-tr btn btn-xs btn-danger'
= paginate @broadcast_messages
= paginate @broadcast_messages, theme: 'gitlab'
.project-issuable-filter
.controls
.pull-left.hidden-xs
- if @all_builds.running_or_pending.any?
= link_to 'Cancel all', cancel_all_admin_builds_path, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
.top-area
%ul.nav-links
%li{class: ('active' if @scope.nil?)}
= link_to admin_builds_path do
......@@ -20,7 +15,11 @@
Finished
%span.badge.js-running-count= number_with_delimiter(@all_builds.finished.count(:id))
.gray-content-block
.nav-controls
- if @all_builds.running_or_pending.any?
= link_to 'Cancel all', cancel_all_admin_builds_path, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
.gray-content-block.second-block
#{(@scope || 'running').capitalize} builds
%ul.content-list
......
......@@ -92,6 +92,11 @@
Rails
%span.pull-right
#{Rails::VERSION::STRING}
%p
= Gitlab::Database.adapter_name
%span.pull-right
= Gitlab::Database.version
%hr
.row
.col-sm-4
......
- user = spam_log.user
%tr
%td
= time_ago_with_tooltip(spam_log.created_at)
%td
- if user
= link_to user.name, [:admin, user]
.light.small
Joined #{time_ago_with_tooltip(user.created_at)}
- else
(removed)
%td
= spam_log.source_ip
%td
= spam_log.via_api? ? 'Y' : 'N'
%td
= spam_log.noteable_type
%td
= spam_log.title
%td
= truncate(spam_log.description, length: 100)
%td
- if user
= link_to 'Remove user', admin_spam_log_path(spam_log, remove_user: true),
data: { confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-xs btn-remove"
%td
- if user && !user.blocked?
= link_to 'Block user', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-xs"
- else
.btn.btn-xs.disabled
Already Blocked
= link_to 'Remove log', [:admin, spam_log], remote: true, method: :delete, class: "btn btn-xs btn-close js-remove-tr"
- page_title "Spam Logs"
%h3.page-title Spam Logs
%hr
- if @spam_logs.present?
.table-holder
%table.table
%thead
%tr
%th Date
%th User
%th Source IP
%th API?
%th Type
%th Title
%th Description
%th Primary Action
%th
= render @spam_logs
= paginate @spam_logs
- else
%h4 There are no Spam Logs
%ul.nav-links
= 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: {placement: 'bottom'} do
Explore Groups
.top-area
%ul.nav-links
= nav_link(page: dashboard_groups_path) do
= link_to dashboard_groups_path, title: 'Your groups' do
Your Groups
= nav_link(page: explore_groups_path) do
= link_to explore_groups_path, title: 'Explore groups' do
Explore Groups
- if current_user.can_create_group?
.nav-controls
= link_to new_group_path, class: "btn btn-new" do
= icon('plus')
New Group
......@@ -8,13 +8,15 @@
= 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], html_options: { class: 'hidden-xs' }) do
= 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: {placement: 'right'} do
Explore Projects
.projects-search-form
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name...', class: 'projects-list-filter form-control hidden-xs', spellcheck: false
.nav-controls
= form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
= search_field_tag :filter_projects, params[:filter_projects], placeholder: 'Filter by name...', class: 'project-filter-form-field form-control input-short', spellcheck: false, id: 'project-filter-form-field'
= render 'explore/projects/dropdown'
- if current_user.can_create_project?
= link_to new_project_path, class: 'btn btn-green' do
%i.fa.fa-plus
= link_to new_project_path, class: 'btn btn-new' do
= icon('plus')
New Project
......@@ -2,15 +2,6 @@
- header_title "Groups", dashboard_groups_path
= render 'dashboard/groups_head'
.gray-content-block
- if current_user.can_create_group?
%span.pull-right.hidden-xs
= link_to new_group_path, class: "btn btn-new" do
%i.fa.fa-plus
New Group
.oneline
Group members have access to all group projects.
%ul.content-list
- @group_members.each do |group_member|
- group = group_member.group
......
......@@ -4,20 +4,15 @@
- if current_user
= auto_discovery_link_tag(:atom, issues_dashboard_url(format: :atom, private_token: current_user.private_token), title: "#{current_user.name} issues")
.project-issuable-filter
.controls
.pull-left
- if current_user
.hidden-xs.pull-left
= link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: 'btn' do
%i.fa.fa-rss
.top-area
= render 'shared/issuable/nav', type: :issues
.nav-controls
- if current_user
= link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: 'btn' do
= icon('rss')
= render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue"
= render 'shared/issuable/filter', type: :issues
.gray-content-block.second-block
List all issues from all projects you have access to.
= render 'shared/issuable/filter', type: :issues
.prepend-top-default
= render 'shared/issues'
- page_title "Merge Requests"
- header_title "Merge Requests", merge_requests_dashboard_path(assignee_id: current_user.id)
.project-issuable-filter
.controls
.top-area
= render 'shared/issuable/nav', type: :merge_requests
.nav-controls
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request"
= render 'shared/issuable/filter', type: :merge_requests
.gray-content-block.second-block
List all merge requests from all projects you have access to.
= render 'shared/issuable/filter', type: :merge_requests
.prepend-top-default
= render 'shared/merge_requests'
- page_title "Milestones"
- header_title "Milestones", dashboard_milestones_path
.project-issuable-filter
.controls
= render 'shared/new_project_item_select', path: 'milestones/new', label: "New Milestone", include_groups: true
.top-area
= render 'shared/milestones_filter'
.gray-content-block
List all milestones from all projects you have access to.
.nav-controls
= render 'shared/new_project_item_select', path: 'milestones/new', label: "New Milestone", include_groups: true
.milestones
%ul.content-list
......
.projects-list-holder
= render 'shared/projects/list', projects: @projects, ci: true
:javascript
Dashboard.init()
......@@ -3,8 +3,8 @@
.event-item-timestamp
#{time_ago_with_tooltip(event.created_at)}
= cache [event, current_application_settings, "v2.1"] do
= image_tag avatar_icon(event.author_email, 46), class: "avatar s46", alt:''
= cache [event, current_application_settings, "v2.2"] do
= image_tag avatar_icon(event.author_email, 40), class: "avatar s40", alt:''
- if event.created_project?
= render "events/event/created_project", event: event
- elsif event.push?
......
......@@ -3,19 +3,13 @@
%span.light
- 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
= sort_title_recently_updated
%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_name) do
= sort_title_name
= 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
......
......@@ -2,6 +2,7 @@
= form_tag explore_projects_filter_path, method: :get, class: 'form-inline form-tiny' do |f|
.form-group
= search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input", id: "projects_search", spellcheck: false
= hidden_field_tag :sort, @sort
.form-group
= button_tag 'Search', class: "btn"
......@@ -46,4 +47,3 @@
= link_to explore_projects_filter_path(tag: tag.name) do
%i.fa.fa-tag
= tag.name
= render 'explore/projects/dropdown'
%ul.nav-links
= nav_link(page: [trending_explore_projects_path, explore_root_path]) do
= link_to trending_explore_projects_path do
Trending
= nav_link(page: starred_explore_projects_path) do
= link_to starred_explore_projects_path do
Most stars
= nav_link(page: explore_projects_path) do
= link_to explore_projects_path do
All
......@@ -6,7 +6,10 @@
- else
= render 'explore/head'
.gray-content-block.clearfix.second-block
.top-area
= render 'explore/projects/nav'
.gray-content-block.second-block.clearfix
= render 'filter'
= render 'projects', projects: @projects
= paginate @projects, theme: "gitlab"
......@@ -6,12 +6,5 @@
- else
= render 'explore/head'
.explore-trending-block
.gray-content-block.second-block
.pull-right
= render 'explore/projects/dropdown'
.oneline
%i.fa.fa-star
See most starred projects
= render 'projects', projects: @starred_projects
= paginate @starred_projects, theme: 'gitlab'
= render 'explore/projects/nav'
= render 'projects', projects: @projects
......@@ -6,11 +6,5 @@
- else
= render 'explore/head'
.explore-trending-block
.gray-content-block.second-block
.pull-right
= render 'explore/projects/dropdown'
.oneline
%i.fa.fa-comments-o
See most discussed projects for last month
= render 'projects', projects: @trending_projects
= render 'explore/projects/nav'
= render 'projects', projects: @projects
.projects-list-holder
.projects-search-form
.input-group
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false
- if can? current_user, :create_projects, @group
%span.input-group-btn
= link_to new_project_path(namespace_id: @group.id), class: 'btn btn-green' do
%i.fa.fa-plus
New Project
.projects-list-holder.prepend-top-default
.input-group
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false
- if can? current_user, :create_projects, @group
%span.input-group-btn
= link_to new_project_path(namespace_id: @group.id), class: 'btn btn-new' do
= icon('plus')
New Project
= render 'shared/projects/list', projects: @projects, projects_limit: 20, stars: false, skip_namespace: true
......@@ -4,17 +4,15 @@
- if current_user
= auto_discovery_link_tag(:atom, issues_group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} issues")
.project-issuable-filter
.controls
.pull-left
- if current_user
.hidden-xs.pull-left
= link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token), class: 'btn' do
%i.fa.fa-rss
.top-area
= render 'shared/issuable/nav', type: :issues
.nav-controls
- if current_user
= link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token), class: 'btn' do
= icon('rss')
= render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue"
= render 'shared/issuable/filter', type: :issues
= render 'shared/issuable/filter', type: :issues
.gray-content-block.second-block
Only issues from
......
- page_title "Merge Requests"
- header_title group_title(@group, "Merge Requests", merge_requests_group_path(@group))
.project-issuable-filter
.controls
.top-area
= render 'shared/issuable/nav', type: :merge_requests
.nav-controls
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request"
= render 'shared/issuable/filter', type: :merge_requests
= render 'shared/issuable/filter', type: :merge_requests
.gray-content-block.second-block
Only merge requests from
......
- page_title "Milestones"
- header_title group_title(@group, "Milestones", group_milestones_path(@group))
.project-issuable-filter
.controls
- if can?(current_user, :admin_milestones, @group)
.pull-right
%span.pull-right.hidden-xs
= link_to new_group_milestone_path(@group), class: "btn btn-new" do
= icon('plus')
New Milestone
.top-area
= render 'shared/milestones_filter'
.nav-controls
- if can?(current_user, :admin_milestones, @group)
= link_to new_group_milestone_path(@group), class: "btn btn-new" do
= icon('plus')
New Milestone
.gray-content-block
Only milestones from
%strong #{@group.name}
......
......@@ -40,10 +40,6 @@
%td.shortcut
.key enter
%td Open Selection
%tr
%td.shortcut
.key t
%td Go to finding file
%tbody
%tr
%th
......@@ -161,6 +157,10 @@
.key s
%td
Go to snippets
%tr
%td.shortcut
.key t
%td Go to finding file
.col-lg-4
%table.shortcut-mappings
%tbody{ class: 'hidden-shortcut network', style: 'display:none' }
......
......@@ -138,8 +138,32 @@
%h2#navs Navigation
%h4
%code .top-area
%p Holder for top page navigation. Includes navigation, search field, sorting and button
.example
.top-area
%ul.nav-links
%li.active
%a Open
%li
%a Closed
.nav-controls
= text_field_tag 'sample', nil, class: 'form-control'
.dropdown
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
%span Sort by name
%b.caret
%ul.dropdown-menu
%li
%a Sort by date
= link_to 'New issue', '#', class: 'btn btn-new'
%h4
%code .nav-links
%p Only nav links without button and search
.example
%ul.nav-links
%li.active
......
.page-with-sidebar{ class: page_sidebar_class }
.page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" }
= render "layouts/broadcast"
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
.header-logo
= link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home' do
%a#logo
= brand_header_logo
= link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home' do
.gitlab-text-container
%h3 GitLab
......
.search
= 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)
- if @project && @project.persisted?
= hidden_field_tag :project_id, @project.id
......
......@@ -2,8 +2,9 @@
= render "layouts/broadcast"
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
.header-logo
= link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home' do
%a#logo
= brand_header_logo
= link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home' do
.gitlab-text-container
%h3 GitLab
......
%ul.nav.nav-sidebar
= nav_link(controller: :dashboard, html_options: {class: 'home'}) do
= link_to admin_root_path, title: "Stats" do
= link_to admin_root_path, title: 'Overview' do
= icon('dashboard fw')
%span
Overview
......@@ -25,13 +25,13 @@
%span
Deploy Keys
= nav_link path: ['runners#index', 'runners#show'] do
= link_to admin_runners_path do
= link_to admin_runners_path, title: 'Runners' do
= icon('cog fw')
%span
Runners
%span.count= number_with_delimiter(Ci::Runner.count(:all))
= nav_link path: 'builds#index' do
= link_to admin_builds_path do
= link_to admin_builds_path, title: 'Builds' do
= icon('link fw')
%span
Builds
......@@ -92,6 +92,14 @@
Abuse Reports
%span.count= number_with_delimiter(AbuseReport.count(:all))
- if askimet_enabled?
= nav_link(controller: :spam_logs) do
= link_to admin_spam_logs_path, title: "Spam Logs" do
= icon('exclamation-triangle fw')
%span
Spam Logs
%span.count= number_with_delimiter(SpamLog.count(:all))
= nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do
= link_to admin_application_settings_path, title: 'Settings' do
= icon('cogs fw')
......
.file-content.image_file
%img{ src: "data:#{blob.mime_type};base64,#{Base64.encode64(blob.data)}"}
%img{ src: namespace_project_raw_path(@project.namespace, @project, @id)}
- blob.load_all_data!(@repository)
- if markup?(blob.name)
.file-content.wiki
= render_markup(blob.name, blob.data)
......
- page_title "Builds"
= render "header_title"
.project-issuable-filter
.controls
- if can?(current_user, :manage_builds, @project)
.pull-left.hidden-xs
- if @all_builds.running_or_pending.any?
= link_to 'Cancel running', cancel_all_namespace_project_builds_path(@project.namespace, @project),
data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
= link_to ci_lint_path, class: 'btn btn-default' do
= icon('wrench')
%span CI Lint
.top-area
%ul.nav-links
%li{class: ('active' if @scope.nil?)}
= link_to project_builds_path(@project) do
......@@ -32,6 +21,16 @@
%span.badge.js-running-count
= number_with_delimiter(@all_builds.finished.count(:id))
.nav-controls
- if can?(current_user, :manage_builds, @project)
- if @all_builds.running_or_pending.any?
= link_to 'Cancel running', cancel_all_namespace_project_builds_path(@project.namespace, @project),
data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
= link_to ci_lint_path, class: 'btn btn-default' do
= icon('wrench')
%span CI Lint
.gray-content-block
#{(@scope || 'running').capitalize} builds from this project
......
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