BigW Consortium Gitlab

Commit 6b27b9f9 by James Lopez

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into fix/project-wiki-ending

parents c401e833 f838dbe4
...@@ -24,7 +24,12 @@ before_script: ...@@ -24,7 +24,12 @@ before_script:
- bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}" - bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}"
- RAILS_ENV=test bundle exec rake db:drop db:create db:schema:load db:migrate - RAILS_ENV=test bundle exec rake db:drop db:create db:schema:load db:migrate
stages:
- test
- notifications
spec:feature: spec:feature:
stage: test
script: script:
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:feature - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:feature
...@@ -33,6 +38,7 @@ spec:feature: ...@@ -33,6 +38,7 @@ spec:feature:
- mysql - mysql
spec:api: spec:api:
stage: test
script: script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:api - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:api
tags: tags:
...@@ -40,6 +46,7 @@ spec:api: ...@@ -40,6 +46,7 @@ spec:api:
- mysql - mysql
spec:models: spec:models:
stage: test
script: script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:models - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:models
tags: tags:
...@@ -47,6 +54,7 @@ spec:models: ...@@ -47,6 +54,7 @@ spec:models:
- mysql - mysql
spec:lib: spec:lib:
stage: test
script: script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:lib - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:lib
tags: tags:
...@@ -54,6 +62,7 @@ spec:lib: ...@@ -54,6 +62,7 @@ spec:lib:
- mysql - mysql
spec:services: spec:services:
stage: test
script: script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:services - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:services
tags: tags:
...@@ -61,6 +70,7 @@ spec:services: ...@@ -61,6 +70,7 @@ spec:services:
- mysql - mysql
spec:benchmark: spec:benchmark:
stage: test
script: script:
- RAILS_ENV=test bundle exec rake spec:benchmark - RAILS_ENV=test bundle exec rake spec:benchmark
tags: tags:
...@@ -69,6 +79,7 @@ spec:benchmark: ...@@ -69,6 +79,7 @@ spec:benchmark:
allow_failure: true allow_failure: true
spec:other: spec:other:
stage: test
script: script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:other - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:other
tags: tags:
...@@ -76,6 +87,7 @@ spec:other: ...@@ -76,6 +87,7 @@ spec:other:
- mysql - mysql
spinach:project:half: spinach:project:half:
stage: test
script: script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:half - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:half
tags: tags:
...@@ -83,6 +95,7 @@ spinach:project:half: ...@@ -83,6 +95,7 @@ spinach:project:half:
- mysql - mysql
spinach:project:rest: spinach:project:rest:
stage: test
script: script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest
tags: tags:
...@@ -90,6 +103,7 @@ spinach:project:rest: ...@@ -90,6 +103,7 @@ spinach:project:rest:
- mysql - mysql
spinach:other: spinach:other:
stage: test
script: script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:other - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:other
tags: tags:
...@@ -97,6 +111,7 @@ spinach:other: ...@@ -97,6 +111,7 @@ spinach:other:
- mysql - mysql
teaspoon: teaspoon:
stage: test
script: script:
- RAILS_ENV=test bundle exec teaspoon - RAILS_ENV=test bundle exec teaspoon
tags: tags:
...@@ -104,6 +119,7 @@ teaspoon: ...@@ -104,6 +119,7 @@ teaspoon:
- mysql - mysql
rubocop: rubocop:
stage: test
script: script:
- bundle exec rubocop - bundle exec rubocop
tags: tags:
...@@ -111,6 +127,7 @@ rubocop: ...@@ -111,6 +127,7 @@ rubocop:
- mysql - mysql
brakeman: brakeman:
stage: test
script: script:
- bundle exec rake brakeman - bundle exec rake brakeman
tags: tags:
...@@ -118,6 +135,7 @@ brakeman: ...@@ -118,6 +135,7 @@ brakeman:
- mysql - mysql
flog: flog:
stage: test
script: script:
- bundle exec rake flog - bundle exec rake flog
tags: tags:
...@@ -125,6 +143,7 @@ flog: ...@@ -125,6 +143,7 @@ flog:
- mysql - mysql
flay: flay:
stage: test
script: script:
- bundle exec rake flay - bundle exec rake flay
tags: tags:
...@@ -132,6 +151,7 @@ flay: ...@@ -132,6 +151,7 @@ flay:
- mysql - mysql
bundler:audit: bundler:audit:
stage: test
script: script:
- "bundle exec bundle-audit update" - "bundle exec bundle-audit update"
- "bundle exec bundle-audit check" - "bundle exec bundle-audit check"
...@@ -143,6 +163,7 @@ bundler:audit: ...@@ -143,6 +163,7 @@ bundler:audit:
# Ruby 2.2 jobs # Ruby 2.2 jobs
spec:feature:ruby22: spec:feature:ruby22:
stage: test
image: ruby:2.2 image: ruby:2.2
only: only:
- master - master
...@@ -158,6 +179,7 @@ spec:feature:ruby22: ...@@ -158,6 +179,7 @@ spec:feature:ruby22:
- mysql - mysql
spec:api:ruby22: spec:api:ruby22:
stage: test
image: ruby:2.2 image: ruby:2.2
only: only:
- master - master
...@@ -172,6 +194,7 @@ spec:api:ruby22: ...@@ -172,6 +194,7 @@ spec:api:ruby22:
- mysql - mysql
spec:models:ruby22: spec:models:ruby22:
stage: test
image: ruby:2.2 image: ruby:2.2
only: only:
- master - master
...@@ -186,6 +209,7 @@ spec:models:ruby22: ...@@ -186,6 +209,7 @@ spec:models:ruby22:
- mysql - mysql
spec:lib:ruby22: spec:lib:ruby22:
stage: test
image: ruby:2.2 image: ruby:2.2
only: only:
- master - master
...@@ -200,6 +224,7 @@ spec:lib:ruby22: ...@@ -200,6 +224,7 @@ spec:lib:ruby22:
- mysql - mysql
spec:services:ruby22: spec:services:ruby22:
stage: test
image: ruby:2.2 image: ruby:2.2
only: only:
- master - master
...@@ -214,6 +239,7 @@ spec:services:ruby22: ...@@ -214,6 +239,7 @@ spec:services:ruby22:
- mysql - mysql
spec:benchmark:ruby22: spec:benchmark:ruby22:
stage: test
image: ruby:2.2 image: ruby:2.2
only: only:
- master - master
...@@ -229,6 +255,7 @@ spec:benchmark:ruby22: ...@@ -229,6 +255,7 @@ spec:benchmark:ruby22:
allow_failure: true allow_failure: true
spec:other:ruby22: spec:other:ruby22:
stage: test
image: ruby:2.2 image: ruby:2.2
only: only:
- master - master
...@@ -243,6 +270,7 @@ spec:other:ruby22: ...@@ -243,6 +270,7 @@ spec:other:ruby22:
- mysql - mysql
spinach:project:half:ruby22: spinach:project:half:ruby22:
stage: test
image: ruby:2.2 image: ruby:2.2
only: only:
- master - master
...@@ -257,6 +285,7 @@ spinach:project:half:ruby22: ...@@ -257,6 +285,7 @@ spinach:project:half:ruby22:
- mysql - mysql
spinach:project:rest:ruby22: spinach:project:rest:ruby22:
stage: test
image: ruby:2.2 image: ruby:2.2
only: only:
- master - master
...@@ -271,6 +300,7 @@ spinach:project:rest:ruby22: ...@@ -271,6 +300,7 @@ spinach:project:rest:ruby22:
- mysql - mysql
spinach:other:ruby22: spinach:other:ruby22:
stage: test
image: ruby:2.2 image: ruby:2.2
only: only:
- master - master
...@@ -284,3 +314,14 @@ spinach:other:ruby22: ...@@ -284,3 +314,14 @@ spinach:other:ruby22:
- ruby - ruby
- mysql - mysql
notify:slack:
stage: notifications
script:
- ./scripts/notify_slack.sh "#builds" "Build on \`$CI_BUILD_REF_NAME\` failed! Check <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_BUILD_REF"/builds>"
when: on_failure
only:
- master@gitlab-org/gitlab-ce
- tags@gitlab-org/gitlab-ce
- master@gitlab-org/gitlab-ee
- tags@gitlab-org/gitlab-ee
\ No newline at end of file
...@@ -3,6 +3,13 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -3,6 +3,13 @@ Please view this file on the master branch, on stable branches it's out of date.
v 8.6.0 (unreleased) v 8.6.0 (unreleased)
- Improve the formatting for the user page bio (Connor Shea) - Improve the formatting for the user page bio (Connor Shea)
- Fix issue when pushing to projects ending in .wiki - Fix issue when pushing to projects ending in .wiki
- Fix avatar stretching by providing a cropping feature (Johann Pardanaud)
- Strip leading and trailing spaces in URL validator (evuez)
- Update documentation to reflect Guest role not being enforced on internal projects
v 8.5.2
- Fix sidebar overlapping content when screen width was below 1200px
- Fix error 500 when commenting on a commit
v 8.5.1 v 8.5.1
- Fix group projects styles - Fix group projects styles
...@@ -21,6 +28,7 @@ v 8.5.1 ...@@ -21,6 +28,7 @@ v 8.5.1
- Changed padding & background color for highlighted notes - Changed padding & background color for highlighted notes
- Re-add the newrelic_rpm gem which was removed without any deprecation or warning (Stan Hu) - Re-add the newrelic_rpm gem which was removed without any deprecation or warning (Stan Hu)
- Update sentry-raven gem to 0.15.6 - Update sentry-raven gem to 0.15.6
- Add build coverage in project's builds page (Steffen Köhler)
v 8.5.0 v 8.5.0
- Fix duplicate "me" in tooltip of the "thumbsup" awards Emoji (Stan Hu) - Fix duplicate "me" in tooltip of the "thumbsup" awards Emoji (Stan Hu)
...@@ -101,6 +109,9 @@ v 8.5.0 ...@@ -101,6 +109,9 @@ v 8.5.0
- Show label row when filtering issues or merge requests by label (Nuttanart Pornprasitsakul) - Show label row when filtering issues or merge requests by label (Nuttanart Pornprasitsakul)
- Add Todos - Add Todos
v 8.4.5
- No CE-specific changes
v 8.4.4 v 8.4.4
- Update omniauth-saml gem to 1.4.2 - Update omniauth-saml gem to 1.4.2
- Prevent long-running backup tasks from timing out the database connection - Prevent long-running backup tasks from timing out the database connection
......
...@@ -77,6 +77,9 @@ gem "haml-rails", '~> 0.9.0' ...@@ -77,6 +77,9 @@ gem "haml-rails", '~> 0.9.0'
# Files attachments # Files attachments
gem "carrierwave", '~> 0.9.0' gem "carrierwave", '~> 0.9.0'
# Image editing
gem "mini_magick", '~> 4.4.0'
# Drag and Drop UI # Drag and Drop UI
gem 'dropzonejs-rails', '~> 0.7.1' gem 'dropzonejs-rails', '~> 0.7.1'
......
...@@ -468,6 +468,7 @@ GEM ...@@ -468,6 +468,7 @@ GEM
method_source (0.8.2) method_source (0.8.2)
mime-types (1.25.1) mime-types (1.25.1)
mimemagic (0.3.0) mimemagic (0.3.0)
mini_magick (4.4.0)
mini_portile2 (2.0.0) mini_portile2 (2.0.0)
minitest (5.7.0) minitest (5.7.0)
mousetrap-rails (1.4.6) mousetrap-rails (1.4.6)
...@@ -955,13 +956,14 @@ DEPENDENCIES ...@@ -955,13 +956,14 @@ DEPENDENCIES
loofah (~> 2.0.3) loofah (~> 2.0.3)
mail_room (~> 0.6.1) mail_room (~> 0.6.1)
method_source (~> 0.8) method_source (~> 0.8)
mini_magick (~> 4.4.0)
minitest (~> 5.7.0) minitest (~> 5.7.0)
mousetrap-rails (~> 1.4.6) mousetrap-rails (~> 1.4.6)
mysql2 (~> 0.3.16) mysql2 (~> 0.3.16)
nested_form (~> 0.3.2) nested_form (~> 0.3.2)
net-ssh (~> 3.0.1) net-ssh (~> 3.0.1)
nokogiri (~> 1.6.7, >= 1.6.7.2)
newrelic_rpm (~> 3.14) newrelic_rpm (~> 3.14)
nokogiri (~> 1.6.7, >= 1.6.7.2)
nprogress-rails (~> 0.1.6.7) nprogress-rails (~> 0.1.6.7)
oauth2 (~> 1.0.0) oauth2 (~> 1.0.0)
octokit (~> 3.8.0) octokit (~> 3.8.0)
......
...@@ -44,6 +44,7 @@ ...@@ -44,6 +44,7 @@
#= require jquery.nicescroll #= require jquery.nicescroll
#= require_tree . #= require_tree .
#= require fuzzaldrin-plus #= require fuzzaldrin-plus
#= require cropper.js
window.slugify = (text) -> window.slugify = (text) ->
text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase() text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase()
......
...@@ -16,7 +16,7 @@ class @Autosave ...@@ -16,7 +16,7 @@ class @Autosave
try try
text = window.localStorage.getItem @key text = window.localStorage.getItem @key
catch catch e
return return
@field.val text if text?.length > 0 @field.val text if text?.length > 0
......
...@@ -70,6 +70,7 @@ class @MergeRequestTabs ...@@ -70,6 +70,7 @@ class @MergeRequestTabs
@loadCommits($target.attr('href')) @loadCommits($target.attr('href'))
else if action == 'diffs' else if action == 'diffs'
@loadDiff($target.attr('href')) @loadDiff($target.attr('href'))
@shrinkView()
else if action == 'builds' else if action == 'builds'
@loadBuilds($target.attr('href')) @loadBuilds($target.attr('href'))
...@@ -185,3 +186,14 @@ class @MergeRequestTabs ...@@ -185,3 +186,14 @@ class @MergeRequestTabs
expandViewContainer: -> expandViewContainer: ->
$('.container-fluid').removeClass('container-limited') $('.container-fluid').removeClass('container-limited')
shrinkView: ->
$gutterIcon = $('.gutter-toggle i')
# Wait until listeners are set
setTimeout( ->
# Only when sidebar is collapsed
if $gutterIcon.is('.fa-angle-double-right')
$gutterIcon.closest('a').trigger('click')
, 0)
...@@ -16,11 +16,50 @@ class @Profile ...@@ -16,11 +16,50 @@ class @Profile
$('.update-notifications').on 'ajax:complete', -> $('.update-notifications').on 'ajax:complete', ->
$(this).find('.btn-save').enable() $(this).find('.btn-save').enable()
$('.js-choose-user-avatar-button').bind "click", -> # Avatar management
form = $(this).closest("form")
form.find(".js-user-avatar-input").click() $avatarInput = $('.js-user-avatar-input')
$filename = $('.js-avatar-filename')
$modalCrop = $('.modal-profile-crop')
$modalCropImg = $('.modal-profile-crop-image')
$('.js-choose-user-avatar-button').on "click", ->
$form = $(this).closest("form")
$form.find(".js-user-avatar-input").click()
$modalCrop.on 'shown.bs.modal', ->
setTimeout ( -> # The cropper must be asynchronously initialized
$modalCropImg.cropper
aspectRatio: 1
modal: false
scalable: false
rotatable: false
zoomable: false
crop: (event) ->
['x', 'y'].forEach (key) ->
$("#user_avatar_crop_#{key}").val(Math.floor(event[key]))
$("#user_avatar_crop_size").val(Math.floor(event.width))
), 0
$modalCrop.on 'hidden.bs.modal', ->
$modalCropImg.attr('src', '').cropper('destroy')
$avatarInput.val('')
$filename.text($filename.data('label'))
$('.js-user-avatar-input').bind "change", -> $('.js-upload-user-avatar').on 'click', ->
$('.edit_user').submit()
$avatarInput.on "change", ->
form = $(this).closest("form") form = $(this).closest("form")
filename = $(this).val().replace(/^.*[\\\/]/, '') filename = $(this).val().replace(/^.*[\\\/]/, '')
form.find(".js-avatar-filename").text(filename) $filename.data('label', $filename.text()).text(filename)
reader = new FileReader
reader.onload = (event) ->
$modalCrop.modal('show')
$modalCropImg.attr('src', event.target.result)
fileData = reader.readAsDataURL(this.files[0])
...@@ -8,4 +8,10 @@ $(document).on("click", '.toggle-nav-collapse', (e) -> ...@@ -8,4 +8,10 @@ $(document).on("click", '.toggle-nav-collapse', (e) ->
$('.sidebar-wrapper').toggleClass("sidebar-collapsed sidebar-expanded") $('.sidebar-wrapper').toggleClass("sidebar-collapsed sidebar-expanded")
$('.toggle-nav-collapse i').toggleClass("fa-angle-right fa-angle-left") $('.toggle-nav-collapse i').toggleClass("fa-angle-right fa-angle-left")
$.cookie("collapsed_nav", $('.page-with-sidebar').hasClass(collapsed), { path: '/' }) $.cookie("collapsed_nav", $('.page-with-sidebar').hasClass(collapsed), { path: '/' })
setTimeout ( ->
niceScrollBars = $('.nicescroll').niceScroll();
niceScrollBars.updateScrollBar();
), 300
) )
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
*= require_self *= require_self
*= require dropzone/basic *= require dropzone/basic
*= require cal-heatmap *= require cal-heatmap
*= require cropper.css
*/ */
/* /*
......
...@@ -142,8 +142,12 @@ header { ...@@ -142,8 +142,12 @@ header {
} }
@media (max-width: $screen-md-max) { @media (max-width: $screen-md-max) {
.header-collapsed, .header-expanded { .header-collapsed {
@include collapsed-header; margin-left: $sidebar_collapsed_width;
}
.header-expanded {
margin-left: $sidebar_width;
} }
} }
......
...@@ -110,7 +110,20 @@ ul.content-list { ...@@ -110,7 +110,20 @@ ul.content-list {
> li { > li {
border-color: $table-border-color; border-color: $table-border-color;
color: $gl-gray; color: $list-text-color;
font-size: $list-font-size;
.title {
color: $list-title-color;
font-weight: 600;
}
.description {
p {
@include str-truncated;
margin-bottom: 0;
}
}
.avatar { .avatar {
margin-right: 15px; margin-right: 15px;
...@@ -127,13 +140,6 @@ ul.content-list { ...@@ -127,13 +140,6 @@ ul.content-list {
} }
} }
.panel > .content-list {
li {
margin: 0;
padding: $gl-padding;
}
}
ul.controls { ul.controls {
padding-top: 1px; padding-top: 1px;
float: right; float: right;
......
...@@ -41,6 +41,12 @@ ...@@ -41,6 +41,12 @@
transition: $transition; transition: $transition;
} }
@mixin transform($transform) {
-webkit-transform: $transform;
-ms-transform: $transform;
transform: $transform;
}
/** /**
* Prefilled mixins * Prefilled mixins
* Mixins with fixed values * Mixins with fixed values
......
...@@ -12,6 +12,10 @@ ...@@ -12,6 +12,10 @@
height: 100%; height: 100%;
transition-duration: .3s; transition-duration: .3s;
} }
&.right-sidebar-expanded {
padding-right: $gutter_width;
}
} }
.sidebar-wrapper { .sidebar-wrapper {
...@@ -45,19 +49,6 @@ ...@@ -45,19 +49,6 @@
overflow: hidden; overflow: hidden;
transition-duration: .3s; transition-duration: .3s;
.home {
z-index: 1;
position: absolute;
left: 0px;
}
#logo {
z-index: 2;
position: absolute;
width: 58px;
cursor: pointer;
}
a { a {
float: left; float: left;
height: $header-height; height: $header-height;
...@@ -83,7 +74,7 @@ ...@@ -83,7 +74,7 @@
width: 158px; width: 158px;
float: left; float: left;
margin: 0; margin: 0;
margin-left: 50px; margin-left: 14px;
font-size: 19px; font-size: 19px;
line-height: 41px; line-height: 41px;
font-weight: normal; font-weight: normal;
...@@ -194,6 +185,10 @@ ...@@ -194,6 +185,10 @@
@mixin expanded-sidebar { @mixin expanded-sidebar {
padding-left: $sidebar_width; padding-left: $sidebar_width;
&.right-sidebar-collapsed {
padding-right: $sidebar_collapsed_width;
}
.sidebar-wrapper { .sidebar-wrapper {
width: $sidebar_width; width: $sidebar_width;
...@@ -213,17 +208,13 @@ ...@@ -213,17 +208,13 @@
} }
} }
@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;
&.right-sidebar-collapsed {
padding-right: $sidebar_collapsed_width;
}
.sidebar-wrapper { .sidebar-wrapper {
width: $sidebar_collapsed_width; width: $sidebar_collapsed_width;
...@@ -287,47 +278,10 @@ ...@@ -287,47 +278,10 @@
background: #f2f6f7; background: #f2f6f7;
} }
// page is small enough .page-sidebar-collapsed {
@media (max-width: $screen-md-max) {
.page-sidebar-collapsed {
@include collapsed-sidebar;
}
.page-sidebar-expanded {
@include collapsed-sidebar; @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 .page-sidebar-expanded {
@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;
}
.page-sidebar-expanded {
@include expanded-sidebar; @include expanded-sidebar;
}
} }
...@@ -32,6 +32,8 @@ $gl-avatar-size: 40px; ...@@ -32,6 +32,8 @@ $gl-avatar-size: 40px;
$secondary-text: #7f8fa4; $secondary-text: #7f8fa4;
$error-exclamation-point: #E62958; $error-exclamation-point: #E62958;
$border-radius-default: 3px; $border-radius-default: 3px;
$list-title-color: #333333;
$list-text-color: #555555;
/* /*
* Color schema * Color schema
......
...@@ -203,14 +203,7 @@ ...@@ -203,14 +203,7 @@
overflow: hidden; overflow: hidden;
} }
.issuable-count, .hide-collapsed {
.issuable-nav,
.assignee > *,
.milestone > *,
.labels > *,
.participants > *,
.light > *,
.project-reference > * {
display: none; display: none;
} }
......
...@@ -4,13 +4,7 @@ ...@@ -4,13 +4,7 @@
position: relative; position: relative;
.issue-title { .issue-title {
margin-bottom: 5px; margin-bottom: 2px;
font-size: $list-font-size;
font-weight: 600;
}
.issue-info {
color: $gl-gray;
} }
.issue-check { .issue-check {
......
...@@ -148,15 +148,8 @@ ...@@ -148,15 +148,8 @@
position: relative; position: relative;
.merge-request-title { .merge-request-title {
margin-bottom: 5px; margin-bottom: 2px;
font-size: $list-font-size;
font-weight: 600;
} }
.merge-request-info {
color: $gl-gray;
}
} }
.merge-request-labels { .merge-request-labels {
......
...@@ -78,3 +78,39 @@ ...@@ -78,3 +78,39 @@
max-width: 750px; max-width: 750px;
margin: auto; margin: auto;
} }
.modal-profile-crop {
.modal-dialog {
width: 500px;
}
.modal-body {
p {
display: table;
margin: auto;
overflow: hidden;
}
img {
display: block;
max-width: 400px;
max-height: 400px;
}
.cropper-bg {
background: none;
}
.cropper-crop-box {
box-sizing: content-box;
border: 999px solid transparentize(#ccc, 0.5);
@include transform(translate(-999px, -999px));
}
}
}
@media (max-width: 520px) {
.modal-profile-crop .modal-dialog {
width: auto;
}
}
...@@ -397,15 +397,10 @@ pre.light-well { ...@@ -397,15 +397,10 @@ pre.light-well {
.project-full-name { .project-full-name {
@include str-truncated; @include str-truncated;
font-weight: 600;
color: #4c4e54;
} }
.project-controls { .controls {
float: right;
color: $gl-gray;
line-height: 40px; line-height: 40px;
color: #7f8fa4;
a:hover { a:hover {
text-decoration: none; text-decoration: none;
...@@ -415,16 +410,6 @@ pre.light-well { ...@@ -415,16 +410,6 @@ pre.light-well {
margin-left: 10px; margin-left: 10px;
} }
} }
.project-description {
color: #7f8fa4;
p {
@include str-truncated;
margin-bottom: 0;
color: #7f8fa4;
}
}
} }
.bottom { .bottom {
......
...@@ -2,30 +2,6 @@ ...@@ -2,30 +2,6 @@
padding: 2px; padding: 2px;
} }
.snippet-row {
.snippet-title {
font-size: 15px;
font-weight: bold;
line-height: 20px;
margin-bottom: 2px;
.monospace {
font-weight: normal;
}
}
.snippet-info {
color: #888;
font-size: 13px;
line-height: 24px;
a {
color: #888;
}
}
}
.snippet-holder { .snippet-holder {
margin-bottom: -$gl-padding; margin-bottom: -$gl-padding;
......
...@@ -3,4 +3,15 @@ ...@@ -3,4 +3,15 @@
margin: 35px 0 20px; margin: 35px 0 20px;
font-weight: bold; font-weight: bold;
} }
.example {
&:before {
content: "Example";
color: #BBB;
}
padding: 15px;
border: 1px dashed #ddd;
margin-bottom: 15px;
}
} }
...@@ -65,6 +65,9 @@ class ProfilesController < Profiles::ApplicationController ...@@ -65,6 +65,9 @@ class ProfilesController < Profiles::ApplicationController
def user_params def user_params
params.require(:user).permit( params.require(:user).permit(
:avatar_crop_x,
:avatar_crop_y,
:avatar_crop_size,
:avatar, :avatar,
:bio, :bio,
:email, :email,
......
...@@ -6,6 +6,10 @@ module AuthHelper ...@@ -6,6 +6,10 @@ module AuthHelper
Gitlab.config.ldap.enabled Gitlab.config.ldap.enabled
end end
def omniauth_enabled?
Gitlab.config.omniauth.enabled
end
def provider_has_icon?(name) def provider_has_icon?(name)
PROVIDERS_WITH_ICONS.include?(name.to_s) PROVIDERS_WITH_ICONS.include?(name.to_s)
end end
......
...@@ -801,10 +801,7 @@ class Project < ActiveRecord::Base ...@@ -801,10 +801,7 @@ class Project < ActiveRecord::Base
end end
def change_head(branch) def change_head(branch)
# Cached divergent commit counts are based on repository head repository.before_change_head
repository.expire_branch_cache
repository.expire_root_ref_cache
gitlab_shell.update_repository_head(self.path_with_namespace, branch) gitlab_shell.update_repository_head(self.path_with_namespace, branch)
reload_default_branch reload_default_branch
end end
......
...@@ -245,15 +245,6 @@ class Repository ...@@ -245,15 +245,6 @@ class Repository
expire_emptiness_caches if empty? expire_emptiness_caches if empty?
end end
# Expires _all_ caches, including those that would normally only be expired
# under specific conditions.
def expire_all_caches!
expire_cache
expire_root_ref_cache
expire_emptiness_caches
expire_has_visible_content_cache
end
def expire_branch_cache(branch_name = nil) def expire_branch_cache(branch_name = nil)
# When we push to the root branch we have to flush the cache for all other # When we push to the root branch we have to flush the cache for all other
# branches as their statistics are based on the commits relative to the # branches as their statistics are based on the commits relative to the
...@@ -307,6 +298,46 @@ class Repository ...@@ -307,6 +298,46 @@ class Repository
cache.expire(:branch_names) cache.expire(:branch_names)
end end
# Runs code just before a repository is deleted.
def before_delete
expire_cache if exists?
expire_root_ref_cache
expire_emptiness_caches
end
# Runs code just before the HEAD of a repository is changed.
def before_change_head
# Cached divergent commit counts are based on repository head
expire_branch_cache
expire_root_ref_cache
end
# Runs code before creating a new tag.
def before_create_tag
expire_cache
end
# Runs code after a repository has been forked/imported.
def after_import
expire_emptiness_caches
end
# Runs code after a new commit has been pushed.
def after_push_commit(branch_name)
expire_cache(branch_name)
end
# Runs code after a new branch has been created.
def after_create_branch
expire_has_visible_content_cache
end
# Runs code after an existing branch has been removed.
def after_remove_branch
expire_has_visible_content_cache
end
def method_missing(m, *args, &block) def method_missing(m, *args, &block)
if m == :lookup && !block_given? if m == :lookup && !block_given?
lookup_cache[m] ||= {} lookup_cache[m] ||= {}
......
...@@ -98,6 +98,9 @@ class User < ActiveRecord::Base ...@@ -98,6 +98,9 @@ class User < ActiveRecord::Base
# Virtual attribute for authenticating by either username or email # Virtual attribute for authenticating by either username or email
attr_accessor :login attr_accessor :login
# Virtual attributes to define avatar cropping
attr_accessor :avatar_crop_x, :avatar_crop_y, :avatar_crop_size
# #
# Relations # Relations
# #
...@@ -163,6 +166,11 @@ class User < ActiveRecord::Base ...@@ -163,6 +166,11 @@ class User < ActiveRecord::Base
validate :owns_public_email, if: ->(user) { user.public_email_changed? } validate :owns_public_email, if: ->(user) { user.public_email_changed? }
validates :avatar, file_size: { maximum: 200.kilobytes.to_i } validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
validates :avatar_crop_x, :avatar_crop_y, :avatar_crop_size,
numericality: { only_integer: true },
presence: true,
if: ->(user) { user.avatar? }
before_validation :generate_password, on: :create before_validation :generate_password, on: :create
before_validation :restricted_signup_domains, on: :create before_validation :restricted_signup_domains, on: :create
before_validation :sanitize_attrs before_validation :sanitize_attrs
......
...@@ -16,13 +16,13 @@ class GitPushService < BaseService ...@@ -16,13 +16,13 @@ class GitPushService < BaseService
# 5. Executes the project's services # 5. Executes the project's services
# #
def execute def execute
@project.repository.expire_cache(branch_name) @project.repository.after_push_commit(branch_name)
if push_remove_branch? if push_remove_branch?
@project.repository.expire_has_visible_content_cache @project.repository.after_remove_branch
@push_commits = [] @push_commits = []
elsif push_to_new_branch? elsif push_to_new_branch?
@project.repository.expire_has_visible_content_cache @project.repository.after_create_branch
# Re-find the pushed commits. # Re-find the pushed commits.
if is_default_branch? if is_default_branch?
......
...@@ -2,7 +2,7 @@ class GitTagPushService ...@@ -2,7 +2,7 @@ class GitTagPushService
attr_accessor :project, :user, :push_data attr_accessor :project, :user, :push_data
def execute(project, user, oldrev, newrev, ref) def execute(project, user, oldrev, newrev, ref)
project.repository.expire_cache project.repository.before_create_tag
@project, @user = project, user @project, @user = project, user
@push_data = build_push_data(oldrev, newrev, ref) @push_data = build_push_data(oldrev, newrev, ref)
......
...@@ -13,6 +13,5 @@ module Notes ...@@ -13,6 +13,5 @@ module Notes
note note
end end
end end
end end
...@@ -76,11 +76,9 @@ module Projects ...@@ -76,11 +76,9 @@ module Projects
end end
def flush_caches(project, wiki_path) def flush_caches(project, wiki_path)
project.repository.expire_all_caches! if project.repository.exists? project.repository.before_delete
wiki_repo = Repository.new(wiki_path, project) Repository.new(wiki_path, project).before_delete
wiki_repo.expire_all_caches! if wiki_repo.exists?
end end
end end
end end
...@@ -130,8 +130,8 @@ class TodoService ...@@ -130,8 +130,8 @@ class TodoService
end end
def handle_note(note, author) def handle_note(note, author)
# Skip system notes, like status changes and cross-references # Skip system notes, notes on commit, and notes on project snippet
return if note.system return if note.system? || ['Commit', 'Snippet'].include?(note.noteable_type)
project = note.project project = note.project
target = note.noteable target = note.noteable
......
...@@ -2,11 +2,22 @@ ...@@ -2,11 +2,22 @@
class AvatarUploader < CarrierWave::Uploader::Base class AvatarUploader < CarrierWave::Uploader::Base
include UploaderHelper include UploaderHelper
include CarrierWave::MiniMagick
storage :file storage :file
after :store, :reset_events_cache after :store, :reset_events_cache
process :cropper
def cropper
return unless model.respond_to?(:avatar_crop_size) && model.valid?
manipulate! do |img|
img.crop "#{model.avatar_crop_size}x#{model.avatar_crop_size}+#{model.avatar_crop_x}+#{model.avatar_crop_y}"
end
end
def store_dir def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end end
......
...@@ -29,8 +29,11 @@ class UrlValidator < ActiveModel::EachValidator ...@@ -29,8 +29,11 @@ class UrlValidator < ActiveModel::EachValidator
end end
def valid_url?(value) def valid_url?(value)
return false if value.nil?
options = default_options.merge(self.options) options = default_options.merge(self.options)
value.strip!
value =~ /\A#{URI.regexp(options[:protocols])}\z/ value =~ /\A#{URI.regexp(options[:protocols])}\z/
end end
end end
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
= render 'devise/shared/signin_box' = render 'devise/shared/signin_box'
-# Omniauth fits between signin/ldap signin and signup and does not have a surrounding box -# Omniauth fits between signin/ldap signin and signup and does not have a surrounding box
- if Gitlab.config.omniauth.enabled && devise_mapping.omniauthable? - if omniauth_enabled? && devise_mapping.omniauthable?
.clearfix.prepend-top-20 .clearfix.prepend-top-20
= render 'devise/shared/omniauth_box' = render 'devise/shared/omniauth_box'
...@@ -14,6 +14,6 @@ ...@@ -14,6 +14,6 @@
= render 'devise/shared/signup_box' = render 'devise/shared/signup_box'
-# Show a message if none of the mechanisms above are enabled -# Show a message if none of the mechanisms above are enabled
- if !signin_enabled? && !ldap_enabled? && !(Gitlab.config.omniauth.enabled && devise_mapping.omniauthable?) - if !signin_enabled? && !ldap_enabled? && !(omniauth_enabled? && devise_mapping.omniauthable?)
%div %div
No authentication methods configured. No authentication methods configured.
...@@ -31,10 +31,25 @@ ...@@ -31,10 +31,25 @@
%h2#blocks Blocks %h2#blocks Blocks
%h4 .lead
Content block separated with botton border
%code .content-block
.example
.content-block
%h4 Normal block inside content
= lorem
.content-block
%h4 Second block
= lorem
.lead
Gray content block with side padding using
%code .gray-content-block %code .gray-content-block
.gray-content-block.middle-block .example
.gray-content-block
%h4 Normal block inside content %h4 Normal block inside content
= lorem = lorem
...@@ -43,9 +58,10 @@ ...@@ -43,9 +58,10 @@
= lorem = lorem
%h4 .lead
Cover block for profile page with avatar, name and description
%code .cover-block %code .cover-block
%br .example
.cover-block .cover-block
.avatar-holder .avatar-holder
= image_tag avatar_icon('admin@example.com', 90), class: "avatar s90", alt: '' = image_tag avatar_icon('admin@example.com', 90), class: "avatar s90", alt: ''
...@@ -64,8 +80,11 @@ ...@@ -64,8 +80,11 @@
%h2#lists Lists %h2#lists Lists
%h4 .lead
Simple list using
%code .content-list %code .content-list
.example
%ul.content-list %ul.content-list
%li %li
One item One item
...@@ -74,21 +93,29 @@ ...@@ -74,21 +93,29 @@
%li %li
One item One item
%h4 .lead
%code .well-list List with avatar, title and description using
%ul.well-list %code .content-list
.example
%ul.content-list
%li %li
One item = image_tag 'no_avatar.png', class: 'avatar s40'
.title Title
.description Description
%li %li
One item = image_tag 'no_avatar.png', class: 'avatar s40'
.title Title
.description Description
%li %li
One item = image_tag 'no_avatar.png', class: 'avatar s40'
.title Title
.description Description
%h4 .lead
%code .panel .well-list List with hover effect
%code .well-list
.panel.panel-default .example
.panel-heading Your list
%ul.well-list %ul.well-list
%li %li
One item One item
...@@ -97,9 +124,12 @@ ...@@ -97,9 +124,12 @@
%li %li
One item One item
%h4 .lead
%code .bordered-list List inside panel
%ul.bordered-list .example
.panel.panel-default
.panel-heading Your list
%ul.well-list
%li %li
One item One item
%li %li
...@@ -107,8 +137,6 @@ ...@@ -107,8 +137,6 @@
%li %li
One item One item
%h2#tables Tables %h2#tables Tables
.example .example
...@@ -138,9 +166,9 @@ ...@@ -138,9 +166,9 @@
%h2#navs Navigation %h2#navs Navigation
%h4 .lead
Holder for top page navigation. Includes navigation, search field, sorting and button
%code .top-area %code .top-area
%p Holder for top page navigation. Includes navigation, search field, sorting and button
.example .example
.top-area .top-area
...@@ -161,9 +189,9 @@ ...@@ -161,9 +189,9 @@
= link_to 'New issue', '#', class: 'btn btn-new' = link_to 'New issue', '#', class: 'btn btn-new'
%h4 .lead
Only nav links without button and search
%code .nav-links %code .nav-links
%p Only nav links without button and search
.example .example
%ul.nav-links %ul.nav-links
%li.active %li.active
...@@ -228,9 +256,11 @@ ...@@ -228,9 +256,11 @@
%h2#forms Forms %h2#forms Forms
%h4 .lead
Horizontal form when label rendered inline with input
%code form.horizontal-form %code form.horizontal-form
.example
%form.form-horizontal %form.form-horizontal
.form-group .form-group
%label.col-sm-2.control-label{:for => "inputEmail3"} Email %label.col-sm-2.control-label{:for => "inputEmail3"} Email
...@@ -250,9 +280,11 @@ ...@@ -250,9 +280,11 @@
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
%button.btn.btn-default{:type => "submit"} Sign in %button.btn.btn-default{:type => "submit"} Sign in
%h4 .lead
Form when label rendered above input
%code form %code form
.example
%form %form
.form-group .form-group
%label{:for => "exampleInputEmail1"} Email address %label{:for => "exampleInputEmail1"} Email address
......
...@@ -90,6 +90,9 @@ ...@@ -90,6 +90,9 @@
&nbsp; &nbsp;
%span.file_name.js-avatar-filename File name... %span.file_name.js-avatar-filename File name...
= f.file_field :avatar, class: "js-user-avatar-input hidden" = f.file_field :avatar, class: "js-user-avatar-input hidden"
= f.hidden_field :avatar_crop_x
= f.hidden_field :avatar_crop_y
= f.hidden_field :avatar_crop_size
.light The maximum file size allowed is 200KB. .light The maximum file size allowed is 200KB.
- if @user.avatar? - if @user.avatar?
%hr %hr
...@@ -99,3 +102,19 @@ ...@@ -99,3 +102,19 @@
.form-actions .form-actions
= f.submit 'Save changes', class: "btn btn-success" = f.submit 'Save changes', class: "btn btn-success"
= link_to "Cancel", user_path(current_user), class: "btn btn-cancel" = link_to "Cancel", user_path(current_user), class: "btn btn-cancel"
.modal.modal-profile-crop
.modal-dialog
.modal-content
.modal-header
%button.close{type: 'button', data: {dismiss: 'modal'}}
%span
&times;
%h4.modal-title
Crop your new profile picture
.modal-body
%p
%img.modal-profile-crop-image
.modal-footer
%button.btn.btn-primary.js-upload-user-avatar{:type => "button"}
Set new profile picture
...@@ -51,9 +51,11 @@ ...@@ -51,9 +51,11 @@
%th Name %th Name
%th Duration %th Duration
%th Finished at %th Finished at
- if @project.build_coverage_enabled?
%th Coverage
%th %th
- @builds.each do |build| - @builds.each do |build|
= render 'projects/commit_statuses/commit_status', commit_status: build, commit_sha: true, stage: true, allow_retry: true = render 'projects/commit_statuses/commit_status', commit_status: build, commit_sha: true, stage: true, coverage: @project.build_coverage_enabled?, allow_retry: true
= paginate @builds, theme: 'gitlab' = paginate @builds, theme: 'gitlab'
- content_for :note_actions do - content_for :note_actions do
- if can?(current_user, :update_issue, @issue) - if can?(current_user, :update_issue, @issue)
= link_to 'Reopen Issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-reopen btn-comment js-note-target-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen Issue' = link_to 'Reopen issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-reopen btn-comment js-note-target-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
= link_to 'Close Issue', issue_path(@issue, issue: {state_event: :close}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-close btn-comment js-note-target-close #{issue_button_visibility(@issue, true)}", title: 'Close Issue' = link_to 'Close issue', issue_path(@issue, issue: {state_event: :close}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-close btn-comment js-note-target-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
#notes #notes
= render 'projects/notes/notes_with_form' = render 'projects/notes/notes_with_form'
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
.issue-title .issue-title
%span.issue-title-text %span.issue-title-text
= link_to_gfm issue.title, issue_path(issue), class: "row_title" = link_to_gfm issue.title, issue_path(issue), class: "title"
%ul.controls.light %ul.controls.light
- if issue.closed? - if issue.closed?
%li %li
......
...@@ -8,12 +8,12 @@ ...@@ -8,12 +8,12 @@
.detail-page-header .detail-page-header
.pull-right .pull-right
- if can?(current_user, :create_issue, @project) - if can?(current_user, :create_issue, @project)
= link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'btn btn-nr btn-grouped new-issue-link btn-success', title: 'New Issue', id: 'new_issue_link' do = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'btn btn-nr btn-grouped new-issue-link btn-success', title: 'New issue', id: 'new_issue_link' do
= icon('plus') = icon('plus')
New Issue New issue
- if can?(current_user, :update_issue, @issue) - if can?(current_user, :update_issue, @issue)
= link_to 'Reopen', issue_path(@issue, issue: {state_event: :reopen}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen Issue' = link_to 'Reopen issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
= link_to 'Close', issue_path(@issue, issue: {state_event: :close}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close Issue' = link_to 'Close issue', issue_path(@issue, issue: {state_event: :close}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
= link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'btn btn-nr btn-grouped issuable-edit' do = link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'btn btn-nr btn-grouped issuable-edit' do
= icon('pencil-square-o') = icon('pencil-square-o')
......
%li{ class: mr_css_classes(merge_request) } %li{ class: mr_css_classes(merge_request) }
.merge-request-title .merge-request-title
%span.merge-request-title-text %span.merge-request-title-text
= link_to_gfm merge_request.title, merge_request_path(merge_request), class: "row_title" = link_to_gfm merge_request.title, merge_request_path(merge_request), class: "title"
%ul.controls.light %ul.controls.light
- if merge_request.merged? - if merge_request.merged?
%li %li
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
.pull-right .pull-right
= link_to 'New issue', new_namespace_project_issue_path(project.namespace, project) = link_to 'New issue', new_namespace_project_issue_path(project.namespace, project)
%ul.well-list.issues-list %ul.content-list.issues-list
- group[1].each do |issue| - group[1].each do |issue|
= render 'projects/issues/issue', issue: issue = render 'projects/issues/issue', issue: issue
= paginate @issues, theme: "gitlab" = paginate @issues, theme: "gitlab"
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
.pull-right .pull-right
= link_to 'New merge request', new_namespace_project_merge_request_path(project.namespace, project) = link_to 'New merge request', new_namespace_project_merge_request_path(project.namespace, project)
%ul.well-list.mr-list %ul.content-list.mr-list
- group[1].each do |merge_request| - group[1].each do |merge_request|
= render 'projects/merge_requests/merge_request', merge_request: merge_request = render 'projects/merge_requests/merge_request', merge_request: merge_request
= paginate @merge_requests, theme: "gitlab" = paginate @merge_requests, theme: "gitlab"
......
...@@ -22,13 +22,13 @@ ...@@ -22,13 +22,13 @@
= number_with_delimiter(group.users.count) = number_with_delimiter(group.users.count)
= image_tag group_icon(group), class: "avatar s40 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 title' do
%span.item-title= group.name = group.name
- if group_member - if group_member
as as
%span #{group_member.human_access} %span #{group_member.human_access}
- if group.description.present? - if group.description.present?
.light .description
= markdown(group.description, pipeline: :description) = markdown(group.description, pipeline: :description)
...@@ -3,7 +3,8 @@ ...@@ -3,7 +3,8 @@
= icon('users') = icon('users')
%span %span
= participants.count = participants.count
.title .title.hide-collapsed
= pluralize participants.count, "participant" = pluralize participants.count, "participant"
- participants.each do |participant| - participants.each do |participant|
%span.hide-collapsed
= link_to_member(@project, participant, name: false, size: 24) = link_to_member(@project, participant, name: false, size: 24)
%aside.right-sidebar{ class: sidebar_gutter_collapsed_class } %aside.right-sidebar{ class: sidebar_gutter_collapsed_class }
.issuable-sidebar .issuable-sidebar
.block .block
%span.issuable-count.pull-left %span.issuable-count.hide-collapsed.pull-left
= issuable.iid = issuable.iid
of of
= issuables_count(issuable) = issuables_count(issuable)
%span.pull-right %span.pull-right
%a.gutter-toggle{href: '#'} %a.gutter-toggle{href: '#'}
= sidebar_gutter_toggle_icon = sidebar_gutter_toggle_icon
.issuable-nav.pull-right.btn-group{role: 'group', "aria-label" => '...'} .issuable-nav.hide-collapsed.pull-right.btn-group{role: 'group', "aria-label" => '...'}
- if prev_issuable = prev_issuable_for(issuable) - if prev_issuable = prev_issuable_for(issuable)
= link_to 'Prev', [@project.namespace.becomes(Namespace), @project, prev_issuable], class: 'btn btn-default prev-btn' = link_to 'Prev', [@project.namespace.becomes(Namespace), @project, prev_issuable], class: 'btn btn-default prev-btn'
- else - else
...@@ -27,13 +27,13 @@ ...@@ -27,13 +27,13 @@
= link_to_member_avatar(issuable.assignee, size: 24) = link_to_member_avatar(issuable.assignee, size: 24)
- else - else
= icon('user') = icon('user')
.title .title.hide-collapsed
%label %label
Assignee Assignee
- 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 .value.hide-collapsed
- if issuable.assignee - if issuable.assignee
%strong= link_to_member(@project, issuable.assignee, size: 24) %strong= link_to_member(@project, issuable.assignee, size: 24)
- if issuable.instance_of?(MergeRequest) && !issuable.can_be_merged_by?(issuable.assignee) - if issuable.instance_of?(MergeRequest) && !issuable.can_be_merged_by?(issuable.assignee)
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
- else - else
.light None .light None
.selectbox .selectbox.hide-collapsed
= 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) = 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.milestone .block.milestone
...@@ -53,13 +53,13 @@ ...@@ -53,13 +53,13 @@
= issuable.milestone.title = issuable.milestone.title
- else - else
No No
.title .title.hide-collapsed
%label %label
Milestone 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 .value.hide-collapsed
- if issuable.milestone - if issuable.milestone
%span.back-to-milestone %span.back-to-milestone
= link_to namespace_project_milestone_path(@project.namespace, @project, issuable.milestone) do = link_to namespace_project_milestone_path(@project.namespace, @project, issuable.milestone) do
...@@ -68,7 +68,7 @@ ...@@ -68,7 +68,7 @@
= issuable.milestone.title = issuable.milestone.title
- else - else
.light None .light None
.selectbox .selectbox.hide-collapsed
= f.select(:milestone_id, milestone_options(issuable), { include_blank: true }, { class: 'select2 select2-compact js-select2 js-milestone', data: { placeholder: 'Select milestone' }}) = 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 = hidden_field_tag :issuable_context
= f.submit class: 'btn hide' = f.submit class: 'btn hide'
...@@ -79,18 +79,18 @@ ...@@ -79,18 +79,18 @@
= icon('tags') = icon('tags')
%span %span
= issuable.labels.count = issuable.labels.count
.title .title.hide-collapsed
%label Labels %label Labels
- 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.issuable-show-labels.hide-collapsed
- if issuable.labels.any? - if issuable.labels.any?
- issuable.labels.each do |label| - issuable.labels.each do |label|
= link_to_label(label, type: issuable.to_ability_name) = link_to_label(label, type: issuable.to_ability_name)
- else - else
.light None .light None
.selectbox .selectbox.hide-collapsed
= f.collection_select :label_ids, issuable.project.labels.all, :id, :name, = 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" } { selected: issuable.label_ids }, multiple: true, class: 'select2 js-select2', data: { placeholder: "Select labels" }
...@@ -101,12 +101,12 @@ ...@@ -101,12 +101,12 @@
.block.light .block.light
.sidebar-collapsed-icon .sidebar-collapsed-icon
= icon('rss') = icon('rss')
.title .title.hide-collapsed
%label.light Notifications %label.light Notifications
- subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed' - subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed'
%button.btn.btn-block.btn-gray.subscribe-button{:type => 'button'} %button.btn.btn-block.btn-gray.subscribe-button.hide-collapsed{:type => 'button'}
%span= subscribed ? 'Unsubscribe' : 'Subscribe' %span= subscribed ? 'Unsubscribe' : 'Subscribe'
.subscription-status{data: {status: subscribtion_status}} .subscription-status.hide-collapsed{data: {status: subscribtion_status}}
.unsubscribed{class: ( 'hidden' if subscribed )} .unsubscribed{class: ( 'hidden' if subscribed )}
You're not receiving notifications from this thread. You're not receiving notifications from this thread.
.subscribed{class: ( 'hidden' unless subscribed )} .subscribed{class: ( 'hidden' unless subscribed )}
...@@ -116,8 +116,7 @@ ...@@ -116,8 +116,7 @@
.block.project-reference .block.project-reference
.sidebar-collapsed-icon .sidebar-collapsed-icon
= clipboard_button(clipboard_text: project_ref) = clipboard_button(clipboard_text: project_ref)
.title .cross-project-reference.hide-collapsed
.cross-project-reference
%span %span
Reference: Reference:
%cite{title: project_ref} %cite{title: project_ref}
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
- skip_namespace = false unless local_assigns[:skip_namespace] == true - skip_namespace = false unless local_assigns[:skip_namespace] == true
- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true - show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true
%ul.projects-list %ul.projects-list.content-list
- if projects.any? - if projects.any?
- projects.each_with_index do |project, i| - projects.each_with_index do |project, i|
- css_class = (i >= projects_limit) ? 'hide' : nil - css_class = (i >= projects_limit) ? 'hide' : nil
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
= image_tag avatar_icon(project.creator.email, 40), class: "avatar s40", alt:'' = image_tag avatar_icon(project.creator.email, 40), class: "avatar s40", alt:''
- else - else
= project_icon(project, alt: '', class: 'avatar project-avatar s40') = project_icon(project, alt: '', class: 'avatar project-avatar s40')
%span.project-full-name %span.project-full-name.title
%span.namespace-name %span.namespace-name
- if project.namespace && !skip_namespace - if project.namespace && !skip_namespace
= project.namespace.human_name = project.namespace.human_name
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
%span.project-name.filter-title %span.project-name.filter-title
= project.name = project.name
.project-controls .controls
- if ci_commit - if ci_commit
%span %span
= render_ci_status(ci_commit) = render_ci_status(ci_commit)
...@@ -43,9 +43,9 @@ ...@@ -43,9 +43,9 @@
title: "#{visibility_level_label(project.visibility_level)} - #{project_visibility_level_description(project.visibility_level)}"} title: "#{visibility_level_label(project.visibility_level)} - #{project_visibility_level_description(project.visibility_level)}"}
= visibility_level_icon(project.visibility_level, fw: false) = visibility_level_icon(project.visibility_level, fw: false)
- if show_last_commit_as_description - if show_last_commit_as_description
.project-description .description
= link_to_gfm project.commit.title, namespace_project_commit_path(project.namespace, project, project.commit), = link_to_gfm project.commit.title, namespace_project_commit_path(project.namespace, project, project.commit),
class: "commit-row-message" class: "commit-row-message"
- elsif project.description.present? - elsif project.description.present?
.project-description .description
= markdown(project.description, pipeline: :description) = markdown(project.description, pipeline: :description)
%li.snippet-row %li.snippet-row
= image_tag avatar_icon(snippet.author_email), class: "avatar s40 hidden-xs", alt: ''
.snippet-title .snippet-title
= link_to reliable_snippet_path(snippet) do = link_to reliable_snippet_path(snippet), class: 'title' do
= truncate(snippet.title, length: 60) = truncate(snippet.title, length: 60)
- if snippet.private? - if snippet.private?
%span.label.label-gray %span.label.label-gray
%i.fa.fa-lock = icon('lock')
private private
%span.monospace.pull-right %span.monospace.pull-right
= snippet.file_name = snippet.file_name
...@@ -15,6 +17,5 @@ ...@@ -15,6 +17,5 @@
.snippet-info .snippet-info
= link_to user_snippets_path(snippet.author) do = link_to user_snippets_path(snippet.author) do
= image_tag avatar_icon(snippet.author_email), class: "avatar s24", alt: ''
= snippet.author_name = snippet.author_name
authored #{time_ago_with_tooltip(snippet.created_at)} authored #{time_ago_with_tooltip(snippet.created_at)}
%ul.bordered-list %ul.content-list
= render partial: 'shared/snippets/snippet', collection: @snippets = render partial: 'shared/snippets/snippet', collection: @snippets
- if @snippets.empty? - if @snippets.empty?
%li %li
......
...@@ -27,7 +27,7 @@ class RepositoryForkWorker ...@@ -27,7 +27,7 @@ class RepositoryForkWorker
return return
end end
project.repository.expire_emptiness_caches project.repository.after_import
project.import_finish project.import_finish
end end
end end
...@@ -18,7 +18,7 @@ class RepositoryImportWorker ...@@ -18,7 +18,7 @@ class RepositoryImportWorker
return return
end end
project.repository.expire_emptiness_caches project.repository.after_import
project.import_finish project.import_finish
end end
end end
...@@ -42,7 +42,7 @@ Gitlab-shell communicates with Sidekiq via the “communication board” (Redis) ...@@ -42,7 +42,7 @@ Gitlab-shell communicates with Sidekiq via the “communication board” (Redis)
## System Layout ## System Layout
When referring to ~git in the pictures it means the home directory of the git user which is typically /home/git. When referring to `~git` in the pictures it means the home directory of the git user which is typically /home/git.
GitLab is primarily installed within the `/home/git` user home directory as `git` user. Within the home directory is where the gitlabhq server software resides as well as the repositories (though the repository location is configurable). GitLab is primarily installed within the `/home/git` user home directory as `git` user. Within the home directory is where the gitlabhq server software resides as well as the repositories (though the repository location is configurable).
......
...@@ -18,7 +18,7 @@ You accept and agree to the following terms and conditions for Your present and ...@@ -18,7 +18,7 @@ You accept and agree to the following terms and conditions for Your present and
6. You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON- INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. 6. You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON- INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE.
7. Should You wish to submit work that is not Your original creation, You may submit it to GitLab B.V. separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [[]named here]". 7. Should You wish to submit work that is not Your original creation, You may submit it to GitLab B.V. separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [insert_name_here]".
8. You agree to notify GitLab B.V. of any facts or circumstances of which you become aware that would make these representations inaccurate in any respect. 8. You agree to notify GitLab B.V. of any facts or circumstances of which you become aware that would make these representations inaccurate in any respect.
......
...@@ -6,7 +6,7 @@ If a user is both in a project group and in the project itself, the highest perm ...@@ -6,7 +6,7 @@ If a user is both in a project group and in the project itself, the highest perm
If a user is a GitLab administrator they receive all permissions. If a user is a GitLab administrator they receive all permissions.
On public projects the Guest role is not enforced. On public and internal projects the Guest role is not enforced.
All users will be able to create issues, leave comments, and pull or download the project code. All users will be able to create issues, leave comments, and pull or download the project code.
To add or import a user, you can follow the [project users and members To add or import a user, you can follow the [project users and members
...@@ -26,6 +26,7 @@ documentation](../workflow/add-user/add-user.md). ...@@ -26,6 +26,7 @@ documentation](../workflow/add-user/add-user.md).
| Create code snippets | | ✓ | ✓ | ✓ | ✓ | | Create code snippets | | ✓ | ✓ | ✓ | ✓ |
| Manage issue tracker | | ✓ | ✓ | ✓ | ✓ | | Manage issue tracker | | ✓ | ✓ | ✓ | ✓ |
| Manage labels | | ✓ | ✓ | ✓ | ✓ | | Manage labels | | ✓ | ✓ | ✓ | ✓ |
| See a commit status | | ✓ | ✓ | ✓ | ✓ |
| Manage merge requests | | | ✓ | ✓ | ✓ | | Manage merge requests | | | ✓ | ✓ | ✓ |
| Create new merge request | | | ✓ | ✓ | ✓ | | Create new merge request | | | ✓ | ✓ | ✓ |
| Create new branches | | | ✓ | ✓ | ✓ | | Create new branches | | | ✓ | ✓ | ✓ |
...@@ -35,6 +36,7 @@ documentation](../workflow/add-user/add-user.md). ...@@ -35,6 +36,7 @@ documentation](../workflow/add-user/add-user.md).
| Add tags | | | ✓ | ✓ | ✓ | | Add tags | | | ✓ | ✓ | ✓ |
| Write a wiki | | | ✓ | ✓ | ✓ | | Write a wiki | | | ✓ | ✓ | ✓ |
| Cancel and retry builds | | | ✓ | ✓ | ✓ | | Cancel and retry builds | | | ✓ | ✓ | ✓ |
| Create or update commit status | | | ✓ | ✓ | ✓ |
| Create new milestones | | | | ✓ | ✓ | | Create new milestones | | | | ✓ | ✓ |
| Add new team members | | | | ✓ | ✓ | | Add new team members | | | | ✓ | ✓ |
| Push to protected branches | | | | ✓ | ✓ | | Push to protected branches | | | | ✓ | ✓ |
......
...@@ -69,6 +69,7 @@ branches and tags. ...@@ -69,6 +69,7 @@ branches and tags.
```bash ```bash
git remote add origin git@gitlab.com:<group>/<project>.git git remote add origin git@gitlab.com:<group>/<project>.git
git push --all origin git push --all origin
git push --tags origin
``` ```
## Contribute to this guide ## Contribute to this guide
......
Feature: Login form
Scenario: I see Crowd form
Given Crowd integration enabled
When I visit sign in page
Then I should see Crowd login form
Scenario: I see Crowd form when sign-in is disabled
Given Crowd integration enabled
And Sign-in is disabled
When I visit sign in page
Then I should see Crowd login form
...@@ -3,6 +3,7 @@ Feature: Project Builds Summary ...@@ -3,6 +3,7 @@ Feature: Project Builds Summary
Given I sign in as a user Given I sign in as a user
And I own a project And I own a project
And project has CI enabled And project has CI enabled
And project has coverage enabled
And project has a recent build And project has a recent build
Scenario: I browse build details page Scenario: I browse build details page
...@@ -12,6 +13,7 @@ Feature: Project Builds Summary ...@@ -12,6 +13,7 @@ Feature: Project Builds Summary
Scenario: I browse project builds page Scenario: I browse project builds page
When I visit project builds page When I visit project builds page
Then I see coverage
Then I see button to CI Lint Then I see button to CI Lint
Scenario: I erase a build Scenario: I erase a build
......
class Spinach::Features::LoginForm < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedSnippet
include SharedUser
include SharedSearch
step 'Sign-in is disabled' do
allow_any_instance_of(ApplicationHelper).to receive(:signin_enabled?).and_return(false)
end
step 'Crowd integration enabled' do
expect(Gitlab::OAuth::Provider).to receive(:providers).and_return([:crowd])
expect(Gitlab.config.omniauth).to receive(:enabled).and_return(true)
allow_any_instance_of(ApplicationHelper).to receive(:user_omniauth_authorize_path).and_return(root_path)
end
step 'I should see Crowd login form' do
expect(page).to have_selector '#tab-crowd form'
end
step 'I visit sign in page' do
visit new_user_session_path
end
end
...@@ -27,9 +27,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps ...@@ -27,9 +27,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
end end
step 'I change my avatar' do step 'I change my avatar' do
attach_file(:user_avatar, File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif')) attach_avatar
click_button "Save changes"
@user.reload
end end
step 'I should see new avatar' do step 'I should see new avatar' do
...@@ -42,9 +40,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps ...@@ -42,9 +40,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
end end
step 'I have an avatar' do step 'I have an avatar' do
attach_file(:user_avatar, File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif')) attach_avatar
click_button "Save changes"
@user.reload
end end
step 'I remove my avatar' do step 'I remove my avatar' do
...@@ -233,4 +229,16 @@ class Spinach::Features::Profile < Spinach::FeatureSteps ...@@ -233,4 +229,16 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
step "I see that application is removed" do step "I see that application is removed" do
expect(page.find(".oauth-applications")).not_to have_content "test_changed" expect(page.find(".oauth-applications")).not_to have_content "test_changed"
end end
def attach_avatar
attach_file :user_avatar, Rails.root.join(*%w(spec fixtures banana_sample.gif))
page.find('#user_avatar_crop_x', visible: false).set('0')
page.find('#user_avatar_crop_y', visible: false).set('0')
page.find('#user_avatar_crop_size', visible: false).set('256')
click_button "Save changes"
@user.reload
end
end end
...@@ -4,6 +4,12 @@ class Spinach::Features::ProjectBuildsSummary < Spinach::FeatureSteps ...@@ -4,6 +4,12 @@ class Spinach::Features::ProjectBuildsSummary < Spinach::FeatureSteps
include SharedBuilds include SharedBuilds
include RepoHelpers include RepoHelpers
step 'I see coverage' do
page.within('td.coverage') do
expect(page).to have_content "99.9%"
end
end
step 'I see button to CI Lint' do step 'I see button to CI Lint' do
page.within('.nav-controls') do page.within('.nav-controls') do
ci_lint_tool_link = page.find_link('CI Lint') ci_lint_tool_link = page.find_link('CI Lint')
......
...@@ -5,9 +5,13 @@ module SharedBuilds ...@@ -5,9 +5,13 @@ module SharedBuilds
@project.enable_ci @project.enable_ci
end end
step 'project has coverage enabled' do
@project.update_attribute(:build_coverage_regex, /Coverage (\d+)%/)
end
step 'project has a recent build' do step 'project has a recent build' do
@ci_commit = create(:ci_commit, project: @project, sha: @project.commit.sha) @ci_commit = create(:ci_commit, project: @project, sha: @project.commit.sha)
@build = create(:ci_build, commit: @ci_commit) @build = create(:ci_build_with_coverage, commit: @ci_commit)
end end
step 'recent build is successful' do step 'recent build is successful' do
......
...@@ -6,7 +6,7 @@ timeout = (ENV['CI'] || ENV['CI_SERVER']) ? 90 : 15 ...@@ -6,7 +6,7 @@ timeout = (ENV['CI'] || ENV['CI_SERVER']) ? 90 : 15
Capybara.javascript_driver = :poltergeist Capybara.javascript_driver = :poltergeist
Capybara.register_driver :poltergeist do |app| Capybara.register_driver :poltergeist do |app|
Capybara::Poltergeist::Driver.new(app, js_errors: true, timeout: timeout) Capybara::Poltergeist::Driver.new(app, js_errors: true, timeout: timeout, window_size: [1366, 768])
end end
Capybara.default_wait_time = timeout Capybara.default_wait_time = timeout
......
#!/bin/bash
# Sends Slack notification ERROR_MSG to CHANNEL
# An env. variable CI_SLACK_WEBHOOK_URL needs to be set.
CHANNEL=$1
ERROR_MSG=$2
if [ -z "$CHANNEL" ] || [ -z "$ERROR_MSG" ] || [ -z "$CI_SLACK_WEBHOOK_URL" ]; then
echo "Missing argument(s) - Use: $0 channel message"
echo "and set CI_SLACK_WEBHOOK_URL environment variable."
else
curl -X POST --data-urlencode 'payload={"channel": "'"$CHANNEL"'", "username": "gitlab-ci", "text": "'"$ERROR_MSG"'", "icon_emoji": ":gitlab:"}' "$CI_SLACK_WEBHOOK_URL"
fi
\ No newline at end of file
require 'spec_helper' require 'spec_helper'
describe NamespacesController do describe NamespacesController do
let!(:user) { create(:user, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) } let!(:user) { create(:user, :with_avatar) }
describe "GET show" do describe "GET show" do
context "when the namespace belongs to a user" do context "when the namespace belongs to a user" do
......
require 'spec_helper' require 'spec_helper'
describe Profiles::AvatarsController do describe Profiles::AvatarsController do
let(:user) { create(:user, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png")) } let(:user) { create(:user, :with_avatar) }
before do before do
sign_in(user) sign_in(user)
......
require 'spec_helper' require 'spec_helper'
describe UploadsController do describe UploadsController do
let!(:user) { create(:user, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) } let!(:user) { create(:user, :with_avatar) }
describe "GET show" do describe "GET show" do
context "when viewing a user avatar" do context "when viewing a user avatar" do
......
...@@ -36,6 +36,13 @@ FactoryGirl.define do ...@@ -36,6 +36,13 @@ FactoryGirl.define do
end end
end end
trait :with_avatar do
avatar { fixture_file_upload(Rails.root.join(*%w(spec fixtures dk.png)), 'image/png') }
avatar_crop_x 0
avatar_crop_y 0
avatar_crop_size 256
end
factory :omniauth_user do factory :omniauth_user do
ignore do ignore do
extern_uid '123456' extern_uid '123456'
......
...@@ -53,6 +53,10 @@ FactoryGirl.define do ...@@ -53,6 +53,10 @@ FactoryGirl.define do
tag true tag true
end end
factory :ci_build_with_coverage do
coverage 99.9
end
trait :trace do trait :trace do
after(:create) do |build, evaluator| after(:create) do |build, evaluator|
build.trace = 'BUILD TRACE' build.trace = 'BUILD TRACE'
......
...@@ -13,7 +13,7 @@ feature 'Issue filtering by Milestone', feature: true do ...@@ -13,7 +13,7 @@ feature 'Issue filtering by Milestone', feature: true do
visit_issues(project) visit_issues(project)
filter_by_milestone(Milestone::None.title) filter_by_milestone(Milestone::None.title)
expect(page).to have_css('.title', count: 1) expect(page).to have_css('.issue .title', count: 1)
end end
scenario 'filters by a specific Milestone', js: true do scenario 'filters by a specific Milestone', js: true do
...@@ -23,7 +23,7 @@ feature 'Issue filtering by Milestone', feature: true do ...@@ -23,7 +23,7 @@ feature 'Issue filtering by Milestone', feature: true do
visit_issues(project) visit_issues(project)
filter_by_milestone(milestone.title) filter_by_milestone(milestone.title)
expect(page).to have_css('.title', count: 1) expect(page).to have_css('.issue .title', count: 1)
end end
def visit_issues(project) def visit_issues(project)
......
...@@ -77,7 +77,7 @@ describe ApplicationHelper do ...@@ -77,7 +77,7 @@ describe ApplicationHelper do
let(:avatar_file_path) { File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif') } let(:avatar_file_path) { File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif') }
it 'should return an url for the avatar' do it 'should return an url for the avatar' do
user = create(:user, avatar: File.open(avatar_file_path)) user = create(:user, :with_avatar, avatar: File.open(avatar_file_path))
expect(helper.avatar_icon(user.email).to_s). expect(helper.avatar_icon(user.email).to_s).
to match("/uploads/user/avatar/#{user.id}/banana_sample.gif") to match("/uploads/user/avatar/#{user.id}/banana_sample.gif")
...@@ -88,7 +88,7 @@ describe ApplicationHelper do ...@@ -88,7 +88,7 @@ describe ApplicationHelper do
# Must be stubbed after the stub above, and separately # Must be stubbed after the stub above, and separately
stub_config_setting(url: Settings.send(:build_gitlab_url)) stub_config_setting(url: Settings.send(:build_gitlab_url))
user = create(:user, avatar: File.open(avatar_file_path)) user = create(:user, :with_avatar, avatar: File.open(avatar_file_path))
expect(helper.avatar_icon(user.email).to_s). expect(helper.avatar_icon(user.email).to_s).
to match("/gitlab/uploads/user/avatar/#{user.id}/banana_sample.gif") to match("/gitlab/uploads/user/avatar/#{user.id}/banana_sample.gif")
...@@ -102,7 +102,7 @@ describe ApplicationHelper do ...@@ -102,7 +102,7 @@ describe ApplicationHelper do
describe 'using a User' do describe 'using a User' do
it 'should return an URL for the avatar' do it 'should return an URL for the avatar' do
user = create(:user, avatar: File.open(avatar_file_path)) user = create(:user, :with_avatar, avatar: File.open(avatar_file_path))
expect(helper.avatar_icon(user).to_s). expect(helper.avatar_icon(user).to_s).
to match("/uploads/user/avatar/#{user.id}/banana_sample.gif") to match("/uploads/user/avatar/#{user.id}/banana_sample.gif")
......
...@@ -19,6 +19,10 @@ ...@@ -19,6 +19,10 @@
require 'spec_helper' require 'spec_helper'
describe ProjectHook, models: true do describe ProjectHook, models: true do
describe "Associations" do
it { is_expected.to belong_to :project }
end
describe '.push_hooks' do describe '.push_hooks' do
it 'should return hooks for push events only' do it 'should return hooks for push events only' do
hook = create(:project_hook, push_events: true) hook = create(:project_hook, push_events: true)
......
...@@ -18,20 +18,14 @@ ...@@ -18,20 +18,14 @@
require 'spec_helper' require 'spec_helper'
describe ProjectHook, models: true do describe WebHook, models: true do
describe "Associations" do
it { is_expected.to belong_to :project }
end
describe "Mass assignment" do
end
describe "Validations" do describe "Validations" do
it { is_expected.to validate_presence_of(:url) } it { is_expected.to validate_presence_of(:url) }
context "url format" do describe 'url' do
it { is_expected.to allow_value("http://example.com").for(:url) } it { is_expected.to allow_value("http://example.com").for(:url) }
it { is_expected.to allow_value("https://excample.com").for(:url) } it { is_expected.to allow_value("https://example.com").for(:url) }
it { is_expected.to allow_value(" https://example.com ").for(:url) }
it { is_expected.to allow_value("http://test.com/api").for(:url) } it { is_expected.to allow_value("http://test.com/api").for(:url) }
it { is_expected.to allow_value("http://test.com/api?key=abc").for(:url) } it { is_expected.to allow_value("http://test.com/api?key=abc").for(:url) }
it { is_expected.to allow_value("http://test.com/api?key=abc&type=def").for(:url) } it { is_expected.to allow_value("http://test.com/api?key=abc&type=def").for(:url) }
...@@ -39,6 +33,12 @@ describe ProjectHook, models: true do ...@@ -39,6 +33,12 @@ describe ProjectHook, models: true do
it { is_expected.not_to allow_value("example.com").for(:url) } it { is_expected.not_to allow_value("example.com").for(:url) }
it { is_expected.not_to allow_value("ftp://example.com").for(:url) } it { is_expected.not_to allow_value("ftp://example.com").for(:url) }
it { is_expected.not_to allow_value("herp-and-derp").for(:url) } it { is_expected.not_to allow_value("herp-and-derp").for(:url) }
it 'strips :url before saving it' do
hook = create(:project_hook, url: ' https://example.com ')
expect(hook.url).to eq('https://example.com')
end
end end
end end
......
...@@ -362,14 +362,14 @@ describe Repository, models: true do ...@@ -362,14 +362,14 @@ describe Repository, models: true do
repository.expire_cache('master') repository.expire_cache('master')
end end
it 'expires the emptiness cache for an empty repository' do it 'expires the emptiness caches for an empty repository' do
expect(repository).to receive(:empty?).and_return(true) expect(repository).to receive(:empty?).and_return(true)
expect(repository).to receive(:expire_emptiness_caches) expect(repository).to receive(:expire_emptiness_caches)
repository.expire_cache repository.expire_cache
end end
it 'does not expire the emptiness cache for a non-empty repository' do it 'does not expire the emptiness caches for a non-empty repository' do
expect(repository).to receive(:empty?).and_return(false) expect(repository).to receive(:empty?).and_return(false)
expect(repository).to_not receive(:expire_emptiness_caches) expect(repository).to_not receive(:expire_emptiness_caches)
...@@ -464,4 +464,108 @@ describe Repository, models: true do ...@@ -464,4 +464,108 @@ describe Repository, models: true do
expect(repository.blob_at_branch('master', 'files/ruby/feature.rb')).not_to be_present expect(repository.blob_at_branch('master', 'files/ruby/feature.rb')).not_to be_present
end end
end end
describe '#before_delete' do
describe 'when a repository does not exist' do
before do
allow(repository).to receive(:exists?).and_return(false)
end
it 'does not flush caches that depend on repository data' do
expect(repository).to_not receive(:expire_cache)
repository.before_delete
end
it 'flushes the root ref cache' do
expect(repository).to receive(:expire_root_ref_cache)
repository.before_delete
end
it 'flushes the emptiness caches' do
expect(repository).to receive(:expire_emptiness_caches)
repository.before_delete
end
end
describe 'when a repository exists' do
before do
allow(repository).to receive(:exists?).and_return(true)
end
it 'flushes the caches that depend on repository data' do
expect(repository).to receive(:expire_cache)
repository.before_delete
end
it 'flushes the root ref cache' do
expect(repository).to receive(:expire_root_ref_cache)
repository.before_delete
end
it 'flushes the emptiness caches' do
expect(repository).to receive(:expire_emptiness_caches)
repository.before_delete
end
end
end
describe '#before_change_head' do
it 'flushes the branch cache' do
expect(repository).to receive(:expire_branch_cache)
repository.before_change_head
end
it 'flushes the root ref cache' do
expect(repository).to receive(:expire_root_ref_cache)
repository.before_change_head
end
end
describe '#before_create_tag' do
it 'flushes the cache' do
expect(repository).to receive(:expire_cache)
repository.before_create_tag
end
end
describe '#after_import' do
it 'flushes the emptiness cachess' do
expect(repository).to receive(:expire_emptiness_caches)
repository.after_import
end
end
describe '#after_push_commit' do
it 'flushes the cache' do
expect(repository).to receive(:expire_cache).with('master')
repository.after_push_commit('master')
end
end
describe '#after_create_branch' do
it 'flushes the visible content cache' do
expect(repository).to receive(:expire_has_visible_content_cache)
repository.after_create_branch
end
end
describe '#after_remove_branch' do
it 'flushes the visible content cache' do
expect(repository).to receive(:expire_has_visible_content_cache)
repository.after_remove_branch
end
end
end end
...@@ -174,6 +174,18 @@ describe User, models: true do ...@@ -174,6 +174,18 @@ describe User, models: true do
end end
end end
end end
describe 'avatar' do
it 'only validates when avatar is present' do
user = build(:user, :with_avatar)
user.avatar_crop_x = nil
user.avatar_crop_y = nil
user.avatar_crop_size = nil
expect(user).not_to be_valid
end
end
end end
describe "Respond to" do describe "Respond to" do
......
...@@ -110,6 +110,8 @@ describe TodoService, services: true do ...@@ -110,6 +110,8 @@ describe TodoService, services: true do
let!(:first_todo) { create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author) } let!(:first_todo) { create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author) }
let!(:second_todo) { create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author) } let!(:second_todo) { create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author) }
let(:note) { create(:note, project: project, noteable: issue, author: john_doe, note: mentions) } let(:note) { create(:note, project: project, noteable: issue, author: john_doe, note: mentions) }
let(:note_on_commit) { create(:note_on_commit, project: project, author: john_doe, note: mentions) }
let(:note_on_project_snippet) { create(:note_on_project_snippet, project: project, author: john_doe, note: mentions) }
let(:award_note) { create(:note, :award, project: project, noteable: issue, author: john_doe, note: 'thumbsup') } let(:award_note) { create(:note, :award, project: project, noteable: issue, author: john_doe, note: 'thumbsup') }
let(:system_note) { create(:system_note, project: project, noteable: issue) } let(:system_note) { create(:system_note, project: project, noteable: issue) }
...@@ -145,6 +147,14 @@ describe TodoService, services: true do ...@@ -145,6 +147,14 @@ describe TodoService, services: true do
should_not_create_todo(user: john_doe, target: issue, author: john_doe, action: Todo::MENTIONED, note: note) should_not_create_todo(user: john_doe, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
should_not_create_todo(user: stranger, target: issue, author: john_doe, action: Todo::MENTIONED, note: note) should_not_create_todo(user: stranger, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
end end
it 'does not create todo when leaving a note on commit' do
should_not_create_any_todo { service.new_note(note_on_commit, john_doe) }
end
it 'does not create todo when leaving a note on snippet' do
should_not_create_any_todo { service.new_note(note_on_project_snippet, john_doe) }
end
end end
end end
......
...@@ -7,7 +7,7 @@ timeout = (ENV['CI'] || ENV['CI_SERVER']) ? 90 : 10 ...@@ -7,7 +7,7 @@ timeout = (ENV['CI'] || ENV['CI_SERVER']) ? 90 : 10
Capybara.javascript_driver = :poltergeist Capybara.javascript_driver = :poltergeist
Capybara.register_driver :poltergeist do |app| Capybara.register_driver :poltergeist do |app|
Capybara::Poltergeist::Driver.new(app, js_errors: true, timeout: timeout) Capybara::Poltergeist::Driver.new(app, js_errors: true, timeout: timeout, window_size: [1366, 768])
end end
Capybara.default_wait_time = timeout Capybara.default_wait_time = timeout
......
require 'rails_helper'
describe 'devise/shared/_signin_box' do
describe 'Crowd form' do
before do
stub_devise
assign(:ldap_servers, [])
end
it 'is shown when Crowd is enabled' do
enable_crowd
render
expect(rendered).to have_selector('#tab-crowd form')
end
it 'is not shown when Crowd is disabled' do
render
expect(rendered).not_to have_selector('#tab-crowd')
end
end
def stub_devise
allow(view).to receive(:devise_mapping).and_return(Devise.mappings[:user])
allow(view).to receive(:resource).and_return(spy)
allow(view).to receive(:resource_name).and_return(:user)
end
def enable_crowd
allow(view).to receive(:form_based_providers).and_return([:crowd])
allow(view).to receive(:crowd_enabled?).and_return(true)
allow(view).to receive(:user_omniauth_authorize_path).with('crowd').
and_return('/crowd')
end
end
/*!
* Cropper v2.2.5
* https://github.com/fengyuanchen/cropper
*
* Copyright (c) 2014-2016 Fengyuan Chen and contributors
* Released under the MIT license
*
* Date: 2016-01-18T05:42:29.639Z
*/
.cropper-container {
font-size: 0;
line-height: 0;
position: relative;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
direction: ltr !important;
-ms-touch-action: none;
touch-action: none;
-webkit-tap-highlight-color: transparent;
-webkit-touch-callout: none;
}
.cropper-container img {
display: block;
width: 100%;
min-width: 0 !important;
max-width: none !important;
height: 100%;
min-height: 0 !important;
max-height: none !important;
image-orientation: 0deg !important;
}
.cropper-wrap-box,
.cropper-canvas,
.cropper-drag-box,
.cropper-crop-box,
.cropper-modal {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.cropper-wrap-box {
overflow: hidden;
}
.cropper-drag-box {
opacity: 0;
background-color: #fff;
filter: alpha(opacity=0);
}
.cropper-modal {
opacity: .5;
background-color: #000;
filter: alpha(opacity=50);
}
.cropper-view-box {
display: block;
overflow: hidden;
width: 100%;
height: 100%;
outline: 1px solid #39f;
outline-color: rgba(51, 153, 255, .75);
}
.cropper-dashed {
position: absolute;
display: block;
opacity: .5;
border: 0 dashed #eee;
filter: alpha(opacity=50);
}
.cropper-dashed.dashed-h {
top: 33.33333%;
left: 0;
width: 100%;
height: 33.33333%;
border-top-width: 1px;
border-bottom-width: 1px;
}
.cropper-dashed.dashed-v {
top: 0;
left: 33.33333%;
width: 33.33333%;
height: 100%;
border-right-width: 1px;
border-left-width: 1px;
}
.cropper-center {
position: absolute;
top: 50%;
left: 50%;
display: block;
width: 0;
height: 0;
opacity: .75;
filter: alpha(opacity=75);
}
.cropper-center:before,
.cropper-center:after {
position: absolute;
display: block;
content: ' ';
background-color: #eee;
}
.cropper-center:before {
top: 0;
left: -3px;
width: 7px;
height: 1px;
}
.cropper-center:after {
top: -3px;
left: 0;
width: 1px;
height: 7px;
}
.cropper-face,
.cropper-line,
.cropper-point {
position: absolute;
display: block;
width: 100%;
height: 100%;
opacity: .1;
filter: alpha(opacity=10);
}
.cropper-face {
top: 0;
left: 0;
background-color: #fff;
}
.cropper-line {
background-color: #39f;
}
.cropper-line.line-e {
top: 0;
right: -3px;
width: 5px;
cursor: e-resize;
}
.cropper-line.line-n {
top: -3px;
left: 0;
height: 5px;
cursor: n-resize;
}
.cropper-line.line-w {
top: 0;
left: -3px;
width: 5px;
cursor: w-resize;
}
.cropper-line.line-s {
bottom: -3px;
left: 0;
height: 5px;
cursor: s-resize;
}
.cropper-point {
width: 5px;
height: 5px;
opacity: .75;
background-color: #39f;
filter: alpha(opacity=75);
}
.cropper-point.point-e {
top: 50%;
right: -3px;
margin-top: -3px;
cursor: e-resize;
}
.cropper-point.point-n {
top: -3px;
left: 50%;
margin-left: -3px;
cursor: n-resize;
}
.cropper-point.point-w {
top: 50%;
left: -3px;
margin-top: -3px;
cursor: w-resize;
}
.cropper-point.point-s {
bottom: -3px;
left: 50%;
margin-left: -3px;
cursor: s-resize;
}
.cropper-point.point-ne {
top: -3px;
right: -3px;
cursor: ne-resize;
}
.cropper-point.point-nw {
top: -3px;
left: -3px;
cursor: nw-resize;
}
.cropper-point.point-sw {
bottom: -3px;
left: -3px;
cursor: sw-resize;
}
.cropper-point.point-se {
right: -3px;
bottom: -3px;
width: 20px;
height: 20px;
cursor: se-resize;
opacity: 1;
filter: alpha(opacity=100);
}
.cropper-point.point-se:before {
position: absolute;
right: -50%;
bottom: -50%;
display: block;
width: 200%;
height: 200%;
content: ' ';
opacity: 0;
background-color: #39f;
filter: alpha(opacity=0);
}
@media (min-width: 768px) {
.cropper-point.point-se {
width: 15px;
height: 15px;
}
}
@media (min-width: 992px) {
.cropper-point.point-se {
width: 10px;
height: 10px;
}
}
@media (min-width: 1200px) {
.cropper-point.point-se {
width: 5px;
height: 5px;
opacity: .75;
filter: alpha(opacity=75);
}
}
.cropper-invisible {
opacity: 0;
filter: alpha(opacity=0);
}
.cropper-bg {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC');
}
.cropper-hide {
position: absolute;
display: block;
width: 0;
height: 0;
}
.cropper-hidden {
display: none !important;
}
.cropper-move {
cursor: move;
}
.cropper-crop {
cursor: crosshair;
}
.cropper-disabled .cropper-drag-box,
.cropper-disabled .cropper-face,
.cropper-disabled .cropper-line,
.cropper-disabled .cropper-point {
cursor: not-allowed;
}
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