diff options
author | Alfredo Sumaran <alfredo@gitlab.com> | 2016-03-01 13:37:03 -0500 |
---|---|---|
committer | Alfredo Sumaran <alfredo@gitlab.com> | 2016-03-01 13:37:03 -0500 |
commit | 9f1c3bb7659365d09f2482d57621a3ff0510d9bf (patch) | |
tree | 1bdde66b08c960e8447d9400f129477095fac2e2 | |
parent | fefa6a6f9e8f484300a808f1db6254a502cef73a (diff) | |
parent | 90ced487f2e8a6252864f9ac8821c2de06be9ced (diff) | |
download | gitlab-ce-9f1c3bb7659365d09f2482d57621a3ff0510d9bf.tar.gz |
Merge branch 'master' into improve-user-tabs
52 files changed, 466 insertions, 232 deletions
diff --git a/.gitignore b/.gitignore index 91ea81bfc4e..1eb785451f4 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ config/gitlab.yml config/gitlab_ci.yml config/initializers/rack_attack.rb config/initializers/smtp_settings.rb +config/initializers/relative_url.rb config/resque.yml config/unicorn.rb config/secrets.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4e98b7a68ee..c477721f9da 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -89,6 +89,7 @@ spec:other: spinach:project:half: stage: test script: + - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:half tags: - ruby @@ -97,6 +98,7 @@ spinach:project:half: spinach:project:rest: stage: test script: + - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest tags: - ruby @@ -105,6 +107,7 @@ spinach:project:rest: spinach:other: stage: test script: + - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:other tags: - ruby @@ -275,6 +278,7 @@ spinach:project:half:ruby22: only: - master script: + - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:half cache: key: "ruby22" @@ -290,6 +294,7 @@ spinach:project:rest:ruby22: only: - master script: + - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest cache: key: "ruby22" @@ -305,6 +310,7 @@ spinach:other:ruby22: only: - master script: + - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:other cache: key: "ruby22" @@ -318,7 +324,7 @@ spinach:other:ruby22: notify:slack: stage: notifications script: - - ./scripts/notify_slack.sh "#builds" "Build on \`$CI_BUILD_REF_NAME\` failed! Check <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_BUILD_REF"/builds>" + - ./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 only: - master@gitlab-org/gitlab-ce diff --git a/CHANGELOG b/CHANGELOG index 9e897644af0..4dc38117eb7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,14 +1,18 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.6.0 (unreleased) + - Contributions to forked projects are included in calendar - 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) + - Return empty array instead of 404 when commit has no statuses in commit status API - 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 + - Fix broken icons on installations with relative URL (Artem Sidorenko) v 8.5.1 - Fix group projects styles @@ -28,6 +32,7 @@ v 8.5.1 - Re-add the newrelic_rpm gem which was removed without any deprecation or warning (Stan Hu) - Update sentry-raven gem to 0.15.6 - Add build coverage in project's builds page (Steffen Köhler) + - Changed # to ! for merge requests in activity view v 8.5.0 - Fix duplicate "me" in tooltip of the "thumbsup" awards Emoji (Stan Hu) @@ -1,6 +1,6 @@ source "https://rubygems.org" -gem 'rails', '4.2.5.1' +gem 'rails', '4.2.5.2' gem 'rails-deprecated_sanitizer', '~> 1.0.3' # Responders respond_to and respond_with @@ -213,7 +213,6 @@ gem 'jquery-atwho-rails', '~> 1.3.2' gem 'jquery-rails', '~> 4.0.0' gem 'jquery-scrollto-rails', '~> 1.4.3' gem 'jquery-ui-rails', '~> 5.0.0' -gem 'nprogress-rails', '~> 0.1.6.7' gem 'raphael-rails', '~> 2.1.2' gem 'request_store', '~> 1.2.0' gem 'select2-rails', '~> 3.5.9' diff --git a/Gemfile.lock b/Gemfile.lock index d0f780e9519..29563b18db1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -4,41 +4,41 @@ GEM CFPropertyList (2.3.2) RedCloth (4.2.9) ace-rails-ap (2.0.1) - actionmailer (4.2.5.1) - actionpack (= 4.2.5.1) - actionview (= 4.2.5.1) - activejob (= 4.2.5.1) + actionmailer (4.2.5.2) + actionpack (= 4.2.5.2) + actionview (= 4.2.5.2) + activejob (= 4.2.5.2) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 1.0, >= 1.0.5) - actionpack (4.2.5.1) - actionview (= 4.2.5.1) - activesupport (= 4.2.5.1) + actionpack (4.2.5.2) + actionview (= 4.2.5.2) + activesupport (= 4.2.5.2) rack (~> 1.6) rack-test (~> 0.6.2) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (4.2.5.1) - activesupport (= 4.2.5.1) + actionview (4.2.5.2) + activesupport (= 4.2.5.2) builder (~> 3.1) erubis (~> 2.7.0) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.2) - activejob (4.2.5.1) - activesupport (= 4.2.5.1) + activejob (4.2.5.2) + activesupport (= 4.2.5.2) globalid (>= 0.3.0) - activemodel (4.2.5.1) - activesupport (= 4.2.5.1) + activemodel (4.2.5.2) + activesupport (= 4.2.5.2) builder (~> 3.1) - activerecord (4.2.5.1) - activemodel (= 4.2.5.1) - activesupport (= 4.2.5.1) + activerecord (4.2.5.2) + activemodel (= 4.2.5.2) + activesupport (= 4.2.5.2) arel (~> 6.0) activerecord-deprecated_finders (1.0.4) activerecord-session_store (0.1.2) actionpack (>= 4.0.0, < 5) activerecord (>= 4.0.0, < 5) railties (>= 4.0.0, < 5) - activesupport (4.2.5.1) + activesupport (4.2.5.2) i18n (~> 0.7) json (~> 1.7, >= 1.7.7) minitest (~> 5.1) @@ -483,7 +483,6 @@ GEM newrelic_rpm (3.14.1.311) nokogiri (1.6.7.2) mini_portile2 (~> 2.0.0.rc2) - nprogress-rails (0.1.6.7) oauth (0.4.7) oauth2 (1.0.0) faraday (>= 0.8, < 0.10) @@ -587,16 +586,16 @@ GEM rack rack-test (0.6.3) rack (>= 1.0) - rails (4.2.5.1) - actionmailer (= 4.2.5.1) - actionpack (= 4.2.5.1) - actionview (= 4.2.5.1) - activejob (= 4.2.5.1) - activemodel (= 4.2.5.1) - activerecord (= 4.2.5.1) - activesupport (= 4.2.5.1) + rails (4.2.5.2) + actionmailer (= 4.2.5.2) + actionpack (= 4.2.5.2) + actionview (= 4.2.5.2) + activejob (= 4.2.5.2) + activemodel (= 4.2.5.2) + activerecord (= 4.2.5.2) + activesupport (= 4.2.5.2) bundler (>= 1.3.0, < 2.0) - railties (= 4.2.5.1) + railties (= 4.2.5.2) sprockets-rails rails-deprecated_sanitizer (1.0.3) activesupport (>= 4.2.0.alpha) @@ -606,9 +605,9 @@ GEM rails-deprecated_sanitizer (>= 1.0.1) rails-html-sanitizer (1.0.3) loofah (~> 2.0) - railties (4.2.5.1) - actionpack (= 4.2.5.1) - activesupport (= 4.2.5.1) + railties (4.2.5.2) + actionpack (= 4.2.5.2) + activesupport (= 4.2.5.2) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) rainbow (2.0.0) @@ -964,7 +963,6 @@ DEPENDENCIES net-ssh (~> 3.0.1) newrelic_rpm (~> 3.14) nokogiri (~> 1.6.7, >= 1.6.7.2) - nprogress-rails (~> 0.1.6.7) oauth2 (~> 1.0.0) octokit (~> 3.8.0) omniauth (~> 1.3.1) @@ -989,7 +987,7 @@ DEPENDENCIES rack-attack (~> 4.3.1) rack-cors (~> 0.4.0) rack-oauth2 (~> 1.2.1) - rails (= 4.2.5.1) + rails (= 4.2.5.2) rails-deprecated_sanitizer (~> 1.0.3) raphael-rails (~> 2.1.2) rblineprof diff --git a/PROCESS.md b/PROCESS.md index 5f4d67bc10e..54ebe11d5a0 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -36,18 +36,15 @@ The most important thing is making sure valid issues receive feedback from the d Workflow labels are purposely not very detailed since that would be hard to keep updated as you would need to re-evaluate them after every comment. We optionally use functional labels on demand when want to group related issues to get an overview (for example all issues related to RVM, to tackle them in one go) and to add details to the issue. -- *Awaiting feedback*: Feedback pending from the reporter -- *Awaiting confirmation of fix*: The issue should already be solved in **master** (generally you can avoid this workflow item and just close the issue right away) -- *Attached MR*: There is a MR attached and the discussion should happen there - - We need to let issues stay in sync with the MR's. We can do this with a "Closing #XXXX" or "Fixes #XXXX" comment in the MR. We can't close the issue when there is a merge request because sometimes a MR is not good and we just close the MR, then the issue must stay. -- *Developer*: needs help from a developer -- *UX* needs needs help from a UX designer -- *Frontend* needs help from a Front-end engineer -- *Graphics* needs help from a Graphics designer -- *up-for-grabs* is an issue suitable for first-time contributors, of reasonable difficulty and size. Not exclusive with other labels. -- *feature proposal* is a proposal for a new feature for GitLab. People are encouraged to vote +- ~"Awaiting Feedback" Feedback pending from the reporter +- ~UX needs help from a UX designer +- ~Frontend needs help from a Front-end engineer +- ~up-for-grabs is an issue suitable for first-time contributors, of reasonable difficulty and size. Not exclusive with other labels. +- ~"feature proposal" is a proposal for a new feature for GitLab. People are encouraged to vote in support or comment for further detail. Do not use `feature request`. - +- ~bug is an issue reporting undesirable or incorrect behavior. +- ~customer is an issue reported by enterprise subscribers. This label should +be accompanied by *bug* or *feature proposal* labels. Example workflow: when a UX designer provided a design but it needs frontend work they remove the UX label and add the frontend label. ## Functional labels @@ -4,4 +4,7 @@ require File.expand_path('../config/application', __FILE__) +relative_url_conf = File.expand_path('../config/initializers/relative_url', __FILE__) +require relative_url_conf if File.exist?("#{relative_url_conf}.rb") + Gitlab::Application.load_tasks diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 5463397f475..c17d2186e29 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -31,8 +31,6 @@ #= require ace/ace #= require ace/ext-searchbox #= require underscore -#= require nprogress -#= require nprogress-turbolinks #= require dropzone #= require mousetrap #= require mousetrap/pause diff --git a/app/assets/javascripts/logo.js.coffee b/app/assets/javascripts/logo.js.coffee index 35b2fbbba07..d14b7139237 100644 --- a/app/assets/javascripts/logo.js.coffee +++ b/app/assets/javascripts/logo.js.coffee @@ -1,4 +1,4 @@ -NProgress.configure(showSpinner: false) +Turbolinks.enableProgressBar(); defaultClass = 'tanuki-shape' pieces = [ diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee index 40cfa59a229..23a218b4c7d 100644 --- a/app/assets/javascripts/merge_request_tabs.js.coffee +++ b/app/assets/javascripts/merge_request_tabs.js.coffee @@ -146,6 +146,7 @@ class @MergeRequestTabs url: "#{source}.json" + @_location.search success: (data) => document.querySelector("div#diffs").innerHTML = data.html + $('.js-timeago').timeago() $('div#diffs .js-syntax-highlight').syntaxHighlight() @expandViewContainer() if @diffViewType() is 'parallel' @diffsLoaded = true diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index f51054f13dc..e2d590f4df4 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -26,12 +26,6 @@ @import "framework"; /* - * NProgress load bar css - */ -@import 'nprogress'; -@import 'nprogress-bootstrap'; - -/* * Font icons */ @import "font-awesome"; diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss index fa7641b1676..e2a30f5ed34 100644 --- a/app/assets/stylesheets/framework.scss +++ b/app/assets/stylesheets/framework.scss @@ -26,6 +26,7 @@ @import "framework/mobile.scss"; @import "framework/nav.scss"; @import "framework/pagination.scss"; +@import "framework/progress.scss"; @import "framework/panels.scss"; @import "framework/selects.scss"; @import "framework/sidebar.scss"; diff --git a/app/assets/stylesheets/framework/progress.scss b/app/assets/stylesheets/framework/progress.scss new file mode 100644 index 00000000000..e9800bd24b5 --- /dev/null +++ b/app/assets/stylesheets/framework/progress.scss @@ -0,0 +1,5 @@ +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; +} diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index e0ccd6f100f..de947c89c19 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -13,6 +13,19 @@ transition-duration: .3s; } + .gitlab-text-container-link { + z-index: 1; + position: absolute; + left: 0px; + } + + #logo { + z-index: 2; + position: absolute; + width: 58px; + cursor: pointer; + } + &.right-sidebar-expanded { padding-right: $gutter_width; } @@ -74,7 +87,7 @@ width: 158px; float: left; margin: 0; - margin-left: 14px; + margin-left: 50px; font-size: 19px; line-height: 41px; font-weight: normal; diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 2706d031d7b..7834cb0bfa5 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -7,7 +7,7 @@ $gl-header-color: #323232; $gl-link-color: #333c48; $md-text-color: #444; $md-link-color: #3084bb; -$nprogress-color: #c0392b; +$progress-color: #c0392b; $gl-font-size: 15px; $list-font-size: 15px; $sidebar_collapsed_width: 62px; diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index f9c6f1b39f9..61bec02f6c5 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -8,6 +8,10 @@ max-width: none; } + .flash-container { + margin-bottom: $gl-padding; + } + .brand-holder { font-size: 18px; line-height: 1.5; diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 19ead07c06a..d5f9852ebed 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -14,6 +14,18 @@ ul.notes { margin: 0px; padding: 0px; + .timeline-icon { + float: left; + } + + .timeline-content { + margin-left: 55px; + } + + .note_created_ago, .note-updated-at { + white-space: nowrap; + } + .system-note { font-size: 14px; padding-top: 10px; @@ -151,6 +163,7 @@ ul.notes { border-left: none; &.notes_line { + vertical-align: middle; text-align: center; padding: 10px 0; background: #FFF; diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss index 2f57f21963d..0dc5a905f99 100644 --- a/app/assets/stylesheets/pages/todos.scss +++ b/app/assets/stylesheets/pages/todos.scss @@ -12,29 +12,10 @@ } } -.todos { - .panel { - border-top: none; - margin-bottom: 0; - } -} - .todo-item { font-size: $gl-font-size; - padding: $gl-padding-top 0 $gl-padding-top ($gl-avatar-size + $gl-padding-top); - border-bottom: 1px solid $table-border-color; - color: #7f8fa4; - - &.todo-inline { - .avatar { - position: relative; - top: -2px; - } - - .todo-title { - line-height: 40px; - } - } + padding-left: $gl-avatar-size + $gl-padding-top; + color: $secondary-text; a { color: #4c4e54; @@ -48,7 +29,7 @@ @include str-truncated(calc(100% - 174px)); font-weight: 600; - .author_name { + .author-name { color: #333; } } @@ -88,17 +69,7 @@ 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) { diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb index f3bfede4354..8f83fdd02bc 100644 --- a/app/controllers/profiles/two_factor_auths_controller.rb +++ b/app/controllers/profiles/two_factor_auths_controller.rb @@ -12,11 +12,13 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController current_user.save! if current_user.changed? - if two_factor_grace_period_expired? - flash.now[:alert] = 'You must enable Two-factor Authentication for your account.' - else - 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)}." + if two_factor_authentication_required? + if two_factor_grace_period_expired? + flash.now[:alert] = 'You must enable Two-factor Authentication for your account.' + else + 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 @qr_code = build_qr_code diff --git a/app/controllers/projects/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb index f7e6bb34443..b64dbbd89ce 100644 --- a/app/controllers/projects/avatars_controller.rb +++ b/app/controllers/projects/avatars_controller.rb @@ -1,4 +1,6 @@ class Projects::AvatarsController < Projects::ApplicationController + include BlobHelper + before_action :project def show @@ -7,7 +9,7 @@ class Projects::AvatarsController < Projects::ApplicationController headers['X-Content-Type-Options'] = 'nosniff' headers.store(*Gitlab::Workhorse.send_git_blob(@repository, @blob)) 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 else render_404 diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb index 0c551501ca4..a0835c9aad0 100644 --- a/app/controllers/projects/forks_controller.rb +++ b/app/controllers/projects/forks_controller.rb @@ -4,12 +4,22 @@ class Projects::ForksController < Projects::ApplicationController before_action :authorize_download_code! def index - @sort = params[:sort] || 'id_desc' - @all_forks = project.forks.includes(:creator).order_by(@sort) - - @public_forks, @protected_forks = @all_forks.partition do |project| - can?(current_user, :read_project, project) - end + base_query = project.forks.includes(:creator) + + @forks = if current_user + base_query.where('projects.visibility_level IN (?) OR projects.id IN (?)', + Project.public_and_internal_levels, + 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 def new diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb index 87b4d08da0e..d9723acb1d9 100644 --- a/app/controllers/projects/raw_controller.rb +++ b/app/controllers/projects/raw_controller.rb @@ -1,6 +1,7 @@ # Controller for viewing a file's raw class Projects::RawController < Projects::ApplicationController include ExtractsPath + include BlobHelper before_action :require_non_empty_project before_action :assign_ref_vars @@ -17,7 +18,7 @@ class Projects::RawController < Projects::ApplicationController else headers.store(*Gitlab::Workhorse.send_git_blob(@repository, @blob)) 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 end else @@ -27,16 +28,6 @@ class Projects::RawController < Projects::ApplicationController 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 lfs_object = find_lfs_object diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index eae19214bf5..4b1cf242885 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -89,7 +89,7 @@ class UsersController < ApplicationController def contributions_calendar @contributions_calendar ||= Gitlab::ContributionsCalendar. - new(contributed_projects.reject(&:forked?), @user) + new(contributed_projects, @user) end def load_events diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 7143a744869..7f63a2e2cb4 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -134,4 +134,22 @@ module BlobHelper blob.data = Loofah.scrub_fragment(blob.data, :strip).to_xml blob 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 diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 31bf45baeb7..e5fcaab9551 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -168,11 +168,11 @@ module EventsHelper link_to(namespace_project_snippet_path(event.project.namespace, event.project, event.note_target)) do - "#{event.note_target_type} ##{truncate event.note_target_id}" + "#{event.note_target_type} #{truncate event.note_target.to_reference}" end else 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 else diff --git a/app/models/user.rb b/app/models/user.rb index 6baf2468ade..3098d49d58a 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -169,7 +169,7 @@ class User < ActiveRecord::Base validates :avatar_crop_x, :avatar_crop_y, :avatar_crop_size, numericality: { only_integer: true }, presence: true, - if: ->(user) { user.avatar? } + if: ->(user) { user.avatar? && user.avatar_changed? } before_validation :generate_password, on: :create before_validation :restricted_signup_domains, on: :create @@ -362,17 +362,19 @@ class User < ActiveRecord::Base def disable_two_factor! update_attributes( - two_factor_enabled: false, - encrypted_otp_secret: nil, - encrypted_otp_secret_iv: nil, - encrypted_otp_secret_salt: nil, - otp_backup_codes: nil + two_factor_enabled: false, + encrypted_otp_secret: nil, + encrypted_otp_secret_iv: nil, + encrypted_otp_secret_salt: nil, + otp_grace_period_started_at: nil, + otp_backup_codes: nil ) end def namespace_uniq # Return early if username already failed the first uniqueness validation - return if self.errors[:username].include?('has already been taken') + return if self.errors.key?(:username) && + self.errors[:username].include?('has already been taken') namespace_name = self.username existing_namespace = Namespace.by_path(namespace_name) diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml index 6975f6ed0db..f878d36e739 100644 --- a/app/views/dashboard/todos/_todo.html.haml +++ b/app/views/dashboard/todos/_todo.html.haml @@ -1,11 +1,11 @@ %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:'' .todo-title - %span.author_name + %span.author-name = link_to_author todo - %span.todo_label + %span.todo-label = todo_action_name(todo) = todo_target_link(todo) diff --git a/app/views/events/event/_common.html.haml b/app/views/events/event/_common.html.haml index 4ecf1c33d2a..e9e16a7646f 100644 --- a/app/views/events/event/_common.html.haml +++ b/app/views/events/event/_common.html.haml @@ -4,7 +4,7 @@ = event_action_name(event) - 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) diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index e53d5b07801..c799e9c588d 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -4,7 +4,7 @@ .header-logo %a#logo = brand_header_logo - = link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home' do + = link_to root_path, class: 'gitlab-text-container-link', title: 'Dashboard', id: 'js-shortcuts-home' do .gitlab-text-container %h3 GitLab diff --git a/app/views/layouts/ci/_page.html.haml b/app/views/layouts/ci/_page.html.haml index 3cfd36720f0..a13241bebee 100644 --- a/app/views/layouts/ci/_page.html.haml +++ b/app/views/layouts/ci/_page.html.haml @@ -4,7 +4,7 @@ .header-logo %a#logo = brand_header_logo - = link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home' do + = link_to root_path, class: 'gitlab-text-container-link', title: 'Dashboard', id: 'js-shortcuts-home' do .gitlab-text-container %h3 GitLab diff --git a/app/views/projects/diffs/_warning.html.haml b/app/views/projects/diffs/_warning.html.haml index f99bc9a85eb..63ede71e6f1 100644 --- a/app/views/projects/diffs/_warning.html.haml +++ b/app/views/projects/diffs/_warning.html.haml @@ -3,17 +3,16 @@ Too many changes to show. .pull-right - 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) - = link_to "Plain diff", namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff), class: "btn btn-warning btn-sm" - = link_to "Email patch", namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch), 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-sm" - 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 "Email patch", merge_request_path(@merge_request, format: :patch), 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-sm" %p To preserve performance only %strong #{shown_files_count} of #{diffs.size} files are displayed. - diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml index 42fa6fdb782..ace22625d1d 100644 --- a/app/views/projects/forks/index.html.haml +++ b/app/views/projects/forks/index.html.haml @@ -1,9 +1,7 @@ .top-area .nav-text - - public_count = @public_forks.size - - protected_count = @protected_forks.size - - full_count_title = "#{public_count} public and #{protected_count} private" - == #{pluralize(@all_forks.size, 'fork')}: #{full_count_title} + - full_count_title = "#{@public_forks_count} public and #{@private_forks_count} private" + == #{pluralize(@total_forks_count, 'fork')}: #{full_count_title} .nav-controls = 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 @@ .projects-list-holder - - if @public_forks.blank? + - if @forks.blank? %ul.content-list %li .nothing-here-block No forks to show - 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 - - if protected_count > 0 + - if @private_forks_count > 0 %ul.projects-list.private-forks-notice %li.project-row = 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. diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index b9d5982a56f..18cf3f14f0b 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -48,7 +48,7 @@ = note_count .merge-request-info - \##{merge_request.iid} · + #{merge_request.to_reference} · opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')} by #{link_to_member(@project, merge_request.author, avatar: false)} - if merge_request.target_project.default_branch != merge_request.target_branch diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 648512e5379..d7bc26e24b9 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -1,4 +1,4 @@ -- 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_card_attributes @merge_request.card_attributes diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml index 14ea7b17786..b634a4af8d2 100644 --- a/app/views/projects/merge_requests/show/_mr_title.html.haml +++ b/app/views/projects/merge_requests/show/_mr_title.html.haml @@ -2,7 +2,7 @@ .status-box{ class: status_box_class(@merge_request) } = @merge_request.state_human_name %span.identifier - Merge Request ##{@merge_request.iid} + Merge Request #{@merge_request.to_reference} %span.creator · by #{link_to_member(@project, @merge_request.author, size: 24)} diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index e858c412836..52972576aff 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -28,7 +28,7 @@ %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') - if note.updated_at != note.created_at - %span + %span.note-updated-at · = icon('edit', title: 'edited') = time_ago_with_tooltip(note.updated_at, placement: 'bottom', html_class: 'note_edited_ago') diff --git a/config/application.rb b/config/application.rb index b905f1a3e90..28684a3e578 100644 --- a/config/application.rb +++ b/config/application.rb @@ -54,14 +54,6 @@ module Gitlab config.action_view.sanitized_allowed_protocols = %w(smb) - # Relative URL support - # WARNING: We recommend using an FQDN to host GitLab in a root path instead - # of using a relative URL. - # Documentation: http://doc.gitlab.com/ce/install/relative_url.html - # Uncomment and customize the following line to run in a non-root path - # - # config.relative_url_root = "/gitlab" - config.middleware.use Rack::Attack # Allow access to GitLab API from other domains diff --git a/config/initializers/relative_url.rb.sample b/config/initializers/relative_url.rb.sample new file mode 100644 index 00000000000..125297d5385 --- /dev/null +++ b/config/initializers/relative_url.rb.sample @@ -0,0 +1,10 @@ +# Relative URL support +# WARNING: We recommend using an FQDN to host GitLab in a root path instead +# of using a relative URL. +# Documentation: http://doc.gitlab.com/ce/install/relative_url.html +# Copy this file to relative_url.rb and customize it to run in a non-root path +# + +Rails.application.configure do + config.relative_url_root = "/gitlab" +end diff --git a/db/migrate/20160222153918_create_appearances_ce.rb b/db/migrate/20160222153918_create_appearances_ce.rb index 5e66d5094bd..bec66bcc71e 100644 --- a/db/migrate/20160222153918_create_appearances_ce.rb +++ b/db/migrate/20160222153918_create_appearances_ce.rb @@ -1,4 +1,4 @@ -class CreateAppearancesCE < ActiveRecord::Migration +class CreateAppearancesCe < ActiveRecord::Migration def change unless table_exists?(:appearances) create_table :appearances do |t| diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 9e89e6e395e..b0e53cbc261 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -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_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_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_DIR** | all | The full path where the repository is cloned and where the build is ran | @@ -104,3 +104,5 @@ job_name: script: - export ``` + +[triggered]: ../triggers/README.md diff --git a/doc/install/relative_url.md b/doc/install/relative_url.md index b341b6311c6..0245febfcd8 100644 --- a/doc/install/relative_url.md +++ b/doc/install/relative_url.md @@ -25,7 +25,7 @@ that points to your GitLab instance. The TL;DR list of configuration files that you need to change in order to serve GitLab under a relative URL is: -- `/home/git/gitlab/config/application.rb` +- `/home/git/gitlab/config/initializers/relative_url.rb` - `/home/git/gitlab/config/gitlab.yml` - `/home/git/gitlab/config/unicorn.rb` - `/home/git/gitlab-shell/config.yml` @@ -66,8 +66,14 @@ Make sure to follow all steps below: sudo service gitlab stop ``` -1. Edit `/home/git/gitlab/config/application.rb` and uncomment/change the - following line: +1. Create `/home/git/gitlab/config/initializers/relative_url.rb` + + ```shell + cp /home/git/gitlab/config/initializers/relative_url.rb.sample \ + /home/git/gitlab/config/initializers/relative_url.rb + ``` + + and change the following line: ```ruby config.relative_url_root = "/gitlab" @@ -119,8 +125,12 @@ Make sure to follow all steps below: ### Disable relative URL in GitLab -To disable the relative URL, follow the same steps as above and set up the -GitLab URL to one that doesn't contain a relative path. +To disable the relative URL: + +1. Remove `/home/git/gitlab/config/initializers/relative_url.rb` + +1. Follow the same as above starting from 2. and set up the + GitLab URL to one that doesn't contain a relative path. [omnibus-rel]: http://doc.gitlab.com/omnibus/settings/configuration.html#configuring-a-relative-url-for-gitlab "How to setup relative URL in Omnibus GitLab" [restart gitlab]: ../administration/restart_gitlab.md#installations-from-source "How to restart GitLab" diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index 9422d438d21..8e74e177ea0 100644 --- a/lib/api/commit_statuses.rb +++ b/lib/api/commit_statuses.rb @@ -18,10 +18,12 @@ module API # Examples: # GET /projects/:id/repository/commits/:sha/statuses get ':id/repository/commits/:sha/statuses' do - authorize! :read_commit_status, user_project - sha = params[:sha] - ci_commit = user_project.ci_commit(sha) - not_found! 'Commit' unless ci_commit + authorize!(:read_commit_status, user_project) + + not_found!('Commit') unless user_project.commit(params[:sha]) + ci_commit = user_project.ci_commit(params[:sha]) + return [] unless ci_commit + statuses = ci_commit.statuses statuses = statuses.latest unless parse_boolean(params[:all]) statuses = statuses.where(ref: params[:ref]) if params[:ref].present? diff --git a/lib/api/internal.rb b/lib/api/internal.rb index e38736fc28b..2200208b946 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -14,6 +14,14 @@ module API # ref - branch name # 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 status 200 @@ -30,13 +38,12 @@ module API # Strip out the .wiki from the pathname before finding the # project. This applies the correct project permissions to # 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) access = - if wiki + if wiki? Gitlab::GitAccessWiki.new(actor, project) else Gitlab::GitAccess.new(actor, project) diff --git a/lib/banzai/filter/gollum_tags_filter.rb b/lib/banzai/filter/gollum_tags_filter.rb index fe01dae4850..bcf5297e382 100644 --- a/lib/banzai/filter/gollum_tags_filter.rb +++ b/lib/banzai/filter/gollum_tags_filter.rb @@ -50,21 +50,30 @@ module Banzai # See https://github.com/gollum/gollum/wiki # # Rubular: http://rubular.com/r/7dQnE5CUCH - TAGS_PATTERN = %r{\[\[(.+?)\]\]} + TAGS_PATTERN = %r{\[\[(.+?)\]\]}.freeze # Pattern to match allowed image extensions - ALLOWED_IMAGE_EXTENSIONS = %r{.+(jpg|png|gif|svg|bmp)\z}i + ALLOWED_IMAGE_EXTENSIONS = %r{.+(jpg|png|gif|svg|bmp)\z}i.freeze def call search_text_nodes(doc).each do |node| - content = node.content + # A Gollum ToC tag is `[[_TOC_]]`, but due to MarkdownFilter running + # before this one, it will be converted into `[[<em>TOC</em>]]`, so it + # needs special-case handling + if toc_tag?(node) + next unless result[:toc].present? - next unless content.match(TAGS_PATTERN) + process_toc_tag(node) + else + content = node.content - html = process_tag($1) + next unless content =~ TAGS_PATTERN - if html && html != node.content - node.replace(html) + html = process_tag($1) + + if html && html != node.content + node.replace(html) + end end end @@ -73,6 +82,12 @@ module Banzai private + # Replace an entire `[[<em>TOC</em>]]` node with the result generated by + # TableOfContentsFilter + def process_toc_tag(node) + node.parent.parent.replace(result[:toc]) + end + # Process a single tag into its final HTML form. # # tag - The String tag contents (the stuff inside the double brackets). @@ -108,6 +123,12 @@ module Banzai end end + def toc_tag?(node) + node.content == 'TOC' && + node.parent.name == 'em' && + node.parent.parent.text == '[[TOC]]' + end + def image?(path) path =~ ALLOWED_IMAGE_EXTENSIONS end diff --git a/lib/banzai/pipeline/wiki_pipeline.rb b/lib/banzai/pipeline/wiki_pipeline.rb index 50b5450e70b..bc2bf111971 100644 --- a/lib/banzai/pipeline/wiki_pipeline.rb +++ b/lib/banzai/pipeline/wiki_pipeline.rb @@ -4,7 +4,11 @@ module Banzai module Pipeline class WikiPipeline < FullPipeline def self.filters - super.insert(1, Filter::GollumTagsFilter) + @filters ||= begin + filters = super + toc = filters.index(Filter::TableOfContentsFilter) + filters.insert(toc + 1, Filter::GollumTagsFilter) + end end end end diff --git a/lib/tasks/cache.rake b/lib/tasks/cache.rake index 9e2fb429d57..f221afcf73a 100644 --- a/lib/tasks/cache.rake +++ b/lib/tasks/cache.rake @@ -1,5 +1,5 @@ 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 desc "GitLab | Clear redis cache" diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index 104a5f50143..7337ff58be1 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -41,6 +41,7 @@ describe UsersController do end describe 'GET #calendar' do + it 'renders calendar' do sign_in(user) @@ -48,6 +49,23 @@ describe UsersController do expect(response).to render_template('calendar') 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 describe 'GET #calendar_activities' do diff --git a/spec/factories.rb b/spec/factories.rb index 264e3ed2c8d..cd57661c1cd 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -32,6 +32,7 @@ FactoryGirl.define do before(:create) do |user| user.two_factor_enabled = true user.otp_secret = User.generate_otp_secret(32) + user.otp_grace_period_started_at = Time.now user.generate_otp_backup_codes! end end diff --git a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb index 38baa819957..11cc290d6d0 100644 --- a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb +++ b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb @@ -86,4 +86,56 @@ describe Banzai::Filter::GollumTagsFilter, lib: true do expect(doc.at_css('a')['href']).to eq 'wiki-slug' end end + + context 'table of contents' do + let(:pipeline) { Banzai::Pipeline[:wiki] } + + it 'replaces the tag with the TableOfContentsFilter result' do + markdown = <<-MD.strip_heredoc + [[_TOC_]] + + ## Header + + Foo + MD + + result = pipeline.call(markdown, project_wiki: project_wiki, project: project) + + aggregate_failures do + expect(result[:output].text).not_to include '[[_TOC_]]' + expect(result[:output].text).not_to include '[[' + expect(result[:output].to_html).to include(result[:toc]) + end + end + + it 'is case-sensitive' do + markdown = <<-MD.strip_heredoc + [[_toc_]] + + # Header 1 + + Foo + MD + + output = pipeline.to_html(markdown, project_wiki: project_wiki, project: project) + + expect(output).to include('[[<em>toc</em>]]') + end + + it 'handles an empty pipeline result' do + # No Markdown headers in this doc, so `result[:toc]` will be empty + markdown = <<-MD.strip_heredoc + [[_TOC_]] + + Foo + MD + + output = pipeline.to_html(markdown, project_wiki: project_wiki, project: project) + + aggregate_failures do + expect(output).not_to include('<ul>') + expect(output).to include('[[<em>TOC</em>]]') + end + end + end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 88821dd0dad..412101ac9f9 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -176,7 +176,7 @@ describe User, models: true do end describe 'avatar' do - it 'only validates when avatar is present' do + it 'only validates when avatar is present and changed' do user = build(:user, :with_avatar) user.avatar_crop_x = nil @@ -184,6 +184,20 @@ describe User, models: true do user.avatar_crop_size = nil expect(user).not_to be_valid + expect(user.errors.keys). + to match_array %i(avatar_crop_x avatar_crop_y avatar_crop_size) + end + + it 'does not validate when avatar has not changed' do + user = create(:user, :with_avatar) + + expect { user.avatar_crop_x = nil }.not_to change(user, :valid?) + end + + it 'does not validate when avatar is not present' do + user = create(:user) + + expect { user.avatar_crop_y = nil }.not_to change(user, :valid?) end end end @@ -268,6 +282,7 @@ describe User, models: true do expect(user).to be_two_factor_enabled expect(user.encrypted_otp_secret).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! @@ -276,6 +291,7 @@ describe User, models: true do expect(user.encrypted_otp_secret_iv).to be_nil expect(user.encrypted_otp_secret_salt).to be_nil expect(user.otp_backup_codes).to be_nil + expect(user.otp_grace_period_started_at).to be_nil end end diff --git a/spec/requests/api/commit_status_spec.rb b/spec/requests/api/commit_status_spec.rb index 89b554622ef..8017ed97d88 100644 --- a/spec/requests/api/commit_status_spec.rb +++ b/spec/requests/api/commit_status_spec.rb @@ -2,88 +2,125 @@ require 'spec_helper' describe API::CommitStatus, api: true do include ApiHelpers + let!(:project) { create(:project) } let(:commit) { project.repository.commit } - let!(:ci_commit) { project.ensure_ci_commit(commit.id) } let(:commit_status) { create(:commit_status, commit: ci_commit) } let(:guest) { create_user(ProjectMember::GUEST) } let(:reporter) { create_user(ProjectMember::REPORTER) } let(:developer) { create_user(ProjectMember::DEVELOPER) } + let(:sha) { commit.id } + describe "GET /projects/:id/repository/commits/:sha/statuses" do - it_behaves_like 'a paginated resources' do - let(:request) { get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses", reporter) } - end + let(:get_url) { "/projects/#{project.id}/repository/commits/#{sha}/statuses" } - context "reporter user" do - let(:statuses_id) { json_response.map { |status| status['id'] } } + context 'ci commit exists' do + let!(:ci_commit) { project.ensure_ci_commit(commit.id) } - before do - @status1 = create(:commit_status, commit: ci_commit, status: 'running') - @status2 = create(:commit_status, commit: ci_commit, name: 'coverage', status: 'pending') - @status3 = create(:commit_status, commit: ci_commit, name: 'coverage', ref: 'develop', status: 'running', allow_failure: true) - @status4 = create(:commit_status, commit: ci_commit, name: 'coverage', status: 'success') - @status5 = create(:commit_status, commit: ci_commit, ref: 'develop', status: 'success') - @status6 = create(:commit_status, commit: ci_commit, status: 'success') + it_behaves_like 'a paginated resources' do + let(:request) { get api(get_url, reporter) } end - it "should return latest commit statuses" do - get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses", reporter) - expect(response.status).to eq(200) + context "reporter user" do + let(:statuses_id) { json_response.map { |status| status['id'] } } - expect(json_response).to be_an Array - expect(statuses_id).to contain_exactly(@status3.id, @status4.id, @status5.id, @status6.id) - json_response.sort_by!{ |status| status['id'] } - expect(json_response.map{ |status| status['allow_failure'] }).to eq([true, false, false, false]) - end + def create_status(opts = {}) + create(:commit_status, { commit: ci_commit }.merge(opts)) + end - it "should return all commit statuses" do - get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses?all=1", reporter) - expect(response.status).to eq(200) + let!(:status1) { create_status(status: 'running') } + let!(:status2) { create_status(name: 'coverage', status: 'pending') } + let!(:status3) { create_status(ref: 'develop', status: 'running', allow_failure: true) } + let!(:status4) { create_status(name: 'coverage', status: 'success') } + let!(:status5) { create_status(name: 'coverage', ref: 'develop', status: 'success') } + let!(:status6) { create_status(status: 'success') } - expect(json_response).to be_an Array - expect(statuses_id).to contain_exactly(@status1.id, @status2.id, @status3.id, @status4.id, @status5.id, @status6.id) - end + context 'latest commit statuses' do + before { get api(get_url, reporter) } - it "should return latest commit statuses for specific ref" do - get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses?ref=develop", reporter) - expect(response.status).to eq(200) + it 'returns latest commit statuses' do + expect(response.status).to eq(200) - expect(json_response).to be_an Array - expect(statuses_id).to contain_exactly(@status3.id, @status5.id) + expect(json_response).to be_an Array + expect(statuses_id).to contain_exactly(status3.id, status4.id, status5.id, status6.id) + json_response.sort_by!{ |status| status['id'] } + expect(json_response.map{ |status| status['allow_failure'] }).to eq([true, false, false, false]) + end + end + + context 'all commit statuses' do + before { get api(get_url, reporter), all: 1 } + + it 'returns all commit statuses' do + expect(response.status).to eq(200) + + expect(json_response).to be_an Array + expect(statuses_id).to contain_exactly(status1.id, status2.id, + status3.id, status4.id, + status5.id, status6.id) + end + end + + context 'latest commit statuses for specific ref' do + before { get api(get_url, reporter), ref: 'develop' } + + it 'returns latest commit statuses for specific ref' do + expect(response.status).to eq(200) + + expect(json_response).to be_an Array + expect(statuses_id).to contain_exactly(status3.id, status5.id) + end + end + + context 'latest commit statues for specific name' do + before { get api(get_url, reporter), name: 'coverage' } + + it 'return latest commit statuses for specific name' do + expect(response.status).to eq(200) + + expect(json_response).to be_an Array + expect(statuses_id).to contain_exactly(status4.id, status5.id) + end + end end + end - it "should return latest commit statuses for specific name" do - get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses?name=coverage", reporter) - expect(response.status).to eq(200) + context 'ci commit does not exist' do + before { get api(get_url, reporter) } + it 'returns empty array' do + expect(response.status).to eq 200 expect(json_response).to be_an Array - expect(statuses_id).to contain_exactly(@status3.id, @status4.id) + expect(json_response).to be_empty end end context "guest user" do + before { get api(get_url, guest) } + it "should not return project commits" do - get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses", guest) expect(response.status).to eq(403) end end context "unauthorized user" do + before { get api(get_url) } + it "should not return project commits" do - get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses") expect(response.status).to eq(401) end end end describe 'POST /projects/:id/statuses/:sha' do - let(:post_url) { "/projects/#{project.id}/statuses/#{commit.id}" } + let(:post_url) { "/projects/#{project.id}/statuses/#{sha}" } context 'developer user' do - context 'should create commit status' do - it 'with only required parameters' do - post api(post_url, developer), state: 'success' + context 'only required parameters' do + before { post api(post_url, developer), state: 'success' } + + it 'creates commit status' do expect(response.status).to eq(201) expect(json_response['sha']).to eq(commit.id) expect(json_response['status']).to eq('success') @@ -92,9 +129,17 @@ describe API::CommitStatus, api: true do expect(json_response['target_url']).to be_nil expect(json_response['description']).to be_nil end + end - it 'with all optional parameters' do - post api(post_url, developer), state: 'success', context: 'coverage', ref: 'develop', target_url: 'url', description: 'test' + context 'with all optional parameters' do + before do + optional_params = { state: 'success', context: 'coverage', + ref: 'develop', target_url: 'url', description: 'test' } + + post api(post_url, developer), optional_params + end + + it 'creates commit status' do expect(response.status).to eq(201) expect(json_response['sha']).to eq(commit.id) expect(json_response['status']).to eq('success') @@ -105,41 +150,52 @@ describe API::CommitStatus, api: true do end end - context 'should not create commit status' do - it 'with invalid state' do - post api(post_url, developer), state: 'invalid' + context 'invalid status' do + before { post api(post_url, developer), state: 'invalid' } + + it 'does not create commit status' do expect(response.status).to eq(400) end + end - it 'without state' do - post api(post_url, developer) + context 'request without state' do + before { post api(post_url, developer) } + + it 'does not create commit status' do expect(response.status).to eq(400) end + end - it 'invalid commit' do - post api("/projects/#{project.id}/statuses/invalid_sha", developer), state: 'running' + context 'invalid commit' do + let(:sha) { 'invalid_sha' } + before { post api(post_url, developer), state: 'running' } + + it 'returns not found error' do expect(response.status).to eq(404) end end end context 'reporter user' do + before { post api(post_url, reporter) } + it 'should not create commit status' do - post api(post_url, reporter) expect(response.status).to eq(403) end end context 'guest user' do + before { post api(post_url, guest) } + it 'should not create commit status' do - post api(post_url, guest) expect(response.status).to eq(403) end end context 'unauthorized user' do + before { post api(post_url) } + it 'should not create commit status' do - post api(post_url) expect(response.status).to eq(401) end end diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 8d0ae1475c2..22802dd0e05 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -54,6 +54,18 @@ describe API::API, api: true do project.team << [user, :developer] 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 it do pull(key, project) |