Commit ccbeb64e authored by Valery Sizov's avatar Valery Sizov

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into ce_upstream

parents a00dc548 204a9895
...@@ -90,6 +90,7 @@ spec:other: ...@@ -90,6 +90,7 @@ spec:other:
spinach:project:half: spinach:project:half:
stage: test stage: test
script: script:
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:half - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:half
tags: tags:
- ruby - ruby
...@@ -98,6 +99,7 @@ spinach:project:half: ...@@ -98,6 +99,7 @@ spinach:project:half:
spinach:project:rest: spinach:project:rest:
stage: test stage: test
script: script:
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest
tags: tags:
- ruby - ruby
...@@ -106,6 +108,7 @@ spinach:project:rest: ...@@ -106,6 +108,7 @@ spinach:project:rest:
spinach:other: spinach:other:
stage: test stage: test
script: script:
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:other - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:other
tags: tags:
- ruby - ruby
...@@ -276,6 +279,7 @@ spinach:project:half:ruby22: ...@@ -276,6 +279,7 @@ spinach:project:half:ruby22:
only: only:
- master - master
script: script:
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:half - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:half
cache: cache:
key: "ruby22" key: "ruby22"
...@@ -291,6 +295,7 @@ spinach:project:rest:ruby22: ...@@ -291,6 +295,7 @@ spinach:project:rest:ruby22:
only: only:
- master - master
script: script:
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest
cache: cache:
key: "ruby22" key: "ruby22"
...@@ -306,6 +311,7 @@ spinach:other:ruby22: ...@@ -306,6 +311,7 @@ spinach:other:ruby22:
only: only:
- master - master
script: script:
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:other - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:other
cache: cache:
key: "ruby22" key: "ruby22"
...@@ -319,7 +325,7 @@ spinach:other:ruby22: ...@@ -319,7 +325,7 @@ spinach:other:ruby22:
notify:slack: notify:slack:
stage: notifications stage: notifications
script: script:
- ./scripts/notify_slack.sh "#builds" "Build failed for master/tags!" - ./scripts/notify_slack.sh "#builds" "Build on \`$CI_BUILD_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_BUILD_REF"/builds>"
when: on_failure when: on_failure
only: only:
- master@gitlab-org/gitlab-ce - master@gitlab-org/gitlab-ce
......
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.6.0 (unreleased) v 8.6.0 (unreleased)
- Contributions to forked projects are included in calendar
- 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 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,9 +30,7 @@ v 8.5.1 ...@@ -21,9 +30,7 @@ v 8.5.1
- 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) - Add build coverage in project's builds page (Steffen Köhler)
- Changed # to ! for merge requests in activity view
v 8.5.2
- Fix error 500 when commenting on a commit
v 8.5.1 v 8.5.1
- Fix group projects styles - Fix group projects styles
......
source "https://rubygems.org" source "https://rubygems.org"
gem 'rails', '4.2.5.1' gem 'rails', '4.2.5.2'
gem 'rails-deprecated_sanitizer', '~> 1.0.3' gem 'rails-deprecated_sanitizer', '~> 1.0.3'
# Responders respond_to and respond_with # Responders respond_to and respond_with
...@@ -82,6 +82,9 @@ gem "haml-rails", '~> 0.9.0' ...@@ -82,6 +82,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'
...@@ -220,7 +223,6 @@ gem 'jquery-atwho-rails', '~> 1.3.2' ...@@ -220,7 +223,6 @@ gem 'jquery-atwho-rails', '~> 1.3.2'
gem 'jquery-rails', '~> 4.0.0' gem 'jquery-rails', '~> 4.0.0'
gem 'jquery-scrollto-rails', '~> 1.4.3' gem 'jquery-scrollto-rails', '~> 1.4.3'
gem 'jquery-ui-rails', '~> 5.0.0' gem 'jquery-ui-rails', '~> 5.0.0'
gem 'nprogress-rails', '~> 0.1.6.7'
gem 'raphael-rails', '~> 2.1.2' gem 'raphael-rails', '~> 2.1.2'
gem 'request_store', '~> 1.2.0' gem 'request_store', '~> 1.2.0'
gem 'select2-rails', '~> 3.5.9' gem 'select2-rails', '~> 3.5.9'
......
...@@ -4,41 +4,41 @@ GEM ...@@ -4,41 +4,41 @@ GEM
CFPropertyList (2.3.2) CFPropertyList (2.3.2)
RedCloth (4.2.9) RedCloth (4.2.9)
ace-rails-ap (2.0.1) ace-rails-ap (2.0.1)
actionmailer (4.2.5.1) actionmailer (4.2.5.2)
actionpack (= 4.2.5.1) actionpack (= 4.2.5.2)
actionview (= 4.2.5.1) actionview (= 4.2.5.2)
activejob (= 4.2.5.1) activejob (= 4.2.5.2)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 1.0, >= 1.0.5) rails-dom-testing (~> 1.0, >= 1.0.5)
actionpack (4.2.5.1) actionpack (4.2.5.2)
actionview (= 4.2.5.1) actionview (= 4.2.5.2)
activesupport (= 4.2.5.1) activesupport (= 4.2.5.2)
rack (~> 1.6) rack (~> 1.6)
rack-test (~> 0.6.2) rack-test (~> 0.6.2)
rails-dom-testing (~> 1.0, >= 1.0.5) rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2) rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (4.2.5.1) actionview (4.2.5.2)
activesupport (= 4.2.5.1) activesupport (= 4.2.5.2)
builder (~> 3.1) builder (~> 3.1)
erubis (~> 2.7.0) erubis (~> 2.7.0)
rails-dom-testing (~> 1.0, >= 1.0.5) rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2) rails-html-sanitizer (~> 1.0, >= 1.0.2)
activejob (4.2.5.1) activejob (4.2.5.2)
activesupport (= 4.2.5.1) activesupport (= 4.2.5.2)
globalid (>= 0.3.0) globalid (>= 0.3.0)
activemodel (4.2.5.1) activemodel (4.2.5.2)
activesupport (= 4.2.5.1) activesupport (= 4.2.5.2)
builder (~> 3.1) builder (~> 3.1)
activerecord (4.2.5.1) activerecord (4.2.5.2)
activemodel (= 4.2.5.1) activemodel (= 4.2.5.2)
activesupport (= 4.2.5.1) activesupport (= 4.2.5.2)
arel (~> 6.0) arel (~> 6.0)
activerecord-deprecated_finders (1.0.4) activerecord-deprecated_finders (1.0.4)
activerecord-session_store (0.1.2) activerecord-session_store (0.1.2)
actionpack (>= 4.0.0, < 5) actionpack (>= 4.0.0, < 5)
activerecord (>= 4.0.0, < 5) activerecord (>= 4.0.0, < 5)
railties (>= 4.0.0, < 5) railties (>= 4.0.0, < 5)
activesupport (4.2.5.1) activesupport (4.2.5.2)
i18n (~> 0.7) i18n (~> 0.7)
json (~> 1.7, >= 1.7.7) json (~> 1.7, >= 1.7.7)
minitest (~> 5.1) minitest (~> 5.1)
...@@ -492,6 +492,7 @@ GEM ...@@ -492,6 +492,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)
...@@ -506,7 +507,6 @@ GEM ...@@ -506,7 +507,6 @@ GEM
newrelic_rpm (3.14.1.311) newrelic_rpm (3.14.1.311)
nokogiri (1.6.7.2) nokogiri (1.6.7.2)
mini_portile2 (~> 2.0.0.rc2) mini_portile2 (~> 2.0.0.rc2)
nprogress-rails (0.1.6.7)
oauth (0.4.7) oauth (0.4.7)
oauth2 (1.0.0) oauth2 (1.0.0)
faraday (>= 0.8, < 0.10) faraday (>= 0.8, < 0.10)
...@@ -610,16 +610,16 @@ GEM ...@@ -610,16 +610,16 @@ GEM
rack rack
rack-test (0.6.3) rack-test (0.6.3)
rack (>= 1.0) rack (>= 1.0)
rails (4.2.5.1) rails (4.2.5.2)
actionmailer (= 4.2.5.1) actionmailer (= 4.2.5.2)
actionpack (= 4.2.5.1) actionpack (= 4.2.5.2)
actionview (= 4.2.5.1) actionview (= 4.2.5.2)
activejob (= 4.2.5.1) activejob (= 4.2.5.2)
activemodel (= 4.2.5.1) activemodel (= 4.2.5.2)
activerecord (= 4.2.5.1) activerecord (= 4.2.5.2)
activesupport (= 4.2.5.1) activesupport (= 4.2.5.2)
bundler (>= 1.3.0, < 2.0) bundler (>= 1.3.0, < 2.0)
railties (= 4.2.5.1) railties (= 4.2.5.2)
sprockets-rails sprockets-rails
rails-deprecated_sanitizer (1.0.3) rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha) activesupport (>= 4.2.0.alpha)
...@@ -629,9 +629,9 @@ GEM ...@@ -629,9 +629,9 @@ GEM
rails-deprecated_sanitizer (>= 1.0.1) rails-deprecated_sanitizer (>= 1.0.1)
rails-html-sanitizer (1.0.3) rails-html-sanitizer (1.0.3)
loofah (~> 2.0) loofah (~> 2.0)
railties (4.2.5.1) railties (4.2.5.2)
actionpack (= 4.2.5.1) actionpack (= 4.2.5.2)
activesupport (= 4.2.5.1) activesupport (= 4.2.5.2)
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0) thor (>= 0.18.1, < 2.0)
rainbow (2.0.0) rainbow (2.0.0)
...@@ -987,6 +987,7 @@ DEPENDENCIES ...@@ -987,6 +987,7 @@ 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)
...@@ -995,7 +996,6 @@ DEPENDENCIES ...@@ -995,7 +996,6 @@ DEPENDENCIES
net-ssh (~> 3.0.1) net-ssh (~> 3.0.1)
newrelic_rpm (~> 3.14) newrelic_rpm (~> 3.14)
nokogiri (~> 1.6.7, >= 1.6.7.2) nokogiri (~> 1.6.7, >= 1.6.7.2)
nprogress-rails (~> 0.1.6.7)
oauth2 (~> 1.0.0) oauth2 (~> 1.0.0)
octokit (~> 3.8.0) octokit (~> 3.8.0)
omniauth (~> 1.3.1) omniauth (~> 1.3.1)
...@@ -1020,7 +1020,7 @@ DEPENDENCIES ...@@ -1020,7 +1020,7 @@ DEPENDENCIES
rack-attack (~> 4.3.1) rack-attack (~> 4.3.1)
rack-cors (~> 0.4.0) rack-cors (~> 0.4.0)
rack-oauth2 (~> 1.2.1) rack-oauth2 (~> 1.2.1)
rails (= 4.2.5.1) rails (= 4.2.5.2)
rails-deprecated_sanitizer (~> 1.0.3) rails-deprecated_sanitizer (~> 1.0.3)
raphael-rails (~> 2.1.2) raphael-rails (~> 2.1.2)
rblineprof rblineprof
......
...@@ -32,8 +32,6 @@ ...@@ -32,8 +32,6 @@
#= require ace/ace #= require ace/ace
#= require ace/ext-searchbox #= require ace/ext-searchbox
#= require underscore #= require underscore
#= require nprogress
#= require nprogress-turbolinks
#= require dropzone #= require dropzone
#= require mousetrap #= require mousetrap
#= require mousetrap/pause #= require mousetrap/pause
...@@ -45,6 +43,7 @@ ...@@ -45,6 +43,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,11 +16,11 @@ class @Autosave ...@@ -16,11 +16,11 @@ 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
@field.trigger "input" @field.trigger "input"
save: -> save: ->
return unless window.localStorage? return unless window.localStorage?
...@@ -35,5 +35,5 @@ class @Autosave ...@@ -35,5 +35,5 @@ class @Autosave
reset: -> reset: ->
return unless window.localStorage? return unless window.localStorage?
try try
window.localStorage.removeItem @key window.localStorage.removeItem @key
NProgress.configure(showSpinner: false) Turbolinks.enableProgressBar();
defaultClass = 'tanuki-shape' defaultClass = 'tanuki-shape'
pieces = [ pieces = [
......
...@@ -146,6 +146,7 @@ class @MergeRequestTabs ...@@ -146,6 +146,7 @@ class @MergeRequestTabs
url: "#{source}.json" + @_location.search url: "#{source}.json" + @_location.search
success: (data) => success: (data) =>
document.querySelector("div#diffs").innerHTML = data.html document.querySelector("div#diffs").innerHTML = data.html
$('.js-timeago').timeago()
$('div#diffs .js-syntax-highlight').syntaxHighlight() $('div#diffs .js-syntax-highlight').syntaxHighlight()
@expandViewContainer() if @diffViewType() is 'parallel' @expandViewContainer() if @diffViewType() is 'parallel'
@diffsLoaded = true @diffsLoaded = true
......
...@@ -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])
...@@ -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
*/ */
/* /*
...@@ -24,12 +25,6 @@ ...@@ -24,12 +25,6 @@
*/ */
@import "framework"; @import "framework";
/*
* NProgress load bar css
*/
@import 'nprogress';
@import 'nprogress-bootstrap';
/* /*
* Font icons * Font icons
*/ */
......
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
@import "framework/mobile.scss"; @import "framework/mobile.scss";
@import "framework/nav.scss"; @import "framework/nav.scss";
@import "framework/pagination.scss"; @import "framework/pagination.scss";
@import "framework/progress.scss";
@import "framework/panels.scss"; @import "framework/panels.scss";
@import "framework/selects.scss"; @import "framework/selects.scss";
@import "framework/sidebar.scss"; @import "framework/sidebar.scss";
......
...@@ -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
......
html.turbolinks-progress-bar::before {
background-color: $progress-color!important;
height: 2px!important;
box-shadow: 0 0 10px $progress-color, 0 0 5px $progress-color;
}
...@@ -13,6 +13,19 @@ ...@@ -13,6 +13,19 @@
transition-duration: .3s; transition-duration: .3s;
} }
.home {
z-index: 1;
position: absolute;
left: 0px;
}
#logo {
z-index: 2;
position: absolute;
width: 58px;
cursor: pointer;
}
&.right-sidebar-expanded { &.right-sidebar-expanded {
padding-right: $gutter_width; padding-right: $gutter_width;
} }
...@@ -74,7 +87,7 @@ ...@@ -74,7 +87,7 @@
width: 158px; width: 158px;
float: left; float: left;
margin: 0; margin: 0;
margin-left: 14px; margin-left: 50px;
font-size: 19px; font-size: 19px;
line-height: 41px; line-height: 41px;
font-weight: normal; font-weight: normal;
......
...@@ -7,7 +7,7 @@ $gl-header-color: #323232; ...@@ -7,7 +7,7 @@ $gl-header-color: #323232;
$gl-link-color: #333c48; $gl-link-color: #333c48;
$md-text-color: #444; $md-text-color: #444;
$md-link-color: #3084bb; $md-link-color: #3084bb;
$nprogress-color: #c0392b; $progress-color: #c0392b;
$gl-font-size: 15px; $gl-font-size: 15px;
$list-font-size: 15px; $list-font-size: 15px;
$sidebar_collapsed_width: 62px; $sidebar_collapsed_width: 62px;
...@@ -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
......
...@@ -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 {
......
...@@ -14,6 +14,18 @@ ul.notes { ...@@ -14,6 +14,18 @@ ul.notes {
margin: 0px; margin: 0px;
padding: 0px; padding: 0px;
.timeline-icon {
float: left;
}
.timeline-content {
margin-left: 55px;
}
.note_created_ago, .note-updated-at {
white-space: nowrap;
}
.system-note { .system-note {
font-size: 14px; font-size: 14px;
padding-top: 10px; padding-top: 10px;
...@@ -151,6 +163,7 @@ ul.notes { ...@@ -151,6 +163,7 @@ ul.notes {
border-left: none; border-left: none;
&.notes_line { &.notes_line {
vertical-align: middle;
text-align: center; text-align: center;
padding: 10px 0; padding: 10px 0;
background: #FFF; background: #FFF;
......
...@@ -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;
......
...@@ -12,29 +12,10 @@ ...@@ -12,29 +12,10 @@
} }
} }
.todos {
.panel {
border-top: none;
margin-bottom: 0;
}
}
.todo-item { .todo-item {
font-size: $gl-font-size; font-size: $gl-font-size;
padding: $gl-padding-top 0 $gl-padding-top ($gl-avatar-size + $gl-padding-top); padding-left: $gl-avatar-size + $gl-padding-top;
border-bottom: 1px solid $table-border-color; color: $secondary-text;
color: #7f8fa4;
&.todo-inline {
.avatar {
position: relative;
top: -2px;
}
.todo-title {
line-height: 40px;
}
}
a { a {
color: #4c4e54; color: #4c4e54;
...@@ -48,7 +29,7 @@ ...@@ -48,7 +29,7 @@
@include str-truncated(calc(100% - 174px)); @include str-truncated(calc(100% - 174px));
font-weight: 600; font-weight: 600;
.author_name { .author-name {
color: #333; color: #333;
} }
} }
...@@ -88,17 +69,7 @@ ...@@ -88,17 +69,7 @@
margin-bottom: 0; margin-bottom: 0;
} }
} }
.todo-note-icon {
color: #777;
float: left;
font-size: $gl-font-size;
line-height: 16px;
margin-right: 5px;
}
} }
&:last-child { border:none }
} }
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
......
...@@ -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;
}
} }
...@@ -12,11 +12,13 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController ...@@ -12,11 +12,13 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
current_user.save! if current_user.changed? current_user.save! if current_user.changed?
if two_factor_grace_period_expired? if two_factor_authentication_required?
flash.now[:alert] = 'You must enable Two-factor Authentication for your account.' if two_factor_grace_period_expired?
else flash.now[:alert] = 'You must enable Two-factor Authentication for your account.'
grace_period_deadline = current_user.otp_grace_period_started_at + two_factor_grace_period.hours else
flash.now[:alert] = "You must enable Two-factor Authentication for your account before #{l(grace_period_deadline)}." grace_period_deadline = current_user.otp_grace_period_started_at + two_factor_grace_period.hours
flash.now[:alert] = "You must enable Two-factor Authentication for your account before #{l(grace_period_deadline)}."
end
end end
@qr_code = build_qr_code @qr_code = build_qr_code
......
...@@ -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,
......
class Projects::AvatarsController < Projects::ApplicationController class Projects::AvatarsController < Projects::ApplicationController
include BlobHelper
before_action :project before_action :project
def show def show
...@@ -7,7 +9,7 @@ class Projects::AvatarsController < Projects::ApplicationController ...@@ -7,7 +9,7 @@ class Projects::AvatarsController < Projects::ApplicationController
headers['X-Content-Type-Options'] = 'nosniff' headers['X-Content-Type-Options'] = 'nosniff'
headers.store(*Gitlab::Workhorse.send_git_blob(@repository, @blob)) headers.store(*Gitlab::Workhorse.send_git_blob(@repository, @blob))
headers['Content-Disposition'] = 'inline' headers['Content-Disposition'] = 'inline'
headers['Content-Type'] = @blob.content_type headers['Content-Type'] = safe_content_type(@blob)
head :ok # 'render nothing: true' messes up the Content-Type head :ok # 'render nothing: true' messes up the Content-Type
else else
render_404 render_404
......
...@@ -4,12 +4,22 @@ class Projects::ForksController < Projects::ApplicationController ...@@ -4,12 +4,22 @@ class Projects::ForksController < Projects::ApplicationController
before_action :authorize_download_code! before_action :authorize_download_code!
def index def index
@sort = params[:sort] || 'id_desc' base_query = project.forks.includes(:creator)
@all_forks = project.forks.includes(:creator).order_by(@sort)
@forks = if current_user
@public_forks, @protected_forks = @all_forks.partition do |project| base_query.where('projects.visibility_level IN (?) OR projects.id IN (?)',
can?(current_user, :read_project, project) Project.public_and_internal_levels,
end current_user.authorized_projects.pluck(:id))
else
base_query.where('projects.visibility_level = ?', Project::PUBLIC)
end
@total_forks_count = base_query.size
@private_forks_count = @total_forks_count - @forks.size
@public_forks_count = @total_forks_count - @private_forks_count
@sort = params[:sort] || 'id_desc'
@forks = @forks.order_by(@sort).page(params[:page]).per(PER_PAGE)
end end
def new def new
......
# Controller for viewing a file's raw # Controller for viewing a file's raw
class Projects::RawController < Projects::ApplicationController class Projects::RawController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
include BlobHelper
before_action :require_non_empty_project before_action :require_non_empty_project
before_action :assign_ref_vars before_action :assign_ref_vars
...@@ -17,7 +18,7 @@ class Projects::RawController < Projects::ApplicationController ...@@ -17,7 +18,7 @@ class Projects::RawController < Projects::ApplicationController
else else
headers.store(*Gitlab::Workhorse.send_git_blob(@repository, @blob)) headers.store(*Gitlab::Workhorse.send_git_blob(@repository, @blob))
headers['Content-Disposition'] = 'inline' headers['Content-Disposition'] = 'inline'
headers['Content-Type'] = get_blob_type headers['Content-Type'] = safe_content_type(@blob)
head :ok # 'render nothing: true' messes up the Content-Type head :ok # 'render nothing: true' messes up the Content-Type
end end
else else
...@@ -27,16 +28,6 @@ class Projects::RawController < Projects::ApplicationController ...@@ -27,16 +28,6 @@ class Projects::RawController < Projects::ApplicationController
private private
def get_blob_type
if @blob.text?
'text/plain; charset=utf-8'
elsif @blob.image?
@blob.content_type
else
'application/octet-stream'
end
end
def send_lfs_object def send_lfs_object
lfs_object = find_lfs_object lfs_object = find_lfs_object
......
...@@ -57,7 +57,7 @@ class UsersController < ApplicationController ...@@ -57,7 +57,7 @@ class UsersController < ApplicationController
def contributions_calendar def contributions_calendar
@contributions_calendar ||= Gitlab::ContributionsCalendar. @contributions_calendar ||= Gitlab::ContributionsCalendar.
new(contributed_projects.reject(&:forked?), @user) new(contributed_projects, @user)
end end
def load_events def load_events
......
...@@ -3,7 +3,11 @@ module AppearancesHelper ...@@ -3,7 +3,11 @@ module AppearancesHelper
if brand_item && brand_item.title if brand_item && brand_item.title
brand_item.title brand_item.title
else else
<<<<<<< HEAD
'GitLab Enterprise Edition' 'GitLab Enterprise Edition'
=======
'GitLab Community Edition'
>>>>>>> 204a9895333178cc90b40ef365ad566d750fa594
end end
end end
......
...@@ -134,4 +134,22 @@ module BlobHelper ...@@ -134,4 +134,22 @@ module BlobHelper
blob.data = Loofah.scrub_fragment(blob.data, :strip).to_xml blob.data = Loofah.scrub_fragment(blob.data, :strip).to_xml
blob blob
end end
# If we blindly set the 'real' content type when serving a Git blob we
# are enabling XSS attacks. An attacker could upload e.g. a Javascript
# file to a Git repository, trick the browser of a victim into
# downloading the blob, and then the 'application/javascript' content
# type would tell the browser to execute the attacker's Javascript. By
# overriding the content type and setting it to 'text/plain' (in the
# example of Javascript) we tell the browser of the victim not to
# execute untrusted data.
def safe_content_type(blob)
if blob.text?
'text/plain; charset=utf-8'
elsif blob.image?
blob.content_type
else
'application/octet-stream'
end
end
end end
...@@ -168,11 +168,11 @@ module EventsHelper ...@@ -168,11 +168,11 @@ module EventsHelper
link_to(namespace_project_snippet_path(event.project.namespace, link_to(namespace_project_snippet_path(event.project.namespace,
event.project, event.project,
event.note_target)) do event.note_target)) do
"#{event.note_target_type} ##{truncate event.note_target_id}" "#{event.note_target_type} #{truncate event.note_target.to_reference}"
end end
else else
link_to event_note_target_path(event) do link_to event_note_target_path(event) do
"#{event.note_target_type} ##{truncate event.note_target_iid}" "#{event.note_target_type} #{truncate event.note_target.to_reference}"
end end
end end
else else
......
<<<<<<< HEAD
# == Schema Information # == Schema Information
# #
# Table name: appearances # Table name: appearances
...@@ -12,6 +13,8 @@ ...@@ -12,6 +13,8 @@
# header_logo :string(255) # header_logo :string(255)
# #
=======
>>>>>>> 204a9895333178cc90b40ef365ad566d750fa594
class Appearance < ActiveRecord::Base class Appearance < ActiveRecord::Base
validates :title, presence: true validates :title, presence: true
validates :description, presence: true validates :description, presence: true
......
...@@ -28,7 +28,7 @@ class Milestone < ActiveRecord::Base ...@@ -28,7 +28,7 @@ class Milestone < ActiveRecord::Base
belongs_to :project belongs_to :project
has_many :issues has_many :issues
has_many :labels, through: :issues has_many :labels, -> { distinct.reorder('labels.title') }, through: :issues
has_many :merge_requests has_many :merge_requests
has_many :participants, through: :issues, source: :assignee has_many :participants, through: :issues, source: :assignee
......
...@@ -908,10 +908,7 @@ class Project < ActiveRecord::Base ...@@ -908,10 +908,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
......
...@@ -269,15 +269,6 @@ class Repository ...@@ -269,15 +269,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
...@@ -331,6 +322,46 @@ class Repository ...@@ -331,6 +322,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
# #
...@@ -165,6 +168,11 @@ class User < ActiveRecord::Base ...@@ -165,6 +168,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
...@@ -383,11 +391,12 @@ class User < ActiveRecord::Base ...@@ -383,11 +391,12 @@ class User < ActiveRecord::Base
def disable_two_factor! def disable_two_factor!
update_attributes( update_attributes(
two_factor_enabled: false, two_factor_enabled: false,
encrypted_otp_secret: nil, encrypted_otp_secret: nil,
encrypted_otp_secret_iv: nil, encrypted_otp_secret_iv: nil,
encrypted_otp_secret_salt: nil, encrypted_otp_secret_salt: nil,
otp_backup_codes: nil otp_grace_period_started_at: nil,
otp_backup_codes: nil
) )
end end
......
...@@ -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, mirror_update: false) def execute(project, user, oldrev, newrev, ref, mirror_update: false)
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)
......
...@@ -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
...@@ -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
%li{class: "todo todo-#{todo.done? ? 'done' : 'pending'}", id: dom_id(todo) } %li{class: "todo todo-#{todo.done? ? 'done' : 'pending'}", id: dom_id(todo) }
.todo-item{class: 'todo-block'} .todo-item.todo-block
= image_tag avatar_icon(todo.author_email, 40), class: 'avatar s40', alt:'' = image_tag avatar_icon(todo.author_email, 40), class: 'avatar s40', alt:''
.todo-title .todo-title
%span.author_name %span.author-name
= link_to_author todo = link_to_author todo
%span.todo_label %span.todo-label
= todo_action_name(todo) = todo_action_name(todo)
= todo_target_link(todo) = todo_target_link(todo)
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
= event_action_name(event) = event_action_name(event)
- if event.target - if event.target
%strong= link_to "##{event.target_iid}", [event.project.namespace.becomes(Namespace), event.project, event.target] %strong= link_to event.target.to_reference, [event.project.namespace.becomes(Namespace), event.project, event.target]
= event_preposition(event) = event_preposition(event)
......
...@@ -31,64 +31,91 @@ ...@@ -31,64 +31,91 @@
%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
%h4 Normal block inside content .gray-content-block
= lorem %h4 Normal block inside content
= lorem
.gray-content-block.second-block .gray-content-block.second-block
%h4 Second block %h4 Second block
= 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: ''
.cover-title .cover-title
John Smith John Smith
.cover-desc .cover-desc
= lorem = lorem
.cover-controls .cover-controls
= link_to '#', class: 'btn btn-gray' do = link_to '#', class: 'btn btn-gray' do
= icon('pencil') = icon('pencil')
&nbsp; &nbsp;
= link_to '#', class: 'btn btn-gray' do = link_to '#', class: 'btn btn-gray' do
= icon('rss') = icon('rss')
%h2#lists Lists %h2#lists Lists
%h4 .lead
Simple list using
%code .content-list %code .content-list
%ul.content-list
%li
One item
%li
One item
%li
One item
%h4 .example
%code .well-list %ul.content-list
%ul.well-list %li
%li One item
One item %li
%li One item
One item %li
%li One item
One item
%h4 .lead
%code .panel .well-list List with avatar, title and description using
%code .content-list
.example
%ul.content-list
%li
= image_tag 'no_avatar.png', class: 'avatar s40'
.title Title
.description Description
%li
= image_tag 'no_avatar.png', class: 'avatar s40'
.title Title
.description Description
%li
= image_tag 'no_avatar.png', class: 'avatar s40'
.title Title
.description Description
.panel.panel-default .lead
.panel-heading Your list List with hover effect
%code .well-list
.example
%ul.well-list %ul.well-list
%li %li
One item One item
...@@ -97,17 +124,18 @@ ...@@ -97,17 +124,18 @@
%li %li
One item One item
%h4 .lead
%code .bordered-list List inside panel
%ul.bordered-list .example
%li .panel.panel-default
One item .panel-heading Your list
%li %ul.well-list
One item %li
%li One item
One item %li
One item
%li
One item
%h2#tables Tables %h2#tables Tables
...@@ -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,43 +256,47 @@ ...@@ -228,43 +256,47 @@
%h2#forms Forms %h2#forms Forms
%h4 .lead
Horizontal form when label rendered inline with input
%code form.horizontal-form %code form.horizontal-form
%form.form-horizontal .example
.form-group %form.form-horizontal
%label.col-sm-2.control-label{:for => "inputEmail3"} Email .form-group
.col-sm-10 %label.col-sm-2.control-label{:for => "inputEmail3"} Email
%input#inputEmail3.form-control{:placeholder => "Email", :type => "email"}/ .col-sm-10
.form-group %input#inputEmail3.form-control{:placeholder => "Email", :type => "email"}/
%label.col-sm-2.control-label{:for => "inputPassword3"} Password .form-group
.col-sm-10 %label.col-sm-2.control-label{:for => "inputPassword3"} Password
%input#inputPassword3.form-control{:placeholder => "Password", :type => "password"}/ .col-sm-10
.form-group %input#inputPassword3.form-control{:placeholder => "Password", :type => "password"}/
.col-sm-offset-2.col-sm-10 .form-group
.checkbox .col-sm-offset-2.col-sm-10
%label .checkbox
%input{:type => "checkbox"}/ %label
Remember me %input{:type => "checkbox"}/
.form-group Remember me
.col-sm-offset-2.col-sm-10 .form-group
%button.btn.btn-default{:type => "submit"} Sign in .col-sm-offset-2.col-sm-10
%button.btn.btn-default{:type => "submit"} Sign in
%h4
.lead
Form when label rendered above input
%code form %code form
%form .example
.form-group %form
%label{:for => "exampleInputEmail1"} Email address .form-group
%input#exampleInputEmail1.form-control{:placeholder => "Enter email", :type => "email"}/ %label{:for => "exampleInputEmail1"} Email address
.form-group %input#exampleInputEmail1.form-control{:placeholder => "Enter email", :type => "email"}/
%label{:for => "exampleInputPassword1"} Password .form-group
%input#exampleInputPassword1.form-control{:placeholder => "Password", :type => "password"}/ %label{:for => "exampleInputPassword1"} Password
.checkbox %input#exampleInputPassword1.form-control{:placeholder => "Password", :type => "password"}/
%label .checkbox
%input{:type => "checkbox"}/ %label
Remember me %input{:type => "checkbox"}/
%button.btn.btn-default{:type => "submit"} Sign in Remember me
%button.btn.btn-default{:type => "submit"} Sign in
%h2#file File %h2#file File
%h4 %h4
......
...@@ -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
...@@ -3,17 +3,16 @@ ...@@ -3,17 +3,16 @@
Too many changes to show. Too many changes to show.
.pull-right .pull-right
- unless diff_hard_limit_enabled? - unless diff_hard_limit_enabled?
= link_to "Reload with full diff", url_for(params.merge(force_show_diff: true, format: nil)), class: "btn btn-sm btn-warning" = link_to "Reload with full diff", url_for(params.merge(force_show_diff: true, format: nil)), class: "btn btn-sm"
- if current_controller?(:commit) or current_controller?(:merge_requests) - if current_controller?(:commit) or current_controller?(:merge_requests)
- if current_controller?(:commit) - if current_controller?(:commit)
= link_to "Plain diff", namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff), class: "btn btn-warning btn-sm" = link_to "Plain diff", namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff), class: "btn btn-sm"
= link_to "Email patch", namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch), class: "btn btn-warning btn-sm" = link_to "Email patch", namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch), class: "btn btn-sm"
- elsif @merge_request && @merge_request.persisted? - elsif @merge_request && @merge_request.persisted?
= link_to "Plain diff", merge_request_path(@merge_request, format: :diff), class: "btn btn-warning btn-sm" = link_to "Plain diff", merge_request_path(@merge_request, format: :diff), class: "btn btn-sm"
= link_to "Email patch", merge_request_path(@merge_request, format: :patch), class: "btn btn-warning btn-sm" = link_to "Email patch", merge_request_path(@merge_request, format: :patch), class: "btn btn-sm"
%p %p
To preserve performance only To preserve performance only
%strong #{shown_files_count} of #{diffs.size} %strong #{shown_files_count} of #{diffs.size}
files are displayed. files are displayed.
.top-area .top-area
.nav-text .nav-text
- public_count = @public_forks.size - full_count_title = "#{@public_forks_count} public and #{@private_forks_count} private"
- protected_count = @protected_forks.size == #{pluralize(@total_forks_count, 'fork')}: #{full_count_title}
- full_count_title = "#{public_count} public and #{protected_count} private"
== #{pluralize(@all_forks.size, 'fork')}: #{full_count_title}
.nav-controls .nav-controls
= search_field_tag :filter_projects, nil, placeholder: 'Search forks', class: 'projects-list-filter project-filter-form-field form-control input-short', = search_field_tag :filter_projects, nil, placeholder: 'Search forks', class: 'projects-list-filter project-filter-form-field form-control input-short',
...@@ -41,17 +39,17 @@ ...@@ -41,17 +39,17 @@
.projects-list-holder .projects-list-holder
- if @public_forks.blank? - if @forks.blank?
%ul.content-list %ul.content-list
%li %li
.nothing-here-block No forks to show .nothing-here-block No forks to show
- else - else
= render 'shared/projects/list', projects: @public_forks, use_creator_avatar: true, = render 'shared/projects/list', projects: @forks, use_creator_avatar: true,
forks: true, show_last_commit_as_description: true forks: true, show_last_commit_as_description: true
- if protected_count > 0 - if @private_forks_count > 0
%ul.projects-list.private-forks-notice %ul.projects-list.private-forks-notice
%li.project-row %li.project-row
= icon('lock fw', base: 'circle', class: 'fa-lg private-fork-icon') = icon('lock fw', base: 'circle', class: 'fa-lg private-fork-icon')
%strong= pluralize(protected_count, 'private fork') %strong= pluralize(@private_forks_count, 'private fork')
%span you have no access to. %span you have no access to.
...@@ -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
......
%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
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
= note_count = note_count
.merge-request-info .merge-request-info
\##{merge_request.iid} &middot; #{merge_request.to_reference} &middot;
opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')} opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')}
by #{link_to_member(@project, merge_request.author, avatar: false)} by #{link_to_member(@project, merge_request.author, avatar: false)}
- if merge_request.target_project.default_branch != merge_request.target_branch - if merge_request.target_project.default_branch != merge_request.target_branch
......
- page_title "#{@merge_request.title} (##{@merge_request.iid})", "Merge Requests" - page_title "#{@merge_request.title} (#{@merge_request.to_reference})", "Merge Requests"
- page_description @merge_request.description - page_description @merge_request.description
- page_card_attributes @merge_request.card_attributes - page_card_attributes @merge_request.card_attributes
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
.status-box{ class: status_box_class(@merge_request) } .status-box{ class: status_box_class(@merge_request) }
= @merge_request.state_human_name = @merge_request.state_human_name
%span.identifier %span.identifier
Merge Request ##{@merge_request.iid} Merge Request #{@merge_request.to_reference}
%span.creator %span.creator
&middot; &middot;
by #{link_to_member(@project, @merge_request.author, size: 24)} by #{link_to_member(@project, @merge_request.author, size: 24)}
......
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
%a{name: dom_id(note), href: "##{dom_id(note)}", title: 'Link here'} %a{name: dom_id(note), href: "##{dom_id(note)}", title: 'Link here'}
= time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note_created_ago') = time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note_created_ago')
- if note.updated_at != note.created_at - if note.updated_at != note.created_at
%span %span.note-updated-at
&middot; &middot;
= icon('edit', title: 'edited') = icon('edit', title: 'edited')
= time_ago_with_tooltip(note.updated_at, placement: 'bottom', html_class: 'note_edited_ago') = time_ago_with_tooltip(note.updated_at, placement: 'bottom', html_class: 'note_edited_ago')
......
...@@ -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)
%aside.right-sidebar{ class: sidebar_gutter_collapsed_class } %aside.right-sidebar{ class: sidebar_gutter_collapsed_class }
.issuable-sidebar .issuable-sidebar
.block .block
<<<<<<< HEAD
%span.issuable-count.pull-left.hide-collapsed %span.issuable-count.pull-left.hide-collapsed
=======
%span.issuable-count.hide-collapsed.pull-left
>>>>>>> 204a9895333178cc90b40ef365ad566d750fa594
= 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
<<<<<<< HEAD
.issuable-nav.pull-right.btn-group.hide-collapsed{role: 'group', "aria-label" => '...'} .issuable-nav.pull-right.btn-group.hide-collapsed{role: 'group', "aria-label" => '...'}
=======
.issuable-nav.hide-collapsed.pull-right.btn-group{role: 'group', "aria-label" => '...'}
>>>>>>> 204a9895333178cc90b40ef365ad566d750fa594
- 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
......
...@@ -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
# Explicitly update mirror so that upstream remote is created and fetched # Explicitly update mirror so that upstream remote is created and fetched
......
...@@ -19,40 +19,42 @@ ...@@ -19,40 +19,42 @@
- [GitLab Pages](pages/README.md) Using GitLab Pages. - [GitLab Pages](pages/README.md) Using GitLab Pages.
- [Custom templates for issues and merge requests](customization/issue_and_merge_request_template.md) Pre-fill the description of issues and merge requests to your liking. - [Custom templates for issues and merge requests](customization/issue_and_merge_request_template.md) Pre-fill the description of issues and merge requests to your liking.
## CI Documentation ## CI User documentation
- [Quick Start](ci/quick_start/README.md) - [Get started with GitLab CI](ci/quick_start/README.md)
- [Enable or disable GitLab CI](ci/enable_or_disable_ci.md) - [Learn how to enable or disable GitLab CI](ci/enable_or_disable_ci.md)
- [Configuring project (.gitlab-ci.yml)](ci/yaml/README.md) - [Learn how `.gitlab-ci.yml` works](ci/yaml/README.md)
- [Configuring runner](ci/runners/README.md) - [Configure a Runner, the application that runs your builds](ci/runners/README.md)
- [Configuring deployment](ci/deployment/README.md) - [Use Docker images with GitLab Runner](ci/docker/using_docker_images.md)
- [Using Docker Images](ci/docker/using_docker_images.md) - [Use CI to build Docker images](ci/docker/using_docker_build.md)
- [Using Docker Build](ci/docker/using_docker_build.md) - [Use variables in your `.gitlab-ci.yml`](ci/variables/README.md)
- [Using Variables](ci/variables/README.md) - [Use SSH keys in your build environment](ci/ssh_keys/README.md)
- [Using SSH keys](ci/ssh_keys/README.md) - [Trigger builds through the API](ci/triggers/README.md)
- [Build artifacts](ci/build_artifacts/README.md)
- [User permissions](ci/permissions/README.md) - [User permissions](ci/permissions/README.md)
- [API](ci/api/README.md) - [API](ci/api/README.md)
- [Triggering builds through the API](ci/triggers/README.md)
- [Build artifacts](ci/build_artifacts/README.md)
### CI Languages ### CI Examples
- [Testing PHP](ci/languages/php.md) - [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml)
- [Test your PHP applications](ci/examples/php.md)
- [Test and deploy Ruby applications to Heroku](ci/examples/test-and-deploy-ruby-application-to-heroku.md)
- [Test and deploy Python applications to Heroku](ci/examples/test-and-deploy-python-application-to-heroku.md)
- [Test Clojure applications](ci/examples/test-clojure-application.md)
- [Using `dpl` as deployment tool](ci/deployment/README.md)
- Help your favorite programming language and GitLab by sending a merge request
with a guide for that language.
### CI Services ### CI Services
GitLab CI uses the `services` keyword to define what docker containers should
be linked with your base image. Below is a list of examples you may use:
- [Using MySQL](ci/services/mysql.md) - [Using MySQL](ci/services/mysql.md)
- [Using PostgreSQL](ci/services/postgres.md) - [Using PostgreSQL](ci/services/postgres.md)
- [Using Redis](ci/services/redis.md) - [Using Redis](ci/services/redis.md)
- [Using Other Services](ci/docker/using_docker_images.md#how-to-use-other-images-as-services) - [Using Other Services](ci/docker/using_docker_images.md#how-to-use-other-images-as-services)
### CI Examples
- [Test and deploy Ruby applications to Heroku](ci/examples/test-and-deploy-ruby-application-to-heroku.md)
- [Test and deploy Python applications to Heroku](ci/examples/test-and-deploy-python-application-to-heroku.md)
- [Test Clojure applications](ci/examples/test-clojure-application.md)
- Help your favorite programming language and GitLab by sending a merge request with a guide for that language.
## Administrator documentation ## Administrator documentation
- [Audit Events](administration/audit_events.md) Check how user access changed in projects and groups. - [Audit Events](administration/audit_events.md) Check how user access changed in projects and groups.
......
## GitLab CI Documentation ## GitLab CI Documentation
### User documentation ### CI User documentation
* [Quick Start](quick_start/README.md) - [Get started with GitLab CI](quick_start/README.md)
* [Enable or disable GitLab CI](enable_or_disable_ci.md) - [Learn how to enable or disable GitLab CI](enable_or_disable_ci.md)
* [Configuring project (.gitlab-ci.yml)](yaml/README.md) - [Learn how `.gitlab-ci.yml` works](yaml/README.md)
* [Configuring runner](runners/README.md) - [Configure a Runner, the application that runs your builds](runners/README.md)
* [Configuring deployment](deployment/README.md) - [Use Docker images with GitLab Runner](docker/using_docker_images.md)
* [Using Docker Images](docker/using_docker_images.md) - [Use CI to build Docker images](docker/using_docker_build.md)
* [Using Docker Build](docker/using_docker_build.md) - [Use variables in your `.gitlab-ci.yml`](variables/README.md)
* [Using Variables](variables/README.md) - [Use SSH keys in your build environment](ssh_keys/README.md)
* [Using SSH keys](ssh_keys/README.md) - [Trigger builds through the API](triggers/README.md)
* [Triggering builds through the API](triggers/README.md) - [Build artifacts](build_artifacts/README.md)
* [Build artifacts](build_artifacts/README.md) - [User permissions](permissions/README.md)
- [API](api/README.md)
### Languages
### CI Examples
* [Testing PHP](languages/php.md)
- [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml)
### Services - [Test your PHP applications](examples/php.md)
- [Test and deploy Ruby applications to Heroku](examples/test-and-deploy-ruby-application-to-heroku.md)
* [Using MySQL](services/mysql.md) - [Test and deploy Python applications to Heroku](examples/test-and-deploy-python-application-to-heroku.md)
* [Using PostgreSQL](services/postgres.md) - [Test Clojure applications](examples/test-clojure-application.md)
* [Using Redis](services/redis.md) - [Using `dpl` as deployment tool](deployment/README.md)
* [Using Other Services](docker/using_docker_images.md#how-to-use-other-images-as-services) - Help your favorite programming language and GitLab by sending a merge request
with a guide for that language.
### Examples
### CI Services
+ [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml)
+ [Test and deploy Ruby applications to Heroku](examples/test-and-deploy-ruby-application-to-heroku.md) GitLab CI uses the `services` keyword to define what docker containers should
+ [Test and deploy Python applications to Heroku](examples/test-and-deploy-python-application-to-heroku.md) be linked with your base image. Below is a list of examples you may use:
+ [Test Clojure applications](examples/test-clojure-application.md)
+ Help your favorite programming language and GitLab by sending a merge request with a guide for that language. - [Using MySQL](services/mysql.md)
- [Using PostgreSQL](services/postgres.md)
### Administrator documentation - [Using Redis](services/redis.md)
- [Using Other Services](docker/using_docker_images.md#how-to-use-other-images-as-services)
* [User permissions](permissions/README.md)
* [API](api/README.md)
# Build script examples ## Build script examples
+ [Test and deploy a Ruby application to Heroku](test-and-deploy-ruby-application-to-heroku.md) - [Test and deploy a Ruby application to Heroku](test-and-deploy-ruby-application-to-heroku.md)
+ [Test and deploy a Python application to Heroku](test-and-deploy-python-application-to-heroku.md) - [Test and deploy a Python application to Heroku](test-and-deploy-python-application-to-heroku.md)
+ [Test a Clojure application](test-clojure-application.md) - [Test a Clojure application](test-clojure-application.md)
## Languages
This is a list of languages you can test with GitLab CI. Each section has
comprehensive documentation and comes with a test repository hosted on
GitLab.com.
- [Testing PHP](php.md)
### Languages
This is a list of languages you can test with GitLab CI. Each section has
comprehensive documentation and comes with a test repository hosted on
GitLab.com
+ [Testing PHP](php.md)
...@@ -201,6 +201,11 @@ You can access a builds badge image using following link: ...@@ -201,6 +201,11 @@ You can access a builds badge image using following link:
http://example.gitlab.com/namespace/project/badges/branch/build.svg http://example.gitlab.com/namespace/project/badges/branch/build.svg
``` ```
## Examples
Visit the [examples README][examples] to see a list of examples using GitLab
CI with various languages.
## Next steps ## Next steps
Awesome! You started using CI in GitLab! Awesome! You started using CI in GitLab!
...@@ -212,3 +217,4 @@ Visit our various languages examples at <https://gitlab.com/groups/gitlab-exampl ...@@ -212,3 +217,4 @@ Visit our various languages examples at <https://gitlab.com/groups/gitlab-exampl
[runner-install]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/tree/master#installation [runner-install]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/tree/master#installation
[blog-ci]: https://about.gitlab.com/2015/05/06/why-were-replacing-gitlab-ci-jobs-with-gitlab-ci-dot-yml/ [blog-ci]: https://about.gitlab.com/2015/05/06/why-were-replacing-gitlab-ci-jobs-with-gitlab-ci-dot-yml/
[examples]: ../examples/README.md
## GitLab CI Services ## GitLab CI Services
GitLab CI uses the `services` keyword to define what docker containers should be GitLab CI uses the `services` keyword to define what docker containers should
linked with your base image. Below is a list of examples you may use. be linked with your base image. Below is a list of examples you may use.
+ [Using MySQL](mysql.md) - [Using MySQL](mysql.md)
+ [Using PostgreSQL](postgres.md) - [Using PostgreSQL](postgres.md)
+ [Using Redis](redis.md) - [Using Redis](redis.md)
+ [Using Other Services](../docker/using_docker_images.md#how-to-use-other-images-as-services) - [Using Other Services](../docker/using_docker_images.md#how-to-use-other-images-as-services)
...@@ -30,7 +30,7 @@ The API_TOKEN will take the Secure Variable value: `SECURE`. ...@@ -30,7 +30,7 @@ The API_TOKEN will take the Secure Variable value: `SECURE`.
| **CI_BUILD_REF_NAME** | all | The branch or tag name for which project is built | | **CI_BUILD_REF_NAME** | all | The branch or tag name for which project is built |
| **CI_BUILD_ID** | all | The unique id of the current build that GitLab CI uses internally | | **CI_BUILD_ID** | all | The unique id of the current build that GitLab CI uses internally |
| **CI_BUILD_REPO** | all | The URL to clone the Git repository | | **CI_BUILD_REPO** | all | The URL to clone the Git repository |
| **CI_BUILD_TRIGGERED** | 0.5 | The flag to indicate that build was triggered | | **CI_BUILD_TRIGGERED** | 0.5 | The flag to indicate that build was [triggered] |
| **CI_PROJECT_ID** | all | The unique id of the current project that GitLab CI uses internally | | **CI_PROJECT_ID** | all | The unique id of the current project that GitLab CI uses internally |
| **CI_PROJECT_DIR** | all | The full path where the repository is cloned and where the build is ran | | **CI_PROJECT_DIR** | all | The full path where the repository is cloned and where the build is ran |
...@@ -104,3 +104,5 @@ job_name: ...@@ -104,3 +104,5 @@ job_name:
script: script:
- export - export
``` ```
[triggered]: ../triggers/README.md
...@@ -518,3 +518,10 @@ You can find the link under `/ci/lint` of your gitlab instance. ...@@ -518,3 +518,10 @@ You can find the link under `/ci/lint` of your gitlab instance.
If your commit message contains `[ci skip]`, the commit will be created but the If your commit message contains `[ci skip]`, the commit will be created but the
builds will be skipped. builds will be skipped.
## Examples
Visit the [examples README][examples] to see a list of examples using GitLab
CI with various languages.
[examples]: ../examples/README.md
# Changing the appearance of the login page # Changing the appearance of the login page
<<<<<<< HEAD
GitLab Enterprise Edition offers a way to put your company's identity on the login page of your GitLab server and make it a branded login page. GitLab Enterprise Edition offers a way to put your company's identity on the login page of your GitLab server and make it a branded login page.
=======
GitLab Community Edition offers a way to put your company's identity on the login page of your GitLab server and make it a branded login page.
>>>>>>> 204a9895333178cc90b40ef365ad566d750fa594
By default, the page shows the GitLab logo and description. By default, the page shows the GitLab logo and description.
......
# Development # Development
- [Architecture](architecture.md) of GitLab - [Architecture](architecture.md) of GitLab
- [Shell commands](shell_commands.md) in the GitLab codebase - [Benchmarking](benchmarking.md)
- [Rake tasks](rake_tasks.md) for development
- [CI setup](ci_setup.md) for testing GitLab - [CI setup](ci_setup.md) for testing GitLab
- [Gotchas](gotchas.md) to avoid
- [How to dump production data to staging](db_dump.md)
- [Migration Style Guide](migration_style_guide.md) for creating safe migrations
- [Rake tasks](rake_tasks.md) for development
- [Shell commands](shell_commands.md) in the GitLab codebase
- [Sidekiq debugging](sidekiq_debugging.md) - [Sidekiq debugging](sidekiq_debugging.md)
- [UI guide](ui_guide.md) for building GitLab with existing css styles and elements - [UI guide](ui_guide.md) for building GitLab with existing css styles and elements
- [Migration Style Guide](migration_style_guide.md) for creating safe migrations
- [How to dump production data to staging](dump_db.md)
- [Benchmarking](benchmarking.md)
# Gotchas
The purpose of this guide is to document potential "gotchas" that contributors
might encounter or should avoid during development of GitLab CE and EE.
## Don't `describe` symbols
Consider the following model spec:
```ruby
require 'rails_helper'
describe User do
describe :to_param do
it 'converts the username to a param' do
user = described_class.new(username: 'John Smith')
expect(user.to_param).to eq 'john-smith'
end
end
end
```
When run, this spec doesn't do what we might expect:
```sh
spec/models/user_spec.rb|6 error| Failure/Error: u = described_class.new NoMethodError: undefined method `new' for :to_param:Symbol
```
### Solution
Except for the top-level `describe` block, always provide a String argument to
`describe`.
## Don't `rescue Exception`
See ["Why is it bad style to `rescue Exception => e` in Ruby?"][Exception].
_**Note:** This rule is [enforced automatically by
Rubocop](https://gitlab.com/gitlab-org/gitlab-ce/blob/8-4-stable/.rubocop.yml#L911-914)._
[Exception]: http://stackoverflow.com/q/10048173/223897
## Don't use inline CoffeeScript in views
Using the inline `:coffee` or `:coffeescript` Haml filters comes with a
performance overhead.
_**Note:** We've [removed these two filters](https://gitlab.com/gitlab-org/gitlab-ce/blob/8-5-stable/config/initializers/haml.rb)
in an initializer._
### Further reading
- Pull Request: [Replace CoffeeScript block into JavaScript in Views](https://git.io/vztMu)
- Stack Overflow: [Performance implications of using :coffescript filter inside HAML templates?](http://stackoverflow.com/a/17571242/223897)
## ID-based CSS selectors need to be a bit more specific
Normally, because HTML `id` attributes need to be unique to the page, it's
perfectly fine to write some JavaScript like the following:
```javascript
$('#js-my-selector').hide();
```
However, there's a feature of GitLab's Markdown processing that [automatically
adds anchors to header elements][ToC Processing], with the `id` attribute being
automatically generated based on the content of the header.
Unfortunately, this feature makes it possible for user-generated content to
create a header element with the same `id` attribute we're using in our
selector, potentially breaking the JavaScript behavior. A user could break the
above example with the following Markdown:
```markdown
## JS My Selector
```
Which gets converted to the following HTML:
```html
<h2>
<a id="js-my-selector" class="anchor" href="#js-my-selector" aria-hidden="true"></a>
JS My Selector
</h2>
```
[ToC Processing]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-4-stable/lib/banzai/filter/table_of_contents_filter.rb#L31-37
### Solution
The current recommended fix for this is to make our selectors slightly more
specific:
```javascript
$('div#js-my-selector').hide();
```
### Further reading
- Issue: [Merge request ToC anchor conflicts with tabs](https://gitlab.com/gitlab-org/gitlab-ce/issues/3908)
- Merge Request: [Make tab target selectors less naive](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2023)
- Merge Request: [Make cross-project reference's clipboard target less naive](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2024)
...@@ -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
......
...@@ -13,6 +13,7 @@ Feature: Project Milestone ...@@ -13,6 +13,7 @@ Feature: Project Milestone
Given I visit project "Shop" milestones page Given I visit project "Shop" milestones page
And I click link "v2.2" And I click link "v2.2"
Then I should see the labels "bug", "enhancement" and "feature" Then I should see the labels "bug", "enhancement" and "feature"
And I should see the "bug" label listed only once
@javascript @javascript
Scenario: Listing labels from labels tab Scenario: Listing labels from labels tab
......
...@@ -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
...@@ -41,6 +41,12 @@ class Spinach::Features::ProjectMilestone < Spinach::FeatureSteps ...@@ -41,6 +41,12 @@ class Spinach::Features::ProjectMilestone < Spinach::FeatureSteps
end end
end end
step 'I should see the "bug" label listed only once' do
page.within('#tab-labels') do
expect(page).to have_content('bug', count: 1)
end
end
step 'I click link "v2.2"' do step 'I click link "v2.2"' do
click_link "v2.2" click_link "v2.2"
end end
......
...@@ -14,6 +14,14 @@ module API ...@@ -14,6 +14,14 @@ module API
# ref - branch name # ref - branch name
# forced_push - forced_push # forced_push - forced_push
# #
helpers do
def wiki?
@wiki ||= params[:project].end_with?('.wiki') &&
!Project.find_with_namespace(params[:project])
end
end
post "/allowed" do post "/allowed" do
status 200 status 200
...@@ -30,13 +38,12 @@ module API ...@@ -30,13 +38,12 @@ module API
# Strip out the .wiki from the pathname before finding the # Strip out the .wiki from the pathname before finding the
# project. This applies the correct project permissions to # project. This applies the correct project permissions to
# the wiki repository as well. # the wiki repository as well.
wiki = project_path.end_with?('.wiki') project_path.chomp!('.wiki') if wiki?
project_path.chomp!('.wiki') if wiki
project = Project.find_with_namespace(project_path) project = Project.find_with_namespace(project_path)
access = access =
if wiki if wiki?
Gitlab::GitAccessWiki.new(actor, project) Gitlab::GitAccessWiki.new(actor, project)
else else
Gitlab::GitAccess.new(actor, project) Gitlab::GitAccess.new(actor, project)
......
...@@ -7,6 +7,8 @@ module Banzai ...@@ -7,6 +7,8 @@ module Banzai
# #
# Extends HTML::Pipeline::SanitizationFilter with a custom whitelist. # Extends HTML::Pipeline::SanitizationFilter with a custom whitelist.
class SanitizationFilter < HTML::Pipeline::SanitizationFilter class SanitizationFilter < HTML::Pipeline::SanitizationFilter
UNSAFE_PROTOCOLS = %w(javascript :javascript data vbscript).freeze
def whitelist def whitelist
whitelist = super whitelist = super
...@@ -43,8 +45,8 @@ module Banzai ...@@ -43,8 +45,8 @@ module Banzai
# Allow any protocol in `a` elements... # Allow any protocol in `a` elements...
whitelist[:protocols].delete('a') whitelist[:protocols].delete('a')
# ...but then remove links with the `javascript` protocol # ...but then remove links with unsafe protocols
whitelist[:transformers].push(remove_javascript_links) whitelist[:transformers].push(remove_unsafe_links)
# Remove `rel` attribute from `a` elements # Remove `rel` attribute from `a` elements
whitelist[:transformers].push(remove_rel) whitelist[:transformers].push(remove_rel)
...@@ -55,14 +57,14 @@ module Banzai ...@@ -55,14 +57,14 @@ module Banzai
whitelist whitelist
end end
def remove_javascript_links def remove_unsafe_links
lambda do |env| lambda do |env|
node = env[:node] node = env[:node]
return unless node.name == 'a' return unless node.name == 'a'
return unless node.has_attribute?('href') return unless node.has_attribute?('href')
if node['href'].start_with?('javascript', ':javascript') if node['href'].start_with?(*UNSAFE_PROTOCOLS)
node.remove_attribute('href') node.remove_attribute('href')
end end
end end
......
namespace :cache do namespace :cache do
CLEAR_BATCH_SIZE = 1000 # The more the faster, but having too many can crash Ruby CLEAR_BATCH_SIZE = 1000 # There seems to be no speedup when pushing beyond 1,000
REDIS_SCAN_START_STOP = '0' # Magic value, see http://redis.io/commands/scan REDIS_SCAN_START_STOP = '0' # Magic value, see http://redis.io/commands/scan
desc "GitLab | Clear redis cache" desc "GitLab | Clear redis cache"
......
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
......
...@@ -41,6 +41,7 @@ describe UsersController do ...@@ -41,6 +41,7 @@ describe UsersController do
end end
describe 'GET #calendar' do describe 'GET #calendar' do
it 'renders calendar' do it 'renders calendar' do
sign_in(user) sign_in(user)
...@@ -48,6 +49,23 @@ describe UsersController do ...@@ -48,6 +49,23 @@ describe UsersController do
expect(response).to render_template('calendar') expect(response).to render_template('calendar')
end end
context 'forked project' do
let!(:project) { create(:project) }
let!(:forked_project) { Projects::ForkService.new(project, user).execute }
before do
sign_in(user)
project.team << [user, :developer]
EventCreateService.new.push(project, user, [])
EventCreateService.new.push(forked_project, user, [])
end
it 'includes forked projects' do
get :calendar, username: user.username
expect(assigns(:contributions_calendar).projects.count).to eq(2)
end
end
end end
describe 'GET #calendar_activities' do describe 'GET #calendar_activities' do
......
...@@ -32,10 +32,18 @@ FactoryGirl.define do ...@@ -32,10 +32,18 @@ FactoryGirl.define do
before(:create) do |user| before(:create) do |user|
user.two_factor_enabled = true user.two_factor_enabled = true
user.otp_secret = User.generate_otp_secret(32) user.otp_secret = User.generate_otp_secret(32)
user.otp_grace_period_started_at = Time.now
user.generate_otp_backup_codes! user.generate_otp_backup_codes!
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'
......
...@@ -2,7 +2,12 @@ ...@@ -2,7 +2,12 @@
FactoryGirl.define do FactoryGirl.define do
factory :appearance do factory :appearance do
<<<<<<< HEAD
title "GitLab Enterprise Edition" title "GitLab Enterprise Edition"
description "Open source software to collaborate on code" description "Open source software to collaborate on code"
=======
title "MepMep"
description "This is my Community Edition instance"
>>>>>>> 204a9895333178cc90b40ef365ad566d750fa594
end end
end end
...@@ -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")
......
...@@ -156,13 +156,27 @@ describe Banzai::Filter::SanitizationFilter, lib: true do ...@@ -156,13 +156,27 @@ describe Banzai::Filter::SanitizationFilter, lib: true do
} }
protocols.each do |name, data| protocols.each do |name, data|
it "handles #{name}" do it "disallows #{name}" do
doc = filter(data[:input]) doc = filter(data[:input])
expect(doc.to_html).to eq data[:output] expect(doc.to_html).to eq data[:output]
end end
end end
it 'disallows data links' do
input = '<a href="data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K">XSS</a>'
output = filter(input)
expect(output.to_html).to eq '<a>XSS</a>'
end
it 'disallows vbscript links' do
input = '<a href="vbscript:alert(document.domain)">XSS</a>'
output = filter(input)
expect(output.to_html).to eq '<a>XSS</a>'
end
it 'allows non-standard anchor schemes' do it 'allows non-standard anchor schemes' do
exp = %q{<a href="irc://irc.freenode.net/git">IRC</a>} exp = %q{<a href="irc://irc.freenode.net/git">IRC</a>}
act = filter(exp) act = filter(exp)
......
...@@ -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)
...@@ -465,6 +465,110 @@ describe Repository, models: true do ...@@ -465,6 +465,110 @@ describe Repository, models: true do
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
describe "Elastic search", elastic: true do describe "Elastic search", elastic: true do
before do before do
Repository.__elasticsearch__.create_index! Repository.__elasticsearch__.create_index!
......
...@@ -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 "non_ldap" do describe "non_ldap" do
...@@ -269,6 +281,7 @@ describe User, models: true do ...@@ -269,6 +281,7 @@ describe User, models: true do
expect(user).to be_two_factor_enabled expect(user).to be_two_factor_enabled
expect(user.encrypted_otp_secret).not_to be_nil expect(user.encrypted_otp_secret).not_to be_nil
expect(user.otp_backup_codes).not_to be_nil expect(user.otp_backup_codes).not_to be_nil
expect(user.otp_grace_period_started_at).not_to be_nil
user.disable_two_factor! user.disable_two_factor!
...@@ -277,6 +290,7 @@ describe User, models: true do ...@@ -277,6 +290,7 @@ describe User, models: true do
expect(user.encrypted_otp_secret_iv).to be_nil expect(user.encrypted_otp_secret_iv).to be_nil
expect(user.encrypted_otp_secret_salt).to be_nil expect(user.encrypted_otp_secret_salt).to be_nil
expect(user.otp_backup_codes).to be_nil expect(user.otp_backup_codes).to be_nil
expect(user.otp_grace_period_started_at).to be_nil
end end
end end
......
...@@ -54,6 +54,18 @@ describe API::API, api: true do ...@@ -54,6 +54,18 @@ describe API::API, api: true do
project.team << [user, :developer] project.team << [user, :developer]
end end
context "git push with project.wiki" do
it 'responds with success' do
project_wiki = create(:project, name: 'my.wiki', path: 'my.wiki')
project_wiki.team << [user, :developer]
push(key, project_wiki)
expect(response.status).to eq(200)
expect(json_response["status"]).to be_truthy
end
end
context "git pull" do context "git pull" do
it do it do
pull(key, project) pull(key, project)
......
This diff is collapsed.
/*!
* 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