diff options
766 files changed, 14540 insertions, 4592 deletions
diff --git a/.flayignore b/.flayignore index 47597025115..e2d0a2e50c5 100644 --- a/.flayignore +++ b/.flayignore @@ -3,3 +3,4 @@ lib/gitlab/sanitizers/svg/whitelist.rb lib/gitlab/diff/position_tracer.rb app/policies/project_policy.rb app/models/concerns/relative_positioning.rb +lib/gitlab/redis/*.rb diff --git a/.gitignore b/.gitignore index 0d6194dd1e5..3baf640a9c3 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,9 @@ eslint-report.html /config/initializers/smtp_settings.rb /config/initializers/relative_url.rb /config/resque.yml +/config/redis.cache.yml +/config/redis.queues.yml +/config/redis.shared_state.yml /config/unicorn.rb /config/secrets.yml /config/sidekiq.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f7ab0259448..1a65e0473c4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -160,6 +160,9 @@ build-package: when: manual script: - scripts/trigger-build + only: + - //@gitlab-org/gitlab-ce + - //@gitlab-org/gitlab-ee # Prepare and merge knapsack tests knapsack: @@ -180,6 +183,7 @@ update-knapsack: <<: *only-canonical-masters stage: post-test script: + - retry gem install fog-aws mime-types - scripts/merge-reports ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/rspec-pg_node_*.json - scripts/merge-reports ${KNAPSACK_SPINACH_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/spinach-pg_node_*.json - '[[ -z ${KNAPSACK_S3_BUCKET} ]] || scripts/sync-reports put $KNAPSACK_S3_BUCKET $KNAPSACK_RSPEC_SUITE_REPORT_PATH $KNAPSACK_SPINACH_SUITE_REPORT_PATH' @@ -203,69 +207,69 @@ setup-test-env: - public/assets - tmp/tests -rspec-pg 0 20: *rspec-knapsack-pg -rspec-pg 1 20: *rspec-knapsack-pg -rspec-pg 2 20: *rspec-knapsack-pg -rspec-pg 3 20: *rspec-knapsack-pg -rspec-pg 4 20: *rspec-knapsack-pg -rspec-pg 5 20: *rspec-knapsack-pg -rspec-pg 6 20: *rspec-knapsack-pg -rspec-pg 7 20: *rspec-knapsack-pg -rspec-pg 8 20: *rspec-knapsack-pg -rspec-pg 9 20: *rspec-knapsack-pg -rspec-pg 10 20: *rspec-knapsack-pg -rspec-pg 11 20: *rspec-knapsack-pg -rspec-pg 12 20: *rspec-knapsack-pg -rspec-pg 13 20: *rspec-knapsack-pg -rspec-pg 14 20: *rspec-knapsack-pg -rspec-pg 15 20: *rspec-knapsack-pg -rspec-pg 16 20: *rspec-knapsack-pg -rspec-pg 17 20: *rspec-knapsack-pg -rspec-pg 18 20: *rspec-knapsack-pg -rspec-pg 19 20: *rspec-knapsack-pg - -rspec-mysql 0 20: *rspec-knapsack-mysql -rspec-mysql 1 20: *rspec-knapsack-mysql -rspec-mysql 2 20: *rspec-knapsack-mysql -rspec-mysql 3 20: *rspec-knapsack-mysql -rspec-mysql 4 20: *rspec-knapsack-mysql -rspec-mysql 5 20: *rspec-knapsack-mysql -rspec-mysql 6 20: *rspec-knapsack-mysql -rspec-mysql 7 20: *rspec-knapsack-mysql -rspec-mysql 8 20: *rspec-knapsack-mysql -rspec-mysql 9 20: *rspec-knapsack-mysql -rspec-mysql 10 20: *rspec-knapsack-mysql -rspec-mysql 11 20: *rspec-knapsack-mysql -rspec-mysql 12 20: *rspec-knapsack-mysql -rspec-mysql 13 20: *rspec-knapsack-mysql -rspec-mysql 14 20: *rspec-knapsack-mysql -rspec-mysql 15 20: *rspec-knapsack-mysql -rspec-mysql 16 20: *rspec-knapsack-mysql -rspec-mysql 17 20: *rspec-knapsack-mysql -rspec-mysql 18 20: *rspec-knapsack-mysql -rspec-mysql 19 20: *rspec-knapsack-mysql - -spinach-pg 0 10: *spinach-knapsack-pg -spinach-pg 1 10: *spinach-knapsack-pg -spinach-pg 2 10: *spinach-knapsack-pg -spinach-pg 3 10: *spinach-knapsack-pg -spinach-pg 4 10: *spinach-knapsack-pg -spinach-pg 5 10: *spinach-knapsack-pg -spinach-pg 6 10: *spinach-knapsack-pg -spinach-pg 7 10: *spinach-knapsack-pg -spinach-pg 8 10: *spinach-knapsack-pg -spinach-pg 9 10: *spinach-knapsack-pg - -spinach-mysql 0 10: *spinach-knapsack-mysql -spinach-mysql 1 10: *spinach-knapsack-mysql -spinach-mysql 2 10: *spinach-knapsack-mysql -spinach-mysql 3 10: *spinach-knapsack-mysql -spinach-mysql 4 10: *spinach-knapsack-mysql -spinach-mysql 5 10: *spinach-knapsack-mysql -spinach-mysql 6 10: *spinach-knapsack-mysql -spinach-mysql 7 10: *spinach-knapsack-mysql -spinach-mysql 8 10: *spinach-knapsack-mysql -spinach-mysql 9 10: *spinach-knapsack-mysql +rspec-pg 0 25: *rspec-knapsack-pg +rspec-pg 1 25: *rspec-knapsack-pg +rspec-pg 2 25: *rspec-knapsack-pg +rspec-pg 3 25: *rspec-knapsack-pg +rspec-pg 4 25: *rspec-knapsack-pg +rspec-pg 5 25: *rspec-knapsack-pg +rspec-pg 6 25: *rspec-knapsack-pg +rspec-pg 7 25: *rspec-knapsack-pg +rspec-pg 8 25: *rspec-knapsack-pg +rspec-pg 9 25: *rspec-knapsack-pg +rspec-pg 10 25: *rspec-knapsack-pg +rspec-pg 11 25: *rspec-knapsack-pg +rspec-pg 12 25: *rspec-knapsack-pg +rspec-pg 13 25: *rspec-knapsack-pg +rspec-pg 14 25: *rspec-knapsack-pg +rspec-pg 15 25: *rspec-knapsack-pg +rspec-pg 16 25: *rspec-knapsack-pg +rspec-pg 17 25: *rspec-knapsack-pg +rspec-pg 18 25: *rspec-knapsack-pg +rspec-pg 19 25: *rspec-knapsack-pg +rspec-pg 20 25: *rspec-knapsack-pg +rspec-pg 21 25: *rspec-knapsack-pg +rspec-pg 22 25: *rspec-knapsack-pg +rspec-pg 23 25: *rspec-knapsack-pg +rspec-pg 24 25: *rspec-knapsack-pg + +rspec-mysql 0 25: *rspec-knapsack-mysql +rspec-mysql 1 25: *rspec-knapsack-mysql +rspec-mysql 2 25: *rspec-knapsack-mysql +rspec-mysql 3 25: *rspec-knapsack-mysql +rspec-mysql 4 25: *rspec-knapsack-mysql +rspec-mysql 5 25: *rspec-knapsack-mysql +rspec-mysql 6 25: *rspec-knapsack-mysql +rspec-mysql 7 25: *rspec-knapsack-mysql +rspec-mysql 8 25: *rspec-knapsack-mysql +rspec-mysql 9 25: *rspec-knapsack-mysql +rspec-mysql 10 25: *rspec-knapsack-mysql +rspec-mysql 11 25: *rspec-knapsack-mysql +rspec-mysql 12 25: *rspec-knapsack-mysql +rspec-mysql 13 25: *rspec-knapsack-mysql +rspec-mysql 14 25: *rspec-knapsack-mysql +rspec-mysql 15 25: *rspec-knapsack-mysql +rspec-mysql 16 25: *rspec-knapsack-mysql +rspec-mysql 17 25: *rspec-knapsack-mysql +rspec-mysql 18 25: *rspec-knapsack-mysql +rspec-mysql 19 25: *rspec-knapsack-mysql +rspec-mysql 20 25: *rspec-knapsack-mysql +rspec-mysql 21 25: *rspec-knapsack-mysql +rspec-mysql 22 25: *rspec-knapsack-mysql +rspec-mysql 23 25: *rspec-knapsack-mysql +rspec-mysql 24 25: *rspec-knapsack-mysql + +spinach-pg 0 5: *spinach-knapsack-pg +spinach-pg 1 5: *spinach-knapsack-pg +spinach-pg 2 5: *spinach-knapsack-pg +spinach-pg 3 5: *spinach-knapsack-pg +spinach-pg 4 5: *spinach-knapsack-pg + +spinach-mysql 0 5: *spinach-knapsack-mysql +spinach-mysql 1 5: *spinach-knapsack-mysql +spinach-mysql 2 5: *spinach-knapsack-mysql +spinach-mysql 3 5: *spinach-knapsack-mysql +spinach-mysql 4 5: *spinach-knapsack-mysql # Static analysis jobs .ruby-static-analysis: &ruby-static-analysis diff --git a/.scss-lint.yml b/.scss-lint.yml index db234ad739c..73f8d27f78c 100644 --- a/.scss-lint.yml +++ b/.scss-lint.yml @@ -10,7 +10,7 @@ linters: # Reports when you use improper spacing around ! (the "bang") in !default, # !global, !important, and !optional flags. BangFormat: - enabled: false + enabled: true # Whether or not to prefer `border: 0` over `border: none`. BorderZero: @@ -43,10 +43,11 @@ linters: # Rule sets should be ordered as follows: # - @extend declarations # - @include declarations without inner @content - # - properties, @include declarations with inner @content + # - properties + # - @include declarations with inner @content # - nested rule sets. DeclarationOrder: - enabled: false + enabled: true # `scss-lint:disable` control comments should be preceded by a comment # explaining why these linters are being disabled for this file. @@ -93,7 +94,7 @@ linters: # The basenames of @imported SCSS partials should not begin with an # underscore and should not include the filename extension. ImportPath: - enabled: false + enabled: true # Avoid using !important in properties. It is usually indicative of a # misunderstanding of CSS specificity and can lead to brittle code. @@ -133,7 +134,7 @@ linters: # Reports when you use an unknown or disabled CSS property # (ignoring vendor-prefixed properties). PropertySpelling: - enabled: false + enabled: true # Configure which units are allowed for property values. PropertyUnits: @@ -176,6 +177,10 @@ linters: # Commas in lists should be followed by a space. SpaceAfterComma: + enabled: true + + # Comment literals should be followed by a space. + SpaceAfterComment: enabled: false # Properties should be formatted with a single space separating the colon @@ -240,7 +245,7 @@ linters: # Do not use parent selector references (&) when they would otherwise # be unnecessary. UnnecessaryParentReference: - enabled: false + enabled: true # URLs should be valid and not contain protocols or domain names. UrlFormat: diff --git a/CHANGELOG.md b/CHANGELOG.md index c15a59d25d4..de3b4b0d3e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,25 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 9.3.8 (2017-07-19) + +- Improve support for external issue references. !12485 +- Renders 404 if given project is not readable by the user on Todos dashboard. +- Use uploads/system directory for personal snippets. +- Remove uploads/appearance symlink. A leftover from a previous migration. + +## 9.3.7 (2017-07-18) + +- Prevent bad data being added to application settings when Redis is unavailable. !12750 +- Return `is_admin` attribute in the GET /user endpoint for admins. !12811 + +## 9.3.6 (2017-07-12) + +- Fix API Scoping. !12300 +- Username and password are no longer stripped from import url on mirror update. !12725 +- Fix issues with non-UTF8 filenames by always fixing the encoding of tree and blob paths. +- Fixed GFM references not being included when updating issues inline. + ## 9.3.5 (2017-07-05) - Remove "Remove from board" button from backlog and closed list. !12430 @@ -251,6 +270,13 @@ entry. - Remove foreigh key on ci_trigger_schedules only if it exists. - Allow translation of Pipeline Schedules. +## 9.2.8 (2017-07-19) + +- Improve support for external issue references. !12485 +- Renders 404 if given project is not readable by the user on Todos dashboard. +- Fix incorrect project authorizations. +- Remove uploads/appearance symlink. A leftover from a previous migration. + ## 9.2.7 (2017-06-21) - Reinstate is_admin flag in users api when authenticated user is an admin. !12211 (rickettm) @@ -495,6 +521,13 @@ entry. - Fix preemptive scroll bar on user activity calendar. - Pipeline chat notifications convert seconds to minutes and hours. +## 9.1.8 (2017-07-19) + +- Improve support for external issue references. !12485 +- Renders 404 if given project is not readable by the user on Todos dashboard. +- Fix incorrect project authorizations. +- Remove uploads/appearance symlink. A leftover from a previous migration. + ## 9.1.7 (2017-06-07) - No changes. @@ -807,6 +840,12 @@ entry. - Only send chat notifications for the default branch. - Don't fill in the default kubernetes namespace. +## 9.0.11 (2017-07-19) + +- Renders 404 if given project is not readable by the user on Todos dashboard. +- Fix incorrect project authorizations. +- Remove uploads/appearance symlink. A leftover from a previous migration. + ## 9.0.10 (2017-06-07) - No changes. @@ -1177,6 +1216,11 @@ entry. - Change development tanuki favicon colors to match logo color order. - API issues - support filtering by iids. +## 8.17.7 (2017-07-19) + +- Renders 404 if given project is not readable by the user on Todos dashboard. +- Fix incorrect project authorizations. + ## 8.17.6 (2017-05-05) - Enforce project features when searching blobs and wikis. diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 04a373efe6b..59dad104b0b 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0.16.0 +0.21.2 diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index ac14c3dfaa8..c7cb1311a64 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -5.1.1 +5.3.1 diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index 276cbf9e285..4a36342fcab 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -2.3.0 +3.0.0 @@ -2,7 +2,6 @@ source 'https://rubygems.org' gem 'rails', '4.2.8' gem 'rails-deprecated_sanitizer', '~> 1.0.3' -gem 'bootsnap', '~> 1.1' # Responders respond_to and respond_with gem 'responders', '~> 2.0' @@ -13,7 +12,7 @@ gem 'sprockets', '~> 3.7.0' gem 'default_value_for', '~> 3.0.0' # Supported DBs -gem 'mysql2', '~> 0.3.16', group: :mysql +gem 'mysql2', '~> 0.4.5', group: :mysql gem 'pg', '~> 0.18.2', group: :postgres gem 'rugged', '~> 0.25.1.1' @@ -38,7 +37,7 @@ gem 'omniauth-saml', '~> 1.7.0' gem 'omniauth-shibboleth', '~> 1.2.0' gem 'omniauth-twitter', '~> 1.2.0' gem 'omniauth_crowd', '~> 2.2.0' -gem 'omniauth-authentiq', '~> 0.3.0' +gem 'omniauth-authentiq', '~> 0.3.1' gem 'rack-oauth2', '~> 1.2.1' gem 'jwt', '~> 1.5.6' @@ -92,7 +91,7 @@ gem 'carrierwave', '~> 1.1' gem 'dropzonejs-rails', '~> 0.7.1' # for backups -gem 'fog-aws', '~> 0.9' +gem 'fog-aws', '~> 1.4' gem 'fog-core', '~> 1.44' gem 'fog-google', '~> 0.5' gem 'fog-local', '~> 0.3' @@ -164,6 +163,9 @@ gem 'rainbow', '~> 2.2' # GitLab settings gem 'settingslogic', '~> 2.0.9' +# Linear-time regex library for untrusted regular expressions +gem 're2', '~> 1.0.0' + # Misc gem 'version_sorter', '~> 2.1.0' @@ -238,7 +240,6 @@ gem 'webpack-rails', '~> 0.9.10' gem 'rack-proxy', '~> 0.6.0' gem 'sass-rails', '~> 5.0.6' -gem 'coffee-rails', '~> 4.1.0' gem 'uglifier', '~> 2.7.2' gem 'addressable', '~> 2.3.8' @@ -251,7 +252,6 @@ gem 'jquery-rails', '~> 4.1.0' gem 'request_store', '~> 1.3' gem 'select2-rails', '~> 3.5.9' gem 'virtus', '~> 1.0.1' -gem 'net-ssh', '~> 3.0.1' gem 'base32', '~> 0.3.0' # Sentry integration @@ -271,7 +271,7 @@ gem 'peek', '~> 1.0.1' gem 'peek-gc', '~> 0.0.2' gem 'peek-host', '~> 1.0.0' gem 'peek-mysql2', '~> 1.1.0', group: :mysql -gem 'peek-performance_bar', '~> 1.2.1' +gem 'peek-performance_bar', '~> 1.3.0' gem 'peek-pg', '~> 1.3.0', group: :postgres gem 'peek-rblineprof', '~> 0.2.0' gem 'peek-redis', '~> 1.2.0' @@ -284,7 +284,7 @@ group :metrics do gem 'influxdb', '~> 0.2', require: false # Prometheus - gem 'prometheus-client-mmap', '~>0.7.0.beta5' + gem 'prometheus-client-mmap', '~>0.7.0.beta9' gem 'raindrops', '~> 0.18' end @@ -336,7 +336,7 @@ group :development, :test do gem 'rubocop', '~> 0.47.1', require: false gem 'rubocop-rspec', '~> 1.15.0', require: false - gem 'scss_lint', '~> 0.47.0', require: false + gem 'scss_lint', '~> 0.54.0', require: false gem 'haml_lint', '~> 0.21.0', require: false gem 'simplecov', '~> 0.14.0', require: false gem 'flay', '~> 2.8.0', require: false @@ -393,3 +393,6 @@ gem 'toml-rb', '~> 0.3.15', require: false # Feature toggles gem 'flipper', '~> 0.10.2' gem 'flipper-active_record', '~> 0.10.2' + +# Structured logging +gem 'lograge', '~> 0.5' diff --git a/Gemfile.lock b/Gemfile.lock index f356024506c..dfa7acc8917 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -83,8 +83,6 @@ GEM bindata (2.3.5) binding_of_caller (0.7.2) debug_inspector (>= 0.0.1) - bootsnap (1.1.1) - msgpack (~> 1.0) bootstrap-sass (3.3.6) autoprefixer-rails (>= 5.2.1) sass (>= 3.3.4) @@ -124,13 +122,6 @@ GEM coderay (1.1.1) coercible (1.0.0) descendants_tracker (~> 0.0.1) - coffee-rails (4.1.1) - coffee-script (>= 2.2.0) - railties (>= 4.0.0, < 5.1.x) - coffee-script (2.4.1) - coffee-script-source - execjs - coffee-script-source (1.10.0) colorize (0.7.7) concurrent-ruby (1.0.5) concurrent-ruby-ext (1.0.5) @@ -188,7 +179,7 @@ GEM et-orbi (1.0.3) tzinfo eventmachine (1.0.8) - excon (0.55.0) + excon (0.57.1) execjs (2.6.0) expression_parser (0.9.0) extlib (0.9.16) @@ -224,26 +215,26 @@ GEM fog-json (~> 1.0) ipaddress (~> 0.8) xml-simple (~> 1.1) - fog-aws (0.13.0) + fog-aws (1.4.0) fog-core (~> 1.38) fog-json (~> 1.0) fog-xml (~> 0.1) ipaddress (~> 0.8) - fog-core (1.44.1) + fog-core (1.44.3) builder excon (~> 0.49) formatador (~> 0.2) - fog-google (0.5.0) + fog-google (0.5.3) fog-core fog-json fog-xml fog-json (1.0.2) fog-core (~> 1.0) multi_json (~> 1.10) - fog-local (0.3.0) + fog-local (0.3.1) fog-core (~> 1.27) - fog-openstack (0.1.6) - fog-core (>= 1.39) + fog-openstack (0.1.21) + fog-core (>= 1.40) fog-json (>= 1.0) ipaddress (>= 0.8) fog-rackspace (0.1.1) @@ -452,6 +443,10 @@ GEM logging (2.2.2) little-plugger (~> 1.1) multi_json (~> 1.10) + lograge (0.5.1) + actionpack (>= 4, < 5.2) + activesupport (>= 4, < 5.2) + railties (>= 4, < 5.2) loofah (2.0.3) nokogiri (>= 1.5.9) mail (2.6.5) @@ -465,7 +460,6 @@ GEM minitest (5.7.0) mmap2 (2.2.7) mousetrap-rails (1.4.6) - msgpack (1.1.0) multi_json (1.12.1) multi_xml (0.6.0) multipart-post (2.0.0) @@ -473,9 +467,8 @@ GEM tool (~> 0.2) mustermann-grape (0.4.0) mustermann (= 0.4.0) - mysql2 (0.3.20) + mysql2 (0.4.5) net-ldap (0.12.1) - net-ssh (3.0.1) netrc (0.11.0) nokogiri (1.6.8.1) mini_portile2 (~> 2.1.0) @@ -495,7 +488,7 @@ GEM rack (>= 1.0, < 3) omniauth-auth0 (1.4.1) omniauth-oauth2 (~> 1.1) - omniauth-authentiq (0.3.0) + omniauth-authentiq (0.3.1) omniauth-oauth2 (~> 1.3, >= 1.3.1) omniauth-azure-oauth2 (0.0.6) jwt (~> 1.0) @@ -564,7 +557,7 @@ GEM atomic (>= 1.0.0) mysql2 peek - peek-performance_bar (1.2.1) + peek-performance_bar (1.3.0) peek (>= 0.1.0) peek-pg (1.3.0) concurrent-ruby @@ -599,7 +592,7 @@ GEM premailer-rails (1.9.7) actionmailer (>= 3, < 6) premailer (~> 1.7, >= 1.7.9) - prometheus-client-mmap (0.7.0.beta8) + prometheus-client-mmap (0.7.0.beta9) mmap2 (~> 2.2, >= 2.2.7) pry (0.10.4) coderay (~> 1.1.0) @@ -664,6 +657,7 @@ GEM debugger-ruby_core_source (~> 1.3) rdoc (4.2.2) json (~> 1.4) + re2 (1.0.0) recaptcha (3.0.0) json recursive-open-struct (1.0.0) @@ -766,9 +760,9 @@ GEM sawyer (0.8.1) addressable (>= 2.3.5, < 2.6) faraday (~> 0.8, < 1.0) - scss_lint (0.47.1) - rake (>= 0.9, < 11) - sass (~> 3.4.15) + scss_lint (0.54.0) + rake (>= 0.9, < 13) + sass (~> 3.4.20) securecompare (1.0.0) seed-fu (2.3.6) activerecord (>= 3.1) @@ -783,7 +777,7 @@ GEM rack shoulda-matchers (2.8.0) activesupport (>= 3.0.0) - sidekiq (5.0.0) + sidekiq (5.0.4) concurrent-ruby (~> 1.0) connection_pool (~> 2.2, >= 2.2.0) rack-protection (>= 1.5.0) @@ -930,7 +924,6 @@ DEPENDENCIES benchmark-ips (~> 2.3.0) better_errors (~> 2.1.0) binding_of_caller (~> 0.7.2) - bootsnap (~> 1.1) bootstrap-sass (~> 3.3.0) bootstrap_form (~> 2.7.0) brakeman (~> 3.6.0) @@ -943,7 +936,6 @@ DEPENDENCIES charlock_holmes (~> 0.7.3) chronic (~> 0.10.2) chronic_duration (~> 0.10.6) - coffee-rails (~> 4.1.0) concurrent-ruby (~> 1.0.5) connection_pool (~> 2.0) creole (~> 0.5.0) @@ -966,7 +958,7 @@ DEPENDENCIES flipper (~> 0.10.2) flipper-active_record (~> 0.10.2) fog-aliyun (~> 0.1.0) - fog-aws (~> 0.9) + fog-aws (~> 1.4) fog-core (~> 1.44) fog-google (~> 0.5) fog-local (~> 0.3) @@ -1011,20 +1003,20 @@ DEPENDENCIES letter_opener_web (~> 1.3.0) license_finder (~> 2.1.0) licensee (~> 8.7.0) + lograge (~> 0.5) loofah (~> 2.0.3) mail_room (~> 0.9.1) method_source (~> 0.8) minitest (~> 5.7.0) mousetrap-rails (~> 1.4.6) - mysql2 (~> 0.3.16) - net-ssh (~> 3.0.1) + mysql2 (~> 0.4.5) nokogiri (~> 1.6.7, >= 1.6.7.2) oauth2 (~> 1.4) octokit (~> 4.6.2) oj (~> 2.17.4) omniauth (~> 1.4.2) omniauth-auth0 (~> 1.4.1) - omniauth-authentiq (~> 0.3.0) + omniauth-authentiq (~> 0.3.1) omniauth-azure-oauth2 (~> 0.0.6) omniauth-cas3 (~> 1.1.2) omniauth-facebook (~> 4.0.0) @@ -1043,7 +1035,7 @@ DEPENDENCIES peek-gc (~> 0.0.2) peek-host (~> 1.0.0) peek-mysql2 (~> 1.1.0) - peek-performance_bar (~> 1.2.1) + peek-performance_bar (~> 1.3.0) peek-pg (~> 1.3.0) peek-rblineprof (~> 0.2.0) peek-redis (~> 1.2.0) @@ -1051,7 +1043,7 @@ DEPENDENCIES pg (~> 0.18.2) poltergeist (~> 1.9.0) premailer-rails (~> 1.9.7) - prometheus-client-mmap (~> 0.7.0.beta5) + prometheus-client-mmap (~> 0.7.0.beta9) pry-byebug (~> 3.4.1) pry-rails (~> 0.3.4) rack-attack (~> 4.4.1) @@ -1065,6 +1057,7 @@ DEPENDENCIES raindrops (~> 0.18) rblineprof (~> 0.3.6) rdoc (~> 4.2) + re2 (~> 1.0.0) recaptcha (~> 3.0) redcarpet (~> 3.4) redis (~> 3.2) @@ -1087,7 +1080,7 @@ DEPENDENCIES rugged (~> 0.25.1.1) sanitize (~> 2.0) sass-rails (~> 5.0.6) - scss_lint (~> 0.47.0) + scss_lint (~> 0.54.0) seed-fu (~> 2.3.5) select2-rails (~> 3.5.9) sentry-raven (~> 2.5.3) diff --git a/README.md b/README.md index 59de828e1ac..9309922ae39 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ ## Test coverage - [![Ruby coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby) Ruby -- [![JavaScript coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=rake+karma)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-javascript) JavaScript +- [![JavaScript coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=karma)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-javascript) JavaScript ## Canonical source diff --git a/app/assets/images/new_nav.png b/app/assets/images/new_nav.png Binary files differindex 8879d26d341..f98ca15d787 100644 --- a/app/assets/images/new_nav.png +++ b/app/assets/images/new_nav.png diff --git a/app/assets/javascripts/blob/notebook/index.js b/app/assets/javascripts/blob/notebook/index.js index 36fe8a7184f..27312d718b0 100644 --- a/app/assets/javascripts/blob/notebook/index.js +++ b/app/assets/javascripts/blob/notebook/index.js @@ -51,8 +51,9 @@ export default () => { methods: { loadFile() { this.$http.get(el.dataset.endpoint) + .then(response => response.json()) .then((res) => { - this.json = res.json(); + this.json = res; this.loading = false; }) .catch((e) => { diff --git a/app/assets/javascripts/boards/boards_bundle.js b/app/assets/javascripts/boards/boards_bundle.js index b94009ee76b..88b054b76e6 100644 --- a/app/assets/javascripts/boards/boards_bundle.js +++ b/app/assets/javascripts/boards/boards_bundle.js @@ -81,8 +81,9 @@ $(() => { mounted () { Store.disabled = this.disabled; gl.boardService.all() + .then(response => response.json()) .then((resp) => { - resp.json().forEach((board) => { + resp.forEach((board) => { const list = Store.addList(board, this.defaultAvatar); if (list.type === 'closed') { @@ -97,7 +98,8 @@ $(() => { Store.addBlankState(); this.loading = false; - }).catch(() => new Flash('An error occurred. Please try again.')); + }) + .catch(() => new Flash('An error occurred. Please try again.')); }, methods: { updateTokens() { diff --git a/app/assets/javascripts/boards/components/board_blank_state.js b/app/assets/javascripts/boards/components/board_blank_state.js index 870e115bd1a..e7f16899362 100644 --- a/app/assets/javascripts/boards/components/board_blank_state.js +++ b/app/assets/javascripts/boards/components/board_blank_state.js @@ -64,8 +64,9 @@ export default { // Save the labels gl.boardService.generateDefaultLists() - .then((resp) => { - resp.json().forEach((listObj) => { + .then(resp => resp.json()) + .then((data) => { + data.forEach((listObj) => { const list = Store.findList('title', listObj.title); list.id = listObj.id; diff --git a/app/assets/javascripts/boards/components/modal/index.js b/app/assets/javascripts/boards/components/modal/index.js index 6356c266ee2..1d36519c75c 100644 --- a/app/assets/javascripts/boards/components/modal/index.js +++ b/app/assets/javascripts/boards/components/modal/index.js @@ -88,9 +88,9 @@ gl.issueBoards.IssuesModal = Vue.extend({ return gl.boardService.getBacklog(queryData(this.filter.path, { page: this.page, per: this.perPage, - })).then((res) => { - const data = res.json(); - + })) + .then(resp => resp.json()) + .then((data) => { if (clearIssues) { this.issues = []; } diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js index b4b09b3876e..08f7c5ddcd2 100644 --- a/app/assets/javascripts/boards/models/list.js +++ b/app/assets/javascripts/boards/models/list.js @@ -40,9 +40,8 @@ class List { save () { return gl.boardService.createList(this.label.id) - .then((resp) => { - const data = resp.json(); - + .then(resp => resp.json()) + .then((data) => { this.id = data.id; this.type = data.list_type; this.position = data.position; @@ -91,8 +90,8 @@ class List { } return gl.boardService.getIssuesForList(this.id, data) - .then((resp) => { - const data = resp.json(); + .then(resp => resp.json()) + .then((data) => { this.loading = false; this.issuesSize = data.size; @@ -109,8 +108,8 @@ class List { this.issuesSize += 1; return gl.boardService.newIssue(this.id, issue) - .then((resp) => { - const data = resp.json(); + .then(resp => resp.json()) + .then((data) => { issue.id = data.iid; if (this.issuesSize > 1) { diff --git a/app/assets/javascripts/boards/services/board_service.js b/app/assets/javascripts/boards/services/board_service.js index db9bced2f89..3742507b236 100644 --- a/app/assets/javascripts/boards/services/board_service.js +++ b/app/assets/javascripts/boards/services/board_service.js @@ -23,11 +23,6 @@ class BoardService { url: bulkUpdatePath, }, }); - - Vue.http.interceptors.push((request, next) => { - request.headers['X-CSRF-Token'] = $.rails.csrfToken(); - next(); - }); } all () { diff --git a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js index 2c38440a2af..687f09882a7 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js +++ b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js @@ -18,13 +18,26 @@ window.gl.CommitPipelinesTable = CommitPipelinesTable; document.addEventListener('DOMContentLoaded', () => { const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view'); - if (pipelineTableViewEl && pipelineTableViewEl.dataset.disableInitialization === undefined) { - const table = new CommitPipelinesTable({ - propsData: { - endpoint: pipelineTableViewEl.dataset.endpoint, - helpPagePath: pipelineTableViewEl.dataset.helpPagePath, - }, - }).$mount(); - pipelineTableViewEl.appendChild(table.$el); + if (pipelineTableViewEl) { + // Update MR and Commits tabs + pipelineTableViewEl.addEventListener('update-pipelines-count', (event) => { + if (event.detail.pipelines && + event.detail.pipelines.count && + event.detail.pipelines.count.all) { + const badge = document.querySelector('.js-pipelines-mr-count'); + + badge.textContent = event.detail.pipelines.count.all; + } + }); + + if (pipelineTableViewEl.dataset.disableInitialization === undefined) { + const table = new CommitPipelinesTable({ + propsData: { + endpoint: pipelineTableViewEl.dataset.endpoint, + helpPagePath: pipelineTableViewEl.dataset.helpPagePath, + }, + }).$mount(); + pipelineTableViewEl.appendChild(table.$el); + } } }); diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.vue b/app/assets/javascripts/commit/pipelines/pipelines_table.vue index 3c77f14d533..dd751ec97a8 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_table.vue +++ b/app/assets/javascripts/commit/pipelines/pipelines_table.vue @@ -51,11 +51,22 @@ }, methods: { successCallback(resp) { - const response = resp.json(); + return resp.json().then((response) => { + // depending of the endpoint the response can either bring a `pipelines` key or not. + const pipelines = response.pipelines || response; + this.setCommonData(pipelines); - // depending of the endpoint the response can either bring a `pipelines` key or not. - const pipelines = response.pipelines || response; - this.setCommonData(pipelines); + const updatePipelinesEvent = new CustomEvent('update-pipelines-count', { + detail: { + pipelines: response, + }, + }); + + // notifiy to update the count in tabs + if (this.$el.parentElement) { + this.$el.parentElement.dispatchEvent(updatePipelinesEvent); + } + }); }, }, }; diff --git a/app/assets/javascripts/diff_notes/components/resolve_btn.js b/app/assets/javascripts/diff_notes/components/resolve_btn.js index 9d51fb53eb2..efb6ced9f46 100644 --- a/app/assets/javascripts/diff_notes/components/resolve_btn.js +++ b/app/assets/javascripts/diff_notes/components/resolve_btn.js @@ -1,4 +1,4 @@ -/* eslint-disable comma-dangle, object-shorthand, func-names, quote-props, no-else-return, camelcase, no-new, max-len */ +/* eslint-disable comma-dangle, object-shorthand, func-names, quote-props, no-else-return, camelcase, max-len */ /* global CommentsStore */ /* global ResolveService */ /* global Flash */ @@ -64,8 +64,6 @@ const ResolveBtn = Vue.extend({ }); }, resolve: function () { - const errorFlashMsg = 'An error occurred when trying to resolve a comment. Please try again.'; - if (!this.canResolve) return; let promise; @@ -79,24 +77,20 @@ const ResolveBtn = Vue.extend({ .resolve(this.noteId); } - promise.then((response) => { - this.loading = false; + promise + .then(resp => resp.json()) + .then((data) => { + this.loading = false; - if (response.status === 200) { - const data = response.json(); const resolved_by = data ? data.resolved_by : null; CommentsStore.update(this.discussionId, this.noteId, !this.isResolved, resolved_by); this.discussion.updateHeadline(data); gl.mrWidget.checkStatus(); - } else { - new Flash(errorFlashMsg); - } - this.updateTooltip(); - }).catch(() => { - new Flash(errorFlashMsg); - }); + this.updateTooltip(); + }) + .catch(() => new Flash('An error occurred when trying to resolve a comment. Please try again.')); } }, mounted: function () { diff --git a/app/assets/javascripts/diff_notes/services/resolve.js b/app/assets/javascripts/diff_notes/services/resolve.js index 807ab11d292..2f063f6fe1f 100644 --- a/app/assets/javascripts/diff_notes/services/resolve.js +++ b/app/assets/javascripts/diff_notes/services/resolve.js @@ -1,4 +1,3 @@ -/* eslint-disable class-methods-use-this, one-var, camelcase, no-new, comma-dangle, no-param-reassign, max-len */ /* global Flash */ /* global CommentsStore */ @@ -32,27 +31,22 @@ class ResolveServiceClass { promise = this.resolveAll(mergeRequestId, discussionId); } - promise.then((response) => { - discussion.loading = false; - - if (response.status === 200) { - const data = response.json(); - const resolved_by = data ? data.resolved_by : null; + promise + .then(resp => resp.json()) + .then((data) => { + discussion.loading = false; + const resolvedBy = data ? data.resolved_by : null; if (isResolved) { discussion.unResolveAllNotes(); } else { - discussion.resolveAllNotes(resolved_by); + discussion.resolveAllNotes(resolvedBy); } gl.mrWidget.checkStatus(); discussion.updateHeadline(data); - } else { - throw new Error('An error occurred when trying to resolve discussion.'); - } - }).catch(() => { - new Flash('An error occurred when trying to resolve a discussion. Please try again.'); - }); + }) + .catch(() => new Flash('An error occurred when trying to resolve a discussion. Please try again.')); } resolveAll(mergeRequestId, discussionId) { @@ -62,7 +56,7 @@ class ResolveServiceClass { return this.discussionResource.save({ mergeRequestId, - discussionId + discussionId, }, {}); } @@ -73,7 +67,7 @@ class ResolveServiceClass { return this.discussionResource.delete({ mergeRequestId, - discussionId + discussionId, }, {}); } } diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index ae19592ecbe..9e90a36a364 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -1,4 +1,5 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, wrap-iife, no-shadow, consistent-return, one-var, one-var-declaration-per-line, camelcase, default-case, no-new, quotes, no-duplicate-case, no-case-declarations, no-fallthrough, max-len */ +/* global ProjectSelect */ /* global ShortcutsNavigation */ /* global IssuableIndex */ /* global ShortcutsIssuable */ @@ -157,6 +158,9 @@ import PerformanceBar from './performance_bar'; shortcut_handler = new ShortcutsIssuable(); new ZenMode(); break; + case 'dashboard:milestones:index': + new ProjectSelect(); + break; case 'projects:milestones:show': case 'groups:milestones:show': case 'dashboard:milestones:show': @@ -166,6 +170,7 @@ import PerformanceBar from './performance_bar'; case 'groups:issues': case 'groups:merge_requests': new UsersSelect(); + new ProjectSelect(); break; case 'dashboard:todos:index': new Todos(); @@ -259,6 +264,7 @@ import PerformanceBar from './performance_bar'; break; case 'dashboard:issues': case 'dashboard:merge_requests': + new ProjectSelect(); new UsersSelect(); break; case 'projects:commit:show': diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js index 73675d300be..9ebbb22e807 100644 --- a/app/assets/javascripts/dropzone_input.js +++ b/app/assets/javascripts/dropzone_input.js @@ -5,21 +5,28 @@ import './preview_markdown'; window.DropzoneInput = (function() { function DropzoneInput(form) { - var updateAttachingMessage, $attachingFileMessage, $mdArea, $attachButton, $cancelButton, $retryLink, $uploadingErrorContainer, $uploadingErrorMessage, $uploadProgress, $uploadingProgressContainer, appendToTextArea, btnAlert, child, closeAlertMessage, closeSpinner, divHover, divSpinner, dropzone, $formDropzone, formTextarea, getFilename, handlePaste, iconPaperclip, iconSpinner, insertToTextArea, isImage, maxFileSize, pasteText, uploadsPath, showError, showSpinner, uploadFile, addFileToForm; Dropzone.autoDiscover = false; - divHover = '<div class="div-dropzone-hover"></div>'; - iconPaperclip = '<i class="fa fa-paperclip div-dropzone-icon"></i>'; - $attachButton = form.find('.button-attach-file'); - $attachingFileMessage = form.find('.attaching-file-message'); - $cancelButton = form.find('.button-cancel-uploading-files'); - $retryLink = form.find('.retry-uploading-link'); - $uploadProgress = form.find('.uploading-progress'); - $uploadingErrorContainer = form.find('.uploading-error-container'); - $uploadingErrorMessage = form.find('.uploading-error-message'); - $uploadingProgressContainer = form.find('.uploading-progress-container'); - uploadsPath = window.uploads_path || null; - maxFileSize = gon.max_file_size || 10; - formTextarea = form.find('.js-gfm-input'); + const divHover = '<div class="div-dropzone-hover"></div>'; + const iconPaperclip = '<i class="fa fa-paperclip div-dropzone-icon"></i>'; + const $attachButton = form.find('.button-attach-file'); + const $attachingFileMessage = form.find('.attaching-file-message'); + const $cancelButton = form.find('.button-cancel-uploading-files'); + const $retryLink = form.find('.retry-uploading-link'); + const $uploadProgress = form.find('.uploading-progress'); + const $uploadingErrorContainer = form.find('.uploading-error-container'); + const $uploadingErrorMessage = form.find('.uploading-error-message'); + const $uploadingProgressContainer = form.find('.uploading-progress-container'); + const uploadsPath = window.uploads_path || null; + const maxFileSize = gon.max_file_size || 10; + const formTextarea = form.find('.js-gfm-input'); + let handlePaste; + let pasteText; + let addFileToForm; + let updateAttachingMessage; + let isImage; + let getFilename; + let uploadFile; + formTextarea.wrap('<div class="div-dropzone"></div>'); formTextarea.on('paste', (function(_this) { return function(event) { @@ -28,16 +35,16 @@ window.DropzoneInput = (function() { })(this)); // Add dropzone area to the form. - $mdArea = formTextarea.closest('.md-area'); + const $mdArea = formTextarea.closest('.md-area'); form.setupMarkdownPreview(); - $formDropzone = form.find('.div-dropzone'); + const $formDropzone = form.find('.div-dropzone'); $formDropzone.parent().addClass('div-dropzone-wrapper'); $formDropzone.append(divHover); $formDropzone.find('.div-dropzone-hover').append(iconPaperclip); if (!uploadsPath) return; - dropzone = $formDropzone.dropzone({ + const dropzone = $formDropzone.dropzone({ url: uploadsPath, dictDefaultMessage: '', clickable: true, @@ -117,7 +124,7 @@ window.DropzoneInput = (function() { } }); - child = $(dropzone[0]).children('textarea'); + const child = $(dropzone[0]).children('textarea'); // removeAllFiles(true) stops uploading files (if any) // and remove them from dropzone files queue. @@ -214,6 +221,35 @@ window.DropzoneInput = (function() { return value.first(); }; + const showSpinner = function(e) { + return $uploadingProgressContainer.removeClass('hide'); + }; + + const closeSpinner = function() { + return $uploadingProgressContainer.addClass('hide'); + }; + + const showError = function(message) { + $uploadingErrorContainer.removeClass('hide'); + $uploadingErrorMessage.html(message); + }; + + const closeAlertMessage = function() { + return form.find('.div-dropzone-alert').alert('close'); + }; + + const insertToTextArea = function(filename, url) { + return $(child).val(function(index, val) { + return val.replace(`{{${filename}}}`, url); + }); + }; + + const appendToTextArea = function(url) { + return $(child).val(function(index, val) { + return val + url + "\n"; + }); + }; + uploadFile = function(item, filename) { var formData; formData = new FormData(); @@ -262,35 +298,6 @@ window.DropzoneInput = (function() { messageContainer.text(attachingMessage); }; - insertToTextArea = function(filename, url) { - return $(child).val(function(index, val) { - return val.replace(`{{${filename}}}`, url); - }); - }; - - appendToTextArea = function(url) { - return $(child).val(function(index, val) { - return val + url + "\n"; - }); - }; - - showSpinner = function(e) { - return $uploadingProgressContainer.removeClass('hide'); - }; - - closeSpinner = function() { - return $uploadingProgressContainer.addClass('hide'); - }; - - showError = function(message) { - $uploadingErrorContainer.removeClass('hide'); - $uploadingErrorMessage.html(message); - }; - - closeAlertMessage = function() { - return form.find('.div-dropzone-alert').alert('close'); - }; - form.find('.markdown-selector').click(function(e) { e.preventDefault(); $(this).closest('.gfm-form').find('.div-dropzone').click(); diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js index a8fc5b41fb4..2856c8e2862 100644 --- a/app/assets/javascripts/due_date_select.js +++ b/app/assets/javascripts/due_date_select.js @@ -2,6 +2,8 @@ /* global dateFormat */ /* global Pikaday */ +import DateFix from './lib/utils/datefix'; + class DueDateSelect { constructor({ $dropdown, $loading } = {}) { const $dropdownParent = $dropdown.closest('.dropdown'); @@ -43,14 +45,13 @@ class DueDateSelect { initDatePicker() { const $dueDateInput = $(`input[name='${this.fieldName}']`); - + const dateFix = DateFix.dashedFix($dueDateInput.val()); const calendar = new Pikaday({ field: $dueDateInput.get(0), theme: 'gitlab-theme', format: 'yyyy-mm-dd', onSelect: (dateText) => { const formattedDate = dateFormat(new Date(dateText), 'yyyy-mm-dd'); - $dueDateInput.val(formattedDate); if (this.$dropdown.hasClass('js-issue-boards-due-date')) { @@ -62,7 +63,7 @@ class DueDateSelect { } }); - calendar.setDate(new Date($dueDateInput.val())); + calendar.setDate(dateFix); this.$datePicker.append(calendar.el); this.$datePicker.data('pikaday', calendar); } @@ -168,6 +169,7 @@ class DueDateSelectors { initMilestoneDatePicker() { $('.datepicker').each(function() { const $datePicker = $(this); + const dateFix = DateFix.dashedFix($datePicker.val()); const calendar = new Pikaday({ field: $datePicker.get(0), theme: 'gitlab-theme animate-picker', @@ -177,7 +179,8 @@ class DueDateSelectors { $datePicker.val(dateFormat(new Date(dateText), 'yyyy-mm-dd')); } }); - calendar.setDate(new Date($datePicker.val())); + + calendar.setDate(dateFix); $datePicker.data('pikaday', calendar); }); diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue index b25113e0fc6..d8b1b2f1b92 100644 --- a/app/assets/javascripts/environments/components/environment_item.vue +++ b/app/assets/javascripts/environments/components/environment_item.vue @@ -498,9 +498,9 @@ export default { <div class="table-section section-15 hidden-xs hidden-sm" role="gridcell"> <a v-if="shouldRenderBuildName" - class="build-link" + class="build-link flex-truncate-parent" :href="buildPath"> - {{buildName}} + <span class="flex-truncate-child">{{buildName}}</span> </a> </div> diff --git a/app/assets/javascripts/environments/mixins/environments_mixin.js b/app/assets/javascripts/environments/mixins/environments_mixin.js index 25b24fbd6dc..8f4066e3a6e 100644 --- a/app/assets/javascripts/environments/mixins/environments_mixin.js +++ b/app/assets/javascripts/environments/mixins/environments_mixin.js @@ -1,17 +1,15 @@ export default { methods: { saveData(resp) { - const response = { - headers: resp.headers, - body: resp.json(), - }; + const headers = resp.headers; + return resp.json().then((response) => { + this.isLoading = false; - this.isLoading = false; - - this.store.storeAvailableCount(response.body.available_count); - this.store.storeStoppedCount(response.body.stopped_count); - this.store.storeEnvironments(response.body.environments); - this.store.setPagination(response.headers); + this.store.storeAvailableCount(response.available_count); + this.store.storeStoppedCount(response.stopped_count); + this.store.storeEnvironments(response.environments); + this.store.setPagination(headers); + }); }, }, }; diff --git a/app/assets/javascripts/experimental_flags.js b/app/assets/javascripts/experimental_flags.js index dbd3843cef7..6ee65ca72f9 100644 --- a/app/assets/javascripts/experimental_flags.js +++ b/app/assets/javascripts/experimental_flags.js @@ -7,5 +7,8 @@ export default () => { Cookies.set(el.name, el.value, { expires: 365 * 10, }); + + document.body.scrollTop = 0; + window.location.reload(); }); }; diff --git a/app/assets/javascripts/group_name.js b/app/assets/javascripts/group_name.js index 37c6765d942..3e483b69fd2 100644 --- a/app/assets/javascripts/group_name.js +++ b/app/assets/javascripts/group_name.js @@ -5,12 +5,15 @@ export default class GroupName { constructor() { this.titleContainer = document.querySelector('.js-title-container'); this.title = this.titleContainer.querySelector('.title'); - this.titleWidth = this.title.offsetWidth; - this.groupTitle = this.titleContainer.querySelector('.group-title'); - this.groups = this.titleContainer.querySelectorAll('.group-path'); - this.toggle = null; - this.isHidden = false; - this.init(); + + if (this.title) { + this.titleWidth = this.title.offsetWidth; + this.groupTitle = this.titleContainer.querySelector('.group-title'); + this.groups = this.titleContainer.querySelectorAll('.group-path'); + this.toggle = null; + this.isHidden = false; + this.init(); + } } init() { diff --git a/app/assets/javascripts/groups/index.js b/app/assets/javascripts/groups/index.js index ff601db2aa6..00e1bd94c9c 100644 --- a/app/assets/javascripts/groups/index.js +++ b/app/assets/javascripts/groups/index.js @@ -99,8 +99,10 @@ document.addEventListener('DOMContentLoaded', () => { page: currentPath, }, document.title, currentPath); - this.updateGroups(response.json()); - this.updatePagination(response.headers); + return response.json().then((data) => { + this.updateGroups(data); + this.updatePagination(response.headers); + }); }) .catch(this.handleErrorResponse); }, @@ -114,18 +116,19 @@ document.addEventListener('DOMContentLoaded', () => { }, leaveGroup(group, collection) { this.service.leaveGroup(group.leavePath) + .then(resp => resp.json()) .then((response) => { $.scrollTo(0); this.store.removeGroup(group, collection); // eslint-disable-next-line no-new - new Flash(response.json().notice, 'notice'); + new Flash(response.notice, 'notice'); }) - .catch((response) => { + .catch((error) => { let message = 'An error occurred. Please try again.'; - if (response.status === 403) { + if (error.status === 403) { message = 'Failed to leave the group. Please make sure you are not the only owner'; } diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue index 3d5fb7f441c..efae112923d 100644 --- a/app/assets/javascripts/issue_show/components/app.vue +++ b/app/assets/javascripts/issue_show/components/app.vue @@ -202,10 +202,7 @@ export default { this.poll = new Poll({ resource: this.service, method: 'getData', - successCallback: (res) => { - const data = res.json(); - this.store.updateState(data); - }, + successCallback: res => res.json().then(data => this.store.updateState(data)), errorCallback(err) { throw new Error(err); }, diff --git a/app/assets/javascripts/jobs/job_details_mediator.js b/app/assets/javascripts/jobs/job_details_mediator.js index 063c52fac74..cc014b815c4 100644 --- a/app/assets/javascripts/jobs/job_details_mediator.js +++ b/app/assets/javascripts/jobs/job_details_mediator.js @@ -54,9 +54,8 @@ export default class JobMediator { } successCallback(response) { - const data = response.json(); this.state.isLoading = false; - this.store.storeJob(data); + return response.json().then(data => this.store.storeJob(data)); } errorCallback() { diff --git a/app/assets/javascripts/lib/utils/datefix.js b/app/assets/javascripts/lib/utils/datefix.js new file mode 100644 index 00000000000..990dc3f6d1a --- /dev/null +++ b/app/assets/javascripts/lib/utils/datefix.js @@ -0,0 +1,8 @@ +const DateFix = { + dashedFix(val) { + const [y, m, d] = val.split('-'); + return new Date(y, m - 1, d); + }, +}; + +export default DateFix; diff --git a/app/assets/javascripts/lib/utils/http_status.js b/app/assets/javascripts/lib/utils/http_status.js index 415e50f32ae..625e53ee9de 100644 --- a/app/assets/javascripts/lib/utils/http_status.js +++ b/app/assets/javascripts/lib/utils/http_status.js @@ -3,6 +3,7 @@ */ export default { + ABORTED: 0, NO_CONTENT: 204, OK: 200, }; diff --git a/app/assets/javascripts/lib/utils/poll.js b/app/assets/javascripts/lib/utils/poll.js index e31cc5fbabe..97666e13ebe 100644 --- a/app/assets/javascripts/lib/utils/poll.js +++ b/app/assets/javascripts/lib/utils/poll.js @@ -81,6 +81,9 @@ export default class Poll { }) .catch((error) => { notificationCallback(false); + if (error.status === httpStatusCodes.ABORTED) { + return; + } errorCallback(error); }); } diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 892b3fab1c6..26c67fb721c 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -149,7 +149,6 @@ import './star'; import './subscription'; import './subscription_select'; import './syntax_highlight'; -import './user'; // eslint-disable-next-line global-require, import/no-commonjs if (process.env.NODE_ENV !== 'production') require('./test_utils/'); diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 1a68c5bca00..b2c503d1656 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -1270,7 +1270,7 @@ export default class Notes { <div class="timeline-entry-inner"> <div class="timeline-icon"> <a href="/${currentUsername}"> - <img class="avatar s40" src="${currentUserAvatar}"> + <img class="avatar s40" src="${currentUserAvatar}" /> </a> </div> <div class="timeline-content ${discussionClass}"> diff --git a/app/assets/javascripts/pipelines/components/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines.vue index 01ae07aad65..5df317a76bf 100644 --- a/app/assets/javascripts/pipelines/components/pipelines.vue +++ b/app/assets/javascripts/pipelines/components/pipelines.vue @@ -129,14 +129,11 @@ }, successCallback(resp) { - const response = { - headers: resp.headers, - body: resp.json(), - }; - - this.store.storeCount(response.body.count); - this.store.storePagination(response.headers); - this.setCommonData(response.body.pipelines); + return resp.json().then((response) => { + this.store.storeCount(response.count); + this.store.storePagination(resp.headers); + this.setCommonData(response.pipelines); + }); }, }, }; diff --git a/app/assets/javascripts/pipelines/components/stage.vue b/app/assets/javascripts/pipelines/components/stage.vue index 87b2725a045..a4a27247406 100644 --- a/app/assets/javascripts/pipelines/components/stage.vue +++ b/app/assets/javascripts/pipelines/components/stage.vue @@ -73,8 +73,9 @@ export default { fetchJobs() { this.$http.get(this.stage.dropdown_path) - .then((response) => { - this.dropdownContent = response.json().html; + .then(response => response.json()) + .then((data) => { + this.dropdownContent = data.html; this.isLoading = false; }) .catch(() => { diff --git a/app/assets/javascripts/pipelines/pipeline_details_mediatior.js b/app/assets/javascripts/pipelines/pipeline_details_mediatior.js index 82537ea06f5..385e7430a7d 100644 --- a/app/assets/javascripts/pipelines/pipeline_details_mediatior.js +++ b/app/assets/javascripts/pipelines/pipeline_details_mediatior.js @@ -40,10 +40,10 @@ export default class pipelinesMediator { } successCallback(response) { - const data = response.json(); - - this.state.isLoading = false; - this.store.storePipeline(data); + return response.json().then((data) => { + this.state.isLoading = false; + this.store.storePipeline(data); + }); } errorCallback() { diff --git a/app/assets/javascripts/project_select.js b/app/assets/javascripts/project_select.js index 9896b88d487..ebcefc819f5 100644 --- a/app/assets/javascripts/project_select.js +++ b/app/assets/javascripts/project_select.js @@ -104,6 +104,14 @@ import Api from './api'; dropdownCssClass: "ajax-project-dropdown" }); }); + + $('.new-project-item-select-button').on('click', function() { + $('.project-item-select', this.parentNode).select2('open'); + }); + + $('.project-item-select').on('click', function() { + window.location = `${$(this).val()}/${this.dataset.relativePath}`; + }); } return ProjectSelect; diff --git a/app/assets/javascripts/shortcuts.js b/app/assets/javascripts/shortcuts.js index 49d980212d6..e3daa8cf949 100644 --- a/app/assets/javascripts/shortcuts.js +++ b/app/assets/javascripts/shortcuts.js @@ -1,6 +1,5 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, prefer-arrow-callback, consistent-return, object-shorthand, no-unused-vars, one-var, one-var-declaration-per-line, no-else-return, comma-dangle, max-len */ /* global Mousetrap */ -/* global findFileURL */ import Cookies from 'js-cookie'; import findAndFollowLink from './shortcuts_dashboard_navigation'; @@ -20,6 +19,7 @@ import findAndFollowLink from './shortcuts_dashboard_navigation'; const $globalDropdownMenu = $('.global-dropdown-menu'); const $globalDropdownToggle = $('.global-dropdown-toggle'); + const findFileURL = document.body.dataset.findFile; $('.global-dropdown').on('hide.bs.dropdown', () => { $globalDropdownMenu.removeClass('shortcuts'); diff --git a/app/assets/javascripts/sidebar/sidebar_mediator.js b/app/assets/javascripts/sidebar/sidebar_mediator.js index 5ccfb4ee9c1..721e92221cf 100644 --- a/app/assets/javascripts/sidebar/sidebar_mediator.js +++ b/app/assets/javascripts/sidebar/sidebar_mediator.js @@ -28,8 +28,8 @@ export default class SidebarMediator { fetch() { this.service.get() - .then((response) => { - const data = response.json(); + .then(response => response.json()) + .then((data) => { this.store.setAssigneeData(data); this.store.setTimeTrackingData(data); }) diff --git a/app/assets/javascripts/users/activity_calendar.js b/app/assets/javascripts/users/activity_calendar.js new file mode 100644 index 00000000000..b7f50cfd083 --- /dev/null +++ b/app/assets/javascripts/users/activity_calendar.js @@ -0,0 +1,227 @@ +/* eslint-disable func-names, space-before-function-paren, no-var, wrap-iife, camelcase, vars-on-top, object-shorthand, comma-dangle, eqeqeq, no-mixed-operators, no-return-assign, newline-per-chained-call, prefer-arrow-callback, consistent-return, one-var, one-var-declaration-per-line, prefer-template, quotes, no-unused-vars, no-else-return, max-len, class-methods-use-this */ + +import d3 from 'd3'; + +export default class ActivityCalendar { + constructor(timestamps, calendar_activities_path) { + this.calendar_activities_path = calendar_activities_path; + this.clickDay = this.clickDay.bind(this); + this.currentSelectedDate = ''; + this.daySpace = 1; + this.daySize = 15; + this.daySizeWithSpace = this.daySize + (this.daySpace * 2); + this.monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + this.months = []; + // Loop through the timestamps to create a group of objects + // The group of objects will be grouped based on the day of the week they are + this.timestampsTmp = []; + var group = 0; + + var today = new Date(); + today.setHours(0, 0, 0, 0, 0); + + var oneYearAgo = new Date(today); + oneYearAgo.setFullYear(today.getFullYear() - 1); + + var days = gl.utils.getDayDifference(oneYearAgo, today); + + for (var i = 0; i <= days; i += 1) { + var date = new Date(oneYearAgo); + date.setDate(date.getDate() + i); + + var day = date.getDay(); + var count = timestamps[date.format('yyyy-mm-dd')]; + + // Create a new group array if this is the first day of the week + // or if is first object + if ((day === 0 && i !== 0) || i === 0) { + this.timestampsTmp.push([]); + group += 1; + } + + var innerArray = this.timestampsTmp[group - 1]; + // Push to the inner array the values that will be used to render map + innerArray.push({ + count: count || 0, + date: date, + day: day + }); + } + + // Init color functions + this.colorKey = this.initColorKey(); + this.color = this.initColor(); + // Init the svg element + this.renderSvg(group); + this.renderDays(); + this.renderMonths(); + this.renderDayTitles(); + this.renderKey(); + this.initTooltips(); + } + + // Add extra padding for the last month label if it is also the last column + getExtraWidthPadding(group) { + var extraWidthPadding = 0; + var lastColMonth = this.timestampsTmp[group - 1][0].date.getMonth(); + var secondLastColMonth = this.timestampsTmp[group - 2][0].date.getMonth(); + + if (lastColMonth != secondLastColMonth) { + extraWidthPadding = 3; + } + + return extraWidthPadding; + } + + renderSvg(group) { + var width = (group + 1) * this.daySizeWithSpace + this.getExtraWidthPadding(group); + return this.svg = d3.select('.js-contrib-calendar').append('svg').attr('width', width).attr('height', 167).attr('class', 'contrib-calendar'); + } + + renderDays() { + return this.svg.selectAll('g').data(this.timestampsTmp).enter().append('g').attr('transform', (function(_this) { + return function(group, i) { + _.each(group, function(stamp, a) { + var lastMonth, lastMonthX, month, x; + if (a === 0 && stamp.day === 0) { + month = stamp.date.getMonth(); + x = (_this.daySizeWithSpace * i + 1) + _this.daySizeWithSpace; + lastMonth = _.last(_this.months); + if (lastMonth != null) { + lastMonthX = lastMonth.x; + } + if (lastMonth == null) { + return _this.months.push({ + month: month, + x: x + }); + } else if (month !== lastMonth.month && x - _this.daySizeWithSpace !== lastMonthX) { + return _this.months.push({ + month: month, + x: x + }); + } + } + }); + return "translate(" + ((_this.daySizeWithSpace * i + 1) + _this.daySizeWithSpace) + ", 18)"; + }; + })(this)).selectAll('rect').data(function(stamp) { + return stamp; + }).enter().append('rect').attr('x', '0').attr('y', (function(_this) { + return function(stamp, i) { + return _this.daySizeWithSpace * stamp.day; + }; + })(this)).attr('width', this.daySize).attr('height', this.daySize).attr('title', (function(_this) { + return function(stamp) { + var contribText, date, dateText; + date = new Date(stamp.date); + contribText = 'No contributions'; + if (stamp.count > 0) { + contribText = stamp.count + " contribution" + (stamp.count > 1 ? 's' : ''); + } + dateText = date.format('mmm d, yyyy'); + return contribText + "<br />" + (gl.utils.getDayName(date)) + " " + dateText; + }; + })(this)).attr('class', 'user-contrib-cell js-tooltip').attr('fill', (function(_this) { + return function(stamp) { + if (stamp.count !== 0) { + return _this.color(Math.min(stamp.count, 40)); + } else { + return '#ededed'; + } + }; + })(this)).attr('data-container', 'body').on('click', this.clickDay); + } + + renderDayTitles() { + var days; + days = [ + { + text: 'M', + y: 29 + (this.daySizeWithSpace * 1) + }, { + text: 'W', + y: 29 + (this.daySizeWithSpace * 3) + }, { + text: 'F', + y: 29 + (this.daySizeWithSpace * 5) + } + ]; + return this.svg.append('g').selectAll('text').data(days).enter().append('text').attr('text-anchor', 'middle').attr('x', 8).attr('y', function(day) { + return day.y; + }).text(function(day) { + return day.text; + }).attr('class', 'user-contrib-text'); + } + + renderMonths() { + return this.svg.append('g').attr('direction', 'ltr').selectAll('text').data(this.months).enter().append('text').attr('x', function(date) { + return date.x; + }).attr('y', 10).attr('class', 'user-contrib-text').text((function(_this) { + return function(date) { + return _this.monthNames[date.month]; + }; + })(this)); + } + + renderKey() { + const keyValues = ['no contributions', '1-9 contributions', '10-19 contributions', '20-29 contributions', '30+ contributions']; + const keyColors = ['#ededed', this.colorKey(0), this.colorKey(1), this.colorKey(2), this.colorKey(3)]; + + this.svg.append('g') + .attr('transform', `translate(18, ${this.daySizeWithSpace * 8 + 16})`) + .selectAll('rect') + .data(keyColors) + .enter() + .append('rect') + .attr('width', this.daySize) + .attr('height', this.daySize) + .attr('x', (color, i) => this.daySizeWithSpace * i) + .attr('y', 0) + .attr('fill', color => color) + .attr('class', 'js-tooltip') + .attr('title', (color, i) => keyValues[i]) + .attr('data-container', 'body'); + } + + initColor() { + var colorRange; + colorRange = ['#ededed', this.colorKey(0), this.colorKey(1), this.colorKey(2), this.colorKey(3)]; + return d3.scale.threshold().domain([0, 10, 20, 30]).range(colorRange); + } + + initColorKey() { + return d3.scale.linear().range(['#acd5f2', '#254e77']).domain([0, 3]); + } + + clickDay(stamp) { + var formatted_date; + if (this.currentSelectedDate !== stamp.date) { + this.currentSelectedDate = stamp.date; + formatted_date = this.currentSelectedDate.getFullYear() + "-" + (this.currentSelectedDate.getMonth() + 1) + "-" + this.currentSelectedDate.getDate(); + return $.ajax({ + url: this.calendar_activities_path, + data: { + date: formatted_date + }, + cache: false, + dataType: 'html', + beforeSend: function() { + return $('.user-calendar-activities').html('<div class="text-center"><i class="fa fa-spinner fa-spin user-calendar-activities-loading"></i></div>'); + }, + success: function(data) { + return $('.user-calendar-activities').html(data); + } + }); + } else { + this.currentSelectedDate = ''; + return $('.user-calendar-activities').html(''); + } + } + + initTooltips() { + return $('.js-contrib-calendar .js-tooltip').tooltip({ + html: true + }); + } +} diff --git a/app/assets/javascripts/users/calendar.js b/app/assets/javascripts/users/calendar.js deleted file mode 100644 index b11f691e424..00000000000 --- a/app/assets/javascripts/users/calendar.js +++ /dev/null @@ -1,231 +0,0 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, camelcase, vars-on-top, object-shorthand, comma-dangle, eqeqeq, no-mixed-operators, no-return-assign, newline-per-chained-call, prefer-arrow-callback, consistent-return, one-var, one-var-declaration-per-line, prefer-template, quotes, no-unused-vars, no-else-return, max-len */ - -import d3 from 'd3'; - -(function() { - this.Calendar = (function() { - function Calendar(timestamps, calendar_activities_path) { - this.calendar_activities_path = calendar_activities_path; - this.clickDay = this.clickDay.bind(this); - this.currentSelectedDate = ''; - this.daySpace = 1; - this.daySize = 15; - this.daySizeWithSpace = this.daySize + (this.daySpace * 2); - this.monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; - this.months = []; - // Loop through the timestamps to create a group of objects - // The group of objects will be grouped based on the day of the week they are - this.timestampsTmp = []; - var group = 0; - - var today = new Date(); - today.setHours(0, 0, 0, 0, 0); - - var oneYearAgo = new Date(today); - oneYearAgo.setFullYear(today.getFullYear() - 1); - - var days = gl.utils.getDayDifference(oneYearAgo, today); - - for (var i = 0; i <= days; i += 1) { - var date = new Date(oneYearAgo); - date.setDate(date.getDate() + i); - - var day = date.getDay(); - var count = timestamps[date.format('yyyy-mm-dd')]; - - // Create a new group array if this is the first day of the week - // or if is first object - if ((day === 0 && i !== 0) || i === 0) { - this.timestampsTmp.push([]); - group += 1; - } - - var innerArray = this.timestampsTmp[group - 1]; - // Push to the inner array the values that will be used to render map - innerArray.push({ - count: count || 0, - date: date, - day: day - }); - } - - // Init color functions - this.colorKey = this.initColorKey(); - this.color = this.initColor(); - // Init the svg element - this.renderSvg(group); - this.renderDays(); - this.renderMonths(); - this.renderDayTitles(); - this.renderKey(); - this.initTooltips(); - } - - // Add extra padding for the last month label if it is also the last column - Calendar.prototype.getExtraWidthPadding = function(group) { - var extraWidthPadding = 0; - var lastColMonth = this.timestampsTmp[group - 1][0].date.getMonth(); - var secondLastColMonth = this.timestampsTmp[group - 2][0].date.getMonth(); - - if (lastColMonth != secondLastColMonth) { - extraWidthPadding = 3; - } - - return extraWidthPadding; - }; - - Calendar.prototype.renderSvg = function(group) { - var width = (group + 1) * this.daySizeWithSpace + this.getExtraWidthPadding(group); - return this.svg = d3.select('.js-contrib-calendar').append('svg').attr('width', width).attr('height', 167).attr('class', 'contrib-calendar'); - }; - - Calendar.prototype.renderDays = function() { - return this.svg.selectAll('g').data(this.timestampsTmp).enter().append('g').attr('transform', (function(_this) { - return function(group, i) { - _.each(group, function(stamp, a) { - var lastMonth, lastMonthX, month, x; - if (a === 0 && stamp.day === 0) { - month = stamp.date.getMonth(); - x = (_this.daySizeWithSpace * i + 1) + _this.daySizeWithSpace; - lastMonth = _.last(_this.months); - if (lastMonth != null) { - lastMonthX = lastMonth.x; - } - if (lastMonth == null) { - return _this.months.push({ - month: month, - x: x - }); - } else if (month !== lastMonth.month && x - _this.daySizeWithSpace !== lastMonthX) { - return _this.months.push({ - month: month, - x: x - }); - } - } - }); - return "translate(" + ((_this.daySizeWithSpace * i + 1) + _this.daySizeWithSpace) + ", 18)"; - }; - })(this)).selectAll('rect').data(function(stamp) { - return stamp; - }).enter().append('rect').attr('x', '0').attr('y', (function(_this) { - return function(stamp, i) { - return _this.daySizeWithSpace * stamp.day; - }; - })(this)).attr('width', this.daySize).attr('height', this.daySize).attr('title', (function(_this) { - return function(stamp) { - var contribText, date, dateText; - date = new Date(stamp.date); - contribText = 'No contributions'; - if (stamp.count > 0) { - contribText = stamp.count + " contribution" + (stamp.count > 1 ? 's' : ''); - } - dateText = date.format('mmm d, yyyy'); - return contribText + "<br />" + (gl.utils.getDayName(date)) + " " + dateText; - }; - })(this)).attr('class', 'user-contrib-cell js-tooltip').attr('fill', (function(_this) { - return function(stamp) { - if (stamp.count !== 0) { - return _this.color(Math.min(stamp.count, 40)); - } else { - return '#ededed'; - } - }; - })(this)).attr('data-container', 'body').on('click', this.clickDay); - }; - - Calendar.prototype.renderDayTitles = function() { - var days; - days = [ - { - text: 'M', - y: 29 + (this.daySizeWithSpace * 1) - }, { - text: 'W', - y: 29 + (this.daySizeWithSpace * 3) - }, { - text: 'F', - y: 29 + (this.daySizeWithSpace * 5) - } - ]; - return this.svg.append('g').selectAll('text').data(days).enter().append('text').attr('text-anchor', 'middle').attr('x', 8).attr('y', function(day) { - return day.y; - }).text(function(day) { - return day.text; - }).attr('class', 'user-contrib-text'); - }; - - Calendar.prototype.renderMonths = function() { - return this.svg.append('g').attr('direction', 'ltr').selectAll('text').data(this.months).enter().append('text').attr('x', function(date) { - return date.x; - }).attr('y', 10).attr('class', 'user-contrib-text').text((function(_this) { - return function(date) { - return _this.monthNames[date.month]; - }; - })(this)); - }; - - Calendar.prototype.renderKey = function() { - const keyValues = ['no contributions', '1-9 contributions', '10-19 contributions', '20-29 contributions', '30+ contributions']; - const keyColors = ['#ededed', this.colorKey(0), this.colorKey(1), this.colorKey(2), this.colorKey(3)]; - - this.svg.append('g') - .attr('transform', `translate(18, ${this.daySizeWithSpace * 8 + 16})`) - .selectAll('rect') - .data(keyColors) - .enter() - .append('rect') - .attr('width', this.daySize) - .attr('height', this.daySize) - .attr('x', (color, i) => this.daySizeWithSpace * i) - .attr('y', 0) - .attr('fill', color => color) - .attr('class', 'js-tooltip') - .attr('title', (color, i) => keyValues[i]) - .attr('data-container', 'body'); - }; - - Calendar.prototype.initColor = function() { - var colorRange; - colorRange = ['#ededed', this.colorKey(0), this.colorKey(1), this.colorKey(2), this.colorKey(3)]; - return d3.scale.threshold().domain([0, 10, 20, 30]).range(colorRange); - }; - - Calendar.prototype.initColorKey = function() { - return d3.scale.linear().range(['#acd5f2', '#254e77']).domain([0, 3]); - }; - - Calendar.prototype.clickDay = function(stamp) { - var formatted_date; - if (this.currentSelectedDate !== stamp.date) { - this.currentSelectedDate = stamp.date; - formatted_date = this.currentSelectedDate.getFullYear() + "-" + (this.currentSelectedDate.getMonth() + 1) + "-" + this.currentSelectedDate.getDate(); - return $.ajax({ - url: this.calendar_activities_path, - data: { - date: formatted_date - }, - cache: false, - dataType: 'html', - beforeSend: function() { - return $('.user-calendar-activities').html('<div class="text-center"><i class="fa fa-spinner fa-spin user-calendar-activities-loading"></i></div>'); - }, - success: function(data) { - return $('.user-calendar-activities').html(data); - } - }); - } else { - this.currentSelectedDate = ''; - return $('.user-calendar-activities').html(''); - } - }; - - Calendar.prototype.initTooltips = function() { - return $('.js-contrib-calendar .js-tooltip').tooltip({ - html: true - }); - }; - - return Calendar; - })(); -}).call(window); diff --git a/app/assets/javascripts/users/index.js b/app/assets/javascripts/users/index.js new file mode 100644 index 00000000000..ecd8e09161e --- /dev/null +++ b/app/assets/javascripts/users/index.js @@ -0,0 +1,7 @@ +import ActivityCalendar from './activity_calendar'; +import User from './user'; + +// use legacy exports until embedded javascript is refactored +window.Calendar = ActivityCalendar; +window.gl = window.gl || {}; +window.gl.User = User; diff --git a/app/assets/javascripts/user.js b/app/assets/javascripts/users/user.js index 9ef94ac7616..0b0a3e1afb4 100644 --- a/app/assets/javascripts/user.js +++ b/app/assets/javascripts/users/user.js @@ -1,9 +1,9 @@ -/* eslint-disable class-methods-use-this, comma-dangle, arrow-parens, no-param-reassign */ +/* eslint-disable class-methods-use-this */ import Cookies from 'js-cookie'; import UserTabs from './user_tabs'; -class User { +export default class User { constructor({ action }) { this.action = action; this.placeProfileAvatarsToTop(); @@ -13,25 +13,22 @@ class User { placeProfileAvatarsToTop() { $('.profile-groups-avatars').tooltip({ - placement: 'top' + placement: 'top', }); } initTabs() { return new UserTabs({ parentEl: '.user-profile', - action: this.action + action: this.action, }); } hideProjectLimitMessage() { - $('.hide-project-limit-message').on('click', e => { + $('.hide-project-limit-message').on('click', (e) => { e.preventDefault(); Cookies.set('hide_project_limit_message', 'false'); $(this).parents('.project-limit-message').remove(); }); } } - -window.gl = window.gl || {}; -window.gl.User = User; diff --git a/app/assets/javascripts/user_tabs.js b/app/assets/javascripts/users/user_tabs.js index f8e23c8624d..f8e23c8624d 100644 --- a/app/assets/javascripts/user_tabs.js +++ b/app/assets/javascripts/users/user_tabs.js diff --git a/app/assets/javascripts/users/users_bundle.js b/app/assets/javascripts/users/users_bundle.js deleted file mode 100644 index a38ce4eb25e..00000000000 --- a/app/assets/javascripts/users/users_bundle.js +++ /dev/null @@ -1 +0,0 @@ -import './calendar'; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js index f8b3fb748ae..8430548903c 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js @@ -92,13 +92,13 @@ export default { :class="{'label-truncated has-tooltip': isBranchTitleLong(mr.targetBranch)}" :title="isBranchTitleLong(mr.targetBranch) ? mr.targetBranch : ''" data-placement="bottom"> - <a :href="mr.targetBranchPath">{{mr.targetBranch}}</a> + <a :href="mr.targetBranchTreePath">{{mr.targetBranch}}</a> </span> </strong> <span v-if="shouldShowCommitsBehindText" class="diverged-commits-count"> - ({{mr.divergedCommitsCount}} {{commitsText}} behind) + (<a :href="mr.targetBranchPath">{{mr.divergedCommitsCount}} {{commitsText}} behind</a>) </span> </div> </div> diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js index 69bc1436284..72a13108404 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js @@ -48,6 +48,7 @@ export default class MergeRequestStore { this.sourceBranchLink = data.source_branch_with_namespace_link; this.mergeError = data.merge_error; this.targetBranchPath = data.target_branch_commits_path; + this.targetBranchTreePath = data.target_branch_tree_path; this.conflictResolutionPath = data.conflict_resolution_path; this.cancelAutoMergePath = data.cancel_merge_when_pipeline_succeeds_path; this.removeWIPPath = data.remove_wip_path; diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue index 8303c556f64..4e10bbc7408 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/field.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue @@ -44,9 +44,8 @@ text: this.$slots.textarea[0].elm.value, }, ) - .then((res) => { - const data = res.json(); - + .then(resp => resp.json()) + .then((data) => { this.markdownPreviewLoading = false; this.markdownPreview = data.body; diff --git a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js index 740930dce5b..7f8e514fda1 100644 --- a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js +++ b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js @@ -14,11 +14,22 @@ Vue.http.interceptors.push((request, next) => { }); }); -// Inject CSRF token so we don't break any tests. +// Inject CSRF token and parse headers. +// New Vue Resource version uses Headers, we are expecting a plain object to render pagination +// and polling. Vue.http.interceptors.push((request, next) => { if ($.rails) { - // eslint-disable-next-line no-param-reassign - request.headers['X-CSRF-Token'] = $.rails.csrfToken(); + request.headers.set('X-CSRF-Token', $.rails.csrfToken()); } - next(); + + next((response) => { + // Headers object has a `forEach` property that iterates through all values. + const headers = {}; + + response.headers.forEach((value, key) => { + headers[key] = value; + }); + // eslint-disable-next-line no-param-reassign + response.headers = headers; + }); }); diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 83a8eeaafde..0665622fe4a 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -42,4 +42,4 @@ /* * Styles for JS behaviors. */ -@import "behaviors.scss"; +@import "behaviors"; diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss index 9dc9f9a9068..6ce331a9129 100644 --- a/app/assets/stylesheets/framework.scss +++ b/app/assets/stylesheets/framework.scss @@ -4,49 +4,49 @@ @import 'framework/tw_bootstrap'; @import "framework/layout"; -@import "framework/animations.scss"; -@import "framework/avatar.scss"; -@import "framework/asciidoctor.scss"; -@import "framework/blocks.scss"; -@import "framework/buttons.scss"; -@import "framework/badges.scss"; -@import "framework/calendar.scss"; -@import "framework/callout.scss"; -@import "framework/common.scss"; -@import "framework/dropdowns.scss"; -@import "framework/files.scss"; -@import "framework/filters.scss"; -@import "framework/flash.scss"; -@import "framework/forms.scss"; -@import "framework/gfm.scss"; -@import "framework/header.scss"; -@import "framework/highlight.scss"; -@import "framework/issue_box.scss"; -@import "framework/jquery.scss"; -@import "framework/lists.scss"; -@import "framework/logo.scss"; -@import "framework/markdown_area.scss"; -@import "framework/mobile.scss"; -@import "framework/modal.scss"; -@import "framework/nav.scss"; -@import "framework/pagination.scss"; -@import "framework/panels.scss"; -@import "framework/selects.scss"; -@import "framework/sidebar.scss"; -@import "framework/tables.scss"; -@import "framework/notes.scss"; -@import "framework/timeline.scss"; -@import "framework/typography.scss"; -@import "framework/zen.scss"; +@import "framework/animations"; +@import "framework/avatar"; +@import "framework/asciidoctor"; +@import "framework/blocks"; +@import "framework/buttons"; +@import "framework/badges"; +@import "framework/calendar"; +@import "framework/callout"; +@import "framework/common"; +@import "framework/dropdowns"; +@import "framework/files"; +@import "framework/filters"; +@import "framework/flash"; +@import "framework/forms"; +@import "framework/gfm"; +@import "framework/header"; +@import "framework/highlight"; +@import "framework/issue_box"; +@import "framework/jquery"; +@import "framework/lists"; +@import "framework/logo"; +@import "framework/markdown_area"; +@import "framework/mobile"; +@import "framework/modal"; +@import "framework/nav"; +@import "framework/pagination"; +@import "framework/panels"; +@import "framework/selects"; +@import "framework/sidebar"; +@import "framework/tables"; +@import "framework/notes"; +@import "framework/timeline"; +@import "framework/typography"; +@import "framework/zen"; @import "framework/blank"; -@import "framework/wells.scss"; -@import "framework/page-header.scss"; -@import "framework/awards.scss"; -@import "framework/images.scss"; +@import "framework/wells"; +@import "framework/page-header"; +@import "framework/awards"; +@import "framework/images"; @import "framework/broadcast-messages"; -@import "framework/emojis.scss"; -@import "framework/emoji-sprites.scss"; -@import "framework/icons.scss"; -@import "framework/snippets.scss"; -@import "framework/memory_graph.scss"; -@import "framework/responsive-tables.scss"; +@import "framework/emojis"; +@import "framework/emoji-sprites"; +@import "framework/icons"; +@import "framework/snippets"; +@import "framework/memory_graph"; +@import "framework/responsive-tables"; diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss index 4ae2b164d2e..06f7af33f94 100644 --- a/app/assets/stylesheets/framework/avatar.scss +++ b/app/assets/stylesheets/framework/avatar.scss @@ -60,7 +60,7 @@ } &:not([href]):hover { - border-color: rgba($avatar-border, .2); + border-color: darken($avatar-border, 10%); } } @@ -99,7 +99,7 @@ .avatar-counter { background-color: $gray-darkest; color: $white-light; - border: 1px solid $border-color; + border: 1px solid $avatar-border; border-radius: 1em; font-family: $regular_font; font-size: 9px; diff --git a/app/assets/stylesheets/framework/awards.scss b/app/assets/stylesheets/framework/awards.scss index 19166757e64..bb30da4f4b2 100644 --- a/app/assets/stylesheets/framework/awards.scss +++ b/app/assets/stylesheets/framework/awards.scss @@ -24,7 +24,7 @@ opacity: 0; transform: scale(.2); transform-origin: 0 -45px; - transition: .3s cubic-bezier(.67,.06,.19,1.44); + transition: .3s cubic-bezier(.67, .06, .19, 1.44); transition-property: transform, opacity; &.is-aligned-right { @@ -231,11 +231,11 @@ .award-control-icon-positive, .award-control-icon-super-positive { + @include transition(opacity, transform); position: absolute; left: 10px; bottom: 6px; opacity: 0; - @include transition(opacity, transform); } .award-control-text { diff --git a/app/assets/stylesheets/framework/blank.scss b/app/assets/stylesheets/framework/blank.scss index c0224d3bfa9..6bb096fc5bd 100644 --- a/app/assets/stylesheets/framework/blank.scss +++ b/app/assets/stylesheets/framework/blank.scss @@ -1,9 +1,5 @@ .blank-state-parent-container { - display: flex; - .section-container { - display: flex; - flex: 1; padding: 10px; } @@ -13,91 +9,42 @@ padding-bottom: 25px; border: 1px solid $border-color; border-radius: $border-radius-default; - - &.section-ee-trial { - display: flex; - align-items: center; - justify-content: center; - } - } -} - -.blank-state-welcome { - text-align: center; - - .blank-state-text { - margin-bottom: 0; } } .blank-state { padding-top: 20px; padding-bottom: 20px; -} - -.blank-state.ee-trial { - padding: 20px; text-align: center; -} - -.blank-state-no-icon { - padding-top: 40px; - padding-bottom: 40px; -} - -.blank-state-icon { - padding-bottom: 20px; - font-size: 56px; - svg { - display: block; - margin: auto; - } -} - -@media (min-width: $screen-sm-max) { - .section-welcome .blank-state-icon svg { - width: 130%; - } -} - -.blank-state-title { - margin-top: 0; - margin-bottom: 10px; - font-size: 18px; -} - -.blank-state-text { - margin-top: 0; - margin-bottom: $gl-padding; - font-size: 14px; + &.blank-state-welcome { + .blank-state-welcome-title { + font-size: 24px; + } - > strong { - font-weight: 600; + .blank-state-text { + margin-bottom: 0; + } } -} -.blank-state-welcome-title { - font-size: 24px; -} + .blank-state-icon { + padding-bottom: 20px; -@media (max-width: $screen-md-min) { - .blank-state-parent-container { - &, - .section-container { + svg { display: block; + margin: auto; } } - .blank-state { - text-align: center; + .blank-state-title { + margin-top: 0; + margin-bottom: 10px; + font-size: 18px; } - .blank-state-icon { - padding-bottom: 0; - } - - .blank-state-body { - margin-top: 15px; + .blank-state-text { + max-width: $container-text-max-width; + margin: 0 auto $gl-padding; + font-size: 14px; } } diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 00c981f64c5..5e374360359 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -336,11 +336,6 @@ table { text-align: center; } -#nprogress .spinner { - top: 15px !important; - right: 10px !important; -} - .header-with-avatar { h3 { margin: 0; @@ -450,4 +445,3 @@ table { pointer-events: none; opacity: .5; } - diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index dc4ed42544f..5e410cbf563 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -35,8 +35,8 @@ .open { .dropdown-menu, .dropdown-menu-nav { - display: block; @include set-visible; + display: block; @media (max-width: $screen-xs-max) { width: 100%; @@ -184,13 +184,15 @@ .dropdown-menu, .dropdown-menu-nav { + @include set-invisible; display: block; position: absolute; - width: 100%; + width: auto; top: 100%; left: 0; z-index: 9; min-width: 240px; + max-width: 500px; margin-top: 2px; margin-bottom: 0; font-size: 14px; @@ -200,10 +202,10 @@ border: 1px solid $dropdown-border-color; border-radius: $border-radius-base; box-shadow: 0 2px 4px $dropdown-shadow-color; - @include set-invisible; @media (max-width: $screen-sm-min) { width: 100%; + min-width: 180px; } &.dropdown-open-left { @@ -287,27 +289,15 @@ padding: 5px 8px; color: $gl-text-color-secondary; } - - .badge { - position: absolute; - right: 8px; - top: 5px; - } } .droplab-dropdown { - .description { - display: inline-block; - white-space: normal; - margin-left: 5px; - } - .dropdown-toggle > i { pointer-events: none; } - li { - padding: $gl-btn-padding $gl-btn-padding 2px; + .dropdown-menu li { + padding: $gl-btn-padding; cursor: pointer; > a, @@ -343,9 +333,25 @@ visibility: visible; } + &.divider { + margin: 0 8px; + padding: 0; + border-top: $gray-darkest; + } + .icon { visibility: hidden; } + + .description { + display: inline-block; + white-space: normal; + margin-left: 5px; + + p { + margin-bottom: 0; + } + } } .icon { @@ -353,12 +359,6 @@ vertical-align: top; padding-top: 2px; } - - .divider { - margin: 0 8px; - padding: 0; - border-top: $gray-darkest; - } } .droplab-dropdown .dropdown-menu, @@ -461,10 +461,6 @@ left: auto; right: 0; margin-top: -5px; - - @media (max-width: $screen-xs-max) { - left: 0; - } } .dropdown-menu-selectable { @@ -675,8 +671,8 @@ } .pika-single { - position: relative!important; - top: 0!important; + position: relative !important; + top: 0 !important; border: 0; box-shadow: none; } diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index f05348ee4e3..41184907abb 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -275,7 +275,7 @@ } .filtered-search-input-dropdown-menu { - max-height: 215px; + max-height: 225px; max-width: 280px; overflow: auto; @@ -368,7 +368,7 @@ margin-right: 0.3em; } - & > .value { + > .value { font-weight: 600; } } @@ -382,10 +382,6 @@ } } -.dropdown-menu .filter-dropdown-item { - padding: 0; -} - @media (max-width: $screen-xs-max) { .issues-details-filters { padding: 0 0 10px; @@ -435,6 +431,7 @@ .fa { width: 15px; + line-height: $line-height-base; } .dropdown-label-box { @@ -467,7 +464,7 @@ -webkit-flex-direction: column; flex-direction: column; - &> span { + > span { white-space: normal; word-break: break-all; } diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 5bd6c095109..20fb10c28d4 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -330,7 +330,7 @@ header { padding-left: 5px; .nav > li:not(.hidden-xs) { - display: table-cell!important; + display: table-cell !important; width: 25%; a { diff --git a/app/assets/stylesheets/framework/highlight.scss b/app/assets/stylesheets/framework/highlight.scss index 6d27d7568cf..71d5949b023 100644 --- a/app/assets/stylesheets/framework/highlight.scss +++ b/app/assets/stylesheets/framework/highlight.scss @@ -61,7 +61,7 @@ &:focus { outline: none; - & i { + i { visibility: visible; } } diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index e59cd0eea82..868e65a8f46 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -236,6 +236,8 @@ ul.content-list { ul.controls { float: right; list-style: none; + display: flex; + align-items: center; .btn { padding: 10px 14px; @@ -259,6 +261,12 @@ ul.controls { } } } + + .issuable-pipeline-broken a, + .issuable-pipeline-status a, + .author_link { + display: flex; + } } ul.indent-list { diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index b21bcc22a87..a2de4598167 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -165,8 +165,8 @@ .cur { .avatar { - border: 1px solid $white-light; @include disableAllAnimation; + border: 1px solid $white-light; } } diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 28b2a7cfacd..e71bf04aec7 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -325,7 +325,7 @@ position: absolute; top: 7px; right: 15px; - z-index: 2; + z-index: 300; li.active { font-weight: bold; diff --git a/app/assets/stylesheets/framework/responsive-tables.scss b/app/assets/stylesheets/framework/responsive-tables.scss index d2c90908baa..8e653c443cf 100644 --- a/app/assets/stylesheets/framework/responsive-tables.scss +++ b/app/assets/stylesheets/framework/responsive-tables.scss @@ -100,9 +100,9 @@ } .table-mobile-header { + @include flex-max-width(40); color: $gl-text-color-secondary; text-align: left; - @include flex-max-width(40); @media (min-width: $screen-md-min) { display: none; diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 785b09e622f..8a58c1ed567 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -2,6 +2,10 @@ color: $gl-text-color; word-wrap: break-word; + [dir="auto"] { + text-align: initial; + } + a { color: $md-link-color; } @@ -112,9 +116,12 @@ blockquote p { color: $gl-grayish-blue !important; - margin: 0; font-size: inherit; line-height: 1.5; + + &:last-child { + margin: 0; + } } p { diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 3f032776d82..7016208f624 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -110,10 +110,10 @@ $well-light-text-color: #5b6169; * Text */ $gl-font-size: 14px; -$gl-text-color: rgba(0, 0, 0, .85); -$gl-text-color-light: rgba(0, 0, 0, .7); -$gl-text-color-secondary: rgba(0, 0, 0, .55); -$gl-text-color-disabled: rgba(0, 0, 0, .35); +$gl-text-color: #2e2e2e; +$gl-text-color-secondary: #707070; +$gl-text-color-tertiary: #949494; +$gl-text-color-quaternary: #d6d6d6; $gl-text-color-inverted: rgba(255, 255, 255, 1.0); $gl-text-color-secondary-inverted: rgba(255, 255, 255, .85); $gl-text-green: $green-600; @@ -127,7 +127,7 @@ $gl-gray-dark: #313236; $gl-gray-light: #5c5c5c; $gl-header-color: #4c4e54; $gl-header-nav-hover-color: #434343; -$placeholder-text-color: rgba(0, 0, 0, .42); +$placeholder-text-color: $gl-text-color-tertiary; /* * Lists @@ -135,7 +135,7 @@ $placeholder-text-color: rgba(0, 0, 0, .42); $list-font-size: $gl-font-size; $list-title-color: $gl-text-color; $list-text-color: $gl-text-color; -$list-text-disabled-color: $gl-text-color-disabled; +$list-text-disabled-color: $gl-text-color-tertiary; $list-border-light: #eee; $list-border: rgba(0, 0, 0, 0.05); $list-text-height: 42px; @@ -176,6 +176,7 @@ $header-height: 50px; $fixed-layout-width: 1280px; $limited-layout-width: 990px; $limited-layout-width-sm: 790px; +$container-text-max-width: 540px; $gl-avatar-size: 40px; $error-exclamation-point: $red-500; $border-radius-default: 3px; @@ -316,7 +317,7 @@ $badge-color: $gl-text-color-secondary; /* * Award emoji */ -$award-emoji-menu-shadow: rgba(0,0,0,.175); +$award-emoji-menu-shadow: rgba(0, 0, 0, .175); $award-emoji-positive-add-bg: #fed159; $award-emoji-positive-add-lines: #bb9c13; @@ -378,7 +379,7 @@ $issue-boards-card-shadow: rgba(186, 186, 186, 0.5); * Avatar */ $avatar_radius: 50%; -$avatar-border: rgba(0, 0, 0, .1); +$avatar-border: $border-color; $gl-avatar-size: 40px; /* @@ -567,7 +568,7 @@ $kdb-color: #555; $kdb-border: #ccc; $kdb-border-bottom: #bbb; $kdb-shadow: #bbb; -$body-text-shadow: rgba(255,255,255,0.01); +$body-text-shadow: rgba(255, 255, 255, 0.01); /* * UI Dev Kit diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss index 1daa10aef24..578f1902cce 100644 --- a/app/assets/stylesheets/highlight/white.scss +++ b/app/assets/stylesheets/highlight/white.scss @@ -113,7 +113,7 @@ $white-gc-bg: #eaf2f5; border-color: $line-removed-dark; a { - color: scale-color($line-number-old,$red: -30%, $green: -30%, $blue: -30%); + color: scale-color($line-number-old, $red: -30%, $green: -30%, $blue: -30%); } } @@ -122,7 +122,7 @@ $white-gc-bg: #eaf2f5; border-color: $line-added-dark; a { - color: scale-color($line-number-new,$red: -30%, $green: -30%, $blue: -30%); + color: scale-color($line-number-new, $red: -30%, $green: -30%, $blue: -30%); } } @@ -163,7 +163,7 @@ $white-gc-bg: #eaf2f5; background-color: $line-removed; &::before { - color: scale-color($line-number-old,$red: -30%, $green: -30%, $blue: -30%); + color: scale-color($line-number-old, $red: -30%, $green: -30%, $blue: -30%); } span.idiff { @@ -175,7 +175,7 @@ $white-gc-bg: #eaf2f5; background-color: $line-added; &::before { - color: scale-color($line-number-new,$red: -30%, $green: -30%, $blue: -30%); + color: scale-color($line-number-new, $red: -30%, $green: -30%, $blue: -30%); } span.idiff { diff --git a/app/assets/stylesheets/new_nav.scss b/app/assets/stylesheets/new_nav.scss index 73cb3a7cf4c..2ee831e5e32 100644 --- a/app/assets/stylesheets/new_nav.scss +++ b/app/assets/stylesheets/new_nav.scss @@ -284,7 +284,7 @@ header.navbar-gitlab-new { position: relative; top: -1px; padding: 0 5px; - color: rgba($black, .65); + color: $gl-text-color-secondary; font-size: 10px; line-height: 1; background: none; @@ -310,10 +310,10 @@ header.navbar-gitlab-new { .breadcrumbs-links { flex: 1; align-self: center; - color: $black-transparent; + color: $gl-text-color-quaternary; a { - color: rgba($black, .65); + color: $gl-text-color-secondary; &:not(:first-child), &.group-path { @@ -330,7 +330,7 @@ header.navbar-gitlab-new { white-space: nowrap; > a { - &:last-of-type { + &:last-of-type:not(:first-child) { font-weight: 600; } } @@ -368,9 +368,10 @@ header.navbar-gitlab-new { } .breadcrumbs-sub-title { - margin: 2px 0 0; + margin: 2px 0; font-size: 16px; font-weight: normal; + line-height: 1; ul { margin: 0; @@ -383,6 +384,7 @@ header.navbar-gitlab-new { &::after { content: "/"; margin: 0 2px 0 5px; + color: rgba($black, .65); } } @@ -395,3 +397,13 @@ header.navbar-gitlab-new { color: $gl-text-color; } } + +.top-area { + .nav-controls-new-nav { + .dropdown { + @media (min-width: $screen-sm-min) { + margin-right: 0; + } + } + } +} diff --git a/app/assets/stylesheets/new_sidebar.scss b/app/assets/stylesheets/new_sidebar.scss index 96459fe31cc..bd9a5d7392d 100644 --- a/app/assets/stylesheets/new_sidebar.scss +++ b/app/assets/stylesheets/new_sidebar.scss @@ -2,12 +2,12 @@ @import 'framework/tw_bootstrap_variables'; @import "bootstrap/variables"; -$active-background: rgba(0,0,0,.04); +$active-background: rgba(0, 0, 0, .04); $active-border: $indigo-500; $active-color: $indigo-700; $active-hover-background: $active-background; $active-hover-color: $gl-text-color; -$inactive-badge-background: rgba(0,0,0,.08); +$inactive-badge-background: rgba(0, 0, 0, .08); $hover-background: $indigo-700; $hover-color: $white-light; $inactive-color: $gl-text-color-secondary; @@ -35,6 +35,7 @@ $new-sidebar-width: 220px; .avatar-container { flex: 0 0 40px; + background-color: $white-light; } &:hover { @@ -64,7 +65,6 @@ $new-sidebar-width: 220px; .settings-avatar { background-color: $white-light; - transition: background-color 100ms linear; i { font-size: 20px; @@ -72,7 +72,6 @@ $new-sidebar-width: 220px; color: $gl-text-color-secondary; text-align: center; align-self: center; - transition: color 100ms linear; } } @@ -89,6 +88,7 @@ $new-sidebar-width: 220px; box-shadow: inset -2px 0 0 $border-color; a { + transition: none; text-decoration: none; } @@ -176,7 +176,6 @@ $new-sidebar-width: 220px; color: $hover-color; .badge { - transition: background-color 100ms linear, color 100ms linear; background-color: $indigo-500; color: $hover-color; } diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index 85109fec91a..df858cffe09 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -11,7 +11,7 @@ .is-dragging { // Important because plugin sets inline CSS - opacity: 1!important; + opacity: 1 !important; * { -webkit-user-select: none; @@ -19,8 +19,8 @@ -ms-user-select: none; user-select: none; // !important to make sure no style can override this when dragging - cursor: -webkit-grabbing!important; - cursor: grabbing!important; + cursor: -webkit-grabbing !important; + cursor: grabbing !important; } } diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 23c06eca3c3..b6fc628c02b 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -6,26 +6,26 @@ @keyframes blinking-dots { 0% { background-color: rgba($white-light, 1); - box-shadow: 12px 0 0 0 rgba($white-light,0.2), - 24px 0 0 0 rgba($white-light,0.2); + box-shadow: 12px 0 0 0 rgba($white-light, 0.2), + 24px 0 0 0 rgba($white-light, 0.2); } 25% { background-color: rgba($white-light, 0.4); - box-shadow: 12px 0 0 0 rgba($white-light,2), - 24px 0 0 0 rgba($white-light,0.2); + box-shadow: 12px 0 0 0 rgba($white-light, 2), + 24px 0 0 0 rgba($white-light, 0.2); } 75% { background-color: rgba($white-light, 0.4); - box-shadow: 12px 0 0 0 rgba($white-light,0.2), - 24px 0 0 0 rgba($white-light,1); + box-shadow: 12px 0 0 0 rgba($white-light, 0.2), + 24px 0 0 0 rgba($white-light, 1); } 100% { background-color: rgba($white-light, 1); - box-shadow: 12px 0 0 0 rgba($white-light,0.2), - 24px 0 0 0 rgba($white-light,0.2); + box-shadow: 12px 0 0 0 rgba($white-light, 0.2), + 24px 0 0 0 rgba($white-light, 0.2); } } @@ -244,6 +244,10 @@ } } + .block-last { + padding: 16px 0; + } + .trigger-build-variable { color: $code-color; } diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index 9db0f2075cb..a5e4c3311f8 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -250,8 +250,8 @@ } .committed_ago { - float: right; @extend .cgray; + float: right; } } } diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss index 3039732ca5b..eeb90759f10 100644 --- a/app/assets/stylesheets/pages/cycle_analytics.scss +++ b/app/assets/stylesheets/pages/cycle_analytics.scss @@ -24,9 +24,9 @@ .col-headers { ul { + @include clearfix; margin: 0; padding: 0; - @include clearfix; } li { @@ -189,8 +189,8 @@ } li { - list-style-type: none; @include clearfix; + list-style-type: none; } .stage-nav-item { @@ -281,11 +281,11 @@ } .stage-event-item { + @include clearfix; list-style-type: none; padding: 0 0 $gl-padding; margin: 0 $gl-padding $gl-padding; border-bottom: 1px solid $gray-darker; - @include clearfix; &:last-child { border-bottom: none; @@ -307,9 +307,9 @@ &.issue-title, &.commit-title, &.merge-merquest-title { + @include text-overflow(); max-width: 100%; display: block; - @include text-overflow(); a { color: $gl-text-color; diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 55011e8a21b..398fd4444ea 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -91,6 +91,7 @@ .old_line, .new_line { + @include user-select(none); margin: 0; border: none; padding: 0 5px; @@ -99,7 +100,6 @@ min-width: 35px; max-width: 50px; width: 35px; - @include user-select(none); a { float: left; @@ -354,12 +354,12 @@ } &.active { + cursor: default; + color: $gl-text-color; + &:hover { text-decoration: none; } - - cursor: default; - color: $gl-text-color; } &.disabled { diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 56a4b53ed61..aa04e490649 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -346,13 +346,9 @@ display: none; } - .avatar:hover, - .avatar-counter:hover { - border-color: $issuable-sidebar-color; - } - .avatar-counter:hover { color: $issuable-sidebar-color; + border-color: $issuable-sidebar-color; } .btn-clipboard { @@ -813,8 +809,6 @@ } .description { - margin-bottom: 10px; - .text { margin: 0; } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 7adf17dddb8..2db967547dd 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -96,7 +96,7 @@ overflow: visible; } - & > span { + > span { padding-right: 4px; } @@ -125,7 +125,7 @@ .dropdown-menu { margin-top: 11px; - z-index: 200; + z-index: 300; } .ci-action-icon-wrapper { diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 303425041df..2bb867052f6 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -121,10 +121,11 @@ ul.notes { overflow-y: hidden; .note-text { - word-wrap: break-word; @include md-typography; // Reset ul style types since we're nested inside a ul already @include bulleted-list; + word-wrap: break-word; + ul.task-list { ul:not(.task-list) { padding-left: 1.3em; @@ -250,7 +251,7 @@ ul.notes { } .note-text { - & p:first-child { + p:first-child { display: none; } diff --git a/app/assets/stylesheets/pages/pipeline_schedules.scss b/app/assets/stylesheets/pages/pipeline_schedules.scss index dc719a6ba94..dc1654e006e 100644 --- a/app/assets/stylesheets/pages/pipeline_schedules.scss +++ b/app/assets/stylesheets/pages/pipeline_schedules.scss @@ -1,7 +1,7 @@ .js-pipeline-schedule-form { .dropdown-select, .dropdown-menu-toggle { - width: 100%!important; + width: 100% !important; } .gl-field-error { @@ -96,12 +96,12 @@ } &:last-child { - & .pipeline-variable-row-remove-button { + .pipeline-variable-row-remove-button { display: none; } @media (max-width: $screen-sm-max) { - & .pipeline-variable-value-input { + .pipeline-variable-value-input { margin-right: $pipeline-variable-remove-button-width; } } @@ -137,6 +137,7 @@ } .pipeline-variable-row-remove-button { + @include transition(color); flex-shrink: 0; display: flex; justify-content: center; @@ -147,7 +148,6 @@ background: transparent; border: 0; color: $gl-text-color-secondary; - @include transition(color); &:hover, &:focus { diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index 235c475ff26..22672614e0d 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -376,3 +376,18 @@ table.u2f-registrations { } } } + +.nav-wip { + border: 1px solid $blue-500; + background: $blue-25; + padding: $gl-padding; + margin-bottom: $gl-padding; + + a { + color: $blue-500; + } + + p:last-child { + margin-bottom: 0; + } +} diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 7d7c34115f9..c1423965d0a 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -26,7 +26,7 @@ margin-bottom: 5px; } - & > .form-group { + > .form-group { padding-left: 0; } @@ -83,7 +83,7 @@ border: 1px solid $border-color; } - & + .select2 a { + + .select2 a { border-top-left-radius: 0; border-bottom-left-radius: 0; } @@ -587,9 +587,9 @@ pre.light-well { } .project-row { + @include basic-list-stats; display: flex; align-items: center; - @include basic-list-stats; } h3 { diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss index de652a79369..d7a9dda3770 100644 --- a/app/assets/stylesheets/pages/todos.scss +++ b/app/assets/stylesheets/pages/todos.scss @@ -81,7 +81,7 @@ .todo-title { display: flex; - & > .title-item { + > .title-item { -webkit-flex: 0 0 auto; flex: 0 0 auto; margin: 0 2px; diff --git a/app/assets/stylesheets/pages/ui_dev_kit.scss b/app/assets/stylesheets/pages/ui_dev_kit.scss index 8c87bc3cafd..798e060a261 100644 --- a/app/assets/stylesheets/pages/ui_dev_kit.scss +++ b/app/assets/stylesheets/pages/ui_dev_kit.scss @@ -5,13 +5,13 @@ } .example { + padding: 15px; + border: 1px dashed $ui-dev-kit-example-border; + margin-bottom: 15px; + &::before { content: "Example"; color: $ui-dev-kit-example-color; } - - padding: 15px; - border: 1px dashed $ui-dev-kit-example-border; - margin-bottom: 15px; } } diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss index 94d0a39f397..45c21c5d274 100644 --- a/app/assets/stylesheets/pages/wiki.scss +++ b/app/assets/stylesheets/pages/wiki.scss @@ -147,13 +147,13 @@ } ul.wiki-pages-list.content-list { - & ul { + ul { list-style: none; margin-left: 0; padding-left: 15px; } - & ul li { + ul li { padding: 5px 0; } } diff --git a/app/assets/stylesheets/print.scss b/app/assets/stylesheets/print.scss index 136d0c79467..113e6e86bb5 100644 --- a/app/assets/stylesheets/print.scss +++ b/app/assets/stylesheets/print.scss @@ -37,7 +37,7 @@ ul.notes-form, .issuable-details .content-block-small, .edit-link, .note-action-button { - display: none!important; + display: none !important; } pre { diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 1cc060e4de8..c1bc4c0d675 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -113,6 +113,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :html_emails_enabled, :koding_enabled, :koding_url, + :password_authentication_enabled, :plantuml_enabled, :plantuml_url, :max_artifacts_size, @@ -135,7 +136,6 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :require_two_factor_authentication, :session_expire_delay, :sign_in_text, - :signin_enabled, :signup_enabled, :sentry_dsn, :sentry_enabled, diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index db7edbd619b..43462b13903 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -170,7 +170,7 @@ class ApplicationController < ActionController::Base end def check_password_expiration - if current_user && current_user.password_expires_at && current_user.password_expires_at < Time.now && !current_user.ldap_user? + if current_user && current_user.password_expires_at && current_user.password_expires_at < Time.now && current_user.allow_password_authentication? return redirect_to new_profile_password_path end end diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb index fe331a883c1..3120916c5bb 100644 --- a/app/controllers/autocomplete_controller.rb +++ b/app/controllers/autocomplete_controller.rb @@ -5,10 +5,10 @@ class AutocompleteController < ApplicationController def users @users ||= User.none - @users = @users.search(params[:search]) if params[:search].present? - @users = @users.where.not(id: params[:skip_users]) if params[:skip_users].present? @users = @users.active @users = @users.reorder(:name) + @users = @users.search(params[:search]) if params[:search].present? + @users = @users.where.not(id: params[:skip_users]) if params[:skip_users].present? @users = @users.page(params[:page]).per(params[:per_page]) if params[:todo_filter].present? && current_user diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb index 693e2f6365c..b43b2c5621f 100644 --- a/app/controllers/concerns/issuable_collections.rb +++ b/app/controllers/concerns/issuable_collections.rb @@ -1,6 +1,7 @@ module IssuableCollections extend ActiveSupport::Concern include SortingHelper + include Gitlab::IssuableMetadata included do helper_method :issues_finder @@ -9,39 +10,6 @@ module IssuableCollections private - def issuable_meta_data(issuable_collection, collection_type) - # map has to be used here since using pluck or select will - # throw an error when ordering issuables by priority which inserts - # a new order into the collection. - # We cannot use reorder to not mess up the paginated collection. - issuable_ids = issuable_collection.map(&:id) - - return {} if issuable_ids.empty? - - issuable_note_count = Note.count_for_collection(issuable_ids, @collection_type) - issuable_votes_count = AwardEmoji.votes_for_collection(issuable_ids, @collection_type) - issuable_merge_requests_count = - if collection_type == 'Issue' - MergeRequestsClosingIssues.count_for_collection(issuable_ids) - else - [] - end - - issuable_ids.each_with_object({}) do |id, issuable_meta| - downvotes = issuable_votes_count.find { |votes| votes.awardable_id == id && votes.downvote? } - upvotes = issuable_votes_count.find { |votes| votes.awardable_id == id && votes.upvote? } - notes = issuable_note_count.find { |notes| notes.noteable_id == id } - merge_requests = issuable_merge_requests_count.find { |mr| mr.first == id } - - issuable_meta[id] = Issuable::IssuableMeta.new( - upvotes.try(:count).to_i, - downvotes.try(:count).to_i, - notes.try(:count).to_i, - merge_requests.try(:last).to_i - ) - end - end - def issues_collection issues_finder.execute.preload(:project, :author, :assignees, :labels, :milestone, project: :namespace) end @@ -64,10 +32,10 @@ module IssuableCollections def filter_params set_sort_order_from_cookie - set_default_scope set_default_state - @filter_params = params.dup + # Skip irrelevant Rails routing params + @filter_params = params.dup.except(:controller, :action, :namespace_id) @filter_params[:sort] ||= default_sort_order @sort = @filter_params[:sort] @@ -87,10 +55,6 @@ module IssuableCollections @filter_params end - def set_default_scope - params[:scope] = 'all' if params[:scope].blank? - end - def set_default_state params[:state] = 'opened' if params[:state].blank? end diff --git a/app/controllers/concerns/requires_health_token.rb b/app/controllers/concerns/requires_health_token.rb deleted file mode 100644 index 34ab1a97649..00000000000 --- a/app/controllers/concerns/requires_health_token.rb +++ /dev/null @@ -1,25 +0,0 @@ -module RequiresHealthToken - extend ActiveSupport::Concern - included do - before_action :validate_health_check_access! - end - - private - - def validate_health_check_access! - render_404 unless token_valid? - end - - def token_valid? - token = params[:token].presence || request.headers['TOKEN'] - token.present? && - ActiveSupport::SecurityUtils.variable_size_secure_compare( - token, - current_application_settings.health_check_access_token - ) - end - - def render_404 - render file: Rails.root.join('public', '404'), layout: false, status: '404' - end -end diff --git a/app/controllers/concerns/requires_whitelisted_monitoring_client.rb b/app/controllers/concerns/requires_whitelisted_monitoring_client.rb new file mode 100644 index 00000000000..ad2f4bbc486 --- /dev/null +++ b/app/controllers/concerns/requires_whitelisted_monitoring_client.rb @@ -0,0 +1,33 @@ +module RequiresWhitelistedMonitoringClient + extend ActiveSupport::Concern + included do + before_action :validate_ip_whitelisted_or_valid_token! + end + + private + + def validate_ip_whitelisted_or_valid_token! + render_404 unless client_ip_whitelisted? || valid_token? + end + + def client_ip_whitelisted? + ip_whitelist.any? { |e| e.include?(Gitlab::RequestContext.client_ip) } + end + + def ip_whitelist + @ip_whitelist ||= Settings.monitoring.ip_whitelist.map(&IPAddr.method(:new)) + end + + def valid_token? + token = params[:token].presence || request.headers['TOKEN'] + token.present? && + ActiveSupport::SecurityUtils.variable_size_secure_compare( + token, + current_application_settings.health_check_access_token + ) + end + + def render_404 + render file: Rails.root.join('public', '404'), layout: false, status: '404' + end +end diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb index 28c90548cc1..59e5b5e4775 100644 --- a/app/controllers/dashboard/todos_controller.rb +++ b/app/controllers/dashboard/todos_controller.rb @@ -1,6 +1,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController include ActionView::Helpers::NumberHelper + before_action :authorize_read_project!, only: :index before_action :find_todos, only: [:index, :destroy_all] def index @@ -49,6 +50,15 @@ class Dashboard::TodosController < Dashboard::ApplicationController private + def authorize_read_project! + project_id = params[:project_id] + + if project_id.present? + project = Project.find(project_id) + render_404 unless can?(current_user, :read_project, project) + end + end + def find_todos @todos ||= TodosFinder.new(current_user, params).execute end diff --git a/app/controllers/health_check_controller.rb b/app/controllers/health_check_controller.rb index 5d3109b7187..c3d18991fd4 100644 --- a/app/controllers/health_check_controller.rb +++ b/app/controllers/health_check_controller.rb @@ -1,3 +1,3 @@ class HealthCheckController < HealthCheck::HealthCheckController - include RequiresHealthToken + include RequiresWhitelistedMonitoringClient end diff --git a/app/controllers/health_controller.rb b/app/controllers/health_controller.rb index abc832e6ddc..98c2aaa3526 100644 --- a/app/controllers/health_controller.rb +++ b/app/controllers/health_controller.rb @@ -1,10 +1,13 @@ class HealthController < ActionController::Base protect_from_forgery with: :exception - include RequiresHealthToken + include RequiresWhitelistedMonitoringClient CHECKS = [ Gitlab::HealthChecks::DbCheck, - Gitlab::HealthChecks::RedisCheck, + Gitlab::HealthChecks::Redis::RedisCheck, + Gitlab::HealthChecks::Redis::CacheCheck, + Gitlab::HealthChecks::Redis::QueuesCheck, + Gitlab::HealthChecks::Redis::SharedStateCheck, Gitlab::HealthChecks::FsShardsCheck ].freeze diff --git a/app/controllers/metrics_controller.rb b/app/controllers/metrics_controller.rb index 0e9a19c0b6f..37587a52eaf 100644 --- a/app/controllers/metrics_controller.rb +++ b/app/controllers/metrics_controller.rb @@ -1,12 +1,12 @@ class MetricsController < ActionController::Base - include RequiresHealthToken + include RequiresWhitelistedMonitoringClient protect_from_forgery with: :exception before_action :validate_prometheus_metrics def index - render text: metrics_service.metrics_text, content_type: 'text/plain; verssion=0.0.4' + render text: metrics_service.metrics_text, content_type: 'text/plain; version=0.0.4' end private diff --git a/app/controllers/passwords_controller.rb b/app/controllers/passwords_controller.rb index a8575e037e4..aa8cf630032 100644 --- a/app/controllers/passwords_controller.rb +++ b/app/controllers/passwords_controller.rb @@ -1,6 +1,8 @@ class PasswordsController < Devise::PasswordsController + include Gitlab::CurrentSettings + before_action :resource_from_email, only: [:create] - before_action :prevent_ldap_reset, only: [:create] + before_action :check_password_authentication_available, only: [:create] before_action :throttle_reset, only: [:create] def edit @@ -25,7 +27,7 @@ class PasswordsController < Devise::PasswordsController def update super do |resource| - if resource.valid? && resource.require_password? + if resource.valid? && resource.require_password_creation? resource.update_attribute(:password_automatically_set, false) end end @@ -38,11 +40,11 @@ class PasswordsController < Devise::PasswordsController self.resource = resource_class.find_by_email(email) end - def prevent_ldap_reset - return unless resource && resource.ldap_user? + def check_password_authentication_available + return if current_application_settings.password_authentication_enabled? && (resource.nil? || resource.allow_password_authentication?) redirect_to after_sending_reset_password_instructions_path_for(resource_name), - alert: "Cannot reset password for LDAP user." + alert: "Password authentication is unavailable." end def throttle_reset diff --git a/app/controllers/profiles/passwords_controller.rb b/app/controllers/profiles/passwords_controller.rb index 10145bae0d3..c423761ab24 100644 --- a/app/controllers/profiles/passwords_controller.rb +++ b/app/controllers/profiles/passwords_controller.rb @@ -77,7 +77,7 @@ class Profiles::PasswordsController < Profiles::ApplicationController end def authorize_change_password! - return render_404 if @user.ldap_user? + render_404 unless @user.allow_password_authentication? end def user_params diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 14a1e11a6ea..6de125e7e80 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -38,9 +38,14 @@ class Projects::CommitController < Projects::ApplicationController format.json do Gitlab::PollingInterval.set_header(response, interval: 10_000) - render json: PipelineSerializer - .new(project: @project, current_user: @current_user) - .represent(@pipelines) + render json: { + pipelines: PipelineSerializer + .new(project: @project, current_user: @current_user) + .represent(@pipelines), + count: { + all: @pipelines.count + } + } end end end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index c9e636fb65e..0ac9da2ff0f 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -227,7 +227,7 @@ class Projects::IssuesController < Projects::ApplicationController def issue return @issue if defined?(@issue) # The Sortable default scope causes performance issues when used with find_by - @noteable = @issue ||= @project.issues.find_by!(iid: params[:id]) + @noteable = @issue ||= @project.issues.where(iid: params[:id]).reorder(nil).take! return render_404 unless can?(current_user, :read_issue, @issue) @@ -266,7 +266,7 @@ class Projects::IssuesController < Projects::ApplicationController if action_name == 'new' redirect_to external.new_issue_path else - redirect_to external.project_path + redirect_to external.issue_tracker_path end end diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb index da058da795e..f35d53896ba 100644 --- a/app/controllers/projects/merge_requests/creations_controller.rb +++ b/app/controllers/projects/merge_requests/creations_controller.rb @@ -107,7 +107,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap @target_project = @merge_request.target_project @source_project = @merge_request.source_project - @commits = @merge_request.compare_commits.reverse + @commits = @merge_request.commits @commit = @merge_request.diff_head_commit @note_counts = Note.where(commit_id: @commits.map(&:id)) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index a573b392591..70c41da4de5 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -112,9 +112,14 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo Gitlab::PollingInterval.set_header(response, interval: 10_000) - render json: PipelineSerializer - .new(project: @project, current_user: @current_user) - .represent(@pipelines) + render json: { + pipelines: PipelineSerializer + .new(project: @project, current_user: @current_user) + .represent(@pipelines), + count: { + all: @pipelines.count + } + } end def edit diff --git a/app/controllers/projects/triggers_controller.rb b/app/controllers/projects/triggers_controller.rb index a5b17fa65ea..e04145dd0b3 100644 --- a/app/controllers/projects/triggers_controller.rb +++ b/app/controllers/projects/triggers_controller.rb @@ -69,8 +69,7 @@ class Projects::TriggersController < Projects::ApplicationController def trigger_params params.require(:trigger).permit( - :description, - trigger_schedule_attributes: [:id, :active, :cron, :cron_timezone, :ref] + :description ) end end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 87a69e8e6f9..c769693255c 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -50,10 +50,13 @@ class ProjectsController < Projects::ApplicationController respond_to do |format| if result[:status] == :success flash[:notice] = _("Project '%{project_name}' was successfully updated.") % { project_name: @project.name } + format.html do redirect_to(edit_project_path(@project)) end else + flash[:alert] = result[:message] + format.html { render 'edit' } end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index f39441a281e..0e8a57f8e03 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -48,7 +48,7 @@ class SessionsController < Devise::SessionsController private def login_counter - @login_counter ||= Gitlab::Metrics.counter(:user_session_logins, 'User sign in count') + @login_counter ||= Gitlab::Metrics.counter(:user_session_logins_total, 'User sign in count') end # Handle an "initial setup" state, where there's only one user, it's an admin, @@ -58,7 +58,7 @@ class SessionsController < Devise::SessionsController user = User.admins.last - return unless user && user.require_password? + return unless user && user.require_password_creation? Users::UpdateService.new(user).execute do |user| @token = user.generate_reset_token diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 2e5a6493134..fc63e30c8fb 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -20,9 +20,9 @@ # class IssuableFinder include CreatedAtFilter - + NONE = '0'.freeze - IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page].freeze + IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page state].freeze attr_accessor :current_user, :params @@ -89,8 +89,14 @@ class IssuableFinder execute.find_by!(*params) end - def state_counter_cache_key(state) - Digest::SHA1.hexdigest(state_counter_cache_key_components(state).flatten.join('-')) + def state_counter_cache_key + cache_key(state_counter_cache_key_components) + end + + def clear_caches! + state_counter_cache_key_components_permutations.each do |components| + Rails.cache.delete(cache_key(components)) + end end def group @@ -417,12 +423,19 @@ class IssuableFinder params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me' end - def state_counter_cache_key_components(state) + def state_counter_cache_key_components opts = params.with_indifferent_access - opts[:state] = state opts.except!(*IRRELEVANT_PARAMS_FOR_CACHE_KEY) opts.delete_if { |_, value| value.blank? } ['issuables_count', klass.to_ability_name, opts.sort] end + + def state_counter_cache_key_components_permutations + [state_counter_cache_key_components] + end + + def cache_key(components) + Digest::SHA1.hexdigest(components.flatten.join('-')) + end end diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb index 85230ff1293..0ec42a4e6eb 100644 --- a/app/finders/issues_finder.rb +++ b/app/finders/issues_finder.rb @@ -75,7 +75,7 @@ class IssuesFinder < IssuableFinder current_user.blank? || for_counting || params[:for_counting] end - def state_counter_cache_key_components(state) + def state_counter_cache_key_components extra_components = [ user_can_see_all_confidential_issues?, user_cannot_see_confidential_issues?(for_counting: true) @@ -84,6 +84,16 @@ class IssuesFinder < IssuableFinder super + extra_components end + def state_counter_cache_key_components_permutations + # Ignore the last two, as we'll provide both options for them. + components = super.first[0..-3] + + [ + components + [false, true], + components + [true, false] + ] + end + def by_assignee(items) if assignee items.assigned_to(assignee) diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index f652f4901b7..29b88c60dab 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -1,7 +1,7 @@ module ApplicationSettingsHelper delegate :gravatar_enabled?, :signup_enabled?, - :signin_enabled?, + :password_authentication_enabled?, :akismet_enabled?, :koding_enabled?, to: :current_application_settings @@ -35,7 +35,7 @@ module ApplicationSettingsHelper # Return a group of checkboxes that use Bootstrap's button plugin for a # toggle button effect. def restricted_level_checkboxes(help_block_id, checkbox_name) - Gitlab::VisibilityLevel.options.map do |name, level| + Gitlab::VisibilityLevel.values.map do |level| checked = restricted_visibility_levels(true).include?(level) css_class = checked ? 'active' : '' tag_name = "application_setting_visibility_level_#{level}" @@ -44,7 +44,7 @@ module ApplicationSettingsHelper check_box_tag(checkbox_name, level, checked, autocomplete: 'off', 'aria-describedby' => help_block_id, - id: tag_name) + visibility_level_icon(level) + name + id: tag_name) + visibility_level_icon(level) + visibility_level_label(level) end end end diff --git a/app/helpers/breadcrumbs_helper.rb b/app/helpers/breadcrumbs_helper.rb new file mode 100644 index 00000000000..abe8edd6a8c --- /dev/null +++ b/app/helpers/breadcrumbs_helper.rb @@ -0,0 +1,25 @@ +module BreadcrumbsHelper + def add_to_breadcrumbs(text, link) + @breadcrumbs_extra_links ||= [] + @breadcrumbs_extra_links.push({ + text: text, + link: link + }) + end + + def breadcrumb_title_link + return @breadcrumb_link if @breadcrumb_link + + if controller.available_action?(:index) + url_for(action: "index") + else + request.path + end + end + + def breadcrumb_title(title) + return if defined?(@breadcrumb_title) + + @breadcrumb_title = title + end +end diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index ba84dbe4a7a..bf9ad95b7c2 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -50,12 +50,12 @@ module ButtonHelper def http_clone_button(project, placement = 'right', append_link: true) klass = 'http-selector' - klass << ' has-tooltip' if current_user.try(:require_password?) || current_user.try(:require_personal_access_token?) + klass << ' has-tooltip' if current_user.try(:require_password_creation?) || current_user.try(:require_personal_access_token_creation_for_git_auth?) protocol = gitlab_config.protocol.upcase tooltip_title = - if current_user.try(:require_password?) + if current_user.try(:require_password_creation?) _("Set a password on your account to pull or push via %{protocol}.") % { protocol: protocol } else _("Create a personal access token on your account to pull or push via %{protocol}.") % { protocol: protocol } diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index d0c518f81f7..425af547330 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -235,7 +235,7 @@ module IssuablesHelper def issuables_count_for_state(issuable_type, state, finder: nil) finder ||= public_send("#{issuable_type}_finder") - cache_key = finder.state_counter_cache_key(state) + cache_key = finder.state_counter_cache_key @counts ||= {} @counts[cache_key] ||= Rails.cache.fetch(cache_key, expires_in: 2.minutes) do diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb index 3286a92a8a7..b30b2eb1d03 100644 --- a/app/helpers/page_layout_helper.rb +++ b/app/helpers/page_layout_helper.rb @@ -4,6 +4,10 @@ module PageLayoutHelper @page_title.push(*titles.compact) if titles.any? + if show_new_nav? && titles.any? && !defined?(@breadcrumb_title) + @breadcrumb_title = @page_title.last + end + # Segments are seperated by middot @page_title.join(" \u00b7 ") end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 25969adb649..9a8d296d514 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -195,7 +195,7 @@ module ProjectsHelper controller.controller_name, controller.action_name, current_application_settings.cache_key, - 'v2.4' + 'v2.5' ] key << pipeline_status_cache_key(project.pipeline_status) if project.pipeline_status.has_status? @@ -214,11 +214,11 @@ module ProjectsHelper def show_no_password_message? cookies[:hide_no_password_message].blank? && !current_user.hide_no_password && - ( current_user.require_password? || current_user.require_personal_access_token? ) + ( current_user.require_password_creation? || current_user.require_personal_access_token_creation_for_git_auth? ) end def link_to_set_password - if current_user.require_password? + if current_user.require_password_creation? link_to s_('SetPasswordToCloneLink|set a password'), edit_profile_password_path else link_to s_('CreateTokenToCloneLink|create a personal access token'), profile_personal_access_tokens_path @@ -518,4 +518,12 @@ module ProjectsHelper current_application_settings.restricted_visibility_levels || [] end + + def find_file_path + return unless @project && !@project.empty_repo? + + ref = @ref || @project.repository.root_ref + + project_find_file_path(@project, ref) + end end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 14516091495..898ce45f60e 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -184,6 +184,9 @@ class ApplicationSetting < ActiveRecord::Base Rails.cache.fetch(CACHE_KEY) do ApplicationSetting.last end + rescue + # Fall back to an uncached value if there are any problems (e.g. redis down) + ApplicationSetting.last end def self.expire @@ -234,6 +237,7 @@ class ApplicationSetting < ActiveRecord::Base koding_url: nil, max_artifacts_size: Settings.artifacts['max_size'], max_attachment_size: Settings.gitlab['max_attachment_size'], + password_authentication_enabled: Settings.gitlab['password_authentication_enabled'], performance_bar_allowed_group_id: nil, plantuml_enabled: false, plantuml_url: nil, @@ -248,7 +252,6 @@ class ApplicationSetting < ActiveRecord::Base shared_runners_text: nil, sidekiq_throttling_enabled: false, sign_in_text: nil, - signin_enabled: Settings.gitlab['signin_enabled'], signup_enabled: Settings.gitlab['signup_enabled'], terminal_max_session_time: 0, two_factor_grace_period: 48, diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index fb0fbb43fb1..c6d23898560 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -149,7 +149,7 @@ module Ci private def cleanup_runner_queue - Gitlab::Redis.with do |redis| + Gitlab::Redis::Queues.with do |redis| redis.del(runner_queue_key) end end diff --git a/app/models/commit.rb b/app/models/commit.rb index c7f62617c4c..1e19f00106a 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -1,5 +1,6 @@ class Commit extend ActiveModel::Naming + extend Gitlab::Cache::RequestCache include ActiveModel::Conversion include Noteable @@ -169,19 +170,9 @@ class Commit end def author - if RequestStore.active? - key = "commit_author:#{author_email.downcase}" - # nil is a valid value since no author may exist in the system - if RequestStore.store.key?(key) - @author = RequestStore.store[key] - else - @author = find_author_by_any_email - RequestStore.store[key] = @author - end - else - @author ||= find_author_by_any_email - end + User.find_by_any_email(author_email.downcase) end + request_cache(:author) { author_email.downcase } def committer @committer ||= User.find_by_any_email(committer_email.downcase) @@ -322,7 +313,7 @@ class Commit def raw_diffs(*args) if Gitlab::GitalyClient.feature_enabled?(:commit_raw_diffs) - Gitlab::GitalyClient::Commit.new(project.repository).diff_from_parent(self, *args) + Gitlab::GitalyClient::CommitService.new(project.repository).diff_from_parent(self, *args) else raw.diffs(*args) end @@ -331,7 +322,7 @@ class Commit def raw_deltas @deltas ||= Gitlab::GitalyClient.migrate(:commit_deltas) do |is_enabled| if is_enabled - Gitlab::GitalyClient::Commit.new(project.repository).commit_deltas(self) + Gitlab::GitalyClient::CommitService.new(project.repository).commit_deltas(self) else raw.deltas end @@ -368,10 +359,6 @@ class Commit end end - def find_author_by_any_email - User.find_by_any_email(author_email.downcase) - end - def repo_changes changes = { added: [], modified: [], removed: [] } diff --git a/app/models/concerns/editable.rb b/app/models/concerns/editable.rb index c62c7e1e936..28623d257a6 100644 --- a/app/models/concerns/editable.rb +++ b/app/models/concerns/editable.rb @@ -4,4 +4,8 @@ module Editable def is_edited? last_edited_at.present? && last_edited_at != created_at end + + def last_edited_by + super || User.ghost + end end diff --git a/app/models/concerns/sortable.rb b/app/models/concerns/sortable.rb index fdacfa5a194..a155a064032 100644 --- a/app/models/concerns/sortable.rb +++ b/app/models/concerns/sortable.rb @@ -5,25 +5,6 @@ module Sortable extend ActiveSupport::Concern - module DropDefaultScopeOnFinders - # Override these methods to drop the `ORDER BY id DESC` default scope. - # See http://dba.stackexchange.com/a/110919 for why we do this. - %i[find find_by find_by!].each do |meth| - define_method meth do |*args, &block| - return super(*args, &block) if block - - unordered_relation = unscope(:order) - - # We cannot simply call `meth` on `unscope(:order)`, since that is also - # an instance of the same relation class this module is included into, - # which means we'd get infinite recursion. - # We explicitly use the original implementation to prevent this. - original_impl = method(__method__).super_method.unbind - original_impl.bind(unordered_relation).call(*args) - end - end - end - included do # By default all models should be ordered # by created_at field starting from newest @@ -37,10 +18,6 @@ module Sortable scope :order_updated_asc, -> { reorder(updated_at: :asc) } scope :order_name_asc, -> { reorder(name: :asc) } scope :order_name_desc, -> { reorder(name: :desc) } - - # All queries (relations) on this model are instances of this `relation_klass`. - relation_klass = relation_delegate_class(ActiveRecord::Relation) - relation_klass.prepend DropDefaultScopeOnFinders end module ClassMethods diff --git a/app/models/group.rb b/app/models/group.rb index 70a4ceeffd8..dfa4e8adedd 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -2,7 +2,6 @@ require 'carrierwave/orm/activerecord' class Group < Namespace include Gitlab::ConfigHelper - include Gitlab::VisibilityLevel include AccessRequestable include Avatarable include Referable @@ -103,10 +102,6 @@ class Group < Namespace full_name end - def visibility_level_field - :visibility_level - end - def visibility_level_allowed_by_projects allowed_by_projects = self.projects.where('visibility_level > ?', self.visibility_level).none? diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 815c5b43406..e4e7999d0f2 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -32,9 +32,6 @@ class MergeRequest < ActiveRecord::Base after_create :ensure_merge_request_diff, unless: :importing? after_update :reload_diff_if_branch_changed - delegate :commits, :real_size, :commit_shas, :commits_count, - to: :merge_request_diff, prefix: nil - # When this attribute is true some MR validation is ignored # It allows us to close or modify broken merge requests attr_accessor :allow_broken @@ -224,6 +221,36 @@ class MergeRequest < ActiveRecord::Base "#{project.to_reference(from, full: full)}#{reference}" end + def commits + if persisted? + merge_request_diff.commits + elsif compare_commits + compare_commits.reverse + else + [] + end + end + + def commits_count + if persisted? + merge_request_diff.commits_count + elsif compare_commits + compare_commits.size + else + 0 + end + end + + def commit_shas + if persisted? + merge_request_diff.commit_shas + elsif compare_commits + compare_commits.reverse.map(&:sha) + else + [] + end + end + def first_commit merge_request_diff ? merge_request_diff.first_commit : compare_commits.first end @@ -246,9 +273,7 @@ class MergeRequest < ActiveRecord::Base def diff_size # Calling `merge_request_diff.diffs.real_size` will also perform # highlighting, which we don't need here. - return real_size if merge_request_diff - - diffs.real_size + merge_request_diff&.real_size || diffs.real_size end def diff_base_commit diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 15713fc5f6d..0bb04194bdb 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -5,6 +5,7 @@ class Namespace < ActiveRecord::Base include Sortable include Gitlab::ShellAdapter include Gitlab::CurrentSettings + include Gitlab::VisibilityLevel include Routable include AfterCommitQueue @@ -105,6 +106,10 @@ class Namespace < ActiveRecord::Base end end + def visibility_level_field + :visibility_level + end + def to_param full_path end diff --git a/app/models/note.rb b/app/models/note.rb index 3d39047d32f..d0e3bc0bfed 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -190,7 +190,7 @@ class Note < ActiveRecord::Base # override to return commits, which are not active record def noteable if for_commit? - project.commit(commit_id) + @commit ||= project.commit(commit_id) else super end diff --git a/app/models/project.rb b/app/models/project.rb index d5760164663..0b357d5d003 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -486,7 +486,9 @@ class Project < ActiveRecord::Base end def has_container_registry_tags? - container_repositories.to_a.any?(&:has_tags?) || + return @images if defined?(@images) + + @images = container_repositories.to_a.any?(&:has_tags?) || has_root_container_repository_tags? end @@ -845,7 +847,7 @@ class Project < ActiveRecord::Base end def ci_service - @ci_service ||= ci_services.find_by(active: true) + @ci_service ||= ci_services.reorder(nil).find_by(active: true) end def deployment_services @@ -853,7 +855,7 @@ class Project < ActiveRecord::Base end def deployment_service - @deployment_service ||= deployment_services.find_by(active: true) + @deployment_service ||= deployment_services.reorder(nil).find_by(active: true) end def monitoring_services @@ -861,7 +863,7 @@ class Project < ActiveRecord::Base end def monitoring_service - @monitoring_service ||= monitoring_services.find_by(active: true) + @monitoring_service ||= monitoring_services.reorder(nil).find_by(active: true) end def jira_tracker? @@ -977,8 +979,6 @@ class Project < ActiveRecord::Base Rails.logger.error "Attempting to rename #{old_path_with_namespace} -> #{new_path_with_namespace}" - expire_caches_before_rename(old_path_with_namespace) - if has_container_registry_tags? Rails.logger.error "Project #{old_path_with_namespace} cannot be renamed because container registry tags are present!" @@ -986,6 +986,8 @@ class Project < ActiveRecord::Base raise StandardError.new('Project cannot be renamed, because images are present in its container registry') end + expire_caches_before_rename(old_path_with_namespace) + if gitlab_shell.mv_repository(repository_storage_path, old_path_with_namespace, new_path_with_namespace) # If repository moved successfully we need to send update instructions to users. # However we cannot allow rollback since we moved repository @@ -1386,15 +1388,15 @@ class Project < ActiveRecord::Base end def pushes_since_gc - Gitlab::Redis.with { |redis| redis.get(pushes_since_gc_redis_key).to_i } + Gitlab::Redis::SharedState.with { |redis| redis.get(pushes_since_gc_redis_shared_state_key).to_i } end def increment_pushes_since_gc - Gitlab::Redis.with { |redis| redis.incr(pushes_since_gc_redis_key) } + Gitlab::Redis::SharedState.with { |redis| redis.incr(pushes_since_gc_redis_shared_state_key) } end def reset_pushes_since_gc - Gitlab::Redis.with { |redis| redis.del(pushes_since_gc_redis_key) } + Gitlab::Redis::SharedState.with { |redis| redis.del(pushes_since_gc_redis_shared_state_key) } end def route_map_for(commit_sha) @@ -1457,7 +1459,7 @@ class Project < ActiveRecord::Base from && self != from end - def pushes_since_gc_redis_key + def pushes_since_gc_redis_shared_state_key "projects/#{id}/pushes_since_gc" end diff --git a/app/models/project_services/gitlab_issue_tracker_service.rb b/app/models/project_services/gitlab_issue_tracker_service.rb index 420102875a5..88c428b4aae 100644 --- a/app/models/project_services/gitlab_issue_tracker_service.rb +++ b/app/models/project_services/gitlab_issue_tracker_service.rb @@ -23,7 +23,7 @@ class GitlabIssueTrackerService < IssueTrackerService project_issue_url(project, id: iid) end - def project_path + def issue_tracker_path project_issues_path(project) end diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb index 1fa4cd4db30..6d6a3ae3647 100644 --- a/app/models/project_services/issue_tracker_service.rb +++ b/app/models/project_services/issue_tracker_service.rb @@ -20,8 +20,8 @@ class IssueTrackerService < Service self.issues_url.gsub(':id', iid.to_s) end - def project_path - read_attribute(:project_url) + def issue_tracker_path + project_url end def new_issue_path diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb index 62f7c057c5b..dee99bbb859 100644 --- a/app/models/project_services/kubernetes_service.rb +++ b/app/models/project_services/kubernetes_service.rb @@ -59,21 +59,21 @@ class KubernetesService < DeploymentService def fields [ { type: 'text', - name: 'namespace', - title: 'Kubernetes namespace', - placeholder: namespace_placeholder }, - { type: 'text', name: 'api_url', title: 'API URL', placeholder: 'Kubernetes API URL, like https://kube.example.com/' }, - { type: 'text', - name: 'token', - title: 'Service token', - placeholder: 'Service token' }, { type: 'textarea', name: 'ca_pem', - title: 'Custom CA bundle', - placeholder: 'Certificate Authority bundle (PEM format)' } + title: 'CA Certificate', + placeholder: 'Certificate Authority bundle (PEM format)' }, + { type: 'text', + name: 'namespace', + title: 'Project namespace (optional/unique)', + placeholder: namespace_placeholder }, + { type: 'text', + name: 'token', + title: 'Token', + placeholder: 'Service token' } ] end diff --git a/app/models/repository.rb b/app/models/repository.rb index 10b429c707e..8663cf5e602 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -123,6 +123,7 @@ class Repository commits end + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/384 def find_commits_by_message(query, ref = nil, path = nil, limit = 1000, offset = 0) unless exists? && has_visible_content? && query.present? return [] @@ -610,6 +611,7 @@ class Repository commit(sha) end + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/383 def last_commit_id_for_path(sha, path) key = path.blank? ? "last_commit_id_for_path:#{sha}" : "last_commit_id_for_path:#{sha}:#{Digest::SHA1.hexdigest(path)}" diff --git a/app/models/user.rb b/app/models/user.rb index 4b01c2f19f0..c26be6d05a2 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -314,7 +314,7 @@ class User < ActiveRecord::Base table[:name].matches(pattern) .or(table[:email].matches(pattern)) .or(table[:username].matches(pattern)) - ).reorder(order % { query: ActiveRecord::Base.connection.quote(query) }, id: :desc) + ).reorder(order % { query: ActiveRecord::Base.connection.quote(query) }, :name) end # searches user by given pattern @@ -385,9 +385,11 @@ class User < ActiveRecord::Base # Return (create if necessary) the ghost user. The ghost user # owns records previously belonging to deleted users. def ghost - unique_internal(where(ghost: true), 'ghost', 'ghost%s@example.com') do |u| + email = 'ghost%s@example.com' + unique_internal(where(ghost: true), 'ghost', email) do |u| u.bio = 'This is a "Ghost User", created to hold all issues authored by users that have since been deleted. This user cannot be removed.' u.name = 'Ghost User' + u.notification_email = email end end end @@ -580,16 +582,20 @@ class User < ActiveRecord::Base keys.count == 0 && Gitlab::ProtocolAccess.allowed?('ssh') end - def require_password? - password_automatically_set? && !ldap_user? && current_application_settings.signin_enabled? + def require_password_creation? + password_automatically_set? && allow_password_authentication? end - def require_personal_access_token? - return false if current_application_settings.signin_enabled? || ldap_user? + def require_personal_access_token_creation_for_git_auth? + return false if allow_password_authentication? || ldap_user? PersonalAccessTokensFinder.new(user: self, impersonation: false, state: 'active').execute.none? end + def allow_password_authentication? + !ldap_user? && current_application_settings.password_authentication_enabled? + end + def can_change_username? gitlab_config.username_changing_enabled end @@ -699,7 +705,7 @@ class User < ActiveRecord::Base end def sanitize_attrs - %w[name username skype linkedin twitter].each do |attr| + %w[username skype linkedin twitter].each do |attr| value = public_send(attr) public_send("#{attr}=", Sanitize.clean(value)) if value.present? end diff --git a/app/policies/ci/build_policy.rb b/app/policies/ci/build_policy.rb index a886efc1360..386822d3ff6 100644 --- a/app/policies/ci/build_policy.rb +++ b/app/policies/ci/build_policy.rb @@ -3,9 +3,13 @@ module Ci condition(:protected_action) do next false unless @subject.action? - !::Gitlab::UserAccess - .new(@user, project: @subject.project) - .can_merge_to_branch?(@subject.ref) + access = ::Gitlab::UserAccess.new(@user, project: @subject.project) + + if @subject.tag? + !access.can_create_tag?(@subject.ref) + else + !access.can_merge_to_branch?(@subject.ref) + end end rule { protected_action }.prevent :update_build diff --git a/app/presenters/merge_request_presenter.rb b/app/presenters/merge_request_presenter.rb index 6ba1d3165e9..2df84e58575 100644 --- a/app/presenters/merge_request_presenter.rb +++ b/app/presenters/merge_request_presenter.rb @@ -76,6 +76,12 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated end end + def target_branch_tree_path + if target_branch_exists? + project_tree_path(project, target_branch) + end + end + def target_branch_commits_path if target_branch_exists? project_commits_path(project, target_branch) @@ -94,7 +100,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated if source_branch_exists? namespace = link_to(namespace, project_path(source_project)) - branch = link_to(branch, project_commits_path(source_project, source_branch)) + branch = link_to(branch, project_tree_path(source_project, source_branch)) end if for_fork? diff --git a/app/serializers/merge_request_entity.rb b/app/serializers/merge_request_entity.rb index 7ec2dbd0efe..7f17f2bf604 100644 --- a/app/serializers/merge_request_entity.rb +++ b/app/serializers/merge_request_entity.rb @@ -97,6 +97,10 @@ class MergeRequestEntity < IssuableEntity presenter(merge_request).target_branch_commits_path end + expose :target_branch_tree_path do |merge_request| + presenter(merge_request).target_branch_tree_path + end + expose :new_blob_path do |merge_request| if can?(current_user, :push_code, merge_request.project) project_new_blob_path(merge_request.project, merge_request.source_branch) diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb index a1d67cbc244..eb345fead2d 100644 --- a/app/services/boards/issues/list_service.rb +++ b/app/services/boards/issues/list_service.rb @@ -33,17 +33,12 @@ module Boards end def filter_params - set_default_scope set_project set_state params end - def set_default_scope - params[:scope] = 'all' - end - def set_project params[:project_id] = project.id end diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb index 4f35255fb53..273386776fa 100644 --- a/app/services/ci/create_pipeline_service.rb +++ b/app/services/ci/create_pipeline_service.rb @@ -135,7 +135,7 @@ module Ci end def pipeline_created_counter - @pipeline_created_counter ||= Gitlab::Metrics.counter(:pipelines_created_count, "Pipelines created count") + @pipeline_created_counter ||= Gitlab::Metrics.counter(:pipelines_created_total, "Counter of pipelines created") end end end diff --git a/app/services/git_operation_service.rb b/app/services/git_operation_service.rb index 43636fde0be..32925e9c1f2 100644 --- a/app/services/git_operation_service.rb +++ b/app/services/git_operation_service.rb @@ -129,6 +129,7 @@ class GitOperationService end end + # Gitaly note: JV: wait with migrating #update_ref until we know how to migrate its call sites. def update_ref(ref, newrev, oldrev) # We use 'git update-ref' because libgit2/rugged currently does not # offer 'compare and swap' ref updates. Without compare-and-swap we can diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index a03a7abfeb1..9078b1f0983 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -183,7 +183,7 @@ class IssuableBaseService < BaseService after_create(issuable) issuable.create_cross_references!(current_user) execute_hooks(issuable) - invalidate_cache_counts(issuable.assignees, issuable) + invalidate_cache_counts(issuable, users: issuable.assignees) end issuable @@ -240,12 +240,12 @@ class IssuableBaseService < BaseService old_assignees: old_assignees ) - if old_assignees != issuable.assignees - new_assignees = issuable.assignees.to_a - affected_assignees = (old_assignees + new_assignees) - (old_assignees & new_assignees) - invalidate_cache_counts(affected_assignees.compact, issuable) - end + new_assignees = issuable.assignees.to_a + affected_assignees = (old_assignees + new_assignees) - (old_assignees & new_assignees) + # Don't clear the project cache, because it will be handled by the + # appropriate service (close / reopen / merge / etc.). + invalidate_cache_counts(issuable, users: affected_assignees.compact, skip_project_cache: true) after_update(issuable) issuable.create_new_cross_references!(current_user) execute_hooks(issuable, 'update') @@ -339,9 +339,18 @@ class IssuableBaseService < BaseService create_labels_note(issuable, old_labels) if issuable.labels != old_labels end - def invalidate_cache_counts(users, issuable) + def invalidate_cache_counts(issuable, users: [], skip_project_cache: false) users.each do |user| user.public_send("invalidate_#{issuable.model_name.singular}_cache_counts") end + + unless skip_project_cache + case issuable + when Issue + IssuesFinder.new(nil, project_id: issuable.project_id).clear_caches! + when MergeRequest + MergeRequestsFinder.new(nil, project_id: issuable.target_project_id).clear_caches! + end + end end end diff --git a/app/services/issues/close_service.rb b/app/services/issues/close_service.rb index 85c616ca576..ddef5281498 100644 --- a/app/services/issues/close_service.rb +++ b/app/services/issues/close_service.rb @@ -28,7 +28,7 @@ module Issues notification_service.close_issue(issue, current_user) if notifications todo_service.close_issue(issue, current_user) execute_hooks(issue, 'close') - invalidate_cache_counts(issue.assignees, issue) + invalidate_cache_counts(issue, users: issue.assignees) end issue diff --git a/app/services/issues/reopen_service.rb b/app/services/issues/reopen_service.rb index 80ea6312768..73b2e85cba3 100644 --- a/app/services/issues/reopen_service.rb +++ b/app/services/issues/reopen_service.rb @@ -8,7 +8,7 @@ module Issues create_note(issue) notification_service.reopen_issue(issue, current_user) execute_hooks(issue, 'reopen') - invalidate_cache_counts(issue.assignees, issue) + invalidate_cache_counts(issue, users: issue.assignees) end issue diff --git a/app/services/merge_requests/close_service.rb b/app/services/merge_requests/close_service.rb index 2ffc989ed71..c0ce01f7523 100644 --- a/app/services/merge_requests/close_service.rb +++ b/app/services/merge_requests/close_service.rb @@ -13,7 +13,7 @@ module MergeRequests notification_service.close_mr(merge_request, current_user) todo_service.close_merge_request(merge_request, current_user) execute_hooks(merge_request, 'close') - invalidate_cache_counts(merge_request.assignees, merge_request) + invalidate_cache_counts(merge_request, users: merge_request.assignees) end merge_request diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb index 71d37797bb4..19189e64acf 100644 --- a/app/services/merge_requests/create_service.rb +++ b/app/services/merge_requests/create_service.rb @@ -7,9 +7,8 @@ module MergeRequests source_project = @project @project = Project.find(params[:target_project_id]) if params[:target_project_id] - params[:target_project_id] ||= source_project.id - merge_request = MergeRequest.new + merge_request.target_project = @project merge_request.source_project = source_project merge_request.source_branch = params[:source_branch] merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch) diff --git a/app/services/merge_requests/post_merge_service.rb b/app/services/merge_requests/post_merge_service.rb index f0d998731d7..261a8bfa200 100644 --- a/app/services/merge_requests/post_merge_service.rb +++ b/app/services/merge_requests/post_merge_service.rb @@ -13,7 +13,7 @@ module MergeRequests create_note(merge_request) notification_service.merge_mr(merge_request, current_user) execute_hooks(merge_request, 'merge') - invalidate_cache_counts(merge_request.assignees, merge_request) + invalidate_cache_counts(merge_request, users: merge_request.assignees) end private diff --git a/app/services/merge_requests/reopen_service.rb b/app/services/merge_requests/reopen_service.rb index f2fddf7f345..52f6d511f98 100644 --- a/app/services/merge_requests/reopen_service.rb +++ b/app/services/merge_requests/reopen_service.rb @@ -10,7 +10,7 @@ module MergeRequests execute_hooks(merge_request, 'reopen') merge_request.reload_diff(current_user) merge_request.mark_as_unchecked - invalidate_cache_counts(merge_request.assignees, merge_request) + invalidate_cache_counts(merge_request, users: merge_request.assignees) end merge_request diff --git a/app/services/metrics_service.rb b/app/services/metrics_service.rb index d726db4e99b..a02eee4961b 100644 --- a/app/services/metrics_service.rb +++ b/app/services/metrics_service.rb @@ -3,7 +3,10 @@ require 'prometheus/client/formats/text' class MetricsService CHECKS = [ Gitlab::HealthChecks::DbCheck, - Gitlab::HealthChecks::RedisCheck, + Gitlab::HealthChecks::Redis::RedisCheck, + Gitlab::HealthChecks::Redis::CacheCheck, + Gitlab::HealthChecks::Redis::QueuesCheck, + Gitlab::HealthChecks::Redis::SharedStateCheck, Gitlab::HealthChecks::FsShardsCheck ].freeze @@ -28,6 +31,6 @@ class MetricsService end def multiprocess_metrics_path - @multiprocess_metrics_path ||= Rails.root.join(ENV['prometheus_multiproc_dir']).freeze + ::Prometheus::Client.configuration.multiprocess_files_dir end end diff --git a/app/services/milestones/destroy_service.rb b/app/services/milestones/destroy_service.rb index e457212508f..600ebcfbecb 100644 --- a/app/services/milestones/destroy_service.rb +++ b/app/services/milestones/destroy_service.rb @@ -1,15 +1,17 @@ module Milestones class DestroyService < Milestones::BaseService def execute(milestone) + return unless milestone.is_project_milestone? + Milestone.transaction do update_params = { milestone: nil } milestone.issues.each do |issue| - Issues::UpdateService.new(project, current_user, update_params).execute(issue) + Issues::UpdateService.new(parent, current_user, update_params).execute(issue) end milestone.merge_requests.each do |merge_request| - MergeRequests::UpdateService.new(project, current_user, update_params).execute(merge_request) + MergeRequests::UpdateService.new(parent, current_user, update_params).execute(merge_request) end event_service.destroy_milestone(milestone, current_user) diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb index 55d9cb13ae4..30ca95eef7a 100644 --- a/app/services/projects/update_service.rb +++ b/app/services/projects/update_service.rb @@ -1,22 +1,16 @@ module Projects class UpdateService < BaseService def execute - # check that user is allowed to set specified visibility_level - new_visibility = params[:visibility_level] - - if new_visibility && new_visibility.to_i != project.visibility_level - unless can?(current_user, :change_visibility_level, project) && - Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility) - - deny_visibility_level(project, new_visibility) - return error('Visibility level unallowed') - end + unless visibility_level_allowed? + return error('New visibility level not allowed!') end - new_branch = params[:default_branch] + if project.has_container_registry_tags? + return error('Cannot rename project because it contains container registry tags!') + end - if project.repository.exists? && new_branch && new_branch != project.default_branch - project.change_head(new_branch) + if changing_default_branch? + project.change_head(params[:default_branch]) end if project.update_attributes(params.except(:default_branch)) @@ -28,8 +22,33 @@ module Projects success else - error('Project could not be updated') + error('Project could not be updated!') end end + + private + + def visibility_level_allowed? + # check that user is allowed to set specified visibility_level + new_visibility = params[:visibility_level] + + if new_visibility && new_visibility.to_i != project.visibility_level + unless can?(current_user, :change_visibility_level, project) && + Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility) + + deny_visibility_level(project, new_visibility) + return false + end + end + + true + end + + def changing_default_branch? + new_branch = params[:default_branch] + + project.repository.exists? && + new_branch && new_branch != project.default_branch + end end end diff --git a/app/services/users/migrate_to_ghost_user_service.rb b/app/services/users/migrate_to_ghost_user_service.rb index 4628c4c6f6e..3a9c151cf9b 100644 --- a/app/services/users/migrate_to_ghost_user_service.rb +++ b/app/services/users/migrate_to_ghost_user_service.rb @@ -50,10 +50,12 @@ module Users def migrate_issues user.issues.update_all(author_id: ghost_user.id) + Issue.where(last_edited_by_id: user.id).update_all(last_edited_by_id: ghost_user.id) end def migrate_merge_requests user.merge_requests.update_all(author_id: ghost_user.id) + MergeRequest.where(merge_user_id: user.id).update_all(merge_user_id: ghost_user.id) end def migrate_notes diff --git a/app/uploaders/gitlab_uploader.rb b/app/uploaders/gitlab_uploader.rb index 0da7a025591..05a2091633a 100644 --- a/app/uploaders/gitlab_uploader.rb +++ b/app/uploaders/gitlab_uploader.rb @@ -16,7 +16,7 @@ class GitlabUploader < CarrierWave::Uploader::Base def self.base_dir return root_dir unless file_storage? - File.join(root_dir, 'system') + File.join(root_dir, '-', 'system') end def self.file_storage? diff --git a/app/uploaders/personal_file_uploader.rb b/app/uploaders/personal_file_uploader.rb index 7f857765fbf..ef70871624b 100644 --- a/app/uploaders/personal_file_uploader.rb +++ b/app/uploaders/personal_file_uploader.rb @@ -3,6 +3,10 @@ class PersonalFileUploader < FileUploader File.join(CarrierWave.root, model_path(model)) end + def self.base_dir + File.join(root_dir, 'system') + end + private def secure_url diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 7f1e13c7989..26f7c1a473a 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -145,9 +145,9 @@ .form-group .col-sm-offset-2.col-sm-10 .checkbox - = f.label :signin_enabled do - = f.check_box :signin_enabled - Sign-in enabled + = f.label :password_authentication_enabled do + = f.check_box :password_authentication_enabled + Password authentication enabled - if omniauth_enabled? && button_based_providers.any? .form-group = f.label :enabled_oauth_sign_in_sources, 'Enabled OAuth sign-in sources', class: 'control-label col-sm-2' diff --git a/app/views/admin/applications/edit.html.haml b/app/views/admin/applications/edit.html.haml index c596866bde2..13b583e6072 100644 --- a/app/views/admin/applications/edit.html.haml +++ b/app/views/admin/applications/edit.html.haml @@ -1,4 +1,5 @@ - page_title "Edit", @application.name, "Applications" + %h3.page-title Edit application - @url = admin_application_path(@application) = render 'form', application: @application diff --git a/app/views/admin/applications/new.html.haml b/app/views/admin/applications/new.html.haml index 6310d89bd6b..346c58877d9 100644 --- a/app/views/admin/applications/new.html.haml +++ b/app/views/admin/applications/new.html.haml @@ -1,4 +1,6 @@ +- breadcrumb_title "Applications" - page_title "New Application" + %h3.page-title New application - @url = admin_applications_path = render 'form', application: @application diff --git a/app/views/admin/broadcast_messages/edit.html.haml b/app/views/admin/broadcast_messages/edit.html.haml index 45e053eb31d..8cbc4597e32 100644 --- a/app/views/admin/broadcast_messages/edit.html.haml +++ b/app/views/admin/broadcast_messages/edit.html.haml @@ -1,3 +1,4 @@ +- breadcrumb_title "Messages" - page_title "Broadcast Messages" = render 'form' diff --git a/app/views/admin/broadcast_messages/index.html.haml b/app/views/admin/broadcast_messages/index.html.haml index 4f2ae081d7a..b806882eee3 100644 --- a/app/views/admin/broadcast_messages/index.html.haml +++ b/app/views/admin/broadcast_messages/index.html.haml @@ -1,3 +1,4 @@ +- breadcrumb_title "Messages" - page_title "Broadcast Messages" %h3.page-title diff --git a/app/views/dashboard/_groups_head.html.haml b/app/views/dashboard/_groups_head.html.haml index 4594c52b34b..5a379eae8f4 100644 --- a/app/views/dashboard/_groups_head.html.haml +++ b/app/views/dashboard/_groups_head.html.haml @@ -1,3 +1,7 @@ +- if show_new_nav? && current_user.can_create_group? + - content_for :breadcrumbs_extra do + = link_to "New group", new_group_path, class: "btn btn-new" + .top-area %ul.nav-links = nav_link(page: dashboard_groups_path) do @@ -6,9 +10,8 @@ = nav_link(page: explore_groups_path) do = link_to explore_groups_path, title: 'Explore public groups' do Explore public groups - .nav-controls + .nav-controls{ class: ("nav-controls-new-nav" if show_new_nav?) } = render 'shared/groups/search_form' = render 'shared/groups/dropdown' - if current_user.can_create_group? - = link_to new_group_path, class: "btn btn-new" do - New group + = link_to "New group", new_group_path, class: "btn btn-new #{("visible-xs" if show_new_nav?)}" diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml index 64b737ee886..1f9a5b401b6 100644 --- a/app/views/dashboard/_projects_head.html.haml +++ b/app/views/dashboard/_projects_head.html.haml @@ -1,5 +1,10 @@ = content_for :flash_message do = render 'shared/project_limit' + +- if show_new_nav? && current_user.can_create_project? + - content_for :breadcrumbs_extra do + = link_to "New project", new_project_path, class: 'btn btn-new' + .top-area.scrolling-tabs-container.inner-page-scroll-tabs .fade-left= icon('angle-left') .fade-right= icon('angle-right') @@ -14,9 +19,8 @@ = link_to explore_root_path, title: 'Explore', data: {placement: 'right'} do Explore projects - .nav-controls + .nav-controls{ class: ("nav-controls-new-nav" if show_new_nav?) } = render 'shared/projects/search_form' = render 'shared/projects/dropdown' - if current_user.can_create_project? - = link_to new_project_path, class: 'btn btn-new' do - New project + = link_to "New project", new_project_path, class: "btn btn-new #{("visible-xs" if show_new_nav?)}" diff --git a/app/views/dashboard/_snippets_head.html.haml b/app/views/dashboard/_snippets_head.html.haml index 02e90bbfa55..fd5389106bb 100644 --- a/app/views/dashboard/_snippets_head.html.haml +++ b/app/views/dashboard/_snippets_head.html.haml @@ -1,3 +1,7 @@ +- if show_new_nav? && current_user + - content_for :breadcrumbs_extra do + = link_to "New snippet", new_snippet_path, class: "btn btn-new", title: "New snippet" + .top-area %ul.nav-links = nav_link(page: dashboard_snippets_path, html_options: {class: 'home'}) do @@ -8,6 +12,5 @@ Explore Snippets - if current_user - .nav-controls.hidden-xs - = link_to new_snippet_path, class: "btn btn-new", title: "New snippet" do - New snippet + .nav-controls.hidden-xs{ class: ("hidden-sm hidden-md hidden-lg" if show_new_nav?) } + = link_to "New snippet", new_snippet_path, class: "btn btn-new", title: "New snippet" diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml index d6b46dee0e4..52e0012fd7d 100644 --- a/app/views/dashboard/issues.html.haml +++ b/app/views/dashboard/issues.html.haml @@ -1,11 +1,18 @@ +- @hide_top_links = true - page_title "Issues" - header_title "Issues", issues_dashboard_path(assignee_id: current_user.id) = content_for :meta_tags do = auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{current_user.name} issues") +- if show_new_nav? + - content_for :breadcrumbs_extra do + = link_to params.merge(rss_url_options), class: 'btn has-tooltip append-right-10', title: 'Subscribe' do + = icon('rss') + = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues' + .top-area = render 'shared/issuable/nav', type: :issues - .nav-controls + .nav-controls{ class: ("visible-xs" if show_new_nav?) } = link_to params.merge(rss_url_options), class: 'btn has-tooltip', title: 'Subscribe' do = icon('rss') = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues' diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml index 6f6afe161d1..c3fe14da2b2 100644 --- a/app/views/dashboard/merge_requests.html.haml +++ b/app/views/dashboard/merge_requests.html.haml @@ -1,9 +1,14 @@ +- @hide_top_links = true - page_title "Merge Requests" - header_title "Merge Requests", merge_requests_dashboard_path(assignee_id: current_user.id) +- if show_new_nav? + - content_for :breadcrumbs_extra do + = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", with_feature_enabled: 'merge_requests' + .top-area = render 'shared/issuable/nav', type: :merge_requests - .nav-controls + .nav-controls{ class: ("visible-xs" if show_new_nav?) } = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", with_feature_enabled: 'merge_requests' = render 'shared/issuable/filter', type: :merge_requests diff --git a/app/views/dashboard/milestones/index.html.haml b/app/views/dashboard/milestones/index.html.haml index ef1467c4d78..37dbcaf5cb8 100644 --- a/app/views/dashboard/milestones/index.html.haml +++ b/app/views/dashboard/milestones/index.html.haml @@ -2,10 +2,14 @@ - page_title 'Milestones' - header_title 'Milestones', dashboard_milestones_path +- if show_new_nav? + - content_for :breadcrumbs_extra do + = render 'shared/new_project_item_select', path: 'milestones/new', label: 'New milestone', include_groups: true + .top-area = render 'shared/milestones_filter', counts: @milestone_states - .nav-controls + .nav-controls{ class: ("visible-xs" if show_new_nav?) } = render 'shared/new_project_item_select', path: 'milestones/new', label: 'New milestone', include_groups: true .milestones diff --git a/app/views/dashboard/projects/_blank_state_admin_welcome.html.haml b/app/views/dashboard/projects/_blank_state_admin_welcome.html.haml index 0319838bdb4..209afd4aab4 100644 --- a/app/views/dashboard/projects/_blank_state_admin_welcome.html.haml +++ b/app/views/dashboard/projects/_blank_state_admin_welcome.html.haml @@ -1,7 +1,7 @@ -.row.blank-state.clearfix - .col-md-1.col-md-offset-3.blank-state-icon +.blank-state + .blank-state-icon = custom_icon("add_new_user", size: 50) - .col-md-5.blank-state-body + .blank-state-body %h3.blank-state-title Add user %p.blank-state-text @@ -9,10 +9,10 @@ = link_to new_admin_user_path, class: "btn btn-new" do New user -.row.blank-state.clearfix - .col-md-1.col-md-offset-3.blank-state-icon +.blank-state + .blank-state-icon = custom_icon("configure_server", size: 50) - .col-md-5.blank-state-body + .blank-state-body %h3.blank-state-title Configure GitLab %p.blank-state-text @@ -21,10 +21,10 @@ Configure - if current_user.can_create_group? - .row.blank-state.clearfix - .col-md-1.col-md-offset-3.blank-state-icon + .blank-state + .blank-state-icon = custom_icon("add_new_group", size: 50) - .col-md-5.blank-state-body + .blank-state-body %h3.blank-state-title Create a group %p.blank-state-text diff --git a/app/views/dashboard/projects/_blank_state_welcome.html.haml b/app/views/dashboard/projects/_blank_state_welcome.html.haml index a079f0ac1a4..a93a3415ee1 100644 --- a/app/views/dashboard/projects/_blank_state_welcome.html.haml +++ b/app/views/dashboard/projects/_blank_state_welcome.html.haml @@ -1,10 +1,10 @@ - public_project_count = ProjectsFinder.new(current_user: current_user).execute.count - if current_user.can_create_group? - .row.blank-state.clearfix - .col-md-1.col-md-offset-3.blank-state-icon + .blank-state + .blank-state-icon = custom_icon("add_new_group", size: 50) - .col-md-5.blank-state-body + .blank-state-body %h3.blank-state-title Create a group for several dependent projects. %p.blank-state-text @@ -12,10 +12,10 @@ = link_to new_group_path, class: "btn btn-new" do New group -.row.blank-state.clearfix - .col-md-1.col-md-offset-3.blank-state-icon +.blank-state + .blank-state-icon = custom_icon("add_new_project", size: 50) - .col-md-5.blank-state-body + .blank-state-body %h3.blank-state-title Create a project %p.blank-state-text @@ -32,10 +32,10 @@ New project - if public_project_count > 0 - .row.blank-state.clearfix - .col-md-1.col-md-offset-3.blank-state-icon + .blank-state + .blank-state-icon = custom_icon("globe", size: 50) - .col-md-5.blank-state-body + .blank-state-body %h3.blank-state-title Explore public projects %p.blank-state-text diff --git a/app/views/dashboard/projects/_zero_authorized_projects.html.haml b/app/views/dashboard/projects/_zero_authorized_projects.html.haml index 94af033c1e3..ad3fac6d164 100644 --- a/app/views/dashboard/projects/_zero_authorized_projects.html.haml +++ b/app/views/dashboard/projects/_zero_authorized_projects.html.haml @@ -1,6 +1,6 @@ .row.blank-state-parent-container - .section-container - .container.section-body.section-welcome{ class: "#{ 'section-admin-welcome' if current_user.admin? }" } + .section-container.section-welcome{ class: "#{ 'section-admin-welcome' if current_user.admin? }" } + .container.section-body .blank-state.blank-state-welcome %h2.blank-state-welcome-title Welcome to GitLab diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml index 7ac6cf06fb9..ec6cb1a9624 100644 --- a/app/views/dashboard/projects/index.html.haml +++ b/app/views/dashboard/projects/index.html.haml @@ -1,6 +1,5 @@ - @no_container = true - @hide_top_links = true -- @breadcrumb_title = "Projects" = content_for :meta_tags do = auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity") diff --git a/app/views/dashboard/projects/starred.html.haml b/app/views/dashboard/projects/starred.html.haml index 99efe9c9b86..ae1d733a516 100644 --- a/app/views/dashboard/projects/starred.html.haml +++ b/app/views/dashboard/projects/starred.html.haml @@ -1,5 +1,6 @@ +- @hide_top_links = true - @no_container = true - +- breadcrumb_title "Projects" - page_title "Starred Projects" - header_title "Projects", dashboard_projects_path diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index 52d6ebd8a14..9b615ec999e 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -1,3 +1,4 @@ +- @hide_top_links = true - page_title "Todos" - header_title "Todos", dashboard_todos_path diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index af87129e49e..dd61dcf2a7b 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -6,15 +6,15 @@ - else = render 'devise/shared/tabs_normal' .tab-content - - if signin_enabled? || ldap_enabled? || crowd_enabled? + - if password_authentication_enabled? || ldap_enabled? || crowd_enabled? = render 'devise/shared/signin_box' -# Signup only makes sense if you can also sign-in - - if signin_enabled? && signup_enabled? + - if password_authentication_enabled? && signup_enabled? = render 'devise/shared/signup_box' -# Show a message if none of the mechanisms above are enabled - - if !signin_enabled? && !ldap_enabled? && !(omniauth_enabled? && devise_mapping.omniauthable?) + - if !password_authentication_enabled? && !ldap_enabled? && !(omniauth_enabled? && devise_mapping.omniauthable?) %div No authentication methods configured. diff --git a/app/views/devise/shared/_signin_box.html.haml b/app/views/devise/shared/_signin_box.html.haml index da4769e214e..3b06008febe 100644 --- a/app/views/devise/shared/_signin_box.html.haml +++ b/app/views/devise/shared/_signin_box.html.haml @@ -7,12 +7,12 @@ .login-box.tab-pane{ id: "#{server['provider_name']}", role: 'tabpanel', class: active_when(i.zero? && !crowd_enabled?) } .login-body = render 'devise/sessions/new_ldap', server: server - - if signin_enabled? + - if password_authentication_enabled? .login-box.tab-pane{ id: 'ldap-standard', role: 'tabpanel' } .login-body = render 'devise/sessions/new_base' -- elsif signin_enabled? +- elsif password_authentication_enabled? .login-box.tab-pane.active{ id: 'login-pane', role: 'tabpanel' } .login-body = render 'devise/sessions/new_base' diff --git a/app/views/devise/shared/_tabs_ldap.html.haml b/app/views/devise/shared/_tabs_ldap.html.haml index dd34600490e..6d0243a325d 100644 --- a/app/views/devise/shared/_tabs_ldap.html.haml +++ b/app/views/devise/shared/_tabs_ldap.html.haml @@ -5,9 +5,9 @@ - @ldap_servers.each_with_index do |server, i| %li{ class: active_when(i.zero? && !crowd_enabled?) } = link_to server['label'], "##{server['provider_name']}", 'data-toggle' => 'tab' - - if signin_enabled? + - if password_authentication_enabled? %li = link_to 'Standard', '#ldap-standard', 'data-toggle' => 'tab' - - if signin_enabled? && signup_enabled? + - if password_authentication_enabled? && signup_enabled? %li = link_to 'Register', '#register-pane', 'data-toggle' => 'tab' diff --git a/app/views/devise/shared/_tabs_normal.html.haml b/app/views/devise/shared/_tabs_normal.html.haml index c225d800a98..212856c0676 100644 --- a/app/views/devise/shared/_tabs_normal.html.haml +++ b/app/views/devise/shared/_tabs_normal.html.haml @@ -1,6 +1,6 @@ %ul.nav-links.new-session-tabs.nav-tabs{ role: 'tablist' } %li.active{ role: 'presentation' } %a{ href: '#login-pane', data: { toggle: 'tab' }, role: 'tab' } Sign in - - if signin_enabled? && signup_enabled? + - if password_authentication_enabled? && signup_enabled? %li{ role: 'presentation' } %a{ href: '#register-pane', data: { toggle: 'tab' }, role: 'tab' } Register diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml index ffe07b217a7..2651ef37e67 100644 --- a/app/views/explore/groups/index.html.haml +++ b/app/views/explore/groups/index.html.haml @@ -1,3 +1,4 @@ +- @hide_top_links = true - page_title "Groups" - header_title "Groups", dashboard_groups_path diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml index ec461755103..f00802e0af7 100644 --- a/app/views/explore/projects/index.html.haml +++ b/app/views/explore/projects/index.html.haml @@ -1,3 +1,4 @@ +- @hide_top_links = true - page_title "Projects" - header_title "Projects", dashboard_projects_path diff --git a/app/views/explore/projects/starred.html.haml b/app/views/explore/projects/starred.html.haml index ec461755103..f00802e0af7 100644 --- a/app/views/explore/projects/starred.html.haml +++ b/app/views/explore/projects/starred.html.haml @@ -1,3 +1,4 @@ +- @hide_top_links = true - page_title "Projects" - header_title "Projects", dashboard_projects_path diff --git a/app/views/explore/projects/trending.html.haml b/app/views/explore/projects/trending.html.haml index ec461755103..f00802e0af7 100644 --- a/app/views/explore/projects/trending.html.haml +++ b/app/views/explore/projects/trending.html.haml @@ -1,3 +1,4 @@ +- @hide_top_links = true - page_title "Projects" - header_title "Projects", dashboard_projects_path diff --git a/app/views/explore/snippets/index.html.haml b/app/views/explore/snippets/index.html.haml index e5706d04736..94fc4ac21d2 100644 --- a/app/views/explore/snippets/index.html.haml +++ b/app/views/explore/snippets/index.html.haml @@ -1,3 +1,4 @@ +- @hide_top_links = true - page_title "Snippets" - header_title "Snippets", snippets_path diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml index 182dbe2f98a..735d9390699 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -1,12 +1,19 @@ - page_title "Issues" +- group_issues_exists = group_issues(@group).exists? = render "head_issues" = content_for :meta_tags do = auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{@group.name} issues") -- if group_issues(@group).exists? +- if show_new_nav? && group_issues_exists + - content_for :breadcrumbs_extra do + = link_to params.merge(rss_url_options), class: 'btn btn-default append-right-10' do + = icon('rss') + = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue" + +- if group_issues_exists .top-area = render 'shared/issuable/nav', type: :issues - .nav-controls + .nav-controls{ class: ("visible-xs" if show_new_nav?) } = link_to params.merge(rss_url_options), class: 'btn' do = icon('rss') %span.icon-label diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml index 2bc00fb16c8..50179a47797 100644 --- a/app/views/groups/labels/index.html.haml +++ b/app/views/groups/labels/index.html.haml @@ -1,14 +1,18 @@ - page_title 'Labels' +- if show_new_nav? && can?(current_user, :admin_label, @group) + - content_for :breadcrumbs_extra do + = link_to "New label", new_group_label_path(@group), class: "btn btn-new" + = render "groups/head_issues" + .top-area.adjust .nav-text Labels can be applied to issues and merge requests. Group labels are available for any project within the group. - .nav-controls + .nav-controls{ class: ("visible-xs" if show_new_nav?) } - if can?(current_user, :admin_label, @group) - = link_to new_group_label_path(@group), class: "btn btn-new" do - New label + = link_to "New label", new_group_label_path(@group), class: "btn btn-new" .labels .other-labels diff --git a/app/views/groups/labels/new.html.haml b/app/views/groups/labels/new.html.haml index 2be87460b1d..ae240490bbd 100644 --- a/app/views/groups/labels/new.html.haml +++ b/app/views/groups/labels/new.html.haml @@ -1,3 +1,4 @@ +- breadcrumb_title "Labels" - page_title 'New Label' - header_title group_title(@group, 'Labels', group_labels_path(@group)) diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml index 45e39252e16..997c82c77d9 100644 --- a/app/views/groups/merge_requests.html.haml +++ b/app/views/groups/merge_requests.html.haml @@ -1,12 +1,16 @@ - page_title "Merge Requests" +- if show_new_nav? && current_user + - content_for :breadcrumbs_extra do + = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request" + - if @group_merge_requests.empty? = render 'shared/empty_states/merge_requests', project_select_button: true - else .top-area = render 'shared/issuable/nav', type: :merge_requests - if current_user - .nav-controls + .nav-controls{ class: ("visible-xs" if show_new_nav?) } = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request" = render 'shared/issuable/filter', type: :merge_requests diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml index 6ceb4092307..66c6cc9e279 100644 --- a/app/views/groups/milestones/index.html.haml +++ b/app/views/groups/milestones/index.html.haml @@ -1,13 +1,16 @@ - page_title "Milestones" +- if show_new_nav? && can?(current_user, :admin_milestones, @group) + - content_for :breadcrumbs_extra do + = link_to "New milestone", new_group_milestone_path(@group), class: "btn btn-new" + = render "groups/head_issues" .top-area = render 'shared/milestones_filter', counts: @milestone_states - .nav-controls + .nav-controls{ class: ("visible-xs" if show_new_nav?) } - if can?(current_user, :admin_milestones, @group) - = link_to new_group_milestone_path(@group), class: "btn btn-new" do - New milestone + = link_to "New milestone", new_group_milestone_path(@group), class: "btn btn-new" .milestones %ul.content-list diff --git a/app/views/groups/milestones/new.html.haml b/app/views/groups/milestones/new.html.haml index e24844661ee..eca7fb9ddb1 100644 --- a/app/views/groups/milestones/new.html.haml +++ b/app/views/groups/milestones/new.html.haml @@ -1,3 +1,4 @@ +- breadcrumb_title "Milestones" - page_title "Milestones" - header_title group_title(@group, "Milestones", group_milestones_path(@group)) diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml index 000c7af2326..e9daac95ca1 100644 --- a/app/views/groups/new.html.haml +++ b/app/views/groups/new.html.haml @@ -1,3 +1,6 @@ +- @breadcrumb_link = dashboard_groups_path +- breadcrumb_title "Groups" +- @hide_top_links = true - page_title 'New Group' - header_title "Groups", dashboard_groups_path diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 80a8ba4a755..e07f61c94e4 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -1,4 +1,5 @@ - @no_container = true +- breadcrumb_title "Group" = content_for :meta_tags do = auto_discovery_link_tag(:atom, group_url(@group, rss_url_options), title: "#{@group.name} activity") diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml index 615dd56afbd..48edbb8c16f 100644 --- a/app/views/help/ui.html.haml +++ b/app/views/help/ui.html.haml @@ -525,7 +525,7 @@ %h4 %code .file-holder - - blob = Snippet.new(content: "Wow\nSuch\nFile") + - blob = Snippet.new(content: "Wow\nSuch\nFile").blob .example .file-holder .js-file-title.file-title diff --git a/app/views/layouts/_broadcast.html.haml b/app/views/layouts/_broadcast.html.haml index bcd2f03e83c..e2dbdcbb939 100644 --- a/app/views/layouts/_broadcast.html.haml +++ b/app/views/layouts/_broadcast.html.haml @@ -1,2 +1,2 @@ -- BroadcastMessage.current.each do |message| +- BroadcastMessage.current&.each do |message| = broadcast_message(message) diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index cc9219cb6fe..e90197320f2 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -15,7 +15,8 @@ - if show_new_nav? - if content_for?(:new_global_flash) = yield :new_global_flash - = render "layouts/nav/breadcrumbs" + - unless @hide_breadcrumbs + = render "layouts/nav/breadcrumbs" = render "layouts/flash" = yield :flash_message %div{ class: "#{(container_class unless @no_container)} #{@content_class}" } diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 81f83b74826..38b95d11fd4 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -1,7 +1,7 @@ !!! 5 %html{ lang: I18n.locale, class: "#{page_class}" } = render "layouts/head" - %body{ class: @body_class, data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}" } } + %body{ class: @body_class, data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}", find_file: find_file_path } } = render "layouts/init_auto_complete" if @gfm_form - if show_new_nav? = render "layouts/header/new" diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index ed44263741e..bc3293fd100 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -75,7 +75,7 @@ %li = link_to "Settings", profile_path %li - = link_to "Turn on new nav", profile_preferences_path(anchor: "new-navigation") + = link_to "Turn on new navigation", profile_preferences_path(anchor: "new-navigation") %li.divider %li = link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link" @@ -91,8 +91,3 @@ = yield :header_content = render 'shared/outdated_browser' - -- if @project && !@project.empty_repo? - - if ref = @ref || @project.repository.root_ref - :javascript - var findFileURL = "#{project_find_file_path(@project, ref)}"; diff --git a/app/views/layouts/header/_new.html.haml b/app/views/layouts/header/_new.html.haml index bee7291da45..4697d91724b 100644 --- a/app/views/layouts/header/_new.html.haml +++ b/app/views/layouts/header/_new.html.haml @@ -69,7 +69,7 @@ %li = link_to "Settings", profile_path %li - = link_to "Turn off new nav", profile_preferences_path(anchor: "new-navigation") + = link_to "Turn off new navigation", profile_preferences_path(anchor: "new-navigation") %li.divider %li = link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link" @@ -84,8 +84,3 @@ = icon('times', class: 'js-navbar-toggle-left', style: 'display: none;') = render 'shared/outdated_browser' - -- if @project && !@project.empty_repo? - - if ref = @ref || @project.repository.root_ref - :javascript - var findFileURL = "#{project_find_file_path(@project, ref)}"; diff --git a/app/views/layouts/nav/_breadcrumbs.html.haml b/app/views/layouts/nav/_breadcrumbs.html.haml index 5f1641f4300..9aed0efae1c 100644 --- a/app/views/layouts/nav/_breadcrumbs.html.haml +++ b/app/views/layouts/nav/_breadcrumbs.html.haml @@ -1,19 +1,23 @@ -- breadcrumb_title = @breadcrumb_title || controller.controller_name.humanize +- breadcrumb_link = breadcrumb_title_link - hide_top_links = @hide_top_links || false %nav.breadcrumbs{ role: "navigation" } - .breadcrumbs-container{ class: container_class } + .breadcrumbs-container{ class: [container_class, @content_class] } .breadcrumbs-links.js-title-container - unless hide_top_links .title = link_to "GitLab", root_path \/ + - if content_for?(:header_title_before) + = yield :header_title_before + \/ = header_title %h2.breadcrumbs-sub-title %ul.list-unstyled - - if content_for?(:sub_title_before) - = yield :sub_title_before - %li= link_to breadcrumb_title, request.path + - if @breadcrumbs_extra_links + - @breadcrumbs_extra_links.each do |extra| + %li= link_to extra[:text], extra[:link] + %li= link_to @breadcrumb_title, breadcrumb_link - if content_for?(:breadcrumbs_extra) .breadcrumbs-extra.hidden-xs= yield :breadcrumbs_extra = yield :header_content diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index ac222ad8c82..be7d27df2a0 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -42,18 +42,18 @@ .key = icon('arrow-up', 'aria-label' => 'hidden') I + %span.badge.pull-right= number_with_delimiter(assigned_issuables_count(:issues)) %span Issues - .badge= number_with_delimiter(assigned_issuables_count(:issues)) = nav_link(path: 'dashboard#merge_requests') do = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do .shortcut-mappings .key = icon('arrow-up', 'aria-label' => 'hidden') M + %span.badge.pull-right= number_with_delimiter(assigned_issuables_count(:merge_requests)) %span Merge Requests - .badge= number_with_delimiter(assigned_issuables_count(:merge_requests)) = nav_link(controller: 'dashboard/snippets') do = link_to dashboard_snippets_path, class: 'dashboard-shortcuts-snippets', title: 'Snippets' do .shortcut-mappings diff --git a/app/views/layouts/nav/_new_admin_sidebar.html.haml b/app/views/layouts/nav/_new_admin_sidebar.html.haml index d7a9e530983..5cc636e89ef 100644 --- a/app/views/layouts/nav/_new_admin_sidebar.html.haml +++ b/app/views/layouts/nav/_new_admin_sidebar.html.haml @@ -13,7 +13,7 @@ = nav_link(controller: :dashboard, html_options: {class: 'home'}) do = link_to admin_root_path, title: 'Overview' do %span - Overview + Dashboard = nav_link(controller: [:admin, :projects]) do = link_to admin_projects_path, title: 'Projects' do %span diff --git a/app/views/layouts/nav/_new_dashboard.html.haml b/app/views/layouts/nav/_new_dashboard.html.haml index 7109baa4dad..cfdfcbebc9f 100644 --- a/app/views/layouts/nav/_new_dashboard.html.haml +++ b/app/views/layouts/nav/_new_dashboard.html.haml @@ -3,7 +3,7 @@ = link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do Projects - = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do + = nav_link(controller: ['dashboard/groups', 'explore/groups']) do = link_to dashboard_groups_path, class: 'dashboard-shortcuts-groups', title: 'Groups' do Groups diff --git a/app/views/layouts/nav/_new_group_sidebar.html.haml b/app/views/layouts/nav/_new_group_sidebar.html.haml index 7b68d11041e..6e0c45739f1 100644 --- a/app/views/layouts/nav/_new_group_sidebar.html.haml +++ b/app/views/layouts/nav/_new_group_sidebar.html.haml @@ -6,15 +6,15 @@ = @group.name %ul.sidebar-top-level-items = nav_link(path: ['groups#show', 'groups#activity', 'groups#subgroups'], html_options: { class: 'home' }) do - = link_to group_path(@group), title: 'Home' do + = link_to group_path(@group), title: 'About group' do %span - Group + About %ul.sidebar-sub-level-items = nav_link(path: ['groups#show', 'groups#subgroups'], html_options: { class: 'home' }) do - = link_to group_path(@group), title: 'Group Home' do + = link_to group_path(@group), title: 'Group details' do %span - Home + Details = nav_link(path: 'groups#activity') do = link_to activity_group_path(@group), title: 'Activity' do @@ -55,7 +55,22 @@ %span Members - if current_user && can?(current_user, :admin_group, @group) - = nav_link(path: %w[groups#projects groups#edit]) do + = nav_link(path: %w[groups#projects groups#edit ci_cd#show]) do = link_to edit_group_path(@group), title: 'Settings' do %span Settings + %ul.sidebar-sub-level-items + = nav_link(path: 'groups#edit') do + = link_to edit_group_path(@group), title: 'General' do + %span + General + + = nav_link(path: 'groups#projects') do + = link_to projects_group_path(@group), title: 'Projects' do + %span + Projects + + = nav_link(controller: :ci_cd) do + = link_to group_settings_ci_cd_path(@group), title: 'Pipelines' do + %span + Pipelines diff --git a/app/views/layouts/nav/_new_project_sidebar.html.haml b/app/views/layouts/nav/_new_project_sidebar.html.haml index cc731db3cc1..882123c0b0a 100644 --- a/app/views/layouts/nav/_new_project_sidebar.html.haml +++ b/app/views/layouts/nav/_new_project_sidebar.html.haml @@ -7,14 +7,14 @@ = @project.name %ul.sidebar-top-level-items = nav_link(path: ['projects#show', 'projects#activity', 'cycle_analytics#show'], html_options: { class: 'home' }) do - = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do + = link_to project_path(@project), title: 'About project', class: 'shortcuts-project' do %span - Project + About %ul.sidebar-sub-level-items = nav_link(path: 'projects#show') do - = link_to project_path(@project), title: _('Project home'), class: 'shortcuts-project' do - %span= _('Home') + = link_to project_path(@project), title: _('Project details'), class: 'shortcuts-project' do + %span= _('Details') = nav_link(path: 'projects#activity') do = link_to activity_project_path(@project), title: _('Activity'), class: 'shortcuts-project-activity' do @@ -74,9 +74,9 @@ = nav_link(controller: @project.default_issues_tracker? ? [:issues, :labels, :milestones, :boards] : :issues) do = link_to project_issues_path(@project), title: 'Issues', class: 'shortcuts-issues' do %span - Issues - if @project.default_issues_tracker? %span.badge.count.issue_counter= number_with_delimiter(IssuesFinder.new(current_user, project_id: @project.id).execute.opened.count) + Issues %ul.sidebar-sub-level-items - if project_nav_tab?(:issues) && !current_controller?(:merge_requests) @@ -112,8 +112,8 @@ = nav_link(controller: @project.default_issues_tracker? ? :merge_requests : [:merge_requests, :labels, :milestones]) do = link_to project_merge_requests_path(@project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do %span - Merge Requests %span.badge.count.merge_counter.js-merge-counter= number_with_delimiter(MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.count) + Merge Requests - if project_nav_tab? :pipelines = nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :environments, :artifacts]) do @@ -165,7 +165,7 @@ Snippets - if project_nav_tab? :settings - = nav_link(path: %w[projects#edit members#show integrations#show services#edit repository#show ci_cd#show pages#show]) do + = nav_link(path: %w[projects#edit project_members#index integrations#show services#edit repository#show ci_cd#show pages#show]) do = link_to edit_project_path(@project), title: 'Settings', class: 'shortcuts-tree' do %span Settings @@ -177,8 +177,8 @@ = link_to edit_project_path(@project), title: 'General' do %span General - = nav_link(controller: :members) do - = link_to project_settings_members_path(@project), title: 'Members' do + = nav_link(controller: :project_members) do + = link_to project_project_members_path(@project), title: 'Members' do %span Members - if can_edit diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml index ae1e1361f0f..424905ea890 100644 --- a/app/views/layouts/nav/_profile.html.haml +++ b/app/views/layouts/nav/_profile.html.haml @@ -29,7 +29,7 @@ = link_to profile_emails_path, title: 'Emails' do %span Emails - - unless current_user.ldap_user? + - if current_user.allow_password_authentication? = nav_link(controller: :passwords) do = link_to edit_profile_password_path, title: 'Password' do %span diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index bd602071384..9aed498a8a0 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -24,6 +24,12 @@ %p This setting allows you to turn on or off the new upcoming navigation concept. .col-lg-8.syntax-theme + .nav-wip + %p + The new navigation is currently a work-in-progress concept and is currently only usable on wide-screens. There are a number of improvements that we are working on in order to further refine our navigation. + %p + %a{ href: 'https://gitlab.com/gitlab-org/gitlab-ce/issues/32794', target: 'blank' } Learn more + about the improvements that are coming soon! = label_tag do .preview= image_tag "old_nav.png" %input.js-experiment-feature-toggle{ type: "radio", value: "false", name: "new_nav", checked: !show_new_nav? } diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index bac75a49075..a8ae0b92334 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -1,3 +1,4 @@ +- breadcrumb_title "Profile" - @content_class = "limit-container-width" unless fluid_layout = render 'profiles/head' diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml index 67792de3870..037cb30efb9 100644 --- a/app/views/profiles/two_factor_auths/show.html.haml +++ b/app/views/profiles/two_factor_auths/show.html.haml @@ -1,6 +1,10 @@ - page_title 'Two-Factor Authentication', 'Account' -- header_title "Two-Factor Authentication", profile_two_factor_auth_path +- if show_new_nav? + - add_to_breadcrumbs("Account", profile_account_path) +- else + - header_title "Two-Factor Authentication", profile_two_factor_auth_path - @content_class = "limit-container-width" unless fluid_layout + = render 'profiles/head' - if inject_u2f_api? diff --git a/app/views/projects/activity.html.haml b/app/views/projects/activity.html.haml index ef8d8051cbf..9e2688e492e 100644 --- a/app/views/projects/activity.html.haml +++ b/app/views/projects/activity.html.haml @@ -1,5 +1,8 @@ - @no_container = true +- if show_new_nav? + - add_to_breadcrumbs("Project", project_path(@project)) + - page_title "Activity" = render "projects/head" diff --git a/app/views/projects/artifacts/browse.html.haml b/app/views/projects/artifacts/browse.html.haml index 576e5b385af..a33743c2f57 100644 --- a/app/views/projects/artifacts/browse.html.haml +++ b/app/views/projects/artifacts/browse.html.haml @@ -5,12 +5,6 @@ .tree-holder .nav-block - .tree-controls - = link_to download_project_job_artifacts_path(@project, @build), - rel: 'nofollow', download: '', class: 'btn btn-default download' do - = icon('download') - Download artifacts archive - %ul.breadcrumb.repo-breadcrumb %li = link_to 'Artifacts', browse_project_job_artifacts_path(@project, @build) @@ -18,6 +12,12 @@ %li = link_to truncate(title, length: 40), browse_project_job_artifacts_path(@project, @build, path) + .tree-controls + = link_to download_project_job_artifacts_path(@project, @build), + rel: 'nofollow', download: '', class: 'btn btn-default download' do + = icon('download') + Download artifacts archive + .tree-content-holder %table.table.tree-table %thead diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml index f8cb612a2b4..992fe7f717f 100644 --- a/app/views/projects/blob/edit.html.haml +++ b/app/views/projects/blob/edit.html.haml @@ -1,3 +1,4 @@ +- breadcrumb_title "Repository" - @no_container = true - page_title "Edit", @blob.path, @ref - content_for :page_specific_javascripts do diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml index 8620a470041..a4263774dfd 100644 --- a/app/views/projects/blob/new.html.haml +++ b/app/views/projects/blob/new.html.haml @@ -1,3 +1,4 @@ +- breadcrumb_title "Repository" - page_title "New File", @path.presence, @ref - content_for :page_specific_javascripts do = page_specific_javascript_tag('lib/ace.js') diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml index 6e2ae4717cd..7dd834e84b5 100644 --- a/app/views/projects/blob/show.html.haml +++ b/app/views/projects/blob/show.html.haml @@ -1,3 +1,4 @@ +- breadcrumb_title "Repository" - @no_container = true - page_title @blob.path, @ref diff --git a/app/views/projects/boards/_show.html.haml b/app/views/projects/boards/_show.html.haml index 07272ea2df1..2076e46fde8 100644 --- a/app/views/projects/boards/_show.html.haml +++ b/app/views/projects/boards/_show.html.haml @@ -3,8 +3,7 @@ - page_title "Boards" - if show_new_nav? - - content_for :sub_title_before do - %li= link_to "Issues", project_issues_path(@project) + - add_to_breadcrumbs("Issues", project_issues_path(@project)) - content_for :page_specific_javascripts do = webpack_bundle_tag 'common_vue' diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml index 8bc1996452b..945a5c11d6d 100644 --- a/app/views/projects/branches/index.html.haml +++ b/app/views/projects/branches/index.html.haml @@ -2,11 +2,15 @@ - page_title "Branches" = render "projects/commits/head" +- if show_new_nav? + - add_to_breadcrumbs("Repository", project_tree_path(@project)) + %div{ class: container_class } .top-area.adjust - .nav-text - Protected branches can be managed in - = link_to 'project settings', project_protected_branches_path(@project) + - if can?(current_user, :admin_project, @project) + .nav-text + Protected branches can be managed in + = link_to 'project settings', project_protected_branches_path(@project) .nav-controls = form_tag(filter_branches_path, method: :get) do diff --git a/app/views/projects/commit/_ci_menu.html.haml b/app/views/projects/commit/_ci_menu.html.haml index f3f11b5b405..7338468967f 100644 --- a/app/views/projects/commit/_ci_menu.html.haml +++ b/app/views/projects/commit/_ci_menu.html.haml @@ -7,4 +7,4 @@ = nav_link(path: 'commit#pipelines') do = link_to pipelines_project_commit_path(@project, @commit.id) do Pipelines - %span.badge= @commit.pipelines.size + %span.badge.js-pipelines-mr-count= @commit.pipelines.size diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 572c368990e..45109f2c58b 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -30,7 +30,7 @@ %ul.dropdown-menu.dropdown-menu-align-right %li.visible-xs-block.visible-sm-block = link_to project_tree_path(@project, @commit) do - _('Browse Files') + #{ _('Browse Files') } - unless @commit.has_been_reverted?(current_user) %li.clearfix = revert_commit_link(@commit, project_commit_path(@project, @commit.id), has_tooltip: false) diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml index b778e8af121..07c83c0a590 100644 --- a/app/views/projects/commit/show.html.haml +++ b/app/views/projects/commit/show.html.haml @@ -1,6 +1,7 @@ - @no_container = true - container_class = !fluid_layout && diff_view == :inline ? 'container-limited' : '' - limited_container_width = fluid_layout ? '' : 'limit-container-width' +- @content_class = limited_container_width - page_title "#{@commit.title} (#{@commit.short_id})", "Commits" - page_description @commit.description = render "projects/commits/head" diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml index b8547c10c73..844ebb65148 100644 --- a/app/views/projects/commits/show.html.haml +++ b/app/views/projects/commits/show.html.haml @@ -1,9 +1,13 @@ - @no_container = true +- breadcrumb_title _("Commits") - page_title _("Commits"), @ref = content_for :meta_tags do = auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits") +- if show_new_nav? + - add_to_breadcrumbs("Repository", project_tree_path(@project)) + = content_for :sub_nav do = render "head" diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml index 2cf14859f30..05de21e8dbf 100644 --- a/app/views/projects/compare/index.html.haml +++ b/app/views/projects/compare/index.html.haml @@ -1,5 +1,7 @@ - @no_container = true - page_title "Compare" +- if show_new_nav? + - add_to_breadcrumbs("Repository", project_tree_path(@project)) = render "projects/commits/head" %div{ class: container_class } diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml index a1bca2cf83a..8bc863f77b3 100644 --- a/app/views/projects/compare/show.html.haml +++ b/app/views/projects/compare/show.html.haml @@ -1,5 +1,8 @@ - @no_container = true +- breadcrumb_title "Compare" - page_title "#{params[:from]}...#{params[:to]}" +- if show_new_nav? + - add_to_breadcrumbs("Repository", project_tree_path(@project)) = render "projects/commits/head" %div{ class: container_class } diff --git a/app/views/projects/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml index 7000b289f75..c704635ead3 100644 --- a/app/views/projects/cycle_analytics/show.html.haml +++ b/app/views/projects/cycle_analytics/show.html.haml @@ -1,5 +1,7 @@ - @no_container = true - page_title "Cycle Analytics" +- if show_new_nav? + - add_to_breadcrumbs("Project", project_path(@project)) - content_for :page_specific_javascripts do = page_specific_javascript_bundle_tag('common_vue') = page_specific_javascript_bundle_tag('cycle_analytics') @@ -9,8 +11,8 @@ #cycle-analytics{ class: container_class, "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project) } } - if @cycle_analytics_no_data .landing.content-block{ "v-if" => "!isOverviewDialogDismissed" } - %button.dismiss-button{ type: 'button', 'aria-label': 'Dismiss Cycle Analytics introduction box' } - = icon("times", "@click" => "dismissOverviewDialog()") + %button.dismiss-button{ type: 'button', 'aria-label': 'Dismiss Cycle Analytics introduction box', "@click" => "dismissOverviewDialog()" } + = icon("times") .svg-container = custom_icon('icon_cycle_analytics_splash') .inner-content diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index 30cdbc5ae04..d0f723af5bf 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -2,6 +2,9 @@ - page_title "Environments" = render "projects/pipelines/head" +- if show_new_nav? + - add_to_breadcrumbs("Pipelines", project_pipelines_path(@project)) + - content_for :page_specific_javascripts do = page_specific_javascript_bundle_tag('common_vue') = page_specific_javascript_bundle_tag("environments") diff --git a/app/views/projects/environments/new.html.haml b/app/views/projects/environments/new.html.haml index 24638c77cbb..88f43a1e7e4 100644 --- a/app/views/projects/environments/new.html.haml +++ b/app/views/projects/environments/new.html.haml @@ -1,4 +1,5 @@ - @no_container = true +- breadcrumb_title "Environments" - page_title 'New Environment' = render "projects/pipelines/head" diff --git a/app/views/projects/graphs/charts.html.haml b/app/views/projects/graphs/charts.html.haml index 464ac34d961..249b9d82ad9 100644 --- a/app/views/projects/graphs/charts.html.haml +++ b/app/views/projects/graphs/charts.html.haml @@ -1,5 +1,7 @@ - @no_container = true - page_title "Charts" +- if show_new_nav? + - add_to_breadcrumbs("Repository", project_tree_path(@project)) - content_for :page_specific_javascripts do = page_specific_javascript_bundle_tag('common_d3') = page_specific_javascript_bundle_tag('graphs') diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml index 640e0d689ca..4256a8c4d7e 100644 --- a/app/views/projects/graphs/show.html.haml +++ b/app/views/projects/graphs/show.html.haml @@ -3,6 +3,10 @@ - content_for :page_specific_javascripts do = page_specific_javascript_bundle_tag('common_d3') = page_specific_javascript_bundle_tag('graphs') + +- if show_new_nav? + - add_to_breadcrumbs("Repository", project_tree_path(@project)) + = render 'projects/commits/head' %div{ class: container_class } diff --git a/app/views/projects/issues/new.html.haml b/app/views/projects/issues/new.html.haml index e8aae0f47e2..60fe442014f 100644 --- a/app/views/projects/issues/new.html.haml +++ b/app/views/projects/issues/new.html.haml @@ -1,3 +1,4 @@ +- breadcrumb_title "Issues" - page_title "New Issue" %h3.page-title diff --git a/app/views/projects/jobs/_header.html.haml b/app/views/projects/jobs/_header.html.haml index d81b8f6bb4c..83a2af1dc74 100644 --- a/app/views/projects/jobs/_header.html.haml +++ b/app/views/projects/jobs/_header.html.haml @@ -1,7 +1,7 @@ - show_controls = local_assigns.fetch(:show_controls, true) - pipeline = @build.pipeline -.content-block.build-header.top-area +.content-block.build-header.top-area.page-content-header .header-content = render 'ci/status/badge', status: @build.detailed_status(current_user), link: false, title: @build.status_title %strong diff --git a/app/views/projects/jobs/_sidebar.html.haml b/app/views/projects/jobs/_sidebar.html.haml index bddb587ddc6..f2db71e8838 100644 --- a/app/views/projects/jobs/_sidebar.html.haml +++ b/app/views/projects/jobs/_sidebar.html.haml @@ -11,7 +11,7 @@ #js-details-block-vue - if can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?) - .block{ class: ("block-first" if !@build.coverage) } + .block .title Job artifacts - if @build.artifacts_expired? @@ -37,7 +37,7 @@ Browse - if @build.trigger_request - .build-widget + .build-widget.block %h4.title Trigger @@ -55,7 +55,7 @@ .js-build-variable.trigger-build-variable= key .js-build-value.trigger-build-value= value - .block + %div{ class: (@build.pipeline.stages_count > 1 ? "block" : "block-last") } %p Commit = link_to @build.pipeline.short_sha, project_commit_path(@project, @build.pipeline.sha), class: 'commit-sha link-commit' @@ -69,7 +69,7 @@ - if @build.pipeline.stages_count > 1 .dropdown.build-dropdown - .title + %div %span{ class: "ci-status-icon-#{@build.pipeline.status}" } = ci_icon_for_status(@build.pipeline.status) Pipeline diff --git a/app/views/projects/jobs/index.html.haml b/app/views/projects/jobs/index.html.haml index 8604c7d3ea4..d78891546f7 100644 --- a/app/views/projects/jobs/index.html.haml +++ b/app/views/projects/jobs/index.html.haml @@ -2,6 +2,9 @@ - page_title "Jobs" = render "projects/pipelines/head" +- if show_new_nav? + - add_to_breadcrumbs("Pipelines", project_pipelines_path(@project)) + %div{ class: container_class } .top-area - build_path_proc = ->(scope) { project_jobs_path(@project, scope: scope) } diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index 8fbc4588902..d02ea5cccc3 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -1,6 +1,11 @@ - @no_container = true - page_title "Labels" - hide_class = '' + +- if show_new_nav? && can?(current_user, :admin_label, @project) + - content_for :breadcrumbs_extra do + = link_to "New label", new_namespace_project_label_path(@project.namespace, @project), class: "btn btn-new" + = render "shared/mr_head" - if @labels.exists? || @prioritized_labels.exists? @@ -9,7 +14,7 @@ .nav-text Labels can be applied to issues and merge requests. Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging. - .nav-controls + .nav-controls{ class: ("visible-xs" if show_new_nav?) } - if can?(current_user, :admin_label, @project) = link_to new_project_label_path(@project), class: "btn btn-new" do New label diff --git a/app/views/projects/labels/new.html.haml b/app/views/projects/labels/new.html.haml index 79e90b7ca3b..562b6fb8d8c 100644 --- a/app/views/projects/labels/new.html.haml +++ b/app/views/projects/labels/new.html.haml @@ -1,4 +1,5 @@ - @no_container = true +- breadcrumb_title "Labels" - page_title "New Label" = render "shared/mr_head" diff --git a/app/views/projects/merge_requests/creations/new.html.haml b/app/views/projects/merge_requests/creations/new.html.haml index 2e798ce780a..3220512d60d 100644 --- a/app/views/projects/merge_requests/creations/new.html.haml +++ b/app/views/projects/merge_requests/creations/new.html.haml @@ -1,3 +1,4 @@ +- breadcrumb_title "Merge Requests" - page_title "New Merge Request" - if @merge_request.can_be_created && !params[:change_branches] diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml index 13012151349..2efc1d68190 100644 --- a/app/views/projects/merge_requests/show.html.haml +++ b/app/views/projects/merge_requests/show.html.haml @@ -47,7 +47,7 @@ %li.pipelines-tab = link_to pipelines_project_merge_request_path(@project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do Pipelines - %span.badge= @pipelines.size + %span.badge.js-pipelines-mr-count= @pipelines.size %li.diffs-tab = link_to diffs_project_merge_request_path(@project, @merge_request), data: { target: 'div#diffs', action: 'diffs', toggle: 'tab' } do Changes diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml index e53fcd6e425..a89387bc8f1 100644 --- a/app/views/projects/milestones/index.html.haml +++ b/app/views/projects/milestones/index.html.haml @@ -1,5 +1,10 @@ - @no_container = true - page_title 'Milestones' + +- if show_new_nav? + - content_for :breadcrumbs_extra do + = link_to "New milestone", new_namespace_project_milestone_path(@project.namespace, @project), class: 'btn btn-new', title: 'New milestone' + = render "shared/mr_head" %div{ class: container_class } diff --git a/app/views/projects/milestones/new.html.haml b/app/views/projects/milestones/new.html.haml index 586eb909afa..84ffbc0a926 100644 --- a/app/views/projects/milestones/new.html.haml +++ b/app/views/projects/milestones/new.html.haml @@ -1,4 +1,5 @@ - @no_container = true +- breadcrumb_title "Milestones" - page_title "New Milestone" = render "shared/mr_head" diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml index e8c26636be9..ab948df4a3f 100644 --- a/app/views/projects/network/show.html.haml +++ b/app/views/projects/network/show.html.haml @@ -1,6 +1,9 @@ +- breadcrumb_title "Graph" - page_title "Graph", @ref - content_for :page_specific_javascripts do = page_specific_javascript_bundle_tag('network') +- if show_new_nav? + - add_to_breadcrumbs("Repository", project_tree_path(@project)) = render "projects/commits/head" = render "head" %div{ class: container_class } diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 7b8be58554a..a2d7a21d5f6 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -1,3 +1,6 @@ +- @breadcrumb_link = dashboard_projects_path +- breadcrumb_title "Projects" +- @hide_top_links = true - page_title 'New Project' - header_title "Projects", dashboard_projects_path - visibility_level = params.dig(:project, :visibility_level) || default_project_visibility @@ -9,8 +12,9 @@ .col-lg-3.profile-settings-sidebar %h4.prepend-top-0 New project - %p - Create or Import your project from popular Git services + - if import_sources_enabled? + %p + Create or Import your project from popular Git services .col-lg-9 = form_for @project, html: { class: 'new_project' } do |f| .row diff --git a/app/views/projects/pipeline_schedules/index.html.haml b/app/views/projects/pipeline_schedules/index.html.haml index 05fe80e5fed..8426b29bb14 100644 --- a/app/views/projects/pipeline_schedules/index.html.haml +++ b/app/views/projects/pipeline_schedules/index.html.haml @@ -1,9 +1,18 @@ +- breadcrumb_title "Schedules" + - content_for :page_specific_javascripts do = webpack_bundle_tag 'common_vue' = webpack_bundle_tag 'schedules_index' - @no_container = true - page_title _("Pipeline Schedules") + +- if show_new_nav? && can?(current_user, :create_pipeline_schedule, @project) + - content_for :breadcrumbs_extra do + = link_to _('New schedule'), new_namespace_project_pipeline_schedule_path(@project.namespace, @project), class: 'btn btn-create' + + - add_to_breadcrumbs("Pipelines", project_pipelines_path(@project)) + = render "projects/pipelines/head" %div{ class: container_class } @@ -12,9 +21,10 @@ - schedule_path_proc = ->(scope) { pipeline_schedules_path(@project, scope: scope) } = render "tabs", schedule_path_proc: schedule_path_proc, all_schedules: @all_schedules, scope: @scope - .nav-controls - = link_to new_project_pipeline_schedule_path(@project), class: 'btn btn-create' do - %span= _('New schedule') + - if can?(current_user, :create_pipeline_schedule, @project) + .nav-controls{ class: ("visible-xs" if show_new_nav?) } + = link_to new_project_pipeline_schedule_path(@project), class: 'btn btn-create' do + %span= _('New schedule') - if @schedules.present? %ul.content-list diff --git a/app/views/projects/pipeline_schedules/new.html.haml b/app/views/projects/pipeline_schedules/new.html.haml index 87390d4dd02..c7237cb96d8 100644 --- a/app/views/projects/pipeline_schedules/new.html.haml +++ b/app/views/projects/pipeline_schedules/new.html.haml @@ -1,5 +1,10 @@ +- breadcrumb_title "Schedules" +- @breadcrumb_link = namespace_project_pipeline_schedules_path(@project.namespace, @project) - page_title _("New Pipeline Schedule") +- if show_new_nav? + - add_to_breadcrumbs("Pipelines", project_pipelines_path(@project)) + %h3.page-title = _("Schedule a new pipeline") %hr diff --git a/app/views/projects/pipelines/charts.html.haml b/app/views/projects/pipelines/charts.html.haml index 78002e8cd64..fd3ad69d85d 100644 --- a/app/views/projects/pipelines/charts.html.haml +++ b/app/views/projects/pipelines/charts.html.haml @@ -1,5 +1,7 @@ - @no_container = true - page_title _("Charts"), _("Pipelines") +- if show_new_nav? + - add_to_breadcrumbs("Pipelines", project_pipelines_path(@project)) - content_for :page_specific_javascripts do = page_specific_javascript_bundle_tag('common_d3') = page_specific_javascript_bundle_tag('graphs') diff --git a/app/views/projects/pipelines/new.html.haml b/app/views/projects/pipelines/new.html.haml index 308f2611e02..c966df62856 100644 --- a/app/views/projects/pipelines/new.html.haml +++ b/app/views/projects/pipelines/new.html.haml @@ -1,3 +1,4 @@ +- breadcrumb_title "Pipelines" - page_title "New Pipeline" %h3.page-title diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 25153fd0b6f..9f7c5a315eb 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -1,5 +1,8 @@ - page_title "Members" +- if show_new_nav? + - add_to_breadcrumbs("Settings", edit_project_path(@project)) + .row.prepend-top-default .col-lg-12 %h4 diff --git a/app/views/projects/protected_branches/_branches_list.html.haml b/app/views/projects/protected_branches/_branches_list.html.haml index cf0db943865..5377d745371 100644 --- a/app/views/projects/protected_branches/_branches_list.html.haml +++ b/app/views/projects/protected_branches/_branches_list.html.haml @@ -1,28 +1,4 @@ -.panel.panel-default.protected-branches-list - - if @protected_branches.empty? - .panel-heading - %h3.panel-title - Protected branch (#{@protected_branches.size}) - %p.settings-message.text-center - There are currently no protected branches, protect a branch with the form above. - - else - - can_admin_project = can?(current_user, :admin_project, @project) +- can_admin_project = can?(current_user, :admin_project, @project) - %table.table.table-bordered - %colgroup - %col{ width: "25%" } - %col{ width: "30%" } - %col{ width: "25%" } - %col{ width: "20%" } - %thead - %tr - %th Protected branch (#{@protected_branches.size}) - %th Last commit - %th Allowed to merge - %th Allowed to push - - if can_admin_project - %th - %tbody - = render partial: 'projects/protected_branches/protected_branch', collection: @protected_branches, locals: { can_admin_project: can_admin_project} - - = paginate @protected_branches, theme: 'gitlab' += render layout: 'projects/protected_branches/shared/branches_list', locals: { can_admin_project: can_admin_project } do + = render partial: 'projects/protected_branches/protected_branch', collection: @protected_branches, locals: { can_admin_project: can_admin_project} diff --git a/app/views/projects/protected_branches/_create_protected_branch.html.haml b/app/views/projects/protected_branches/_create_protected_branch.html.haml index 99bc2516366..98d56a3e5c5 100644 --- a/app/views/projects/protected_branches/_create_protected_branch.html.haml +++ b/app/views/projects/protected_branches/_create_protected_branch.html.haml @@ -1,41 +1,14 @@ -= form_for [@project.namespace.becomes(Namespace), @project, @protected_branch] do |f| - .panel.panel-default - .panel-heading - %h3.panel-title - Protect a branch - .panel-body - .form-horizontal - = form_errors(@protected_branch) - .form-group - = f.label :name, class: 'col-md-2 text-right' do - Branch: - .col-md-10 - = render partial: "projects/protected_branches/dropdown", locals: { f: f } - .help-block - = link_to 'Wildcards', help_page_path('user/project/protected_branches', anchor: 'wildcard-protected-branches') - such as - %code *-stable - or - %code production/* - are supported - .form-group - %label.col-md-2.text-right{ for: 'merge_access_levels_attributes' } - Allowed to merge: - .col-md-10 - .merge_access_levels-container - = dropdown_tag('Select', - options: { toggle_class: 'js-allowed-to-merge wide', - dropdown_class: 'dropdown-menu-selectable capitalize-header', - data: { field_name: 'protected_branch[merge_access_levels_attributes][0][access_level]', input_id: 'merge_access_levels_attributes' }}) - .form-group - %label.col-md-2.text-right{ for: 'push_access_levels_attributes' } - Allowed to push: - .col-md-10 - .push_access_levels-container - = dropdown_tag('Select', - options: { toggle_class: 'js-allowed-to-push wide', - dropdown_class: 'dropdown-menu-selectable capitalize-header', - data: { field_name: 'protected_branch[push_access_levels_attributes][0][access_level]', input_id: 'push_access_levels_attributes' }}) +- content_for :merge_access_levels do + .merge_access_levels-container + = dropdown_tag('Select', + options: { toggle_class: 'js-allowed-to-merge wide', + dropdown_class: 'dropdown-menu-selectable capitalize-header', + data: { field_name: 'protected_branch[merge_access_levels_attributes][0][access_level]', input_id: 'merge_access_levels_attributes' }}) +- content_for :push_access_levels do + .push_access_levels-container + = dropdown_tag('Select', + options: { toggle_class: 'js-allowed-to-push wide', + dropdown_class: 'dropdown-menu-selectable capitalize-header', + data: { field_name: 'protected_branch[push_access_levels_attributes][0][access_level]', input_id: 'push_access_levels_attributes' }}) - .panel-footer - = f.submit 'Protect', class: 'btn-create btn', disabled: true += render 'projects/protected_branches/shared/create_protected_branch' diff --git a/app/views/projects/protected_branches/_index.html.haml b/app/views/projects/protected_branches/_index.html.haml index 5d2422bdf54..2f30fe33a97 100644 --- a/app/views/projects/protected_branches/_index.html.haml +++ b/app/views/projects/protected_branches/_index.html.haml @@ -1,26 +1,10 @@ -- expanded = Rails.env.test? - content_for :page_specific_javascripts do = page_specific_javascript_bundle_tag('protected_branches') -%section.settings - .settings-header - %h4 - Protected Branches - %button.btn.js-settings-toggle - = expanded ? 'Collapse' : 'Expand' - %p - Keep stable branches secure and force developers to use merge requests. - .settings-content.no-animate{ class: ('expanded' if expanded) } - %p - By default, protected branches are designed to: - %ul - %li prevent their creation, if not already created, from everybody except Masters - %li prevent pushes from everybody except Masters - %li prevent <strong>anyone</strong> from force pushing to the branch - %li prevent <strong>anyone</strong> from deleting the branch - %p Read more about #{link_to "protected branches", help_page_path("user/project/protected_branches"), class: "underlined-link"} and #{link_to "project permissions", help_page_path("user/permissions"), class: "underlined-link"}. +- content_for :create_protected_branch do + = render 'projects/protected_branches/create_protected_branch' - - if can? current_user, :admin_project, @project - = render 'projects/protected_branches/create_protected_branch' +- content_for :branches_list do + = render "projects/protected_branches/branches_list" - = render "projects/protected_branches/branches_list" += render 'projects/protected_branches/shared/index' diff --git a/app/views/projects/protected_branches/_protected_branch.html.haml b/app/views/projects/protected_branches/_protected_branch.html.haml index e4dadc42cc0..b12ae995ece 100644 --- a/app/views/projects/protected_branches/_protected_branch.html.haml +++ b/app/views/projects/protected_branches/_protected_branch.html.haml @@ -1,22 +1,2 @@ -%tr.js-protected-branch-edit-form{ data: { url: project_protected_branch_path(@project, protected_branch) } } - %td - %span.ref-name= protected_branch.name - - - if @project.root_ref?(protected_branch.name) - %span.label.label-info.prepend-left-5 default - %td - - if protected_branch.wildcard? - - matching_branches = protected_branch.matching(repository.branches) - = link_to pluralize(matching_branches.count, "matching branch"), project_protected_branch_path(@project, protected_branch) - - else - - if commit = protected_branch.commit - = link_to(commit.short_id, project_commit_path(@project, commit.id), class: 'commit-sha') - = time_ago_with_tooltip(commit.committed_date) - - else - (branch was removed from repository) - += render layout: 'projects/protected_branches/shared/protected_branch', locals: { protected_branch: protected_branch } do = render partial: 'projects/protected_branches/update_protected_branch', locals: { protected_branch: protected_branch } - - - if can_admin_project - %td - = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: 'btn btn-warning' diff --git a/app/views/projects/protected_branches/shared/_branches_list.html.haml b/app/views/projects/protected_branches/shared/_branches_list.html.haml new file mode 100644 index 00000000000..5c00bb6883c --- /dev/null +++ b/app/views/projects/protected_branches/shared/_branches_list.html.haml @@ -0,0 +1,28 @@ +.panel.panel-default.protected-branches-list + - if @protected_branches.empty? + .panel-heading + %h3.panel-title + Protected branch (#{@protected_branches.size}) + %p.settings-message.text-center + There are currently no protected branches, protect a branch with the form above. + - else + %table.table.table-bordered + %colgroup + %col{ width: "20%" } + %col{ width: "20%" } + %col{ width: "20%" } + %col{ width: "20%" } + - if can_admin_project + %col + %thead + %tr + %th Protected branch (#{@protected_branches.size}) + %th Last commit + %th Allowed to merge + %th Allowed to push + - if can_admin_project + %th + %tbody + = yield + + = paginate @protected_branches, theme: 'gitlab' diff --git a/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml b/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml new file mode 100644 index 00000000000..b619fa57e05 --- /dev/null +++ b/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml @@ -0,0 +1,33 @@ += form_for [@project.namespace.becomes(Namespace), @project, @protected_branch] do |f| + .panel.panel-default + .panel-heading + %h3.panel-title + Protect a branch + .panel-body + .form-horizontal + = form_errors(@protected_branch) + .form-group + = f.label :name, class: 'col-md-2 text-right' do + Branch: + .col-md-10 + = render partial: "projects/protected_branches/shared/dropdown", locals: { f: f } + .help-block + = link_to 'Wildcards', help_page_path('user/project/protected_branches', anchor: 'wildcard-protected-branches') + such as + %code *-stable + or + %code production/* + are supported + .form-group + %label.col-md-2.text-right{ for: 'merge_access_levels_attributes' } + Allowed to merge: + .col-md-10 + = yield :merge_access_levels + .form-group + %label.col-md-2.text-right{ for: 'push_access_levels_attributes' } + Allowed to push: + .col-md-10 + = yield :push_access_levels + + .panel-footer + = f.submit 'Protect', class: 'btn-create btn', disabled: true diff --git a/app/views/projects/protected_branches/_dropdown.html.haml b/app/views/projects/protected_branches/shared/_dropdown.html.haml index 6e9c473494e..6e9c473494e 100644 --- a/app/views/projects/protected_branches/_dropdown.html.haml +++ b/app/views/projects/protected_branches/shared/_dropdown.html.haml diff --git a/app/views/projects/protected_branches/shared/_index.html.haml b/app/views/projects/protected_branches/shared/_index.html.haml new file mode 100644 index 00000000000..6a47cbdf724 --- /dev/null +++ b/app/views/projects/protected_branches/shared/_index.html.haml @@ -0,0 +1,24 @@ +- expanded = Rails.env.test? + +%section.settings + .settings-header + %h4 + Protected Branches + %button.btn.js-settings-toggle + = expanded ? 'Collapse' : 'Expand' + %p + Keep stable branches secure and force developers to use merge requests. + .settings-content.no-animate{ class: ('expanded' if expanded) } + %p + By default, protected branches are designed to: + %ul + %li prevent their creation, if not already created, from everybody except Masters + %li prevent pushes from everybody except Masters + %li prevent <strong>anyone</strong> from force pushing to the branch + %li prevent <strong>anyone</strong> from deleting the branch + %p Read more about #{link_to "protected branches", help_page_path("user/project/protected_branches"), class: "underlined-link"} and #{link_to "project permissions", help_page_path("user/permissions"), class: "underlined-link"}. + + - if can? current_user, :admin_project, @project + = content_for :create_protected_branch + + = content_for :branches_list diff --git a/app/views/projects/protected_branches/_matching_branch.html.haml b/app/views/projects/protected_branches/shared/_matching_branch.html.haml index 98793d632e6..98793d632e6 100644 --- a/app/views/projects/protected_branches/_matching_branch.html.haml +++ b/app/views/projects/protected_branches/shared/_matching_branch.html.haml diff --git a/app/views/projects/protected_branches/shared/_protected_branch.html.haml b/app/views/projects/protected_branches/shared/_protected_branch.html.haml new file mode 100644 index 00000000000..10b81e42572 --- /dev/null +++ b/app/views/projects/protected_branches/shared/_protected_branch.html.haml @@ -0,0 +1,24 @@ +- can_admin_project = can?(current_user, :admin_project, @project) + +%tr.js-protected-branch-edit-form{ data: { url: namespace_project_protected_branch_path(@project.namespace, @project, protected_branch) } } + %td + %span.ref-name= protected_branch.name + + - if @project.root_ref?(protected_branch.name) + %span.label.label-info.prepend-left-5 default + %td + - if protected_branch.wildcard? + - matching_branches = protected_branch.matching(repository.branches) + = link_to pluralize(matching_branches.count, "matching branch"), namespace_project_protected_branch_path(@project.namespace, @project, protected_branch) + - else + - if commit = protected_branch.commit + = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit-sha') + = time_ago_with_tooltip(commit.committed_date) + - else + (branch was removed from repository) + + = yield + + - if can_admin_project + %td + = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: 'btn btn-warning' diff --git a/app/views/projects/protected_branches/show.html.haml b/app/views/projects/protected_branches/show.html.haml index a806a0756ec..1012ceefe93 100644 --- a/app/views/projects/protected_branches/show.html.haml +++ b/app/views/projects/protected_branches/show.html.haml @@ -19,7 +19,7 @@ %th Last commit %tbody - @matching_refs.each do |matching_branch| - = render partial: "matching_branch", object: matching_branch + = render partial: "projects/protected_branches/shared/matching_branch", object: matching_branch - else %p.settings-message.text-center Couldn't find any matching branches. diff --git a/app/views/projects/protected_tags/_create_protected_tag.html.haml b/app/views/projects/protected_tags/_create_protected_tag.html.haml index dd5b346d922..ea91e8af70e 100644 --- a/app/views/projects/protected_tags/_create_protected_tag.html.haml +++ b/app/views/projects/protected_tags/_create_protected_tag.html.haml @@ -1,32 +1,8 @@ -= form_for [@project.namespace.becomes(Namespace), @project, @protected_tag], html: { class: 'new-protected-tag js-new-protected-tag' } do |f| - .panel.panel-default - .panel-heading - %h3.panel-title - Protect a tag - .panel-body - .form-horizontal - = form_errors(@protected_tag) - .form-group - = f.label :name, class: 'col-md-2 text-right' do - Tag: - .col-md-10.protected-tags-dropdown - = render partial: "projects/protected_tags/dropdown", locals: { f: f } - .help-block - = link_to 'Wildcards', help_page_path('user/project/protected_tags', anchor: 'wildcard-protected-tags') - such as - %code v* - or - %code *-release - are supported - .form-group - %label.col-md-2.text-right{ for: 'create_access_levels_attributes' } - Allowed to create: - .col-md-10 - .create_access_levels-container - = dropdown_tag('Select', - options: { toggle_class: 'js-allowed-to-create wide', - dropdown_class: 'dropdown-menu-selectable', - data: { field_name: 'protected_tag[create_access_levels_attributes][0][access_level]', input_id: 'create_access_levels_attributes' }}) +- content_for :create_access_levels do + .create_access_levels-container + = dropdown_tag('Select', + options: { toggle_class: 'js-allowed-to-create wide', + dropdown_class: 'dropdown-menu-selectable', + data: { field_name: 'protected_tag[create_access_levels_attributes][0][access_level]', input_id: 'create_access_levels_attributes' }}) - .panel-footer - = f.submit 'Protect', class: 'btn-create btn', disabled: true += render 'projects/protected_tags/shared/create_protected_tag' diff --git a/app/views/projects/protected_tags/_index.html.haml b/app/views/projects/protected_tags/_index.html.haml index 8250f692a69..955220562a6 100644 --- a/app/views/projects/protected_tags/_index.html.haml +++ b/app/views/projects/protected_tags/_index.html.haml @@ -1,26 +1,10 @@ -- expanded = Rails.env.test? - content_for :page_specific_javascripts do = page_specific_javascript_bundle_tag('protected_tags') -%section.settings - .settings-header - %h4 - Protected Tags - %button.btn.js-settings-toggle - = expanded ? 'Collapse' : 'Expand' - %p - Limit access to creating and updating tags. - .settings-content.no-animate{ class: ('expanded' if expanded) } - %p - By default, protected tags are designed to: - %ul - %li Prevent tag creation by everybody except Masters - %li Prevent <strong>anyone</strong> from updating the tag - %li Prevent <strong>anyone</strong> from deleting the tag +- content_for :create_protected_tag do + = render 'projects/protected_tags/create_protected_tag' - %p Read more about #{link_to "protected tags", help_page_path("user/project/protected_tags"), class: "underlined-link"}. +- content_for :tag_list do + = render "projects/protected_tags/tags_list" - - if can? current_user, :admin_project, @project - = render 'projects/protected_tags/create_protected_tag' - - = render "projects/protected_tags/tags_list" += render 'projects/protected_tags/shared/index' diff --git a/app/views/projects/protected_tags/_protected_tag.html.haml b/app/views/projects/protected_tags/_protected_tag.html.haml index 5162da5e429..da1f97c8d6a 100644 --- a/app/views/projects/protected_tags/_protected_tag.html.haml +++ b/app/views/projects/protected_tags/_protected_tag.html.haml @@ -1,22 +1,2 @@ -%tr.js-protected-tag-edit-form{ data: { url: project_protected_tag_path(@project, protected_tag) } } - %td - %span.ref-name= protected_tag.name - - - if @project.root_ref?(protected_tag.name) - %span.label.label-info.prepend-left-5 default - %td - - if protected_tag.wildcard? - - matching_tags = protected_tag.matching(repository.tags) - = link_to pluralize(matching_tags.count, "matching tag"), project_protected_tag_path(@project, protected_tag) - - else - - if commit = protected_tag.commit - = link_to(commit.short_id, project_commit_path(@project, commit.id), class: 'commit-sha') - = time_ago_with_tooltip(commit.committed_date) - - else - (tag was removed from repository) - += render layout: 'projects/protected_tags/shared/protected_tag', locals: { protected_tag: protected_tag } do = render partial: 'projects/protected_tags/update_protected_tag', locals: { protected_tag: protected_tag } - - - if can_admin_project - %td - = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_tag], data: { confirm: 'Tag will be writable for developers. Are you sure?' }, method: :delete, class: 'btn btn-warning' diff --git a/app/views/projects/protected_tags/_tags_list.html.haml b/app/views/projects/protected_tags/_tags_list.html.haml index d432a5c9113..a6b18cc9f8f 100644 --- a/app/views/projects/protected_tags/_tags_list.html.haml +++ b/app/views/projects/protected_tags/_tags_list.html.haml @@ -1,30 +1,4 @@ -.panel.panel-default.protected-tags-list - - if @protected_tags.empty? - .panel-heading - %h3.panel-title - Protected tag (#{@protected_tags.size}) - %p.settings-message.text-center - There are currently no protected tags, protect a tag with the form above. - - else - - can_admin_project = can?(current_user, :admin_project, @project) +- can_admin_project = can?(current_user, :admin_project, @project) - %table.table.table-bordered - %colgroup - %col{ width: "25%" } - %col{ width: "25%" } - %col{ width: "50%" } - - if can_admin_project - %col - %thead - %tr - %th Protected tag (#{@protected_tags.size}) - %th Last commit - %th Allowed to create - - if can_admin_project - %th - %tbody - %tr - %td.flash-container{ colspan: 4 } - = render partial: 'projects/protected_tags/protected_tag', collection: @protected_tags, locals: { can_admin_project: can_admin_project} - - = paginate @protected_tags, theme: 'gitlab' += render layout: 'projects/protected_tags/shared/tags_list' do + = render partial: 'projects/protected_tags/protected_tag', collection: @protected_tags, locals: { can_admin_project: can_admin_project} diff --git a/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml b/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml new file mode 100644 index 00000000000..5a53c704fcb --- /dev/null +++ b/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml @@ -0,0 +1,29 @@ += form_for [@project.namespace.becomes(Namespace), @project, @protected_tag], html: { class: 'new-protected-tag js-new-protected-tag' } do |f| + .panel.panel-default + .panel-heading + %h3.panel-title + Protect a tag + .panel-body + .form-horizontal + = form_errors(@protected_tag) + .form-group + = f.label :name, class: 'col-md-2 text-right' do + Tag: + .col-md-10.protected-tags-dropdown + = render partial: "projects/protected_tags/shared/dropdown", locals: { f: f } + .help-block + = link_to 'Wildcards', help_page_path('user/project/protected_tags', anchor: 'wildcard-protected-tags') + such as + %code v* + or + %code *-release + are supported + .form-group + %label.col-md-2.text-right{ for: 'create_access_levels_attributes' } + Allowed to create: + .col-md-10 + .create_access_levels-container + = yield :create_access_levels + + .panel-footer + = f.submit 'Protect', class: 'btn-create btn', disabled: true diff --git a/app/views/projects/protected_tags/_dropdown.html.haml b/app/views/projects/protected_tags/shared/_dropdown.html.haml index 9b6923210f7..9b6923210f7 100644 --- a/app/views/projects/protected_tags/_dropdown.html.haml +++ b/app/views/projects/protected_tags/shared/_dropdown.html.haml diff --git a/app/views/projects/protected_tags/shared/_index.html.haml b/app/views/projects/protected_tags/shared/_index.html.haml new file mode 100644 index 00000000000..c07bd454ff6 --- /dev/null +++ b/app/views/projects/protected_tags/shared/_index.html.haml @@ -0,0 +1,24 @@ +- expanded = Rails.env.test? + +%section.settings + .settings-header + %h4 + Protected Tags + %button.btn.js-settings-toggle + = expanded ? 'Collapse' : 'Expand' + %p + Limit access to creating and updating tags. + .settings-content.no-animate{ class: ('expanded' if expanded) } + %p + By default, protected tags are designed to: + %ul + %li Prevent tag creation by everybody except Masters + %li Prevent <strong>anyone</strong> from updating the tag + %li Prevent <strong>anyone</strong> from deleting the tag + + %p Read more about #{link_to "protected tags", help_page_path("user/project/protected_tags"), class: "underlined-link"}. + + - if can? current_user, :admin_project, @project + = yield :create_protected_tag + + = yield :tag_list diff --git a/app/views/projects/protected_tags/_matching_tag.html.haml b/app/views/projects/protected_tags/shared/_matching_tag.html.haml index 05f102d1ca3..05f102d1ca3 100644 --- a/app/views/projects/protected_tags/_matching_tag.html.haml +++ b/app/views/projects/protected_tags/shared/_matching_tag.html.haml diff --git a/app/views/projects/protected_tags/shared/_protected_tag.html.haml b/app/views/projects/protected_tags/shared/_protected_tag.html.haml new file mode 100644 index 00000000000..c778f7b9781 --- /dev/null +++ b/app/views/projects/protected_tags/shared/_protected_tag.html.haml @@ -0,0 +1,22 @@ +%tr.js-protected-tag-edit-form{ data: { url: project_protected_tag_path(@project, protected_tag) } } + %td + %span.ref-name= protected_tag.name + + - if @project.root_ref?(protected_tag.name) + %span.label.label-info.prepend-left-5 default + %td + - if protected_tag.wildcard? + - matching_tags = protected_tag.matching(repository.tags) + = link_to pluralize(matching_tags.count, "matching tag"), project_protected_tag_path(@project, protected_tag) + - else + - if commit = protected_tag.commit + = link_to(commit.short_id, project_commit_path(@project, commit.id), class: 'commit-sha') + = time_ago_with_tooltip(commit.committed_date) + - else + (tag was removed from repository) + + = yield + + - if can? current_user, :admin_project, @project + %td + = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_tag], data: { confirm: 'Tag will be writable for developers. Are you sure?' }, method: :delete, class: 'btn btn-warning' diff --git a/app/views/projects/protected_tags/shared/_tags_list.html.haml b/app/views/projects/protected_tags/shared/_tags_list.html.haml new file mode 100644 index 00000000000..6e3cd4ada71 --- /dev/null +++ b/app/views/projects/protected_tags/shared/_tags_list.html.haml @@ -0,0 +1,30 @@ +.panel.panel-default.protected-tags-list + - if @protected_tags.empty? + .panel-heading + %h3.panel-title + Protected tag (#{@protected_tags.size}) + %p.settings-message.text-center + There are currently no protected tags, protect a tag with the form above. + - else + - can_admin_project = can?(current_user, :admin_project, @project) + + %table.table.table-bordered + %colgroup + %col{ width: "25%" } + %col{ width: "25%" } + %col{ width: "50%" } + - if can_admin_project + %col + %thead + %tr + %th Protected tag (#{@protected_tags.size}) + %th Last commit + %th Allowed to create + - if can_admin_project + %th + %tbody + %tr + %td.flash-container{ colspan: 4 } + = yield + + = paginate @protected_tags, theme: 'gitlab' diff --git a/app/views/projects/protected_tags/show.html.haml b/app/views/projects/protected_tags/show.html.haml index 16fc02fe9f4..86629f1753b 100644 --- a/app/views/projects/protected_tags/show.html.haml +++ b/app/views/projects/protected_tags/show.html.haml @@ -19,7 +19,7 @@ %th Last commit %tbody - @matching_refs.each do |matching_tag| - = render partial: "matching_tag", object: matching_tag + = render partial: "projects/protected_tags/shared/matching_tag", object: matching_tag - else %p.settings-message.text-center Couldn't find any matching tags. diff --git a/app/views/projects/services/edit.html.haml b/app/views/projects/services/edit.html.haml index 0f1a76a104a..8056217bb1e 100644 --- a/app/views/projects/services/edit.html.haml +++ b/app/views/projects/services/edit.html.haml @@ -1,3 +1,8 @@ +- breadcrumb_title "Integrations" - page_title @service.title, "Services" + +- if show_new_nav? + - add_to_breadcrumbs("Settings", edit_project_path(@project)) + = render "projects/settings/head" = render 'form' diff --git a/app/views/projects/services/prometheus/_show.html.haml b/app/views/projects/services/prometheus/_show.html.haml index 0996ec06ab7..d8e11500964 100644 --- a/app/views/projects/services/prometheus/_show.html.haml +++ b/app/views/projects/services/prometheus/_show.html.haml @@ -8,7 +8,7 @@ %p Metrics are automatically configured and monitored based on a library of metrics from popular exporters. - = link_to 'More information', '#' + = link_to 'More information', help_page_path('user/project/integrations/prometheus') .col-lg-9 .panel.panel-default.js-panel-monitored-metrics{ data: { "active-metrics" => "#{project_prometheus_active_metrics_path(@project, :json)}" } } @@ -41,5 +41,5 @@ %code $CI_ENVIRONMENT_SLUG to exporter’s queries. - = link_to 'More information', '#' + = link_to 'More information', help_page_path('user/project/integrations/prometheus', anchor: 'metrics-and-labels') %ul.list-unstyled.metrics-list.js-missing-var-metrics-list diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml index 6afb38c5709..0c4130857da 100644 --- a/app/views/projects/settings/ci_cd/show.html.haml +++ b/app/views/projects/settings/ci_cd/show.html.haml @@ -1,5 +1,9 @@ - @content_class = "limit-container-width" unless fluid_layout - page_title "Pipelines" + +- if show_new_nav? + - add_to_breadcrumbs("Settings", edit_project_path(@project)) + = render "projects/settings/head" = render 'projects/runners/index' diff --git a/app/views/projects/settings/integrations/show.html.haml b/app/views/projects/settings/integrations/show.html.haml index 1d1d0849289..149da96d3f6 100644 --- a/app/views/projects/settings/integrations/show.html.haml +++ b/app/views/projects/settings/integrations/show.html.haml @@ -1,5 +1,7 @@ - @content_class = "limit-container-width" unless fluid_layout - page_title 'Integrations' +- if show_new_nav? + - add_to_breadcrumbs("Settings", edit_project_path(@project)) = render "projects/settings/head" = render 'projects/hooks/index' = render 'projects/services/index' diff --git a/app/views/projects/settings/repository/show.html.haml b/app/views/projects/settings/repository/show.html.haml index 40ea02abce9..cb37f3c7580 100644 --- a/app/views/projects/settings/repository/show.html.haml +++ b/app/views/projects/settings/repository/show.html.haml @@ -1,11 +1,19 @@ - page_title "Repository" - @content_class = "limit-container-width" unless fluid_layout + +- if show_new_nav? + - add_to_breadcrumbs("Settings", edit_project_path(@project)) + = render "projects/settings/head" - content_for :page_specific_javascripts do = page_specific_javascript_bundle_tag('common_vue') = page_specific_javascript_bundle_tag('deploy_keys') +-# Protected branches & tags use a lot of nested partials. +-# The shared parts of the views can be found in the `shared` directory. +-# Those are used throughout the actual views. These `shared` views are then +-# reused in EE. = render "projects/protected_branches/index" = render "projects/protected_tags/index" = render @deploy_keys diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index d413c4619be..49d0a6828fe 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -1,4 +1,6 @@ - @no_container = true +- breadcrumb_title "Project" +- @content_class = "limit-container-width" unless fluid_layout - flash_message_container = show_new_nav? ? :new_global_flash : :flash_message = content_for :meta_tags do diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml index 4f8ce526c83..ccc5fe80755 100644 --- a/app/views/projects/snippets/index.html.haml +++ b/app/views/projects/snippets/index.html.haml @@ -1,19 +1,16 @@ - page_title "Snippets" +- if show_new_nav? && can?(current_user, :create_project_snippet, @project) + - content_for :breadcrumbs_extra do + = link_to "New snippet", new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New snippet" + - if current_user .top-area - include_private = @project.team.member?(current_user) || current_user.admin? = render partial: 'snippets/snippets_scope_menu', locals: { subject: @project, include_private: include_private } - .nav-controls.hidden-xs + .nav-controls{ class: ("visible-xs" if show_new_nav?) } - if can?(current_user, :create_project_snippet, @project) - = link_to new_project_snippet_path(@project), class: "btn btn-new", title: "New snippet" do - New snippet - -- if can?(current_user, :create_project_snippet, @project) - .visible-xs - - = link_to new_project_snippet_path(@project), class: "btn btn-new btn-block", title: "New snippet" do - New snippet + = link_to "New snippet", new_project_snippet_path(@project), class: "btn btn-new", title: "New snippet" = render 'snippets/snippets' diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml index bf97cbc1f68..00000e0667c 100644 --- a/app/views/projects/tags/index.html.haml +++ b/app/views/projects/tags/index.html.haml @@ -3,6 +3,9 @@ - page_title "Tags" = render "projects/commits/head" +- if show_new_nav? + - add_to_breadcrumbs("Repository", project_tree_path(@project)) + .flex-list{ class: container_class } .top-area.adjust .nav-text.row-main-content diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml index 130980556c1..c8587245f88 100644 --- a/app/views/projects/tree/show.html.haml +++ b/app/views/projects/tree/show.html.haml @@ -1,4 +1,6 @@ - @no_container = true +- breadcrumb_title _("Repository") +- @content_class = "limit-container-width" unless fluid_layout - page_title @path.presence || _("Files"), @ref = content_for :meta_tags do diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml index 13591dd8e74..9dadd685ea2 100644 --- a/app/views/projects/wikis/show.html.haml +++ b/app/views/projects/wikis/show.html.haml @@ -1,4 +1,5 @@ - @content_class = "limit-container-width limit-container-width-sm" unless fluid_layout +- breadcrumb_title "Wiki" - page_title @page.title.capitalize, "Wiki" .wiki-page-header.has-sidebar-toggle diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml index 215dbb3909e..499697f2777 100644 --- a/app/views/search/show.html.haml +++ b/app/views/search/show.html.haml @@ -1,3 +1,5 @@ +- @hide_top_links = true +- breadcrumb_title "Search" - page_title @search_term .prepend-top-default diff --git a/app/views/shared/_new_project_item_select.html.haml b/app/views/shared/_new_project_item_select.html.haml index 9ed844cf5e7..c1acee1a211 100644 --- a/app/views/shared/_new_project_item_select.html.haml +++ b/app/views/shared/_new_project_item_select.html.haml @@ -1,19 +1,6 @@ - if @projects.any? .project-item-select-holder = project_select_tag :project_path, class: "project-item-select", data: { include_groups: local_assigns[:include_groups], order_by: 'last_activity_at' }, with_feature_enabled: local_assigns[:with_feature_enabled] - %a.btn.btn-new.new-project-item-select-button + %a.btn.btn-new.new-project-item-select-button{ data: { relative_path: local_assigns[:path] } } = local_assigns[:label] = icon('caret-down') - - :javascript - $('.new-project-item-select-button').on('click', function() { - $('.project-item-select').select2('open'); - }); - - var relativePath = '#{local_assigns[:path]}'; - - $('.project-item-select').on('click', function() { - window.location = $(this).val() + '/' + relativePath; - }); - - new ProjectSelect() diff --git a/app/views/shared/_personal_access_tokens_table.html.haml b/app/views/shared/_personal_access_tokens_table.html.haml index ab7a2db002e..c5e4d6e2871 100644 --- a/app/views/shared/_personal_access_tokens_table.html.haml +++ b/app/views/shared/_personal_access_tokens_table.html.haml @@ -39,22 +39,3 @@ - else .settings-message.text-center This user has no active #{type} Tokens. - -%hr - -%h5 Inactive #{type} Tokens (#{inactive_tokens.length}) -- if inactive_tokens.present? - .table-responsive - %table.table.inactive-tokens - %thead - %tr - %th Name - %th Created - %tbody - - inactive_tokens.each do |token| - %tr - %td= token.name - %td= token.created_at.to_date.to_s(:medium) -- else - .settings-message.text-center - This user has no inactive #{type} Tokens. diff --git a/app/views/shared/empty_states/_issues.html.haml b/app/views/shared/empty_states/_issues.html.haml index 046b127f73c..b0c0ab523c7 100644 --- a/app/views/shared/empty_states/_issues.html.haml +++ b/app/views/shared/empty_states/_issues.html.haml @@ -16,7 +16,8 @@ Also, issues are searchable and filterable. - if project_select_button = render 'shared/new_project_item_select', path: 'issues/new', label: 'New issue' - = link_to 'New issue', button_path, class: 'btn btn-new', title: 'New issue', id: 'new_issue_link' + - else + = link_to 'New issue', button_path, class: 'btn btn-new', title: 'New issue', id: 'new_issue_link' - else .text-center %h4 There are no issues to show. diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index bdb573cb8fd..6f0b7600698 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -98,7 +98,7 @@ - if type == :boards - if can?(current_user, :admin_list, @project) .dropdown.prepend-left-10#js-add-list - %button.btn.btn-create.btn-inverted.js-new-board-list{ type: "button", data: { toggle: "dropdown", labels: labels_filter_path, namespace_path: @project.try(:namespace).try(:path), project_path: @project.try(:path) } } + %button.btn.btn-create.btn-inverted.js-new-board-list{ type: "button", data: { toggle: "dropdown", labels: labels_filter_path, namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path) } } Add list .dropdown-menu.dropdown-menu-paging.dropdown-menu-align-right.dropdown-menu-issues-board-new.dropdown-menu-selectable = render partial: "shared/issuable/label_page_default", locals: { show_footer: true, show_create: true, show_boards_content: true, title: "Add list" } diff --git a/app/views/snippets/new.html.haml b/app/views/snippets/new.html.haml index ca8afb4bb6a..f01915107e3 100644 --- a/app/views/snippets/new.html.haml +++ b/app/views/snippets/new.html.haml @@ -1,3 +1,5 @@ +- @hide_top_links = true +- breadcrumb_title "Snippets" - page_title "New Snippet" %h3.page-title New Snippet diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml index 8818590362d..706f13dd004 100644 --- a/app/views/snippets/show.html.haml +++ b/app/views/snippets/show.html.haml @@ -1,3 +1,4 @@ +- @hide_top_links = true - @content_class = "limit-container-width limited-inner-width-container" unless fluid_layout - page_title "#{@snippet.title} (#{@snippet.to_reference})", "Snippets" diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index f246bd7a586..919ba5d15d3 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -1,3 +1,5 @@ +- @hide_top_links = true +- @hide_breadcrumbs = true - page_title @user.name - page_description @user.bio - content_for :page_specific_javascripts do diff --git a/app/workers/project_service_worker.rb b/app/workers/project_service_worker.rb index fdfdeab7b41..4883d848c53 100644 --- a/app/workers/project_service_worker.rb +++ b/app/workers/project_service_worker.rb @@ -2,6 +2,8 @@ class ProjectServiceWorker include Sidekiq::Worker include DedicatedSidekiqQueue + sidekiq_options dead: false + def perform(hook_id, data) data = data.with_indifferent_access Service.find(hook_id).execute(data) diff --git a/app/workers/web_hook_worker.rb b/app/workers/web_hook_worker.rb index ad5ddf02a12..713c0228040 100644 --- a/app/workers/web_hook_worker.rb +++ b/app/workers/web_hook_worker.rb @@ -2,7 +2,7 @@ class WebHookWorker include Sidekiq::Worker include DedicatedSidekiqQueue - sidekiq_options retry: 4 + sidekiq_options retry: 4, dead: false def perform(hook_id, data, hook_name) hook = WebHook.find(hook_id) diff --git a/changelogs/unreleased/10085-stop-encoding-user-name.yml b/changelogs/unreleased/10085-stop-encoding-user-name.yml new file mode 100644 index 00000000000..8fab474e047 --- /dev/null +++ b/changelogs/unreleased/10085-stop-encoding-user-name.yml @@ -0,0 +1,4 @@ +--- +title: "Insert user name directly without encoding" +merge_request: 10085 +author: Nathan Neulinger <nneul@neulinger.org> diff --git a/changelogs/unreleased/12892-reset-css-text-align-to-initial-for-rtl.md b/changelogs/unreleased/12892-reset-css-text-align-to-initial-for-rtl.md new file mode 100644 index 00000000000..87e95240bba --- /dev/null +++ b/changelogs/unreleased/12892-reset-css-text-align-to-initial-for-rtl.md @@ -0,0 +1,4 @@ +--- +title: "reset text-align to initial to let elements with dir="auto" align texts to right in RTL languages ( default is left )" +merge_request: 12892 +author: goshhob diff --git a/changelogs/unreleased/19629-remove-inactive-tokens-list.yml b/changelogs/unreleased/19629-remove-inactive-tokens-list.yml new file mode 100644 index 00000000000..414e3d49e29 --- /dev/null +++ b/changelogs/unreleased/19629-remove-inactive-tokens-list.yml @@ -0,0 +1,4 @@ +--- +title: Remove Inactive Personal Access Tokens list from Access Tokens page +merge_request: 12866 +author: diff --git a/changelogs/unreleased/23036-replace-dashboard-event-filters-spinach.yml b/changelogs/unreleased/23036-replace-dashboard-event-filters-spinach.yml new file mode 100644 index 00000000000..807cd097178 --- /dev/null +++ b/changelogs/unreleased/23036-replace-dashboard-event-filters-spinach.yml @@ -0,0 +1,4 @@ +--- +title: Replaces dashboard/event_filters.feature spinach with rspec +merge_request: 12651 +author: Alexander Randa (@randaalex) diff --git a/changelogs/unreleased/23036-replace-dashboard-spinach.yml b/changelogs/unreleased/23036-replace-dashboard-spinach.yml new file mode 100644 index 00000000000..b3197c4cfa6 --- /dev/null +++ b/changelogs/unreleased/23036-replace-dashboard-spinach.yml @@ -0,0 +1,4 @@ +--- +title: Replaces dashboard/dashboard.feature spinach with rspec +merge_request: 12876 +author: Alexander Randa (@randaalex) diff --git a/changelogs/unreleased/29901-refactor-initialization-dropzone_input-js.yml b/changelogs/unreleased/29901-refactor-initialization-dropzone_input-js.yml new file mode 100644 index 00000000000..8850422fc88 --- /dev/null +++ b/changelogs/unreleased/29901-refactor-initialization-dropzone_input-js.yml @@ -0,0 +1,4 @@ +--- +title: refactor initializations in dropzone_input.js +merge_request: 12768 +author: Brandon Everett diff --git a/changelogs/unreleased/31571-don-t-let-webhooks-jobs-go-to-the-dead-jobs-queue.yml b/changelogs/unreleased/31571-don-t-let-webhooks-jobs-go-to-the-dead-jobs-queue.yml new file mode 100644 index 00000000000..69900f0b314 --- /dev/null +++ b/changelogs/unreleased/31571-don-t-let-webhooks-jobs-go-to-the-dead-jobs-queue.yml @@ -0,0 +1,5 @@ +--- +title: Prevent web hook and project service background jobs from going to the dead + jobs queue +merge_request: +author: diff --git a/changelogs/unreleased/33580-fix-api-scoping.yml b/changelogs/unreleased/33580-fix-api-scoping.yml deleted file mode 100644 index f4ebb13c082..00000000000 --- a/changelogs/unreleased/33580-fix-api-scoping.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix API Scoping -merge_request: 12300 -author: diff --git a/changelogs/unreleased/33672-supplement_portuguese_brazil_translation_of_i18n.yml b/changelogs/unreleased/33672-supplement_portuguese_brazil_translation_of_i18n.yml new file mode 100644 index 00000000000..d2bdc631d2a --- /dev/null +++ b/changelogs/unreleased/33672-supplement_portuguese_brazil_translation_of_i18n.yml @@ -0,0 +1,4 @@ +--- +title: Supplement Portuguese Brazil translation of Project Page & Repository Page +merge_request: 12156 +author: Huang Tao diff --git a/changelogs/unreleased/33741-clarify-k8s-service-keys.yml b/changelogs/unreleased/33741-clarify-k8s-service-keys.yml new file mode 100644 index 00000000000..91142a0d580 --- /dev/null +++ b/changelogs/unreleased/33741-clarify-k8s-service-keys.yml @@ -0,0 +1,5 @@ +--- +title: Clarifies and rearranges the input variables on the kubernetes integration + page and adjusts the docs slightly to meet the same order +merge_request: !12188 +author: diff --git a/changelogs/unreleased/33770-respect-blockquote-line-breaks.yml b/changelogs/unreleased/33770-respect-blockquote-line-breaks.yml new file mode 100644 index 00000000000..3a45ad88270 --- /dev/null +++ b/changelogs/unreleased/33770-respect-blockquote-line-breaks.yml @@ -0,0 +1,4 @@ +--- +title: Respect blockquote line breaks in markdown +merge_request: +author: diff --git a/changelogs/unreleased/33949-deprecate-healthcheck-access-token.yml b/changelogs/unreleased/33949-deprecate-healthcheck-access-token.yml new file mode 100644 index 00000000000..a08795e1a26 --- /dev/null +++ b/changelogs/unreleased/33949-deprecate-healthcheck-access-token.yml @@ -0,0 +1,4 @@ +--- +title: Deprecate Healthcheck Access Token in favor of IP whitelist +merge_request: +author: diff --git a/changelogs/unreleased/34075-pipelines-count-mt.yml b/changelogs/unreleased/34075-pipelines-count-mt.yml new file mode 100644 index 00000000000..3846e7b06a4 --- /dev/null +++ b/changelogs/unreleased/34075-pipelines-count-mt.yml @@ -0,0 +1,5 @@ +--- +title: Update Pipeline's badge count in Merge Request and Commits view to match real-time + content +merge_request: +author: diff --git a/changelogs/unreleased/34173-add-portuguese-brazil-translations-of-commits-page.yml b/changelogs/unreleased/34173-add-portuguese-brazil-translations-of-commits-page.yml new file mode 100644 index 00000000000..16a9216852d --- /dev/null +++ b/changelogs/unreleased/34173-add-portuguese-brazil-translations-of-commits-page.yml @@ -0,0 +1,4 @@ +--- +title: Add Portuguese Brazil translations of Commits Page +merge_request: 12408 +author: Huang Tao diff --git a/changelogs/unreleased/34468-remove-extra-blank-on-admin-on-mobile.yml b/changelogs/unreleased/34468-remove-extra-blank-on-admin-on-mobile.yml new file mode 100644 index 00000000000..99291b4c75a --- /dev/null +++ b/changelogs/unreleased/34468-remove-extra-blank-on-admin-on-mobile.yml @@ -0,0 +1,4 @@ +--- +title: Use smaller min-width for dropdown-menu-nav only on mobile +merge_request: 12528 +author: Takuya Noguchi diff --git a/changelogs/unreleased/34534-update-vue-resource.yml b/changelogs/unreleased/34534-update-vue-resource.yml new file mode 100644 index 00000000000..2d0af0c9bfe --- /dev/null +++ b/changelogs/unreleased/34534-update-vue-resource.yml @@ -0,0 +1,4 @@ +--- +title: Updates vue resource and code according to breaking changes +merge_request: +author: diff --git a/changelogs/unreleased/34563-usage-ping-github.yml b/changelogs/unreleased/34563-usage-ping-github.yml new file mode 100644 index 00000000000..3ab982beea3 --- /dev/null +++ b/changelogs/unreleased/34563-usage-ping-github.yml @@ -0,0 +1,4 @@ +--- +title: Add GitHub imported projects count to usage data +merge_request: +author: diff --git a/changelogs/unreleased/34729-blob.yml b/changelogs/unreleased/34729-blob.yml new file mode 100644 index 00000000000..15a469d3af0 --- /dev/null +++ b/changelogs/unreleased/34729-blob.yml @@ -0,0 +1,4 @@ +--- +title: Fix crash on /help/ui +merge_request: +author: diff --git a/changelogs/unreleased/34789-add-japanese-translations-of-commits-page.yml b/changelogs/unreleased/34789-add-japanese-translations-of-commits-page.yml new file mode 100644 index 00000000000..40a24847580 --- /dev/null +++ b/changelogs/unreleased/34789-add-japanese-translations-of-commits-page.yml @@ -0,0 +1,4 @@ +--- +title: Add Japanese translations for Cycle Analytics & Project pages & Repository pages & Commits pages & Pipeline Charts. +merge_request: 12693 +author: Huang Tao diff --git a/changelogs/unreleased/34831-remove-coffee-rails-gem.yml b/changelogs/unreleased/34831-remove-coffee-rails-gem.yml new file mode 100644 index 00000000000..b555f112b8d --- /dev/null +++ b/changelogs/unreleased/34831-remove-coffee-rails-gem.yml @@ -0,0 +1,4 @@ +--- +title: Remove coffee-rails gem +merge_request: +author: Takuya Noguchi diff --git a/changelogs/unreleased/34858-bump-scss-lint-to-0-54-0.yml b/changelogs/unreleased/34858-bump-scss-lint-to-0-54-0.yml new file mode 100644 index 00000000000..e6cd834aed2 --- /dev/null +++ b/changelogs/unreleased/34858-bump-scss-lint-to-0-54-0.yml @@ -0,0 +1,4 @@ +--- +title: Bump scss-lint to 0.54.0 +merge_request: 12733 +author: Takuya Noguchi diff --git a/changelogs/unreleased/34867-remove-net-ssh-gem.yml b/changelogs/unreleased/34867-remove-net-ssh-gem.yml new file mode 100644 index 00000000000..f5648d62467 --- /dev/null +++ b/changelogs/unreleased/34867-remove-net-ssh-gem.yml @@ -0,0 +1,4 @@ +--- +title: Remove net-ssh gem +merge_request: +author: Takuya Noguchi diff --git a/changelogs/unreleased/34880-add-ukrainian-translations-to-i18n.yml b/changelogs/unreleased/34880-add-ukrainian-translations-to-i18n.yml new file mode 100644 index 00000000000..4e8a042fdb5 --- /dev/null +++ b/changelogs/unreleased/34880-add-ukrainian-translations-to-i18n.yml @@ -0,0 +1,4 @@ +--- +title: Add Ukrainian translations for Cycle Analytics & Project pages & Repository pages & Commits pages & Pipeline Charts. +merge_request: 12744 +author: Huang Tao diff --git a/changelogs/unreleased/34881-add-russian-translations-to-i18n.yml b/changelogs/unreleased/34881-add-russian-translations-to-i18n.yml new file mode 100644 index 00000000000..aed05dd1031 --- /dev/null +++ b/changelogs/unreleased/34881-add-russian-translations-to-i18n.yml @@ -0,0 +1,4 @@ +--- +title: Add Russian translations for Cycle Analytics & Project pages & Repository pages & Commits pages & Pipeline Charts. +merge_request: 12743 +author: Huang Tao diff --git a/changelogs/unreleased/34907-dont-show-pipeline-schedule-button-for-non-member.yml b/changelogs/unreleased/34907-dont-show-pipeline-schedule-button-for-non-member.yml new file mode 100644 index 00000000000..22c9c45bc75 --- /dev/null +++ b/changelogs/unreleased/34907-dont-show-pipeline-schedule-button-for-non-member.yml @@ -0,0 +1,4 @@ +--- +title: Do not show pipeline schedule button for non-member +merge_request: 12757 +author: Takuya Noguchi diff --git a/changelogs/unreleased/34927-protect-manual-actions-on-tags.yml b/changelogs/unreleased/34927-protect-manual-actions-on-tags.yml new file mode 100644 index 00000000000..d996ae2826a --- /dev/null +++ b/changelogs/unreleased/34927-protect-manual-actions-on-tags.yml @@ -0,0 +1,4 @@ +--- +title: Protect manual actions against protected tag too +merge_request: 12908 +author: diff --git a/changelogs/unreleased/34930-fix-edited-by.yml b/changelogs/unreleased/34930-fix-edited-by.yml new file mode 100644 index 00000000000..f133dfab0c2 --- /dev/null +++ b/changelogs/unreleased/34930-fix-edited-by.yml @@ -0,0 +1,4 @@ +--- +title: Use Ghost user for last_edited_by and merge_user when original user is deleted +merge_request: 12933 +author: diff --git a/changelogs/unreleased/34978-remove-public-ci-favicon-ico.yml b/changelogs/unreleased/34978-remove-public-ci-favicon-ico.yml new file mode 100644 index 00000000000..25cc8b5e45f --- /dev/null +++ b/changelogs/unreleased/34978-remove-public-ci-favicon-ico.yml @@ -0,0 +1,4 @@ +--- +title: Remove public/ci/favicon.ico +merge_request: 12803 +author: Takuya Noguchi diff --git a/changelogs/unreleased/35035-sidebar-job-spaces.yml b/changelogs/unreleased/35035-sidebar-job-spaces.yml new file mode 100644 index 00000000000..a9a0211efd9 --- /dev/null +++ b/changelogs/unreleased/35035-sidebar-job-spaces.yml @@ -0,0 +1,4 @@ +--- +title: Fix vertical space in job details sidebar +merge_request: +author: diff --git a/changelogs/unreleased/35087-mr-status-misaligned.yml b/changelogs/unreleased/35087-mr-status-misaligned.yml new file mode 100644 index 00000000000..3be43125a61 --- /dev/null +++ b/changelogs/unreleased/35087-mr-status-misaligned.yml @@ -0,0 +1,4 @@ +--- +title: Fix alignment of controls in mr issuable list +merge_request: +author: diff --git a/changelogs/unreleased/35155-upgrade-fog-core-to-1-44-3-and-its-providers-to-the-latest.yml b/changelogs/unreleased/35155-upgrade-fog-core-to-1-44-3-and-its-providers-to-the-latest.yml new file mode 100644 index 00000000000..9d9558347ba --- /dev/null +++ b/changelogs/unreleased/35155-upgrade-fog-core-to-1-44-3-and-its-providers-to-the-latest.yml @@ -0,0 +1,4 @@ +--- +title: Bump fog-core to 1.44.3 and fog providers' plugins to latest +merge_request: 12897 +author: Takuya Noguchi diff --git a/changelogs/unreleased/35164-cycle-analytics-firefox.yml b/changelogs/unreleased/35164-cycle-analytics-firefox.yml new file mode 100644 index 00000000000..0b7115136ca --- /dev/null +++ b/changelogs/unreleased/35164-cycle-analytics-firefox.yml @@ -0,0 +1,4 @@ +--- +title: allow closing Cycle Analytics intro box in firefox +merge_request: +author: diff --git a/changelogs/unreleased/35181-cannot-create-label-from-board-page.yml b/changelogs/unreleased/35181-cannot-create-label-from-board-page.yml new file mode 100644 index 00000000000..4afe603720d --- /dev/null +++ b/changelogs/unreleased/35181-cannot-create-label-from-board-page.yml @@ -0,0 +1,4 @@ +--- +title: Fix label creation from new list for subgroup projects +merge_request: +author: diff --git a/changelogs/unreleased/35209-add-wip-info-new-nav-pref.yml b/changelogs/unreleased/35209-add-wip-info-new-nav-pref.yml new file mode 100644 index 00000000000..680e1cd8222 --- /dev/null +++ b/changelogs/unreleased/35209-add-wip-info-new-nav-pref.yml @@ -0,0 +1,4 @@ +--- +title: Add wip message to new navigation preference section +merge_request: +author: diff --git a/changelogs/unreleased/35225-transient-poll.yml b/changelogs/unreleased/35225-transient-poll.yml new file mode 100644 index 00000000000..59e2e738c7b --- /dev/null +++ b/changelogs/unreleased/35225-transient-poll.yml @@ -0,0 +1,4 @@ +--- +title: fix transient js error in rspec tests +merge_request: +author: diff --git a/changelogs/unreleased/35253-desc-protected-branches-for-non-member.yml b/changelogs/unreleased/35253-desc-protected-branches-for-non-member.yml new file mode 100644 index 00000000000..9b2a66da1c3 --- /dev/null +++ b/changelogs/unreleased/35253-desc-protected-branches-for-non-member.yml @@ -0,0 +1,4 @@ +--- +title: Hide description about protected branches to non-member +merge_request: 12945 +author: Takuya Noguchi diff --git a/changelogs/unreleased/adam-external-issue-references-spike.yml b/changelogs/unreleased/adam-external-issue-references-spike.yml deleted file mode 100644 index aeec6688425..00000000000 --- a/changelogs/unreleased/adam-external-issue-references-spike.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Improve support for external issue references -merge_request: 12485 -author: diff --git a/changelogs/unreleased/artifacts-download-dropdown-menu-is-too-narrow.yml b/changelogs/unreleased/artifacts-download-dropdown-menu-is-too-narrow.yml new file mode 100644 index 00000000000..7d47c60e262 --- /dev/null +++ b/changelogs/unreleased/artifacts-download-dropdown-menu-is-too-narrow.yml @@ -0,0 +1,4 @@ +--- +title: Increase width of dropdown menus automatically +merge_request: 12809 +author: Thomas Wucher diff --git a/changelogs/unreleased/bvl-free-system-namespace.yml b/changelogs/unreleased/bvl-free-system-namespace.yml new file mode 100644 index 00000000000..6c2d1e0e61f --- /dev/null +++ b/changelogs/unreleased/bvl-free-system-namespace.yml @@ -0,0 +1,4 @@ +--- +title: "Move uploads from `uploads/system` to `uploads/-/system` to free up `system` as a group name" +merge_request: 11713 +author: diff --git a/changelogs/unreleased/dm-drop-default-scope-on-sortable-finders.yml b/changelogs/unreleased/dm-drop-default-scope-on-sortable-finders.yml deleted file mode 100644 index b359a25053a..00000000000 --- a/changelogs/unreleased/dm-drop-default-scope-on-sortable-finders.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Improve performance of lookups of issues, merge requests etc by dropping unnecessary ORDER BY clause -merge_request: -author: diff --git a/changelogs/unreleased/dm-encode-tree-and-blob-paths.yml b/changelogs/unreleased/dm-encode-tree-and-blob-paths.yml deleted file mode 100644 index c1a026e1f29..00000000000 --- a/changelogs/unreleased/dm-encode-tree-and-blob-paths.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix issues with non-UTF8 filenames by always fixing the encoding of tree and - blob paths -merge_request: -author: diff --git a/changelogs/unreleased/enable-scss-lint-bang-format.yml b/changelogs/unreleased/enable-scss-lint-bang-format.yml new file mode 100644 index 00000000000..0b73760198e --- /dev/null +++ b/changelogs/unreleased/enable-scss-lint-bang-format.yml @@ -0,0 +1,4 @@ +--- +title: Enable BangFormat in scss-lint [ci skip] +merge_request: 12815 +author: Takuya Noguchi diff --git a/changelogs/unreleased/enable-scss-lint-declaration-order.yml b/changelogs/unreleased/enable-scss-lint-declaration-order.yml new file mode 100644 index 00000000000..7ac2f55592e --- /dev/null +++ b/changelogs/unreleased/enable-scss-lint-declaration-order.yml @@ -0,0 +1,4 @@ +--- +title: Enable DeclarationOrder in scss-lint +merge_request: 12805 +author: Takuya Noguchi diff --git a/changelogs/unreleased/enable-scss-lint-import-path.yml b/changelogs/unreleased/enable-scss-lint-import-path.yml new file mode 100644 index 00000000000..d158cf5b5f3 --- /dev/null +++ b/changelogs/unreleased/enable-scss-lint-import-path.yml @@ -0,0 +1,4 @@ +--- +title: Enable ImportPath in scss-lint +merge_request: 12749 +author: Takuya Noguchi diff --git a/changelogs/unreleased/enable-scss-lint-property-spelling.yml b/changelogs/unreleased/enable-scss-lint-property-spelling.yml new file mode 100644 index 00000000000..c5a5a4dddb6 --- /dev/null +++ b/changelogs/unreleased/enable-scss-lint-property-spelling.yml @@ -0,0 +1,4 @@ +--- +title: Enable PropertySpelling in scss-lint +merge_request: 12752 +author: Takuya Noguchi diff --git a/changelogs/unreleased/enable-scss-lint-space-after-comma.yml b/changelogs/unreleased/enable-scss-lint-space-after-comma.yml new file mode 100644 index 00000000000..210f34fbb87 --- /dev/null +++ b/changelogs/unreleased/enable-scss-lint-space-after-comma.yml @@ -0,0 +1,4 @@ +--- +title: Enable SpaceAfterComma in scss-lint +merge_request: 12734 +author: Takuya Noguchi diff --git a/changelogs/unreleased/enable-scss-lint-unnecessary-parent-reference.yml b/changelogs/unreleased/enable-scss-lint-unnecessary-parent-reference.yml new file mode 100644 index 00000000000..59d5df56525 --- /dev/null +++ b/changelogs/unreleased/enable-scss-lint-unnecessary-parent-reference.yml @@ -0,0 +1,4 @@ +--- +title: Enable UnnecessaryParentReference in scss-lint +merge_request: 12738 +author: Takuya Noguchi diff --git a/changelogs/unreleased/fix-exact-matches-of-username-and-email-on-top-of-the-user-search.yml b/changelogs/unreleased/fix-exact-matches-of-username-and-email-on-top-of-the-user-search.yml new file mode 100644 index 00000000000..2e0573beab6 --- /dev/null +++ b/changelogs/unreleased/fix-exact-matches-of-username-and-email-on-top-of-the-user-search.yml @@ -0,0 +1,4 @@ +--- +title: Exact matches of username and email are now on top of the user search +merge_request: 12868 +author: diff --git a/changelogs/unreleased/fix-gb-recover-from-renaming-project-with-container-images.yml b/changelogs/unreleased/fix-gb-recover-from-renaming-project-with-container-images.yml new file mode 100644 index 00000000000..7adc53eb8fa --- /dev/null +++ b/changelogs/unreleased/fix-gb-recover-from-renaming-project-with-container-images.yml @@ -0,0 +1,4 @@ +--- +title: Recover from renaming project that has container images +merge_request: 12840 +author: diff --git a/changelogs/unreleased/fix-n-plus-one-in-url-builder.yml b/changelogs/unreleased/fix-n-plus-one-in-url-builder.yml new file mode 100644 index 00000000000..5781316cfd9 --- /dev/null +++ b/changelogs/unreleased/fix-n-plus-one-in-url-builder.yml @@ -0,0 +1,4 @@ +--- +title: Improve issue rendering performance with lots of notes from other users +merge_request: +author: diff --git a/changelogs/unreleased/fixes-for-internal-auth-disabled.yml b/changelogs/unreleased/fixes-for-internal-auth-disabled.yml new file mode 100644 index 00000000000..188d2770455 --- /dev/null +++ b/changelogs/unreleased/fixes-for-internal-auth-disabled.yml @@ -0,0 +1,4 @@ +--- +title: Fixes needed when GitLab sign-in is not enabled +merge_request: 12491 +author: Robin Bobbitt diff --git a/changelogs/unreleased/issue-description-gfm.yml b/changelogs/unreleased/issue-description-gfm.yml deleted file mode 100644 index 4d421bff677..00000000000 --- a/changelogs/unreleased/issue-description-gfm.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fixed GFM references not being included when updating issues inline -merge_request: -author: diff --git a/changelogs/unreleased/mr-branch-link-use-tree.yml b/changelogs/unreleased/mr-branch-link-use-tree.yml new file mode 100644 index 00000000000..f4c4d9f5082 --- /dev/null +++ b/changelogs/unreleased/mr-branch-link-use-tree.yml @@ -0,0 +1,4 @@ +--- +title: MR branch link now links to tree instead of commits +merge_request: +author: diff --git a/changelogs/unreleased/pass-before-script-as-is.yml b/changelogs/unreleased/pass-before-script-as-is.yml new file mode 100644 index 00000000000..ac6513dcff6 --- /dev/null +++ b/changelogs/unreleased/pass-before-script-as-is.yml @@ -0,0 +1,4 @@ +--- +title: Pass before_script and script as-is preserving arrays +merge_request: +author: diff --git a/changelogs/unreleased/remove-nprogress-gleaning.yml b/changelogs/unreleased/remove-nprogress-gleaning.yml new file mode 100644 index 00000000000..78e4dc82dd4 --- /dev/null +++ b/changelogs/unreleased/remove-nprogress-gleaning.yml @@ -0,0 +1,4 @@ +--- +title: Remove CSS for nprogress removed +merge_request: 12737 +author: Takuya Noguchi diff --git a/changelogs/unreleased/replace_spinach_spec_browse_files.yml b/changelogs/unreleased/replace_spinach_spec_browse_files.yml new file mode 100644 index 00000000000..7380d39fa9f --- /dev/null +++ b/changelogs/unreleased/replace_spinach_spec_browse_files.yml @@ -0,0 +1,4 @@ +--- +title: Replace 'browse_files.feature' spinach test with an rspec analog +merge_request: 12251 +author: @blackst0ne diff --git a/changelogs/unreleased/request-store-wrap.yml b/changelogs/unreleased/request-store-wrap.yml new file mode 100644 index 00000000000..8017054b77b --- /dev/null +++ b/changelogs/unreleased/request-store-wrap.yml @@ -0,0 +1,4 @@ +--- +title: Add RequestCache which makes caching with RequestStore easier +merge_request: 12920 +author: diff --git a/changelogs/unreleased/sh-add-mr-simple-mode.yml b/changelogs/unreleased/sh-add-mr-simple-mode.yml new file mode 100644 index 00000000000..0033ca28444 --- /dev/null +++ b/changelogs/unreleased/sh-add-mr-simple-mode.yml @@ -0,0 +1,4 @@ +--- +title: Add a simple mode to merge request API +merge_request: +author: diff --git a/changelogs/unreleased/sh-optimize-mr-api-emojis-and-labels.yml b/changelogs/unreleased/sh-optimize-mr-api-emojis-and-labels.yml new file mode 100644 index 00000000000..9589659cdc2 --- /dev/null +++ b/changelogs/unreleased/sh-optimize-mr-api-emojis-and-labels.yml @@ -0,0 +1,4 @@ +--- +title: Remove remaining N+1 queries in merge requests API with emojis and labels +merge_request: +author: diff --git a/changelogs/unreleased/sh-structured-logging.yml b/changelogs/unreleased/sh-structured-logging.yml new file mode 100644 index 00000000000..d89eb93f689 --- /dev/null +++ b/changelogs/unreleased/sh-structured-logging.yml @@ -0,0 +1,4 @@ +--- +title: Add structured logging for Rails processes +merge_request: +author: diff --git a/changelogs/unreleased/toggle-new-project-import-description.yml b/changelogs/unreleased/toggle-new-project-import-description.yml new file mode 100644 index 00000000000..8f0d09e0540 --- /dev/null +++ b/changelogs/unreleased/toggle-new-project-import-description.yml @@ -0,0 +1,4 @@ +--- +title: Toggle import description with import_sources_enabled +merge_request: 12691 +author: Brianna Kicia
\ No newline at end of file diff --git a/changelogs/unreleased/update_bootsnap_1-1-1.yml b/changelogs/unreleased/update_bootsnap_1-1-1.yml deleted file mode 100644 index 9ecfe4b60c8..00000000000 --- a/changelogs/unreleased/update_bootsnap_1-1-1.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Bump bootsnap to 1.1.1 -merge_request: 12425 -author: @blackst0ne diff --git a/changelogs/unreleased/username-password-stripped-from-import-url-fix.yml b/changelogs/unreleased/username-password-stripped-from-import-url-fix.yml deleted file mode 100644 index 571279d3dc7..00000000000 --- a/changelogs/unreleased/username-password-stripped-from-import-url-fix.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Username and password are no longer stripped from import url on mirror update -merge_request: 12725 -author: diff --git a/config/README.md b/config/README.md index 0a5ea2424e0..2778d0d4f02 100644 --- a/config/README.md +++ b/config/README.md @@ -19,4 +19,132 @@ an ERB file and then loads the resulting YML as its configuration. This file is called `resque.yml` for historical reasons. We are **NOT** using Resque at the moment. It is used to specify Redis configuration -values instead. +values when a single database instance of Redis is desired. + +# Advanced Redis configuration files + +In more advanced configurations of Redis key-value storage, it is desirable +to separate the keys by lifecycle and intended use to ease provisioning and +management of scalable Redis clusters. + +These settings provide routing and other configuration data (such as sentinel, +persistence policies, and other Redis customization) for connections +to Redis single instances, Redis sentinel, and Redis clusters. + +If desired, the routing URL provided by these settings can be used with: +1. Unix Socket + 1. named socket for each Redis instance desired. + 2. `database number` for each Redis instance desired. +2. TCP Socket + 1. `host name` or IP for each Redis instance desired + 2. TCP port number for each Redis instance desired + 3. `database number` for each Redis instance desired + +## Example URL attribute formats for GitLab Redis `.yml` configuration files +* Unix Socket, default Redis database (0) + * `url: unix:/path/to/redis.sock` + * `url: unix:/path/to/redis.sock?db=` +* Unix Socket, Redis database 44 + * `url: unix:/path/to/redis.sock?db=44` + * `url: unix:/path/to/redis.sock?extra=foo&db=44` +* TCP Socket for Redis on localhost, port 6379, database 33 + * `url: redis://:mynewpassword@localhost:6379/33` +* TCP Socket for Redis on remote host `myserver`, port 6379, database 33 + * `url: redis://:mynewpassword@myserver:6379/33` + +## redis.cache.yml + +If configured, `redis.cache.yml` overrides the +`resque.yml` settings to configure the Redis database instance +used for `Rails.cache` and other volatile non-persistent data which enhances +the performance of GitLab. +Settings here can be overridden by the environment variable +`GITLAB_REDIS_CACHE_CONFIG_FILE` which provides +an alternate location for configuration settings. + +The order of precedence for the URL used to connect to the Redis instance +used for `cache` is: +1. URL from a configuration file pointed to by the +`GITLAB_REDIS_CACHE_CONFIG_FILE` environment variable +2. URL from `redis.cache.yml` +3. URL from a configuration file pointed to by the +`GITLAB_REDIS_CONFIG_FILE` environment variable +4. URL from `resque.yml` +5. `redis://localhost:6380` + +The order of precedence for all other configuration settings for `cache` +are selected from only the first of the following files found (if a setting +is not provided in an earlier file, the remainder of the files are not +searched): +1. the configuration file pointed to by the +`GITLAB_REDIS_CACHE_CONFIG_FILE` environment variable +2. the configuration file `redis.cache.yml` +3. the configuration file pointed to by the +`GITLAB_REDIS_CONFIG_FILE` environment variable +4. the configuration file `resque.yml` + +## redis.queues.yml + +If configured, `redis.queues.yml` overrides the +`resque.yml` settings to configure the Redis database instance +used for clients of `::Gitlab::Redis::Queues`. +These queues are intended to be the foundation +of reliable inter-process communication between modules, whether on the same +host node, or within a cluster. The primary clients of the queues are +SideKiq, Mailroom, CI Runner, Workhorse, and push services. Settings here can +be overridden by the environment variable +`GITLAB_REDIS_QUEUES_CONFIG_FILE` which provides an alternate location for +configuration settings. + +The order of precedence for the URL used to connect to the Redis instance +used for `queues` is: +1. URL from a configuration file pointed to by the +`GITLAB_REDIS_QUEUES_CONFIG_FILE` environment variable +2. URL from `redis.queues.yml` +3. URL from a configuration file pointed to by the +`GITLAB_REDIS_CONFIG_FILE` environment variable +4. URL from `resque.yml` +5. `redis://localhost:6381` + +The order of precedence for all other configuration settings for `queues` +are selected from only the first of the following files found (if a setting +is not provided in an earlier file, the remainder of the files are not +searched): +1. the configuration file pointed to by the +`GITLAB_REDIS_QUEUES_CONFIG_FILE` environment variable +2. the configuration file `redis.queues.yml` +3. the configuration file pointed to by the +`GITLAB_REDIS_CONFIG_FILE` environment variable +4. the configuration file `resque.yml` + +## redis.shared_state.yml + +If configured, `redis.shared_state.yml` overrides the +`resque.yml` settings to configure the Redis database instance +used for clients of `::Gitlab::Redis::SharedState` such as session state, +and rate limiting. +Settings here can be overridden by the environment variable +`GITLAB_REDIS_SHARED_STATE_CONFIG_FILE` which provides +an alternate location for configuration settings. + +The order of precedence for the URL used to connect to the Redis instance +used for `shared_state` is: +1. URL from a configuration file pointed to by the +`GITLAB_REDIS_SHARED_STATE_CONFIG_FILE` environment variable +2. URL from `redis.shared_state.yml` +3. URL from a configuration file pointed to by the +`GITLAB_REDIS_CONFIG_FILE` environment variable +4. URL from `resque.yml` +5. `redis://localhost:6382` + +The order of precedence for all other configuration settings for `shared_state` +are selected from only the first of the following files found (if a setting +is not provided in an earlier file, the remainder of the files are not +searched): +1. the configuration file pointed to by the +`GITLAB_REDIS_SHARED_STATE_CONFIG_FILE` environment variable +2. the configuration file `redis.shared_state.yml` +3. the configuration file pointed to by the +`GITLAB_REDIS_CONFIG_FILE` environment variable +4. the configuration file `resque.yml` + diff --git a/config/application.rb b/config/application.rb index 2f4e2624195..1c13cc81270 100644 --- a/config/application.rb +++ b/config/application.rb @@ -6,7 +6,9 @@ Bundler.require(:default, Rails.env) module Gitlab class Application < Rails::Application - require_dependency Rails.root.join('lib/gitlab/redis') + require_dependency Rails.root.join('lib/gitlab/redis/cache') + require_dependency Rails.root.join('lib/gitlab/redis/queues') + require_dependency Rails.root.join('lib/gitlab/redis/shared_state') require_dependency Rails.root.join('lib/gitlab/request_context') # Settings in config/environments/* take precedence over those specified here. @@ -142,15 +144,15 @@ module Gitlab end end - # Use Redis caching across all environments - redis_config_hash = Gitlab::Redis.params - redis_config_hash[:namespace] = Gitlab::Redis::CACHE_NAMESPACE - redis_config_hash[:expires_in] = 2.weeks # Cache should not grow forever + # Use caching across all environments + caching_config_hash = Gitlab::Redis::Cache.params + caching_config_hash[:namespace] = Gitlab::Redis::Cache::CACHE_NAMESPACE + caching_config_hash[:expires_in] = 2.weeks # Cache should not grow forever if Sidekiq.server? # threaded context - redis_config_hash[:pool_size] = Sidekiq.options[:concurrency] + 5 - redis_config_hash[:pool_timeout] = 1 + caching_config_hash[:pool_size] = Sidekiq.options[:concurrency] + 5 + caching_config_hash[:pool_timeout] = 1 end - config.cache_store = :redis_store, redis_config_hash + config.cache_store = :redis_store, caching_config_hash config.active_record.raise_in_transactional_callbacks = true diff --git a/config/boot.rb b/config/boot.rb index 02baeab29ab..f2830ae3166 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -4,14 +4,3 @@ require 'rubygems' ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) - -begin - require 'bootsnap/setup' -rescue SystemCallError => exception - $stderr.puts "WARNING: Bootsnap failed to setup: #{exception.message}" -end - -# set default directory for multiproces metrics gathering -if ENV['RAILS_ENV'] == 'development' || ENV['RAILS_ENV'] == 'test' - ENV['prometheus_multiproc_dir'] ||= 'tmp/prometheus_multiproc_dir' -end diff --git a/config/database.yml.mysql b/config/database.yml.mysql index db1b712d3bc..eb71d3f5fe1 100644 --- a/config/database.yml.mysql +++ b/config/database.yml.mysql @@ -42,3 +42,4 @@ test: &test password: # host: localhost # socket: /tmp/mysql.sock + prepared_statements: false diff --git a/config/database.yml.postgresql b/config/database.yml.postgresql index c517a4c0cb8..4b30982fe82 100644 --- a/config/database.yml.postgresql +++ b/config/database.yml.postgresql @@ -46,3 +46,4 @@ test: &test username: postgres password: # host: localhost + prepared_statements: false diff --git a/config/environments/test.rb b/config/environments/test.rb index c3b788c038e..986107150cf 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -43,4 +43,9 @@ Rails.application.configure do config.cache_store = :null_store config.active_job.queue_adapter = :test + + if ENV['CI'] && !ENV['RAILS_ENABLE_TEST_LOG'] + config.logger = Logger.new(nil) + config.log_level = :fatal + end end diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 221e3d6e03b..cb007813b65 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -383,13 +383,13 @@ production: &base # service_validate_url: '/cas/p3/serviceValidate', # logout_url: '/cas/logout'} } # - { name: 'authentiq', - # # for client credentials (client ID and secret), go to https://www.authentiq.com/ + # # for client credentials (client ID and secret), go to https://www.authentiq.com/developers # app_id: 'YOUR_CLIENT_ID', # app_secret: 'YOUR_CLIENT_SECRET', # args: { # scope: 'aq:name email~rs address aq:push' - # # redirect_uri parameter is optional except when 'gitlab.host' in this file is set to 'localhost' - # # redirect_uri: 'YOUR_REDIRECT_URI' + # # callback_url parameter is optional except when 'gitlab.host' in this file is set to 'localhost' + # # callback_url: 'YOUR_CALLBACK_URL' # } # } # - { name: 'github', @@ -539,10 +539,15 @@ production: &base # enabled: true # host: localhost # port: 3808 - prometheus: + + ## Monitoring + # Built in monitoring settings + monitoring: # Time between sampling of unicorn socket metrics, in seconds # unicorn_sampler_interval: 10 - + # IP whitelist to access monitoring endpoints + ip_whitelist: + - 127.0.0.0/8 # # 5. Extra customization diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index fa33e602e93..ec7ce51b542 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -223,7 +223,7 @@ rescue ArgumentError # no user configured end Settings.gitlab['time_zone'] ||= nil Settings.gitlab['signup_enabled'] ||= true if Settings.gitlab['signup_enabled'].nil? -Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].nil? +Settings.gitlab['password_authentication_enabled'] ||= true if Settings.gitlab['password_authentication_enabled'].nil? Settings.gitlab['restricted_visibility_levels'] = Settings.__send__(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], []) Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil? Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing))(:?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z][A-Z0-9_]+-\d+))+)' if Settings.gitlab['issue_closing_pattern'].nil? @@ -494,10 +494,11 @@ Settings.webpack.dev_server['host'] ||= 'localhost' Settings.webpack.dev_server['port'] ||= 3808 # -# Prometheus metrics settings +# Monitoring settings # -Settings['prometheus'] ||= Settingslogic.new({}) -Settings.prometheus['unicorn_sampler_interval'] ||= 10 +Settings['monitoring'] ||= Settingslogic.new({}) +Settings.monitoring['ip_whitelist'] ||= ['127.0.0.1/8'] +Settings.monitoring['unicorn_sampler_interval'] ||= 10 # # Testing settings diff --git a/config/initializers/7_prometheus_metrics.rb b/config/initializers/7_prometheus_metrics.rb new file mode 100644 index 00000000000..987324a86c9 --- /dev/null +++ b/config/initializers/7_prometheus_metrics.rb @@ -0,0 +1,12 @@ +require 'prometheus/client' + +Prometheus::Client.configure do |config| + config.logger = Rails.logger + + config.initial_mmap_file_size = 4 * 1024 + config.multiprocess_files_dir = ENV['prometheus_multiproc_dir'] + + if Rails.env.development? && Rails.env.test? + config.multiprocess_files_dir ||= Rails.root.join('tmp/prometheus_multiproc_dir') + end +end diff --git a/config/initializers/7_redis.rb b/config/initializers/7_redis.rb index ae2ca258df1..af4967521b8 100644 --- a/config/initializers/7_redis.rb +++ b/config/initializers/7_redis.rb @@ -1,3 +1,8 @@ -# Make sure we initialize a Redis connection pool before Sidekiq starts -# multi-threaded execution. -Gitlab::Redis.with { nil } +# Make sure we initialize a Redis connection pool before multi-threaded +# execution starts by +# 1. Sidekiq +# 2. Rails.cache +# 3. HTTP clients +Gitlab::Redis::Cache.with { nil } +Gitlab::Redis::Queues.with { nil } +Gitlab::Redis::SharedState.with { nil } diff --git a/config/initializers/8_metrics.rb b/config/initializers/8_metrics.rb index d56fd7a6cfa..25630b298ce 100644 --- a/config/initializers/8_metrics.rb +++ b/config/initializers/8_metrics.rb @@ -119,11 +119,11 @@ def instrument_classes(instrumentation) end # rubocop:enable Metrics/AbcSize -Gitlab::Metrics::UnicornSampler.initialize_instance(Settings.prometheus.unicorn_sampler_interval).start +Gitlab::Metrics::UnicornSampler.initialize_instance(Settings.monitoring.unicorn_sampler_interval).start Gitlab::Application.configure do |config| # 0 should be Sentry to catch errors in this middleware - config.middleware.insert(1, Gitlab::Metrics::ConnectionRackMiddleware) + config.middleware.insert(1, Gitlab::Metrics::RequestsRackMiddleware) end if Gitlab::Metrics.enabled? @@ -174,6 +174,10 @@ if Gitlab::Metrics.enabled? loc && loc[0].start_with?(models) && method.source =~ regex end end + + # Ability is in app/models, is not an ActiveRecord model, but should still + # be instrumented. + Gitlab::Metrics::Instrumentation.instrument_methods(Ability) end Gitlab::Metrics::Instrumentation.configure do |config| diff --git a/config/initializers/flipper.rb b/config/initializers/flipper.rb index 8ec9613a4b7..bfab8c77a4b 100644 --- a/config/initializers/flipper.rb +++ b/config/initializers/flipper.rb @@ -3,4 +3,6 @@ require 'flipper/middleware/memoizer' unless Rails.env.test? Rails.application.config.middleware.use Flipper::Middleware::Memoizer, lambda { Feature.flipper } + + Feature.register_feature_groups end diff --git a/config/initializers/gettext_rails_i18n_patch.rb b/config/initializers/gettext_rails_i18n_patch.rb index 69118f464ca..377e5104f9d 100644 --- a/config/initializers/gettext_rails_i18n_patch.rb +++ b/config/initializers/gettext_rails_i18n_patch.rb @@ -33,7 +33,6 @@ module GettextI18nRailsJs [ ".js", ".jsx", - ".coffee", ".vue" ].include? ::File.extname(file) end diff --git a/config/initializers/lograge.rb b/config/initializers/lograge.rb new file mode 100644 index 00000000000..14902316240 --- /dev/null +++ b/config/initializers/lograge.rb @@ -0,0 +1,21 @@ +# Only use Lograge for Rails +unless Sidekiq.server? + filename = File.join(Rails.root, 'log', "#{Rails.env}_json.log") + + Rails.application.configure do + config.lograge.enabled = true + # Store the lograge JSON files in a separate file + config.lograge.keep_original_rails_log = true + # Don't use the Logstash formatter since this requires logstash-event, an + # unmaintained gem that monkey patches `Time` + config.lograge.formatter = Lograge::Formatters::Json.new + config.lograge.logger = ActiveSupport::Logger.new(filename) + # Add request parameters to log output + config.lograge.custom_options = lambda do |event| + { + time: event.time, + params: event.payload[:params].except(%w(controller action format)) + } + end + end +end diff --git a/config/initializers/peek.rb b/config/initializers/peek.rb index 65432caac2a..a54d53cbbe2 100644 --- a/config/initializers/peek.rb +++ b/config/initializers/peek.rb @@ -1,4 +1,4 @@ -Rails.application.config.peek.adapter = :redis, { client: ::Redis.new(Gitlab::Redis.params) } +Rails.application.config.peek.adapter = :redis, { client: ::Redis.new(Gitlab::Redis::Cache.params) } Peek.into Peek::Views::Host Peek.into Peek::Views::PerformanceBar @@ -26,7 +26,3 @@ class PEEK_DB_CLIENT end PEEK_DB_VIEW.prepend ::Gitlab::PerformanceBar::PeekQueryTracker - -class Peek::Views::PerformanceBar::ProcessUtilization - prepend ::Gitlab::PerformanceBar::PeekPerformanceBarWithRackBody -end diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb index 8919f7640fe..e8213ac8ba4 100644 --- a/config/initializers/session_store.rb +++ b/config/initializers/session_store.rb @@ -19,12 +19,12 @@ cookie_key = if Rails.env.development? if Rails.env.test? Gitlab::Application.config.session_store :cookie_store, key: "_gitlab_session" else - redis_config = Gitlab::Redis.params - redis_config[:namespace] = Gitlab::Redis::SESSION_NAMESPACE + sessions_config = Gitlab::Redis::SharedState.params + sessions_config[:namespace] = Gitlab::Redis::SharedState::SESSION_NAMESPACE Gitlab::Application.config.session_store( :redis_store, # Using the cookie_store would enable session replay attacks. - servers: redis_config, + servers: sessions_config, key: cookie_key, secure: Gitlab.config.gitlab.https, httponly: true, diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb index ecd73956488..a1cc9655319 100644 --- a/config/initializers/sidekiq.rb +++ b/config/initializers/sidekiq.rb @@ -1,12 +1,12 @@ -# Custom Redis configuration -redis_config_hash = Gitlab::Redis.params -redis_config_hash[:namespace] = Gitlab::Redis::SIDEKIQ_NAMESPACE +# Custom Queues configuration +queues_config_hash = Gitlab::Redis::Queues.params +queues_config_hash[:namespace] = Gitlab::Redis::Queues::SIDEKIQ_NAMESPACE # Default is to retry 25 times with exponential backoff. That's too much. Sidekiq.default_worker_options = { retry: 3 } Sidekiq.configure_server do |config| - config.redis = redis_config_hash + config.redis = queues_config_hash config.server_middleware do |chain| chain.add Gitlab::SidekiqMiddleware::ArgumentsLogger if ENV['SIDEKIQ_LOG_ARGUMENTS'] @@ -54,7 +54,7 @@ Sidekiq.configure_server do |config| end Sidekiq.configure_client do |config| - config.redis = redis_config_hash + config.redis = queues_config_hash config.client_middleware do |chain| chain.add Gitlab::SidekiqStatus::ClientMiddleware @@ -74,5 +74,5 @@ begin end end end -rescue Redis::BaseError, SocketError, Errno::ENOENT, Errno::EAFNOSUPPORT, Errno::ECONNRESET, Errno::ECONNREFUSED +rescue Redis::BaseError, SocketError, Errno::ENOENT, Errno::EADDRNOTAVAIL, Errno::EAFNOSUPPORT, Errno::ECONNRESET, Errno::ECONNREFUSED end diff --git a/config/mail_room.yml b/config/mail_room.yml index 88d93d4bc6b..c3a5be8d38c 100644 --- a/config/mail_room.yml +++ b/config/mail_room.yml @@ -21,7 +21,7 @@ :delivery_method: sidekiq :delivery_options: :redis_url: <%= config[:redis_url].to_json %> - :namespace: <%= Gitlab::Redis::SIDEKIQ_NAMESPACE %> + :namespace: <%= Gitlab::Redis::Queues::SIDEKIQ_NAMESPACE %> :queue: email_receiver :worker: EmailReceiverWorker <% if config[:sentinels] %> @@ -36,7 +36,7 @@ :arbitration_method: redis :arbitration_options: :redis_url: <%= config[:redis_url].to_json %> - :namespace: <%= Gitlab::Redis::MAILROOM_NAMESPACE %> + :namespace: <%= Gitlab::Redis::Queues::MAILROOM_NAMESPACE %> <% if config[:sentinels] %> :sentinels: <% config[:sentinels].each do |sentinel| %> diff --git a/config/prometheus/additional_metrics.yml b/config/prometheus/additional_metrics.yml index d33fae4182d..60355e9140c 100644 --- a/config/prometheus/additional_metrics.yml +++ b/config/prometheus/additional_metrics.yml @@ -7,7 +7,7 @@ - aws_elb_request_count_sum weight: 1 queries: - - query_range: 'sum(aws_elb_request_count_sum{%{environment_filter}}) * 60' + - query_range: 'sum(aws_elb_request_count_sum{%{environment_filter}}) / 60' label: Total unit: req / sec - title: "Latency" diff --git a/config/redis.cache.yml.example b/config/redis.cache.yml.example new file mode 100644 index 00000000000..27478f0a93e --- /dev/null +++ b/config/redis.cache.yml.example @@ -0,0 +1,38 @@ +# If you change this file in a Merge Request, please also create +# a Merge Request on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests +# +development: + url: redis://localhost:6379/10 + # + # url: redis://localhost:6380 + # sentinels: + # - + # host: localhost + # port: 26380 # point to sentinel, not to redis port + # - + # host: slave2 + # port: 26380 # point to sentinel, not to redis port +test: + url: redis://localhost:6379/10 + # + # url: redis://localhost:6380 +production: + # Redis (single instance) + url: unix:/var/run/redis/redis.cache.sock + ## + # Redis + Sentinel (for HA) + # + # Please read instructions carefully before using it as you may lose data: + # http://redis.io/topics/sentinel + # + # You must specify a list of a few sentinels that will handle client connection + # please read here for more information: https://docs.gitlab.com/ce/administration/high_availability/redis.html + ## + # url: redis://master:6380 + # sentinels: + # - + # host: slave1 + # port: 26380 # point to sentinel, not to redis port + # - + # host: slave2 + # port: 26380 # point to sentinel, not to redis port diff --git a/config/redis.queues.yml.example b/config/redis.queues.yml.example new file mode 100644 index 00000000000..dab1f26b096 --- /dev/null +++ b/config/redis.queues.yml.example @@ -0,0 +1,38 @@ +# If you change this file in a Merge Request, please also create +# a Merge Request on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests +# +development: + url: redis://localhost:6379/11 + # + # url: redis://localhost:6381 + # sentinels: + # - + # host: localhost + # port: 26381 # point to sentinel, not to redis port + # - + # host: slave2 + # port: 26381 # point to sentinel, not to redis port +test: + url: redis://localhost:6379/11 + # + # url: redis://localhost:6381 +production: + # Redis (single instance) + url: unix:/var/run/redis/redis.queues.sock + ## + # Redis + Sentinel (for HA) + # + # Please read instructions carefully before using it as you may lose data: + # http://redis.io/topics/sentinel + # + # You must specify a list of a few sentinels that will handle client connection + # please read here for more information: https://docs.gitlab.com/ce/administration/high_availability/redis.html + ## + # url: redis://master:6381 + # sentinels: + # - + # host: slave1 + # port: 26381 # point to sentinel, not to redis port + # - + # host: slave2 + # port: 26381 # point to sentinel, not to redis port diff --git a/config/redis.shared_state.yml.example b/config/redis.shared_state.yml.example new file mode 100644 index 00000000000..9371e3619b7 --- /dev/null +++ b/config/redis.shared_state.yml.example @@ -0,0 +1,38 @@ +# If you change this file in a Merge Request, please also create +# a Merge Request on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests +# +development: + url: redis://localhost:6379/12 + # + # url: redis://localhost:6382 + # sentinels: + # - + # host: localhost + # port: 26382 # point to sentinel, not to redis port + # - + # host: slave2 + # port: 26382 # point to sentinel, not to redis port +test: + url: redis://localhost:6379/12 + # + # url: redis://localhost:6382 +production: + # Redis (single instance) + url: unix:/var/run/redis/redis.shared_state.sock + ## + # Redis + Sentinel (for HA) + # + # Please read instructions carefully before using it as you may lose data: + # http://redis.io/topics/sentinel + # + # You must specify a list of a few sentinels that will handle client connection + # please read here for more information: https://docs.gitlab.com/ce/administration/high_availability/redis.html + ## + # url: redis://master:6382 + # sentinels: + # - + # host: slave1 + # port: 26382 # point to sentinel, not to redis port + # - + # host: slave2 + # port: 26382 # point to sentinel, not to redis port diff --git a/config/routes/uploads.rb b/config/routes/uploads.rb index a49e244af1a..e9c9aa8b2f9 100644 --- a/config/routes/uploads.rb +++ b/config/routes/uploads.rb @@ -1,21 +1,21 @@ scope path: :uploads do # Note attachments and User/Group/Project avatars - get "system/:model/:mounted_as/:id/:filename", + get "-/system/:model/:mounted_as/:id/:filename", to: "uploads#show", constraints: { model: /note|user|group|project/, mounted_as: /avatar|attachment/, filename: /[^\/]+/ } # show uploads for models, snippets (notes) available for now - get ':model/:id/:secret/:filename', + get 'system/:model/:id/:secret/:filename', to: 'uploads#show', constraints: { model: /personal_snippet/, id: /\d+/, filename: /[^\/]+/ } # show temporary uploads - get 'temp/:secret/:filename', + get 'system/temp/:secret/:filename', to: 'uploads#show', constraints: { filename: /[^\/]+/ } # Appearance - get "system/:model/:mounted_as/:id/:filename", + get "-/system/:model/:mounted_as/:id/:filename", to: "uploads#show", constraints: { model: /appearance/, mounted_as: /logo|header_logo/, filename: /.+/ } diff --git a/config/webpack.config.js b/config/webpack.config.js index c3fdca59a86..1113241e402 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -66,7 +66,7 @@ var config = { stl_viewer: './blob/stl_viewer.js', terminal: './terminal/terminal_bundle.js', u2f: ['vendor/u2f'], - users: './users/users_bundle.js', + users: './users/index.js', raven: './raven/index.js', vue_merge_request_widget: './vue_merge_request_widget/index.js', test: './test.js', diff --git a/db/migrate/20170629171610_rename_application_settings_signin_enabled_to_password_authentication_enabled.rb b/db/migrate/20170629171610_rename_application_settings_signin_enabled_to_password_authentication_enabled.rb new file mode 100644 index 00000000000..858b3bebace --- /dev/null +++ b/db/migrate/20170629171610_rename_application_settings_signin_enabled_to_password_authentication_enabled.rb @@ -0,0 +1,15 @@ +class RenameApplicationSettingsSigninEnabledToPasswordAuthenticationEnabled < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + rename_column_concurrently :application_settings, :signin_enabled, :password_authentication_enabled + end + + def down + cleanup_concurrent_column_rename :application_settings, :password_authentication_enabled, :signin_enabled + end +end diff --git a/db/migrate/20170713104829_add_foreign_key_to_merge_requests.rb b/db/migrate/20170713104829_add_foreign_key_to_merge_requests.rb new file mode 100644 index 00000000000..c25d4fd5986 --- /dev/null +++ b/db/migrate/20170713104829_add_foreign_key_to_merge_requests.rb @@ -0,0 +1,45 @@ +class AddForeignKeyToMergeRequests < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + class MergeRequest < ActiveRecord::Base + self.table_name = 'merge_requests' + include ::EachBatch + end + + def up + scope = <<-SQL.strip_heredoc + head_pipeline_id IS NOT NULL + AND NOT EXISTS ( + SELECT 1 FROM ci_pipelines + WHERE ci_pipelines.id = merge_requests.head_pipeline_id + ) + SQL + + MergeRequest.where(scope).each_batch(of: 1000) do |merge_requests| + merge_requests.update_all(head_pipeline_id: nil) + end + + unless foreign_key_exists?(:merge_requests, :head_pipeline_id) + add_concurrent_foreign_key(:merge_requests, :ci_pipelines, + column: :head_pipeline_id, on_delete: :nullify) + end + end + + def down + if foreign_key_exists?(:merge_requests, :head_pipeline_id) + remove_foreign_key(:merge_requests, column: :head_pipeline_id) + end + end + + private + + def foreign_key_exists?(table, column) + foreign_keys(table).any? do |key| + key.options[:column] == column.to_s + end + end +end diff --git a/db/migrate/20170717074009_move_system_upload_folder.rb b/db/migrate/20170717074009_move_system_upload_folder.rb new file mode 100644 index 00000000000..cce31794115 --- /dev/null +++ b/db/migrate/20170717074009_move_system_upload_folder.rb @@ -0,0 +1,60 @@ +class MoveSystemUploadFolder < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + disable_ddl_transaction! + + DOWNTIME = false + + def up + unless file_storage? + say 'Using object storage, no need to move.' + return + end + + unless File.directory?(old_directory) + say "#{old_directory} doesn't exist, no need to move it." + return + end + + FileUtils.mkdir_p(File.join(base_directory, '-')) + + say "Moving #{old_directory} -> #{new_directory}" + FileUtils.mv(old_directory, new_directory) + FileUtils.ln_s(new_directory, old_directory) + end + + def down + unless file_storage? + say 'Using object storage, no need to move.' + return + end + + unless File.directory?(new_directory) + say "#{new_directory} doesn't exist, no need to move it." + return + end + + if File.symlink?(old_directory) + say "Removing #{old_directory} -> #{new_directory} symlink" + FileUtils.rm(old_directory) + end + + say "Moving #{new_directory} -> #{old_directory}" + FileUtils.mv(new_directory, old_directory) + end + + def new_directory + File.join(base_directory, '-', 'system') + end + + def old_directory + File.join(base_directory, 'system') + end + + def base_directory + File.join(Rails.root, 'public', 'uploads') + end + + def file_storage? + CarrierWave::Uploader::Base.storage == CarrierWave::Storage::File + end +end diff --git a/db/post_migrate/20170324160416_migrate_user_activities_to_users_last_activity_on.rb b/db/post_migrate/20170324160416_migrate_user_activities_to_users_last_activity_on.rb index 397a9a2d28e..cb1b4f1855d 100644 --- a/db/post_migrate/20170324160416_migrate_user_activities_to_users_last_activity_on.rb +++ b/db/post_migrate/20170324160416_migrate_user_activities_to_users_last_activity_on.rb @@ -56,7 +56,7 @@ class MigrateUserActivitiesToUsersLastActivityOn < ActiveRecord::Migration end def activities(from, to, page: 1) - Gitlab::Redis.with do |redis| + Gitlab::Redis::SharedState.with do |redis| redis.zrangebyscore(USER_ACTIVITY_SET_KEY, from.to_i, to.to_i, with_scores: true, limit: limit(page)) @@ -64,7 +64,7 @@ class MigrateUserActivitiesToUsersLastActivityOn < ActiveRecord::Migration end def activities_count(from, to) - Gitlab::Redis.with do |redis| + Gitlab::Redis::SharedState.with do |redis| redis.zcount(USER_ACTIVITY_SET_KEY, from.to_i, to.to_i) end end diff --git a/db/post_migrate/20170406111121_clean_upload_symlinks.rb b/db/post_migrate/20170406111121_clean_upload_symlinks.rb index 3ac9a6c10bc..fc3a4acc0bb 100644 --- a/db/post_migrate/20170406111121_clean_upload_symlinks.rb +++ b/db/post_migrate/20170406111121_clean_upload_symlinks.rb @@ -6,7 +6,7 @@ class CleanUploadSymlinks < ActiveRecord::Migration disable_ddl_transaction! DOWNTIME = false - DIRECTORIES_TO_MOVE = %w(user project note group appeareance) + DIRECTORIES_TO_MOVE = %w(user project note group appearance) def up return unless file_storage? diff --git a/db/post_migrate/20170612071012_move_personal_snippets_files.rb b/db/post_migrate/20170612071012_move_personal_snippets_files.rb new file mode 100644 index 00000000000..33043364bde --- /dev/null +++ b/db/post_migrate/20170612071012_move_personal_snippets_files.rb @@ -0,0 +1,91 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. +class MovePersonalSnippetsFiles < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + disable_ddl_transaction! + + DOWNTIME = false + + def up + return unless file_storage? + + @source_relative_location = File.join('/uploads', 'personal_snippet') + @destination_relative_location = File.join('/uploads', 'system', 'personal_snippet') + + move_personal_snippet_files + end + + def down + return unless file_storage? + + @source_relative_location = File.join('/uploads', 'system', 'personal_snippet') + @destination_relative_location = File.join('/uploads', 'personal_snippet') + + move_personal_snippet_files + end + + def move_personal_snippet_files + query = "SELECT uploads.path, uploads.model_id, snippets.description FROM uploads "\ + "INNER JOIN snippets ON snippets.id = uploads.model_id WHERE uploader = 'PersonalFileUploader'" + select_all(query).each do |upload| + secret = upload['path'].split('/')[0] + file_name = upload['path'].split('/')[1] + + next unless move_file(upload['model_id'], secret, file_name) + update_markdown(upload['model_id'], secret, file_name, upload['description']) + end + end + + def move_file(snippet_id, secret, file_name) + source_dir = File.join(base_directory, @source_relative_location, snippet_id.to_s, secret) + destination_dir = File.join(base_directory, @destination_relative_location, snippet_id.to_s, secret) + + source_file_path = File.join(source_dir, file_name) + destination_file_path = File.join(destination_dir, file_name) + + unless File.exist?(source_file_path) + say "Source file `#{source_file_path}` doesn't exist. Skipping." + return + end + + say "Moving file #{source_file_path} -> #{destination_file_path}" + + FileUtils.mkdir_p(destination_dir) + FileUtils.move(source_file_path, destination_file_path) + + true + end + + def update_markdown(snippet_id, secret, file_name, description) + source_markdown_path = File.join(@source_relative_location, snippet_id.to_s, secret, file_name) + destination_markdown_path = File.join(@destination_relative_location, snippet_id.to_s, secret, file_name) + + source_markdown = "](#{source_markdown_path})" + destination_markdown = "](#{destination_markdown_path})" + + if description.present? + description = description.gsub(source_markdown, destination_markdown) + quoted_description = quote_string(description) + + execute("UPDATE snippets SET description = '#{quoted_description}', description_html = NULL "\ + "WHERE id = #{snippet_id}") + end + + query = "SELECT id, note FROM notes WHERE noteable_id = #{snippet_id} "\ + "AND noteable_type = 'Snippet' AND note IS NOT NULL" + select_all(query).each do |note| + text = note['note'].gsub(source_markdown, destination_markdown) + quoted_text = quote_string(text) + + execute("UPDATE notes SET note = '#{quoted_text}', note_html = NULL WHERE id = #{note['id']}") + end + end + + def base_directory + File.join(Rails.root, 'public') + end + + def file_storage? + CarrierWave::Uploader::Base.storage == CarrierWave::Storage::File + end +end diff --git a/db/post_migrate/20170613111224_clean_appearance_symlinks.rb b/db/post_migrate/20170613111224_clean_appearance_symlinks.rb new file mode 100644 index 00000000000..acb895e426f --- /dev/null +++ b/db/post_migrate/20170613111224_clean_appearance_symlinks.rb @@ -0,0 +1,52 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class CleanAppearanceSymlinks < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + disable_ddl_transaction! + + DOWNTIME = false + + def up + return unless file_storage? + + symlink_location = File.join(old_upload_dir, dir) + + return unless File.symlink?(symlink_location) + say "removing symlink: #{symlink_location}" + FileUtils.rm(symlink_location) + end + + def down + return unless file_storage? + + symlink = File.join(old_upload_dir, dir) + destination = File.join(new_upload_dir, dir) + + return if File.directory?(symlink) + return unless File.directory?(destination) + + say "Creating symlink #{symlink} -> #{destination}" + FileUtils.ln_s(destination, symlink) + end + + def file_storage? + CarrierWave::Uploader::Base.storage == CarrierWave::Storage::File + end + + def dir + 'appearance' + end + + def base_directory + Rails.root + end + + def old_upload_dir + File.join(base_directory, "public", "uploads") + end + + def new_upload_dir + File.join(base_directory, "public", "uploads", "system") + end +end diff --git a/db/post_migrate/20170629180131_cleanup_application_settings_signin_enabled_rename.rb b/db/post_migrate/20170629180131_cleanup_application_settings_signin_enabled_rename.rb new file mode 100644 index 00000000000..52a773ddfee --- /dev/null +++ b/db/post_migrate/20170629180131_cleanup_application_settings_signin_enabled_rename.rb @@ -0,0 +1,15 @@ +class CleanupApplicationSettingsSigninEnabledRename < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + cleanup_concurrent_column_rename :application_settings, :signin_enabled, :password_authentication_enabled + end + + def down + rename_column_concurrently :application_settings, :password_authentication_enabled, :signin_enabled + end +end diff --git a/db/post_migrate/20170717111152_cleanup_move_system_upload_folder_symlink.rb b/db/post_migrate/20170717111152_cleanup_move_system_upload_folder_symlink.rb new file mode 100644 index 00000000000..26b99b61424 --- /dev/null +++ b/db/post_migrate/20170717111152_cleanup_move_system_upload_folder_symlink.rb @@ -0,0 +1,40 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class CleanupMoveSystemUploadFolderSymlink < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + if File.symlink?(old_directory) + say "Removing #{old_directory} -> #{new_directory} symlink" + FileUtils.rm(old_directory) + else + say "Symlink #{old_directory} non existant, nothing to do." + end + end + + def down + if File.directory?(new_directory) + say "Symlinking #{old_directory} -> #{new_directory}" + FileUtils.ln_s(new_directory, old_directory) + else + say "#{new_directory} doesn't exist, skipping." + end + end + + def new_directory + File.join(base_directory, '-', 'system') + end + + def old_directory + File.join(base_directory, 'system') + end + + def base_directory + File.join(Rails.root, 'public', 'uploads') + end +end diff --git a/db/post_migrate/20170717150329_enqueue_migrate_system_uploads_to_new_folder.rb b/db/post_migrate/20170717150329_enqueue_migrate_system_uploads_to_new_folder.rb new file mode 100644 index 00000000000..87069dce006 --- /dev/null +++ b/db/post_migrate/20170717150329_enqueue_migrate_system_uploads_to_new_folder.rb @@ -0,0 +1,20 @@ +class EnqueueMigrateSystemUploadsToNewFolder < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + OLD_FOLDER = 'uploads/system/' + NEW_FOLDER = 'uploads/-/system/' + + disable_ddl_transaction! + + def up + BackgroundMigrationWorker.perform_async('MigrateSystemUploadsToNewFolder', + [OLD_FOLDER, NEW_FOLDER]) + end + + def down + BackgroundMigrationWorker.perform_async('MigrateSystemUploadsToNewFolder', + [NEW_FOLDER, OLD_FOLDER]) + end +end diff --git a/db/schema.rb b/db/schema.rb index 3ef311f48d8..862b2e21f4d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20170711145558) do +ActiveRecord::Schema.define(version: 20170717150329) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -41,7 +41,6 @@ ActiveRecord::Schema.define(version: 20170711145558) do create_table "application_settings", force: :cascade do |t| t.integer "default_projects_limit" t.boolean "signup_enabled" - t.boolean "signin_enabled" t.boolean "gravatar_enabled" t.text "sign_in_text" t.datetime "created_at" @@ -127,6 +126,7 @@ ActiveRecord::Schema.define(version: 20170711145558) do t.boolean "help_page_hide_commercial_content", default: false t.string "help_page_support_url" t.integer "performance_bar_allowed_group_id" + t.boolean "password_authentication_enabled" end create_table "audit_events", force: :cascade do |t| @@ -1616,6 +1616,7 @@ ActiveRecord::Schema.define(version: 20170711145558) do add_foreign_key "merge_request_diffs", "merge_requests", name: "fk_8483f3258f", on_delete: :cascade add_foreign_key "merge_request_metrics", "ci_pipelines", column: "pipeline_id", on_delete: :cascade add_foreign_key "merge_request_metrics", "merge_requests", on_delete: :cascade + add_foreign_key "merge_requests", "ci_pipelines", column: "head_pipeline_id", name: "fk_fd82eae0b9", on_delete: :nullify add_foreign_key "merge_requests", "projects", column: "target_project_id", name: "fk_a6963e8447", on_delete: :cascade add_foreign_key "merge_requests_closing_issues", "issues", on_delete: :cascade add_foreign_key "merge_requests_closing_issues", "merge_requests", on_delete: :cascade diff --git a/doc/README.md b/doc/README.md index 9b81c409570..1a7638b3d7e 100644 --- a/doc/README.md +++ b/doc/README.md @@ -50,8 +50,7 @@ Shortcuts to GitLab's most visited docs: - [Fork a project](gitlab-basics/fork-project.md) - [Importing and exporting projects between instances](user/project/settings/import_export.md). - [Project access](public_access/public_access.md): Setting up your project's visibility to public, internal, or private. -- [Groups](workflow/groups.md): Organize your projects in groups. - - [Create a group](gitlab-basics/create-group.md) +- [Groups](user/group/index.md): Organize your projects in groups. - [GitLab Subgroups](user/group/subgroups/index.md) - [Search through GitLab](user/search/index.md): Search for issues, merge requests, projects, groups, todos, and issues in Issue Boards. - [Snippets](user/snippets.md): Snippets allow you to create little bits of code. diff --git a/doc/administration/auth/authentiq.md b/doc/administration/auth/authentiq.md index fb1a16b0f96..1528f1d2b17 100644 --- a/doc/administration/auth/authentiq.md +++ b/doc/administration/auth/authentiq.md @@ -32,7 +32,7 @@ Authentiq will generate a Client ID and the accompanying Client Secret for you t "app_id" => "YOUR_CLIENT_ID", "app_secret" => "YOUR_CLIENT_SECRET", "args" => { - scope: 'aq:name email~rs aq:push' + "scope": 'aq:name email~rs address aq:push' } } ] @@ -45,21 +45,20 @@ Authentiq will generate a Client ID and the accompanying Client Secret for you t app_id: 'YOUR_CLIENT_ID', app_secret: 'YOUR_CLIENT_SECRET', args: { - scope: 'aq:name email~rs aq:push' + scope: 'aq:name email~rs address aq:push' } } ``` 5. The `scope` is set to request the user's name, email (required and signed), and permission to send push notifications to sign in on subsequent visits. -See [OmniAuth Authentiq strategy](https://github.com/AuthentiqID/omniauth-authentiq#scopes-and-redirect-uri-configuration) for more information on scopes and modifiers. +See [OmniAuth Authentiq strategy](https://github.com/AuthentiqID/omniauth-authentiq/wiki/Scopes,-callback-url-configuration-and-responses) for more information on scopes and modifiers. 6. Change `YOUR_CLIENT_ID` and `YOUR_CLIENT_SECRET` to the Client credentials you received in step 1. 7. Save the configuration file. -8. [Reconfigure](../restart_gitlab.md#omnibus-gitlab-reconfigure) or [restart GitLab](../restart_gitlab.md#installations-from-source) - for the changes to take effect if you installed GitLab via Omnibus or from source respectively. +8. [Reconfigure](../restart_gitlab.md#omnibus-gitlab-reconfigure) or [restart GitLab](../restart_gitlab.md#installations-from-source) for the changes to take effect if you installed GitLab via Omnibus or from source respectively. On the sign in page there should now be an Authentiq icon below the regular sign in form. Click the icon to begin the authentication process. diff --git a/doc/administration/auth/ldap.md b/doc/administration/auth/ldap.md index 725fc1f6076..c8987dea5e2 100644 --- a/doc/administration/auth/ldap.md +++ b/doc/administration/auth/ldap.md @@ -228,9 +228,14 @@ Tip: If you want to limit access to the nested members of an Active Directory group you can use the following syntax: ``` -(memberOf=CN=My Group,DC=Example,DC=com) +(memberOf:1.2.840.113556.1.4.1941=CN=My Group,DC=Example,DC=com) ``` +Find more information about this "LDAP_MATCHING_RULE_IN_CHAIN" filter at +https://msdn.microsoft.com/en-us/library/aa746475(v=vs.85).aspx. Support for +nested members in the user filter should not be confused with +[group sync nested groups support (EE only)](https://docs.gitlab.com/ee/administration/auth/ldap-ee.html#supported-ldap-group-types-attributes). + Please note that GitLab does not support the custom filter syntax used by omniauth-ldap. diff --git a/doc/administration/high_availability/redis_source.md b/doc/administration/high_availability/redis_source.md index fe982ea83c2..8b7a515a076 100644 --- a/doc/administration/high_availability/redis_source.md +++ b/doc/administration/high_availability/redis_source.md @@ -4,6 +4,11 @@ This is the documentation for configuring a Highly Available Redis setup when you have installed Redis all by yourself and not using the bundled one that comes with the Omnibus packages. +Note also that you may elect to override all references to +`/home/git/gitlab/config/resque.yml` in accordance with the advanced Redis +settings outlined in +[Configuration Files Documentation](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/README.md). + We cannot stress enough the importance of reading the [Overview section](redis.md#overview) of the Omnibus Redis HA as it provides some invaluable information to the configuration of Redis. Please proceed to diff --git a/doc/administration/monitoring/ip_whitelist.md b/doc/administration/monitoring/ip_whitelist.md new file mode 100644 index 00000000000..ad2773de132 --- /dev/null +++ b/doc/administration/monitoring/ip_whitelist.md @@ -0,0 +1,39 @@ +# IP whitelist + +> Introduced in GitLab 9.4. + +GitLab provides some [monitoring endpoints] that provide health check information +when probed. + +To control access to those endpoints via IP whitelisting, you can add single +hosts or use IP ranges: + +**For Omnibus installations** + +1. Open `/etc/gitlab/gitlab.rb` and add or uncomment the following: + + ```ruby + gitlab_rails['monitoring_whitelist'] = ['127.0.0.0/8', '192.168.0.1'] + ``` + +1. Save the file and [reconfigure] GitLab for the changes to take effect. + +--- + +**For installations from source** + +1. Edit `config/gitlab.yml`: + + ```yaml + monitoring: + # by default only local IPs are allowed to access monitoring resources + ip_whitelist: + - 127.0.0.0/8 + - 192.168.0.1 + ``` + +1. Save the file and [restart] GitLab for the changes to take effect. + +[reconfigure]: ../restart_gitlab.md#omnibus-gitlab-reconfigure +[restart]: ../restart_gitlab.md#installations-from-source +[monitoring endpoints]: ../../user/admin_area/monitoring/health_check.md diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md index 07c05b5a6fb..7072ab5d02a 100644 --- a/doc/administration/monitoring/prometheus/gitlab_metrics.md +++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md @@ -1,10 +1,8 @@ # GitLab Prometheus metrics >**Note:** -Available since [Omnibus GitLab 9.3][29118]. Currently experimental. For installations from source -you'll have to configure it yourself. - -GitLab monitors its own internal service metrics, and makes them available at the `/-/metrics` endpoint. Unlike other [Prometheus] exporters, this endpoint requires authentication as it is available on the same URL and port as user traffic. +Available since [Omnibus GitLab 9.3][29118]. Currently experimental. For +installations from source you'll have to configure it yourself. To enable the GitLab Prometheus metrics: @@ -15,33 +13,43 @@ To enable the GitLab Prometheus metrics: ## Collecting the metrics -Since the metrics endpoint is available on the same host and port as other traffic, it requires authentication. The token and URL to access is displayed on the [Health Check][health-check] page. +GitLab monitors its own internal service metrics, and makes them available at the +`/-/metrics` endpoint. Unlike other [Prometheus] exporters, in order to access +it, the client IP needs to be [included in a whitelist][whitelist]. -Currently the embedded Prometheus server is not automatically configured to collect metrics from this endpoint. We recommend setting up another Prometheus server, because the embedded server configuration is overwritten one every reconfigure of GitLab. In the future this will not be required. +Currently the embedded Prometheus server is not automatically configured to +collect metrics from this endpoint. We recommend setting up another Prometheus +server, because the embedded server configuration is overwritten once every +[reconfigure of GitLab][reconfigure]. In the future this will not be required. ## Metrics available In this experimental phase, only a few metrics are available: -| Metric | Type | Description | -| ------ | ---- | ----------- | -| db_ping_timeout | Gauge | Whether or not the last database ping timed out | -| db_ping_success | Gauge | Whether or not the last database ping succeeded | -| db_ping_latency | Gauge | Round trip time of the database ping | -| redis_ping_timeout | Gauge | Whether or not the last redis ping timed out | -| redis_ping_success | Gauge | Whether or not the last redis ping succeeded | -| redis_ping_latency | Gauge | Round trip time of the redis ping | -| filesystem_access_latency | gauge | Latency in accessing a specific filesystem | -| filesystem_accessible | gauge | Whether or not a specific filesystem is accessible | -| filesystem_write_latency | gauge | Write latency of a specific filesystem | -| filesystem_writable | gauge | Whether or not the filesystem is writable | -| filesystem_read_latency | gauge | Read latency of a specific filesystem | -| filesystem_readable | gauge | Whether or not the filesystem is readable | -| user_sessions_logins | Counter | Counter of how many users have logged in | +| Metric | Type | Since | Description | +|:--------------------------------- |:--------- |:----- |:----------- | +| db_ping_timeout | Gauge | 9.4 | Whether or not the last database ping timed out | +| db_ping_success | Gauge | 9.4 | Whether or not the last database ping succeeded | +| db_ping_latency_seconds | Gauge | 9.4 | Round trip time of the database ping | +| filesystem_access_latency_seconds | Gauge | 9.4 | Latency in accessing a specific filesystem | +| filesystem_accessible | Gauge | 9.4 | Whether or not a specific filesystem is accessible | +| filesystem_write_latency_seconds | Gauge | 9.4 | Write latency of a specific filesystem | +| filesystem_writable | Gauge | 9.4 | Whether or not the filesystem is writable | +| filesystem_read_latency_seconds | Gauge | 9.4 | Read latency of a specific filesystem | +| filesystem_readable | Gauge | 9.4 | Whether or not the filesystem is readable | +| http_requests_total | Counter | 9.4 | Rack request count | +| http_request_duration_seconds | Histogram | 9.4 | HTTP response time from rack middleware | +| pipelines_created_total | Counter | 9.4 | Counter of pipelines created | +| rack_uncaught_errors_total | Counter | 9.4 | Rack connections handling uncaught errors count | +| redis_ping_timeout | Gauge | 9.4 | Whether or not the last redis ping timed out | +| redis_ping_success | Gauge | 9.4 | Whether or not the last redis ping succeeded | +| redis_ping_latency_seconds | Gauge | 9.4 | Round trip time of the redis ping | +| user_session_logins_total | Counter | 9.4 | Counter of how many users have logged in | [← Back to the main Prometheus page](index.md) [29118]: https://gitlab.com/gitlab-org/gitlab-ce/issues/29118 [Prometheus]: https://prometheus.io [restart]: ../../restart_gitlab.md#omnibus-gitlab-restart -[health-check]: ../../../user/admin_area/monitoring/health_check.md +[whitelist]: ../ip_whitelist.md +[reconfigure]: ../../restart_gitlab.md#omnibus-gitlab-reconfigure diff --git a/doc/administration/monitoring/prometheus/index.md b/doc/administration/monitoring/prometheus/index.md index 695fdf09a87..f43c89dad87 100644 --- a/doc/administration/monitoring/prometheus/index.md +++ b/doc/administration/monitoring/prometheus/index.md @@ -95,8 +95,9 @@ Sample Prometheus queries: ## Configuring Prometheus to monitor Kubernetes > Introduced in GitLab 9.0. +> Pod monitoring introduced in GitLab 9.4. -If your GitLab server is running within Kubernetes, Prometheus will collect metrics from the Nodes in the cluster including performance data on each container. This is particularly helpful if your CI/CD environments run in the same cluster, as you can use the [Prometheus project integration][] to monitor them. +If your GitLab server is running within Kubernetes, Prometheus will collect metrics from the Nodes and [annotated Pods](https://prometheus.io/docs/operating/configuration/#<kubernetes_sd_config>) in the cluster, including performance data on each container. This is particularly helpful if your CI/CD environments run in the same cluster, as you can use the [Prometheus project integration][] to monitor them. To disable the monitoring of Kubernetes: diff --git a/doc/administration/operations/cleaning_up_redis_sessions.md b/doc/administration/operations/cleaning_up_redis_sessions.md index 93521e976d5..3a35aff8366 100644 --- a/doc/administration/operations/cleaning_up_redis_sessions.md +++ b/doc/administration/operations/cleaning_up_redis_sessions.md @@ -15,6 +15,12 @@ prefixed with 'session:gitlab:', so they would look like 'session:gitlab:976aa289e2189b17d7ef525a6702ace9'. Below we describe how to remove the keys in the old format. +**Note:** the instructions below must be modified in accordance with your +configuration settings if you have used the advanced Redis +settings outlined in +[Configuration Files Documentation](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/README.md). + + First we define a shell function with the proper Redis connection details. ``` diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 3dc808c196d..c90d95e4dd0 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -25,6 +25,7 @@ Parameters: | `order_by`| string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` | | `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` | | `milestone` | string | no | Return merge requests for a specific milestone | +| `view` | string | no | If `simple`, returns the `iid`, URL, title, description, and basic state of merge request | | `labels` | string | no | Return merge requests matching a comma separated list of labels | | `created_after` | datetime | no | Return merge requests created after the given time (inclusive) | | `created_before` | datetime | no | Return merge requests created before the given time (inclusive) | diff --git a/doc/api/projects.md b/doc/api/projects.md index 0d892c74d00..61ae89a64c0 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -1257,17 +1257,21 @@ endpoint can be accessed without authentication if the project is publicly accessible. ``` -GET /projects/search/:query +GET /projects ``` Parameters: | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `query` | string | yes | A string contained in the project name | +| `search` | string | yes | A string contained in the project name | | `order_by` | string | no | Return requests ordered by `id`, `name`, `created_at` or `last_activity_at` fields | | `sort` | string | no | Return requests sorted in `asc` or `desc` order | +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects?search=test +``` + ## Start the Housekeeping task for a Project >**Note:** This feature was introduced in GitLab 9.0 diff --git a/doc/api/settings.md b/doc/api/settings.md index eefbdda42ce..0b4cc98cea6 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -25,7 +25,7 @@ Example response: "id" : 1, "default_branch_protection" : 2, "restricted_visibility_levels" : [], - "signin_enabled" : true, + "password_authentication_enabled" : true, "after_sign_out_path" : null, "max_attachment_size" : 10, "user_oauth_applications" : true, @@ -63,7 +63,7 @@ PUT /application/settings | --------- | ---- | :------: | ----------- | | `default_projects_limit` | integer | no | Project limit per user. Default is `100000` | | `signup_enabled` | boolean | no | Enable registration. Default is `true`. | -| `signin_enabled` | boolean | no | Enable login via a GitLab account. Default is `true`. | +| `password_authentication_enabled` | boolean | no | Enable authentication via a GitLab account password. Default is `true`. | | `gravatar_enabled` | boolean | no | Enable Gravatar | | `sign_in_text` | string | no | Text on login page | | `home_page_url` | string | no | Redirect to this URL when not logged in | @@ -102,7 +102,7 @@ Example response: "id": 1, "default_projects_limit": 100000, "signup_enabled": true, - "signin_enabled": true, + "password_authentication_enabled": true, "gravatar_enabled": true, "sign_in_text": "", "created_at": "2015-06-12T15:51:55.432Z", diff --git a/doc/api/users.md b/doc/api/users.md index 91170e79645..6e5ec3231c5 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -364,7 +364,7 @@ GET /user Parameters: -- `sudo` (required) - the ID of a user +- `sudo` (optional) - the ID of a user to make the call in their place ``` GET /user diff --git a/doc/ci/environments.md b/doc/ci/environments.md index 3393030210e..df5c66a4c85 100644 --- a/doc/ci/environments.md +++ b/doc/ci/environments.md @@ -602,9 +602,8 @@ exist, you should see something like: >**Notes:** > - For the monitor dashboard to appear, you need to: - - Have enabled the [Kubernetes integration][kube] - - Have your app deployed on Kubernetes - Have enabled the [Prometheus integration][prom] + - Configured Prometheus to collect at least one [supported metric](../user/project/integrations/prometheus_library/metrics.md) - With GitLab 9.2, all deployments to an environment are shown directly on the monitoring dashboard diff --git a/doc/ci/img/environments_monitoring.png b/doc/ci/img/environments_monitoring.png Binary files differindex 387b6c54b61..d9c46ea4c95 100644 --- a/doc/ci/img/environments_monitoring.png +++ b/doc/ci/img/environments_monitoring.png diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md index 5b09f79f143..36c55cbaceb 100644 --- a/doc/development/doc_styleguide.md +++ b/doc/development/doc_styleguide.md @@ -388,8 +388,8 @@ the style below as a guide: 1. Save the file and [restart] GitLab for the changes to take effect. -[reconfigure]: path/to/administration/gitlab_restart.md#omnibus-gitlab-reconfigure -[restart]: path/to/administration/gitlab_restart.md#installations-from-source +[reconfigure]: path/to/administration/restart_gitlab.md#omnibus-gitlab-reconfigure +[restart]: path/to/administration/restart_gitlab.md#installations-from-source ```` In this case: diff --git a/doc/development/fe_guide/droplab/plugins/input_setter.md b/doc/development/fe_guide/droplab/plugins/input_setter.md index a549603c20d..94f3c8254a8 100644 --- a/doc/development/fe_guide/droplab/plugins/input_setter.md +++ b/doc/development/fe_guide/droplab/plugins/input_setter.md @@ -1,6 +1,6 @@ # InputSetter -`InputSetter` is a plugin that allows for udating DOM out of the scope of droplab when a list item is clicked. +`InputSetter` is a plugin that allows for updating DOM out of the scope of droplab when a list item is clicked. ## Usage diff --git a/doc/development/fe_guide/vue.md b/doc/development/fe_guide/vue.md index a984bb6c94c..0742b202807 100644 --- a/doc/development/fe_guide/vue.md +++ b/doc/development/fe_guide/vue.md @@ -112,7 +112,50 @@ Vue Resource should only be imported in the service file. Vue.use(VueResource); ``` -### CSRF token +#### Vue-resource gotchas +#### Headers +Headers are being parsed into a plain object in an interceptor. +In Vue-resource 1.x `headers` object was changed into an `Headers` object. In order to not change all old code, an interceptor was added. + +If you need to write a unit test that takes the headers in consideration, you need to include an interceptor to parse the headers after your test interceptor. +You can see an example in `spec/javascripts/environments/environment_spec.js`: + ```javascript + import { headersInterceptor } from './helpers/vue_resource_helper'; + + beforeEach(() => { + Vue.http.interceptors.push(myInterceptor); + Vue.http.interceptors.push(headersInterceptor); + }); + + afterEach(() => { + Vue.http.interceptors = _.without(Vue.http.interceptors, myInterceptor); + Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor); + }); + ``` + +#### `.json()` +When making a request to the server, you will most likely need to access the body of the response. +Use `.json()` to convert. Because `.json()` returns a Promise the follwoing structure should be used: + + ```javascript + service.get('url') + .then(resp => resp.json()) + .then((data) => { + this.store.storeData(data); + }) + .catch(() => new Flash('Something went wrong')); + ``` + +When using `Poll` (`app/assets/javascripts/lib/utils/poll.js`), the `successCallback` needs to handle `.json()` as a Promise: + ```javascript + successCallback: (response) => { + return response.json().then((data) => { + // handle the response + }); + } + ``` + +#### CSRF token We use a Vue Resource interceptor to manage the CSRF token. `app/assets/javascripts/vue_shared/vue_resource_interceptor.js` holds all our common interceptors. Note: You don't need to load `app/assets/javascripts/vue_shared/vue_resource_interceptor.js` @@ -126,13 +169,13 @@ The following example shows an application: // store.js export default class Store { - /** + /** * This is where we will iniatialize the state of our data. * Usually in a small SPA you don't need any options when starting the store. In the case you do * need guarantee it's an Object and it's documented. - * - * @param {Object} options - */ + * + * @param {Object} options + */ constructor(options) { this.options = options; @@ -205,14 +248,14 @@ import Store from 'store'; import Service from 'service'; import TodoComponent from 'todoComponent'; export default { - /** + /** * Although most data belongs in the store, each component it's own state. * We want to show a loading spinner while we are fetching the todos, this state belong * in the component. * * We need to access the store methods through all methods of our component. * We need to access the state of our store. - */ + */ data() { const store = new Store(); @@ -396,42 +439,46 @@ need to test the rendered output. [Vue][vue-test] guide's to unit test show us e [Vue Resource Interceptors][vue-resource-interceptor] allow us to add a interceptor with the response we need: -```javascript - // Mock the service to return data - const interceptor = (request, next) => { - next(request.respondWith(JSON.stringify([{ - title: 'This is a todo', - body: 'This is the text' - }]), { - status: 200, - })); - }; + ```javascript + // Mock the service to return data + const interceptor = (request, next) => { + next(request.respondWith(JSON.stringify([{ + title: 'This is a todo', + body: 'This is the text' + }]), { + status: 200, + })); + }; - beforeEach(() => { - Vue.http.interceptors.push(interceptor); - }); + beforeEach(() => { + Vue.http.interceptors.push(interceptor); + }); - afterEach(() => { - Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor); - }); + afterEach(() => { + Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor); + }); - it('should do something', (done) => { - setTimeout(() => { - // Test received data - done(); - }, 0); - }); -``` + it('should do something', (done) => { + setTimeout(() => { + // Test received data + done(); + }, 0); + }); + ``` + +1. Headers interceptor +Refer to [this section](vue.md#headers) 1. Use `$.mount()` to mount the component + ```javascript - // bad - new Component({ - el: document.createElement('div') - }); +// bad +new Component({ + el: document.createElement('div') +}); - // good - new Component().$mount(); +// good +new Component().$mount(); ``` [vue-docs]: http://vuejs.org/guide/index.html diff --git a/doc/development/gotchas.md b/doc/development/gotchas.md index 565d4b33457..c2ca8966a3f 100644 --- a/doc/development/gotchas.md +++ b/doc/development/gotchas.md @@ -3,35 +3,6 @@ The purpose of this guide is to document potential "gotchas" that contributors might encounter or should avoid during development of GitLab CE and EE. -## Do not `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`. - ## Do not assert against the absolute value of a sequence-generated attribute Consider the following factory: diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md index fdaaa65fa28..42bb5e8619c 100644 --- a/doc/development/rake_tasks.md +++ b/doc/development/rake_tasks.md @@ -12,6 +12,56 @@ The `setup` task is a alias for `gitlab:setup`. This tasks calls `db:reset` to create the database, calls `add_limits_mysql` that adds limits to the database schema in case of a MySQL database and finally it calls `db:seed_fu` to seed the database. Note: `db:setup` calls `db:seed` but this does nothing. +### Automation + +If you're very sure that you want to **wipe the current database** and refill +seeds, you could: + +``` shell +echo 'yes' | bundle exec rake setup +``` + +To save you from answering `yes` manually. + +### Discard stdout + +Since the script would print a lot of information, it could be slowing down +your terminal, and it would generate more than 20G logs if you just redirect +it to a file. If we don't care about the output, we could just redirect it to +`/dev/null`: + +``` shell +echo 'yes' | bundle exec rake setup > /dev/null +``` + +Note that since you can't see the questions from stdout, you might just want +to `echo 'yes'` to keep it running. It would still print the errors on stderr +so no worries about missing errors. + +### Notes for MySQL + +Since the seeds would contain various UTF-8 characters, such as emojis or so, +we'll need to make sure that we're using `utf8mb4` for all the encoding +settings and `utf8mb4_unicode_ci` for collation. Please check +[MySQL utf8mb4 support](../install/database_mysql.md#mysql-utf8mb4-support) + +Make sure that `config/database.yml` has `encoding: utf8mb4`, too. + +Next, we'll need to update the schema to make the indices fit: + +``` shell +sed -i 's/limit: 255/limit: 191/g' db/schema.rb +``` + +Then run the setup script: + +``` shell +bundle exec rake setup +``` + +To make sure that indices still fit. You could find great details in: +[How to support full Unicode in MySQL databases](https://mathiasbynens.be/notes/mysql-utf8mb4) + ## Run tests In order to run the test you can use the following commands: diff --git a/doc/development/testing.md b/doc/development/testing.md index cf3ea2ccfc2..e6aa4ae8f2f 100644 --- a/doc/development/testing.md +++ b/doc/development/testing.md @@ -195,7 +195,6 @@ Please consult the [dedicated "Frontend testing" guide](./fe_guide/testing.md). - Use `context` to test branching logic. - Use multi-line `do...end` blocks for `before` and `after`, even when it would fit on a single line. -- Don't `describe` symbols (see [Gotchas](gotchas.md#dont-describe-symbols)). - Don't assert against the absolute value of a sequence-generated attribute (see [Gotchas](gotchas.md#dont-assert-against-the-absolute-value-of-a-sequence-generated-attribute)). - Don't supply the `:each` argument to hooks since it's the default. - Prefer `not_to` to `to_not` (_this is enforced by RuboCop_). @@ -479,6 +478,11 @@ slowest test files and try to improve them. run the suite against MySQL for tags, `master`, and any branch that includes `mysql` in the name. - On EE, the test suite always runs both PostgreSQL and MySQL. +- Rails logging to `log/test.log` is disabled by default in CI [for + performance reasons][logging]. To override this setting, provide the + `RAILS_ENABLE_TEST_LOG` environment variable. + +[logging]: https://jtway.co/speed-up-your-rails-test-suite-by-6-in-1-line-13fedb869ec4 ## Spinach (feature) tests diff --git a/doc/gitlab-basics/README.md b/doc/gitlab-basics/README.md index 12466437edc..3d893ba53dd 100644 --- a/doc/gitlab-basics/README.md +++ b/doc/gitlab-basics/README.md @@ -6,7 +6,7 @@ Step-by-step guides on the basics of working with Git and GitLab. - [Start using Git on the command line](start-using-git.md) - [Create and add your SSH Keys](create-your-ssh-keys.md) - [Create a project](create-project.md) -- [Create a group](create-group.md) +- [Create a group](../user/group/index.md#create-a-new-group) - [Create a branch](create-branch.md) - [Fork a project](fork-project.md) - [Add a file](add-file.md) diff --git a/doc/gitlab-basics/create-group.md b/doc/gitlab-basics/create-group.md index b4889bb8818..985a52d88f5 100644 --- a/doc/gitlab-basics/create-group.md +++ b/doc/gitlab-basics/create-group.md @@ -1,50 +1,2 @@ -# How to create a group in GitLab -Your projects in GitLab can be organized in 2 different ways: -under your own namespace for single projects, such as `your-name/project-1` or -under groups. - -If you organize your projects under a group, it works like a folder. You can -manage your group members' permissions and access to the projects. - ---- - -To create a group: - -1. Expand the left sidebar by clicking the three bars at the upper left corner - and then navigate to **Groups**. - - ![Go to groups](img/create_new_group_sidebar.png) - -1. Once in your groups dashboard, click on **New group**. - - ![Create new group information](img/create_new_group_info.png) - -1. Fill out the needed information: - - 1. Set the "Group path" which will be the namespace under which your projects - will be hosted (path can contain only letters, digits, underscores, dashes - and dots; it cannot start with dashes or end in dot). - 1. The "Group name" will populate with the path. Optionally, you can change - it. This is the name that will display in the group views. - 1. Optionally, you can add a description so that others can briefly understand - what this group is about. - 1. Optionally, choose and avatar for your project. - 1. Choose the [visibility level](../public_access/public_access.md). - -1. Finally, click the **Create group** button. - -## Add a new project to a group - -There are 2 different ways to add a new project to a group: - -- Select a group and then click on the **New project** button. - - ![New project](img/create_new_project_from_group.png) - - You can then continue on [creating a project](create-project.md). - -- While you are [creating a project](create-project.md), select a group namespace - you've already created from the dropdown menu. - - ![Select group](img/select_group_dropdown.png) +This document was moved to [another location](../user/group/index.md#create-a-new-group). diff --git a/doc/gitlab-basics/img/create_new_group_sidebar.png b/doc/gitlab-basics/img/create_new_group_sidebar.png Binary files differdeleted file mode 100644 index fa88d1d51c0..00000000000 --- a/doc/gitlab-basics/img/create_new_group_sidebar.png +++ /dev/null diff --git a/doc/install/google_cloud_platform/index.md b/doc/install/google_cloud_platform/index.md index 35220119e9b..c6b767fff02 100644 --- a/doc/install/google_cloud_platform/index.md +++ b/doc/install/google_cloud_platform/index.md @@ -2,13 +2,13 @@ ![GCP landing page](img/gcp_landing.png) ->**Important note:** -GitLab has no official images in Google Cloud Platform yet. This guide serves -as a template for when the GitLab VM will be available. - The fastest way to get started on [Google Cloud Platform (GCP)][gcp] is through the [Google Cloud Launcher][launcher] program. +GitLab's official Google Launcher apps: +1. [GitLab Community Edition](https://console.cloud.google.com/launcher/details/gitlab-public/gitlab-community-edition?project=gitlab-public) +2. [GitLab Enterprise Edition](https://console.cloud.google.com/launcher/details/gitlab-public/gitlab-enterprise-edition?project=gitlab-public) + ## Prerequisites There are only two prerequisites in order to install GitLab on GCP: diff --git a/doc/install/installation.md b/doc/install/installation.md index 992ff162efb..5e981b0b3e7 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -294,9 +294,9 @@ sudo usermod -aG redis git ### Clone the Source # Clone GitLab repository - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 9-3-stable gitlab + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 9-4-stable gitlab -**Note:** You can change `9-3-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! +**Note:** You can change `9-4-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! ### Configure It @@ -420,6 +420,12 @@ GitLab Shell is an SSH access and repository management software developed speci **Note:** Make sure your hostname can be resolved on the machine itself by either a proper DNS record or an additional line in /etc/hosts ("127.0.0.1 hostname"). This might be necessary for example if you set up GitLab behind a reverse proxy. If the hostname cannot be resolved, the final installation check will fail with "Check GitLab API access: FAILED. code: 401" and pushing commits will be rejected with "[remote rejected] master -> master (hook declined)". +**Note:** GitLab Shell application startup time can be greatly reduced by disabling RubyGems. This can be done in several manners: + +* Export `RUBYOPT=--disable-gems` environment variable for the processes +* Compile Ruby with `configure --disable-rubygems` to disable RubyGems by default. Not recommened for system-wide Ruby. +* Omnibus GitLab [replaces the *shebang* line of the `gitlab-shell/bin/*` scripts](https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/1707) + ### Install gitlab-workhorse GitLab-Workhorse uses [GNU Make](https://www.gnu.org/software/make/). The diff --git a/doc/install/requirements.md b/doc/install/requirements.md index a3d676433e6..141df55f6bc 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -69,7 +69,7 @@ so keep in mind that you need at least 4GB available before running GitLab. With less memory GitLab will give strange errors during the reconfigure run and 500 errors during usage. -- 1GB RAM + 3GB of swap is the absolute minimum but we strongly **advise against** this amount of memory. See the unicorn worker section below for more advice. +- 1GB RAM + 3GB of swap is the absolute minimum but we strongly **advise against** this amount of memory. See the [unicorn worker section below](#unicorn-workers) for more advice. - 2GB RAM + 2GB swap supports up to 100 users but it will be very slow - **4GB RAM** is the **recommended** memory size for all installations and supports up to 100 users - 8GB RAM supports up to 1,000 users diff --git a/doc/update/9.3-to-9.4.md b/doc/update/9.3-to-9.4.md index bbb7f4a8d48..9540c36e7d0 100644 --- a/doc/update/9.3-to-9.4.md +++ b/doc/update/9.3-to-9.4.md @@ -72,8 +72,8 @@ More information can be found on the [yarn website](https://yarnpkg.com/en/docs/ ### 5. Update Go -NOTE: GitLab 9.4 and higher only supports Go 1.8.3 and dropped support for Go 1.5.x through 1.7.x. Be -sure to upgrade your installation if necessary +NOTE: GitLab 9.2 and higher only supports Go 1.8.3 and dropped support for Go +1.5.x through 1.7.x. Be sure to upgrade your installation if necessary. You can check which version you are running with `go version`. @@ -117,7 +117,7 @@ cd /home/git/gitlab sudo -u git -H git checkout 9-4-stable-ee ``` -### 5. Update gitlab-shell +### 7. Update gitlab-shell ```bash cd /home/git/gitlab-shell @@ -127,11 +127,10 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_SHELL_VERSION) sudo -u git -H bin/compile ``` -### 6. Update gitlab-workhorse +### 8. Update gitlab-workhorse -Install and compile gitlab-workhorse. This requires -[Go 1.8](https://golang.org/dl) which should already be on your system from -GitLab 8.1. GitLab-Workhorse uses [GNU Make](https://www.gnu.org/software/make/). +Install and compile gitlab-workhorse. GitLab-Workhorse uses +[GNU Make](https://www.gnu.org/software/make/). If you are not using Linux you may have to run `gmake` instead of `make` below. @@ -143,7 +142,7 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_WORKHORSE_VERSION) sudo -u git -H make ``` -### 7. Update Gitaly +### 9. Update Gitaly If you have not yet set up Gitaly then follow [Gitaly section of the installation guide](../install/installation.md#install-gitaly). @@ -158,8 +157,7 @@ configuration file may contain syntax errors. The block name file, should be `[[storage]]` instead. ```shell -cd /home/git/gitaly -sudo -u git -H editor config.toml +sudo -u git -H sed -i.pre-9.4 's/\[\[storages\]\]/[[storage]]/' /home/git/gitaly/config.toml ``` #### Compile Gitaly @@ -171,7 +169,29 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITALY_SERVER_VERSION) sudo -u git -H make ``` -### 10. Update configuration files +### 10. Update MySQL permissions + +If you are using MySQL you need to grant the GitLab user the necessary +permissions on the database: + +```bash +mysql -u root -p -e "GRANT TRIGGER ON \`gitlabhq_production\`.* TO 'git'@'localhost';" +``` + +If you use MySQL with replication, or just have MySQL configured with binary logging, +you will need to also run the following on all of your MySQL servers: + +```bash +mysql -u root -p -e "SET GLOBAL log_bin_trust_function_creators = 1;" +``` + +You can make this setting permanent by adding it to your `my.cnf`: + +``` +log_bin_trust_function_creators=1 +``` + +### 11. Update configuration files #### New configuration options for `gitlab.yml` @@ -245,7 +265,7 @@ For Ubuntu 16.04.1 LTS: sudo systemctl daemon-reload ``` -### 11. Install libs, migrations, etc. +### 12. Install libs, migrations, etc. ```bash cd /home/git/gitlab @@ -271,14 +291,14 @@ sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production **MySQL installations**: Run through the `MySQL strings limits` and `Tables and data conversion to utf8mb4` [tasks](../install/database_mysql.md). -### 12. Start application +### 13. Start application ```bash sudo service gitlab start sudo service nginx restart ``` -### 13. Check application status +### 14. Check application status Check if GitLab and its environment are configured correctly: diff --git a/doc/user/admin_area/monitoring/health_check.md b/doc/user/admin_area/monitoring/health_check.md index a954840b8a6..70934f9960a 100644 --- a/doc/user/admin_area/monitoring/health_check.md +++ b/doc/user/admin_area/monitoring/health_check.md @@ -5,6 +5,8 @@ - The `health_check` endpoint was [introduced][ce-3888] in GitLab 8.8 and will be deprecated in GitLab 9.1. Read more in the [old behavior](#old-behavior) section. + - [Access token](#access-token) has been deprecated in GitLab 9.4 + in favor of [IP whitelist](#ip-whitelist) GitLab provides liveness and readiness probes to indicate service health and reachability to required services. These probes report on the status of the @@ -12,97 +14,101 @@ database connection, Redis connection, and access to the filesystem. These endpoints [can be provided to schedulers like Kubernetes][kubernetes] to hold traffic until the system is ready or restart the container as needed. -## Access Token +## IP whitelist -An access token needs to be provided while accessing the probe endpoints. The current -accepted token can be found under the **Admin area ➔ Monitoring ➔ Health check** -(`admin/health_check`) page of your GitLab instance. +To access monitoring resources, the client IP needs to be included in a whitelist. -![access token](img/health_check_token.png) +[Read how to add IPs to a whitelist for the monitoring endpoints.][admin]. -The access token can be passed as a URL parameter: +## Using the endpoint -``` -https://gitlab.example.com/-/readiness?token=ACCESS_TOKEN -``` +With default whitelist settings, the probes can be accessed from localhost: + +- `http://localhost/-/readiness` +- `http://localhost/-/liveness` -which will then provide a report of system health in JSON format: +which will then provide a report of system health in JSON format. + +Readiness example output: ``` { - "db_check": { - "status": "ok" - }, - "redis_check": { - "status": "ok" - }, - "fs_shards_check": { - "status": "ok", - "labels": { - "shard": "default" - } - } + "queues_check" : { + "status" : "ok" + }, + "redis_check" : { + "status" : "ok" + }, + "shared_state_check" : { + "status" : "ok" + }, + "fs_shards_check" : { + "labels" : { + "shard" : "default" + }, + "status" : "ok" + }, + "db_check" : { + "status" : "ok" + }, + "cache_check" : { + "status" : "ok" + } } ``` -## Using the Endpoint - -Once you have the access token, the probes can be accessed: +Liveness example output: -- `https://gitlab.example.com/-/readiness?token=ACCESS_TOKEN` -- `https://gitlab.example.com/-/liveness?token=ACCESS_TOKEN` +``` +{ + "fs_shards_check" : { + "status" : "ok" + }, + "cache_check" : { + "status" : "ok" + }, + "db_check" : { + "status" : "ok" + }, + "redis_check" : { + "status" : "ok" + }, + "queues_check" : { + "status" : "ok" + }, + "shared_state_check" : { + "status" : "ok" + } +} +``` ## Status On failure, the endpoint will return a `500` HTTP status code. On success, the endpoint will return a valid successful HTTP status code, and a `success` message. -## Old behavior - ->**Notes:** - - Liveness and readiness probes were [introduced][ce-10416] in GitLab 9.1. - - The `health_check` endpoint was [introduced][ce-3888] in GitLab 8.8 and will - be deprecated in GitLab 9.1. Read more in the [old behavior](#old-behavior) - section. - -GitLab provides a health check endpoint for uptime monitoring on the `health_check` web -endpoint. The health check reports on the overall system status based on the status of -the database connection, the state of the database migrations, and the ability to write -and access the cache. This endpoint can be provided to uptime monitoring services like -[Pingdom][pingdom], [Nagios][nagios-health], and [NewRelic][newrelic-health]. +## Access token (Deprecated) -Once you have the [access token](#access-token), health information can be -retrieved as plain text, JSON, or XML using the `health_check` endpoint: +>**Note:** +Access token has been deprecated in GitLab 9.4 +in favor of [IP whitelist](#ip-whitelist) -- `https://gitlab.example.com/health_check?token=ACCESS_TOKEN` -- `https://gitlab.example.com/health_check.json?token=ACCESS_TOKEN` -- `https://gitlab.example.com/health_check.xml?token=ACCESS_TOKEN` - -You can also ask for the status of specific services: - -- `https://gitlab.example.com/health_check/cache.json?token=ACCESS_TOKEN` -- `https://gitlab.example.com/health_check/database.json?token=ACCESS_TOKEN` -- `https://gitlab.example.com/health_check/migrations.json?token=ACCESS_TOKEN` - -For example, the JSON output of the following health check: +An access token needs to be provided while accessing the probe endpoints. The current +accepted token can be found under the **Admin area ➔ Monitoring ➔ Health check** +(`admin/health_check`) page of your GitLab instance. -```bash -curl --header "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json -``` +![access token](img/health_check_token.png) -would be like: +The access token can be passed as a URL parameter: ``` -{"healthy":true,"message":"success"} +https://gitlab.example.com/-/readiness?token=ACCESS_TOKEN ``` -On failure, the endpoint will return a `500` HTTP status code. On success, the endpoint -will return a valid successful HTTP status code, and a `success` message. Ideally your -uptime monitoring should look for the success message. - [ce-10416]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10416 [ce-3888]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3888 [pingdom]: https://www.pingdom.com [nagios-health]: https://nagios-plugins.org/doc/man/check_http.html [newrelic-health]: https://docs.newrelic.com/docs/alerts/alert-policies/downtime-alerts/availability-monitoring [kubernetes]: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/ +[admin]: ../../../administration/monitoring/ip_whitelist.md diff --git a/doc/workflow/groups/access_requests_management.png b/doc/user/group/img/access_requests_management.png Binary files differindex 36deaa89a70..36deaa89a70 100644 --- a/doc/workflow/groups/access_requests_management.png +++ b/doc/user/group/img/access_requests_management.png diff --git a/doc/user/group/img/add_new_members.png b/doc/user/group/img/add_new_members.png Binary files differnew file mode 100644 index 00000000000..53f5596de23 --- /dev/null +++ b/doc/user/group/img/add_new_members.png diff --git a/doc/gitlab-basics/img/create_new_group_info.png b/doc/user/group/img/create_new_group_info.png Binary files differindex 8d2501d9f7a..8d2501d9f7a 100644 --- a/doc/gitlab-basics/img/create_new_group_info.png +++ b/doc/user/group/img/create_new_group_info.png diff --git a/doc/gitlab-basics/img/create_new_project_from_group.png b/doc/user/group/img/create_new_project_from_group.png Binary files differindex c35234660db..c35234660db 100644 --- a/doc/gitlab-basics/img/create_new_project_from_group.png +++ b/doc/user/group/img/create_new_project_from_group.png diff --git a/doc/user/group/img/group_settings.png b/doc/user/group/img/group_settings.png Binary files differnew file mode 100644 index 00000000000..629cd0729aa --- /dev/null +++ b/doc/user/group/img/group_settings.png diff --git a/doc/user/group/img/groups.png b/doc/user/group/img/groups.png Binary files differnew file mode 100644 index 00000000000..6211f999d5e --- /dev/null +++ b/doc/user/group/img/groups.png diff --git a/doc/user/group/img/membership_lock.png b/doc/user/group/img/membership_lock.png Binary files differnew file mode 100644 index 00000000000..d31fbb43375 --- /dev/null +++ b/doc/user/group/img/membership_lock.png diff --git a/doc/workflow/groups/new_group_form.png b/doc/user/group/img/new_group_form.png Binary files differindex 91727ab5336..91727ab5336 100644 --- a/doc/workflow/groups/new_group_form.png +++ b/doc/user/group/img/new_group_form.png diff --git a/doc/user/group/img/new_group_from_groups.png b/doc/user/group/img/new_group_from_groups.png Binary files differnew file mode 100644 index 00000000000..baf34244cb2 --- /dev/null +++ b/doc/user/group/img/new_group_from_groups.png diff --git a/doc/user/group/img/new_group_from_other_pages.png b/doc/user/group/img/new_group_from_other_pages.png Binary files differnew file mode 100644 index 00000000000..014a7088af2 --- /dev/null +++ b/doc/user/group/img/new_group_from_other_pages.png diff --git a/doc/workflow/groups/request_access_button.png b/doc/user/group/img/request_access_button.png Binary files differindex f1aae6afed7..f1aae6afed7 100644 --- a/doc/workflow/groups/request_access_button.png +++ b/doc/user/group/img/request_access_button.png diff --git a/doc/gitlab-basics/img/select_group_dropdown.png b/doc/user/group/img/select_group_dropdown.png Binary files differindex 68fc950304c..68fc950304c 100644 --- a/doc/gitlab-basics/img/select_group_dropdown.png +++ b/doc/user/group/img/select_group_dropdown.png diff --git a/doc/user/group/img/share_with_group_lock.png b/doc/user/group/img/share_with_group_lock.png Binary files differnew file mode 100644 index 00000000000..8df41bf9465 --- /dev/null +++ b/doc/user/group/img/share_with_group_lock.png diff --git a/doc/user/group/img/transfer_project_to_other_group.png b/doc/user/group/img/transfer_project_to_other_group.png Binary files differnew file mode 100644 index 00000000000..042c002f83f --- /dev/null +++ b/doc/user/group/img/transfer_project_to_other_group.png diff --git a/doc/workflow/groups/withdraw_access_request_button.png b/doc/user/group/img/withdraw_access_request_button.png Binary files differindex c5d8ef6c04f..c5d8ef6c04f 100644 --- a/doc/workflow/groups/withdraw_access_request_button.png +++ b/doc/user/group/img/withdraw_access_request_button.png diff --git a/doc/user/group/index.md b/doc/user/group/index.md new file mode 100644 index 00000000000..2691cf7d671 --- /dev/null +++ b/doc/user/group/index.md @@ -0,0 +1,208 @@ +# Groups + +With GitLab Groups you can assemble related projects together +and grant members access to several projects at once. + +Groups can also be nested in [subgroups](subgroups/index.md). + +Find your groups by expanding the left menu and clicking **Groups**: + +![GitLab Groups](img/groups.png) + +The Groups page displays all groups you are a member of, how many projects it holds, +how many members it has, the group visibility, and, if you have enough permissions, +a link to the group settings. By clicking the last button you can leave that group. + +## Use cases + +You can create groups for numerous reasons. To name a few: + +- Organize related projects under the same [namespace](#namespaces), add members to that +group and grant access to all their projects at once +- Create a group, include members of your team, and make it easier to +`@mention` all the team at once in issues and merge requests + - Create a group for your company members, and create [subgroups](subgroups/index.md) + for each individual team. Let's say you create a group called `company-team`, and among others, + you created subgroups in this group for each individual team `backend-team`, + `frontend-team`, and `production-team`: + 1. When you start a new implementation from an issue, you add a comment: + _"`@company-team`, let's do it! `@company-team/backend-team` you're good to go!"_ + 1. When your backend team needs help from frontend, they add a comment: + _"`@company-team/frontend-team` could you help us here please?"_ + 1. When the frontend team completes their implementation, they comment: + _"`@company-team/backend-team`, it's done! Let's ship it `@company-team/production-team`!"_ + +## Namespaces + +In GitLab, a namespace is a unique name to be used as a user name, a group name, or a subgroup name. + +- `http://gitlab.example.com/username` +- `http://gitlab.example.com/groupname` +- `http://gitlab.example.com/groupname/subgroup_name` + +For example, consider a user called John: + +1. John creates his account on GitLab.com with the username `jonh`; +his profile will be accessed under `https://gitlab.example.com/john` +1. John creates a group for his team with the groupname `john-team`; +his group and its projects will be accessed under `https://gitlab.example.com/john-team` +1. John creates a subgroup of `john-team` with the subgroup name `marketing`; +his subgroup and its projects will be accessed under `https://gitlab.example.com/john-team/marketing` + +By doing so: + +- Any team member mentions John with `@john` +- John mentions everyone from his team with `@john-team` +- John mentions only his marketing team with `@john-team/marketing` + +## Create a new group + +You can create a group in GitLab from: + +1. The Groups page: expand the left menu, click **Groups**, and click the green button **New group**: + + ![new group from groups page](img/new_group_from_groups.png) + +1. Elsewhere: expand the `plus` sign button on the top navbar and choose **New group**: + + ![new group from elsewhere](img/new_group_from_other_pages.png) + +Add the following information: + +![new group info](img/create_new_group_info.png) + +1. Set the **Group path** which will be the **namespace** under which your projects + will be hosted (path can contain only letters, digits, underscores, dashes + and dots; it cannot start with dashes or end in dot). +1. The **Group name** will populate with the path. Optionally, you can change + it. This is the name that will display in the group views. +1. Optionally, you can add a description so that others can briefly understand + what this group is about. +1. Optionally, choose an avatar for your project. +1. Choose the [visibility level](../../public_access/public_access.md). + +## Add users to a group + +Add members to a group by navigating to the group's dashboard, and clicking **Members**: + +![add members to group](img/add_new_members.png) + +Select the [permission level][permissions] and add the new member. You can also set the expiring +date for that user, from which they will no longer have access to your group. + +One of the benefits of putting multiple projects in one group is that you can +give a user to access to all projects in the group with one action. + +Consider we have a group with two projects: + +- On the **Group Members** page we can now add a new user to the group. +- Now because this user is a **Developer** member of the group, he automatically +gets **Developer** access to **all projects** within that group. + +If necessary, you can increase the access level of an individual user for a specific project, +by adding them again as a new member to the project with the new permission levels. + +## Request access to a group + +As a group owner you can enable or disable non members to request access to +your group. Go to the group settings and click on **Allow users to request access**. + +As a user, you can request to be a member of a group. Go to the group you'd +like to be a member of, and click the **Request Access** button on the right +side of your screen. + +![Request access button](img/request_access_button.png) + +--- + +Group owners and masters will be notified of your request and will be able to approve or +decline it on the members page. + +![Manage access requests](img/access_requests_management.png) + +--- + +If you change your mind before your request is approved, just click the +**Withdraw Access Request** button. + +![Withdraw access request button](img/withdraw_access_request_button.png) + +## Add projects to a group + +There are two different ways to add a new project to a group: + +- Select a group and then click on the **New project** button. + + ![New project](img/create_new_project_from_group.png) + + You can then continue on [creating a project](../../gitlab-basics/create-project.md). + +- While you are creating a project, select a group namespace + you've already created from the dropdown menu. + + ![Select group](img/select_group_dropdown.png) + +## Transfer an existing project into a group + +You can transfer an existing project into a group as long as you have at least **Master** [permissions][permissions] to that group +and if you are an **Owner** of the project. + +![Transfer a project to a new namespace](img/transfer_project_to_other_group.png) + +Find this option under your project's settings. + +GitLab administrators can use the admin interface to move any project to any namespace if needed. + +## Manage group memberships via LDAP + +In GitLab Enterprise Edition it is possible to manage GitLab group memberships using LDAP groups. +See [the GitLab Enterprise Edition documentation](../../integration/ldap.md) for more information. + +## Group settings + +Once you have created a group, you can manage its settings by navigating to +the group's dashboard, and clicking **Settings**. + +![group settings](img/group_settings.png) + +### General settings + +Besides giving you the option to edit any settings you've previously +set when [creating the group](#create-a-new-group), you can also +access further configurations for your group. + +#### Enforce 2FA to group members + +Add a secury layer to your group by +[enforcing two-factor authentication (2FA)](../../security/two_factor_authentication.md#enforcing-2fa-for-all-users-in-a-group) +to all group members. + +#### Member Lock (EES/EEP) + +Available in [GitLab Enterprise Edition Starter](https://about.gitlab.com/gitlab-ee/), +with **Member Lock** it is possible to lock membership in project to the +level of members in group. + +Learn more about [Member Lock](https://docs.gitlab.com/ee/user/group/index.html#member-lock-ees-eep). + +#### Share with group lock (EES/EEP) + +In [GitLab Enterprise Edition Starter](https://about.gitlab.com/gitlab-ee/) +it is possible to prevent projects in a group from [sharing +a project with another group](../../workflow/share_projects_with_other_groups.md). +This allows for tighter control over project access. + +Learn more about [Share with group lock](https://docs.gitlab.com/ee/user/group/index.html#share-with-group-lock-ees-eep). + +### Advanced settings + +- **Projects**: view all projects within that group, add members to each project, +access each project's settings, and remove any project from the same screen. +- **Webhooks**: configure [webhooks](../project/integrations/webhooks.md) +and [push rules](https://docs.gitlab.com/ee/push_rules/push_rules.html#push-rules) to your group (Push Rules is available in [GitLab Enteprise Edition Starter][ee].) +- **Audit Events**: view [Audit Events](https://docs.gitlab.com/ee/administration/audit_events.html#audit-events) +for the group (GitLab admins only, available in [GitLab Enterprise Edition Starter][ee]). +- **Pipelines quota**: keep track of the [pipeline quota](../admin_area/settings/continuous_integration.md) for the group + +[permissions]: ../permissions.md#permissions +[ee]: https://about.gitlab.com/products/
\ No newline at end of file diff --git a/doc/user/project/integrations/kubernetes.md b/doc/user/project/integrations/kubernetes.md index bfe2672e098..f4000523938 100644 --- a/doc/user/project/integrations/kubernetes.md +++ b/doc/user/project/integrations/kubernetes.md @@ -19,10 +19,10 @@ of your project and select the **Kubernetes** service to configure it. The Kubernetes service takes the following arguments: -1. Kubernetes namespace 1. API URL -1. Service token 1. Custom CA bundle +1. Kubernetes namespace +1. Service token The API URL is the URL that GitLab uses to access the Kubernetes API. Kubernetes exposes several APIs - we want the "base" URL that is common to all of them, diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md index 86ceb14b965..6f15765751c 100644 --- a/doc/user/project/integrations/prometheus.md +++ b/doc/user/project/integrations/prometheus.md @@ -17,35 +17,30 @@ the settings page with a default template. To configure the template, see the Integration with Prometheus requires the following: 1. GitLab 9.0 or higher -1. The [Kubernetes integration must be enabled][kube] on your project -1. Your app must be deployed on [Kubernetes][] -1. Prometheus must be configured to collect Kubernetes metrics +1. Prometheus must be configured to collect one of the [supported metrics](prometheus_library/metrics.md) 1. Each metric must be have a label to indicate the environment -1. GitLab must have network connectivity to the Prometheus sever +1. GitLab must have network connectivity to the Prometheus server -There are a few steps necessary to set up integration between Prometheus and -GitLab. +## Getting started with Prometheus monitoring -## Configuring Prometheus to collect Kubernetes metrics +Depending on your deployment and where you have located your GitLab server, there are a few options to get started with Prometheus monitoring. -In order for Prometheus to collect Kubernetes metrics, you first must have a -Prometheus server up and running. You have two options here: +* If both GitLab and your applications are installed in the same Kubernetes cluster, you can leverage the [bundled Prometheus server within GitLab](#configuring-omnibus-gitlab-prometheus-to-monitor-kubernetes). +* If your applications are deployed on Kubernetes, but GitLab is not in the same cluster, then you can [configure a Prometheus server in your Kubernetes cluster](#configuring-your-own-prometheus-server-within-kubernetes). +* If your applications are not running in Kubernetes, [get started with Prometheus](#getting-started-with-prometheus-outside-of-kubernetes). -- If you installed Omnibus GitLab inside of Kubernetes, you can simply use the - [bundled version of Prometheus][promgldocs]. In that case, follow the info in the - [Omnibus GitLab section](#configuring-omnibus-gitlab-prometheus-to-monitor-kubernetes) - below. -- If you are using GitLab.com or installed GitLab outside of Kubernetes, you - will likely need to run a Prometheus server within the Kubernetes cluster. - Once installed, the easiest way to monitor Kubernetes is to simply use - Prometheus' support for [Kubernetes Service Discovery][prometheus-k8s-sd]. - In that case, follow the instructions on - [configuring your own Prometheus server within Kubernetes](#configuring-your-own-prometheus-server-within-kubernetes). +### Getting started with Prometheus outside of Kubernetes -### Configuring Omnibus GitLab Prometheus to monitor Kubernetes +Installing and configuring Prometheus to monitor applications is fairly straight forward. + +1. [Install Prometheus](https://prometheus.io/docs/introduction/install/) +1. Set up one of the [supported monitoring targets](prometheus_library/metrics.md) +1. Configure the Prometheus server to [collect their metrics](https://prometheus.io/docs/operating/configuration/#scrape_config) + +### Configuring Omnibus GitLab Prometheus to monitor Kubernetes deployments With Omnibus GitLab running inside of Kubernetes, you can leverage the bundled -version of Prometheus to collect the required metrics. +version of Prometheus to collect the supported metrics. Once enabled, Prometheus will automatically begin monitoring Kubernetes Nodes and any [annotated Pods](https://prometheus.io/docs/operating/configuration/#<kubernetes_sd_config>). 1. Read how to configure the bundled Prometheus server in the [Administration guide][gitlab-prometheus-k8s-monitor]. @@ -74,7 +69,7 @@ kubectl apply -f path/to/prometheus.yml Once deployed, you should see the Prometheus service, deployment, and pod start within the `prometheus` namespace. The server will begin to collect metrics from each Kubernetes Node in the cluster, based on the configuration -provided in the template. +provided in the template. It will also attempt to collect metrics from any Kubernetes Pods that have been [annotated for Prometheus](https://prometheus.io/docs/operating/configuration/#pod). Since GitLab is not running within Kubernetes, the template provides external network access via a `NodePort` running on `30090`. This method allows access @@ -133,30 +128,6 @@ to integrate with. ![Configure Prometheus Service](img/prometheus_service_configuration.png) -## Metrics and Labels - -GitLab retrieves performance data from two metrics, `container_cpu_usage_seconds_total` -and `container_memory_usage_bytes`. These metrics are collected from the -Kubernetes pods via Prometheus, and report CPU and Memory utilization of each -container or Pod running in the cluster. - -In order to isolate and only display relevant metrics for a given environment -however, GitLab needs a method to detect which pods are associated. To do that, -GitLab will specifically request metrics that have an `environment` tag that -matches the [$CI_ENVIRONMENT_SLUG][ci-environment-slug]. - -If you are using [GitLab Auto-Deploy][autodeploy] and one of the methods of -configuring Prometheus above, the `environment` will be automatically added. - -### GitLab Prometheus queries - -The queries utilized by GitLab are shown in the following table. - -| Metric | Query | -| ------ | ----- | -| Average Memory (MB) | `(sum(container_memory_usage_bytes{container_name!="POD",environment="$CI_ENVIRONMENT_SLUG"}) / count(container_memory_usage_bytes{container_name!="POD",environment="$CI_ENVIRONMENT_SLUG"})) /1024/1024` | -| Average CPU Utilization (%) | `sum(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="$CI_ENVIRONMENT_SLUG"}[2m])) / count(container_cpu_usage_seconds_total{container_name!="POD",environment="$CI_ENVIRONMENT_SLUG"}) * 100` | - ## Monitoring CI/CD Environments Once configured, GitLab will attempt to retrieve performance metrics for any @@ -168,8 +139,9 @@ environment which has had a successful deployment. > [Introduced][ce-10408] in GitLab 9.2. > GitLab 9.3 added the [numeric comparison](https://gitlab.com/gitlab-org/gitlab-ce/issues/27439) of the 30 minute averages. +> Requires [Kubernetes](prometheus_library/kubernetes.md) metrics -Developers can view the performance impact of their changes within the merge +Developers can view theperformance impact of their changes within the merge request workflow. When a source branch has been deployed to an environment, a sparkline and numeric comparison of the average memory consumption will appear. On the sparkline, a dot indicates when the current changes were deployed, with up to 30 minutes of performance data displayed before and after. The comparison shows the difference between the 30 minute average before and after the deployment. This information is updated after diff --git a/doc/user/project/integrations/prometheus_library/cloudwatch.md b/doc/user/project/integrations/prometheus_library/cloudwatch.md new file mode 100644 index 00000000000..cc5cee36d28 --- /dev/null +++ b/doc/user/project/integrations/prometheus_library/cloudwatch.md @@ -0,0 +1,25 @@ +# Monitoring AWS Resources +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12621) in GitLab 9.4 + +GitLab has support for automatically detecting and monitoring AWS resources, starting with the [Elastic Load Balancer](https://aws.amazon.com/elasticloadbalancing/). This is provided by leveraging the official [Cloudwatch exporter](https://github.com/prometheus/cloudwatch_exporter), which translates [Cloudwatch metrics](https://aws.amazon.com/cloudwatch/) into a Prometheus readable form. + +## Metrics supported + +| Name | Query | +| ---- | ----- | +| Throughput (req/sec) | sum(aws_elb_request_count_sum{%{environment_filter}}) / 60 | +| Latency (ms) | avg(aws_elb_latency_average{%{environment_filter}}) * 1000 | +| HTTP Error Rate (%) | sum(aws_elb_httpcode_backend_5_xx_sum{%{environment_filter}}) / sum(aws_elb_request_count_sum{%{environment_filter}}) | + +## Configuring Prometheus to monitor for Cloudwatch metrics + +To get started with Cloudwatch monitoring, you should install and configure the [Cloudwatch exporter](https://github.com/hnlq715/nginx-vts-exporter) which retrieves and parses the specified Cloudwatch metrics and translates them into a Prometheus monitoring endpoint. + +Right now, the only AWS resource supported is the Elastic Load Balancer, whose Cloudwatch metrics can be found [here](http://docs.aws.amazon.com/elasticloadbalancing/latest/classic/elb-cloudwatch-metrics.html). + +A sample Cloudwatch Exporter configuration file, configured for basic AWS ELB monitoring, is [available for download](../samples/cloudwatch.yml). + +## Specifying the Environment label + +In order to isolate and only display relevant metrics for a given environment +however, GitLab needs a method to detect which labels are associated. To do this, GitLab will [look for an `environment` label](metrics.md#identifying-environments). diff --git a/doc/user/project/integrations/prometheus_library/kubernetes.md b/doc/user/project/integrations/prometheus_library/kubernetes.md new file mode 100644 index 00000000000..eb8cd821ddc --- /dev/null +++ b/doc/user/project/integrations/prometheus_library/kubernetes.md @@ -0,0 +1,26 @@ +# Monitoring Kubernetes +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8935) in GitLab 9.0 + +GitLab has support for automatically detecting and monitoring Kubernetes metrics. Kubernetes exposes Node level metrics out of the box via the built-in [Prometheus metrics support in cAdvisor](https://github.com/google/cadvisor). No additional services or exporters are needed. + +## Metrics supported + +| Name | Query | +| ---- | ----- | +| Average Memory Usage (MB) | (sum(container_memory_usage_bytes{container_name!="POD",%{environment_filter}}) / count(container_memory_usage_bytes{container_name!="POD",%{environment_filter}})) /1024/1024 | +| Average CPU Utilization (%) | sum(rate(container_cpu_usage_seconds_total{container_name!="POD",%{environment_filter}}[2m])) / count(container_cpu_usage_seconds_total{container_name!="POD",%{environment_filter}}) * 100 | + +## Configuring Prometheus to monitor for Kubernetes node metrics + +In order for Prometheus to collect Kubernetes metrics, you first must have a +Prometheus server up and running. You have two options here: + +- If you have an Omnibus based GitLab installation within your Kubernetes cluster, you can leverage the bundled Prometheus server to [monitor Kubernetes](../../../../administration/monitoring/prometheus/index.md#configuring-prometheus-to-monitor-kubernetes). +- To configure your own Prometheus server, you can follow the [Prometheus documentation](https://prometheus.io/docs/introduction/overview/) or [our guide](../../../../administration/monitoring/prometheus/index.md#configuring-your-own-prometheus-server-within-kubernetes). + +## Specifying the Environment label + +In order to isolate and only display relevant metrics for a given environment +however, GitLab needs a method to detect which labels are associated. To do this, GitLab will [look for an `environment` label](metrics.md#identifying-environments). + +If you are using [GitLab Auto-Deploy][autodeploy] and one of the two [provided Kubernetes monitoring solutions](../prometheus.md#getting-started-with-prometheus-monitoring), the `environment` label will be automatically added. diff --git a/doc/user/project/integrations/prometheus_library/metrics.md b/doc/user/project/integrations/prometheus_library/metrics.md new file mode 100644 index 00000000000..55146e57370 --- /dev/null +++ b/doc/user/project/integrations/prometheus_library/metrics.md @@ -0,0 +1,25 @@ +# Prometheus Metrics library +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8935) in GitLab 9.0 + +GitLab offers automatic detection of select [Prometheus exporters](https://prometheus.io/docs/instrumenting/exporters/). Currently supported exporters are: +* [Kubernetes](kubernetes.md) +* [NGINX](nginx.md) +* [Amazon Cloud Watch](cloudwatch.md) + +We have tried to surface the most important metrics for each exporter, and will be continuing to add support for additional exporters in future releases. If you would like to add support for other official exporters, [contributions](#adding-to-the-library) are welcome. + +## Identifying Environments + +GitLab retrieves performance data from the configured Prometheus server, and attempts to identifying the presence of known metrics. Once identified, GitLab then needs to be able to map the data to a particular environment. + +In order to isolate and only display relevant metrics for a given environment, GitLab needs a method to detect which labels are associated. To do that, +GitLab will look for the required metrics which have a label that +matches the [$CI_ENVIRONMENT_SLUG][ci-environment-slug]. + +For example if you are deploying to an environment named `production`, there must be a label for the metric with the value of `production`. + +## Adding to the library + +We strive to support the 2-4 most important metrics for each common system service that supports Prometheus. If you are looking for support for a particular exporter which has not yet been added to the library, additions can be made [to the `additional_metrics.yml`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/prometheus/additional_metrics.yml) file. + +> Note: The library is only for monitoring public, common, system services which all customers can benefit from. Support for monitoring [customer proprietary metrics](https://gitlab.com/gitlab-org/gitlab-ee/issues/2273) will be added in a subsequent release. diff --git a/doc/user/project/integrations/prometheus_library/nginx.md b/doc/user/project/integrations/prometheus_library/nginx.md new file mode 100644 index 00000000000..fe238e74e36 --- /dev/null +++ b/doc/user/project/integrations/prometheus_library/nginx.md @@ -0,0 +1,23 @@ +# Monitoring NGINX +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12621) in GitLab 9.4 + +GitLab has support for automatically detecting and monitoring NGINX. This is provided by leveraging the [NGINX VTS exporter](https://github.com/hnlq715/nginx-vts-exporter), which translates [VTS statistics](https://github.com/vozlt/nginx-module-vts) into a Prometheus readable form. + +## Metrics supported + +| Name | Query | +| ---- | ----- | +| Throughput (req/sec) | sum(rate(nginx_requests_total{server_zone!="*", server_zone!="_", %{environment_filter}}[2m])) | +| Latency (ms) | avg(nginx_upstream_response_msecs_avg{%{environment_filter}}) * 1000 | +| HTTP Error Rate (%) | sum(nginx_responses_total{status_code="5xx", %{environment_filter}}) / sum(nginx_responses_total{server_zone!="*", server_zone!="_", %{environment_filter}}) | + +## Configuring Prometheus to monitor for NGINX metrics + +To get started with NGINX monitoring, you should first enable the [VTS statistics](https://github.com/vozlt/nginx-module-vts)) module for your NGINX server. This will capture and display statistics in an HTML readable form. Next, you should install and configure the [NGINX VTS exporter](https://github.com/hnlq715/nginx-vts-exporter) which parses these statistics and translates them into a Prometheus monitoring endpoint. + +If you are using NGINX as your Kubernetes ingress, there is [upcoming direct support](https://github.com/kubernetes/ingress/pull/423) for enabling Prometheus monitoring in the 0.9.0 release. + +## Specifying the Environment label + +In order to isolate and only display relevant metrics for a given environment +however, GitLab needs a method to detect which labels are associated. To do this, GitLab will [look for an `environment` label](metrics.md#identifying-environments). diff --git a/doc/user/project/integrations/samples/cloudwatch.yml b/doc/user/project/integrations/samples/cloudwatch.yml new file mode 100644 index 00000000000..d9b58f52c32 --- /dev/null +++ b/doc/user/project/integrations/samples/cloudwatch.yml @@ -0,0 +1,26 @@ +region: us-east-1 + metrics: + - aws_namespace: AWS/ELB + aws_metric_name: RequestCount + aws_dimensions: [AvailabilityZone, LoadBalancerName] + aws_dimension_select: + LoadBalancerName: [gitlab-ha-lb] + aws_statistics: [Sum] + - aws_namespace: AWS/ELB + aws_metric_name: Latency + aws_dimensions: [AvailabilityZone, LoadBalancerName] + aws_dimension_select: + LoadBalancerName: [gitlab-ha-lb] + aws_statistics: [Average] + - aws_namespace: AWS/ELB + aws_metric_name: HTTPCode_Backend_2XX + aws_dimensions: [AvailabilityZone, LoadBalancerName] + aws_dimension_select: + LoadBalancerName: [gitlab-ha-lb] + aws_statistics: [Sum] + - aws_namespace: AWS/ELB + aws_metric_name: HTTPCode_Backend_5XX + aws_dimensions: [AvailabilityZone, LoadBalancerName] + aws_dimension_select: + LoadBalancerName: [gitlab-ha-lb] + aws_statistics: [Sum] diff --git a/doc/user/project/integrations/samples/prometheus.yml b/doc/user/project/integrations/samples/prometheus.yml index 01bbcaffe1e..30b59e172a1 100644 --- a/doc/user/project/integrations/samples/prometheus.yml +++ b/doc/user/project/integrations/samples/prometheus.yml @@ -24,6 +24,44 @@ data: target_label: environment regex: (.+)-.+-.+ replacement: $1 + - job_name: kubernetes-pods + tls_config: + ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" + insecure_skip_verify: true + bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token" + kubernetes_sd_configs: + - role: pod + api_server: https://kubernetes.default.svc:443 + tls_config: + ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" + bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token" + relabel_configs: + - source_labels: + - __meta_kubernetes_pod_annotation_prometheus_io_scrape + action: keep + regex: 'true' + - source_labels: + - __meta_kubernetes_pod_annotation_prometheus_io_path + action: replace + target_label: __metrics_path__ + regex: "(.+)" + - source_labels: + - __address__ + - __meta_kubernetes_pod_annotation_prometheus_io_port + action: replace + regex: "([^:]+)(?::[0-9]+)?;([0-9]+)" + replacement: "$1:$2" + target_label: __address__ + - action: labelmap + regex: __meta_kubernetes_pod_label_(.+) + - source_labels: + - __meta_kubernetes_namespace + action: replace + target_label: kubernetes_namespace + - source_labels: + - __meta_kubernetes_pod_name + action: replace + target_label: kubernetes_pod_name --- apiVersion: v1 kind: Service diff --git a/doc/user/project/issues/index.md b/doc/user/project/issues/index.md index e55e2aea023..1f78849a92c 100644 --- a/doc/user/project/issues/index.md +++ b/doc/user/project/issues/index.md @@ -5,13 +5,13 @@ for tracking the evolution of a new idea or the process of solving a problem. It allows you, your team, and your collaborators to share -and discuss proposals, before and while implementing them. +and discuss proposals before and while implementing them. Issues and the GitLab Issue Tracker are available in all [GitLab Products](https://about.gitlab.com/products/) as part of the [GitLab Workflow](https://about.gitlab.com/2016/10/25/gitlab-workflow-an-overview/). -## Use-Cases +## Use cases Issues can have endless applications. Just to exemplify, these are some cases for which creating issues are most used: @@ -23,7 +23,28 @@ some cases for which creating issues are most used: - Obtaining support - Elaborating new code implementations -See also the blog post [Always start a discussion with an issue](https://about.gitlab.com/2016/03/03/start-with-an-issue/). +See also the blog post "[Always start a discussion with an issue](https://about.gitlab.com/2016/03/03/start-with-an-issue/)". + +### Keep private things private + +For instance, let's assume you have a public project but want to start a discussion on something +you don't want to be public. With [Confidential Issues](#confidential-issues), +you can discuss private matters among the project members, and still keep +your project public, open to collaboration. + +### Streamline collaboration + +With [Multiple Assignees for Issues](https://docs.gitlab.com/ee/user/project/issues/multiple_assignees_for_issues.html), +available in [GitLab Enterprise Edition Starter](https://about.gitlab.com/gitlab-ee/) +you can streamline collaboration and allow shared responsibilities to be clearly displayed. +All assignees are shown across your workflows and receive notifications (as they +would as single assignees), simplifying communication and ownership. + +### Consistent collaboration + +Create [issue templates](#issue-templates) to make collaboration consistent and +containing all information you need. For example, you can create a template +for feature proposals and another one for bug reports. ## Issue Tracker @@ -96,8 +117,8 @@ Find GitLab Issue Boards by navigating to your **Project's Dashboard** > **Issue Read through the documentation for [Issue Boards](../issue_board.md) to find out more about this feature. -[Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards) -are available only in [GitLab Enterprise Edition](https://about.gitlab.com/gitlab-ee/). +With [GitLab Enterprise Edition Starter](https://about.gitlab.com/gitlab-ee/), you can also +create various boards per project with [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards). ### Issue's API diff --git a/doc/user/project/issues/issues_functionalities.md b/doc/user/project/issues/issues_functionalities.md index 294176e61f9..138276edf07 100644 --- a/doc/user/project/issues/issues_functionalities.md +++ b/doc/user/project/issues/issues_functionalities.md @@ -43,7 +43,7 @@ assigned to them if they created the issue themselves. ##### 3.1. Multiple Assignees (EES/EEP) -Issue Weights are only available in [GitLab Enterprise Edition](https://about.gitlab.com/gitlab-ee/). +Multiple Assignees are only available in [GitLab Enterprise Edition](https://about.gitlab.com/gitlab-ee/). Often multiple people likely work on the same issue together, which can especially be difficult to track in large teams @@ -52,9 +52,7 @@ where there is shared ownership of an issue. In GitLab Enterprise Edition, you can also select multiple assignees to an issue. -> **Note:** -Multiple Assignees was [introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/1904) -in [GitLab Enterprise Edition 9.2](https://about.gitlab.com/2017/05/22/gitlab-9-2-released/#multiple-assignees-for-issues). +Learn more on the [Multiple Assignees documentation](https://docs.gitlab.com/ee/user/project/issues/multiple_assignees_for_issues.html). #### 4. Milestone diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md index 954454f7e7a..9bdf2a998d3 100644 --- a/doc/user/project/merge_requests/index.md +++ b/doc/user/project/merge_requests/index.md @@ -3,6 +3,59 @@ Merge requests allow you to exchange changes you made to source code and collaborate with other people on the same project. +## Overview + +A Merge Request (**MR**) is the basis of GitLab as a code collaboration +and version control platform. +Is it simple as the name implies: a _request_ to _merge_ one branch into another. + +With GitLab merge requests, you can: + +- Compare the changes between two [branches](https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell#_git_branching) +- [Review and discuss](../../discussions/index.md#discussions) the proposed modifications inline +- Live preview the changes when [Review Apps](../../../ci/review_apps/index.md) is configured for your project +- Build, test, and deploy your code in a per-branch basis with built-in [GitLab CI/CD](../../../ci/README.md) +- Prevent the merge request from being merged before it's ready with [WIP MRs](#work-in-progress-merge-requests) +- View the deployment process through [Pipeline Graphs](../../../ci/pipelines.md#pipeline-graphs) +- [Automatically close the issue(s)](../../project/issues/closing_issues.md#via-merge-request) that originated the implementation proposed in the merge request +- Assign it to any registered user, and change the assignee how many times you need +- Assign a [milestone](../../project/milestones/index.md) and track the development of a broader implementation +- Organize your issues and merge requests consistently throughout the project with [labels](../../project/labels.md) +- Add a time estimation and the time spent with that merge request with [Time Tracking](../../../workflow/time_tracking.html#time-tracking) +- [Resolve merge conflicts from the UI](#resolve-conflicts) + +With **[GitLab Enterprise Edition][ee]**, you can also: + +- View the deployment process across projects with [Multi-Project Pipeline Graphs](https://docs.gitlab.com/ee/ci/multi_project_pipeline_graphs.html#multi-project-pipeline-graphs) (available only in GitLab Enterprise Edition Premium) +- Request [approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) from your managers (available in GitLab Enterprise Edition Starter) +- Enable [fast-forward merge requests](https://docs.gitlab.com/ee/user/project/merge_requests/fast_forward_merge.html) (available in GitLab Enterprise Edition Starter) +- [Squash and merge](https://docs.gitlab.com/ee/user/project/merge_requests/squash_and_merge.html) for a cleaner commit history (available in GitLab Enterprise Edition Starter) +- Enable [semi-linear history merge requests](https://docs.gitlab.com/ee/user/project/merge_requests/index.html#semi-linear-history-merge-requests) as another security layer to guarantee the pipeline is passing in the target branch (available in GitLab Enterprise Edition Starter) +- Analise the impact of your changes with [Code Quality reports](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html) (available in GitLab Enterprise Edition Starter) + +## Use cases + +A. Consider you are a software developer working in a team: + +1. You checkout a new branch, and submit your changes through a merge request +1. You gather feedback from your team +1. You work on the implementation optimizing code with [Code Quality reports](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html) (available in GitLab Enterprise Edition Starter) +1. You build and test your changes with GitLab CI/CD +1. You request the approval from your manager +1. Your manager pushes a commit with his final review, [approves the merge request](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html), and set it to [merge when pipeline succeeds](#merge-when-pipeline-succeeds) (Merge Request Approvals are available in GitLab Enterprise Edition Starter) +1. Your changes get deployed to production with [manual actions](../../../ci/yaml/README.md#manual-actions) for GitLab CI/CD +1. Your implementations were successfully shipped to your customer + +B. Consider you're a web developer writing a webpage for your company's: + +1. You checkout a new branch, and submit a new page through a merge request +1. You gather feedback from your reviewers +1. Your changes are previewed with [Review Apps](../../../ci/review_apps/index.md) +1. You request your web designers for their implementation +1. You request the [approval](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) from your manager (available in GitLab Enterprise Edition Starter) +1. Once approved, your merge request is [squashed and merged](https://docs.gitlab.com/ee/user/project/merge_requests/squash_and_merge.html), and [deployed to staging with GitLab Pages](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/) (Squash and Merge is available in GitLab Enterprise Edition Starter) +1. Your production team [cherry picks](#cherry-pick-changes) the merge commit into production + ## Authorization for merge requests There are two main ways to have a merge request flow with GitLab: @@ -79,6 +132,16 @@ specific commit page. You can append `?w=1` while on the diffs page of a merge request to ignore any whitespace changes. +## Live preview with Review Apps + +If you configured [Review Apps](https://about.gitlab.com/features/review-apps/) for your project, +you can preview the changes submitted to a feature-branch through a merge request +in a per-branch basis. No need to checkout the branch, install and preview locally; +all your changes will be available to preview by anyone with the Review Apps link. + +[Read more about Review Apps.](../../../ci/review_apps/index.md) + + ## Tips Here are some tips that will help you be more efficient with merge requests in @@ -167,3 +230,4 @@ git checkout origin/merge-requests/1 ``` [protected branches]: ../protected_branches.md +[ee]: https://about.gitlab.com/gitlab-ee/ "GitLab Enterprise Edition" diff --git a/doc/user/project/pages/getting_started_part_one.md b/doc/user/project/pages/getting_started_part_one.md index 2f104c7becc..46fa4378fe7 100644 --- a/doc/user/project/pages/getting_started_part_one.md +++ b/doc/user/project/pages/getting_started_part_one.md @@ -41,7 +41,7 @@ server up and running for your GitLab instance. Before we begin, let's understand a few concepts first. -### Static sites +## Static sites GitLab Pages only supports static websites, meaning, your output files must be HTML, CSS, and JavaScript only. @@ -51,14 +51,14 @@ CSS, and JS, or use a [Static Site Generator (SSG)](https://www.staticgen.com/) to simplify your code and build the static site for you, which is highly recommendable and much faster than hardcoding. -#### Further Reading +### Further reading - Read through this technical overview on [Static versus Dynamic Websites](https://about.gitlab.com/2016/06/03/ssg-overview-gitlab-pages-part-1-dynamic-x-static/) - Understand [how modern Static Site Generators work](https://about.gitlab.com/2016/06/10/ssg-overview-gitlab-pages-part-2/) and what you can add to your static site - You can use [any SSG with GitLab Pages](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/) - Fork an [example project](https://gitlab.com/pages) to build your website based upon -### GitLab Pages domain +## GitLab Pages domain If you set up a GitLab Pages project on GitLab.com, it will automatically be accessible under a @@ -73,9 +73,9 @@ Pages wildcard domain. This guide is valid for any GitLab instance, you just need to replace Pages wildcard domain on GitLab.com (`*.gitlab.io`) with your own. -#### Practical examples +### Practical examples -**Project Websites:** +#### Project Websites - You created a project called `blog` under your username `john`, therefore your project URL is `https://gitlab.com/john/blog/`. @@ -87,16 +87,21 @@ URL is `https://gitlab.com/websites/blog/`. Once you enable GitLab Pages for this project, the site will live under `https://websites.gitlab.io/blog/`. -**User and Group Websites:** +#### User and Group Websites - Under your username, `john`, you created a project called `john.gitlab.io`. Your project URL will be `https://gitlab.com/john/john.gitlab.io`. Once you enable GitLab Pages for your project, your website will be published under `https://john.gitlab.io`. - Under your group `websites`, you created a project called -`websites.gitlab.io`. your project's URL will be `https://gitlab.com/websites/websites.gitlab.io`. Once you enable GitLab Pages for your project, +`websites.gitlab.io`. your project's URL will be `https://gitlab.com/websites/websites.gitlab.io`. +Once you enable GitLab Pages for your project, your website will be published under `https://websites.gitlab.io`. +>**Note:** +GitLab Pages [does **not** support subgroups](../../group/subgroups/index.md#limitations). +You can only create the highest level group website. + **General example:** - On GitLab.com, a project site will always be available under diff --git a/doc/user/project/pages/introduction.md b/doc/user/project/pages/introduction.md index deaceabb7c5..9ecf7a3a8e7 100644 --- a/doc/user/project/pages/introduction.md +++ b/doc/user/project/pages/introduction.md @@ -398,6 +398,9 @@ don't redirect HTTP to HTTPS. [rfc]: https://tools.ietf.org/html/rfc2818#section-3.1 "HTTP Over TLS RFC" +GitLab Pages [does **not** support subgroups](../../group/subgroups/index.md#limitations). +You can only create the highest level group website. + ## Redirects in GitLab Pages Since you cannot use any custom server configuration files, like `.htaccess` or diff --git a/doc/workflow/README.md b/doc/workflow/README.md index 54d4028a50a..925bbf76d49 100644 --- a/doc/workflow/README.md +++ b/doc/workflow/README.md @@ -6,7 +6,7 @@ - [Description templates](../user/project/description_templates.md) - [Feature branch workflow](workflow.md) - [GitLab Flow](gitlab_flow.md) -- [Groups](groups.md) +- [Groups](../user/group/index.md) - Issues - The GitLab Issue Tracker is an advanced and complete tool for tracking the evolution of a new idea or the process of solving a problem. - [Confidential issues](../user/project/issues/confidential_issues.md) diff --git a/doc/workflow/groups.md b/doc/workflow/groups.md index 1645e7e8d65..06eec1ed928 100644 --- a/doc/workflow/groups.md +++ b/doc/workflow/groups.md @@ -1,97 +1,2 @@ -# GitLab Groups -GitLab groups allow you to group projects into directories and give users access to several projects at once. - -When you create a new project in GitLab, the default namespace for the project is the personal namespace associated with your GitLab user. -In this document we will see how to create groups, put projects in groups and manage who can access the projects in a group. - -## Creating groups - -You can create a group by going to the 'Groups' tab of the GitLab dashboard and clicking the 'New group' button. - -![Click the 'New group' button in the 'Groups' tab](groups/new_group_button.png) - -Next, enter the path and name (required) and the optional description and group avatar. - -![Fill in the path for your new group](groups/new_group_form.png) - -When your group has been created you are presented with the group dashboard feed, which will be empty. - -![Group dashboard](groups/group_dashboard.png) - -You can use the 'New project' button to add a project to the new group. - -## Transferring an existing project into a group - -You can transfer an existing project into a group you have at least Master access in from the project settings page. -The option to transfer a project is only available if you are the Owner of the project. -First scroll down to the 'Dangerous settings' and click 'Show them to me'. -Now you can pick any of the groups you have at least Master access in as the new namespace for the group. - -![Transfer a project to a new namespace](groups/transfer_project.png) - -GitLab administrators can use the admin interface to move any project to any namespace if needed. - -## Adding users to a group - -One of the benefits of putting multiple projects in one group is that you can give a user to access to all projects in the group with one action. - -Suppose we have a group with two projects. - -![Group with two projects](groups/group_with_two_projects.png) - -On the 'Group Members' page we can now add a new user Barry to the group. - -![Add user Barry to the group](groups/add_member_to_group.png) - -Now because Barry is a 'Developer' member of the 'Open Source' group, he automatically gets 'Developer' access to all projects in the 'Open Source' group. - -![Barry has 'Developer' access to GitLab CI](groups/project_members_via_group.png) - -If necessary, you can increase the access level of an individual user for a specific project, by adding them as a Member to the project. - -![Barry effectively has 'Master' access to GitLab CI now](groups/override_access_level.png) - -## Requesting access to a group - -As a group owner you can enable or disable non members to request access to -your group. Go to the group settings and click on **Allow users to request access**. - -As a user, you can request to be a member of a group. Go to the group you'd -like to be a member of, and click the **Request Access** button on the right -side of your screen. - -![Request access button](groups/request_access_button.png) - ---- - -Group owners & masters will be notified of your request and will be able to approve or -decline it on the members page. - -![Manage access requests](groups/access_requests_management.png) - ---- - -If you change your mind before your request is approved, just click the -**Withdraw Access Request** button. - -![Withdraw access request button](groups/withdraw_access_request_button.png) - -## Managing group memberships via LDAP - -In GitLab Enterprise Edition it is possible to manage GitLab group memberships using LDAP groups. -See [the GitLab Enterprise Edition documentation](http://docs.gitlab.com/ee/integration/ldap.html) for more information. - -## Allowing only admins to create groups - -By default, any GitLab user can create new groups. -This ability can be disabled for individual users from the admin panel. -It is also possible to configure GitLab so that new users default to not being able to create groups: - -``` -# For omnibus-gitlab, put the following in /etc/gitlab/gitlab.rb -gitlab_rails['gitlab_default_can_create_group'] = false - -# For installations from source, uncomment the 'default_can_create_group' -# line in /home/git/gitlab/config/gitlab.yml -``` +This document was moved to [another location](../user/group/index.md). diff --git a/doc/workflow/groups/add_member_to_group.png b/doc/workflow/groups/add_member_to_group.png Binary files differdeleted file mode 100644 index a10d5032bb0..00000000000 --- a/doc/workflow/groups/add_member_to_group.png +++ /dev/null diff --git a/doc/workflow/groups/group_dashboard.png b/doc/workflow/groups/group_dashboard.png Binary files differdeleted file mode 100644 index a5829f25808..00000000000 --- a/doc/workflow/groups/group_dashboard.png +++ /dev/null diff --git a/doc/workflow/groups/group_with_two_projects.png b/doc/workflow/groups/group_with_two_projects.png Binary files differdeleted file mode 100644 index 76d0a1b8ab2..00000000000 --- a/doc/workflow/groups/group_with_two_projects.png +++ /dev/null diff --git a/doc/workflow/groups/new_group_button.png b/doc/workflow/groups/new_group_button.png Binary files differdeleted file mode 100644 index 7155d6280bd..00000000000 --- a/doc/workflow/groups/new_group_button.png +++ /dev/null diff --git a/doc/workflow/groups/override_access_level.png b/doc/workflow/groups/override_access_level.png Binary files differdeleted file mode 100644 index 2b3e9a49842..00000000000 --- a/doc/workflow/groups/override_access_level.png +++ /dev/null diff --git a/doc/workflow/groups/project_members_via_group.png b/doc/workflow/groups/project_members_via_group.png Binary files differdeleted file mode 100644 index 878c9a03ac9..00000000000 --- a/doc/workflow/groups/project_members_via_group.png +++ /dev/null diff --git a/doc/workflow/groups/transfer_project.png b/doc/workflow/groups/transfer_project.png Binary files differdeleted file mode 100644 index 52161817f11..00000000000 --- a/doc/workflow/groups/transfer_project.png +++ /dev/null diff --git a/doc/workflow/share_projects_with_other_groups.md b/doc/workflow/share_projects_with_other_groups.md index 8e50cb03e63..40d756bc199 100644 --- a/doc/workflow/share_projects_with_other_groups.md +++ b/doc/workflow/share_projects_with_other_groups.md @@ -5,7 +5,7 @@ to a project with a single action. ## Groups as collections of users -Groups are used primarily to [create collections of projects](groups.md), but you can also +Groups are used primarily to [create collections of projects](../user/group/index.md), but you can also take advantage of the fact that groups define collections of _users_, namely the group members. diff --git a/features/dashboard/dashboard.feature b/features/dashboard/dashboard.feature deleted file mode 100644 index 1af4d46dec9..00000000000 --- a/features/dashboard/dashboard.feature +++ /dev/null @@ -1,70 +0,0 @@ -@dashboard -Feature: Dashboard - Background: - Given I sign in as a user - And I own project "Shop" - And project "Shop" has push event - And project "Shop" has CI enabled - And project "Shop" has CI build - And project "Shop" has labels: "bug", "feature", "enhancement" - And project "Shop" has issue: "bug report" - And I visit dashboard page - - Scenario: I should see projects list - Then I should see "New Project" link - Then I should see "Shop" project link - Then I should see "Shop" project CI status - - @javascript - Scenario: I should see activity list - And I visit dashboard activity page - Then I should see project "Shop" activity feed - - Scenario: I should see groups list - Given I have group with projects - And I visit dashboard page - Then I should see groups list - - @javascript - Scenario: I should see last push widget - Then I should see last push widget - And I click "Create Merge Request" link - Then I see prefilled new Merge Request page - - @javascript - Scenario: Sorting Issues - Given I visit dashboard issues page - And I sort the list by "Oldest updated" - And I visit dashboard activity page - And I visit dashboard issues page - Then The list should be sorted by "Oldest updated" - - @javascript - Scenario: Filtering Issues by label - Given project "Shop" has issue "Bugfix1" with label "feature" - When I visit dashboard issues page - And I filter the list by label "feature" - Then I should see "Bugfix1" in issues list - - @javascript - Scenario: Visiting Project's issues after sorting - Given I visit dashboard issues page - And I sort the list by "Oldest updated" - And I visit project "Shop" issues page - Then The list should be sorted by "Oldest updated" - - @javascript - Scenario: Sorting Merge Requests - Given I visit dashboard merge requests page - And I sort the list by "Oldest updated" - And I visit dashboard activity page - And I visit dashboard merge requests page - Then The list should be sorted by "Oldest updated" - - @javascript - Scenario: Visiting Project's merge requests after sorting - Given project "Shop" has a "Bugfix MR" merge request open - And I visit dashboard merge requests page - And I sort the list by "Oldest updated" - And I visit project "Shop" merge requests page - Then The list should be sorted by "Oldest updated" diff --git a/features/dashboard/event_filters.feature b/features/dashboard/event_filters.feature deleted file mode 100644 index 8c3ff64164f..00000000000 --- a/features/dashboard/event_filters.feature +++ /dev/null @@ -1,58 +0,0 @@ -@dashboard -Feature: Event Filters - Background: - Given I sign in as a user - And I own a project - And this project has push event - And this project has new member event - And this project has merge request event - And I visit dashboard activity page - - @javascript - Scenario: I should see all events - Then I should see push event - And I should see new member event - And I should see merge request event - - @javascript - Scenario: I should see only pushed events - When I click "push" event filter - Then I should see push event - And I should not see new member event - And I should not see merge request event - - @javascript - Scenario: I should see only joined events - When I click "team" event filter - Then I should see new member event - And I should not see push event - And I should not see merge request event - - @javascript - Scenario: I should see only merged events - When I click "merge" event filter - Then I should see merge request event - And I should not see push event - And I should not see new member event - - @javascript - Scenario: I should see only selected events while page reloaded - When I click "push" event filter - And I visit dashboard activity page - Then I should see push event - And I should not see new member event - When I click "team" event filter - And I visit dashboard activity page - Then I should not see push event - And I should see new member event - And I should not see merge request event - When I click "push" event filter - And I visit dashboard activity page - Then I should see push event - And I should not see new member event - And I should not see merge request event - When I click "merge" event filter - And I visit dashboard activity page - Then I should see merge request event - And I should not see push event - And I should not see new member event diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature deleted file mode 100644 index 59a625056d6..00000000000 --- a/features/project/source/browse_files.feature +++ /dev/null @@ -1,323 +0,0 @@ -Feature: Project Source Browse Files - Background: - Given I sign in as a user - And I own project "Shop" - Given I visit project source page - - Scenario: I browse files from master branch - Then I should see files from repository - - Scenario: I browse files for specific ref - Given I visit project source page for "6d39438" - Then I should see files from repository for "6d39438" - - @javascript - Scenario: I browse file content - Given I click on ".gitignore" file in repo - Then I should see its content - - Scenario: I browse raw file - Given I visit blob file from repo - And I click link "Raw" - Then I should see raw file content - - Scenario: I can create file - Given I click on "New file" link in repo - Then I can see new file page - - Scenario: I can create file when I don't have write access - Given I don't have write access - And I click on "New file" link in repo - Then I should see a notice about a new fork having been created - Then I can see new file page - - @javascript - Scenario: I can create and commit file - Given I click on "New file" link in repo - And I edit code - And I fill the new file name - And I fill the commit message - And I click on "Commit changes" - Then I am redirected to the new file - And I should see its new content - - @javascript - Scenario: I can create and commit file when I don't have write access - Given I don't have write access - And I click on "New file" link in repo - And I edit code - And I fill the new file name - And I fill the commit message - And I click on "Commit changes" - Then I am redirected to the fork's new merge request page - And I can see the new commit message - - @javascript - Scenario: I can create and commit file with new lines at the end of file - Given I click on "New file" link in repo - And I edit code with new lines at end of file - And I fill the new file name - And I fill the commit message - And I click on "Commit changes" - Then I am redirected to the new file - And I click button "Edit" - And I should see its content with new lines preserved at end of file - - @javascript - Scenario: I can create and commit file and specify new branch - Given I click on "New file" link in repo - And I edit code - And I fill the new file name - And I fill the commit message - And I fill the new branch name - And I click on "Commit changes" - Then I am redirected to the new merge request page - When I click on "Changes" tab - And I should see its new content - - @javascript - Scenario: I can upload file and commit - Given I click on "Upload file" link in repo - And I upload a new text file - And I fill the upload file commit message - And I fill the new branch name - And I click on "Upload file" - Then I can see the new commit message - And I am redirected to the new merge request page - When I click on "Changes" tab - Then I can see the new text file - - @javascript - Scenario: I can upload file and commit when I don't have write access - Given I don't have write access - And I click on "Upload file" link in repo - Then I should see a notice about a new fork having been created - When I click on "Upload file" link in repo - And I upload a new text file - And I fill the upload file commit message - And I click on "Upload file" - Then I can see the new commit message - And I am redirected to the fork's new merge request page - When I click on "Changes" tab - Then I can see the new text file - - @javascript - Scenario: I can replace file and commit - Given I click on ".gitignore" file in repo - And I see the ".gitignore" - And I click on "Replace" - And I replace it with a text file - And I fill the replace file commit message - And I click on "Replace file" - Then I can see the new text file - And I can see the replacement commit message - - @javascript - Scenario: I can replace file and commit when I don't have write access - Given I don't have write access - And I click on ".gitignore" file in repo - And I see the ".gitignore" - And I click on "Replace" - Then I should see a Fork/Cancel combo - And I click button "Fork" - Then I should see a notice about a new fork having been created - When I click on "Replace" - And I replace it with a text file - And I fill the replace file commit message - And I click on "Replace file" - And I can see the replacement commit message - And I am redirected to the fork's new merge request page - When I click on "Changes" tab - Then I can see the new text file - - @javascript - Scenario: I can create file with a directory name - Given I click on "New file" link in repo - And I fill the new file name with a new directory - And I edit code - And I fill the commit message - And I click on "Commit changes" - Then I am redirected to the new file with directory - And I should see its new content - - @javascript - Scenario: I can edit file - Given I click on ".gitignore" file in repo - And I click button "Edit" - Then I can edit code - - @javascript - Scenario: I can edit file when I don't have write access - Given I don't have write access - And I click on ".gitignore" file in repo - And I click button "Edit" - Then I should see a Fork/Cancel combo - And I click button "Fork" - Then I should see a notice about a new fork having been created - And I can edit code - - Scenario: If the file is binary the edit link is hidden - Given I visit a binary file in the repo - Then I cannot see the edit button - - @javascript - Scenario: I can edit and commit file - Given I click on ".gitignore" file in repo - And I click button "Edit" - And I edit code - And I fill the commit message - And I click on "Commit changes" - Then I am redirected to the ".gitignore" - And I should see its new content - - @javascript - Scenario: I can edit and commit file when I don't have write access - Given I don't have write access - And I click on ".gitignore" file in repo - And I click button "Edit" - Then I should see a Fork/Cancel combo - And I click button "Fork" - And I edit code - And I fill the commit message - And I click on "Commit changes" - Then I am redirected to the fork's new merge request page - And I can see the new commit message - - @javascript - Scenario: I can edit and commit file to new branch - Given I click on ".gitignore" file in repo - And I click button "Edit" - And I edit code - And I fill the commit message - And I fill the new branch name - And I click on "Commit changes" - Then I am redirected to the new merge request page - Then I click on "Changes" tab - And I should see its new content - - @javascript @wip - Scenario: If I don't change the content of the file I see an error message - Given I click on ".gitignore" file in repo - And I click button "edit" - And I fill the commit message - And I click on "Commit changes" - # Test fails because carriage returns are added to the file. - Then I am on the ".gitignore" edit file page - And I see a commit error message - - @javascript - Scenario: I can create directory in repo - When I click on "New directory" link in repo - And I fill the new directory name - And I fill the commit message - And I fill the new branch name - And I click on "Create directory" - Then I am redirected to the new merge request page - - @javascript - Scenario: I can create directory in repo when I don't have write access - Given I don't have write access - When I click on "New directory" link in repo - Then I should see a notice about a new fork having been created - When I click on "New directory" link in repo - And I fill the new directory name - And I fill the commit message - And I click on "Create directory" - Then I am redirected to the fork's new merge request page - - @javascript - Scenario: I attempt to create an existing directory - When I click on "New directory" link in repo - And I fill an existing directory name - And I fill the commit message - And I click on "Create directory" - Then I see "Unable to create directory" - And I am redirected to the root directory - - @javascript - Scenario: I can see editing preview - Given I click on ".gitignore" file in repo - And I click button "Edit" - And I edit code - And I click link "Diff" - Then I see diff - - @javascript - Scenario: I can delete file and commit - Given I click on ".gitignore" file in repo - And I see the ".gitignore" - And I click on "Delete" - And I fill the commit message - And I click on "Delete file" - Then I am redirected to the files URL - And I don't see the ".gitignore" - - @javascript - Scenario: I can delete file and commit when I don't have write access - Given I don't have write access - And I click on ".gitignore" file in repo - And I see the ".gitignore" - And I click on "Delete" - Then I should see a Fork/Cancel combo - And I click button "Fork" - Then I should see a notice about a new fork having been created - When I click on "Delete" - And I fill the commit message - And I click on "Delete file" - Then I am redirected to the fork's new merge request page - And I can see the new commit message - - Scenario: I can browse directory with Browse Dir - Given I click on files directory - And I click on History link - Then I see Browse dir link - - Scenario: I can browse file with Browse File - Given I click on readme file - And I click on History link - Then I see Browse file link - - Scenario: I can browse code with Browse Code - Given I click on History link - Then I see Browse code link - - # Permalink - - Scenario: I click on the permalink link from a branch ref - Given I click on ".gitignore" file in repo - And I click on Permalink - Then I am redirected to the permalink URL - - Scenario: I don't see the permalink link from a SHA ref - Given I visit project source page for "6d394385cf567f80a8fd85055db1ab4c5295806f" - And I click on ".gitignore" file in repo - Then I don't see the permalink link - - @javascript - Scenario: I browse code with single quotes in the ref - Given I switch ref to 'test' - And I see the ref 'test' has been selected - And I visit the 'test' tree - Then I see the commit data - - @javascript - Scenario: I browse code with a leading dot in the directory - Given I switch ref to fix - And I visit the fix tree - Then I see the commit data for a directory with a leading dot - - Scenario: I browse LFS object - Given I click on "files/lfs/lfs_object.iso" file in repo - Then I should see download link and object size - And I should not see lfs pointer details - And I should see buttons for allowed commands - - @javascript - Scenario: I preview an SVG file - Given I click on "Upload file" link in repo - And I upload a new SVG file - And I fill the upload file commit message - And I fill the new branch name - And I click on "Upload file" - Given I visit the SVG file - Then I can see the new rendered SVG image diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb deleted file mode 100644 index 0960f49aad3..00000000000 --- a/features/steps/dashboard/dashboard.rb +++ /dev/null @@ -1,83 +0,0 @@ -class Spinach::Features::Dashboard < Spinach::FeatureSteps - include SharedAuthentication - include SharedPaths - include SharedProject - include SharedIssuable - - step 'I should see "New Project" link' do - expect(page).to have_link "New project" - end - - step 'I should see "Shop" project link' do - expect(page).to have_link "Shop" - end - - step 'I should see "Shop" project CI status' do - expect(page).to have_link "Commit: skipped" - end - - step 'I should see last push widget' do - expect(page).to have_content "You pushed to fix" - expect(page).to have_link "Create merge request" - end - - step 'I click "Create merge request" link' do - find_link("Create merge request", visible: false).trigger('click') - end - - step 'I see prefilled new Merge Request page' do - expect(page).to have_selector('.merge-request-form') - expect(current_path).to eq project_new_merge_request_path(@project) - expect(find("#merge_request_target_project_id").value).to eq @project.id.to_s - expect(find("input#merge_request_source_branch").value).to eq "fix" - expect(find("input#merge_request_target_branch").value).to eq "master" - end - - step 'I have group with projects' do - @group = create(:group) - @project = create(:empty_project, namespace: @group) - @event = create(:closed_issue_event, project: @project) - - @project.team << [current_user, :master] - end - - step 'I should see projects list' do - @user.authorized_projects.all.each do |project| - expect(page).to have_link project.name_with_namespace - end - end - - step 'I should see groups list' do - Group.all.each do |group| - expect(page).to have_link group.name - end - end - - step 'group has a projects that does not belongs to me' do - @forbidden_project1 = create(:empty_project, group: @group) - @forbidden_project2 = create(:empty_project, group: @group) - end - - step 'I should see 1 project at group list' do - expect(find('span.last_activity/span')).to have_content('1') - end - - step 'I filter the list by label "feature"' do - page.within ".labels-filter" do - find('.dropdown').click - click_link "feature" - end - end - - step 'I should see "Bugfix1" in issues list' do - page.within "ul.content-list" do - expect(page).to have_content "Bugfix1" - end - end - - step 'project "Shop" has issue "Bugfix1" with label "feature"' do - project = Project.find_by(name: "Shop") - issue = create(:issue, title: "Bugfix1", project: project, assignees: [current_user]) - issue.labels << project.labels.find_by(title: 'feature') - end -end diff --git a/features/steps/dashboard/event_filters.rb b/features/steps/dashboard/event_filters.rb deleted file mode 100644 index a745254cc31..00000000000 --- a/features/steps/dashboard/event_filters.rb +++ /dev/null @@ -1,92 +0,0 @@ -class Spinach::Features::EventFilters < Spinach::FeatureSteps - include WaitForRequests - include SharedAuthentication - include SharedPaths - include SharedProject - - step 'I should see push event' do - expect(page).to have_selector('span.pushed') - end - - step 'I should not see push event' do - expect(page).not_to have_selector('span.pushed') - end - - step 'I should see new member event' do - expect(page).to have_selector('span.joined') - end - - step 'I should not see new member event' do - expect(page).not_to have_selector('span.joined') - end - - step 'I should see merge request event' do - expect(page).to have_selector('span.accepted') - end - - step 'I should not see merge request event' do - expect(page).not_to have_selector('span.accepted') - end - - step 'this project has push event' do - data = { - before: Gitlab::Git::BLANK_SHA, - after: "0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e", - ref: "refs/heads/new_design", - user_id: @user.id, - user_name: @user.name, - repository: { - name: @project.name, - url: "localhost/rubinius", - description: "", - homepage: "localhost/rubinius", - private: true - } - } - - @event = Event.create( - project: @project, - action: Event::PUSHED, - data: data, - author_id: @user.id - ) - end - - step 'this project has new member event' do - user = create(:user, { name: "John Doe" }) - Event.create( - project: @project, - author_id: user.id, - action: Event::JOINED - ) - end - - step 'this project has merge request event' do - merge_request = create :merge_request, author: @user, source_project: @project, target_project: @project - Event.create( - project: @project, - action: Event::MERGED, - target_id: merge_request.id, - target_type: "MergeRequest", - author_id: @user.id - ) - end - - When 'I click "push" event filter' do - wait_for_requests - click_link("Push events") - wait_for_requests - end - - When 'I click "team" event filter' do - wait_for_requests - click_link("Team") - wait_for_requests - end - - When 'I click "merge" event filter' do - wait_for_requests - click_link("Merge events") - wait_for_requests - end -end diff --git a/features/steps/groups.rb b/features/steps/groups.rb index 0aedc422563..6b288b47da4 100644 --- a/features/steps/groups.rb +++ b/features/steps/groups.rb @@ -81,7 +81,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps step 'I should see new group "Owned" avatar' do expect(owned_group.avatar).to be_instance_of AvatarUploader - expect(owned_group.avatar.url).to eq "/uploads/system/group/avatar/#{Group.find_by(name: "Owned").id}/banana_sample.gif" + expect(owned_group.avatar.url).to eq "/uploads/-/system/group/avatar/#{Group.find_by(name: "Owned").id}/banana_sample.gif" end step 'I should see the "Remove avatar" button' do diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb index 254c26bb6af..4b88cb5e27f 100644 --- a/features/steps/profile/profile.rb +++ b/features/steps/profile/profile.rb @@ -36,7 +36,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps step 'I should see new avatar' do expect(@user.avatar).to be_instance_of AvatarUploader - expect(@user.avatar.url).to eq "/uploads/system/user/avatar/#{@user.id}/banana_sample.gif" + expect(@user.avatar.url).to eq "/uploads/-/system/user/avatar/#{@user.id}/banana_sample.gif" end step 'I should see the "Remove avatar" button' do diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb index 7d34331db46..170e2f16c80 100644 --- a/features/steps/project/project.rb +++ b/features/steps/project/project.rb @@ -38,7 +38,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps step 'I should see new project avatar' do expect(@project.avatar).to be_instance_of AvatarUploader url = @project.avatar.url - expect(url).to eq "/uploads/system/project/avatar/#{@project.id}/banana_sample.gif" + expect(url).to eq "/uploads/-/system/project/avatar/#{@project.id}/banana_sample.gif" end step 'I should see the "Remove avatar" button' do diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb index 729e2b8982c..da1cdd9f897 100644 --- a/features/steps/shared/project.rb +++ b/features/steps/shared/project.rb @@ -239,11 +239,6 @@ module SharedProject create(:label, project: project, title: 'enhancement') end - step 'project "Shop" has issue: "bug report"' do - project = Project.find_by(name: "Shop") - create(:issue, project: project, title: "bug report") - end - step 'project "Shop" has CI enabled' do project = Project.find_by(name: "Shop") project.enable_ci diff --git a/lib/api/entities.rb b/lib/api/entities.rb index f4796f311a5..09a88869063 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -314,12 +314,35 @@ module API expose :id end + class MergeRequestSimple < ProjectEntity + expose :title + expose :web_url do |merge_request, options| + Gitlab::UrlBuilder.build(merge_request) + end + end + class MergeRequestBasic < ProjectEntity expose :target_branch, :source_branch - expose :upvotes, :downvotes + expose :upvotes do |merge_request, options| + if options[:issuable_metadata] + options[:issuable_metadata][merge_request.id].upvotes + else + merge_request.upvotes + end + end + expose :downvotes do |merge_request, options| + if options[:issuable_metadata] + options[:issuable_metadata][merge_request.id].downvotes + else + merge_request.downvotes + end + end expose :author, :assignee, using: Entities::UserBasic expose :source_project_id, :target_project_id - expose :label_names, as: :labels + expose :labels do |merge_request, options| + # Avoids an N+1 query since labels are preloaded + merge_request.labels.map(&:title).sort + end expose :work_in_progress?, as: :work_in_progress expose :milestone, using: Entities::Milestone expose :merge_when_pipeline_succeeds @@ -598,7 +621,8 @@ module API expose :id expose :default_projects_limit expose :signup_enabled - expose :signin_enabled + expose :password_authentication_enabled + expose :password_authentication_enabled, as: :signin_enabled expose :gravatar_enabled expose :sign_in_text expose :after_sign_up_text diff --git a/lib/api/internal.rb b/lib/api/internal.rb index ef2c08e902c..8b007869dc3 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -101,7 +101,7 @@ module API end get "/broadcast_message" do - if message = BroadcastMessage.current.last + if message = BroadcastMessage.current&.last present message, with: Entities::BroadcastMessage else {} @@ -150,7 +150,7 @@ module API # # begin # repository = wiki? ? project.wiki.repository : project.repository - # Gitlab::GitalyClient::Notifications.new(repository.raw_repository).post_receive + # Gitlab::GitalyClient::NotificationService.new(repository.raw_repository).post_receive # rescue GRPC::Unavailable => e # render_api_error!(e, 500) # end diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index d419d345ec5..ac33b2b801c 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -10,6 +10,8 @@ module API resource :projects, requirements: { id: %r{[^/]+} } do include TimeTrackingEndpoints + helpers ::Gitlab::IssuableMetadata + helpers do def handle_merge_request_errors!(errors) if errors[:project_access].any? @@ -42,10 +44,14 @@ module API args[:label_name] = args.delete(:labels) merge_requests = MergeRequestsFinder.new(current_user, args).execute - .inc_notes_with_associations - .preload(:target_project, :author, :assignee, :milestone, :merge_request_diff) + .reorder(args[:order_by] => args[:sort]) + merge_requests = paginate(merge_requests) + .preload(:target_project) + + return merge_requests if args[:view] == 'simple' - merge_requests.reorder(args[:order_by] => args[:sort]) + merge_requests + .preload(:notes, :author, :assignee, :milestone, :merge_request_diff, :labels) end params :optional_params_ce do @@ -76,6 +82,7 @@ module API optional :labels, type: String, desc: 'Comma-separated list of label names' optional :created_after, type: DateTime, desc: 'Return merge requests created after the specified time' optional :created_before, type: DateTime, desc: 'Return merge requests created before the specified time' + optional :view, type: String, values: %w[simple], desc: 'If simple, returns the `iid`, URL, title, description, and basic state of merge request' use :pagination end get ":id/merge_requests" do @@ -83,7 +90,17 @@ module API merge_requests = find_merge_requests(project_id: user_project.id) - present paginate(merge_requests), with: Entities::MergeRequestBasic, current_user: current_user, project: user_project + options = { with: Entities::MergeRequestBasic, + current_user: current_user, + project: user_project } + + if params[:view] == 'simple' + options[:with] = Entities::MergeRequestSimple + else + options[:issuable_metadata] = issuable_meta_data(merge_requests, 'MergeRequest') + end + + present merge_requests, options end desc 'Create a merge request' do diff --git a/lib/api/settings.rb b/lib/api/settings.rb index d598f9a62a2..b19095d1252 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -65,6 +65,7 @@ module API :shared_runners_enabled, :sidekiq_throttling_enabled, :sign_in_text, + :password_authentication_enabled, :signin_enabled, :signup_enabled, :terminal_max_session_time, @@ -95,7 +96,9 @@ module API requires :domain_blacklist, type: String, desc: 'Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com' end optional :after_sign_up_text, type: String, desc: 'Text shown after sign up' - optional :signin_enabled, type: Boolean, desc: 'Flag indicating if sign in is enabled' + optional :password_authentication_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled' + optional :signin_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled' + mutually_exclusive :password_authentication_enabled, :signin_enabled optional :require_two_factor_authentication, type: Boolean, desc: 'Require all users to setup Two-factor authentication' given require_two_factor_authentication: ->(val) { val } do requires :two_factor_grace_period, type: Integer, desc: 'Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication' @@ -176,6 +179,10 @@ module API put "application/settings" do attrs = declared_params(include_missing: false) + if attrs.has_key?(:signin_enabled) + attrs[:password_authentication_enabled] = attrs.delete(:signin_enabled) + end + if current_settings.update_attributes(attrs) present current_settings, with: Entities::ApplicationSetting else diff --git a/lib/api/users.rb b/lib/api/users.rb index c469751c31c..81c68ea2658 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -421,7 +421,16 @@ module API success Entities::UserPublic end get do - present current_user, with: sudo? ? Entities::UserWithPrivateDetails : Entities::UserPublic + entity = + if sudo? + Entities::UserWithPrivateDetails + elsif current_user.admin? + Entities::UserWithAdmin + else + Entities::UserPublic + end + + present current_user, with: entity end desc "Get the currently authenticated user's SSH keys" do diff --git a/lib/api/v3/entities.rb b/lib/api/v3/entities.rb index c848f52723b..3759250f7f6 100644 --- a/lib/api/v3/entities.rb +++ b/lib/api/v3/entities.rb @@ -161,7 +161,8 @@ module API expose :id expose :default_projects_limit expose :signup_enabled - expose :signin_enabled + expose :password_authentication_enabled + expose :password_authentication_enabled, as: :signin_enabled expose :gravatar_enabled expose :sign_in_text expose :after_sign_up_text diff --git a/lib/api/v3/settings.rb b/lib/api/v3/settings.rb index 748d6b97d4f..202011cfcbe 100644 --- a/lib/api/v3/settings.rb +++ b/lib/api/v3/settings.rb @@ -44,7 +44,9 @@ module API requires :domain_blacklist, type: String, desc: 'Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com' end optional :after_sign_up_text, type: String, desc: 'Text shown after sign up' - optional :signin_enabled, type: Boolean, desc: 'Flag indicating if sign in is enabled' + optional :password_authentication_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled' + optional :signin_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled' + mutually_exclusive :password_authentication_enabled, :signin_enabled optional :require_two_factor_authentication, type: Boolean, desc: 'Require all users to setup Two-factor authentication' given require_two_factor_authentication: ->(val) { val } do requires :two_factor_grace_period, type: Integer, desc: 'Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication' @@ -116,7 +118,7 @@ module API :max_attachment_size, :session_expire_delay, :disabled_oauth_sign_in_sources, :user_oauth_applications, :user_default_external, :signup_enabled, :send_user_confirmation_email, :domain_whitelist, :domain_blacklist_enabled, - :after_sign_up_text, :signin_enabled, :require_two_factor_authentication, + :after_sign_up_text, :password_authentication_enabled, :signin_enabled, :require_two_factor_authentication, :home_page_url, :after_sign_out_path, :sign_in_text, :help_page_text, :shared_runners_enabled, :max_artifacts_size, :max_pages_size, :container_registry_token_expire_delay, :metrics_enabled, :sidekiq_throttling_enabled, :recaptcha_enabled, @@ -126,7 +128,13 @@ module API :housekeeping_enabled, :terminal_max_session_time end put "application/settings" do - if current_settings.update_attributes(declared_params(include_missing: false)) + attrs = declared_params(include_missing: false) + + if attrs.has_key?(:signin_enabled) + attrs[:password_authentication_enabled] = attrs.delete(:signin_enabled) + end + + if current_settings.update_attributes(attrs) present current_settings, with: Entities::ApplicationSetting else render_validation_error!(current_settings) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 56ad2c77c7d..cf3a0336792 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -80,6 +80,8 @@ module Ci artifacts: job[:artifacts], cache: job[:cache], dependencies: job[:dependencies], + before_script: job[:before_script], + script: job[:script], after_script: job[:after_script], environment: job[:environment] }.compact } diff --git a/lib/declarative_policy.rb b/lib/declarative_policy.rb index d9959bc1aff..b1eb1a6cef1 100644 --- a/lib/declarative_policy.rb +++ b/lib/declarative_policy.rb @@ -8,7 +8,12 @@ require_dependency 'declarative_policy/step' require_dependency 'declarative_policy/base' +require 'thread' + module DeclarativePolicy + CLASS_CACHE_MUTEX = Mutex.new + CLASS_CACHE_IVAR = :@__DeclarativePolicy_CLASS_CACHE + class << self def policy_for(user, subject, opts = {}) cache = opts[:cache] || {} @@ -23,7 +28,36 @@ module DeclarativePolicy subject = find_delegate(subject) - subject.class.ancestors.each do |klass| + class_for_class(subject.class) + end + + private + + # This method is heavily cached because there are a lot of anonymous + # modules in play in a typical rails app, and #name performs quite + # slowly for anonymous classes and modules. + # + # See https://bugs.ruby-lang.org/issues/11119 + # + # if the above bug is resolved, this caching could likely be removed. + def class_for_class(subject_class) + unless subject_class.instance_variable_defined?(CLASS_CACHE_IVAR) + CLASS_CACHE_MUTEX.synchronize do + # re-check in case of a race + break if subject_class.instance_variable_defined?(CLASS_CACHE_IVAR) + + policy_class = compute_class_for_class(subject_class) + subject_class.instance_variable_set(CLASS_CACHE_IVAR, policy_class) + end + end + + policy_class = subject_class.instance_variable_get(CLASS_CACHE_IVAR) + raise "no policy for #{subject.class.name}" if policy_class.nil? + policy_class + end + + def compute_class_for_class(subject_class) + subject_class.ancestors.each do |klass| next unless klass.name begin @@ -37,12 +71,8 @@ module DeclarativePolicy nil end end - - raise "no policy for #{subject.class.name}" end - private - def find_delegate(subject) seen = Set.new diff --git a/lib/declarative_policy/cache.rb b/lib/declarative_policy/cache.rb index b8cc60074c7..0804edba016 100644 --- a/lib/declarative_policy/cache.rb +++ b/lib/declarative_policy/cache.rb @@ -21,11 +21,14 @@ module DeclarativePolicy private def id_for(obj) - if obj.respond_to?(:id) && obj.id - obj.id.to_s - else - "##{obj.object_id}" - end + id = + begin + obj.id + rescue NoMethodError + nil + end + + id || "##{obj.object_id}" end end end diff --git a/lib/declarative_policy/condition.rb b/lib/declarative_policy/condition.rb index 9d7cf6b9726..51c4a8b2bbe 100644 --- a/lib/declarative_policy/condition.rb +++ b/lib/declarative_policy/condition.rb @@ -82,13 +82,14 @@ module DeclarativePolicy # depending on the scope, we may cache only by the user or only by # the subject, resulting in sharing across different policy objects. def cache_key - case @condition.scope - when :normal then "/dp/condition/#{@condition.key}/#{user_key},#{subject_key}" - when :user then "/dp/condition/#{@condition.key}/#{user_key}" - when :subject then "/dp/condition/#{@condition.key}/#{subject_key}" - when :global then "/dp/condition/#{@condition.key}" - else raise 'invalid scope' - end + @cache_key ||= + case @condition.scope + when :normal then "/dp/condition/#{@condition.key}/#{user_key},#{subject_key}" + when :user then "/dp/condition/#{@condition.key}/#{user_key}" + when :subject then "/dp/condition/#{@condition.key}/#{subject_key}" + when :global then "/dp/condition/#{@condition.key}" + else raise 'invalid scope' + end end def user_key diff --git a/lib/feature.rb b/lib/feature.rb index 363f66ba60e..4bd29aed687 100644 --- a/lib/feature.rb +++ b/lib/feature.rb @@ -57,5 +57,11 @@ class Feature Flipper.new(adapter) end end + + # This method is called from config/initializers/flipper.rb and can be used + # to register Flipper groups. + # See https://docs.gitlab.com/ee/development/feature_flags.html#feature-groups + def register_feature_groups + end end end diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index ccb5d886bab..9bed81e7327 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -37,7 +37,7 @@ module Gitlab rate_limit!(ip, success: result.success?, login: login) Gitlab::Auth::UniqueIpsLimiter.limit_user!(result.actor) - return result if result.success? || current_application_settings.signin_enabled? || Gitlab::LDAP::Config.enabled? + return result if result.success? || current_application_settings.password_authentication_enabled? || Gitlab::LDAP::Config.enabled? # If sign-in is disabled and LDAP is not configured, recommend a # personal access token on failed auth attempts @@ -48,6 +48,10 @@ module Gitlab # Avoid resource intensive login checks if password is not provided return unless password.present? + # Nothing to do here if internal auth is disabled and LDAP is + # not configured + return unless current_application_settings.password_authentication_enabled? || Gitlab::LDAP::Config.enabled? + Gitlab::Auth::UniqueIpsLimiter.limit_user! do user = User.by_login(login) diff --git a/lib/gitlab/auth/unique_ips_limiter.rb b/lib/gitlab/auth/unique_ips_limiter.rb index bf2239ca150..baa1f802d8a 100644 --- a/lib/gitlab/auth/unique_ips_limiter.rb +++ b/lib/gitlab/auth/unique_ips_limiter.rb @@ -27,7 +27,7 @@ module Gitlab time = Time.now.utc.to_i key = "#{USER_UNIQUE_IPS_PREFIX}:#{user_id}" - Gitlab::Redis.with do |redis| + Gitlab::Redis::SharedState.with do |redis| unique_ips_count = nil redis.multi do |r| r.zadd(key, time, ip) diff --git a/lib/gitlab/background_migration.rb b/lib/gitlab/background_migration.rb index d95ecd7b291..b0741b1fba7 100644 --- a/lib/gitlab/background_migration.rb +++ b/lib/gitlab/background_migration.rb @@ -1,24 +1,45 @@ module Gitlab module BackgroundMigration + def self.queue + @queue ||= BackgroundMigrationWorker.sidekiq_options['queue'] + end + # Begins stealing jobs from the background migrations queue, blocking the # caller until all jobs have been completed. # + # When a migration raises a StandardError is is going to be retries up to + # three times, for example, to recover from a deadlock. + # + # When Exception is being raised, it enqueues the migration again, and + # re-raises the exception. + # # steal_class - The name of the class for which to steal jobs. def self.steal(steal_class) - queue = Sidekiq::Queue - .new(BackgroundMigrationWorker.sidekiq_options['queue']) + enqueued = Sidekiq::Queue.new(self.queue) + scheduled = Sidekiq::ScheduledSet.new - queue.each do |job| - migration_class, migration_args = job.args + [scheduled, enqueued].each do |queue| + queue.each do |job| + migration_class, migration_args = job.args - next unless migration_class == steal_class + next unless job.queue == self.queue + next unless migration_class == steal_class - perform(migration_class, migration_args) + begin + perform(migration_class, migration_args, retries: 3) if job.delete + rescue Exception # rubocop:disable Lint/RescueException + BackgroundMigrationWorker # enqueue this migration again + .perform_async(migration_class, migration_args) - job.delete + raise + end + end end end + ## + # Performs a background migration. + # # class_name - The name of the background migration class as defined in the # Gitlab::BackgroundMigration namespace. # diff --git a/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder.rb b/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder.rb new file mode 100644 index 00000000000..0881244ed49 --- /dev/null +++ b/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder.rb @@ -0,0 +1,26 @@ +module Gitlab + module BackgroundMigration + class MigrateSystemUploadsToNewFolder + include Gitlab::Database::MigrationHelpers + attr_reader :old_folder, :new_folder + + class Upload < ActiveRecord::Base + self.table_name = 'uploads' + include EachBatch + end + + def perform(old_folder, new_folder) + replace_sql = replace_sql(uploads[:path], old_folder, new_folder) + affected_uploads = Upload.where(uploads[:path].matches("#{old_folder}%")) + + affected_uploads.each_batch do |batch| + batch.update_all("path = #{replace_sql}") + end + end + + def uploads + Arel::Table.new('uploads') + end + end + end +end diff --git a/lib/gitlab/cache/ci/project_pipeline_status.rb b/lib/gitlab/cache/ci/project_pipeline_status.rb index 9c2e09943b0..dba37892863 100644 --- a/lib/gitlab/cache/ci/project_pipeline_status.rb +++ b/lib/gitlab/cache/ci/project_pipeline_status.rb @@ -23,7 +23,7 @@ module Gitlab end def self.cached_results_for_projects(projects) - result = Gitlab::Redis.with do |redis| + result = Gitlab::Redis::Cache.with do |redis| redis.multi do projects.each do |project| cache_key = cache_key_for_project(project) @@ -100,19 +100,19 @@ module Gitlab end def load_from_cache - Gitlab::Redis.with do |redis| + Gitlab::Redis::Cache.with do |redis| self.sha, self.status, self.ref = redis.hmget(cache_key, :sha, :status, :ref) end end def store_in_cache - Gitlab::Redis.with do |redis| + Gitlab::Redis::Cache.with do |redis| redis.mapped_hmset(cache_key, { sha: sha, status: status, ref: ref }) end end def delete_from_cache - Gitlab::Redis.with do |redis| + Gitlab::Redis::Cache.with do |redis| redis.del(cache_key) end end @@ -120,7 +120,7 @@ module Gitlab def has_cache? return self.loaded unless self.loaded.nil? - Gitlab::Redis.with do |redis| + Gitlab::Redis::Cache.with do |redis| redis.exists(cache_key) end end diff --git a/lib/gitlab/cache/request_cache.rb b/lib/gitlab/cache/request_cache.rb new file mode 100644 index 00000000000..f1a04affd38 --- /dev/null +++ b/lib/gitlab/cache/request_cache.rb @@ -0,0 +1,94 @@ +module Gitlab + module Cache + # This module provides a simple way to cache values in RequestStore, + # and the cache key would be based on the class name, method name, + # optionally customized instance level values, optionally customized + # method level values, and optional method arguments. + # + # A simple example: + # + # class UserAccess + # extend Gitlab::Cache::RequestCache + # + # request_cache_key do + # [user&.id, project&.id] + # end + # + # request_cache def can_push_to_branch?(ref) + # # ... + # end + # end + # + # This way, the result of `can_push_to_branch?` would be cached in + # `RequestStore.store` based on the cache key. If RequestStore is not + # currently active, then it would be stored in a hash saved in an + # instance variable, so the cache logic would be the same. + # Here's another example using customized method level values: + # + # class Commit + # extend Gitlab::Cache::RequestCache + # + # def author + # User.find_by_any_email(author_email.downcase) + # end + # request_cache(:author) { author_email.downcase } + # end + # + # So that we could have different strategies for different methods + # + module RequestCache + def self.extended(klass) + return if klass < self + + extension = Module.new + klass.const_set(:RequestCacheExtension, extension) + klass.prepend(extension) + end + + def request_cache_key(&block) + if block_given? + @request_cache_key = block + else + @request_cache_key + end + end + + def request_cache(method_name, &method_key_block) + const_get(:RequestCacheExtension).module_eval do + cache_key_method_name = "#{method_name}_cache_key" + + define_method(method_name) do |*args| + store = + if RequestStore.active? + RequestStore.store + else + ivar_name = # ! and ? cannot be used as ivar name + "@cache_#{method_name.to_s.tr('!?', "\u2605\u2606")}" + + instance_variable_get(ivar_name) || + instance_variable_set(ivar_name, {}) + end + + key = __send__(cache_key_method_name, args) + + store.fetch(key) { store[key] = super(*args) } + end + + define_method(cache_key_method_name) do |args| + klass = self.class + + instance_key = instance_exec(&klass.request_cache_key) if + klass.request_cache_key + + method_key = instance_exec(&method_key_block) if method_key_block + + [klass.name, method_name, *instance_key, *method_key, *args] + .join(':') + end + + private cache_key_method_name + end + end + end + end +end diff --git a/lib/gitlab/chat_name_token.rb b/lib/gitlab/chat_name_token.rb index 1b081aa9b1d..e63e5437331 100644 --- a/lib/gitlab/chat_name_token.rb +++ b/lib/gitlab/chat_name_token.rb @@ -12,23 +12,23 @@ module Gitlab end def get - Gitlab::Redis.with do |redis| - data = redis.get(redis_key) + Gitlab::Redis::SharedState.with do |redis| + data = redis.get(redis_shared_state_key) JSON.parse(data, symbolize_names: true) if data end end def store!(params) - Gitlab::Redis.with do |redis| + Gitlab::Redis::SharedState.with do |redis| params = params.to_json - redis.set(redis_key, params, ex: EXPIRY_TIME) + redis.set(redis_shared_state_key, params, ex: EXPIRY_TIME) token end end def delete - Gitlab::Redis.with do |redis| - redis.del(redis_key) + Gitlab::Redis::SharedState.with do |redis| + redis.del(redis_shared_state_key) end end @@ -38,7 +38,7 @@ module Gitlab Devise.friendly_token(TOKEN_LENGTH) end - def redis_key + def redis_shared_state_key "gitlab:chat_names:#{token}" end end diff --git a/lib/gitlab/ci/build/step.rb b/lib/gitlab/ci/build/step.rb index ee034d9cc56..411f67f8ce7 100644 --- a/lib/gitlab/ci/build/step.rb +++ b/lib/gitlab/ci/build/step.rb @@ -12,7 +12,8 @@ module Gitlab class << self def from_commands(job) self.new(:script).tap do |step| - step.script = job.commands.split("\n") + step.script = job.options[:before_script].to_a + job.options[:script].to_a + step.script = job.commands.split("\n") if step.script.empty? step.timeout = job.timeout step.when = WHEN_ON_SUCCESS end diff --git a/lib/gitlab/ci/trace/stream.rb b/lib/gitlab/ci/trace/stream.rb index c4c0623df6c..5d6977106d6 100644 --- a/lib/gitlab/ci/trace/stream.rb +++ b/lib/gitlab/ci/trace/stream.rb @@ -69,12 +69,12 @@ module Gitlab return unless valid? return unless regex - regex = Regexp.new(regex) + regex = Gitlab::UntrustedRegexp.new(regex) match = "" reverse_line do |line| - matches = line.scan(regex) + matches = regex.scan(line) next unless matches.is_a?(Array) next if matches.empty? diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb index 818b3d9c46b..7fa02f3d7b3 100644 --- a/lib/gitlab/current_settings.rb +++ b/lib/gitlab/current_settings.rb @@ -25,7 +25,7 @@ module Gitlab def cached_application_settings begin ::ApplicationSetting.cached - rescue ::Redis::BaseError, ::Errno::ENOENT + rescue ::Redis::BaseError, ::Errno::ENOENT, ::Errno::EADDRNOTAVAIL # In case Redis isn't running or the Redis UNIX socket file is not available end end @@ -33,12 +33,7 @@ module Gitlab def uncached_application_settings return fake_application_settings unless connect_to_db? - # This loads from the database into the cache, so handle Redis errors - begin - db_settings = ::ApplicationSetting.current - rescue ::Redis::BaseError, ::Errno::ENOENT - # In case Redis isn't running or the Redis UNIX socket file is not available - end + db_settings = ::ApplicationSetting.current # If there are pending migrations, it's possible there are columns that # need to be added to the application settings. To prevent Rake tasks diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb index 0643c56db9b..69ca9aa596b 100644 --- a/lib/gitlab/database/migration_helpers.rb +++ b/lib/gitlab/database/migration_helpers.rb @@ -140,6 +140,8 @@ module Gitlab return add_foreign_key(source, target, column: column, on_delete: on_delete) + else + on_delete = 'SET NULL' if on_delete == :nullify end disable_statement_timeout @@ -155,7 +157,7 @@ module Gitlab ADD CONSTRAINT #{key_name} FOREIGN KEY (#{column}) REFERENCES #{target} (id) - #{on_delete ? "ON DELETE #{on_delete}" : ''} + #{on_delete ? "ON DELETE #{on_delete.upcase}" : ''} NOT VALID; EOF diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb index 33f8939bc61..1a697396ff1 100644 --- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb +++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb @@ -145,7 +145,7 @@ module Gitlab def track_rename(type, old_path, new_path) key = redis_key_for_type(type) - Gitlab::Redis.with do |redis| + Gitlab::Redis::SharedState.with do |redis| redis.lpush(key, [old_path, new_path].to_json) redis.expire(key, 2.weeks.to_i) end @@ -155,7 +155,7 @@ module Gitlab def reverts_for_type(type) key = redis_key_for_type(type) - Gitlab::Redis.with do |redis| + Gitlab::Redis::SharedState.with do |redis| failed_reverts = [] while rename_info = redis.lpop(key) diff --git a/lib/gitlab/etag_caching/store.rb b/lib/gitlab/etag_caching/store.rb index 072fcfc65e6..21172ff8d93 100644 --- a/lib/gitlab/etag_caching/store.rb +++ b/lib/gitlab/etag_caching/store.rb @@ -2,17 +2,17 @@ module Gitlab module EtagCaching class Store EXPIRY_TIME = 20.minutes - REDIS_NAMESPACE = 'etag:'.freeze + SHARED_STATE_NAMESPACE = 'etag:'.freeze def get(key) - Gitlab::Redis.with { |redis| redis.get(redis_key(key)) } + Gitlab::Redis::SharedState.with { |redis| redis.get(redis_shared_state_key(key)) } end def touch(key, only_if_missing: false) etag = generate_etag - Gitlab::Redis.with do |redis| - redis.set(redis_key(key), etag, ex: EXPIRY_TIME, nx: only_if_missing) + Gitlab::Redis::SharedState.with do |redis| + redis.set(redis_shared_state_key(key), etag, ex: EXPIRY_TIME, nx: only_if_missing) end etag @@ -24,10 +24,10 @@ module Gitlab SecureRandom.hex end - def redis_key(key) + def redis_shared_state_key(key) raise 'Invalid key' if !Rails.env.production? && !Gitlab::EtagCaching::Router.match(key) - "#{REDIS_NAMESPACE}#{key}" + "#{SHARED_STATE_NAMESPACE}#{key}" end end end diff --git a/lib/gitlab/exclusive_lease.rb b/lib/gitlab/exclusive_lease.rb index a0f46594eb1..3784f6c4947 100644 --- a/lib/gitlab/exclusive_lease.rb +++ b/lib/gitlab/exclusive_lease.rb @@ -26,17 +26,17 @@ module Gitlab EOS def self.cancel(key, uuid) - Gitlab::Redis.with do |redis| - redis.eval(LUA_CANCEL_SCRIPT, keys: [redis_key(key)], argv: [uuid]) + Gitlab::Redis::SharedState.with do |redis| + redis.eval(LUA_CANCEL_SCRIPT, keys: [redis_shared_state_key(key)], argv: [uuid]) end end - def self.redis_key(key) + def self.redis_shared_state_key(key) "gitlab:exclusive_lease:#{key}" end def initialize(key, timeout:) - @redis_key = self.class.redis_key(key) + @redis_shared_state_key = self.class.redis_shared_state_key(key) @timeout = timeout @uuid = SecureRandom.uuid end @@ -45,24 +45,24 @@ module Gitlab # false if the lease is already taken. def try_obtain # Performing a single SET is atomic - Gitlab::Redis.with do |redis| - redis.set(@redis_key, @uuid, nx: true, ex: @timeout) && @uuid + Gitlab::Redis::SharedState.with do |redis| + redis.set(@redis_shared_state_key, @uuid, nx: true, ex: @timeout) && @uuid end end # Try to renew an existing lease. Return lease UUID on success, # false if the lease is taken by a different UUID or inexistent. def renew - Gitlab::Redis.with do |redis| - result = redis.eval(LUA_RENEW_SCRIPT, keys: [@redis_key], argv: [@uuid, @timeout]) + Gitlab::Redis::SharedState.with do |redis| + result = redis.eval(LUA_RENEW_SCRIPT, keys: [@redis_shared_state_key], argv: [@uuid, @timeout]) result == @uuid end end # Returns true if the key for this lease is set. def exists? - Gitlab::Redis.with do |redis| - redis.exists(@redis_key) + Gitlab::Redis::SharedState.with do |redis| + redis.exists(@redis_shared_state_key) end end end diff --git a/lib/gitlab/git/attributes.rb b/lib/gitlab/git/attributes.rb index 42140ecc993..2d20cd473a7 100644 --- a/lib/gitlab/git/attributes.rb +++ b/lib/gitlab/git/attributes.rb @@ -1,3 +1,8 @@ +# Gitaly note: JV: not sure what to make of this class. Why does it use +# the full disk path of the repository to look up attributes This is +# problematic in Gitaly, because Gitaly hides the full disk path to the +# repository from gitlab-ce. + module Gitlab module Git # Class for parsing Git attribute files and extracting the attributes for diff --git a/lib/gitlab/git/blame.rb b/lib/gitlab/git/blame.rb index 66829a03c2e..0deaab01b5b 100644 --- a/lib/gitlab/git/blame.rb +++ b/lib/gitlab/git/blame.rb @@ -1,3 +1,5 @@ +# Gitaly note: JV: needs 1 RPC for #load_blame. + module Gitlab module Git class Blame @@ -24,6 +26,7 @@ module Gitlab private + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/376 def load_blame cmd = %W(#{Gitlab.config.git.bin_path} --git-dir=#{@repo.path} blame -p #{@sha} -- #{@path}) # Read in binary mode to ensure ASCII-8BIT diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb index ea386c2ddcb..db6cfc9671f 100644 --- a/lib/gitlab/git/blob.rb +++ b/lib/gitlab/git/blob.rb @@ -1,3 +1,5 @@ +# Gitaly note: JV: seems to be completely migrated (behind feature flags). + module Gitlab module Git class Blob @@ -27,7 +29,7 @@ module Gitlab path = path.sub(/\A\/*/, '') path = '/' if path.empty? name = File.basename(path) - entry = Gitlab::GitalyClient::Commit.new(repository).tree_entry(sha, path, MAX_DATA_DISPLAY_SIZE) + entry = Gitlab::GitalyClient::CommitService.new(repository).tree_entry(sha, path, MAX_DATA_DISPLAY_SIZE) return unless entry case entry.type @@ -85,10 +87,10 @@ module Gitlab def raw(repository, sha) Gitlab::GitalyClient.migrate(:git_blob_raw) do |is_enabled| if is_enabled - Gitlab::GitalyClient::Blob.new(repository).get_blob(oid: sha, limit: MAX_DATA_DISPLAY_SIZE) + Gitlab::GitalyClient::BlobService.new(repository).get_blob(oid: sha, limit: MAX_DATA_DISPLAY_SIZE) else blob = repository.lookup(sha) - + new( id: blob.oid, size: blob.size, @@ -107,6 +109,8 @@ module Gitlab detect && detect[:type] == :binary end + private + # Recursive search of blob id by path # # Ex. @@ -178,7 +182,7 @@ module Gitlab Gitlab::GitalyClient.migrate(:git_blob_load_all_data) do |is_enabled| @data = begin if is_enabled - Gitlab::GitalyClient::Blob.new(repository).get_blob(oid: id, limit: -1).data + Gitlab::GitalyClient::BlobService.new(repository).get_blob(oid: id, limit: -1).data else repository.lookup(id).content end diff --git a/lib/gitlab/git/blob_snippet.rb b/lib/gitlab/git/blob_snippet.rb index d7975f88aaa..68116e775c6 100644 --- a/lib/gitlab/git/blob_snippet.rb +++ b/lib/gitlab/git/blob_snippet.rb @@ -1,3 +1,5 @@ +# Gitaly note: JV: no RPC's here. + module Gitlab module Git class BlobSnippet diff --git a/lib/gitlab/git/branch.rb b/lib/gitlab/git/branch.rb index 124526e4b59..c53882787f1 100644 --- a/lib/gitlab/git/branch.rb +++ b/lib/gitlab/git/branch.rb @@ -1,39 +1,10 @@ +# Gitaly note: JV: no RPC's here. + module Gitlab module Git class Branch < Ref - def initialize(repository, name, target) - if target.is_a?(Gitaly::FindLocalBranchResponse) - target = target_from_gitaly_local_branches_response(target) - end - - super(repository, name, target) - end - - def target_from_gitaly_local_branches_response(response) - # Git messages have no encoding enforcements. However, in the UI we only - # handle UTF-8, so basically we cross our fingers that the message force - # encoded to UTF-8 is readable. - message = response.commit_subject.dup.force_encoding('UTF-8') - - # NOTE: For ease of parsing in Gitaly, we have only the subject of - # the commit and not the full message. This is ok, since all the - # code that uses `local_branches` only cares at most about the - # commit message. - # TODO: Once gitaly "takes over" Rugged consider separating the - # subject from the message to make it clearer when there's one - # available but not the other. - hash = { - id: response.commit_id, - message: message, - authored_date: Time.at(response.commit_author.date.seconds), - author_name: response.commit_author.name, - author_email: response.commit_author.email, - committed_date: Time.at(response.commit_committer.date.seconds), - committer_name: response.commit_committer.name, - committer_email: response.commit_committer.email - } - - Gitlab::Git::Commit.decorate(hash) + def initialize(repository, name, target, target_commit) + super(repository, name, target, target_commit) end end end diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb index 9c0606d780a..76a562f356e 100644 --- a/lib/gitlab/git/commit.rb +++ b/lib/gitlab/git/commit.rb @@ -38,7 +38,7 @@ module Gitlab repo = options.delete(:repo) raise 'Gitlab::Git::Repository is required' unless repo.respond_to?(:log) - repo.log(options).map { |c| decorate(c) } + repo.log(options) end # Get single commit @@ -48,6 +48,7 @@ module Gitlab # # Commit.find(repo, 'master') # + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/321 def find(repo, commit_id = "HEAD") return commit_id if commit_id.is_a?(Gitlab::Git::Commit) return decorate(commit_id) if commit_id.is_a?(Rugged::Commit) @@ -97,7 +98,15 @@ module Gitlab # Commit.between(repo, '29eda46b', 'master') # def between(repo, base, head) - repo.commits_between(base, head).map do |commit| + commits = Gitlab::GitalyClient.migrate(:commits_between) do |is_enabled| + if is_enabled + repo.gitaly_commit_client.between(base, head) + else + repo.commits_between(base, head) + end + end + + commits.map do |commit| decorate(commit) end rescue Rugged::ReferenceError @@ -124,6 +133,7 @@ module Gitlab # are documented here: # http://www.rubydoc.info/github/libgit2/rugged/Rugged#SORT_NONE-constant) # + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/326 def find_all(repo, options = {}) actual_options = options.dup @@ -208,6 +218,8 @@ module Gitlab init_from_hash(raw_commit) elsif raw_commit.is_a?(Rugged::Commit) init_from_rugged(raw_commit) + elsif raw_commit.is_a?(Gitaly::GitCommit) + init_from_gitaly(raw_commit) else raise "Invalid raw commit type: #{raw_commit.class}" end @@ -243,6 +255,8 @@ module Gitlab # Shows the diff between the commit's parent and the commit. # # Cuts out the header and stats from #to_patch and returns only the diff. + # + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/324 def to_diff diff_from_parent.patch end @@ -367,6 +381,22 @@ module Gitlab @parent_ids = commit.parents.map(&:oid) end + def init_from_gitaly(commit) + @raw_commit = commit + @id = commit.id + # TODO: Once gitaly "takes over" Rugged consider separating the + # subject from the message to make it clearer when there's one + # available but not the other. + @message = (commit.body.presence || commit.subject).dup + @authored_date = Time.at(commit.author.date.seconds) + @author_name = commit.author.name.dup + @author_email = commit.author.email.dup + @committed_date = Time.at(commit.committer.date.seconds) + @committer_name = commit.committer.name.dup + @committer_email = commit.committer.email.dup + @parent_ids = commit.parent_ids + end + def serialize_keys SERIALIZE_KEYS end diff --git a/lib/gitlab/git/commit_stats.rb b/lib/gitlab/git/commit_stats.rb index e9118bbed0e..57c29ad112c 100644 --- a/lib/gitlab/git/commit_stats.rb +++ b/lib/gitlab/git/commit_stats.rb @@ -1,3 +1,5 @@ +# Gitaly note: JV: 1 RPC, migration in progress. + # Gitlab::Git::CommitStats counts the additions, deletions, and total changes # in a commit. module Gitlab @@ -6,6 +8,8 @@ module Gitlab attr_reader :id, :additions, :deletions, :total # Instantiate a CommitStats object + # + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/323 def initialize(commit) @id = commit.id @additions = 0 diff --git a/lib/gitlab/git/compare.rb b/lib/gitlab/git/compare.rb index 78e440395a5..7cb842256d0 100644 --- a/lib/gitlab/git/compare.rb +++ b/lib/gitlab/git/compare.rb @@ -1,3 +1,5 @@ +# Gitaly note: JV: no RPC's here. + module Gitlab module Git class Compare diff --git a/lib/gitlab/git/diff.rb b/lib/gitlab/git/diff.rb index cf7829a583b..cf95f673667 100644 --- a/lib/gitlab/git/diff.rb +++ b/lib/gitlab/git/diff.rb @@ -1,3 +1,5 @@ +# Gitaly note: JV: needs RPC for Gitlab::Git::Diff.between. + # Gitlab::Git::Diff is a wrapper around native Rugged::Diff object module Gitlab module Git @@ -81,110 +83,16 @@ module Gitlab # Return a copy of the +options+ hash containing only keys that can be # passed to Rugged. Allowed options are: # - # :max_size :: - # An integer specifying the maximum byte size of a file before a it - # will be treated as binary. The default value is 512MB. - # - # :context_lines :: - # The number of unchanged lines that define the boundary of a hunk - # (and to display before and after the actual changes). The default is - # 3. - # - # :interhunk_lines :: - # The maximum number of unchanged lines between hunk boundaries before - # the hunks will be merged into a one. The default is 0. - # - # :old_prefix :: - # The virtual "directory" to prefix to old filenames in hunk headers. - # The default is "a". - # - # :new_prefix :: - # The virtual "directory" to prefix to new filenames in hunk headers. - # The default is "b". - # - # :reverse :: - # If true, the sides of the diff will be reversed. - # - # :force_text :: - # If true, all files will be treated as text, disabling binary - # attributes & detection. - # - # :ignore_whitespace :: - # If true, all whitespace will be ignored. - # # :ignore_whitespace_change :: # If true, changes in amount of whitespace will be ignored. # - # :ignore_whitespace_eol :: - # If true, whitespace at end of line will be ignored. - # - # :ignore_submodules :: - # if true, submodules will be excluded from the diff completely. - # - # :patience :: - # If true, the "patience diff" algorithm will be used (currenlty - # unimplemented). - # - # :include_ignored :: - # If true, ignored files will be included in the diff. - # - # :include_untracked :: - # If true, untracked files will be included in the diff. - # - # :include_unmodified :: - # If true, unmodified files will be included in the diff. - # - # :recurse_untracked_dirs :: - # Even if +:include_untracked+ is true, untracked directories will - # only be marked with a single entry in the diff. If this flag is set - # to true, all files under ignored directories will be included in the - # diff, too. - # # :disable_pathspec_match :: # If true, the given +*paths+ will be applied as exact matches, # instead of as fnmatch patterns. # - # :deltas_are_icase :: - # If true, filename comparisons will be made with case-insensitivity. - # - # :include_untracked_content :: - # if true, untracked content will be contained in the the diff patch - # text. - # - # :skip_binary_check :: - # If true, diff deltas will be generated without spending time on - # binary detection. This is useful to improve performance in cases - # where the actual file content difference is not needed. - # - # :include_typechange :: - # If true, type changes for files will not be interpreted as deletion - # of the "old file" and addition of the "new file", but will generate - # typechange records. - # - # :include_typechange_trees :: - # Even if +:include_typechange+ is true, blob -> tree changes will - # still usually be handled as a deletion of the blob. If this flag is - # set to true, blob -> tree changes will be marked as typechanges. - # - # :ignore_filemode :: - # If true, file mode changes will be ignored. - # - # :recurse_ignored_dirs :: - # Even if +:include_ignored+ is true, ignored directories will only be - # marked with a single entry in the diff. If this flag is set to true, - # all files under ignored directories will be included in the diff, - # too. def filter_diff_options(options, default_options = {}) - allowed_options = [:max_size, :context_lines, :interhunk_lines, - :old_prefix, :new_prefix, :reverse, :force_text, - :ignore_whitespace, :ignore_whitespace_change, - :ignore_whitespace_eol, :ignore_submodules, - :patience, :include_ignored, :include_untracked, - :include_unmodified, :recurse_untracked_dirs, - :disable_pathspec_match, :deltas_are_icase, - :include_untracked_content, :skip_binary_check, - :include_typechange, :include_typechange_trees, - :ignore_filemode, :recurse_ignored_dirs, :paths, + allowed_options = [:ignore_whitespace_change, + :disable_pathspec_match, :paths, :max_files, :max_lines, :limits, :expanded] if default_options diff --git a/lib/gitlab/git/diff_collection.rb b/lib/gitlab/git/diff_collection.rb index 555894907cc..0d8fe185ac5 100644 --- a/lib/gitlab/git/diff_collection.rb +++ b/lib/gitlab/git/diff_collection.rb @@ -1,3 +1,5 @@ +# Gitaly note: JV: no RPC's here. + module Gitlab module Git class DiffCollection diff --git a/lib/gitlab/git/env.rb b/lib/gitlab/git/env.rb index 0fdc57ec954..f80193ac553 100644 --- a/lib/gitlab/git/env.rb +++ b/lib/gitlab/git/env.rb @@ -1,3 +1,5 @@ +# Gitaly note: JV: no RPC's here. + module Gitlab module Git # Ephemeral (per request) storage for environment variables that some Git diff --git a/lib/gitlab/git/gitmodules_parser.rb b/lib/gitlab/git/gitmodules_parser.rb index f4e3b5e5129..4a43b9b444d 100644 --- a/lib/gitlab/git/gitmodules_parser.rb +++ b/lib/gitlab/git/gitmodules_parser.rb @@ -1,3 +1,5 @@ +# Gitaly note: JV: no RPC's here. + module Gitlab module Git class GitmodulesParser diff --git a/lib/gitlab/git/hook.rb b/lib/gitlab/git/hook.rb index 5042916343b..8f0c377ef4f 100644 --- a/lib/gitlab/git/hook.rb +++ b/lib/gitlab/git/hook.rb @@ -1,3 +1,7 @@ +# Gitaly note: JV: looks like this is only used by GitHooksService in +# app/services. We shouldn't bother migrating this until we know how +# GitHooksService will be migrated. + module Gitlab module Git class Hook diff --git a/lib/gitlab/git/index.rb b/lib/gitlab/git/index.rb index 666743006e5..db532600d1b 100644 --- a/lib/gitlab/git/index.rb +++ b/lib/gitlab/git/index.rb @@ -1,3 +1,7 @@ +# Gitaly note: JV: When the time comes I think we will want to copy this +# class into Gitaly. None of its methods look like they should be RPC's. +# The RPC's will be at a higher level. + module Gitlab module Git class Index diff --git a/lib/gitlab/git/path_helper.rb b/lib/gitlab/git/path_helper.rb index 0148cd8df05..42c80aabd0a 100644 --- a/lib/gitlab/git/path_helper.rb +++ b/lib/gitlab/git/path_helper.rb @@ -1,3 +1,5 @@ +# Gitaly note: JV: no RPC's here. + module Gitlab module Git class PathHelper diff --git a/lib/gitlab/git/popen.rb b/lib/gitlab/git/popen.rb index df9ca3ee5ac..25fa62ce4bd 100644 --- a/lib/gitlab/git/popen.rb +++ b/lib/gitlab/git/popen.rb @@ -1,3 +1,5 @@ +# Gitaly note: JV: no RPC's here. + require 'open3' module Gitlab diff --git a/lib/gitlab/git/ref.rb b/lib/gitlab/git/ref.rb index ebf7393dc61..372ce005b94 100644 --- a/lib/gitlab/git/ref.rb +++ b/lib/gitlab/git/ref.rb @@ -1,3 +1,5 @@ +# Gitaly note: JV: probably no RPC's here (just one interaction with Rugged). + module Gitlab module Git class Ref @@ -24,16 +26,16 @@ module Gitlab str.gsub(/\Arefs\/heads\//, '') end + # Gitaly: this method will probably be migrated indirectly via its call sites. def self.dereference_object(object) object = object.target while object.is_a?(Rugged::Tag::Annotation) object end - def initialize(repository, name, target) - encode! name - @name = name.gsub(/\Arefs\/(tags|heads)\//, '') - @dereferenced_target = Gitlab::Git::Commit.find(repository, target) + def initialize(repository, name, target, derefenced_target) + @name = Gitlab::Git.ref_name(name) + @dereferenced_target = derefenced_target @target = if target.respond_to?(:oid) target.oid elsif target.respond_to?(:name) diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index e51966313d4..63eebadff2e 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -80,16 +80,10 @@ module Gitlab end # Returns an Array of Branches - def branches(filter: nil, sort_by: nil) - branches = rugged.branches.each(filter).map do |rugged_ref| - begin - Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target) - rescue Rugged::ReferenceError - # Omit invalid branch - end - end.compact - - sort_branches(branches, sort_by) + # + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/389 + def branches(sort_by: nil) + branches_filter(sort_by: sort_by) end def reload_rugged @@ -107,7 +101,10 @@ module Gitlab reload_rugged if force_reload rugged_ref = rugged.branches[name] - Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target) if rugged_ref + if rugged_ref + target_commit = Gitlab::Git::Commit.find(self, rugged_ref.target) + Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target, target_commit) + end end def local_branches(sort_by: nil) @@ -115,7 +112,7 @@ module Gitlab if is_enabled gitaly_ref_client.local_branches(sort_by: sort_by) else - branches(filter: :local, sort_by: sort_by) + branches_filter(filter: :local, sort_by: sort_by) end end end @@ -162,6 +159,8 @@ module Gitlab end # Returns an Array of Tags + # + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/390 def tags rugged.references.each("refs/tags/*").map do |ref| message = nil @@ -174,7 +173,8 @@ module Gitlab end end - Gitlab::Git::Tag.new(self, ref.name, ref.target, message) + target_commit = Gitlab::Git::Commit.find(self, ref.target) + Gitlab::Git::Tag.new(self, ref.name, ref.target, target_commit, message) end.sort_by(&:name) end @@ -204,13 +204,6 @@ module Gitlab branch_names + tag_names end - # Deprecated. Will be removed in 5.2 - def heads - rugged.references.each("refs/heads/*").map do |head| - Gitlab::Git::Ref.new(self, head.name, head.target) - end.sort_by(&:name) - end - def has_commits? !empty? end @@ -297,28 +290,6 @@ module Gitlab (size.to_f / 1024).round(2) end - # Returns an array of BlobSnippets for files at the specified +ref+ that - # contain the +query+ string. - def search_files(query, ref = nil) - greps = [] - ref ||= root_ref - - populated_index(ref).each do |entry| - # Discard submodules - next if submodule?(entry) - - blob = Gitlab::Git::Blob.raw(self, entry[:oid]) - - # Skip binary files - next if blob.data.encoding == Encoding::ASCII_8BIT - - blob.load_all_data!(self) - greps += build_greps(blob.data, query, ref, entry[:path]) - end - - greps - end - # Use the Rugged Walker API to build an array of commits. # # Usage. @@ -331,85 +302,10 @@ module Gitlab # ) # def log(options) - default_options = { - limit: 10, - offset: 0, - path: nil, - follow: false, - skip_merges: false, - disable_walk: false, - after: nil, - before: nil - } - - options = default_options.merge(options) - options[:limit] ||= 0 - options[:offset] ||= 0 - actual_ref = options[:ref] || root_ref - begin - sha = sha_from_ref(actual_ref) - rescue Rugged::OdbError, Rugged::InvalidError, Rugged::ReferenceError - # Return an empty array if the ref wasn't found - return [] - end - - if log_using_shell?(options) - log_by_shell(sha, options) - else - log_by_walk(sha, options) - end - end - - def log_using_shell?(options) - options[:path].present? || - options[:disable_walk] || - options[:skip_merges] || - options[:after] || - options[:before] - end - - def log_by_walk(sha, options) - walk_options = { - show: sha, - sort: Rugged::SORT_NONE, - limit: options[:limit], - offset: options[:offset] - } - Rugged::Walker.walk(rugged, walk_options).to_a - end - - def log_by_shell(sha, options) - limit = options[:limit].to_i - offset = options[:offset].to_i - use_follow_flag = options[:follow] && options[:path].present? - - # We will perform the offset in Ruby because --follow doesn't play well with --skip. - # See: https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520 - offset_in_ruby = use_follow_flag && options[:offset].present? - limit += offset if offset_in_ruby - - cmd = %W[#{Gitlab.config.git.bin_path} --git-dir=#{path} log] - cmd << "--max-count=#{limit}" - cmd << '--format=%H' - cmd << "--skip=#{offset}" unless offset_in_ruby - cmd << '--follow' if use_follow_flag - cmd << '--no-merges' if options[:skip_merges] - cmd << "--after=#{options[:after].iso8601}" if options[:after] - cmd << "--before=#{options[:before].iso8601}" if options[:before] - cmd << sha - - # :path can be a string or an array of strings - if options[:path].present? - cmd << '--' - cmd += Array(options[:path]) - end - - raw_output = IO.popen(cmd) { |io| io.read } - lines = offset_in_ruby ? raw_output.lines.drop(offset) : raw_output.lines - - lines.map! { |c| Rugged::Commit.new(rugged, c.strip) } + raw_log(options).map { |c| Commit.decorate(c) } end + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/382 def count_commits(options) cmd = %W[#{Gitlab.config.git.bin_path} --git-dir=#{path} rev-list] cmd << "--after=#{options[:after].iso8601}" if options[:after] @@ -454,7 +350,7 @@ module Gitlab # Counts the amount of commits between `from` and `to`. def count_commits_between(from, to) - commits_between(from, to).size + Commit.between(self, from, to).size end # Returns the SHA of the most recent common ancestor of +from+ and +to+ @@ -553,6 +449,7 @@ module Gitlab # @repository.submodule_url_for('master', 'rack') # # => git@localhost:rack.git # + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/329 def submodule_url_for(ref, path) Gitlab::GitalyClient.migrate(:submodule_url_for) do |is_enabled| if is_enabled @@ -567,6 +464,8 @@ module Gitlab end # Return total commits count accessible from passed ref + # + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/330 def commit_count(ref) gitaly_migrate(:commit_count) do |is_enabled| if is_enabled @@ -779,7 +678,8 @@ module Gitlab # create_branch("other-feature", "master") def create_branch(ref, start_point = "HEAD") rugged_ref = rugged.branches.create(ref, start_point) - Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target) + target_commit = Gitlab::Git::Commit.find(self, rugged_ref.target) + Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target, target_commit) rescue Rugged::ReferenceError => e raise InvalidRef.new("Branch #{ref} already exists") if e.to_s =~ /'refs\/heads\/#{ref}'/ raise InvalidRef.new("Invalid reference #{start_point}") @@ -838,6 +738,7 @@ module Gitlab # Ex. # repo.ls_files('master') # + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/327 def ls_files(ref) actual_ref = ref || root_ref @@ -864,6 +765,7 @@ module Gitlab raw_output.compact end + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/328 def copy_gitattributes(ref) begin commit = lookup(ref) @@ -905,8 +807,113 @@ module Gitlab Gitlab::GitalyClient::Util.repository(@storage, @relative_path) end + def gitaly_ref_client + @gitaly_ref_client ||= Gitlab::GitalyClient::RefService.new(self) + end + + def gitaly_commit_client + @gitaly_commit_client ||= Gitlab::GitalyClient::CommitService.new(self) + end + private + # Gitaly note: JV: Trying to get rid of the 'filter' option so we can implement this with 'git'. + def branches_filter(filter: nil, sort_by: nil) + branches = rugged.branches.each(filter).map do |rugged_ref| + begin + target_commit = Gitlab::Git::Commit.find(self, rugged_ref.target) + Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target, target_commit) + rescue Rugged::ReferenceError + # Omit invalid branch + end + end.compact + + sort_branches(branches, sort_by) + end + + def raw_log(options) + default_options = { + limit: 10, + offset: 0, + path: nil, + follow: false, + skip_merges: false, + disable_walk: false, + after: nil, + before: nil + } + + options = default_options.merge(options) + options[:limit] ||= 0 + options[:offset] ||= 0 + actual_ref = options[:ref] || root_ref + begin + sha = sha_from_ref(actual_ref) + rescue Rugged::OdbError, Rugged::InvalidError, Rugged::ReferenceError + # Return an empty array if the ref wasn't found + return [] + end + + if log_using_shell?(options) + log_by_shell(sha, options) + else + log_by_walk(sha, options) + end + end + + def log_using_shell?(options) + options[:path].present? || + options[:disable_walk] || + options[:skip_merges] || + options[:after] || + options[:before] + end + + def log_by_walk(sha, options) + walk_options = { + show: sha, + sort: Rugged::SORT_NONE, + limit: options[:limit], + offset: options[:offset] + } + Rugged::Walker.walk(rugged, walk_options).to_a + end + + # Gitaly note: JV: although #log_by_shell shells out to Git I think the + # complexity is such that we should migrate it as Ruby before trying to + # do it in Go. + def log_by_shell(sha, options) + limit = options[:limit].to_i + offset = options[:offset].to_i + use_follow_flag = options[:follow] && options[:path].present? + + # We will perform the offset in Ruby because --follow doesn't play well with --skip. + # See: https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520 + offset_in_ruby = use_follow_flag && options[:offset].present? + limit += offset if offset_in_ruby + + cmd = %W[#{Gitlab.config.git.bin_path} --git-dir=#{path} log] + cmd << "--max-count=#{limit}" + cmd << '--format=%H' + cmd << "--skip=#{offset}" unless offset_in_ruby + cmd << '--follow' if use_follow_flag + cmd << '--no-merges' if options[:skip_merges] + cmd << "--after=#{options[:after].iso8601}" if options[:after] + cmd << "--before=#{options[:before].iso8601}" if options[:before] + cmd << sha + + # :path can be a string or an array of strings + if options[:path].present? + cmd << '--' + cmd += Array(options[:path]) + end + + raw_output = IO.popen(cmd) { |io| io.read } + lines = offset_in_ruby ? raw_output.lines.drop(offset) : raw_output.lines + + lines.map! { |c| Rugged::Commit.new(rugged, c.strip) } + end + # We are trying to deprecate this method because it does a lot of work # but it seems to be used only to look up submodule URL's. # https://gitlab.com/gitlab-org/gitaly/issues/329 @@ -930,7 +937,7 @@ module Gitlab return unless commit_object && commit_object.type == :COMMIT - gitmodules = gitaly_commit_client.tree_entry(ref, '.gitmodules', Blob::MAX_DATA_DISPLAY_SIZE) + gitmodules = gitaly_commit_client.tree_entry(ref, '.gitmodules', Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE) found_module = GitmodulesParser.new(gitmodules.data).parse[path] found_module && found_module['url'] @@ -1078,73 +1085,6 @@ module Gitlab index end - # Return an array of BlobSnippets for lines in +file_contents+ that match - # +query+ - def build_greps(file_contents, query, ref, filename) - # The file_contents string is potentially huge so we make sure to loop - # through it one line at a time. This gives Ruby the chance to GC lines - # we are not interested in. - # - # We need to do a little extra work because we are not looking for just - # the lines that matches the query, but also for the context - # (surrounding lines). We will use Enumerable#each_cons to efficiently - # loop through the lines while keeping surrounding lines on hand. - # - # First, we turn "foo\nbar\nbaz" into - # [ - # [nil, -3], [nil, -2], [nil, -1], - # ['foo', 0], ['bar', 1], ['baz', 3], - # [nil, 4], [nil, 5], [nil, 6] - # ] - lines_with_index = Enumerator.new do |yielder| - # Yield fake 'before' lines for the first line of file_contents - (-SEARCH_CONTEXT_LINES..-1).each do |i| - yielder.yield [nil, i] - end - - # Yield the actual file contents - count = 0 - file_contents.each_line do |line| - line.chomp! - yielder.yield [line, count] - count += 1 - end - - # Yield fake 'after' lines for the last line of file_contents - (count + 1..count + SEARCH_CONTEXT_LINES).each do |i| - yielder.yield [nil, i] - end - end - - greps = [] - - # Loop through consecutive blocks of lines with indexes - lines_with_index.each_cons(2 * SEARCH_CONTEXT_LINES + 1) do |line_block| - # Get the 'middle' line and index from the block - line, _ = line_block[SEARCH_CONTEXT_LINES] - - next unless line && line.match(/#{Regexp.escape(query)}/i) - - # Yay, 'line' contains a match! - # Get an array with just the context lines (no indexes) - match_with_context = line_block.map(&:first) - # Remove 'nil' lines in case we are close to the first or last line - match_with_context.compact! - - # Get the line number (1-indexed) of the first context line - first_context_line_number = line_block[0][1] + 1 - - greps << Gitlab::Git::BlobSnippet.new( - ref, - match_with_context, - first_context_line_number, - filename - ) - end - - greps - end - # Return the Rugged patches for the diff between +from+ and +to+. def diff_patches(from, to, options = {}, *paths) options ||= {} @@ -1173,14 +1113,6 @@ module Gitlab end end - def gitaly_ref_client - @gitaly_ref_client ||= Gitlab::GitalyClient::Ref.new(self) - end - - def gitaly_commit_client - @gitaly_commit_client ||= Gitlab::GitalyClient::Commit.new(self) - end - def gitaly_migrate(method, &block) Gitlab::GitalyClient.migrate(method, &block) rescue GRPC::NotFound => e diff --git a/lib/gitlab/git/rev_list.rb b/lib/gitlab/git/rev_list.rb index a16b0ed76f4..2b5785a1f08 100644 --- a/lib/gitlab/git/rev_list.rb +++ b/lib/gitlab/git/rev_list.rb @@ -1,3 +1,5 @@ +# Gitaly note: JV: will probably be migrated indirectly by migrating the call sites. + module Gitlab module Git class RevList @@ -15,6 +17,8 @@ module Gitlab end # This methods returns an array of missed references + # + # Should become obsolete after https://gitlab.com/gitlab-org/gitaly/issues/348. def missed_ref execute([*base_args, '--max-count=1', oldrev, "^#{newrev}"]) end diff --git a/lib/gitlab/git/tag.rb b/lib/gitlab/git/tag.rb index b5342c3d310..bc4e160dce9 100644 --- a/lib/gitlab/git/tag.rb +++ b/lib/gitlab/git/tag.rb @@ -1,10 +1,12 @@ +# Gitaly note: JV: no RPC's here. +# module Gitlab module Git class Tag < Ref attr_reader :object_sha - def initialize(repository, name, target, message = nil) - super(repository, name, target) + def initialize(repository, name, target, target_commit, message = nil) + super(repository, name, target, target_commit) @message = message end diff --git a/lib/gitlab/git/tree.rb b/lib/gitlab/git/tree.rb index b6d4e6cfe46..8122ff0e81f 100644 --- a/lib/gitlab/git/tree.rb +++ b/lib/gitlab/git/tree.rb @@ -1,3 +1,5 @@ +# Gitaly note: JV: needs 1 RPC, migration is in progress. + module Gitlab module Git class Tree @@ -10,6 +12,8 @@ module Gitlab # Get list of tree objects # for repository based on commit sha and path # Uses rugged for raw objects + # + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/320 def where(repository, sha, path = nil) path = nil if path == '' || path == '/' @@ -40,6 +44,8 @@ module Gitlab end end + private + # Recursive search of tree id for path # # Ex. diff --git a/lib/gitlab/git/util.rb b/lib/gitlab/git/util.rb index 7973da2e8f8..4708f22dcb3 100644 --- a/lib/gitlab/git/util.rb +++ b/lib/gitlab/git/util.rb @@ -1,3 +1,5 @@ +# Gitaly note: JV: no RPC's here. + module Gitlab module Git module Util diff --git a/lib/gitlab/git_ref_validator.rb b/lib/gitlab/git_ref_validator.rb index 0e87ee30c98..a3c6b21a6a1 100644 --- a/lib/gitlab/git_ref_validator.rb +++ b/lib/gitlab/git_ref_validator.rb @@ -1,3 +1,5 @@ +# Gitaly note: JV: does not need to be migrated, works without a repo. + module Gitlab module GitRefValidator extend self diff --git a/lib/gitlab/gitaly_client/blob.rb b/lib/gitlab/gitaly_client/blob_service.rb index 0c398b46a08..7ea8e8d0857 100644 --- a/lib/gitlab/gitaly_client/blob.rb +++ b/lib/gitlab/gitaly_client/blob_service.rb @@ -1,10 +1,10 @@ module Gitlab module GitalyClient - class Blob + class BlobService def initialize(repository) @gitaly_repo = repository.gitaly_repository end - + def get_blob(oid:, limit:) request = Gitaly::GetBlobRequest.new( repository: @gitaly_repo, diff --git a/lib/gitlab/gitaly_client/commit.rb b/lib/gitlab/gitaly_client/commit_service.rb index aafc0520664..8f5738fed06 100644 --- a/lib/gitlab/gitaly_client/commit.rb +++ b/lib/gitlab/gitaly_client/commit_service.rb @@ -1,6 +1,6 @@ module Gitlab module GitalyClient - class Commit + class CommitService # The ID of empty tree. # See http://stackoverflow.com/a/40884093/1856239 and https://github.com/git/git/blob/3ad8b5bf26362ac67c9020bf8c30eee54a84f56d/cache.h#L1011-L1012 EMPTY_TREE_ID = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'.freeze @@ -17,20 +17,20 @@ module Gitlab child_id: child_id ) - GitalyClient.call(@repository.storage, :commit, :commit_is_ancestor, request).value + GitalyClient.call(@repository.storage, :commit_service, :commit_is_ancestor, request).value end def diff_from_parent(commit, options = {}) request_params = commit_diff_request_params(commit, options) request_params[:ignore_whitespace_change] = options.fetch(:ignore_whitespace_change, false) request = Gitaly::CommitDiffRequest.new(request_params) - response = GitalyClient.call(@repository.storage, :diff, :commit_diff, request) + response = GitalyClient.call(@repository.storage, :diff_service, :commit_diff, request) Gitlab::Git::DiffCollection.new(GitalyClient::DiffStitcher.new(response), options) end def commit_deltas(commit) request = Gitaly::CommitDeltaRequest.new(commit_diff_request_params(commit)) - response = GitalyClient.call(@repository.storage, :diff, :commit_delta, request) + response = GitalyClient.call(@repository.storage, :diff_service, :commit_delta, request) response.flat_map do |msg| msg.deltas.map { |d| Gitlab::Git::Diff.new(d) } end @@ -44,7 +44,7 @@ module Gitlab limit: limit.to_i ) - response = GitalyClient.call(@repository.storage, :commit, :tree_entry, request) + response = GitalyClient.call(@repository.storage, :commit_service, :tree_entry, request) entry = response.first return unless entry.oid.present? @@ -65,6 +65,17 @@ module Gitlab GitalyClient.call(@repository.storage, :commit_service, :count_commits, request).count end + def between(from, to) + request = Gitaly::CommitsBetweenRequest.new( + repository: @gitaly_repo, + from: from, + to: to + ) + + response = GitalyClient.call(@repository.storage, :commit_service, :commits_between, request) + consume_commits_response(response) + end + private def commit_diff_request_params(commit, options = {}) @@ -77,6 +88,10 @@ module Gitlab paths: options.fetch(:paths, []) } end + + def consume_commits_response(response) + response.flat_map { |r| r.commits } + end end end end diff --git a/lib/gitlab/gitaly_client/notifications.rb b/lib/gitlab/gitaly_client/notification_service.rb index 78ed433e6b8..326e6f7dafc 100644 --- a/lib/gitlab/gitaly_client/notifications.rb +++ b/lib/gitlab/gitaly_client/notification_service.rb @@ -1,6 +1,6 @@ module Gitlab module GitalyClient - class Notifications + class NotificationService # 'repository' is a Gitlab::Git::Repository def initialize(repository) @gitaly_repo = repository.gitaly_repository @@ -10,7 +10,7 @@ module Gitlab def post_receive GitalyClient.call( @storage, - :notifications, + :notification_service, :post_receive, Gitaly::PostReceiveRequest.new(repository: @gitaly_repo) ) diff --git a/lib/gitlab/gitaly_client/ref.rb b/lib/gitlab/gitaly_client/ref_service.rb index 6edc69de078..2c3d53410ac 100644 --- a/lib/gitlab/gitaly_client/ref.rb +++ b/lib/gitlab/gitaly_client/ref_service.rb @@ -1,6 +1,6 @@ module Gitlab module GitalyClient - class Ref + class RefService include Gitlab::EncodingHelper # 'repository' is a Gitlab::Git::Repository @@ -12,19 +12,19 @@ module Gitlab def default_branch_name request = Gitaly::FindDefaultBranchNameRequest.new(repository: @gitaly_repo) - response = GitalyClient.call(@storage, :ref, :find_default_branch_name, request) + response = GitalyClient.call(@storage, :ref_service, :find_default_branch_name, request) Gitlab::Git.branch_name(response.name) end def branch_names request = Gitaly::FindAllBranchNamesRequest.new(repository: @gitaly_repo) - response = GitalyClient.call(@storage, :ref, :find_all_branch_names, request) + response = GitalyClient.call(@storage, :ref_service, :find_all_branch_names, request) consume_refs_response(response) { |name| Gitlab::Git.branch_name(name) } end def tag_names request = Gitaly::FindAllTagNamesRequest.new(repository: @gitaly_repo) - response = GitalyClient.call(@storage, :ref, :find_all_tag_names, request) + response = GitalyClient.call(@storage, :ref_service, :find_all_tag_names, request) consume_refs_response(response) { |name| Gitlab::Git.tag_name(name) } end @@ -34,7 +34,7 @@ module Gitlab commit_id: commit_id, prefix: ref_prefix ) - encode!(GitalyClient.call(@storage, :ref, :find_ref_name, request).name.dup) + encode!(GitalyClient.call(@storage, :ref_service, :find_ref_name, request).name.dup) end def count_tag_names @@ -48,7 +48,7 @@ module Gitlab def local_branches(sort_by: nil) request = Gitaly::FindLocalBranchesRequest.new(repository: @gitaly_repo) request.sort_by = sort_by_param(sort_by) if sort_by - response = GitalyClient.call(@storage, :ref, :find_local_branches, request) + response = GitalyClient.call(@storage, :ref_service, :find_local_branches, request) consume_branches_response(response) end @@ -72,11 +72,39 @@ module Gitlab Gitlab::Git::Branch.new( @repository, encode!(gitaly_branch.name.dup), - gitaly_branch.commit_id + gitaly_branch.commit_id, + commit_from_local_branches_response(gitaly_branch) ) end end end + + def commit_from_local_branches_response(response) + # Git messages have no encoding enforcements. However, in the UI we only + # handle UTF-8, so basically we cross our fingers that the message force + # encoded to UTF-8 is readable. + message = response.commit_subject.dup.force_encoding('UTF-8') + + # NOTE: For ease of parsing in Gitaly, we have only the subject of + # the commit and not the full message. This is ok, since all the + # code that uses `local_branches` only cares at most about the + # commit message. + # TODO: Once gitaly "takes over" Rugged consider separating the + # subject from the message to make it clearer when there's one + # available but not the other. + hash = { + id: response.commit_id, + message: message, + authored_date: Time.at(response.commit_author.date.seconds), + author_name: response.commit_author.name.dup, + author_email: response.commit_author.email.dup, + committed_date: Time.at(response.commit_committer.date.seconds), + committer_name: response.commit_committer.name.dup, + committer_email: response.commit_committer.email.dup + } + + Gitlab::Git::Commit.decorate(hash) + end end end end diff --git a/lib/gitlab/health_checks/fs_shards_check.rb b/lib/gitlab/health_checks/fs_shards_check.rb index 70da4080cae..bebde857b16 100644 --- a/lib/gitlab/health_checks/fs_shards_check.rb +++ b/lib/gitlab/health_checks/fs_shards_check.rb @@ -35,9 +35,9 @@ module Gitlab repository_storages.flat_map do |storage_name| tmp_file_path = tmp_file_path(storage_name) [ - operation_metrics(:filesystem_accessible, :filesystem_access_latency, -> { storage_stat_test(storage_name) }, shard: storage_name), - operation_metrics(:filesystem_writable, :filesystem_write_latency, -> { storage_write_test(tmp_file_path) }, shard: storage_name), - operation_metrics(:filesystem_readable, :filesystem_read_latency, -> { storage_read_test(tmp_file_path) }, shard: storage_name) + operation_metrics(:filesystem_accessible, :filesystem_access_latency_seconds, -> { storage_stat_test(storage_name) }, shard: storage_name), + operation_metrics(:filesystem_writable, :filesystem_write_latency_seconds, -> { storage_write_test(tmp_file_path) }, shard: storage_name), + operation_metrics(:filesystem_readable, :filesystem_read_latency_seconds, -> { storage_read_test(tmp_file_path) }, shard: storage_name) ].flatten end end diff --git a/lib/gitlab/health_checks/redis/cache_check.rb b/lib/gitlab/health_checks/redis/cache_check.rb new file mode 100644 index 00000000000..a28658d42d4 --- /dev/null +++ b/lib/gitlab/health_checks/redis/cache_check.rb @@ -0,0 +1,31 @@ +module Gitlab + module HealthChecks + module Redis + class CacheCheck + extend SimpleAbstractCheck + + class << self + def check_up + check + end + + private + + def metric_prefix + 'redis_cache_ping' + end + + def is_successful?(result) + result == 'PONG' + end + + def check + catch_timeout 10.seconds do + Gitlab::Redis::Cache.with(&:ping) + end + end + end + end + end + end +end diff --git a/lib/gitlab/health_checks/redis/queues_check.rb b/lib/gitlab/health_checks/redis/queues_check.rb new file mode 100644 index 00000000000..f97d50d3947 --- /dev/null +++ b/lib/gitlab/health_checks/redis/queues_check.rb @@ -0,0 +1,31 @@ +module Gitlab + module HealthChecks + module Redis + class QueuesCheck + extend SimpleAbstractCheck + + class << self + def check_up + check + end + + private + + def metric_prefix + 'redis_queues_ping' + end + + def is_successful?(result) + result == 'PONG' + end + + def check + catch_timeout 10.seconds do + Gitlab::Redis::Queues.with(&:ping) + end + end + end + end + end + end +end diff --git a/lib/gitlab/health_checks/redis/redis_check.rb b/lib/gitlab/health_checks/redis/redis_check.rb new file mode 100644 index 00000000000..fe4e3c4a3ab --- /dev/null +++ b/lib/gitlab/health_checks/redis/redis_check.rb @@ -0,0 +1,27 @@ +module Gitlab + module HealthChecks + module Redis + class RedisCheck + extend SimpleAbstractCheck + + class << self + private + + def metric_prefix + 'redis_ping' + end + + def is_successful?(result) + result == 'PONG' + end + + def check + ::Gitlab::HealthChecks::Redis::CacheCheck.check_up && + ::Gitlab::HealthChecks::Redis::QueuesCheck.check_up && + ::Gitlab::HealthChecks::Redis::SharedStateCheck.check_up + end + end + end + end + end +end diff --git a/lib/gitlab/health_checks/redis/shared_state_check.rb b/lib/gitlab/health_checks/redis/shared_state_check.rb new file mode 100644 index 00000000000..e3244392902 --- /dev/null +++ b/lib/gitlab/health_checks/redis/shared_state_check.rb @@ -0,0 +1,31 @@ +module Gitlab + module HealthChecks + module Redis + class SharedStateCheck + extend SimpleAbstractCheck + + class << self + def check_up + check + end + + private + + def metric_prefix + 'redis_shared_state_ping' + end + + def is_successful?(result) + result == 'PONG' + end + + def check + catch_timeout 10.seconds do + Gitlab::Redis::SharedState.with(&:ping) + end + end + end + end + end + end +end diff --git a/lib/gitlab/health_checks/redis_check.rb b/lib/gitlab/health_checks/redis_check.rb deleted file mode 100644 index 57bbe5b3ad0..00000000000 --- a/lib/gitlab/health_checks/redis_check.rb +++ /dev/null @@ -1,25 +0,0 @@ -module Gitlab - module HealthChecks - class RedisCheck - extend SimpleAbstractCheck - - class << self - private - - def metric_prefix - 'redis_ping' - end - - def is_successful?(result) - result == 'PONG' - end - - def check - catch_timeout 10.seconds do - Gitlab::Redis.with(&:ping) - end - end - end - end - end -end diff --git a/lib/gitlab/health_checks/simple_abstract_check.rb b/lib/gitlab/health_checks/simple_abstract_check.rb index fbe1645c1b1..3dcb28a193c 100644 --- a/lib/gitlab/health_checks/simple_abstract_check.rb +++ b/lib/gitlab/health_checks/simple_abstract_check.rb @@ -20,7 +20,7 @@ module Gitlab [ metric("#{metric_prefix}_timeout", result.is_a?(Timeout::Error) ? 1 : 0), metric("#{metric_prefix}_success", is_successful?(result) ? 1 : 0), - metric("#{metric_prefix}_latency", elapsed) + metric("#{metric_prefix}_latency_seconds", elapsed) ] end end diff --git a/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb index f3d489aad0d..a1b896c9511 100644 --- a/lib/gitlab/i18n.rb +++ b/lib/gitlab/i18n.rb @@ -12,8 +12,11 @@ module Gitlab 'zh_HK' => '繁體中文(香港)', 'zh_TW' => '繁體中文(臺灣)', 'bg' => 'български', + 'ru' => 'Русский', 'eo' => 'Esperanto', - 'it' => 'Italiano' + 'it' => 'Italiano', + 'uk' => 'Українська', + 'ja' => '日本語' }.freeze def available_locales diff --git a/lib/gitlab/issuable_metadata.rb b/lib/gitlab/issuable_metadata.rb new file mode 100644 index 00000000000..977c05910d3 --- /dev/null +++ b/lib/gitlab/issuable_metadata.rb @@ -0,0 +1,36 @@ +module Gitlab + module IssuableMetadata + def issuable_meta_data(issuable_collection, collection_type) + # map has to be used here since using pluck or select will + # throw an error when ordering issuables by priority which inserts + # a new order into the collection. + # We cannot use reorder to not mess up the paginated collection. + issuable_ids = issuable_collection.map(&:id) + + return {} if issuable_ids.empty? + + issuable_note_count = ::Note.count_for_collection(issuable_ids, collection_type) + issuable_votes_count = ::AwardEmoji.votes_for_collection(issuable_ids, collection_type) + issuable_merge_requests_count = + if collection_type == 'Issue' + ::MergeRequestsClosingIssues.count_for_collection(issuable_ids) + else + [] + end + + issuable_ids.each_with_object({}) do |id, issuable_meta| + downvotes = issuable_votes_count.find { |votes| votes.awardable_id == id && votes.downvote? } + upvotes = issuable_votes_count.find { |votes| votes.awardable_id == id && votes.upvote? } + notes = issuable_note_count.find { |notes| notes.noteable_id == id } + merge_requests = issuable_merge_requests_count.find { |mr| mr.first == id } + + issuable_meta[id] = ::Issuable::IssuableMeta.new( + upvotes.try(:count).to_i, + downvotes.try(:count).to_i, + notes.try(:count).to_i, + merge_requests.try(:last).to_i + ) + end + end + end +end diff --git a/lib/gitlab/lfs_token.rb b/lib/gitlab/lfs_token.rb index 5f67e97fa2a..8e57ba831c5 100644 --- a/lib/gitlab/lfs_token.rb +++ b/lib/gitlab/lfs_token.rb @@ -18,10 +18,10 @@ module Gitlab end def token - Gitlab::Redis.with do |redis| - token = redis.get(redis_key) + Gitlab::Redis::SharedState.with do |redis| + token = redis.get(redis_shared_state_key) token ||= Devise.friendly_token(TOKEN_LENGTH) - redis.set(redis_key, token, ex: EXPIRY_TIME) + redis.set(redis_shared_state_key, token, ex: EXPIRY_TIME) token end @@ -41,7 +41,7 @@ module Gitlab private - def redis_key + def redis_shared_state_key "gitlab:lfs_token:#{actor.class.name.underscore}_#{actor.id}" if actor end end diff --git a/lib/gitlab/mail_room.rb b/lib/gitlab/mail_room.rb index 3503fac40e8..9f432673a6e 100644 --- a/lib/gitlab/mail_room.rb +++ b/lib/gitlab/mail_room.rb @@ -1,6 +1,6 @@ require 'yaml' require 'json' -require_relative 'redis' unless defined?(Gitlab::Redis) +require_relative 'redis/queues' unless defined?(Gitlab::Redis::Queues) module Gitlab module MailRoom @@ -34,11 +34,11 @@ module Gitlab config[:idle_timeout] = 60 if config[:idle_timeout].nil? if config[:enabled] && config[:address] - gitlab_redis = Gitlab::Redis.new(rails_env) - config[:redis_url] = gitlab_redis.url + gitlab_redis_queues = Gitlab::Redis::Queues.new(rails_env) + config[:redis_url] = gitlab_redis_queues.url - if gitlab_redis.sentinels? - config[:sentinels] = gitlab_redis.sentinels + if gitlab_redis_queues.sentinels? + config[:sentinels] = gitlab_redis_queues.sentinels end end diff --git a/lib/gitlab/metrics/connection_rack_middleware.rb b/lib/gitlab/metrics/connection_rack_middleware.rb deleted file mode 100644 index b3da360be8f..00000000000 --- a/lib/gitlab/metrics/connection_rack_middleware.rb +++ /dev/null @@ -1,45 +0,0 @@ -module Gitlab - module Metrics - class ConnectionRackMiddleware - def initialize(app) - @app = app - end - - def self.rack_request_count - @rack_request_count ||= Gitlab::Metrics.counter(:rack_request, 'Rack request count') - end - - def self.rack_response_count - @rack_response_count ||= Gitlab::Metrics.counter(:rack_response, 'Rack response count') - end - - def self.rack_uncaught_errors_count - @rack_uncaught_errors_count ||= Gitlab::Metrics.counter(:rack_uncaught_errors, 'Rack connections handling uncaught errors count') - end - - def self.rack_execution_time - @rack_execution_time ||= Gitlab::Metrics.histogram(:rack_execution_time, 'Rack connection handling execution time', - {}, [0.05, 0.1, 0.25, 0.5, 0.7, 1, 1.5, 2, 2.5, 3, 5, 7, 10]) - end - - def call(env) - method = env['REQUEST_METHOD'].downcase - started = Time.now.to_f - begin - ConnectionRackMiddleware.rack_request_count.increment(method: method) - - status, headers, body = @app.call(env) - - ConnectionRackMiddleware.rack_response_count.increment(method: method, status: status) - [status, headers, body] - rescue - ConnectionRackMiddleware.rack_uncaught_errors_count.increment - raise - ensure - elapsed = Time.now.to_f - started - ConnectionRackMiddleware.rack_execution_time.observe({}, elapsed) - end - end - end - end -end diff --git a/lib/gitlab/metrics/prometheus.rb b/lib/gitlab/metrics/prometheus.rb index fb7bbc7cfc7..460dab47276 100644 --- a/lib/gitlab/metrics/prometheus.rb +++ b/lib/gitlab/metrics/prometheus.rb @@ -6,9 +6,11 @@ module Gitlab include Gitlab::CurrentSettings def metrics_folder_present? - ENV.has_key?('prometheus_multiproc_dir') && - ::Dir.exist?(ENV['prometheus_multiproc_dir']) && - ::File.writable?(ENV['prometheus_multiproc_dir']) + multiprocess_files_dir = ::Prometheus::Client.configuration.multiprocess_files_dir + + multiprocess_files_dir && + ::Dir.exist?(multiprocess_files_dir) && + ::File.writable?(multiprocess_files_dir) end def prometheus_metrics_enabled? diff --git a/lib/gitlab/metrics/requests_rack_middleware.rb b/lib/gitlab/metrics/requests_rack_middleware.rb new file mode 100644 index 00000000000..0dc19f31d03 --- /dev/null +++ b/lib/gitlab/metrics/requests_rack_middleware.rb @@ -0,0 +1,40 @@ +module Gitlab + module Metrics + class RequestsRackMiddleware + def initialize(app) + @app = app + end + + def self.http_request_total + @http_request_total ||= Gitlab::Metrics.counter(:http_requests_total, 'Request count') + end + + def self.rack_uncaught_errors_count + @rack_uncaught_errors_count ||= Gitlab::Metrics.counter(:rack_uncaught_errors_total, 'Request handling uncaught errors count') + end + + def self.http_request_duration_seconds + @http_request_duration_seconds ||= Gitlab::Metrics.histogram(:http_request_duration_seconds, 'Request handling execution time', + {}, [0.05, 0.1, 0.25, 0.5, 0.7, 1, 2.5, 5, 10, 25]) + end + + def call(env) + method = env['REQUEST_METHOD'].downcase + started = Time.now.to_f + begin + RequestsRackMiddleware.http_request_total.increment(method: method) + + status, headers, body = @app.call(env) + + elapsed = Time.now.to_f - started + RequestsRackMiddleware.http_request_duration_seconds.observe({ method: method, status: status }, elapsed) + + [status, headers, body] + rescue + RequestsRackMiddleware.rack_uncaught_errors_count.increment + raise + end + end + end + end +end diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb index b3f453e506d..3f2bbd9f6a6 100644 --- a/lib/gitlab/o_auth/user.rb +++ b/lib/gitlab/o_auth/user.rb @@ -101,14 +101,18 @@ module Gitlab # Look for a corresponding person with same uid in any of the configured LDAP providers Gitlab::LDAP::Config.providers.each do |provider| adapter = Gitlab::LDAP::Adapter.new(provider) - @ldap_person = Gitlab::LDAP::Person.find_by_uid(auth_hash.uid, adapter) - # The `uid` might actually be a DN. Try it next. - @ldap_person ||= Gitlab::LDAP::Person.find_by_dn(auth_hash.uid, adapter) + @ldap_person = find_ldap_person(auth_hash, adapter) break if @ldap_person end @ldap_person end + def find_ldap_person(auth_hash, adapter) + by_uid = Gitlab::LDAP::Person.find_by_uid(auth_hash.uid, adapter) + # The `uid` might actually be a DN. Try it next. + by_uid || Gitlab::LDAP::Person.find_by_dn(auth_hash.uid, adapter) + end + def ldap_config Gitlab::LDAP::Config.new(ldap_person.provider) if ldap_person end diff --git a/lib/gitlab/path_regex.rb b/lib/gitlab/path_regex.rb index d81f825ef96..60a32d5d5ea 100644 --- a/lib/gitlab/path_regex.rb +++ b/lib/gitlab/path_regex.rb @@ -49,7 +49,6 @@ module Gitlab sent_notifications services snippets - system teams u unicorn_test diff --git a/lib/gitlab/performance_bar.rb b/lib/gitlab/performance_bar.rb index 2da2ce45ebc..56112ec2301 100644 --- a/lib/gitlab/performance_bar.rb +++ b/lib/gitlab/performance_bar.rb @@ -2,7 +2,8 @@ module Gitlab module PerformanceBar include Gitlab::CurrentSettings - ALLOWED_USER_IDS_KEY = 'performance_bar_allowed_user_ids'.freeze + ALLOWED_USER_IDS_KEY = 'performance_bar_allowed_user_ids:v2'.freeze + EXPIRY_TIME = 5.minutes def self.enabled?(user = nil) return false unless user && allowed_group_id @@ -15,7 +16,7 @@ module Gitlab end def self.allowed_user_ids - Rails.cache.fetch(ALLOWED_USER_IDS_KEY) do + Rails.cache.fetch(ALLOWED_USER_IDS_KEY, expires_in: EXPIRY_TIME) do group = Group.find_by_id(allowed_group_id) if group diff --git a/lib/gitlab/performance_bar/peek_performance_bar_with_rack_body.rb b/lib/gitlab/performance_bar/peek_performance_bar_with_rack_body.rb deleted file mode 100644 index d939a6ea18d..00000000000 --- a/lib/gitlab/performance_bar/peek_performance_bar_with_rack_body.rb +++ /dev/null @@ -1,22 +0,0 @@ -# This solves a bug with a X-Senfile header that wouldn't be set properly, see -# https://github.com/peek/peek-performance_bar/pull/27 -module Gitlab - module PerformanceBar - module PeekPerformanceBarWithRackBody - def call(env) - @env = env - reset_stats - - @total_requests += 1 - first_request if @total_requests == 1 - - env['process.request_start'] = @start.to_f - env['process.total_requests'] = total_requests - - status, headers, body = @app.call(env) - body = Rack::BodyProxy.new(body) { record_request } - [status, headers, body] - end - end - end -end diff --git a/lib/gitlab/performance_bar/peek_query_tracker.rb b/lib/gitlab/performance_bar/peek_query_tracker.rb index 574ae8731a5..67fee8c227d 100644 --- a/lib/gitlab/performance_bar/peek_query_tracker.rb +++ b/lib/gitlab/performance_bar/peek_query_tracker.rb @@ -1,4 +1,5 @@ # Inspired by https://github.com/peek/peek-pg/blob/master/lib/peek/views/pg.rb +# PEEK_DB_CLIENT is a constant set in config/initializers/peek.rb module Gitlab module PerformanceBar module PeekQueryTracker @@ -23,14 +24,20 @@ module Gitlab subscribe('sql.active_record') do |_, start, finish, _, data| if RequestStore.active? && RequestStore.store[:peek_enabled] - track_query(data[:sql].strip, data[:binds], start, finish) + # data[:cached] is only available starting from Rails 5.1.0 + # https://github.com/rails/rails/blob/v5.1.0/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb#L113 + # Before that, data[:name] was set to 'CACHE' + # https://github.com/rails/rails/blob/v4.2.9/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb#L80 + unless data.fetch(:cached, data[:name] == 'CACHE') + track_query(data[:sql].strip, data[:binds], start, finish) + end end end end def track_query(raw_query, bindings, start, finish) query = Gitlab::Sherlock::Query.new(raw_query, start, finish) - query_info = { duration: '%.3f' % query.duration, sql: query.formatted_query } + query_info = { duration: query.duration.round(3), sql: query.formatted_query } PEEK_DB_CLIENT.query_details << query_info end diff --git a/lib/gitlab/redis.rb b/lib/gitlab/redis.rb deleted file mode 100644 index bc5370de32a..00000000000 --- a/lib/gitlab/redis.rb +++ /dev/null @@ -1,102 +0,0 @@ -# This file should not have any direct dependency on Rails environment -# please require all dependencies below: -require 'active_support/core_ext/hash/keys' -require 'active_support/core_ext/module/delegation' - -module Gitlab - class Redis - CACHE_NAMESPACE = 'cache:gitlab'.freeze - SESSION_NAMESPACE = 'session:gitlab'.freeze - SIDEKIQ_NAMESPACE = 'resque:gitlab'.freeze - MAILROOM_NAMESPACE = 'mail_room:gitlab'.freeze - DEFAULT_REDIS_URL = 'redis://localhost:6379'.freeze - - class << self - delegate :params, :url, to: :new - - def with - @pool ||= ConnectionPool.new(size: pool_size) { ::Redis.new(params) } - @pool.with { |redis| yield redis } - end - - def pool_size - if Sidekiq.server? - # the pool will be used in a multi-threaded context - Sidekiq.options[:concurrency] + 5 - else - # probably this is a Unicorn process, so single threaded - 5 - end - end - - def _raw_config - return @_raw_config if defined?(@_raw_config) - - begin - @_raw_config = ERB.new(File.read(config_file)).result.freeze - rescue Errno::ENOENT - @_raw_config = false - end - - @_raw_config - end - - def config_file - ENV['GITLAB_REDIS_CONFIG_FILE'] || File.expand_path('../../config/resque.yml', __dir__) - end - end - - def initialize(rails_env = nil) - @rails_env = rails_env || ::Rails.env - end - - def params - redis_store_options - end - - def url - raw_config_hash[:url] - end - - def sentinels - raw_config_hash[:sentinels] - end - - def sentinels? - sentinels && !sentinels.empty? - end - - private - - def redis_store_options - config = raw_config_hash - redis_url = config.delete(:url) - redis_uri = URI.parse(redis_url) - - if redis_uri.scheme == 'unix' - # Redis::Store does not handle Unix sockets well, so let's do it for them - config[:path] = redis_uri.path - config - else - redis_hash = ::Redis::Store::Factory.extract_host_options_from_uri(redis_url) - # order is important here, sentinels must be after the connection keys. - # {url: ..., port: ..., sentinels: [...]} - redis_hash.merge(config) - end - end - - def raw_config_hash - config_data = fetch_config - - if config_data - config_data.is_a?(String) ? { url: config_data } : config_data.deep_symbolize_keys - else - { url: DEFAULT_REDIS_URL } - end - end - - def fetch_config - self.class._raw_config ? YAML.load(self.class._raw_config)[@rails_env] : false - end - end -end diff --git a/lib/gitlab/redis/cache.rb b/lib/gitlab/redis/cache.rb new file mode 100644 index 00000000000..b0da516ff83 --- /dev/null +++ b/lib/gitlab/redis/cache.rb @@ -0,0 +1,34 @@ +# please require all dependencies below: +require_relative 'wrapper' unless defined?(::Gitlab::Redis::Wrapper) + +module Gitlab + module Redis + class Cache < ::Gitlab::Redis::Wrapper + CACHE_NAMESPACE = 'cache:gitlab'.freeze + DEFAULT_REDIS_CACHE_URL = 'redis://localhost:6380'.freeze + REDIS_CACHE_CONFIG_ENV_VAR_NAME = 'GITLAB_REDIS_CACHE_CONFIG_FILE'.freeze + if defined?(::Rails) && ::Rails.root.present? + DEFAULT_REDIS_CACHE_CONFIG_FILE_NAME = ::Rails.root.join('config', 'redis.cache.yml').freeze + end + + class << self + def default_url + DEFAULT_REDIS_CACHE_URL + end + + def config_file_name + # if ENV set for this class, use it even if it points to a file does not exist + file_name = ENV[REDIS_CACHE_CONFIG_ENV_VAR_NAME] + return file_name unless file_name.nil? + + # otherwise, if config files exists for this class, use it + file_name = File.expand_path(DEFAULT_REDIS_CACHE_CONFIG_FILE_NAME, __dir__) + return file_name if File.file?(file_name) + + # this will force use of DEFAULT_REDIS_QUEUES_URL when config file is absent + super + end + end + end + end +end diff --git a/lib/gitlab/redis/queues.rb b/lib/gitlab/redis/queues.rb new file mode 100644 index 00000000000..f9249d05565 --- /dev/null +++ b/lib/gitlab/redis/queues.rb @@ -0,0 +1,35 @@ +# please require all dependencies below: +require_relative 'wrapper' unless defined?(::Gitlab::Redis::Wrapper) + +module Gitlab + module Redis + class Queues < ::Gitlab::Redis::Wrapper + SIDEKIQ_NAMESPACE = 'resque:gitlab'.freeze + MAILROOM_NAMESPACE = 'mail_room:gitlab'.freeze + DEFAULT_REDIS_QUEUES_URL = 'redis://localhost:6381'.freeze + REDIS_QUEUES_CONFIG_ENV_VAR_NAME = 'GITLAB_REDIS_QUEUES_CONFIG_FILE'.freeze + if defined?(::Rails) && ::Rails.root.present? + DEFAULT_REDIS_QUEUES_CONFIG_FILE_NAME = ::Rails.root.join('config', 'redis.queues.yml').freeze + end + + class << self + def default_url + DEFAULT_REDIS_QUEUES_URL + end + + def config_file_name + # if ENV set for this class, use it even if it points to a file does not exist + file_name = ENV[REDIS_QUEUES_CONFIG_ENV_VAR_NAME] + return file_name if file_name + + # otherwise, if config files exists for this class, use it + file_name = File.expand_path(DEFAULT_REDIS_QUEUES_CONFIG_FILE_NAME, __dir__) + return file_name if File.file?(file_name) + + # this will force use of DEFAULT_REDIS_QUEUES_URL when config file is absent + super + end + end + end + end +end diff --git a/lib/gitlab/redis/shared_state.rb b/lib/gitlab/redis/shared_state.rb new file mode 100644 index 00000000000..395dcf082da --- /dev/null +++ b/lib/gitlab/redis/shared_state.rb @@ -0,0 +1,34 @@ +# please require all dependencies below: +require_relative 'wrapper' unless defined?(::Gitlab::Redis::Wrapper) + +module Gitlab + module Redis + class SharedState < ::Gitlab::Redis::Wrapper + SESSION_NAMESPACE = 'session:gitlab'.freeze + DEFAULT_REDIS_SHARED_STATE_URL = 'redis://localhost:6382'.freeze + REDIS_SHARED_STATE_CONFIG_ENV_VAR_NAME = 'GITLAB_REDIS_SHARED_STATE_CONFIG_FILE'.freeze + if defined?(::Rails) && ::Rails.root.present? + DEFAULT_REDIS_SHARED_STATE_CONFIG_FILE_NAME = ::Rails.root.join('config', 'redis.shared_state.yml').freeze + end + + class << self + def default_url + DEFAULT_REDIS_SHARED_STATE_URL + end + + def config_file_name + # if ENV set for this class, use it even if it points to a file does not exist + file_name = ENV[REDIS_SHARED_STATE_CONFIG_ENV_VAR_NAME] + return file_name if file_name + + # otherwise, if config files exists for this class, use it + file_name = File.expand_path(DEFAULT_REDIS_SHARED_STATE_CONFIG_FILE_NAME, __dir__) + return file_name if File.file?(file_name) + + # this will force use of DEFAULT_REDIS_SHARED_STATE_URL when config file is absent + super + end + end + end + end +end diff --git a/lib/gitlab/redis/wrapper.rb b/lib/gitlab/redis/wrapper.rb new file mode 100644 index 00000000000..c43b37dde74 --- /dev/null +++ b/lib/gitlab/redis/wrapper.rb @@ -0,0 +1,135 @@ +# This file should only be used by sub-classes, not directly by any clients of the sub-classes +# please require all dependencies below: +require 'active_support/core_ext/hash/keys' +require 'active_support/core_ext/module/delegation' + +module Gitlab + module Redis + class Wrapper + DEFAULT_REDIS_URL = 'redis://localhost:6379'.freeze + REDIS_CONFIG_ENV_VAR_NAME = 'GITLAB_REDIS_CONFIG_FILE'.freeze + if defined?(::Rails) && ::Rails.root.present? + DEFAULT_REDIS_CONFIG_FILE_NAME = ::Rails.root.join('config', 'resque.yml').freeze + end + + class << self + delegate :params, :url, to: :new + + def with + @pool ||= ConnectionPool.new(size: pool_size) { ::Redis.new(params) } + @pool.with { |redis| yield redis } + end + + def pool_size + # heuristic constant 5 should be a config setting somewhere -- related to CPU count? + size = 5 + if Sidekiq.server? + # the pool will be used in a multi-threaded context + size += Sidekiq.options[:concurrency] + end + size + end + + def _raw_config + return @_raw_config if defined?(@_raw_config) + + @_raw_config = + begin + if filename = config_file_name + ERB.new(File.read(filename)).result.freeze + else + false + end + rescue Errno::ENOENT + false + end + end + + def default_url + DEFAULT_REDIS_URL + end + + def config_file_name + # if ENV set for wrapper class, use it even if it points to a file does not exist + file_name = ENV[REDIS_CONFIG_ENV_VAR_NAME] + return file_name unless file_name.nil? + + # otherwise, if config files exists for wrapper class, use it + file_name = File.expand_path(DEFAULT_REDIS_CONFIG_FILE_NAME, __dir__) + return file_name if File.file?(file_name) + + # nil will force use of DEFAULT_REDIS_URL when config file is absent + nil + end + end + + def initialize(rails_env = nil) + @rails_env = rails_env || ::Rails.env + end + + def params + redis_store_options + end + + def url + raw_config_hash[:url] + end + + def sentinels + raw_config_hash[:sentinels] + end + + def sentinels? + sentinels && !sentinels.empty? + end + + private + + def redis_store_options + config = raw_config_hash + redis_url = config.delete(:url) + redis_uri = URI.parse(redis_url) + + if redis_uri.scheme == 'unix' + # Redis::Store does not handle Unix sockets well, so let's do it for them + config[:path] = redis_uri.path + query = redis_uri.query + unless query.nil? + queries = CGI.parse(redis_uri.query) + db_numbers = queries["db"] if queries.key?("db") + config[:db] = db_numbers[0].to_i if db_numbers.any? + end + config + else + redis_hash = ::Redis::Store::Factory.extract_host_options_from_uri(redis_url) + # order is important here, sentinels must be after the connection keys. + # {url: ..., port: ..., sentinels: [...]} + redis_hash.merge(config) + end + end + + def raw_config_hash + config_data = fetch_config + + if config_data + config_data.is_a?(String) ? { url: config_data } : config_data.deep_symbolize_keys + else + { url: self.class.default_url } + end + end + + def fetch_config + return false unless self.class._raw_config + + yaml = YAML.load(self.class._raw_config) + + # If the file has content but it's invalid YAML, `load` returns false + if yaml + yaml.fetch(@rails_env, false) + else + false + end + end + end + end +end diff --git a/lib/gitlab/route_map.rb b/lib/gitlab/route_map.rb index 877aa6e6a28..f3952657983 100644 --- a/lib/gitlab/route_map.rb +++ b/lib/gitlab/route_map.rb @@ -18,7 +18,11 @@ module Gitlab mapping = @map.find { |mapping| mapping[:source] === path } return unless mapping - path.sub(mapping[:source], mapping[:public]) + if mapping[:source].is_a?(String) + path.sub(mapping[:source], mapping[:public]) + else + mapping[:source].replace(path, mapping[:public]) + end end private @@ -35,7 +39,7 @@ module Gitlab source_pattern = source_pattern[1...-1].gsub('\/', '/') begin - source_pattern = /\A#{source_pattern}\z/ + source_pattern = Gitlab::UntrustedRegexp.new('\A' + source_pattern + '\z') rescue RegexpError => e raise FormatError, "Route map entry source is not a valid regular expression: #{e}" end diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb index 0baea092e6a..4366ff336ef 100644 --- a/lib/gitlab/shell.rb +++ b/lib/gitlab/shell.rb @@ -1,3 +1,6 @@ +# Gitaly note: JV: two sets of straightforward RPC's. 1 Hard RPC: fork_repository. +# SSH key operations are not part of Gitaly so will never be migrated. + require 'securerandom' module Gitlab @@ -68,6 +71,7 @@ module Gitlab # Ex. # add_repository("/path/to/storage", "gitlab/gitlab-ci") # + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/387 def add_repository(storage, name) gitlab_shell_fast_execute([gitlab_shell_projects_path, 'add-project', storage, "#{name}.git"]) @@ -81,6 +85,7 @@ module Gitlab # Ex. # import_repository("/path/to/storage", "gitlab/gitlab-ci", "https://github.com/randx/six.git") # + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/387 def import_repository(storage, name, url) # Timeout should be less than 900 ideally, to prevent the memory killer # to silently kill the process without knowing we are timing out here. @@ -99,6 +104,7 @@ module Gitlab # Ex. # fetch_remote("gitlab/gitlab-ci", "upstream") # + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/387 def fetch_remote(storage, name, remote, forced: false, no_tags: false) args = [gitlab_shell_projects_path, 'fetch-remote', storage, "#{name}.git", remote, "#{Gitlab.config.gitlab_shell.git_timeout}"] args << '--force' if forced @@ -115,6 +121,7 @@ module Gitlab # Ex. # mv_repository("/path/to/storage", "gitlab/gitlab-ci", "randx/gitlab-ci-new") # + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/387 def mv_repository(storage, path, new_path) gitlab_shell_fast_execute([gitlab_shell_projects_path, 'mv-project', storage, "#{path}.git", "#{new_path}.git"]) @@ -129,6 +136,7 @@ module Gitlab # Ex. # fork_repository("/path/to/forked_from/storage", "gitlab/gitlab-ci", "/path/to/forked_to/storage", "randx") # + # Gitaly note: JV: not easy to migrate because this involves two Gitaly servers, not one. def fork_repository(forked_from_storage, path, forked_to_storage, fork_namespace) gitlab_shell_fast_execute([gitlab_shell_projects_path, 'fork-project', forked_from_storage, "#{path}.git", forked_to_storage, @@ -143,6 +151,7 @@ module Gitlab # Ex. # remove_repository("/path/to/storage", "gitlab/gitlab-ci") # + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/387 def remove_repository(storage, name) gitlab_shell_fast_execute([gitlab_shell_projects_path, 'rm-project', storage, "#{name}.git"]) @@ -194,6 +203,7 @@ module Gitlab # Ex. # add_namespace("/path/to/storage", "gitlab") # + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/385 def add_namespace(storage, name) path = full_path(storage, name) FileUtils.mkdir_p(path, mode: 0770) unless exists?(storage, name) @@ -207,6 +217,7 @@ module Gitlab # Ex. # rm_namespace("/path/to/storage", "gitlab") # + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/385 def rm_namespace(storage, name) FileUtils.rm_r(full_path(storage, name), force: true) end @@ -216,6 +227,7 @@ module Gitlab # Ex. # mv_namespace("/path/to/storage", "gitlab", "gitlabhq") # + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/385 def mv_namespace(storage, old_name, new_name) return false if exists?(storage, new_name) || !exists?(storage, old_name) @@ -241,6 +253,7 @@ module Gitlab # exists?(storage, 'gitlab') # exists?(storage, 'gitlab/cookies.git') # + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/385 def exists?(storage, dir_name) File.exist?(full_path(storage, dir_name)) end diff --git a/lib/gitlab/untrusted_regexp.rb b/lib/gitlab/untrusted_regexp.rb new file mode 100644 index 00000000000..8b43f0053d6 --- /dev/null +++ b/lib/gitlab/untrusted_regexp.rb @@ -0,0 +1,53 @@ +module Gitlab + # An untrusted regular expression is any regexp containing patterns sourced + # from user input. + # + # Ruby's built-in regular expression library allows patterns which complete in + # exponential time, permitting denial-of-service attacks. + # + # Not all regular expression features are available in untrusted regexes, and + # there is a strict limit on total execution time. See the RE2 documentation + # at https://github.com/google/re2/wiki/Syntax for more details. + class UntrustedRegexp + delegate :===, to: :regexp + + def initialize(pattern) + @regexp = RE2::Regexp.new(pattern, log_errors: false) + + raise RegexpError.new(regexp.error) unless regexp.ok? + end + + def replace_all(text, rewrite) + RE2.GlobalReplace(text, regexp, rewrite) + end + + def scan(text) + scan_regexp.scan(text).map do |match| + if regexp.number_of_capturing_groups == 0 + match.first + else + match + end + end + end + + def replace(text, rewrite) + RE2.Replace(text, regexp, rewrite) + end + + private + + attr_reader :regexp + + # RE2 scan operates differently to Ruby scan when there are no capture + # groups, so work around it + def scan_regexp + @scan_regexp ||= + if regexp.number_of_capturing_groups == 0 + RE2::Regexp.new('(' + regexp.source + ')') + else + regexp + end + end + end +end diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb index 35792d2d67f..824e2d7251f 100644 --- a/lib/gitlab/url_builder.rb +++ b/lib/gitlab/url_builder.rb @@ -52,15 +52,13 @@ module Gitlab commit_url(id: object.commit_id, anchor: dom_id(object)) elsif object.for_issue? - issue = Issue.find(object.noteable_id) - issue_url(issue, anchor: dom_id(object)) + issue_url(object.noteable, anchor: dom_id(object)) elsif object.for_merge_request? - merge_request = MergeRequest.find(object.noteable_id) - merge_request_url(merge_request, anchor: dom_id(object)) + merge_request_url(object.noteable, anchor: dom_id(object)) elsif object.for_snippet? - snippet = Snippet.find(object.noteable_id) + snippet = object.noteable if snippet.is_a?(PersonalSnippet) snippet_url(snippet, anchor: dom_id(object)) diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb index f19b325a126..dba071d7e47 100644 --- a/lib/gitlab/usage_data.rb +++ b/lib/gitlab/usage_data.rb @@ -39,6 +39,7 @@ module Gitlab notes: Note.count, pages_domains: PagesDomain.count, projects: Project.count, + projects_imported_from_github: Project.where(import_type: 'github').count, projects_prometheus_active: PrometheusService.active.count, protected_branches: ProtectedBranch.count, releases: Release.count, diff --git a/lib/gitlab/user_access.rb b/lib/gitlab/user_access.rb index 3b922da7ced..8e91ee7287c 100644 --- a/lib/gitlab/user_access.rb +++ b/lib/gitlab/user_access.rb @@ -1,5 +1,11 @@ module Gitlab class UserAccess + extend Gitlab::Cache::RequestCache + + request_cache_key do + [user&.id, project&.id] + end + attr_reader :user, :project def initialize(user, project: nil) @@ -28,7 +34,7 @@ module Gitlab true end - def can_create_tag?(ref) + request_cache def can_create_tag?(ref) return false unless can_access_git? if ProtectedTag.protected?(project, ref) @@ -38,7 +44,7 @@ module Gitlab end end - def can_delete_branch?(ref) + request_cache def can_delete_branch?(ref) return false unless can_access_git? if ProtectedBranch.protected?(project, ref) @@ -48,7 +54,7 @@ module Gitlab end end - def can_push_to_branch?(ref) + request_cache def can_push_to_branch?(ref) return false unless can_access_git? if ProtectedBranch.protected?(project, ref) @@ -60,7 +66,7 @@ module Gitlab end end - def can_merge_to_branch?(ref) + request_cache def can_merge_to_branch?(ref) return false unless can_access_git? if ProtectedBranch.protected?(project, ref) diff --git a/lib/gitlab/user_activities.rb b/lib/gitlab/user_activities.rb index eb36ab9fded..125488536e1 100644 --- a/lib/gitlab/user_activities.rb +++ b/lib/gitlab/user_activities.rb @@ -6,13 +6,13 @@ module Gitlab BATCH_SIZE = 500 def self.record(key, time = Time.now) - Gitlab::Redis.with do |redis| + Gitlab::Redis::SharedState.with do |redis| redis.hset(KEY, key, time.to_i) end end def delete(*keys) - Gitlab::Redis.with do |redis| + Gitlab::Redis::SharedState.with do |redis| redis.hdel(KEY, keys) end end @@ -21,7 +21,7 @@ module Gitlab cursor = 0 loop do cursor, pairs = - Gitlab::Redis.with do |redis| + Gitlab::Redis::SharedState.with do |redis| redis.hscan(KEY, cursor, count: BATCH_SIZE) end diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb index 48f3d950779..c60bd91ea6e 100644 --- a/lib/gitlab/visibility_level.rb +++ b/lib/gitlab/visibility_level.rb @@ -89,12 +89,12 @@ module Gitlab end def level_name(level) - level_name = 'Unknown' + level_name = N_('VisibilityLevel|Unknown') options.each do |name, lvl| level_name = name if lvl == level.to_i end - level_name + s_(level_name) end def level_value(level) diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb index 4aef23b6aee..916ef365d78 100644 --- a/lib/gitlab/workhorse.rb +++ b/lib/gitlab/workhorse.rb @@ -62,10 +62,21 @@ module Gitlab end def send_git_blob(repository, blob) - params = { - 'RepoPath' => repository.path_to_repo, - 'BlobId' => blob.id - } + params = if Gitlab::GitalyClient.feature_enabled?(:project_raw_show) + { + 'GitalyServer' => gitaly_server_hash(repository), + 'GetBlobRequest' => { + repository: repository.gitaly_repository.to_h, + oid: blob.id, + limit: -1 + } + } + else + { + 'RepoPath' => repository.path_to_repo, + 'BlobId' => blob.id + } + end [ SEND_DATA_HEADER, @@ -176,7 +187,7 @@ module Gitlab end def set_key_and_notify(key, value, expire: nil, overwrite: true) - Gitlab::Redis.with do |redis| + Gitlab::Redis::Queues.with do |redis| result = redis.set(key, value, ex: expire, nx: !overwrite) if result redis.publish(NOTIFICATION_CHANNEL, "#{key}=#{value}") @@ -192,6 +203,13 @@ module Gitlab def encode(hash) Base64.urlsafe_encode64(JSON.dump(hash)) end + + def gitaly_server_hash(repository) + { + address: Gitlab::GitalyClient.address(repository.project.repository_storage), + token: Gitlab::GitalyClient.token(repository.project.repository_storage) + } + end end end end diff --git a/lib/tasks/cache.rake b/lib/tasks/cache.rake index 125a3d560d6..564aa141952 100644 --- a/lib/tasks/cache.rake +++ b/lib/tasks/cache.rake @@ -5,12 +5,12 @@ namespace :cache do desc "GitLab | Clear redis cache" task redis: :environment do - Gitlab::Redis.with do |redis| + Gitlab::Redis::Cache.with do |redis| cursor = REDIS_SCAN_START_STOP loop do cursor, keys = redis.scan( cursor, - match: "#{Gitlab::Redis::CACHE_NAMESPACE}*", + match: "#{Gitlab::Redis::Cache::CACHE_NAMESPACE}*", count: REDIS_CLEAR_BATCH_SIZE ) diff --git a/lib/tasks/gettext.rake b/lib/tasks/gettext.rake index b27f7475115..b48e4dce445 100644 --- a/lib/tasks/gettext.rake +++ b/lib/tasks/gettext.rake @@ -5,7 +5,7 @@ namespace :gettext do # See: https://github.com/grosser/gettext_i18n_rails#customizing-list-of-translatable-files def files_to_translate folders = %W(app lib config #{locale_path}).join(',') - exts = %w(rb erb haml slim rhtml js jsx vue coffee handlebars hbs mustache).join(',') + exts = %w(rb erb haml slim rhtml js jsx vue handlebars hbs mustache).join(',') Dir.glob( "{#{folders}}/**/*.{#{exts}}" diff --git a/locale/bg/gitlab.po b/locale/bg/gitlab.po index db4dc9a02da..1774c911d71 100644 --- a/locale/bg/gitlab.po +++ b/locale/bg/gitlab.po @@ -4,22 +4,22 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-06-19 15:50-0500\n" +"POT-Creation-Date: 2017-07-05 08:50-0500\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-06-23 04:07-0400\n" +"PO-Revision-Date: 2017-07-13 08:13-0400\n" "Last-Translator: Lyubomir Vasilev <lyubomirv@abv.bg>\n" "Language-Team: Bulgarian (https://translate.zanata.org/project/view/GitLab)\n" "Language: bg\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -msgid "%d additional commit has been omitted to prevent performance issues." +msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "" -"%d additional commits have been omitted to prevent performance issues." -msgstr[0] "%d подаване беше пропуснато, за да не се натоварва системата." -msgstr[1] "%d подавания бяха пропуснати, за да не се натоварва системата." +"%s additional commits have been omitted to prevent performance issues." +msgstr[0] "%s подаване беше пропуснато, за да не се натоварва системата." +msgstr[1] "%s подавания бяха пропуснати, за да не се натоварва системата." msgid "%d commit" msgid_plural "%d commits" @@ -29,6 +29,14 @@ msgstr[1] "%d подавания" msgid "%{commit_author_link} committed %{commit_timeago}" msgstr "%{commit_author_link} подаде %{commit_timeago}" +msgid "1 pipeline" +msgid_plural "%d pipelines" +msgstr[0] "1 схема" +msgstr[1] "%d схеми" + +msgid "A collection of graphs regarding Continuous Integration" +msgstr "Набор от графики относно непрекъснатата интеграция" + msgid "About auto deploy" msgstr "Относно автоматичното внедряване" @@ -191,6 +199,9 @@ msgid_plural "Commits" msgstr[0] "Подаване" msgstr[1] "Подавания" +msgid "Commit duration in minutes for last 30 commits" +msgstr "Времетраене на подаванията в минути за последните 30 подавания" + msgid "Commit message" msgstr "Съобщение за подаването" @@ -230,6 +241,13 @@ msgstr "Копиране на идентификатора на подаване msgid "Create New Directory" msgstr "Създаване на нова папка" +msgid "" +"Create a personal access token on your account to pull or push via " +"%{protocol}." +msgstr "" +"Създайте си личен жетон за достъп в профила си, за да можете да изтегляте и " +"изпращате промени чрез %{protocol}." + msgid "Create directory" msgstr "Създаване на папка" @@ -248,6 +266,9 @@ msgstr "Разклоняване" msgid "CreateTag|Tag" msgstr "Етикет" +msgid "CreateTokenToCloneLink|create a personal access token" +msgstr "си създадете личен жетон за достъп" + msgid "Cron Timezone" msgstr "Часова зона за „Cron“" @@ -419,6 +440,15 @@ msgstr "Шаблон за интервала" msgid "Introducing Cycle Analytics" msgstr "Представяме Ви анализа на циклите" +msgid "Jobs for last month" +msgstr "Задачи за последния месец" + +msgid "Jobs for last week" +msgstr "Задачи за последната седмица" + +msgid "Jobs for last year" +msgstr "Задачи за последната година" + msgid "LFSStatus|Disabled" msgstr "Изключено" @@ -584,6 +614,21 @@ msgstr "План за схема" msgid "Pipeline Schedules" msgstr "Планове за схема" +msgid "PipelineCharts|Failed:" +msgstr "Неуспешни:" + +msgid "PipelineCharts|Overall statistics" +msgstr "Обща статистика" + +msgid "PipelineCharts|Success ratio:" +msgstr "Коефициент на успех:" + +msgid "PipelineCharts|Successful:" +msgstr "Успешни:" + +msgid "PipelineCharts|Total:" +msgstr "Общо:" + msgid "PipelineSchedules|Activated" msgstr "Включено" @@ -596,6 +641,12 @@ msgstr "Всички" msgid "PipelineSchedules|Inactive" msgstr "Неактивно" +msgid "PipelineSchedules|Input variable key" +msgstr "Въведете ключ за променливата" + +msgid "PipelineSchedules|Input variable value" +msgstr "Въведете стойността на променливата" + msgid "PipelineSchedules|Next Run" msgstr "Следващо изпълнение" @@ -605,15 +656,33 @@ msgstr "Нищо" msgid "PipelineSchedules|Provide a short description for this pipeline" msgstr "Въведете кратко описание за тази схема" +msgid "PipelineSchedules|Remove variable row" +msgstr "Премахване на реда за променлива" + msgid "PipelineSchedules|Take ownership" msgstr "Поемане на собствеността" msgid "PipelineSchedules|Target" msgstr "Цел" +msgid "PipelineSchedules|Variables" +msgstr "Променливи" + msgid "PipelineSheduleIntervalPattern|Custom" msgstr "собствен" +msgid "Pipelines" +msgstr "Схеми" + +msgid "Pipelines charts" +msgstr "Графики за схемите" + +msgid "Pipeline|all" +msgstr "всички" + +msgid "Pipeline|success" +msgstr "успешни" + msgid "Pipeline|with stage" msgstr "с етап" @@ -1092,6 +1161,15 @@ msgid "Withdraw Access Request" msgstr "Оттегляне на заявката за достъп" msgid "" +"You are going to remove %{group_name}.\n" +"Removed groups CANNOT be restored!\n" +"Are you ABSOLUTELY sure?" +msgstr "" +"На път сте да премахнете „%{group_name}“.\n" +"Ако я премахнете, групата НЕ може да бъде възстановена!\n" +"НАИСТИНА ли искате това?" + +msgid "" "You are going to remove %{project_name_with_namespace}.\n" "Removed project CANNOT be restored!\n" "Are you ABSOLUTELY sure?" diff --git a/locale/en/gitlab.po b/locale/en/gitlab.po index 7065b7a635f..46bf4e33997 100644 --- a/locale/en/gitlab.po +++ b/locale/en/gitlab.po @@ -17,8 +17,8 @@ msgstr "" "Plural-Forms: nplurals=2; plural=n != 1;\n" "\n" -msgid "%d additional commit has been omitted to prevent performance issues." -msgid_plural "%d additional commits have been omitted to prevent performance issues." +msgid "%s additional commit has been omitted to prevent performance issues." +msgid_plural "%s additional commits have been omitted to prevent performance issues." msgstr[0] "" msgstr[1] "" diff --git a/locale/eo/gitlab.po b/locale/eo/gitlab.po index 0ca8dfca266..62dbc2621f4 100644 --- a/locale/eo/gitlab.po +++ b/locale/eo/gitlab.po @@ -4,22 +4,22 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-06-19 15:50-0500\n" +"POT-Creation-Date: 2017-07-05 08:50-0500\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-07-05 02:56-0400\n" +"PO-Revision-Date: 2017-07-13 08:46-0400\n" "Last-Translator: Lyubomir Vasilev <lyubomirv@abv.bg>\n" "Language-Team: Esperanto (https://translate.zanata.org/project/view/GitLab)\n" "Language: eo\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -msgid "%d additional commit has been omitted to prevent performance issues." +msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "" -"%d additional commits have been omitted to prevent performance issues." -msgstr[0] "%d enmetado estis transsaltita, por ne troŝarĝi la sistemon." -msgstr[1] "%d enmetadoj estis transsaltitaj, por ne troŝarĝi la sistemon." +"%s additional commits have been omitted to prevent performance issues." +msgstr[0] "%s enmetado estis transsaltita, por ne troŝarĝi la sistemon." +msgstr[1] "%s enmetadoj estis transsaltitaj, por ne troŝarĝi la sistemon." msgid "%d commit" msgid_plural "%d commits" @@ -29,6 +29,14 @@ msgstr[1] "%d enmetadoj" msgid "%{commit_author_link} committed %{commit_timeago}" msgstr "%{commit_author_link} enmetis %{commit_timeago}" +msgid "1 pipeline" +msgid_plural "%d pipelines" +msgstr[0] "1 ĉenstablo" +msgstr[1] "%d ĉenstabloj" + +msgid "A collection of graphs regarding Continuous Integration" +msgstr "Aro da diagramoj pri la seninterrompa integrado" + msgid "About auto deploy" msgstr "Pri la aŭtomata disponigado" @@ -191,6 +199,9 @@ msgid_plural "Commits" msgstr[0] "Enmetado" msgstr[1] "Enmetadoj" +msgid "Commit duration in minutes for last 30 commits" +msgstr "Daŭro de la enmetadoj por la lastaj 30 enmetadoj" + msgid "Commit message" msgstr "Mesaĝo pri la enmetado" @@ -230,6 +241,13 @@ msgstr "Kopii la identigilon de la enmetado" msgid "Create New Directory" msgstr "Krei novan dosierujon" +msgid "" +"Create a personal access token on your account to pull or push via " +"%{protocol}." +msgstr "" +"Kreu propran atingoĵetonon en via konto por ebligi al vi eltiri kaj alpuŝi " +"per %{protocol}." + msgid "Create directory" msgstr "Krei dosierujon" @@ -248,6 +266,9 @@ msgstr "Disbranĉigi" msgid "CreateTag|Tag" msgstr "Etikedo" +msgid "CreateTokenToCloneLink|create a personal access token" +msgstr "kreos propran atingoĵetonon" + msgid "Cron Timezone" msgstr "Horzono por Cron" @@ -420,6 +441,15 @@ msgstr "Intervala ŝablono" msgid "Introducing Cycle Analytics" msgstr "Ni prezentas al vi la ciklan analizon" +msgid "Jobs for last month" +msgstr "Taskoj po la lasta monato" + +msgid "Jobs for last week" +msgstr "Taskoj po la lasta semajno" + +msgid "Jobs for last year" +msgstr "Taskoj po la lasta jaro" + msgid "LFSStatus|Disabled" msgstr "Malŝaltita" @@ -585,6 +615,21 @@ msgstr "Ĉenstabla plano" msgid "Pipeline Schedules" msgstr "Ĉenstablaj planoj" +msgid "PipelineCharts|Failed:" +msgstr "Malsukcesaj:" + +msgid "PipelineCharts|Overall statistics" +msgstr "Ĝenerala statistiko" + +msgid "PipelineCharts|Success ratio:" +msgstr "Proporcio de sukceso:" + +msgid "PipelineCharts|Successful:" +msgstr "Sukcesaj:" + +msgid "PipelineCharts|Total:" +msgstr "Totalo:" + msgid "PipelineSchedules|Activated" msgstr "Ŝaltita" @@ -597,6 +642,12 @@ msgstr "Ĉiuj" msgid "PipelineSchedules|Inactive" msgstr "Malŝaltitaj" +msgid "PipelineSchedules|Input variable key" +msgstr "Entajpu ŝlosilon por la variablo" + +msgid "PipelineSchedules|Input variable value" +msgstr "Entajpu la valoron de la variablo" + msgid "PipelineSchedules|Next Run" msgstr "Sekvanta plenumo" @@ -606,15 +657,33 @@ msgstr "Nenio" msgid "PipelineSchedules|Provide a short description for this pipeline" msgstr "Entajpu mallongan priskribon pri ĉi tiu ĉenstablo" +msgid "PipelineSchedules|Remove variable row" +msgstr "Forigi la variablan linion" + msgid "PipelineSchedules|Take ownership" msgstr "Akiri posedon" msgid "PipelineSchedules|Target" msgstr "Celo" +msgid "PipelineSchedules|Variables" +msgstr "Variabloj" + msgid "PipelineSheduleIntervalPattern|Custom" msgstr "Propra" +msgid "Pipelines" +msgstr "Ĉenstabloj" + +msgid "Pipelines charts" +msgstr "Ĉenstablaj diagramoj" + +msgid "Pipeline|all" +msgstr "ĉiuj" + +msgid "Pipeline|success" +msgstr "sukcesaj" + msgid "Pipeline|with stage" msgstr "kun etapo" @@ -1094,6 +1163,15 @@ msgid "Withdraw Access Request" msgstr "Nuligi la peton pri atingeblo" msgid "" +"You are going to remove %{group_name}.\n" +"Removed groups CANNOT be restored!\n" +"Are you ABSOLUTELY sure?" +msgstr "" +"Vi forigos „%{group_name}“.\n" +"Oni NE POVAS malfari la forigon de grupo!\n" +"Ĉu vi estas ABSOLUTE certa?" + +msgid "" "You are going to remove %{project_name_with_namespace}.\n" "Removed project CANNOT be restored!\n" "Are you ABSOLUTELY sure?" diff --git a/locale/es/gitlab.po b/locale/es/gitlab.po index cec086b871c..5c669d51a68 100644 --- a/locale/es/gitlab.po +++ b/locale/es/gitlab.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"PO-Revision-Date: 2017-06-21 12:09-0500\n" +"PO-Revision-Date: 2017-07-13 12:10-0500\n" "Language-Team: Spanish\n" "Language: es\n" "MIME-Version: 1.0\n" @@ -17,19 +17,27 @@ msgstr "" "Last-Translator: Bob Van Landuyt <bob@gitlab.com>\n" "X-Generator: Poedit 2.0.2\n" -msgid "%d additional commit has been omitted to prevent performance issues." -msgid_plural "%d additional commits have been omitted to prevent performance issues." -msgstr[0] "%d cambio adicional ha sido omitido para evitar problemas de rendimiento." -msgstr[1] "%d cambios adicionales han sido omitidos para evitar problemas de rendimiento." - msgid "%d commit" msgid_plural "%d commits" msgstr[0] "%d cambio" msgstr[1] "%d cambios" +msgid "%s additional commit has been omitted to prevent performance issues." +msgid_plural "%s additional commits have been omitted to prevent performance issues." +msgstr[0] "%s cambio adicional ha sido omitido para evitar problemas de rendimiento." +msgstr[1] "%s cambios adicionales han sido omitidos para evitar problemas de rendimiento." + msgid "%{commit_author_link} committed %{commit_timeago}" msgstr "%{commit_author_link} cambió %{commit_timeago}" +msgid "1 pipeline" +msgid_plural "%d pipelines" +msgstr[0] "1 pipeline" +msgstr[1] "%d pipelines" + +msgid "A collection of graphs regarding Continuous Integration" +msgstr "Una colección de gráficos sobre Integración Continua" + msgid "About auto deploy" msgstr "Acerca del auto despliegue" @@ -184,6 +192,9 @@ msgid_plural "Commits" msgstr[0] "Cambio" msgstr[1] "Cambios" +msgid "Commit duration in minutes for last 30 commits" +msgstr "Duración de los cambios en minutos para los últimos 30" + msgid "Commit message" msgstr "Mensaje del cambio" @@ -223,6 +234,9 @@ msgstr "Copiar SHA del cambio al portapapeles" msgid "Create New Directory" msgstr "Crear Nuevo Directorio" +msgid "Create a personal access token on your account to pull or push via %{protocol}." +msgstr "Crear un token de acceso personal en tu cuenta para actualizar o enviar a través de %{protocol}." + msgid "Create directory" msgstr "Crear directorio" @@ -241,6 +255,9 @@ msgstr "Bifurcar" msgid "CreateTag|Tag" msgstr "Etiqueta" +msgid "CreateTokenToCloneLink|create a personal access token" +msgstr "crear un token de acceso personal" + msgid "Cron Timezone" msgstr "Zona horaria del Cron" @@ -401,6 +418,15 @@ msgstr "Patrón de intervalo" msgid "Introducing Cycle Analytics" msgstr "Introducción a Cycle Analytics" +msgid "Jobs for last month" +msgstr "Trabajos del mes pasado" + +msgid "Jobs for last week" +msgstr "Trabajos de la semana pasada" + +msgid "Jobs for last year" +msgstr "Trabajos del año pasado" + msgid "LFSStatus|Disabled" msgstr "Deshabilitado" @@ -566,6 +592,21 @@ msgstr "Programación del Pipeline" msgid "Pipeline Schedules" msgstr "Programaciones de los Pipelines" +msgid "PipelineCharts|Failed:" +msgstr "Fallidos:" + +msgid "PipelineCharts|Overall statistics" +msgstr "Estadísticas generales" + +msgid "PipelineCharts|Success ratio:" +msgstr "Ratio de éxito" + +msgid "PipelineCharts|Successful:" +msgstr "Exitosos:" + +msgid "PipelineCharts|Total:" +msgstr "Total:" + msgid "PipelineSchedules|Activated" msgstr "Activado" @@ -578,6 +619,12 @@ msgstr "Todos" msgid "PipelineSchedules|Inactive" msgstr "Inactivos" +msgid "PipelineSchedules|Input variable key" +msgstr "Ingrese nombre de clave" + +msgid "PipelineSchedules|Input variable value" +msgstr "Ingrese el valor de la variable" + msgid "PipelineSchedules|Next Run" msgstr "Próxima Ejecución" @@ -585,7 +632,10 @@ msgid "PipelineSchedules|None" msgstr "Ninguno" msgid "PipelineSchedules|Provide a short description for this pipeline" -msgstr "Proporcione una breve descripción para este pipeline" +msgstr "Proporcione una descripción breve para este pipeline" + +msgid "PipelineSchedules|Remove variable row" +msgstr "Eliminar fila de variable" msgid "PipelineSchedules|Take ownership" msgstr "Tomar posesión" @@ -593,9 +643,24 @@ msgstr "Tomar posesión" msgid "PipelineSchedules|Target" msgstr "Destino" +msgid "PipelineSchedules|Variables" +msgstr "Variables" + msgid "PipelineSheduleIntervalPattern|Custom" msgstr "Personalizado" +msgid "Pipelines" +msgstr "Pipelines" + +msgid "Pipelines charts" +msgstr "Gráficos de los pipelines" + +msgid "Pipeline|all" +msgstr "todos" + +msgid "Pipeline|success" +msgstr "exitósos" + msgid "Pipeline|with stage" msgstr "con etapa" @@ -994,6 +1059,9 @@ msgstr "Privado" msgid "VisibilityLevel|Public" msgstr "Público" +msgid "VisibilityLevel|Unknown" +msgstr "Desconocido" + msgid "Want to see the data? Please ask an administrator for access." msgstr "¿Quieres ver los datos? Por favor pide acceso al administrador." @@ -1004,6 +1072,15 @@ msgid "Withdraw Access Request" msgstr "Retirar Solicitud de Acceso" msgid "" +"You are going to remove %{group_name}.\n" +"Removed groups CANNOT be restored!\n" +"Are you ABSOLUTELY sure?" +msgstr "" +"Va a eliminar %{group_name}.\n" +"¡El grupo eliminado NO puede ser restaurado!\n" +"¿Estás TOTALMENTE seguro?" + +msgid "" "You are going to remove %{project_name_with_namespace}.\n" "Removed project CANNOT be restored!\n" "Are you ABSOLUTELY sure?" diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 6066314b32e..babef3ed0af 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-07-05 08:50-0500\n" -"PO-Revision-Date: 2017-07-05 08:50-0500\n" +"POT-Creation-Date: 2017-07-13 12:07-0500\n" +"PO-Revision-Date: 2017-07-13 12:07-0500\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "Language: \n" @@ -18,13 +18,13 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" -msgid "%d additional commit has been omitted to prevent performance issues." -msgid_plural "%d additional commits have been omitted to prevent performance issues." +msgid "%d commit" +msgid_plural "%d commits" msgstr[0] "" msgstr[1] "" -msgid "%d commit" -msgid_plural "%d commits" +msgid "%s additional commit has been omitted to prevent performance issues." +msgid_plural "%s additional commits have been omitted to prevent performance issues." msgstr[0] "" msgstr[1] "" @@ -1060,6 +1060,9 @@ msgstr "" msgid "VisibilityLevel|Public" msgstr "" +msgid "VisibilityLevel|Unknown" +msgstr "" + msgid "Want to see the data? Please ask an administrator for access." msgstr "" diff --git a/locale/it/gitlab.po b/locale/it/gitlab.po index db992021403..d4fac6ab34e 100644 --- a/locale/it/gitlab.po +++ b/locale/it/gitlab.po @@ -4,25 +4,25 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-06-19 15:50-0500\n" +"POT-Creation-Date: 2017-06-28 13:32+0200\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-07-02 10:32-0400\n" +"PO-Revision-Date: 2017-07-12 05:45-0400\n" "Last-Translator: Paolo Falomo <info@paolofalomo.it>\n" -"Language-Team: Italian\n" +"Language-Team: Italian (https://translate.zanata.org/project/view/GitLab)\n" "Language: it\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -msgid "%d additional commit has been omitted to prevent performance issues." +msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "" -"%d additional commits have been omitted to prevent performance issues." +"%s additional commits have been omitted to prevent performance issues." msgstr[0] "" -"%d commit aggiuntivo è stato omesso per evitare degradi di prestazioni negli " +"%s commit aggiuntivo è stato omesso per evitare degradi di prestazioni negli " "issues." msgstr[1] "" -"%d commit aggiuntivi sono stati omessi per evitare degradi di prestazioni " +"%s commit aggiuntivi sono stati omessi per evitare degradi di prestazioni " "negli issues." msgid "%d commit" @@ -33,6 +33,14 @@ msgstr[1] "%d commit" msgid "%{commit_author_link} committed %{commit_timeago}" msgstr "%{commit_author_link} ha committato %{commit_timeago}" +msgid "1 pipeline" +msgid_plural "%d pipelines" +msgstr[0] "1 pipeline" +msgstr[1] "%d pipeline" + +msgid "A collection of graphs regarding Continuous Integration" +msgstr "Un insieme di grafici riguardo la Continuous Integration" + msgid "About auto deploy" msgstr "Riguardo il rilascio automatico" @@ -196,6 +204,9 @@ msgid_plural "Commits" msgstr[0] "Commit" msgstr[1] "Commits" +msgid "Commit duration in minutes for last 30 commits" +msgstr "Durata del commit (in minuti) per gli ultimi 30 commit" + msgid "Commit message" msgstr "Messaggio del commit" @@ -235,6 +246,13 @@ msgstr "Copia l'SHA del commit negli appunti" msgid "Create New Directory" msgstr "Crea una nuova cartella" +msgid "" +"Create a personal access token on your account to pull or push via " +"%{protocol}." +msgstr "" +"Creare un token di accesso sul tuo account per eseguire pull o push tramite " +"%{protocol}" + msgid "Create directory" msgstr "Crea cartella" @@ -253,6 +271,9 @@ msgstr "Fork" msgid "CreateTag|Tag" msgstr "Tag" +msgid "CreateTokenToCloneLink|create a personal access token" +msgstr "Crea token d'accesso personale" + msgid "Cron Timezone" msgstr "Cron Timezone" @@ -425,6 +446,15 @@ msgstr "Intervallo di Pattern" msgid "Introducing Cycle Analytics" msgstr "Introduzione delle Analisi Cicliche" +msgid "Jobs for last month" +msgstr "Jobs dell'ultimo mese" + +msgid "Jobs for last week" +msgstr "Jobs dell'ultima settimana" + +msgid "Jobs for last year" +msgstr "Jobs dell'ultimo anno" + msgid "LFSStatus|Disabled" msgstr "Disabilitato" @@ -590,6 +620,21 @@ msgstr "Pianificazione Pipeline" msgid "Pipeline Schedules" msgstr "Pianificazione multipla Pipeline" +msgid "PipelineCharts|Failed:" +msgstr "Fallita:" + +msgid "PipelineCharts|Overall statistics" +msgstr "Statistiche riassuntive" + +msgid "PipelineCharts|Success ratio:" +msgstr "Percentuale di successo" + +msgid "PipelineCharts|Successful:" +msgstr "Completata:" + +msgid "PipelineCharts|Total:" +msgstr "Totale:" + msgid "PipelineSchedules|Activated" msgstr "Attivata" @@ -620,6 +665,18 @@ msgstr "Target" msgid "PipelineSheduleIntervalPattern|Custom" msgstr "Personalizzato" +msgid "Pipelines" +msgstr "Pipeline" + +msgid "Pipelines charts" +msgstr "Grafici pipeline" + +msgid "Pipeline|all" +msgstr "tutto" + +msgid "Pipeline|success" +msgstr "successo" + msgid "Pipeline|with stage" msgstr "con stadio" @@ -684,7 +741,7 @@ msgid "ProjectNetworkGraph|Graph" msgstr "Grafico" msgid "Read more" -msgstr "Continua..." +msgstr "Vedi altro" msgid "Readme" msgstr "Leggimi" @@ -751,7 +808,7 @@ msgstr "Seleziona una branch di destinazione" msgid "Set a password on your account to pull or push via %{protocol}." msgstr "" -"Imposta una password sul tuo account per eseguire pull o push tramite " +"Establezca una contraseña en su cuenta para actualizar o enviar a través de " "%{protocol}." msgid "Set up CI" @@ -814,9 +871,9 @@ msgid "" "the issue to a milestone, or add the issue to a list on your Issue Board. " "Begin creating issues to see data for this stage." msgstr "" -"Questo stadio di issue mostra il tempo che ci vuole dal creare un issue " -"all'assegnarli una milestone, o ad aggiungere un issue alla tua board. Crea " -"un issue per vedere questo stadio." +"Lo stadio di Issue mostra il tempo che impiega un issue ad esser correlato " +"ad una Milestone, o ad esser aggiunto ad una tua Lavagna. Inizia la " +"creazione di problemi per visualizzare i dati in questo stadio." msgid "The phase of the development lifecycle." msgstr "Il ciclo vitale della fase di sviluppo." diff --git a/locale/ja/gitlab.po b/locale/ja/gitlab.po new file mode 100644 index 00000000000..b880fc703ec --- /dev/null +++ b/locale/ja/gitlab.po @@ -0,0 +1,1184 @@ +# Arthur Charron <arthur.charron@hotmail.fr>, 2017. #zanata +# Huang Tao <htve@outlook.com>, 2017. #zanata +# Kohei Ota <inductor@kela.jp>, 2017. #zanata +# Taisuke Inoue <taisuke.inoue.jp@gmail.com>, 2017. #zanata +# Takuya Noguchi <takninnovationresearch@gmail.com>, 2017. #zanata +# YANO TETTER <tetuyano+zana@gmail.com>, 2017. #zanata +msgid "" +msgstr "" +"Project-Id-Version: gitlab 1.0.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-07-05 08:50-0500\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language-Team: Japanese (https://translate.zanata.org/project/view/GitLab)\n" +"PO-Revision-Date: 2017-07-18 09:27-0400\n" +"Last-Translator: YANO TETTER <tetuyano+zana@gmail.com>\n" +"Language: ja\n" +"X-Generator: Zanata 3.9.6\n" +"Plural-Forms: nplurals=1; plural=0\n" + +msgid "%s additional commit has been omitted to prevent performance issues." +msgid_plural "" +"%s additional commits have been omitted to prevent performance issues." +msgstr[0] "パフォーマンス低下を避けるため %s 個のコミットを省略しました。" + +msgid "%d commit" +msgid_plural "%d commits" +msgstr[0] "%d個のコミット" + +msgid "%{commit_author_link} committed %{commit_timeago}" +msgstr "%{commit_author_link}は%{commit_timeago}前、コミットしました。" + +msgid "1 pipeline" +msgid_plural "%d pipelines" +msgstr[0] "%d 個のパイプライン" + +msgid "A collection of graphs regarding Continuous Integration" +msgstr "CIについてのグラフ" + +msgid "About auto deploy" +msgstr "自動デプロイについて" + +msgid "Active" +msgstr "有効" + +msgid "Activity" +msgstr "アクティビティー" + +msgid "Add Changelog" +msgstr "変更履歴を追加" + +msgid "Add Contribution guide" +msgstr "貢献者向けガイドを追加" + +msgid "Add License" +msgstr "ライセンスを追加" + +msgid "Add an SSH key to your profile to pull or push via SSH." +msgstr "SSHでプルやプッシュする場合は、プロフィールにSSH鍵を追加してください。" + +msgid "Add new directory" +msgstr "新規ディレクトリを追加" + +msgid "Archived project! Repository is read-only" +msgstr "アーカイブ済みプロジェクト!(レポジトリーは読み取り専用です)" + +msgid "Are you sure you want to delete this pipeline schedule?" +msgstr "このパイプラインスケジュールを削除しますか?" + +msgid "Attach a file by drag & drop or %{upload_link}" +msgstr "ドラッグ&ドロップまたは %{upload_link} でファイルを添付" + +msgid "Branch" +msgid_plural "Branches" +msgstr[0] "ブランチ" + +msgid "" +"Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, " +"choose a GitLab CI Yaml template and commit your changes. " +"%{link_to_autodeploy_doc}" +msgstr "" +"<strong>%{branch_name}</strong> ブランチが作成されました。自動デプロイを設定するには、GitLab CI Yaml " +"テンプレートを選択して、変更をコミットしてください。 %{link_to_autodeploy_doc}" + +msgid "BranchSwitcherPlaceholder|Search branches" +msgstr "ブランチを検索" + +msgid "BranchSwitcherTitle|Switch branch" +msgstr "ブランチを切替" + +msgid "Branches" +msgstr "ブランチ" + +msgid "Browse Directory" +msgstr "ディレクトリを表示" + +msgid "Browse File" +msgstr "ファイルを表示" + +msgid "Browse Files" +msgstr "ファイルを表示" + +msgid "Browse files" +msgstr "ファイルを表示" + +msgid "ByAuthor|by" +msgstr "作者" + +msgid "CI configuration" +msgstr "CI 設定" + +msgid "Cancel" +msgstr "キャンセル" + +msgid "ChangeTypeActionLabel|Pick into branch" +msgstr "ピック先ブランチ:" + +msgid "ChangeTypeActionLabel|Revert in branch" +msgstr "リバート先ブランチ:" + +msgid "ChangeTypeAction|Cherry-pick" +msgstr "チェリーピック" + +msgid "ChangeTypeAction|Revert" +msgstr "リバート" + +msgid "Changelog" +msgstr "変更履歴" + +msgid "Charts" +msgstr "チャート" + +msgid "Cherry-pick this commit" +msgstr "このコミットをチェリーピック" + +msgid "Cherry-pick this merge request" +msgstr "このマージリクエストをチェリーピック" + +msgid "CiStatusLabel|canceled" +msgstr "キャンセル" + +msgid "CiStatusLabel|created" +msgstr "作成済み" + +msgid "CiStatusLabel|failed" +msgstr "失敗" + +msgid "CiStatusLabel|manual action" +msgstr "手動実行" + +msgid "CiStatusLabel|passed" +msgstr "成功" + +msgid "CiStatusLabel|passed with warnings" +msgstr "成功(警告あり)" + +msgid "CiStatusLabel|pending" +msgstr "開始待ち" + +msgid "CiStatusLabel|skipped" +msgstr "スキップ済み" + +msgid "CiStatusLabel|waiting for manual action" +msgstr "手動実行待ち" + +msgid "CiStatusText|blocked" +msgstr "ブロック" + +msgid "CiStatusText|canceled" +msgstr "キャンセル" + +msgid "CiStatusText|created" +msgstr "作成済み" + +msgid "CiStatusText|failed" +msgstr "失敗" + +msgid "CiStatusText|manual" +msgstr "手動" + +msgid "CiStatusText|passed" +msgstr "成功" + +msgid "CiStatusText|pending" +msgstr "実行待ち" + +msgid "CiStatusText|skipped" +msgstr "スキップ済み" + +msgid "CiStatus|running" +msgstr "実行中" + +msgid "Commit" +msgid_plural "Commits" +msgstr[0] "コミット" + +msgid "Commit duration in minutes for last 30 commits" +msgstr "直近30コミットの所要時間(分)" + +msgid "Commit message" +msgstr "コミットメッセージ" + +msgid "CommitBoxTitle|Commit" +msgstr "コミット" + +msgid "CommitMessage|Add %{file_name}" +msgstr "%{file_name} を追加" + +msgid "Commits" +msgstr "コミット" + +msgid "Commits feed" +msgstr "コミットフィード" + +msgid "Commits|History" +msgstr "履歴" + +msgid "Committed by" +msgstr "コミット担当者: " + +msgid "Compare" +msgstr "比較" + +msgid "Contribution guide" +msgstr "貢献者向けガイド" + +msgid "Contributors" +msgstr "貢献者" + +msgid "Copy URL to clipboard" +msgstr "クリップボードにURLをコピー" + +msgid "Copy commit SHA to clipboard" +msgstr "コミットのSHAをクリップボードにコピー" + +msgid "Create New Directory" +msgstr "新規ディレクトリを作成" + +msgid "" +"Create a personal access token on your account to pull or push via " +"%{protocol}." +msgstr "%{protocol} でプッシュやプルするためのあなた個人用アクセストークンを作成" + +msgid "Create directory" +msgstr "ディレクトリを作成" + +msgid "Create empty bare repository" +msgstr "空のbareレポジトリーを作成" + +msgid "Create merge request" +msgstr "マージリクエストを作成" + +msgid "Create new..." +msgstr "新規作成" + +msgid "CreateNewFork|Fork" +msgstr "フォーク" + +msgid "CreateTag|Tag" +msgstr "タグ" + +msgid "CreateTokenToCloneLink|create a personal access token" +msgstr "個人用アクセストークンを作成" + +msgid "Cron Timezone" +msgstr "Cron のタイムゾーン" + +msgid "Cron syntax" +msgstr "Cron の構文" + +msgid "Custom notification events" +msgstr "カスタム通知設定" + +msgid "" +"Custom notification levels are the same as participating levels. With custom " +"notification levels you will also receive notifications for select events. " +"To find out more, check out %{notification_link}." +msgstr "" +"\"カスタム\" の通知レベルの基本は \"参加\" " +"と同じです。また、カスタム通知に設定することで選択したカスタムイベントの通知を受け取ることもできます。もっと詳しく知りたい場合は " +"%{notification_link} を見てください。" + +msgid "Cycle Analytics" +msgstr "サイクル分析" + +msgid "" +"Cycle Analytics gives an overview of how much time it takes to go from idea " +"to production in your project." +msgstr "" +"サイクル分析により、あなたのプロジェクトがアイディアの段階からプロダクション環境にリリースされるまでどれぐらい時間がかかったか俯瞰することができます。" + +msgid "CycleAnalyticsStage|Code" +msgstr "コード" + +msgid "CycleAnalyticsStage|Issue" +msgstr "課題" + +msgid "CycleAnalyticsStage|Plan" +msgstr "計画" + +msgid "CycleAnalyticsStage|Production" +msgstr "プロダクション" + +msgid "CycleAnalyticsStage|Review" +msgstr "レビュー" + +msgid "CycleAnalyticsStage|Staging" +msgstr "ステージング" + +msgid "CycleAnalyticsStage|Test" +msgstr "テスト" + +msgid "Define a custom pattern with cron syntax" +msgstr "Cron 構文でカスタムなパターンを指定する" + +msgid "Delete" +msgstr "削除" + +msgid "Deploy" +msgid_plural "Deploys" +msgstr[0] "デプロイ" + +msgid "Description" +msgstr "説明" + +msgid "Directory name" +msgstr "ディレクトリ名" + +msgid "Don't show again" +msgstr "次回から表示しない" + +msgid "Download" +msgstr "ダウンロード" + +msgid "Download tar" +msgstr "tar形式でダウンロード" + +msgid "Download tar.bz2" +msgstr "tar.bz2形式でダウンロード" + +msgid "Download tar.gz" +msgstr "tar.gz形式でダウンロード" + +msgid "Download zip" +msgstr "zip形式でダウンロード" + +msgid "DownloadArtifacts|Download" +msgstr "ダウンロード" + +msgid "DownloadCommit|Email Patches" +msgstr "パッチをメールで送信" + +msgid "DownloadCommit|Plain Diff" +msgstr "プレーン差分" + +msgid "DownloadSource|Download" +msgstr "ダウンロード" + +msgid "Edit" +msgstr "編集" + +msgid "Edit Pipeline Schedule %{id}" +msgstr "パイプラインスケジュール %{id} を編集" + +msgid "Every day (at 4:00am)" +msgstr "毎日 (午前4:00)" + +msgid "Every month (on the 1st at 4:00am)" +msgstr "毎月 (1日の午前4:00)" + +msgid "Every week (Sundays at 4:00am)" +msgstr "毎週 (日曜日の午前4:00)" + +msgid "Failed to change the owner" +msgstr "オーナーを変更できませんでした" + +msgid "Failed to remove the pipeline schedule" +msgstr "パイプラインスケジュールを削除できませんでした" + +msgid "Files" +msgstr "ファイル" + +msgid "Filter by commit message" +msgstr "コミットメッセージで絞り込み" + +msgid "Find by path" +msgstr "パスで検索" + +msgid "Find file" +msgstr "ファイルを検索" + +msgid "FirstPushedBy|First" +msgstr "初回" + +msgid "FirstPushedBy|pushed by" +msgstr "プッシュした人" + +msgid "Fork" +msgid_plural "Forks" +msgstr[0] "フォーク" + +msgid "ForkedFromProjectPath|Forked from" +msgstr "フォーク元" + +msgid "From issue creation until deploy to production" +msgstr "課題が登録されてからプロダクションにデプロイされるまで" + +msgid "From merge request merge until deploy to production" +msgstr "マージリクエストがマージされてからプロダクションにデプロイされるまで" + +msgid "Go to your fork" +msgstr "自分のフォークへ移動" + +msgid "GoToYourFork|Fork" +msgstr "フォーク" + +msgid "Home" +msgstr "ホーム" + +msgid "Housekeeping successfully started" +msgstr "ハウスキーピングは正常に起動しました。" + +msgid "Import repository" +msgstr "レポジトリーをインポート" + +msgid "Interval Pattern" +msgstr "間隔のパターン" + +msgid "Introducing Cycle Analytics" +msgstr "サイクル分析のご紹介" + +msgid "Jobs for last month" +msgstr "先月のジョブ" + +msgid "Jobs for last week" +msgstr "先週のジョブ" + +msgid "Jobs for last year" +msgstr "昨年のジョブ" + +msgid "LFSStatus|Disabled" +msgstr "無効" + +msgid "LFSStatus|Enabled" +msgstr "有効" + +msgid "Last %d day" +msgid_plural "Last %d days" +msgstr[0] "過去%d日間" + +msgid "Last Pipeline" +msgstr "最新パイプライン" + +msgid "Last Update" +msgstr "最新アップデート" + +msgid "Last commit" +msgstr "最新コミット" + +msgid "Learn more in the" +msgstr "詳しく見る:" + +msgid "Learn more in the|pipeline schedules documentation" +msgstr "詳しくはパイプラインスケジュールのドキュメントを参照" + +msgid "Leave group" +msgstr "グループを離脱" + +msgid "Leave project" +msgstr "プロジェクトを離脱" + +msgid "Limited to showing %d event at most" +msgid_plural "Limited to showing %d events at most" +msgstr[0] "イベント表示数を最大 %d 個に制限" + +msgid "Median" +msgstr "中央値" + +msgid "MissingSSHKeyWarningLink|add an SSH key" +msgstr "SSH 鍵を追加" + +msgid "New Issue" +msgid_plural "New Issues" +msgstr[0] "新規課題" + +msgid "New Pipeline Schedule" +msgstr "新規パイプラインスケジュール" + +msgid "New branch" +msgstr "新規ブランチ" + +msgid "New directory" +msgstr "新規ディレクトリ" + +msgid "New file" +msgstr "新規ファイル" + +msgid "New issue" +msgstr "新規課題" + +msgid "New merge request" +msgstr "新規マージリクエスト" + +msgid "New schedule" +msgstr "新規スケジュール" + +msgid "New snippet" +msgstr "新規スニペット" + +msgid "New tag" +msgstr "新規タグ" + +msgid "No repository" +msgstr "レポジトリーはありません" + +msgid "No schedules" +msgstr "スケジュールなし" + +msgid "Not available" +msgstr "利用できません" + +msgid "Not enough data" +msgstr "データ不足" + +msgid "Notification events" +msgstr "イベント通知" + +msgid "NotificationEvent|Close issue" +msgstr "課題をクローズ" + +msgid "NotificationEvent|Close merge request" +msgstr "マージリクエストをクローズ" + +msgid "NotificationEvent|Failed pipeline" +msgstr "パイプラインに失敗" + +msgid "NotificationEvent|Merge merge request" +msgstr "マージリクエストをマージ" + +msgid "NotificationEvent|New issue" +msgstr "新規課題" + +msgid "NotificationEvent|New merge request" +msgstr "新規マージリクエスト" + +msgid "NotificationEvent|New note" +msgstr "新規ノート" + +msgid "NotificationEvent|Reassign issue" +msgstr "課題の担当者を変更" + +msgid "NotificationEvent|Reassign merge request" +msgstr "マージリクエスト担当者を変更" + +msgid "NotificationEvent|Reopen issue" +msgstr "課題を再オープン" + +msgid "NotificationEvent|Successful pipeline" +msgstr "パイプライン成功" + +msgid "NotificationLevel|Custom" +msgstr "カスタム" + +msgid "NotificationLevel|Disabled" +msgstr "無効" + +msgid "NotificationLevel|Global" +msgstr "全体設定" + +msgid "NotificationLevel|On mention" +msgstr "メンション時" + +msgid "NotificationLevel|Participate" +msgstr "参加" + +msgid "NotificationLevel|Watch" +msgstr "すべて通知" + +msgid "OfSearchInADropdown|Filter" +msgstr "フィルター" + +msgid "OpenedNDaysAgo|Opened" +msgstr "オープンされたのは" + +msgid "Options" +msgstr "オプション" + +msgid "Owner" +msgstr "オーナー" + +msgid "Pipeline" +msgstr "パイプライン" + +msgid "Pipeline Health" +msgstr "パイプラインの進捗状況" + +msgid "Pipeline Schedule" +msgstr "パイプラインスケジュール" + +msgid "Pipeline Schedules" +msgstr "パイプラインスケジュール" + +msgid "PipelineCharts|Failed:" +msgstr "失敗:" + +msgid "PipelineCharts|Overall statistics" +msgstr "全体統計" + +msgid "PipelineCharts|Success ratio:" +msgstr "成功比率:" + +msgid "PipelineCharts|Successful:" +msgstr "成功:" + +msgid "PipelineCharts|Total:" +msgstr "合計:" + +msgid "PipelineSchedules|Activated" +msgstr "アクティブ" + +msgid "PipelineSchedules|Active" +msgstr "アクティブ" + +msgid "PipelineSchedules|All" +msgstr "全件" + +msgid "PipelineSchedules|Inactive" +msgstr "無効" + +msgid "PipelineSchedules|Next Run" +msgstr "次の実行" + +msgid "PipelineSchedules|None" +msgstr "なし" + +msgid "PipelineSchedules|Provide a short description for this pipeline" +msgstr "このパイプラインについて簡単に記述してください。" + +msgid "PipelineSchedules|Take ownership" +msgstr "権限を取得する" + +msgid "PipelineSchedules|Target" +msgstr "ターゲット" + +msgid "PipelineSheduleIntervalPattern|Custom" +msgstr "カスタム" + +msgid "Pipelines" +msgstr "パイプライン" + +msgid "Pipelines charts" +msgstr "パイプラインチャート" + +msgid "Pipeline|all" +msgstr "全件" + +msgid "Pipeline|success" +msgstr "成功" + +msgid "Pipeline|with stage" +msgstr "ステージあり" + +msgid "Pipeline|with stages" +msgstr "ステージあり" + +msgid "Project '%{project_name}' queued for deletion." +msgstr "'%{project_name}' プロジェクトは削除処理待ちです。" + +msgid "Project '%{project_name}' was successfully created." +msgstr "'%{project_name}' プロジェクトは正常に作成されました。" + +msgid "Project '%{project_name}' was successfully updated." +msgstr "'%{project_name}' プロジェクトは正常に更新されました。" + +msgid "Project '%{project_name}' will be deleted." +msgstr "'%{project_name}' プロジェクトは削除されます。" + +msgid "Project access must be granted explicitly to each user." +msgstr "ユーザーごとにプロジェクトアクセスの権限を指定しなければなりません。" + +msgid "Project export could not be deleted." +msgstr "プロジェクトのエクスポートを削除できませんでした。" + +msgid "Project export has been deleted." +msgstr "プロジェクトのエクスポートを削除しました。" + +msgid "" +"Project export link has expired. Please generate a new export from your " +"project settings." +msgstr "プロジェクトのエクスポートリンクは期限切れになりました。プロジェクト設定にて新しくエクスポートリンクを作成してください。" + +msgid "Project export started. A download link will be sent by email." +msgstr "プロジェクトのエクスポートを開始しました。ダウンロードのリンクはメールで送信します" + +msgid "Project home" +msgstr "プロジェクトホーム" + +msgid "ProjectFeature|Disabled" +msgstr "無効" + +msgid "ProjectFeature|Everyone with access" +msgstr "アクセス権限を持っている人" + +msgid "ProjectFeature|Only team members" +msgstr "チームメンバーのみ" + +msgid "ProjectFileTree|Name" +msgstr "名前" + +msgid "ProjectLastActivity|Never" +msgstr "記録なし" + +msgid "ProjectLifecycle|Stage" +msgstr "ステージ" + +msgid "ProjectNetworkGraph|Graph" +msgstr "ネットワークグラフ" + +msgid "Read more" +msgstr "続きを読む" + +msgid "Readme" +msgstr "Readme" + +msgid "RefSwitcher|Branches" +msgstr "ブランチ" + +msgid "RefSwitcher|Tags" +msgstr "タグ" + +msgid "Related Commits" +msgstr "関連するコミット" + +msgid "Related Deployed Jobs" +msgstr "関連するデプロイ済ジョブ" + +msgid "Related Issues" +msgstr "関連する課題" + +msgid "Related Jobs" +msgstr "関連するジョブ" + +msgid "Related Merge Requests" +msgstr "関連するマージリクエスト" + +msgid "Related Merged Requests" +msgstr "関連するマージリクエスト" + +msgid "Remind later" +msgstr "後で通知" + +msgid "Remove project" +msgstr "プロジェクトを削除" + +msgid "Request Access" +msgstr "アクセス権限をリクエストする" + +msgid "Revert this commit" +msgstr "このコミットをリバート" + +msgid "Revert this merge request" +msgstr "このマージリクエストをリバート" + +msgid "Save pipeline schedule" +msgstr "パイプラインスケジュールを保存" + +msgid "Schedule a new pipeline" +msgstr "新しいパイプラインのスケジュールを作成" + +msgid "Scheduling Pipelines" +msgstr "パイプラインスケジューリング" + +msgid "Search branches and tags" +msgstr "ブランチまたはタグを検索" + +msgid "Select Archive Format" +msgstr "アーカイブのフォーマットを選択" + +msgid "Select a timezone" +msgstr "タイムゾーンを選択" + +msgid "Select target branch" +msgstr "ターゲットブランチを選択" + +msgid "Set a password on your account to pull or push via %{protocol}." +msgstr "%{protocol} プロコトル経由でプル、プッシュするためにアカウントのパスワードを設定。" + +msgid "Set up CI" +msgstr "CI を設定" + +msgid "Set up Koding" +msgstr "Koding を設定" + +msgid "Set up auto deploy" +msgstr "自動デプロイを設定" + +msgid "SetPasswordToCloneLink|set a password" +msgstr "パスワードを設定" + +msgid "Showing %d event" +msgid_plural "Showing %d events" +msgstr[0] "%d のイベントを表示中" + +msgid "Source code" +msgstr "ソースコード" + +msgid "StarProject|Star" +msgstr "スターを付ける" + +msgid "Start a %{new_merge_request} with these changes" +msgstr "この変更で %{new_merge_request} を作成する" + +msgid "Switch branch/tag" +msgstr "ブランチ・タグ切り替え" + +msgid "Tag" +msgid_plural "Tags" +msgstr[0] "タグ" + +msgid "Tags" +msgstr "タグ" + +msgid "Target Branch" +msgstr "ターゲットブランチ" + +msgid "" +"The coding stage shows the time from the first commit to creating the merge " +"request. The data will automatically be added here once you create your " +"first merge request." +msgstr "" +"コーディングステージでは、最初のコミットからマージリクエストが作成されるまでの時間が表示されます。このデータは最初のマージリクエストが作成されたときに自動的に追加されます。" + +msgid "The collection of events added to the data gathered for that stage." +msgstr "このステージで計測データに追加されたイベントリスト" + +msgid "The fork relationship has been removed." +msgstr "フォークのリレーションが削除されました。" + +msgid "" +"The issue stage shows the time it takes from creating an issue to assigning " +"the issue to a milestone, or add the issue to a list on your Issue Board. " +"Begin creating issues to see data for this stage." +msgstr "" +"課題ステージでは、課題が登録されてからマイルストーンに割り当てられるか、課題ボードのリストに追加されるまでの時間が表示されます。このリストに表示するには課題を最初に作成してください。" + +msgid "The phase of the development lifecycle." +msgstr "開発ライフサイクルの段階" + +msgid "" +"The pipelines schedule runs pipelines in the future, repeatedly, for " +"specific branches or tags. Those scheduled pipelines will inherit limited " +"project access based on their associated user." +msgstr "" +"パイプラインスケジュールは指定のブランチまたはタグに対して自動的にパイプラインを実行します。計画済みパイプラインはそれらの紐付けられたユーザーのプロジェクトと同じ権限を継承します。" + +msgid "" +"The planning stage shows the time from the previous step to pushing your " +"first commit. This time will be added automatically once you push your first " +"commit." +msgstr "" +"計画ステージでは、課題ステージに登録されてからプッシュされた最初のコミット時刻までの時間が表示されます。最初のコミットがプッシュされときに自動的に追加されます。" + +msgid "" +"The production stage shows the total time it takes between creating an issue " +"and deploying the code to production. The data will be automatically added " +"once you have completed the full idea to production cycle." +msgstr "" +"プロダクションステージでは、課題が作成されてからプロダクションへデプロイされるまでの時間が表示されます。アイディアの時点からプロダクションまでの全ステージが完了したときに自動的に追加されます。" + +msgid "The project can be accessed by any logged in user." +msgstr "プロジェクトは、ログインユーザーであれば誰でもアクセスできます。" + +msgid "The project can be accessed without any authentication." +msgstr "プロジェクトは、ログインなしに誰でもアクセスできます。" + +msgid "The repository for this project does not exist." +msgstr "このプロジェクトにレポジトリーはありません。" + +msgid "" +"The review stage shows the time from creating the merge request to merging " +"it. The data will automatically be added after you merge your first merge " +"request." +msgstr "" +"レビューステージとは、マージリクエストを作成してからマージするまでの時間です。このデータは最初のマージリクエストがマージされたときに自動的に追加されます。" + +msgid "" +"The staging stage shows the time between merging the MR and deploying code " +"to the production environment. The data will be automatically added once you " +"deploy to production for the first time." +msgstr "" +"ステージングステージでは、マージリクエストがマージされてからコードがプロダクション環境にデプロイされるまでの時間が表示されます。このデータは最初にプロダクションにデプロイしたときに自動的に追加されます。" + +msgid "" +"The testing stage shows the time GitLab CI takes to run every pipeline for " +"the related merge request. The data will automatically be added after your " +"first pipeline finishes running." +msgstr "" +"テスティングステージでは、GitLab CI " +"が関連するマージリクエストの各パイプラインを実行する時間が表示されます。このデータは最初のパイプラインが完了したときに自動的に追加されます。" + +msgid "The time taken by each data entry gathered by that stage." +msgstr "このステージに収集されたデータ毎の時間" + +msgid "" +"The value lying at the midpoint of a series of observed values. E.g., " +"between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 =" +" 6." +msgstr "" +"得られた一連のデータを小さい順に並べたときに中央に位置する値。例えば、3, 5, 9の中央値は5。3, 5, 7, 8の中央値は (5+7)/2 = " +"6。" + +msgid "" +"This means you can not push code until you create an empty repository or " +"import existing one." +msgstr "空レポジトリーを作成または既存レポジトリーをインポートをしなければ、コードのプッシュはできません。" + +msgid "Time before an issue gets scheduled" +msgstr "課題が計画されるまでの時間" + +msgid "Time before an issue starts implementation" +msgstr "課題の実装が開始されるまでの時間" + +msgid "Time between merge request creation and merge/close" +msgstr "マージリクエストが作成されてからマージまたはクローズされるまでの時間" + +msgid "Time until first merge request" +msgstr "最初のマージリクエストまでの時間" + +msgid "Timeago|%s days ago" +msgstr "%s日前" + +msgid "Timeago|%s days remaining" +msgstr "残り %s日間" + +msgid "Timeago|%s hours remaining" +msgstr "残り %s時間" + +msgid "Timeago|%s minutes ago" +msgstr "%s分前" + +msgid "Timeago|%s minutes remaining" +msgstr "残り %s分間" + +msgid "Timeago|%s months ago" +msgstr "%sヶ月前" + +msgid "Timeago|%s months remaining" +msgstr "残り %sヶ月" + +msgid "Timeago|%s seconds remaining" +msgstr "残り %s 秒" + +msgid "Timeago|%s weeks ago" +msgstr "%s週間前" + +msgid "Timeago|%s weeks remaining" +msgstr "残り %s週間" + +msgid "Timeago|%s years ago" +msgstr "%s年前" + +msgid "Timeago|%s years remaining" +msgstr "残り %s年間" + +msgid "Timeago|1 day remaining" +msgstr "残り 1日間" + +msgid "Timeago|1 hour remaining" +msgstr "残り 1時間" + +msgid "Timeago|1 minute remaining" +msgstr "残り 1分間" + +msgid "Timeago|1 month remaining" +msgstr "残り 1ヶ月" + +msgid "Timeago|1 week remaining" +msgstr "残り 1週間" + +msgid "Timeago|1 year remaining" +msgstr "残り 1年間" + +msgid "Timeago|Past due" +msgstr "期限オーバー" + +msgid "Timeago|a day ago" +msgstr "1日前" + +msgid "Timeago|a month ago" +msgstr "1ヶ月前" + +msgid "Timeago|a week ago" +msgstr "1週間前" + +msgid "Timeago|a while" +msgstr "しばらく前" + +msgid "Timeago|a year ago" +msgstr "1年前" + +msgid "Timeago|about %s hours ago" +msgstr "約%s時間前" + +msgid "Timeago|about a minute ago" +msgstr "約1分間前" + +msgid "Timeago|about an hour ago" +msgstr "約1時間前" + +msgid "Timeago|in %s days" +msgstr "%s日間以内" + +msgid "Timeago|in %s hours" +msgstr "%s時間以内" + +msgid "Timeago|in %s minutes" +msgstr "%s分間以内" + +msgid "Timeago|in %s months" +msgstr "%sヶ月以内" + +msgid "Timeago|in %s seconds" +msgstr "%s秒以内" + +msgid "Timeago|in %s weeks" +msgstr "%s週間以内" + +msgid "Timeago|in %s years" +msgstr "%s年間以内" + +msgid "Timeago|in 1 day" +msgstr "1日以内" + +msgid "Timeago|in 1 hour" +msgstr "1時間以内" + +msgid "Timeago|in 1 minute" +msgstr "1分以内" + +msgid "Timeago|in 1 month" +msgstr "1ヶ月以内" + +msgid "Timeago|in 1 week" +msgstr "1週間以内" + +msgid "Timeago|in 1 year" +msgstr "1年以内" + +msgid "Timeago|less than a minute ago" +msgstr "1分未満" + +msgid "Time|hr" +msgid_plural "Time|hrs" +msgstr[0] "時間" + +msgid "Time|min" +msgid_plural "Time|mins" +msgstr[0] "分" + +msgid "Time|s" +msgstr "秒" + +msgid "Total Time" +msgstr "合計時間" + +msgid "Total test time for all commits/merges" +msgstr "すべてのコミット/マージの合計テスト時間" + +msgid "Unstar" +msgstr "スターを外す" + +msgid "Upload New File" +msgstr "新規ファイルをアップロード" + +msgid "Upload file" +msgstr "ファイルをアップロード" + +msgid "UploadLink|click to upload" +msgstr "クリックしてアップロード" + +msgid "Use your global notification setting" +msgstr "全体通知設定を利用" + +msgid "View open merge request" +msgstr "オープンなマージリクエストを表示" + +msgid "VisibilityLevel|Internal" +msgstr "内部" + +msgid "VisibilityLevel|Private" +msgstr "プライベート" + +msgid "VisibilityLevel|Public" +msgstr "パブリック" + +msgid "Want to see the data? Please ask an administrator for access." +msgstr "このデータを参照したいですか?アクセスするには管理者に問い合わせてください。" + +msgid "We don't have enough data to show this stage." +msgstr "データ不足のため、このステージの表示はできません。" + +msgid "Withdraw Access Request" +msgstr "アクセスリクエストを取り消す" + +msgid "" +"You are going to remove %{project_name_with_namespace}.\n" +"Removed project CANNOT be restored!\n" +"Are you ABSOLUTELY sure?" +msgstr "" +"%{project_name_with_namespace} プロジェクトを削除しようとしています。\n" +"削除されたプロジェクトは絶対に元には戻せません!\n" +"本当によろしいですか?" + +msgid "" +"You are going to remove the fork relationship to source project " +"%{forked_from_project}. Are you ABSOLUTELY sure?" +msgstr "元のプロジェクト (%{forked_from_project}) とのリレーションを削除しようとしています。\n" +"本当によろしいですか?" + +msgid "" +"You are going to transfer %{project_name_with_namespace} to another owner. " +"Are you ABSOLUTELY sure?" +msgstr "%{project_name_with_namespace} プロジェクトを別のオーナーに移譲しようとしています。本当によろしいですか?" + +msgid "You can only add files when you are on a branch" +msgstr "ファイルを追加するには、どこかのブランチにいなければいけません" + +msgid "You have reached your project limit" +msgstr "プロジェクト数の上限に達しています" + +msgid "You must sign in to star a project" +msgstr "プロジェクトにスターをつけたい場合はログインしてください" + +msgid "You need permission." +msgstr "権限が必要です" + +msgid "You will not get any notifications via email" +msgstr "通知メールを送信しません" + +msgid "You will only receive notifications for the events you choose" +msgstr "選択したイベントのみ通知します" + +msgid "" +"You will only receive notifications for threads you have participated in" +msgstr "参加したスレッドのみ通知します" + +msgid "You will receive notifications for any activity" +msgstr "全てのアクティビティーを通知します" + +msgid "" +"You will receive notifications only for comments in which you were " +"@mentioned" +msgstr "あなたが @mentioned でコメントされた時のみ通知します" + +msgid "" +"You won't be able to pull or push project code via %{protocol} until you " +"%{set_password_link} on your account" +msgstr "" +"%{set_password_link} でアカウントのパスワードがセットされていないので、プロジェクトに %{protocol} " +"でソースコードをプッシュ、プルできません" + +msgid "" +"You won't be able to pull or push project code via SSH until you " +"%{add_ssh_key_link} to your profile" +msgstr "%{add_ssh_key_link} をプロファイルに追加していないので、プロジェクトにソースコードをプッシュ、プルできません" + +msgid "Your name" +msgstr "名前" + +msgid "day" +msgid_plural "days" +msgstr[0] "日" + +msgid "new merge request" +msgstr "新規マージリクエスト" + +msgid "notification emails" +msgstr "メール通知" + +msgid "parent" +msgid_plural "parents" +msgstr[0] "親" + diff --git a/locale/ja/gitlab.po.time_stamp b/locale/ja/gitlab.po.time_stamp new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/locale/ja/gitlab.po.time_stamp diff --git a/locale/pt_BR/gitlab.po b/locale/pt_BR/gitlab.po index fe6d51c36ac..c4918a4c920 100644 --- a/locale/pt_BR/gitlab.po +++ b/locale/pt_BR/gitlab.po @@ -1,29 +1,298 @@ # Alexandre Alencar <alexandre.alencar@gmail.com>, 2017. #zanata # Fabio Beneditto <fabiobeneditto@gmail.com>, 2017. #zanata # Leandro Nunes dos Santos <leandronunes@gmail.com>, 2017. #zanata +# Huang Tao <htve@outlook.com>, 2017. #zanata msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-05-04 19:24-0500\n" +"POT-Creation-Date: 2017-06-28 13:32+0200\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-06-05 03:29-0400\n" -"Last-Translator: Alexandre Alencar <alexandre.alencar@gmail.com>\n" -"Language-Team: Portuguese (Brazil)\n" +"PO-Revision-Date: 2017-07-12 09:05-0400\n" +"Last-Translator: Leandro Nunes dos Santos <leandronunes@gmail.com>\n" +"Language-Team: Portuguese (Brazil) (https://translate.zanata.org/project/view/GitLab)\n" "Language: pt-BR\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" +msgid "%s additional commit has been omitted to prevent performance issues." +msgid_plural "" +"%s additional commits have been omitted to prevent performance issues." +msgstr[0] "" +"%s commit adicional foi omitido para prevenir problemas de performance." +msgstr[1] "" +"%s commits adicionais foram omitidos para prevenir problemas de performance." + +msgid "%d commit" +msgid_plural "%d commits" +msgstr[0] "%d commit" +msgstr[1] "%d commits" + +msgid "%{commit_author_link} committed %{commit_timeago}" +msgstr "%{commit_author_link} fez commit %{commit_timeago}" + +msgid "1 pipeline" +msgid_plural "%d pipelines" +msgstr[0] "1 pipeline" +msgstr[1] "%d pipelines" + +msgid "A collection of graphs regarding Continuous Integration" +msgstr "Uma coleção de gráficos sobre Integração Contínua" + +msgid "About auto deploy" +msgstr "Sobre a implantação automática" + +msgid "Active" +msgstr "Ativo" + +msgid "Activity" +msgstr "Atividade" + +msgid "Add Changelog" +msgstr "Adicionar registro de mudanças" + +msgid "Add Contribution guide" +msgstr "Adicionar Guia de contribuição" + +msgid "Add License" +msgstr "Adicionar Licença" + +msgid "Add an SSH key to your profile to pull or push via SSH." +msgstr "Adicionar chave SSH ao seu perfil para fazer pull ou push via SSH." + +msgid "Add new directory" +msgstr "Adicionar novo diretório" + +msgid "Archived project! Repository is read-only" +msgstr "Projeto arquivado! O repositório é somente leitura" + +msgid "Are you sure you want to delete this pipeline schedule?" +msgstr "Tem certeza que deseja excluir este agendamento de pipeline?" + +msgid "Attach a file by drag & drop or %{upload_link}" +msgstr "Para anexar arquivo, arraste e solte ou %{upload_link}" + +msgid "Branch" +msgid_plural "Branches" +msgstr[0] "Branch" +msgstr[1] "Branches" + +msgid "" +"Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, " +"choose a GitLab CI Yaml template and commit your changes. " +"%{link_to_autodeploy_doc}" +msgstr "" +"O branch <strong>%{branch_name}</strong> foi criado. Para configurar a " +"implantação automática, selecione um modelo de Yaml do GitLab CI e registre " +"suas mudanças. %{link_to_autodeploy_doc}" + +msgid "BranchSwitcherPlaceholder|Search branches" +msgstr "BranchSwitcherPlaceholder|Procurar por branches" + +msgid "BranchSwitcherTitle|Switch branch" +msgstr "BranchSwitcherTitle|Mudar de branch" + +msgid "Branches" +msgstr "Branches" + +msgid "Browse Directory" +msgstr "Navegar no Diretório" + +msgid "Browse File" +msgstr "Pesquisar Arquivo" + +msgid "Browse Files" +msgstr "Pesquisar Arquivos" + +msgid "Browse files" +msgstr "Navegar pelos arquivos" + msgid "ByAuthor|by" msgstr "por" +msgid "CI configuration" +msgstr "Configuração da Integração Contínua" + +msgid "Cancel" +msgstr "Cancelar" + +msgid "ChangeTypeActionLabel|Pick into branch" +msgstr "Pick para um branch" + +msgid "ChangeTypeActionLabel|Revert in branch" +msgstr "Reverter no branch" + +msgid "ChangeTypeAction|Cherry-pick" +msgstr "Cherry-pick" + +msgid "ChangeTypeAction|Revert" +msgstr "Reverter" + +msgid "Changelog" +msgstr "Registro de mudanças" + +msgid "Charts" +msgstr "Gráficos" + +msgid "Cherry-pick this commit" +msgstr "Cherry-pick esse commit" + +msgid "Cherry-pick this merge request" +msgstr "Cherry-pick esse merge request" + +msgid "CiStatusLabel|canceled" +msgstr "cancelado" + +msgid "CiStatusLabel|created" +msgstr "criado" + +msgid "CiStatusLabel|failed" +msgstr "falhou" + +msgid "CiStatusLabel|manual action" +msgstr "ação manual" + +msgid "CiStatusLabel|passed" +msgstr "passou" + +msgid "CiStatusLabel|passed with warnings" +msgstr "passou com avisos" + +msgid "CiStatusLabel|pending" +msgstr "pendente" + +msgid "CiStatusLabel|skipped" +msgstr "ignorado" + +msgid "CiStatusLabel|waiting for manual action" +msgstr "aguardando ação manual" + +msgid "CiStatusText|blocked" +msgstr "bloqueado" + +msgid "CiStatusText|canceled" +msgstr "cancelado" + +msgid "CiStatusText|created" +msgstr "criado" + +msgid "CiStatusText|failed" +msgstr "falhou" + +msgid "CiStatusText|manual" +msgstr "manual" + +msgid "CiStatusText|passed" +msgstr "passou" + +msgid "CiStatusText|pending" +msgstr "pendente" + +msgid "CiStatusText|skipped" +msgstr "ignorado" + +msgid "CiStatus|running" +msgstr "executando" + msgid "Commit" msgid_plural "Commits" msgstr[0] "Commit" msgstr[1] "Commits" +msgid "Commit duration in minutes for last 30 commits" +msgstr "Duração do commit em minutos para os últimos 30 commits" + +msgid "Commit message" +msgstr "Mensagem de commit" + +msgid "CommitBoxTitle|Commit" +msgstr "Commit" + +msgid "CommitMessage|Add %{file_name}" +msgstr "Adicionar %{file_name}" + +msgid "Commits" +msgstr "Commits" + +msgid "Commits feed" +msgstr "Feed de commits" + +msgid "Commits|History" +msgstr "Histórico" + +msgid "Committed by" +msgstr "Commit feito por" + +msgid "Compare" +msgstr "Comparar" + +msgid "Contribution guide" +msgstr "Guia de contribuição" + +msgid "Contributors" +msgstr "Contribuidores" + +msgid "Copy URL to clipboard" +msgstr "Copiar URL para área de transferência" + +msgid "Copy commit SHA to clipboard" +msgstr "Copiar SHA do commit para a área de transferência" + +msgid "Create New Directory" +msgstr "Criar Novo Diretório" + +msgid "" +"Create a personal access token on your account to pull or push via " +"%{protocol}." +msgstr "" +"Crie um token de acesso pessoal na sua conta para dar pull ou push via " +"%{protocol}." + +msgid "Create directory" +msgstr "Criar diretório" + +msgid "Create empty bare repository" +msgstr "Criar repositório bruto vazio" + +msgid "Create merge request" +msgstr "Criar merge request" + +msgid "Create new..." +msgstr "Criar novo..." + +msgid "CreateNewFork|Fork" +msgstr "Fork" + +msgid "CreateTag|Tag" +msgstr "Tag" + +msgid "CreateTokenToCloneLink|create a personal access token" +msgstr "CreateTokenToCloneLink|criar um token de acesso pessoal" + +msgid "Cron Timezone" +msgstr "Fuso horário do cron" + +msgid "Cron syntax" +msgstr "Sintaxe do cron" + +msgid "Custom notification events" +msgstr "Eventos de notificação personalizados" + +msgid "" +"Custom notification levels are the same as participating levels. With custom " +"notification levels you will also receive notifications for select events. " +"To find out more, check out %{notification_link}." +msgstr "" +"Níveis de notificação personalizados são equivalentes a níveis de " +"participação. Com níveis de notificação personalizados você também será " +"notificado sobre eventos selecionados. Para mais informações, visite " +"%{notification_link}." + +msgid "Cycle Analytics" +msgstr "Análise de Ciclo" + msgid "" "Cycle Analytics gives an overview of how much time it takes to go from idea " "to production in your project." @@ -35,7 +304,7 @@ msgid "CycleAnalyticsStage|Code" msgstr "Código" msgid "CycleAnalyticsStage|Issue" -msgstr "Tarefa" +msgstr "Issue" msgid "CycleAnalyticsStage|Plan" msgstr "Plano" @@ -52,43 +321,217 @@ msgstr "Homologação" msgid "CycleAnalyticsStage|Test" msgstr "Teste" +msgid "Define a custom pattern with cron syntax" +msgstr "Defina um padrão personalizado utilizando a sintaxe do cron" + +msgid "Delete" +msgstr "Excluir" + msgid "Deploy" msgid_plural "Deploys" msgstr[0] "Implantação" msgstr[1] "Implantações" +msgid "Description" +msgstr "Descrição" + +msgid "Directory name" +msgstr "Nome do diretório" + +msgid "Don't show again" +msgstr "Não exibir novamente" + +msgid "Download" +msgstr "Baixar" + +msgid "Download tar" +msgstr "Baixar tar" + +msgid "Download tar.bz2" +msgstr "Baixar tar.bz2" + +msgid "Download tar.gz" +msgstr "Baixar tar.gz" + +msgid "Download zip" +msgstr "Baixar zip" + +msgid "DownloadArtifacts|Download" +msgstr "Baixar" + +msgid "DownloadCommit|Email Patches" +msgstr "Email com as mudanças" + +msgid "DownloadCommit|Plain Diff" +msgstr "Arquivo de texto com as mudanças" + +msgid "DownloadSource|Download" +msgstr "Baixar" + +msgid "Edit" +msgstr "Alterar" + +msgid "Edit Pipeline Schedule %{id}" +msgstr "Alterar Agendamento do Pipeline %{id}" + +msgid "Every day (at 4:00am)" +msgstr "Todos os dias (às 4:00)" + +msgid "Every month (on the 1st at 4:00am)" +msgstr "Todos os meses (no dia primeiro às 4:00)" + +msgid "Every week (Sundays at 4:00am)" +msgstr "Toda semana (domingos às 4:00)" + +msgid "Failed to change the owner" +msgstr "Erro ao alterar o proprietário" + +msgid "Failed to remove the pipeline schedule" +msgstr "Erro ao excluir o agendamento do pipeline" + +msgid "Files" +msgstr "Arquivos" + +msgid "Filter by commit message" +msgstr "Filtrar por mensagem de commit" + +msgid "Find by path" +msgstr "Localizar por caminho" + +msgid "Find file" +msgstr "Localizar arquivo" + msgid "FirstPushedBy|First" msgstr "Primeiro" msgid "FirstPushedBy|pushed by" msgstr "publicado por" +msgid "Fork" +msgid_plural "Forks" +msgstr[0] "Fork" +msgstr[1] "Forks" + +msgid "ForkedFromProjectPath|Forked from" +msgstr "Forked de" + msgid "From issue creation until deploy to production" -msgstr "Da criação de tarefas até a implantação para a produção" +msgstr "Da abertura de tarefas até a implantação para a produção" msgid "From merge request merge until deploy to production" -msgstr "Da incorporação do merge request até a implantação em produção" +msgstr "" +"Da aceitação da solicitação de incorporação até a implantação em produção" + +msgid "Go to your fork" +msgstr "Ir para seu fork" + +msgid "GoToYourFork|Fork" +msgstr "Fork" + +msgid "Home" +msgstr "Início" + +msgid "Housekeeping successfully started" +msgstr "Manutenção iniciada com sucesso" + +msgid "Import repository" +msgstr "Importar repositório" + +msgid "Interval Pattern" +msgstr "Padrão de intervalo" msgid "Introducing Cycle Analytics" msgstr "Apresentando a Análise de Ciclo" +msgid "Jobs for last month" +msgstr "Jobs no último mês" + +msgid "Jobs for last week" +msgstr "Jobs na última semana" + +msgid "Jobs for last year" +msgstr "Jobs no último ano" + +msgid "LFSStatus|Disabled" +msgstr "Desabilitado" + +msgid "LFSStatus|Enabled" +msgstr "Habilitado" + msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "Último %d dia" msgstr[1] "Últimos %d dias" +msgid "Last Pipeline" +msgstr "Último Pipeline" + +msgid "Last Update" +msgstr "Última Atualização" + +msgid "Last commit" +msgstr "Último commit" + +msgid "Learn more in the" +msgstr "Saiba mais em" + +msgid "Learn more in the|pipeline schedules documentation" +msgstr "documentação de agendamento de pipeline" + +msgid "Leave group" +msgstr "Sair do grupo" + +msgid "Leave project" +msgstr "Sair do projeto" + msgid "Limited to showing %d event at most" msgid_plural "Limited to showing %d events at most" -msgstr[0] "Limitado a mostrar %d evento no máximo" -msgstr[1] "Limitado a mostrar %d eventos no máximo" +msgstr[0] "Limitado a mostrar %d evento, no máximo" +msgstr[1] "Limitado a mostrar %d eventos, no máximo" msgid "Median" msgstr "Mediana" +msgid "MissingSSHKeyWarningLink|add an SSH key" +msgstr "adicione uma chave SSH" + msgid "New Issue" msgid_plural "New Issues" -msgstr[0] "Nova Tarefa" -msgstr[1] "Novas Tarefas" +msgstr[0] "Nova Issue" +msgstr[1] "Novas Issues" + +msgid "New Pipeline Schedule" +msgstr "Novo Agendamento de Pipeline" + +msgid "New branch" +msgstr "Novo branch" + +msgid "New directory" +msgstr "Novo diretório" + +msgid "New file" +msgstr "Novo arquivo" + +msgid "New issue" +msgstr "Nova issue" + +msgid "New merge request" +msgstr "Novo merge request" + +msgid "New schedule" +msgstr "Novo agendamento" + +msgid "New snippet" +msgstr "Novo snippet" + +msgid "New tag" +msgstr "Nova tag" + +msgid "No repository" +msgstr "Nenhum repositório" + +msgid "No schedules" +msgstr "Nenhum agendamento" msgid "Not available" msgstr "Não disponível" @@ -96,29 +539,228 @@ msgstr "Não disponível" msgid "Not enough data" msgstr "Dados insuficientes" +msgid "Notification events" +msgstr "Eventos de notificação" + +msgid "NotificationEvent|Close issue" +msgstr "Fechar issue" + +msgid "NotificationEvent|Close merge request" +msgstr "Fechar merge request" + +msgid "NotificationEvent|Failed pipeline" +msgstr "Falha no pipeline" + +msgid "NotificationEvent|Merge merge request" +msgstr "Aceitar merge request" + +msgid "NotificationEvent|New issue" +msgstr "Nova issue" + +msgid "NotificationEvent|New merge request" +msgstr "Novo merge request" + +msgid "NotificationEvent|New note" +msgstr "Novo comentário" + +msgid "NotificationEvent|Reassign issue" +msgstr "Reatribuir issue" + +msgid "NotificationEvent|Reassign merge request" +msgstr "Reatribuir merge request" + +msgid "NotificationEvent|Reopen issue" +msgstr "Reabrir issue" + +msgid "NotificationEvent|Successful pipeline" +msgstr "Pipeline bem sucedido" + +msgid "NotificationLevel|Custom" +msgstr "Personalizar" + +msgid "NotificationLevel|Disabled" +msgstr "Desabilitado" + +msgid "NotificationLevel|Global" +msgstr "Global" + +msgid "NotificationLevel|On mention" +msgstr "Quando mencionado" + +msgid "NotificationLevel|Participate" +msgstr "Participar" + +msgid "NotificationLevel|Watch" +msgstr "Observar" + +msgid "OfSearchInADropdown|Filter" +msgstr "Filtrar" + msgid "OpenedNDaysAgo|Opened" msgstr "Aberto" +msgid "Options" +msgstr "Opções" + +msgid "Owner" +msgstr "Proprietário" + +msgid "Pipeline" +msgstr "Pipeline" + msgid "Pipeline Health" msgstr "Saúde da Pipeline" +msgid "Pipeline Schedule" +msgstr "Agendamento da Pipeline" + +msgid "Pipeline Schedules" +msgstr "Agendamentos da Pipeline" + +msgid "PipelineCharts|Failed:" +msgstr "PipelineCharts|Falhou:" + +msgid "PipelineCharts|Overall statistics" +msgstr "PipelineCharts|Estatísticas gerais" + +msgid "PipelineCharts|Success ratio:" +msgstr "PipelineCharts|Taxa de sucesso:" + +msgid "PipelineCharts|Successful:" +msgstr "PipelineCharts|Sucesso:" + +msgid "PipelineCharts|Total:" +msgstr "PipelineCharts|Total:" + +msgid "PipelineSchedules|Activated" +msgstr "Ativado" + +msgid "PipelineSchedules|Active" +msgstr "Ativo" + +msgid "PipelineSchedules|All" +msgstr "Todos" + +msgid "PipelineSchedules|Inactive" +msgstr "Inativo" + +msgid "PipelineSchedules|Next Run" +msgstr "Próxima Execução" + +msgid "PipelineSchedules|None" +msgstr "Nenhum" + +msgid "PipelineSchedules|Provide a short description for this pipeline" +msgstr "Digite uma descrição curta para esta pipeline" + +msgid "PipelineSchedules|Take ownership" +msgstr "Tornar-se proprietário" + +msgid "PipelineSchedules|Target" +msgstr "Destino" + +msgid "PipelineSheduleIntervalPattern|Custom" +msgstr "Personalizado" + +msgid "Pipelines" +msgstr "Pipelines" + +msgid "Pipelines charts" +msgstr "Gráficos de pipelines" + +msgid "Pipeline|all" +msgstr "Pipeline|todos" + +msgid "Pipeline|success" +msgstr "Pipeline|sucesso" + +msgid "Pipeline|with stage" +msgstr "com etapa" + +msgid "Pipeline|with stages" +msgstr "com etapas" + +msgid "Project '%{project_name}' queued for deletion." +msgstr "Projeto'%{project_name}' marcado para exclusão." + +msgid "Project '%{project_name}' was successfully created." +msgstr "Projeto '%{project_name}' criado com sucesso." + +msgid "Project '%{project_name}' was successfully updated." +msgstr "Projeto '%{project_name}' atualizado com sucesso." + +msgid "Project '%{project_name}' will be deleted." +msgstr "Projeto '%{project_name}' será excluído." + +msgid "Project access must be granted explicitly to each user." +msgstr "" +"Acesso ao projeto deve ser concedido explicitamente para cada usuário." + +msgid "Project export could not be deleted." +msgstr "A exportação do projeto não pôde ser excluída." + +msgid "Project export has been deleted." +msgstr "Exportação do projeto excluída." + +msgid "" +"Project export link has expired. Please generate a new export from your " +"project settings." +msgstr "" +"O link para a exportação do projeto expirou. Favor gerar uma nova exportação " +"a partir das configurações do projeto." + +msgid "Project export started. A download link will be sent by email." +msgstr "" +"Exportação do projeto iniciada. Um link para baixá-la será enviado por email." +"" + +msgid "Project home" +msgstr "Página inicial do projeto" + +msgid "ProjectFeature|Disabled" +msgstr "Desabilitado" + +msgid "ProjectFeature|Everyone with access" +msgstr "Todos que possuem acesso" + +msgid "ProjectFeature|Only team members" +msgstr "Apenas membros do time" + +msgid "ProjectFileTree|Name" +msgstr "Nome" + +msgid "ProjectLastActivity|Never" +msgstr "Nunca" + msgid "ProjectLifecycle|Stage" msgstr "Etapa" +msgid "ProjectNetworkGraph|Graph" +msgstr "Árvore" + msgid "Read more" -msgstr "Ler mais" +msgstr "Leia mais" + +msgid "Readme" +msgstr "Leia-me" + +msgid "RefSwitcher|Branches" +msgstr "Branches" + +msgid "RefSwitcher|Tags" +msgstr "Tags" msgid "Related Commits" msgstr "Commits Relacionados" msgid "Related Deployed Jobs" -msgstr "Jobs Relacionados Incorporados" +msgstr "Tarefas Implantadas Relacionadas" msgid "Related Issues" -msgstr "Tarefas Relacionadas" +msgstr "Issues Relacionadas" msgid "Related Jobs" -msgstr "Jobs Relacionados" +msgstr "Tarefas Relacionadas" msgid "Related Merge Requests" msgstr "Merge Requests Relacionados" @@ -126,84 +768,180 @@ msgstr "Merge Requests Relacionados" msgid "Related Merged Requests" msgstr "Merge Requests Relacionados" +msgid "Remind later" +msgstr "Lembrar mais tarde" + +msgid "Remove project" +msgstr "Remover projeto" + +msgid "Request Access" +msgstr "Solicitar acesso" + +msgid "Revert this commit" +msgstr "Reverter este commit" + +msgid "Revert this merge request" +msgstr "Reverter esse merge request" + +msgid "Save pipeline schedule" +msgstr "Salvar agendamento da pipeline" + +msgid "Schedule a new pipeline" +msgstr "Agendar nova pipeline" + +msgid "Scheduling Pipelines" +msgstr "Agendando pipelines" + +msgid "Search branches and tags" +msgstr "Procurar branch e tags" + +msgid "Select Archive Format" +msgstr "Selecionar Formato do Arquivo" + +msgid "Select a timezone" +msgstr "Selecionar fuso horário" + +msgid "Select target branch" +msgstr "Selecionar branch de destino" + +msgid "Set a password on your account to pull or push via %{protocol}." +msgstr "" +"Defina uma senha para sua conta para aceitar ou entregar código via " +"%{protocol}." + +msgid "Set up CI" +msgstr "Configurar CI" + +msgid "Set up Koding" +msgstr "Configurar Koding" + +msgid "Set up auto deploy" +msgstr "Configurar implantação automática" + +msgid "SetPasswordToCloneLink|set a password" +msgstr "defina uma senha" + msgid "Showing %d event" msgid_plural "Showing %d events" msgstr[0] "Mostrando %d evento" msgstr[1] "Mostrando %d eventos" +msgid "Source code" +msgstr "Código-fonte" + +msgid "StarProject|Star" +msgstr "Marcar" + +msgid "Start a %{new_merge_request} with these changes" +msgstr "Iniciar um %{new_merge_request} a partir dessas alterações" + +msgid "Switch branch/tag" +msgstr "Trocar branch/tag" + +msgid "Tag" +msgid_plural "Tags" +msgstr[0] "Tag" +msgstr[1] "Tags" + +msgid "Tags" +msgstr "Tags" + +msgid "Target Branch" +msgstr "Branch de destino" + msgid "" "The coding stage shows the time from the first commit to creating the merge " "request. The data will automatically be added here once you create your " "first merge request." msgstr "" -"O estágio de codificação mostra o tempo desde o primeiro commit até a " -"criação do merge request. \n" -"Os dados serão automaticamente adicionados aqui uma vez que você tenha " -"criado seu primeiro merge request." +"A etapa de codificação mostra o tempo desde a entrega do primeiro commit até " +"a criação do merge request. Os dados serão automaticamente adicionados aqui " +"desde o momento de criação do merge request." msgid "The collection of events added to the data gathered for that stage." -msgstr "" -"A coleção de eventos adicionados aos dados coletados para esse estágio." +msgstr "A coleção de eventos adicionados aos dados coletados para essa etapa." + +msgid "The fork relationship has been removed." +msgstr "O relacionamento como fork foi removido." msgid "" "The issue stage shows the time it takes from creating an issue to assigning " "the issue to a milestone, or add the issue to a list on your Issue Board. " "Begin creating issues to see data for this stage." msgstr "" -"O estágio em questão mostra o tempo que leva desde a criação de uma tarefa " -"até a sua assinatura para um milestone, ou a sua adição para a lista no seu " -"Painel de Tarefas. Comece a criar tarefas para ver dados para esta etapa." +"A etapa de relatos mostra o tempo que leva desde a criação de uma issue até " +"sua atribuição a um marco, ou sua adição a uma lista no seu Issue Board. " +"Comece a criar issues para ver dados para esta etapa." msgid "The phase of the development lifecycle." msgstr "A fase do ciclo de vida do desenvolvimento." msgid "" +"The pipelines schedule runs pipelines in the future, repeatedly, for " +"specific branches or tags. Those scheduled pipelines will inherit limited " +"project access based on their associated user." +msgstr "" +"O agendamento de pipeline executa pipelines no futuro, repetidamente, para " +"branches ou tags específicas. Essas pipelines agendadas terão acesso " +"limitado ao projeto baseado no seu usuário associado." + +msgid "" "The planning stage shows the time from the previous step to pushing your " "first commit. This time will be added automatically once you push your first " "commit." msgstr "" -"A fase de planejamento mostra o tempo do passo anterior até empurrar o seu " -"primeiro commit. Este tempo será adicionado automaticamente assim que você " -"realizar seu primeiro commit." +"A etapa de planejamento mostra o tempo do passo anterior até a publicação de " +"seu primeiro conjunto de mudanças. Este tempo será adicionado " +"automaticamente assim que você enviar seu primeiro conjunto de mudanças." msgid "" "The production stage shows the total time it takes between creating an issue " "and deploying the code to production. The data will be automatically added " "once you have completed the full idea to production cycle." msgstr "" -"O estágio de produção mostra o tempo total que leva entre criar uma tarefa e " -"implantar o código na produção. Os dados serão adicionados automaticamente " -"até que você complete todo o ciclo de produção." +"A etapa de produção mostra o tempo total que leva entre criar uma issue e " +"implantar o código em produção. Os dados serão adicionados automaticamente " +"assim que você completar todo o ciclo de produção." + +msgid "The project can be accessed by any logged in user." +msgstr "O projeto pode ser acessado por qualquer usuário autenticado." + +msgid "The project can be accessed without any authentication." +msgstr "O projeto pode ser acessado sem a necessidade de autenticação." + +msgid "The repository for this project does not exist." +msgstr "Não existe repositório para este projeto." msgid "" "The review stage shows the time from creating the merge request to merging " "it. The data will automatically be added after you merge your first merge " "request." msgstr "" -"A etapa de revisão mostra o tempo de criação de um merge request até que o " -"merge seja feito. Os dados serão automaticamente adicionados depois que você " -"fizer seu primeiro merge request." +"A etapa de revisão mostra o tempo de criação de uma solicitação de " +"incorporação até sua aceitação. Os dados serão automaticamente adicionados " +"depois que sua primeira solicitação de incorporação for aceita." msgid "" "The staging stage shows the time between merging the MR and deploying code " "to the production environment. The data will be automatically added once you " "deploy to production for the first time." msgstr "" -"O estágio de estágio mostra o tempo entre a fusão do MR e o código de " -"implantação para o ambiente de produção. Os dados serão automaticamente " -"adicionados depois de implantar na produção pela primeira vez." +"A etapa de homologação mostra o tempo entre o aceite da solicitação de " +"incorporação e a implantação do código no ambiente de produção. Os dados " +"serão automaticamente adicionados depois que você implantar em produção pela " +"primeira vez." msgid "" "The testing stage shows the time GitLab CI takes to run every pipeline for " "the related merge request. The data will automatically be added after your " "first pipeline finishes running." msgstr "" -"A fase de teste mostra o tempo que o GitLab CI leva para executar cada " -"pipeline para o merge request relacionado. Os dados serão automaticamente " -"adicionados após a conclusão do primeiro pipeline." +"A etapa de testes mostra o tempo que o GitLab CI leva para executar cada " +"pipeline para a solicitação de incorporação associada. Os dados serão " +"automaticamente adicionados após a conclusão do primeiro pipeline." msgid "The time taken by each data entry gathered by that stage." -msgstr "O tempo necessário para cada entrada de dados reunida por essa etapa." +msgstr "O tempo necessário por cada entrada de dados reunida por essa etapa." msgid "" "The value lying at the midpoint of a series of observed values. E.g., " @@ -211,19 +949,151 @@ msgid "" " 6." msgstr "" "O valor situado no ponto médio de uma série de valores observados. Ex., " -"entre 3, 5, 9, a mediana é 5. Entre 3, 5, 7, 8, a mediana é (5 + 7) / 2 = 6." +"entre 3, 5, 9, a mediana é 5. Entre 3, 5, 7, 8, a mediana é (5+7)/2 = 6." + +msgid "" +"This means you can not push code until you create an empty repository or " +"import existing one." +msgstr "" +"Isto significa que você não pode entregar código até que crie um repositório " +"vazio ou importe um existente." msgid "Time before an issue gets scheduled" -msgstr "Tempo até que uma tarefa seja planejada" +msgstr "Tempo até que uma issue seja agendada" msgid "Time before an issue starts implementation" -msgstr "Tempo até que uma tarefa comece a ser implementada" +msgstr "Tempo até que uma issue comece a ser implementado" msgid "Time between merge request creation and merge/close" -msgstr "Tempo entre a criação do merge request e o merge/fechamento" +msgstr "" +"Tempo entre a criação da solicitação de incorporação e a aceitação/" +"fechamento" msgid "Time until first merge request" -msgstr "Tempo até o primeiro merge request" +msgstr "Tempo até a primeira solicitação de incorporação" + +msgid "Timeago|%s days ago" +msgstr "há %s dias" + +msgid "Timeago|%s days remaining" +msgstr "%s dias restantes" + +msgid "Timeago|%s hours remaining" +msgstr "%s horas restantes" + +msgid "Timeago|%s minutes ago" +msgstr "há %s minutos" + +msgid "Timeago|%s minutes remaining" +msgstr "%s minutos restantes" + +msgid "Timeago|%s months ago" +msgstr "há %s meses" + +msgid "Timeago|%s months remaining" +msgstr "%s meses restantes" + +msgid "Timeago|%s seconds remaining" +msgstr "%s segundos restantes" + +msgid "Timeago|%s weeks ago" +msgstr "há %s semanas" + +msgid "Timeago|%s weeks remaining" +msgstr "%s semanas restantes" + +msgid "Timeago|%s years ago" +msgstr "há %s anos" + +msgid "Timeago|%s years remaining" +msgstr "%s anos restantes" + +msgid "Timeago|1 day remaining" +msgstr "1 dia restante" + +msgid "Timeago|1 hour remaining" +msgstr "1 hora restante" + +msgid "Timeago|1 minute remaining" +msgstr "1 minuto restante" + +msgid "Timeago|1 month remaining" +msgstr "1 mês restante" + +msgid "Timeago|1 week remaining" +msgstr "1 semana restante" + +msgid "Timeago|1 year remaining" +msgstr "1 ano restante" + +msgid "Timeago|Past due" +msgstr "Venceu" + +msgid "Timeago|a day ago" +msgstr "há um dia" + +msgid "Timeago|a month ago" +msgstr "há um mês" + +msgid "Timeago|a week ago" +msgstr "há uma semana" + +msgid "Timeago|a while" +msgstr "há algum tempo" + +msgid "Timeago|a year ago" +msgstr "há um ano" + +msgid "Timeago|about %s hours ago" +msgstr "há cerca de %s horas" + +msgid "Timeago|about a minute ago" +msgstr "há cerca de um minuto" + +msgid "Timeago|about an hour ago" +msgstr "há cerca de uma hora" + +msgid "Timeago|in %s days" +msgstr "em %s dias" + +msgid "Timeago|in %s hours" +msgstr "em %s horas" + +msgid "Timeago|in %s minutes" +msgstr "em %s minutos" + +msgid "Timeago|in %s months" +msgstr "em %s meses" + +msgid "Timeago|in %s seconds" +msgstr "em %s segundos" + +msgid "Timeago|in %s weeks" +msgstr "em %s semanas" + +msgid "Timeago|in %s years" +msgstr "em %s anos" + +msgid "Timeago|in 1 day" +msgstr "em 1 dia" + +msgid "Timeago|in 1 hour" +msgstr "em 1 hora" + +msgid "Timeago|in 1 minute" +msgstr "em 1 minuto" + +msgid "Timeago|in 1 month" +msgstr "em 1 mês" + +msgid "Timeago|in 1 week" +msgstr "em 1 semana" + +msgid "Timeago|in 1 year" +msgstr "em 1 ano" + +msgid "Timeago|less than a minute ago" +msgstr "há menos de um minuto" msgid "Time|hr" msgid_plural "Time|hrs" @@ -244,20 +1114,125 @@ msgstr "Tempo Total" msgid "Total test time for all commits/merges" msgstr "Tempo de teste total para todos os commits/merges" +msgid "Unstar" +msgstr "Desmarcar" + +msgid "Upload New File" +msgstr "Enviar Novo Arquivo" + +msgid "Upload file" +msgstr "Enviar arquivo" + +msgid "UploadLink|click to upload" +msgstr "UploadLink|clique para fazer upload" + +msgid "Use your global notification setting" +msgstr "Utilizar configuração de notificação global" + +msgid "View open merge request" +msgstr "Ver merge request aberto" + +msgid "VisibilityLevel|Internal" +msgstr "Interno" + +msgid "VisibilityLevel|Private" +msgstr "Privado" + +msgid "VisibilityLevel|Public" +msgstr "Público" + msgid "Want to see the data? Please ask an administrator for access." msgstr "Precisa visualizar os dados? Solicite acesso ao administrador." msgid "We don't have enough data to show this stage." -msgstr "Não temos dados suficientes para mostrar esta fase." +msgstr "Esta etapa não possui dados suficientes para exibição." -msgid "You have reached your project limit" +msgid "Withdraw Access Request" +msgstr "Remover Requisição de Acesso" + +msgid "" +"You are going to remove %{project_name_with_namespace}.\n" +"Removed project CANNOT be restored!\n" +"Are you ABSOLUTELY sure?" +msgstr "" +"Você irá remover %{project_name_with_namespace}.\n" +"O projeto removido NÃO PODE ser restaurado!\n" +"Tem certeza ABSOLUTA?" + +msgid "" +"You are going to remove the fork relationship to source project " +"%{forked_from_project}. Are you ABSOLUTELY sure?" msgstr "" +"Você ira remover o relacionamento de fork com o projeto original " +"%{forked_from_project}. Tem certeza ABSOLUTA?" + +msgid "" +"You are going to transfer %{project_name_with_namespace} to another owner. " +"Are you ABSOLUTELY sure?" +msgstr "" +"Você irá transferir %{project_name_with_namespace} para outro proprietário. " +"Tem certeza ABSOLUTA?" + +msgid "You can only add files when you are on a branch" +msgstr "Você somente pode adicionar arquivos quando estiver em um branch" + +msgid "You have reached your project limit" +msgstr "Você atingiu o limite de seu projeto" + +msgid "You must sign in to star a project" +msgstr "Você deve estar autenticado para marcar um projeto" msgid "You need permission." msgstr "Você precisa de permissão." +msgid "You will not get any notifications via email" +msgstr "Você não será notificado por email" + +msgid "You will only receive notifications for the events you choose" +msgstr "Você será notificado apenas sobre eventos selecionados" + +msgid "" +"You will only receive notifications for threads you have participated in" +msgstr "Você será notificado apenas sobre tópicos nos quais participou" + +msgid "You will receive notifications for any activity" +msgstr "Você será notificado sobre qualquer atividade" + +msgid "" +"You will receive notifications only for comments in which you were " +"@mentioned" +msgstr "Você será notificado apenas sobre comentários que te @mencionam" + +msgid "" +"You won't be able to pull or push project code via %{protocol} until you " +"%{set_password_link} on your account" +msgstr "" +"Você não poderá fazer pull ou push via %{protocol} até que " +"%{set_password_link} para sua conta" + +msgid "" +"You won't be able to pull or push project code via SSH until you " +"%{add_ssh_key_link} to your profile" +msgstr "" +"Você não conseguirá fazer pull ou push no projeto via SSH até que adicione " +"%{add_ssh_key_link} ao seu perfil" + +msgid "Your name" +msgstr "Seu nome" + msgid "day" msgid_plural "days" msgstr[0] "dia" msgstr[1] "dias" +msgid "new merge request" +msgstr "novo merge request" + +msgid "notification emails" +msgstr "emails de notificação" + +msgid "parent" +msgid_plural "parents" +msgstr[0] "pai" +msgstr[1] "pais" + diff --git a/locale/ru/gitlab.po b/locale/ru/gitlab.po new file mode 100644 index 00000000000..4643bed98e2 --- /dev/null +++ b/locale/ru/gitlab.po @@ -0,0 +1,1233 @@ +# SAS <Stepanov.sa@bashkortostan.ru>, 2017. #zanata +# Huang Tao <htve@outlook.com>, 2017. #zanata +msgid "" +msgstr "" +"Project-Id-Version: gitlab 1.0.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-06-28 13:32+0200\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"PO-Revision-Date: 2017-07-11 05:13-0400\n" +"Last-Translator: SAS <Stepanov.sa@bashkortostan.ru>\n" +"Language-Team: Russian (https://translate.zanata.org/project/view/GitLab)\n" +"Language: ru\n" +"X-Generator: Zanata 3.9.6\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n" + +msgid "%s additional commit has been omitted to prevent performance issues." +msgid_plural "" +"%s additional commits have been omitted to prevent performance issues." +msgstr[0] "" +"%s добавленный коммит был исключен для предотвращения проблем с " +"производительностью." +msgstr[1] "" +"%s добавленные коммиты были исключены для предотвращения проблем с " +"производительностью." +msgstr[2] "" +"%s добавленные коммиты были исключены для предотвращения проблем с " +"производительностью." + +msgid "%d commit" +msgid_plural "%d commits" +msgstr[0] "%d коммит" +msgstr[1] "%d коммит(а|ов)" +msgstr[2] "%d коммит(а|ов)" + +msgid "%{commit_author_link} committed %{commit_timeago}" +msgstr "%{commit_author_link} закоммичено %{commit_timeago}" + +msgid "1 pipeline" +msgid_plural "%d pipelines" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "A collection of graphs regarding Continuous Integration" +msgstr "" + +msgid "About auto deploy" +msgstr "Автоматическое развертывание" + +msgid "Active" +msgstr "Активный" + +msgid "Activity" +msgstr "Активность" + +msgid "Add Changelog" +msgstr "Добавить в журнал изменений" + +msgid "Add Contribution guide" +msgstr "Добавить руководство для контрибьютеров" + +msgid "Add License" +msgstr "Добавить лицензию" + +msgid "Add an SSH key to your profile to pull or push via SSH." +msgstr "" +"Добавьте ключ SSH в свой профиль, чтобы отправлять или получать код через " +"SSH." + +msgid "Add new directory" +msgstr "Добавить новую директорию" + +msgid "Archived project! Repository is read-only" +msgstr "Архивный проект! Репозиторий доступен только для чтения" + +msgid "Are you sure you want to delete this pipeline schedule?" +msgstr "Вы действительно хотите удалить это расписание конвейера?" + +msgid "Attach a file by drag & drop or %{upload_link}" +msgstr "Приложить файл через drag & drop или %{upload_link}" + +msgid "Branch" +msgid_plural "Branches" +msgstr[0] "Ветка" +msgstr[1] "Ветки" +msgstr[2] "Ветки" + +msgid "" +"Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, " +"choose a GitLab CI Yaml template and commit your changes. " +"%{link_to_autodeploy_doc}" +msgstr "" +"Ветка <strong>%{branch_name}</strong> создана. Для настройки автоматического " +"развертывания выберете GitLab CI Yaml-шаблон и зафиксируйте изменения. " +"%{link_to_autodeploy_doc}" + +msgid "BranchSwitcherPlaceholder|Search branches" +msgstr "BranchSwitcherPlaceholder|Поиск веток" + +msgid "BranchSwitcherTitle|Switch branch" +msgstr "BranchSwitcherTitle|Переключить ветку" + +msgid "Branches" +msgstr "Ветки" + +msgid "Browse Directory" +msgstr "Просмотр директории" + +msgid "Browse File" +msgstr "Просмотр файла" + +msgid "Browse Files" +msgstr "Просмотр файлов" + +msgid "Browse files" +msgstr "Просмотр файлов" + +msgid "ByAuthor|by" +msgstr "ByAuthor|по автору" + +msgid "CI configuration" +msgstr "Настройка CI" + +msgid "Cancel" +msgstr "Отмена" + +msgid "ChangeTypeActionLabel|Pick into branch" +msgstr "ChangeTypeActionLabel|Выбрать в ветке" + +msgid "ChangeTypeActionLabel|Revert in branch" +msgstr "ChangeTypeActionLabel|Отменить в ветке" + +msgid "ChangeTypeAction|Cherry-pick" +msgstr "ChangeTypeAction|Подобрать" + +msgid "ChangeTypeAction|Revert" +msgstr "ChangeTypeAction|Отменить" + +msgid "Changelog" +msgstr "Журнал изменений" + +msgid "Charts" +msgstr "Графики" + +msgid "Cherry-pick this commit" +msgstr "Подобрать в этом коммите" + +msgid "Cherry-pick this merge request" +msgstr "Побрать в этом запросе на слияние" + +msgid "CiStatusLabel|canceled" +msgstr "CiStatusLabel|отменено" + +msgid "CiStatusLabel|created" +msgstr "CiStatusLabel|создано" + +msgid "CiStatusLabel|failed" +msgstr "CiStatusLabel|неудачно" + +msgid "CiStatusLabel|manual action" +msgstr "CiStatusLabel|ручное действие" + +msgid "CiStatusLabel|passed" +msgstr "CiStatusLabel|пройдено" + +msgid "CiStatusLabel|passed with warnings" +msgstr "CiStatusLabel|пройдено с предупреждениями" + +msgid "CiStatusLabel|pending" +msgstr "CiStatusLabel|в ожидании" + +msgid "CiStatusLabel|skipped" +msgstr "CiStatusLabel|пропущено" + +msgid "CiStatusLabel|waiting for manual action" +msgstr "CiStatusLabel|ожидание ручных действий" + +msgid "CiStatusText|blocked" +msgstr "CiStatusText|блокировано" + +msgid "CiStatusText|canceled" +msgstr "CiStatusText|отменено" + +msgid "CiStatusText|created" +msgstr "CiStatusText|создано" + +msgid "CiStatusText|failed" +msgstr "CiStatusText|неудачно" + +msgid "CiStatusText|manual" +msgstr "CiStatusText|ручное" + +msgid "CiStatusText|passed" +msgstr "CiStatusText|пройдено" + +msgid "CiStatusText|pending" +msgstr "CiStatusText|в ожидании" + +msgid "CiStatusText|skipped" +msgstr "CiStatusText|пропущено" + +msgid "CiStatus|running" +msgstr "CiStatus|выполняется" + +msgid "Commit" +msgid_plural "Commits" +msgstr[0] "Коммит" +msgstr[1] "Коммиты" +msgstr[2] "Коммиты" + +msgid "Commit duration in minutes for last 30 commits" +msgstr "" + +msgid "Commit message" +msgstr "Описание коммита" + +msgid "CommitBoxTitle|Commit" +msgstr "CommitBoxTitle|Коммит" + +msgid "CommitMessage|Add %{file_name}" +msgstr "CommitMessage|Добавить %{file_name}" + +msgid "Commits" +msgstr "Коммиты" + +msgid "Commits feed" +msgstr "" + +msgid "Commits|History" +msgstr "Commits|История" + +msgid "Committed by" +msgstr "Коммит" + +msgid "Compare" +msgstr "Сравнение" + +msgid "Contribution guide" +msgstr "Руководство контрибьютора" + +msgid "Contributors" +msgstr "Контрибьюторы" + +msgid "Copy URL to clipboard" +msgstr "Копировать URL в буфер обмена" + +msgid "Copy commit SHA to clipboard" +msgstr "Копировать SHA коммита в буфер обмена" + +msgid "Create New Directory" +msgstr "Создать новую директорию" + +msgid "" +"Create a personal access token on your account to pull or push via " +"%{protocol}." +msgstr "" + +msgid "Create directory" +msgstr "Создать директорию" + +msgid "Create empty bare repository" +msgstr "Создать пустой пустой репозиторий" + +msgid "Create merge request" +msgstr "Создать запрос на объединение" + +msgid "Create new..." +msgstr "Новый" + +msgid "CreateNewFork|Fork" +msgstr "CreateNewFork|Форк" + +msgid "CreateTag|Tag" +msgstr "CreateTag|Тэг" + +msgid "CreateTokenToCloneLink|create a personal access token" +msgstr "" + +msgid "Cron Timezone" +msgstr "Временная зона Cron" + +msgid "Cron syntax" +msgstr "Синтаксис Cron" + +msgid "Custom notification events" +msgstr " Настраиваемые уведомления о событиях" + +msgid "" +"Custom notification levels are the same as participating levels. With custom " +"notification levels you will also receive notifications for select events. " +"To find out more, check out %{notification_link}." +msgstr "" +"Настраиваемые уровни уведомлений аналогичны уровню уведомлений в " +"соответствии с участием. С настраиваемыми уровнями уведомлений вы также " +"будете получать уведомления о выбранных событиях. Чтобы узнать больше, " +"посмотрите %{notification_link}." + +msgid "Cycle Analytics" +msgstr "Аналитика цикла разработки" + +msgid "" +"Cycle Analytics gives an overview of how much time it takes to go from idea " +"to production in your project." +msgstr "" + +msgid "CycleAnalyticsStage|Code" +msgstr "CycleAnalyticsStage|Код" + +msgid "CycleAnalyticsStage|Issue" +msgstr "CycleAnalyticsStage|Обращение" + +msgid "CycleAnalyticsStage|Plan" +msgstr "" + +msgid "CycleAnalyticsStage|Production" +msgstr "" + +msgid "CycleAnalyticsStage|Review" +msgstr "CycleAnalyticsStage|Ревьюв" + +msgid "CycleAnalyticsStage|Staging" +msgstr "" + +msgid "CycleAnalyticsStage|Test" +msgstr "" + +msgid "Define a custom pattern with cron syntax" +msgstr "Определить настраиваемый шаблон с синтаксисом cron" + +msgid "Delete" +msgstr "Удалить" + +msgid "Deploy" +msgid_plural "Deploys" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "Description" +msgstr "Описание" + +msgid "Directory name" +msgstr "Наименование директории" + +msgid "Don't show again" +msgstr "Не показывать снова" + +msgid "Download" +msgstr "Загрузить" + +msgid "Download tar" +msgstr "Загрузить tar" + +msgid "Download tar.bz2" +msgstr "Загрузить tar.bz2" + +msgid "Download tar.gz" +msgstr "Загрузить tar.gz" + +msgid "Download zip" +msgstr "Загрузить zip" + +msgid "DownloadArtifacts|Download" +msgstr "DownloadArtifacts|Загрузка" + +msgid "DownloadCommit|Email Patches" +msgstr "DownloadCommit|Email-патчи" + +msgid "DownloadCommit|Plain Diff" +msgstr "DownloadCommit|Plain Diff" + +msgid "DownloadSource|Download" +msgstr "DownloadSource|Загрузка" + +msgid "Edit" +msgstr "Редактировать" + +msgid "Edit Pipeline Schedule %{id}" +msgstr "Изменить расписание конвейера %{id}" + +msgid "Every day (at 4:00am)" +msgstr "Ежедневно (в 4:00)" + +msgid "Every month (on the 1st at 4:00am)" +msgstr "Ежемесячно (каждое 1-е число в 4:00)" + +msgid "Every week (Sundays at 4:00am)" +msgstr "Еженедельно (по воскресениями в 4:00)" + +msgid "Failed to change the owner" +msgstr "Не удалось изменить владельца" + +msgid "Failed to remove the pipeline schedule" +msgstr "Не удалось удалить расписание конвейера" + +msgid "Files" +msgstr "Файлы" + +msgid "Filter by commit message" +msgstr "Фильтр по комментариями к коммитам" + +msgid "Find by path" +msgstr "Поиск по пути" + +msgid "Find file" +msgstr "Найти файл" + +msgid "FirstPushedBy|First" +msgstr "" + +msgid "FirstPushedBy|pushed by" +msgstr "" + +msgid "Fork" +msgid_plural "Forks" +msgstr[0] "Форк" +msgstr[1] "Форки" +msgstr[2] "Форки" + +msgid "ForkedFromProjectPath|Forked from" +msgstr "ForkedFromProjectPath|Форк от " + +msgid "From issue creation until deploy to production" +msgstr "" + +msgid "From merge request merge until deploy to production" +msgstr "" + +msgid "Go to your fork" +msgstr "Перейти к вашему форку" + +msgid "GoToYourFork|Fork" +msgstr "GoToYourFork|Форк" + +msgid "Home" +msgstr "Домашняя" + +msgid "Housekeeping successfully started" +msgstr "Очистка успешно запущена" + +msgid "Import repository" +msgstr "Импорт репозитория" + +msgid "Interval Pattern" +msgstr "Шаблон интервала" + +msgid "Introducing Cycle Analytics" +msgstr "" + +msgid "Jobs for last month" +msgstr "" + +msgid "Jobs for last week" +msgstr "" + +msgid "Jobs for last year" +msgstr "" + +msgid "LFSStatus|Disabled" +msgstr "LFSStatus|Отключено" + +msgid "LFSStatus|Enabled" +msgstr "LFSStatus|Включено" + +msgid "Last %d day" +msgid_plural "Last %d days" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "Last Pipeline" +msgstr "Последний конвейер" + +msgid "Last Update" +msgstr "Последнее обновление" + +msgid "Last commit" +msgstr "Последний коммит" + +msgid "Learn more in the" +msgstr "Узнайте больше в" + +msgid "Learn more in the|pipeline schedules documentation" +msgstr "Подробнее в|документации по расписаниям конвейеров" + +msgid "Leave group" +msgstr "Покинуть группу" + +msgid "Leave project" +msgstr "Покинуть проект" + +msgid "Limited to showing %d event at most" +msgid_plural "Limited to showing %d events at most" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "Median" +msgstr "Медиана" + +msgid "MissingSSHKeyWarningLink|add an SSH key" +msgstr "MissingSSHKeyWarningLink|добавить ключ SSH" + +msgid "New Issue" +msgid_plural "New Issues" +msgstr[0] "Новое обращение" +msgstr[1] "Новые обращения" +msgstr[2] "Новые обращения" + +msgid "New Pipeline Schedule" +msgstr "Новое расписание конвейера" + +msgid "New branch" +msgstr "Новая ветка" + +msgid "New directory" +msgstr "Новая директория" + +msgid "New file" +msgstr "Новый файл" + +msgid "New issue" +msgstr "Новое обращение" + +msgid "New merge request" +msgstr "Новый запрос на объединение" + +msgid "New schedule" +msgstr "Новое расписание" + +msgid "New snippet" +msgstr "Новый сниппет" + +msgid "New tag" +msgstr "Новый тэг" + +msgid "No repository" +msgstr "Нет репозитория" + +msgid "No schedules" +msgstr "Нет расписания" + +msgid "Not available" +msgstr "" + +msgid "Not enough data" +msgstr "" + +msgid "Notification events" +msgstr "Уведомления о событиях" + +msgid "NotificationEvent|Close issue" +msgstr "NotificationEvent|Обращение закрыто" + +msgid "NotificationEvent|Close merge request" +msgstr "Запрос на объединение закрыт" + +msgid "NotificationEvent|Failed pipeline" +msgstr "NotificationEvent|Неудача в конвейере" + +msgid "NotificationEvent|Merge merge request" +msgstr "NotificationEvent|Объединить запрос на слияние" + +msgid "NotificationEvent|New issue" +msgstr "NotificationEvent|Новое обращение" + +msgid "NotificationEvent|New merge request" +msgstr "NotificationEvent|Новый запрос на слияние" + +msgid "NotificationEvent|New note" +msgstr "NotificationEvent|Новая заметка" + +msgid "NotificationEvent|Reassign issue" +msgstr "NotificationEvent|Переназначить обращение" + +msgid "NotificationEvent|Reassign merge request" +msgstr "NotificationEvent|Переназначить запрос на слияние" + +msgid "NotificationEvent|Reopen issue" +msgstr "NotificationEvent|Переоткрыть обращение" + +msgid "NotificationEvent|Successful pipeline" +msgstr "NotificationEvent|Успешно в конвейере" + +msgid "NotificationLevel|Custom" +msgstr "NotificationLevel|Настраиваемый" + +msgid "NotificationLevel|Disabled" +msgstr "NotificationLevel|Отключено" + +msgid "NotificationLevel|Global" +msgstr "NotificationLevel|Глобальный" + +msgid "NotificationLevel|On mention" +msgstr "NotificationLevel|С упоминанием" + +msgid "NotificationLevel|Participate" +msgstr "NotificationLevel|По участию" + +msgid "NotificationLevel|Watch" +msgstr "NotificationLevel|Отслеживать" + +msgid "OfSearchInADropdown|Filter" +msgstr "OfSearchInADropdown|Фильтр" + +msgid "OpenedNDaysAgo|Opened" +msgstr "OpenedNDaysAgo|Открыто" + +msgid "Options" +msgstr "Настройки" + +msgid "Owner" +msgstr "Владелец" + +msgid "Pipeline" +msgstr "Конвейер" + +msgid "Pipeline Health" +msgstr "" + +msgid "Pipeline Schedule" +msgstr "Расписание конвейера" + +msgid "Pipeline Schedules" +msgstr "Расписания конвейеров" + +msgid "PipelineCharts|Failed:" +msgstr "" + +msgid "PipelineCharts|Overall statistics" +msgstr "" + +msgid "PipelineCharts|Success ratio:" +msgstr "" + +msgid "PipelineCharts|Successful:" +msgstr "" + +msgid "PipelineCharts|Total:" +msgstr "" + +msgid "PipelineSchedules|Activated" +msgstr "PipelineSchedules|Активировано" + +msgid "PipelineSchedules|Active" +msgstr "PipelineSchedules|Активно" + +msgid "PipelineSchedules|All" +msgstr "PipelineSchedules|Все" + +msgid "PipelineSchedules|Inactive" +msgstr "PipelineSchedules|Неактивно" + +msgid "PipelineSchedules|Next Run" +msgstr "PipelineSchedules|Следующий запуск" + +msgid "PipelineSchedules|None" +msgstr "PipelineSchedules|None" + +msgid "PipelineSchedules|Provide a short description for this pipeline" +msgstr "PipelineSchedules|Предоставьте краткое описание этого конвейера" + +msgid "PipelineSchedules|Take ownership" +msgstr "PipelineSchedules|Стать владельцем" + +msgid "PipelineSchedules|Target" +msgstr "PipelineSchedules|Цель" + +msgid "PipelineSheduleIntervalPattern|Custom" +msgstr "PipelineSheduleIntervalPattern|Настраиваемый" + +msgid "Pipelines" +msgstr "" + +msgid "Pipelines charts" +msgstr "" + +msgid "Pipeline|all" +msgstr "" + +msgid "Pipeline|success" +msgstr "" + +msgid "Pipeline|with stage" +msgstr "Pipeline|со стадией" + +msgid "Pipeline|with stages" +msgstr "Pipeline|со стадиями" + +msgid "Project '%{project_name}' queued for deletion." +msgstr "Проект '%{project_name}' добавлен в очередь на удаление." + +msgid "Project '%{project_name}' was successfully created." +msgstr "Проект '%{project_name}' успешно создан." + +msgid "Project '%{project_name}' was successfully updated." +msgstr "Проект '%{project_name}' успешно обновлен." + +msgid "Project '%{project_name}' will be deleted." +msgstr "Проект '%{project_name}' удален." + +msgid "Project access must be granted explicitly to each user." +msgstr "Доступ к проекту должен предоставляться явно каждому пользователю." + +msgid "Project export could not be deleted." +msgstr "Невозможно удалить экспорт проекта." + +msgid "Project export has been deleted." +msgstr "Экспорт проекта удален." + +msgid "" +"Project export link has expired. Please generate a new export from your " +"project settings." +msgstr "" +"Истек срок действия ссылки на проект. Создайте новый экспорт в ваших " +"настройках проекта." + +msgid "Project export started. A download link will be sent by email." +msgstr "" +"Начат экспорт проекта. Ссылка для скачивания будет отправлена по электронной " +"почте." + +msgid "Project home" +msgstr "Домашняя страница проекта" + +msgid "ProjectFeature|Disabled" +msgstr "ProjectFeature|Отключено" + +msgid "ProjectFeature|Everyone with access" +msgstr "ProjectFeature|Все с доступом" + +msgid "ProjectFeature|Only team members" +msgstr "ProjectFeature|Только члены команды" + +msgid "ProjectFileTree|Name" +msgstr "ProjectFileTree|Имя" + +msgid "ProjectLastActivity|Never" +msgstr "ProjectLastActivity|Никогда" + +msgid "ProjectLifecycle|Stage" +msgstr "" + +msgid "ProjectNetworkGraph|Graph" +msgstr "ProjectNetworkGraph|Граф" + +msgid "Read more" +msgstr "" + +msgid "Readme" +msgstr "Readme" + +msgid "RefSwitcher|Branches" +msgstr "RefSwitcher|Ветки" + +msgid "RefSwitcher|Tags" +msgstr "RefSwitcher|Тэги" + +msgid "Related Commits" +msgstr "" + +msgid "Related Deployed Jobs" +msgstr "" + +msgid "Related Issues" +msgstr "" + +msgid "Related Jobs" +msgstr "" + +msgid "Related Merge Requests" +msgstr "Связанные запросы на слияние" + +msgid "Related Merged Requests" +msgstr "Связанные объединенные запросы" + +msgid "Remind later" +msgstr "Напомнить позже" + +msgid "Remove project" +msgstr "Удалить проект" + +msgid "Request Access" +msgstr "Запрос доступа" + +msgid "Revert this commit" +msgstr "Отменить это изменение" + +msgid "Revert this merge request" +msgstr "Отменить этот запрос на слияние" + +msgid "Save pipeline schedule" +msgstr "Сохранить расписание конвейра" + +msgid "Schedule a new pipeline" +msgstr "Расписание нового конвейера" + +msgid "Scheduling Pipelines" +msgstr "Планирование конвейеров" + +msgid "Search branches and tags" +msgstr "Найти ветки и тэги" + +msgid "Select Archive Format" +msgstr "Выбрать формат архива" + +msgid "Select a timezone" +msgstr "Выбор временной зоны" + +msgid "Select target branch" +msgstr "Выбор целевой ветки" + +msgid "Set a password on your account to pull or push via %{protocol}." +msgstr "" +"Установите пароль в своем аккаунте, чтобы отправлять или получать код через " +"%{protocol}." + +msgid "Set up CI" +msgstr "Настройка CI" + +msgid "Set up Koding" +msgstr "Настройка Koding" + +msgid "Set up auto deploy" +msgstr "Настройка автоматического развертывания" + +msgid "SetPasswordToCloneLink|set a password" +msgstr "SetPasswordToCloneLink|установить пароль" + +msgid "Showing %d event" +msgid_plural "Showing %d events" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "Source code" +msgstr "Исходный код" + +msgid "StarProject|Star" +msgstr "StarProject|Отметить" + +msgid "Start a %{new_merge_request} with these changes" +msgstr "Начать %{new_merge_request} с этих изменений" + +msgid "Switch branch/tag" +msgstr "Переключить ветка/тэг" + +msgid "Tag" +msgid_plural "Tags" +msgstr[0] "Тэг" +msgstr[1] "Тэги" +msgstr[2] "Тэги" + +msgid "Tags" +msgstr "Тэги" + +msgid "Target Branch" +msgstr "Целевая ветка" + +msgid "" +"The coding stage shows the time from the first commit to creating the merge " +"request. The data will automatically be added here once you create your " +"first merge request." +msgstr "" + +msgid "The collection of events added to the data gathered for that stage." +msgstr "" + +msgid "The fork relationship has been removed." +msgstr "Связь форка удалена." + +msgid "" +"The issue stage shows the time it takes from creating an issue to assigning " +"the issue to a milestone, or add the issue to a list on your Issue Board. " +"Begin creating issues to see data for this stage." +msgstr "" +"Стадия обращения время, которое потребуется с момента создания обращения до " +"назначения обращению вехи, или добавления обращения в вашу доску обращений. " +"Начните создавать обращения, чтобы увидеть сведения для этой стадии. " + +msgid "The phase of the development lifecycle." +msgstr "Фаза жизненного цикла разработки." + +msgid "" +"The pipelines schedule runs pipelines in the future, repeatedly, for " +"specific branches or tags. Those scheduled pipelines will inherit limited " +"project access based on their associated user." +msgstr "" +"Расписание конвейеров запускает в будущем неоднократно конвейеры, для " +"определенных ветвей или тэгов. Запланированные конвейеры наследуют " +"ограничения на доступ к проекту на основе связанного с ними пользователя." + +msgid "" +"The planning stage shows the time from the previous step to pushing your " +"first commit. This time will be added automatically once you push your first " +"commit." +msgstr "" + +msgid "" +"The production stage shows the total time it takes between creating an issue " +"and deploying the code to production. The data will be automatically added " +"once you have completed the full idea to production cycle." +msgstr "" + +msgid "The project can be accessed by any logged in user." +msgstr "Доступ к проекту возможен любым зарегистрированным пользователем." + +msgid "The project can be accessed without any authentication." +msgstr "Доступ к проекту возможен без какой-либо проверки подлинности." + +msgid "The repository for this project does not exist." +msgstr "Репозиторий для этого проекта не существует." + +msgid "" +"The review stage shows the time from creating the merge request to merging " +"it. The data will automatically be added after you merge your first merge " +"request." +msgstr "" + +msgid "" +"The staging stage shows the time between merging the MR and deploying code " +"to the production environment. The data will be automatically added once you " +"deploy to production for the first time." +msgstr "" + +msgid "" +"The testing stage shows the time GitLab CI takes to run every pipeline for " +"the related merge request. The data will automatically be added after your " +"first pipeline finishes running." +msgstr "" + +msgid "The time taken by each data entry gathered by that stage." +msgstr "" + +msgid "" +"The value lying at the midpoint of a series of observed values. E.g., " +"between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 =" +" 6." +msgstr "" + +msgid "" +"This means you can not push code until you create an empty repository or " +"import existing one." +msgstr "" +"Это означает, что вы не можете пушить код, пока не создадите пустой " +"репозиторий или не импортируете существующий." + +msgid "Time before an issue gets scheduled" +msgstr "" + +msgid "Time before an issue starts implementation" +msgstr "" + +msgid "Time between merge request creation and merge/close" +msgstr "Время между созданием запроса слияния и слиянием / закрытием" + +msgid "Time until first merge request" +msgstr "" + +msgid "Timeago|%s days ago" +msgstr "Timeago|%s дн(я|ей) назад" + +msgid "Timeago|%s days remaining" +msgstr "Timeago|Осталось %s дн(я|ей)" + +msgid "Timeago|%s hours remaining" +msgstr "Timeago|Осталось %s часов" + +msgid "Timeago|%s minutes ago" +msgstr "Timeago|%s минут назад" + +msgid "Timeago|%s minutes remaining" +msgstr "Timeago|Осталось %s минут(а|ы)" + +msgid "Timeago|%s months ago" +msgstr "Timeago|%s минут(а|ы) назад" + +msgid "Timeago|%s months remaining" +msgstr "Timeago|Осталось %s месяцев(а)" + +msgid "Timeago|%s seconds remaining" +msgstr "Timeago|Осталось %s секунд(ы)" + +msgid "Timeago|%s weeks ago" +msgstr "Timeago|%s недель(и) назад" + +msgid "Timeago|%s weeks remaining" +msgstr "Timeago|Осталось %s недель(и)" + +msgid "Timeago|%s years ago" +msgstr "Timeago|%s лет/года назад" + +msgid "Timeago|%s years remaining" +msgstr "Timeago|Осталось %s лет/года" + +msgid "Timeago|1 day remaining" +msgstr "Timeago|Остался день" + +msgid "Timeago|1 hour remaining" +msgstr "Timeago|Остался час" + +msgid "Timeago|1 minute remaining" +msgstr "Timeago|Осталась одна минута" + +msgid "Timeago|1 month remaining" +msgstr "Timeago|Остался месяц" + +msgid "Timeago|1 week remaining" +msgstr "Timeago|Осталась неделя" + +msgid "Timeago|1 year remaining" +msgstr "Timeago|Остался год" + +msgid "Timeago|Past due" +msgstr "Timeago|Просрочено" + +msgid "Timeago|a day ago" +msgstr "Timeago|день назад" + +msgid "Timeago|a month ago" +msgstr "Timeago|месяц назад" + +msgid "Timeago|a week ago" +msgstr "Timeago|неделю назад" + +msgid "Timeago|a while" +msgstr "Timeago|какое-то время" + +msgid "Timeago|a year ago" +msgstr "Timeago|год назад" + +msgid "Timeago|about %s hours ago" +msgstr "Timeago|около %s часов назад" + +msgid "Timeago|about a minute ago" +msgstr "Timeago|около минуты назад" + +msgid "Timeago|about an hour ago" +msgstr "Timeago|около часа назад" + +msgid "Timeago|in %s days" +msgstr "Timeago|через %s дня(ей)" + +msgid "Timeago|in %s hours" +msgstr "Timeago|через %s часа(ов)" + +msgid "Timeago|in %s minutes" +msgstr "Timeago|через %s минут(ы)" + +msgid "Timeago|in %s months" +msgstr "Timeago|через %s месяц(а|ев)" + +msgid "Timeago|in %s seconds" +msgstr "Timeago|через %s секунд(ы)" + +msgid "Timeago|in %s weeks" +msgstr "Timeago|через %s недели" + +msgid "Timeago|in %s years" +msgstr "Timeago|через %s лет/года" + +msgid "Timeago|in 1 day" +msgstr "Timeago|через день" + +msgid "Timeago|in 1 hour" +msgstr "Timeago|через час" + +msgid "Timeago|in 1 minute" +msgstr "Timeago|через минуту" + +msgid "Timeago|in 1 month" +msgstr "Timeago|через месяц" + +msgid "Timeago|in 1 week" +msgstr "Timeago|через неделю" + +msgid "Timeago|in 1 year" +msgstr "Timeago|через год" + +msgid "Timeago|less than a minute ago" +msgstr "Timeago|менее чем минуту назад" + +msgid "Time|hr" +msgid_plural "Time|hrs" +msgstr[0] "ч" +msgstr[1] "ч" +msgstr[2] "ч" + +msgid "Time|min" +msgid_plural "Time|mins" +msgstr[0] "мин" +msgstr[1] "мин" +msgstr[2] "мин" + +msgid "Time|s" +msgstr "с" + +msgid "Total Time" +msgstr "Общее время" + +msgid "Total test time for all commits/merges" +msgstr "" + +msgid "Unstar" +msgstr "Снять отметку" + +msgid "Upload New File" +msgstr "Выгрузить новый файл" + +msgid "Upload file" +msgstr "Выгрузить файл" + +msgid "UploadLink|click to upload" +msgstr "UploadLink|кликните для выгрузки" + +msgid "Use your global notification setting" +msgstr "Используются глобальный настройки уведомлений" + +msgid "View open merge request" +msgstr "Просмотреть открытый запрос на слияние" + +msgid "VisibilityLevel|Internal" +msgstr "VisibilityLevel|Ограниченный" + +msgid "VisibilityLevel|Private" +msgstr "VisibilityLevel|Приватный" + +msgid "VisibilityLevel|Public" +msgstr "VisibilityLevel|Публичный" + +msgid "Want to see the data? Please ask an administrator for access." +msgstr "" + +msgid "We don't have enough data to show this stage." +msgstr "" + +msgid "Withdraw Access Request" +msgstr "Отменить запрос доступа" + +msgid "" +"You are going to remove %{project_name_with_namespace}.\n" +"Removed project CANNOT be restored!\n" +"Are you ABSOLUTELY sure?" +msgstr "" +"Вы хотите удалить %{project_name_with_namespace}.\n" +"Удаленный проект НЕ МОЖЕТ быть восстановлен!\n" +"Вы АБСОЛЮТНО уверены?" + +msgid "" +"You are going to remove the fork relationship to source project " +"%{forked_from_project}. Are you ABSOLUTELY sure?" +msgstr "" +"Вы собираетесь удалить связь форка с исходным проектом " +"%{forked_from_project}. Вы АБСОЛЮТНО уверены?" + +msgid "" +"You are going to transfer %{project_name_with_namespace} to another owner. " +"Are you ABSOLUTELY sure?" +msgstr "" +"Вы собираетесь передать проект %{project_name_with_namespace} другому " +"владельцу. Вы АБСОЛЮТНО уверены?" + +msgid "You can only add files when you are on a branch" +msgstr "Вы можете добавлять только файлы, когда находитесь в ветке" + +msgid "You have reached your project limit" +msgstr "Вы достигли ограничения в вашем проекте" + +msgid "You must sign in to star a project" +msgstr "Необходимо войти, чтобы оценить проект" + +msgid "You need permission." +msgstr "Вам нужно разрешение." + +msgid "You will not get any notifications via email" +msgstr "Вы не получите никаких уведомлений по электронной почте" + +msgid "You will only receive notifications for the events you choose" +msgstr "Вы будете получать уведомления только о выбранных вами событиях" + +msgid "" +"You will only receive notifications for threads you have participated in" +msgstr "" +"Вы будете получать уведомления только о тех тредах, в которых вы участвовали" + +msgid "You will receive notifications for any activity" +msgstr "Вы будете получать уведомления о любых действиях" + +msgid "" +"You will receive notifications only for comments in which you were " +"@mentioned" +msgstr "" +"Вы будете получать уведомления только для комментариев, в которых вы были " +"@упомянуты" + +msgid "" +"You won't be able to pull or push project code via %{protocol} until you " +"%{set_password_link} on your account" +msgstr "" +"Вы не сможете получать и отправлять код проекта через %{protocol} пока " +"%{set_password_link} в ваш аккаунт" + +msgid "" +"You won't be able to pull or push project code via SSH until you " +"%{add_ssh_key_link} to your profile" +msgstr "" +"Вы не сможете получать и отправлять код проекта через SSH пока " +"%{add_ssh_key_link} в ваш профиль." + +msgid "Your name" +msgstr "Ваше имя" + +msgid "day" +msgid_plural "days" +msgstr[0] "день" +msgstr[1] "дни" +msgstr[2] "дни" + +msgid "new merge request" +msgstr "новый запрос на слияние" + +msgid "notification emails" +msgstr "email для уведомлений" + +msgid "parent" +msgid_plural "parents" +msgstr[0] "источник" +msgstr[1] "источники" +msgstr[2] "источники" + diff --git a/locale/ru/gitlab.po.time_stamp b/locale/ru/gitlab.po.time_stamp new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/locale/ru/gitlab.po.time_stamp diff --git a/locale/uk/gitlab.po b/locale/uk/gitlab.po new file mode 100644 index 00000000000..59a7eb6e1b3 --- /dev/null +++ b/locale/uk/gitlab.po @@ -0,0 +1,1234 @@ +# Андрей Витюк <andruwa13@gmail.com>, 2017. #zanata +# Huang Tao <htve@outlook.com>, 2017. #zanata +msgid "" +msgstr "" +"Project-Id-Version: gitlab 1.0.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-06-28 13:32+0200\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"PO-Revision-Date: 2017-07-12 09:05-0400\n" +"Last-Translator: Андрей Витюк <andruwa13@gmail.com>\n" +"Language-Team: Ukrainian (https://translate.zanata.org/project/view/GitLab)\n" +"Language: uk\n" +"X-Generator: Zanata 3.9.6\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +msgid "%s additional commit has been omitted to prevent performance issues." +msgid_plural "" +"%s additional commits have been omitted to prevent performance issues." +msgstr[0] "" +"%s доданий Комміт був виключений для запобігання проблем з продуктивністю." +msgstr[1] "" +"%s доданих коммітів були виключені для запобігання проблем з продуктивністю." +msgstr[2] "" +"%s доданих коммітів були виключені для запобігання проблем з продуктивністю." + +msgid "%d commit" +msgid_plural "%d commits" +msgstr[0] "%d комміт" +msgstr[1] "%d комміта" +msgstr[2] "%d коммітів" + +msgid "%{commit_author_link} committed %{commit_timeago}" +msgstr "%{commit_author_link} комміт %{commit_timeago}" + +msgid "1 pipeline" +msgid_plural "%d pipelines" +msgstr[0] "1 конвеєр" +msgstr[1] "%d конвеєра" +msgstr[2] "%d конвеєрів" + +msgid "A collection of graphs regarding Continuous Integration" +msgstr "Це набір графічних елементів для безперервної інтеграції" + +msgid "About auto deploy" +msgstr "Про авто розгортання" + +msgid "Active" +msgstr "Активний" + +msgid "Activity" +msgstr "Активність" + +msgid "Add Changelog" +msgstr "Додати список змін (Changelog)" + +msgid "Add Contribution guide" +msgstr "Додати керівництво для контрибуторів" + +msgid "Add License" +msgstr "Додати ліцензію" + +msgid "Add an SSH key to your profile to pull or push via SSH." +msgstr "" +"Додати SSH ключа в свій профіль, щоб мати можливість завантажити чи " +"надіслати зміни через SSH." + +msgid "Add new directory" +msgstr "Додати новий каталог" + +msgid "Archived project! Repository is read-only" +msgstr "Заархівований проект! Репозиторій доступний лише для читання" + +msgid "Are you sure you want to delete this pipeline schedule?" +msgstr "Ви впевнені, що хочете видалити цей розклад для Конвеєра?" + +msgid "Attach a file by drag & drop or %{upload_link}" +msgstr "Прикріпити файл за допомогою перетягування або %{upload_link}" + +msgid "Branch" +msgid_plural "Branches" +msgstr[0] "Гілка" +msgstr[1] "Гілки" +msgstr[2] "Гілок" + +msgid "" +"Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, " +"choose a GitLab CI Yaml template and commit your changes. " +"%{link_to_autodeploy_doc}" +msgstr "" +"Гілка <strong>%{branch_name}</strong> створена. Для настройки автоматичного " +"розгортання виберіть GitLab CI Yaml-шаблон і закоммітьте зміни. " +"%{link_to_autodeploy_doc}" + +msgid "BranchSwitcherPlaceholder|Search branches" +msgstr "Пошук гілок" + +msgid "BranchSwitcherTitle|Switch branch" +msgstr "Переключити гілку" + +msgid "Branches" +msgstr "Гілки" + +msgid "Browse Directory" +msgstr "Переглянути каталог" + +msgid "Browse File" +msgstr "Переглянути файл" + +msgid "Browse Files" +msgstr "Перегляд файлів" + +msgid "Browse files" +msgstr "Перегляд файлів" + +msgid "ByAuthor|by" +msgstr "від" + +msgid "CI configuration" +msgstr "Налаштування CI" + +msgid "Cancel" +msgstr "Скасувати" + +msgid "ChangeTypeActionLabel|Pick into branch" +msgstr "Вибрати в гілці" + +msgid "ChangeTypeActionLabel|Revert in branch" +msgstr "Скасувати у гілці" + +msgid "ChangeTypeAction|Cherry-pick" +msgstr "Cherry-pick" + +msgid "ChangeTypeAction|Revert" +msgstr "Скасувати" + +msgid "Changelog" +msgstr "Список змін (Changelog)" + +msgid "Charts" +msgstr "Графіки" + +msgid "Cherry-pick this commit" +msgstr "Cherry-pick в цьому комміті" + +msgid "Cherry-pick this merge request" +msgstr "Cherry-pick в цьому запиті на злиття" + +msgid "CiStatusLabel|canceled" +msgstr "скасовано" + +msgid "CiStatusLabel|created" +msgstr "створено" + +msgid "CiStatusLabel|failed" +msgstr "невдало" + +msgid "CiStatusLabel|manual action" +msgstr "вручну" + +msgid "CiStatusLabel|passed" +msgstr "виконано" + +msgid "CiStatusLabel|passed with warnings" +msgstr "виконано з попередженнями" + +msgid "CiStatusLabel|pending" +msgstr "в очікуванні" + +msgid "CiStatusLabel|skipped" +msgstr "пропущено" + +msgid "CiStatusLabel|waiting for manual action" +msgstr "Очікування ручних дій" + +msgid "CiStatusText|blocked" +msgstr "заблоковано" + +msgid "CiStatusText|canceled" +msgstr "скасовано" + +msgid "CiStatusText|created" +msgstr "створено" + +msgid "CiStatusText|failed" +msgstr "невдало" + +msgid "CiStatusText|manual" +msgstr "вручну" + +msgid "CiStatusText|passed" +msgstr "виконано" + +msgid "CiStatusText|pending" +msgstr "в очікуванні" + +msgid "CiStatusText|skipped" +msgstr "пропущено" + +msgid "CiStatus|running" +msgstr "виконується" + +msgid "Commit" +msgid_plural "Commits" +msgstr[0] "Комміт" +msgstr[1] "Комміта" +msgstr[2] "Коммітів" + +msgid "Commit duration in minutes for last 30 commits" +msgstr "Комміт тривалість у хвилинах за останні 30 коммітів" + +msgid "Commit message" +msgstr "Комміт повідомлення" + +msgid "CommitBoxTitle|Commit" +msgstr "Комміт" + +msgid "CommitMessage|Add %{file_name}" +msgstr "Додати %{file_name}" + +msgid "Commits" +msgstr "Комміти" + +msgid "Commits feed" +msgstr "Канал коммітів" + +msgid "Commits|History" +msgstr "Історія" + +msgid "Committed by" +msgstr "Комміт від" + +msgid "Compare" +msgstr "Порівняти" + +msgid "Contribution guide" +msgstr "Керівництво контрибуторів" + +msgid "Contributors" +msgstr "Контрибутори" + +msgid "Copy URL to clipboard" +msgstr "Скопіювати URL в буфер обміну" + +msgid "Copy commit SHA to clipboard" +msgstr "Скопіювати ідентифікатор в буфер обміну" + +msgid "Create New Directory" +msgstr "Створити новий каталог" + +msgid "" +"Create a personal access token on your account to pull or push via " +"%{protocol}." +msgstr "" +"Створити токен доступу для вашого аккауета, щоб відправляти або отримувати " +"через %{protocol}." + +msgid "Create directory" +msgstr "Створити каталог" + +msgid "Create empty bare repository" +msgstr "Створити порожній репозиторій" + +msgid "Create merge request" +msgstr "Створити запит на злиття" + +msgid "Create new..." +msgstr "Створити..." + +msgid "CreateNewFork|Fork" +msgstr "Форк" + +msgid "CreateTag|Tag" +msgstr "Тег" + +msgid "CreateTokenToCloneLink|create a personal access token" +msgstr "Створити токен для особистого доступу" + +msgid "Cron Timezone" +msgstr "Часовий пояс Cron" + +msgid "Cron syntax" +msgstr "Синтаксис Cron" + +msgid "Custom notification events" +msgstr "Користувацькі налаштування повідомлень про події" + +msgid "" +"Custom notification levels are the same as participating levels. With custom " +"notification levels you will also receive notifications for select events. " +"To find out more, check out %{notification_link}." +msgstr "" +"Спеціальні рівні повідомлення співпадають з рівнем участі. За допомогою " +"спеціальних рівнів сповіщень ви також отримуватимете сповіщення про вибрані " +"події. Щоб дізнатись більше, перегляньте %{notification_link}." + +msgid "Cycle Analytics" +msgstr "Аналіз циклу" + +msgid "" +"Cycle Analytics gives an overview of how much time it takes to go from idea " +"to production in your project." +msgstr "" +"Аналітика циклу дає огляд того, скільки часу потрібно, щоб перейти від ідеї " +"до виробництва у вашому проекті." + +msgid "CycleAnalyticsStage|Code" +msgstr "Код" + +msgid "CycleAnalyticsStage|Issue" +msgstr "Проблема" + +msgid "CycleAnalyticsStage|Plan" +msgstr "Планування" + +msgid "CycleAnalyticsStage|Production" +msgstr "ПРОД" + +msgid "CycleAnalyticsStage|Review" +msgstr "Затвердження" + +msgid "CycleAnalyticsStage|Staging" +msgstr "ДЕВ" + +msgid "CycleAnalyticsStage|Test" +msgstr "Тестування" + +msgid "Define a custom pattern with cron syntax" +msgstr "Визначте власний шаблон за допомогою синтаксису cron" + +msgid "Delete" +msgstr "Видалити" + +msgid "Deploy" +msgid_plural "Deploys" +msgstr[0] "Розгортання" +msgstr[1] "Розгортання" +msgstr[2] "Розгортань" + +msgid "Description" +msgstr "Опис" + +msgid "Directory name" +msgstr "Ім'я каталогу" + +msgid "Don't show again" +msgstr "Не показувати знову" + +msgid "Download" +msgstr "Завантажити" + +msgid "Download tar" +msgstr "Завантажити в форматі tar" + +msgid "Download tar.bz2" +msgstr "Завантажити в форматі tar.bz2" + +msgid "Download tar.gz" +msgstr "Завантажити в форматі tar.gz" + +msgid "Download zip" +msgstr "Завантажити в форматі zip" + +msgid "DownloadArtifacts|Download" +msgstr "Завантажити" + +msgid "DownloadCommit|Email Patches" +msgstr "Email-патчи" + +msgid "DownloadCommit|Plain Diff" +msgstr "Plain Diff" + +msgid "DownloadSource|Download" +msgstr "Завантажити" + +msgid "Edit" +msgstr "Редагувати" + +msgid "Edit Pipeline Schedule %{id}" +msgstr "Редагувати Розклад Конвеєра % {id}" + +msgid "Every day (at 4:00am)" +msgstr "Кожен день (в 4:00 ранку)" + +msgid "Every month (on the 1st at 4:00am)" +msgstr "Кожен місяць (1-го числа о 4:00 ранку)" + +msgid "Every week (Sundays at 4:00am)" +msgstr "Щотижня (в неділю о 4:00 ранку)" + +msgid "Failed to change the owner" +msgstr "Не вдалося змінити власника" + +msgid "Failed to remove the pipeline schedule" +msgstr "Не вдалося видалити розклад Конвеєра" + +msgid "Files" +msgstr "Файли" + +msgid "Filter by commit message" +msgstr "Фільтрувати повідомлення коммітів" + +msgid "Find by path" +msgstr "Пошук по шляху" + +msgid "Find file" +msgstr "Знайти файл" + +msgid "FirstPushedBy|First" +msgstr "Перший" + +msgid "FirstPushedBy|pushed by" +msgstr "Надіслані зміни від" + +msgid "Fork" +msgid_plural "Forks" +msgstr[0] "Форк" +msgstr[1] "Форки" +msgstr[2] "Форків" + +msgid "ForkedFromProjectPath|Forked from" +msgstr "Форк від" + +msgid "From issue creation until deploy to production" +msgstr "З моменту створення проблеми до розгортання на ПРОД" + +msgid "From merge request merge until deploy to production" +msgstr "З об'єднання запиту злиття до розгортання на ПРОД" + +msgid "Go to your fork" +msgstr "Перейти до вашого форку" + +msgid "GoToYourFork|Fork" +msgstr "Форк" + +msgid "Home" +msgstr "Початок" + +msgid "Housekeeping successfully started" +msgstr "Очищення успішно розпочато" + +msgid "Import repository" +msgstr "Імпорт репозеторія" + +msgid "Interval Pattern" +msgstr "Шаблон інтервалу" + +msgid "Introducing Cycle Analytics" +msgstr "Представляємо аналітику циклу" + +msgid "Jobs for last month" +msgstr "Завдання за останній місяць" + +msgid "Jobs for last week" +msgstr "Завдання за останній тиждень" + +msgid "Jobs for last year" +msgstr "Завдання за останній рік" + +msgid "LFSStatus|Disabled" +msgstr "Вимкнено" + +msgid "LFSStatus|Enabled" +msgstr "Увімкнено" + +msgid "Last %d day" +msgid_plural "Last %d days" +msgstr[0] "Останній %d день" +msgstr[1] "Останніх %d дні" +msgstr[2] "Останніх %d днів" + +msgid "Last Pipeline" +msgstr "Останній Конвеєр" + +msgid "Last Update" +msgstr "Останнє оновлення" + +msgid "Last commit" +msgstr "Останній комміт" + +msgid "Learn more in the" +msgstr "Дізнайтесь більше" + +msgid "Learn more in the|pipeline schedules documentation" +msgstr "Детальніше в документації по розкладами конвеєрів" + +msgid "Leave group" +msgstr "Залишити групу" + +msgid "Leave project" +msgstr "Залишити проект" + +msgid "Limited to showing %d event at most" +msgid_plural "Limited to showing %d events at most" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "Median" +msgstr "Медіана" + +msgid "MissingSSHKeyWarningLink|add an SSH key" +msgstr "додати SSH ключ" + +msgid "New Issue" +msgid_plural "New Issues" +msgstr[0] "Нова проблема" +msgstr[1] "Нові проблеми" +msgstr[2] "Новах проблем" + +msgid "New Pipeline Schedule" +msgstr "Новий розклад Конвеєра" + +msgid "New branch" +msgstr "Нова гілка" + +msgid "New directory" +msgstr "Новий каталог" + +msgid "New file" +msgstr "Новий файл" + +msgid "New issue" +msgstr "Нова проблема" + +msgid "New merge request" +msgstr "Новий запит на злиття" + +msgid "New schedule" +msgstr "Новий Розклад" + +msgid "New snippet" +msgstr "Новий сніппет" + +msgid "New tag" +msgstr "Новий тег" + +msgid "No repository" +msgstr "Немає репозеторія" + +msgid "No schedules" +msgstr "немає Розкладів" + +msgid "Not available" +msgstr "Недоступний" + +msgid "Not enough data" +msgstr "Недостатньо даних" + +msgid "Notification events" +msgstr "Повідомлення про події" + +msgid "NotificationEvent|Close issue" +msgstr "Проблема закрита" + +msgid "NotificationEvent|Close merge request" +msgstr "Запит на об'єднання закритий" + +msgid "NotificationEvent|Failed pipeline" +msgstr "Невдача в конвеєрі" + +msgid "NotificationEvent|Merge merge request" +msgstr "Об'єднати запит на злиття" + +msgid "NotificationEvent|New issue" +msgstr "Нова проблема" + +msgid "NotificationEvent|New merge request" +msgstr "Новий запит на злиття" + +msgid "NotificationEvent|New note" +msgstr "Нова нотатка" + +msgid "NotificationEvent|Reassign issue" +msgstr "Перепризначити проблему" + +msgid "NotificationEvent|Reassign merge request" +msgstr "Перепризначити запит на злиття" + +msgid "NotificationEvent|Reopen issue" +msgstr "Повторне відкриття проблему" + +msgid "NotificationEvent|Successful pipeline" +msgstr "Успішно в Конвеєрі" + +msgid "NotificationLevel|Custom" +msgstr "Власні" + +msgid "NotificationLevel|Disabled" +msgstr "Вимкнено" + +msgid "NotificationLevel|Global" +msgstr "Загальні" + +msgid "NotificationLevel|On mention" +msgstr "Коли вас згадують" + +msgid "NotificationLevel|Participate" +msgstr "Берете участь" + +msgid "NotificationLevel|Watch" +msgstr "Відстежувати" + +msgid "OfSearchInADropdown|Filter" +msgstr "Фільтр" + +msgid "OpenedNDaysAgo|Opened" +msgstr "Відкрито" + +msgid "Options" +msgstr "Параметри" + +msgid "Owner" +msgstr "Власник" + +msgid "Pipeline" +msgstr "Конвеєр" + +msgid "Pipeline Health" +msgstr "Стан Конвеєра" + +msgid "Pipeline Schedule" +msgstr "Розклад Конвеєра" + +msgid "Pipeline Schedules" +msgstr "Розклади Конвеєрів" + +msgid "PipelineCharts|Failed:" +msgstr "Не вдалося:" + +msgid "PipelineCharts|Overall statistics" +msgstr "Загальна статистика" + +msgid "PipelineCharts|Success ratio:" +msgstr "Коефіцієнт успіху:" + +msgid "PipelineCharts|Successful:" +msgstr "Успішні:" + +msgid "PipelineCharts|Total:" +msgstr "Всього:" + +msgid "PipelineSchedules|Activated" +msgstr "Активовано" + +msgid "PipelineSchedules|Active" +msgstr "Активні" + +msgid "PipelineSchedules|All" +msgstr "Всі" + +msgid "PipelineSchedules|Inactive" +msgstr "Неактивні" + +msgid "PipelineSchedules|Next Run" +msgstr "Наступний запуск" + +msgid "PipelineSchedules|None" +msgstr "Немає" + +msgid "PipelineSchedules|Provide a short description for this pipeline" +msgstr "Задайте короткий опис для цього Конвеєру" + +msgid "PipelineSchedules|Take ownership" +msgstr "Стати власником" + +msgid "PipelineSchedules|Target" +msgstr "Ціль" + +msgid "PipelineSheduleIntervalPattern|Custom" +msgstr "Власні" + +msgid "Pipelines" +msgstr "Конвеєри" + +msgid "Pipelines charts" +msgstr "Чарти Конвеєрів" + +msgid "Pipeline|all" +msgstr "всі" + +msgid "Pipeline|success" +msgstr "успіх" + +msgid "Pipeline|with stage" +msgstr "зі стадією" + +msgid "Pipeline|with stages" +msgstr "зі стадіями" + +msgid "Project '%{project_name}' queued for deletion." +msgstr "Проект '%{project_name}' доданий в чергу на видалення." + +msgid "Project '%{project_name}' was successfully created." +msgstr "Проект '%{project_name}' успішно створений." + +msgid "Project '%{project_name}' was successfully updated." +msgstr "Проект '%{project_name}' успішно оновлено." + +msgid "Project '%{project_name}' will be deleted." +msgstr "Проект '%{project_name}' видалений." + +msgid "Project access must be granted explicitly to each user." +msgstr "Доступ до проекту повинен надаватися кожному користувачеві." + +msgid "Project export could not be deleted." +msgstr "Неможливо видалити експорт проекту." + +msgid "Project export has been deleted." +msgstr "Експорт проекту видалений." + +msgid "" +"Project export link has expired. Please generate a new export from your " +"project settings." +msgstr "" +"Закінчився термін дії посилання на проект. Створіть новий експорт в ваших " +"настройках проекту." + +msgid "Project export started. A download link will be sent by email." +msgstr "" +"Розпочато експорт проекту. Посилання для скачування буде надіслана " +"електронною поштою." + +msgid "Project home" +msgstr "Домашня сторінка проекту" + +msgid "ProjectFeature|Disabled" +msgstr "Вимкнено" + +msgid "ProjectFeature|Everyone with access" +msgstr "Все з доступом" + +msgid "ProjectFeature|Only team members" +msgstr "Тільки члени команди" + +msgid "ProjectFileTree|Name" +msgstr "Ім'я" + +msgid "ProjectLastActivity|Never" +msgstr "Ніколи" + +msgid "ProjectLifecycle|Stage" +msgstr "Етап" + +msgid "ProjectNetworkGraph|Graph" +msgstr "Графік" + +msgid "Read more" +msgstr "Докладніше" + +msgid "Readme" +msgstr "Прочитай Мене" + +msgid "RefSwitcher|Branches" +msgstr "Гілки" + +msgid "RefSwitcher|Tags" +msgstr "Теги" + +msgid "Related Commits" +msgstr "Пов'язані Комміти" + +msgid "Related Deployed Jobs" +msgstr "Пов’язані розгорнуті задачі (Jobs)" + +msgid "Related Issues" +msgstr "Пов’язані Проблеми (Issues)" + +msgid "Related Jobs" +msgstr "Пов’язані Задачі (Jobs)" + +msgid "Related Merge Requests" +msgstr "Пов'язані запити на злиття" + +msgid "Related Merged Requests" +msgstr "Пов'язані об'єднані запити" + +msgid "Remind later" +msgstr "Нагадати пізніше" + +msgid "Remove project" +msgstr "Видалити проект" + +msgid "Request Access" +msgstr "Запит доступу" + +msgid "Revert this commit" +msgstr "Скасувати цей комміт" + +msgid "Revert this merge request" +msgstr "Скасувати цей запит на злиття" + +msgid "Save pipeline schedule" +msgstr "Зберегти Розклад Конвеєра" + +msgid "Schedule a new pipeline" +msgstr "Розклад нового конвеєра" + +msgid "Scheduling Pipelines" +msgstr "Планування конвеєрів" + +msgid "Search branches and tags" +msgstr "Пошук гілок та тегів" + +msgid "Select Archive Format" +msgstr "Виберіть формат архіву" + +msgid "Select a timezone" +msgstr "Вибрати часовий пояс" + +msgid "Select target branch" +msgstr "Вибір цільової гілки" + +msgid "Set a password on your account to pull or push via %{protocol}." +msgstr "" +"Встановіть пароль свого облікового запису, щоб відправляти або отримувати " +"код через %{protocol}." + +msgid "Set up CI" +msgstr "Налаштування CI" + +msgid "Set up Koding" +msgstr "Налаштування Koding" + +msgid "Set up auto deploy" +msgstr "Налаштування автоматичне розгортання" + +msgid "SetPasswordToCloneLink|set a password" +msgstr "встановити пароль" + +msgid "Showing %d event" +msgid_plural "Showing %d events" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "Source code" +msgstr "Код" + +msgid "StarProject|Star" +msgstr "Старт" + +msgid "Start a %{new_merge_request} with these changes" +msgstr "Почати %{new_merge_request} з цих змін" + +msgid "Switch branch/tag" +msgstr "тег" + +msgid "Tag" +msgid_plural "Tags" +msgstr[0] "Тег" +msgstr[1] "Теги" +msgstr[2] "Тегів" + +msgid "Tags" +msgstr "Теги" + +msgid "Target Branch" +msgstr "Цільова гілка" + +msgid "" +"The coding stage shows the time from the first commit to creating the merge " +"request. The data will automatically be added here once you create your " +"first merge request." +msgstr "" + +msgid "The collection of events added to the data gathered for that stage." +msgstr "" + +msgid "The fork relationship has been removed." +msgstr "Зв'язок форка видалена." + +msgid "" +"The issue stage shows the time it takes from creating an issue to assigning " +"the issue to a milestone, or add the issue to a list on your Issue Board. " +"Begin creating issues to see data for this stage." +msgstr "" +"Етап випуску показує, скільки часу потрібно від створення проблеми до " +"присвоєння випуску, або додавання проблеми в вашу дошку проблем. Почніть " +"створювати проблеми, щоб переглядати дані для цього етапу." + +msgid "The phase of the development lifecycle." +msgstr "Фаза життєвого циклу розробки." + +msgid "" +"The pipelines schedule runs pipelines in the future, repeatedly, for " +"specific branches or tags. Those scheduled pipelines will inherit limited " +"project access based on their associated user." +msgstr "" +"Розклад конвеєрів запускає в майбутньому конвеєри, для певних гілок або " +"тегів. Заплановані конвеєри успадковують обмеження на доступ до проекту на " +"основі пов'язаного з ними користувача." + +msgid "" +"The planning stage shows the time from the previous step to pushing your " +"first commit. This time will be added automatically once you push your first " +"commit." +msgstr "" + +msgid "" +"The production stage shows the total time it takes between creating an issue " +"and deploying the code to production. The data will be automatically added " +"once you have completed the full idea to production cycle." +msgstr "" + +msgid "The project can be accessed by any logged in user." +msgstr "Доступ до проекту можливий будь-яким зареєстрованим користувачем." + +msgid "The project can be accessed without any authentication." +msgstr "Доступ до проекту можливий без будь-якої перевірки автентичності." + +msgid "The repository for this project does not exist." +msgstr "Репозиторій для цього проекту не існує." + +msgid "" +"The review stage shows the time from creating the merge request to merging " +"it. The data will automatically be added after you merge your first merge " +"request." +msgstr "" + +msgid "" +"The staging stage shows the time between merging the MR and deploying code " +"to the production environment. The data will be automatically added once you " +"deploy to production for the first time." +msgstr "" + +msgid "" +"The testing stage shows the time GitLab CI takes to run every pipeline for " +"the related merge request. The data will automatically be added after your " +"first pipeline finishes running." +msgstr "" + +msgid "The time taken by each data entry gathered by that stage." +msgstr "" + +msgid "" +"The value lying at the midpoint of a series of observed values. E.g., " +"between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 =" +" 6." +msgstr "" + +msgid "" +"This means you can not push code until you create an empty repository or " +"import existing one." +msgstr "" +"Це означає, що ви не можете відправляти код, поки не створите порожній " +"репозиторій або НЕ імпортуєте існуючий." + +msgid "Time before an issue gets scheduled" +msgstr "" + +msgid "Time before an issue starts implementation" +msgstr "" + +msgid "Time between merge request creation and merge/close" +msgstr "Час між створенням запиту злиття і злиттям або закриттям" + +msgid "Time until first merge request" +msgstr "Час до першого запиту на злиття" + +msgid "Timeago|%s days ago" +msgstr "%s днів тому" + +msgid "Timeago|%s days remaining" +msgstr "%s днів, що залишилися" + +msgid "Timeago|%s hours remaining" +msgstr "%s годин, що залишилися" + +msgid "Timeago|%s minutes ago" +msgstr "%s хвилин тому" + +msgid "Timeago|%s minutes remaining" +msgstr "%s хвилини залишитися" + +msgid "Timeago|%s months ago" +msgstr "%s місяців тому" + +msgid "Timeago|%s months remaining" +msgstr "%s місяці, що залишилися" + +msgid "Timeago|%s seconds remaining" +msgstr "%s секунд, що залишаються" + +msgid "Timeago|%s weeks ago" +msgstr "%s тижнів тому" + +msgid "Timeago|%s weeks remaining" +msgstr "%s тижнів залишилися" + +msgid "Timeago|%s years ago" +msgstr "%s років тому" + +msgid "Timeago|%s years remaining" +msgstr "%s роки, що залишилися" + +msgid "Timeago|1 day remaining" +msgstr "Залишився 1 день" + +msgid "Timeago|1 hour remaining" +msgstr "Залишилась 1 година" + +msgid "Timeago|1 minute remaining" +msgstr "Залишилась 1 хвилина" + +msgid "Timeago|1 month remaining" +msgstr "Залишився 1 місяць" + +msgid "Timeago|1 week remaining" +msgstr "Залишився 1 тиждень" + +msgid "Timeago|1 year remaining" +msgstr "Залишився 1 рік" + +msgid "Timeago|Past due" +msgstr "Прострочені" + +msgid "Timeago|a day ago" +msgstr "годин тому" + +msgid "Timeago|a month ago" +msgstr "місяць тому" + +msgid "Timeago|a week ago" +msgstr "тиждень тому" + +msgid "Timeago|a while" +msgstr "деякий час назад" + +msgid "Timeago|a year ago" +msgstr "рік тому" + +msgid "Timeago|about %s hours ago" +msgstr "Близько %s годин тому" + +msgid "Timeago|about a minute ago" +msgstr "Близько хвилини тому" + +msgid "Timeago|about an hour ago" +msgstr "Близько години тому" + +msgid "Timeago|in %s days" +msgstr "через %s днїв" + +msgid "Timeago|in %s hours" +msgstr "через %s години" + +msgid "Timeago|in %s minutes" +msgstr "через %s хвилини" + +msgid "Timeago|in %s months" +msgstr "через %s місяців" + +msgid "Timeago|in %s seconds" +msgstr "через %s секунд" + +msgid "Timeago|in %s weeks" +msgstr "через %s тижні" + +msgid "Timeago|in %s years" +msgstr "через %s років" + +msgid "Timeago|in 1 day" +msgstr "через день" + +msgid "Timeago|in 1 hour" +msgstr "через годину" + +msgid "Timeago|in 1 minute" +msgstr "через хвилину" + +msgid "Timeago|in 1 month" +msgstr "через місяць" + +msgid "Timeago|in 1 week" +msgstr "через тиждень" + +msgid "Timeago|in 1 year" +msgstr "через рік" + +msgid "Timeago|less than a minute ago" +msgstr "менш хвилини тому" + +msgid "Time|hr" +msgid_plural "Time|hrs" +msgstr[0] "Година" +msgstr[1] "Годині" +msgstr[2] "Годин" + +msgid "Time|min" +msgid_plural "Time|mins" +msgstr[0] "хвилина" +msgstr[1] "хвилині" +msgstr[2] "хвилин" + +msgid "Time|s" +msgstr "секунда" + +msgid "Total Time" +msgstr "Загальний час" + +msgid "Total test time for all commits/merges" +msgstr "Загальний час, щоб перевірити всі фіксації/злиття" + +msgid "Unstar" +msgstr "Зняти позначку" + +msgid "Upload New File" +msgstr "Завантажити новий файл" + +msgid "Upload file" +msgstr "Завантажити файл" + +msgid "UploadLink|click to upload" +msgstr "Натисніть, щоб завантажити" + +msgid "Use your global notification setting" +msgstr "Використовуються глобальний налаштування повідомлень" + +msgid "View open merge request" +msgstr "Перегляд відкритих запитів на злиття" + +msgid "VisibilityLevel|Internal" +msgstr "Внутрішній" + +msgid "VisibilityLevel|Private" +msgstr "Приватний" + +msgid "VisibilityLevel|Public" +msgstr "Публічний" + +msgid "Want to see the data? Please ask an administrator for access." +msgstr "Хочете побачити дані? Будь ласка, попросить у адміністратора доступ." + +msgid "We don't have enough data to show this stage." +msgstr "Ми не маємо достатньо даних для показу цього етапу." + +msgid "Withdraw Access Request" +msgstr "Скасувати запит доступу" + +msgid "" +"You are going to remove %{project_name_with_namespace}.\n" +"Removed project CANNOT be restored!\n" +"Are you ABSOLUTELY sure?" +msgstr "" +"Ви хочете видалити %{project_name_with_namespace}.\n" +"Видалений проект НЕ МОЖЕ бути відновлений!\n" +"Ви АБСОЛЮТНО впевнені?" + +msgid "" +"You are going to remove the fork relationship to source project " +"%{forked_from_project}. Are you ABSOLUTELY sure?" +msgstr "" +"Ви збираєтеся видалити зв'язок з форка з вихідним проектом " +"%{forked_from_project}. Ви АБСОЛЮТНО впевнені?" + +msgid "" +"You are going to transfer %{project_name_with_namespace} to another owner. " +"Are you ABSOLUTELY sure?" +msgstr "" +"Ви збираєтеся передати проект %{project_name_with_namespace} іншому власнику." +" Ви АБСОЛЮТНО впевнені?" + +msgid "You can only add files when you are on a branch" +msgstr "Ви можете додавати тільки файли, коли перебуваєте в гілці" + +msgid "You have reached your project limit" +msgstr "Ви досягли обмеження в вашому проекті" + +msgid "You must sign in to star a project" +msgstr "Необхідно увійти, щоб оцінити проект" + +msgid "You need permission." +msgstr "Вам потрібен дозвіл" + +msgid "You will not get any notifications via email" +msgstr "Ви не отримаєте ніяких повідомлень по електронній пошті" + +msgid "You will only receive notifications for the events you choose" +msgstr "Ви будете отримувати повідомлення тільки про обрані вами події" + +msgid "" +"You will only receive notifications for threads you have participated in" +msgstr "" +"Ви будете отримувати повідомлення тільки про тих темах, в яких ви брали " +"участь" + +msgid "You will receive notifications for any activity" +msgstr "Ви будете отримувати повідомлення про будь-які дії" + +msgid "" +"You will receive notifications only for comments in which you were " +"@mentioned" +msgstr "" +"Ви будете отримувати повідомлення тільки для коментарів, в яких ви були " +"@згадані" + +msgid "" +"You won't be able to pull or push project code via %{protocol} until you " +"%{set_password_link} on your account" +msgstr "" +"Ви не зможете отримувати і відправляти код проекту через %{protocol} поки " +"%{set_password_link} в ваш аккаунт" + +msgid "" +"You won't be able to pull or push project code via SSH until you " +"%{add_ssh_key_link} to your profile" +msgstr "" +"Ви не зможете отримувати і відправляти код проекту через SSH поки " +"%{add_ssh_key_link} в ваш профіль." + +msgid "Your name" +msgstr "Ваше ім'я" + +msgid "day" +msgid_plural "days" +msgstr[0] "день" +msgstr[1] "дні" +msgstr[2] "днів" + +msgid "new merge request" +msgstr "Новий запит на злиття" + +msgid "notification emails" +msgstr "Повідомлення електронною поштою" + +msgid "parent" +msgid_plural "parents" +msgstr[0] "джерело" +msgstr[1] "джерела" +msgstr[2] "джерел" + diff --git a/locale/uk/gitlab.po.time_stamp b/locale/uk/gitlab.po.time_stamp new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/locale/uk/gitlab.po.time_stamp diff --git a/locale/zh_CN/gitlab.po b/locale/zh_CN/gitlab.po index 2f21aae2899..47b72d7be1a 100644 --- a/locale/zh_CN/gitlab.po +++ b/locale/zh_CN/gitlab.po @@ -4,21 +4,21 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-06-19 15:50-0500\n" +"POT-Creation-Date: 2017-07-05 08:50-0500\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-06-27 03:18-0400\n" +"PO-Revision-Date: 2017-07-12 06:23-0400\n" "Last-Translator: Huang Tao <htve@outlook.com>\n" "Language-Team: Chinese (China) (https://translate.zanata.org/project/view/GitLab)\n" "Language: zh-CN\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=1; plural=0\n" -msgid "%d additional commit has been omitted to prevent performance issues." +msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "" -"%d additional commits have been omitted to prevent performance issues." -msgstr[0] "为提高页面加载速度及性能,已省略了 %d 次提交。" +"%s additional commits have been omitted to prevent performance issues." +msgstr[0] "为提高页面加载速度及性能,已省略了 %s 次提交。" msgid "%d commit" msgid_plural "%d commits" @@ -27,6 +27,13 @@ msgstr[0] "%d 次提交" msgid "%{commit_author_link} committed %{commit_timeago}" msgstr "由 %{commit_author_link} 提交于 %{commit_timeago}" +msgid "1 pipeline" +msgid_plural "%d pipelines" +msgstr[0] "%d 条流水线" + +msgid "A collection of graphs regarding Continuous Integration" +msgstr "持续集成数据图" + msgid "About auto deploy" msgstr "关于自动部署" @@ -184,6 +191,9 @@ msgid "Commit" msgid_plural "Commits" msgstr[0] "提交" +msgid "Commit duration in minutes for last 30 commits" +msgstr "最近30次提交相应持续集成花费的时间(分钟)" + msgid "Commit message" msgstr "提交信息" @@ -223,6 +233,11 @@ msgstr "复制提交 SHA 的值到剪贴板" msgid "Create New Directory" msgstr "创建新目录" +msgid "" +"Create a personal access token on your account to pull or push via " +"%{protocol}." +msgstr "在帐户上创建个人访问令牌,以通过%{protocol}来拉取或推送。" + msgid "Create directory" msgstr "创建目录" @@ -241,6 +256,9 @@ msgstr "派生" msgid "CreateTag|Tag" msgstr "标签" +msgid "CreateTokenToCloneLink|create a personal access token" +msgstr "创建个人访问令牌" + msgid "Cron Timezone" msgstr "Cron 时区" @@ -405,6 +423,15 @@ msgstr "循环周期" msgid "Introducing Cycle Analytics" msgstr "周期分析简介" +msgid "Jobs for last month" +msgstr "上个月的作业" + +msgid "Jobs for last week" +msgstr "上个星期的作业" + +msgid "Jobs for last year" +msgstr "去年的作业" + msgid "LFSStatus|Disabled" msgstr "停用" @@ -567,6 +594,21 @@ msgstr "流水线计划" msgid "Pipeline Schedules" msgstr "流水线计划" +msgid "PipelineCharts|Failed:" +msgstr "失败:" + +msgid "PipelineCharts|Overall statistics" +msgstr "总体统计数据" + +msgid "PipelineCharts|Success ratio:" +msgstr "成功率:" + +msgid "PipelineCharts|Successful:" +msgstr "成功:" + +msgid "PipelineCharts|Total:" +msgstr "总计:" + msgid "PipelineSchedules|Activated" msgstr "是否启用" @@ -579,6 +621,12 @@ msgstr "所有" msgid "PipelineSchedules|Inactive" msgstr "未启用" +msgid "PipelineSchedules|Input variable key" +msgstr "输入变量名" + +msgid "PipelineSchedules|Input variable value" +msgstr "输入变量值" + msgid "PipelineSchedules|Next Run" msgstr "下次运行时间" @@ -588,15 +636,33 @@ msgstr "无" msgid "PipelineSchedules|Provide a short description for this pipeline" msgstr "为此流水线提供简短描述" +msgid "PipelineSchedules|Remove variable row" +msgstr "删除变量" + msgid "PipelineSchedules|Take ownership" -msgstr "取得所有者" +msgstr "取得所有权" msgid "PipelineSchedules|Target" msgstr "目标" +msgid "PipelineSchedules|Variables" +msgstr "变量" + msgid "PipelineSheduleIntervalPattern|Custom" msgstr "自定义" +msgid "Pipelines" +msgstr "流水线" + +msgid "Pipelines charts" +msgstr "流水线统计图" + +msgid "Pipeline|all" +msgstr "所有" + +msgid "Pipeline|success" +msgstr "成功" + msgid "Pipeline|with stage" msgstr "于阶段" @@ -1032,6 +1098,14 @@ msgid "Withdraw Access Request" msgstr "取消权限申请" msgid "" +"You are going to remove %{group_name}.\n" +"Removed groups CANNOT be restored!\n" +"Are you ABSOLUTELY sure?" +msgstr "即将删除 %{group_name}。\n" +"已删除的群组无法恢复!\n" +"确定继续吗?" + +msgid "" "You are going to remove %{project_name_with_namespace}.\n" "Removed project CANNOT be restored!\n" "Are you ABSOLUTELY sure?" diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po index afdbd01b7d7..8a4e6da4ea9 100644 --- a/locale/zh_HK/gitlab.po +++ b/locale/zh_HK/gitlab.po @@ -1,25 +1,23 @@ # Huang Tao <htve@outlook.com>, 2017. #zanata -# Victor Wu <anonymous@domain.com>, 2017. -# Hazel Yang <anonymous@domain.com>, 2017. msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-06-15 21:59-0500\n" +"POT-Creation-Date: 2017-07-05 08:50-0500\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-06-23 01:23-0400\n" +"PO-Revision-Date: 2017-07-12 06:32-0400\n" "Last-Translator: Huang Tao <htve@outlook.com>\n" -"Language-Team: Chinese (Hong Kong) (https://translate.zanata.org/project/view/GitLab)\n" +"Language-Team: Chinese (Hong Kong SAR China) (https://translate.zanata.org/project/view/GitLab)\n" "Language: zh-HK\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=1; plural=0\n" -msgid "%d additional commit has been omitted to prevent performance issues." +msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "" -"%d additional commits have been omitted to prevent performance issues." -msgstr[0] "為提高頁面加載速度及性能,已省略了 %d 次提交。" +"%s additional commits have been omitted to prevent performance issues." +msgstr[0] "為提高頁面加載速度及性能,已省略了 %s 次提交。" msgid "%d commit" msgid_plural "%d commits" @@ -28,6 +26,13 @@ msgstr[0] " %d 次提交" msgid "%{commit_author_link} committed %{commit_timeago}" msgstr "由 %{commit_author_link} 提交於 %{commit_timeago}" +msgid "1 pipeline" +msgid_plural "%d pipelines" +msgstr[0] "%d 條流水線" + +msgid "A collection of graphs regarding Continuous Integration" +msgstr "相關持續集成的圖像集合" + msgid "About auto deploy" msgstr "關於自動部署" @@ -185,6 +190,9 @@ msgid "Commit" msgid_plural "Commits" msgstr[0] "提交" +msgid "Commit duration in minutes for last 30 commits" +msgstr "最近30次提交花費的時間(分鐘)" + msgid "Commit message" msgstr "提交信息" @@ -224,6 +232,11 @@ msgstr "複製提交 SHA 到剪貼板" msgid "Create New Directory" msgstr "創建新目錄" +msgid "" +"Create a personal access token on your account to pull or push via " +"%{protocol}." +msgstr "在帳戶上創建個人訪問令牌,以通過 %{protocol} 來拉取或推送。" + msgid "Create directory" msgstr "創建目錄" @@ -242,6 +255,9 @@ msgstr "派生" msgid "CreateTag|Tag" msgstr "標籤" +msgid "CreateTokenToCloneLink|create a personal access token" +msgstr "創建個人訪問令牌" + msgid "Cron Timezone" msgstr "Cron 時區" @@ -406,6 +422,15 @@ msgstr "循環週期" msgid "Introducing Cycle Analytics" msgstr "週期分析簡介" +msgid "Jobs for last month" +msgstr "上個月的作業" + +msgid "Jobs for last week" +msgstr "上個星期的作業" + +msgid "Jobs for last year" +msgstr "去年的作業" + msgid "LFSStatus|Disabled" msgstr "停用" @@ -568,6 +593,21 @@ msgstr "流水線計劃" msgid "Pipeline Schedules" msgstr "流水線計劃" +msgid "PipelineCharts|Failed:" +msgstr "失敗:" + +msgid "PipelineCharts|Overall statistics" +msgstr "總體統計" + +msgid "PipelineCharts|Success ratio:" +msgstr "成功率:" + +msgid "PipelineCharts|Successful:" +msgstr "成功:" + +msgid "PipelineCharts|Total:" +msgstr "總計:" + msgid "PipelineSchedules|Activated" msgstr "是否啟用" @@ -580,6 +620,12 @@ msgstr "所有" msgid "PipelineSchedules|Inactive" msgstr "未啟用" +msgid "PipelineSchedules|Input variable key" +msgstr "輸入變量名" + +msgid "PipelineSchedules|Input variable value" +msgstr "輸入變量值" + msgid "PipelineSchedules|Next Run" msgstr "下次運行時間" @@ -589,15 +635,33 @@ msgstr "無" msgid "PipelineSchedules|Provide a short description for this pipeline" msgstr "為此流水線提供簡短描述" +msgid "PipelineSchedules|Remove variable row" +msgstr "刪除變量" + msgid "PipelineSchedules|Take ownership" -msgstr "取得所有者" +msgstr "取得所有權" msgid "PipelineSchedules|Target" msgstr "目標" +msgid "PipelineSchedules|Variables" +msgstr "變量" + msgid "PipelineSheduleIntervalPattern|Custom" msgstr "自定義" +msgid "Pipelines" +msgstr "流水線" + +msgid "Pipelines charts" +msgstr "流水線圖表" + +msgid "Pipeline|all" +msgstr "所有" + +msgid "Pipeline|success" +msgstr "成功" + msgid "Pipeline|with stage" msgstr "於階段" @@ -1033,6 +1097,14 @@ msgid "Withdraw Access Request" msgstr "取消權限申请" msgid "" +"You are going to remove %{group_name}.\n" +"Removed groups CANNOT be restored!\n" +"Are you ABSOLUTELY sure?" +msgstr "即將刪除 %{group_name}。\n" +"已刪除的群組無法恢復!\n" +"確定繼續嗎?" + +msgid "" "You are going to remove %{project_name_with_namespace}.\n" "Removed project CANNOT be restored!\n" "Are you ABSOLUTELY sure?" diff --git a/locale/zh_TW/gitlab.po b/locale/zh_TW/gitlab.po index fa0b3b339fa..e61cf0e5152 100644 --- a/locale/zh_TW/gitlab.po +++ b/locale/zh_TW/gitlab.po @@ -1,27 +1,27 @@ # Huang Tao <htve@outlook.com>, 2017. #zanata -# Lin Jen-Shin <anonymous@domain.com>, 2017. # Hazel Yang <anonymous@domain.com>, 2017. # TzeKei Lee <anonymous@domain.com>, 2017. # Jerry Ho <a29988122@gmail.com>, 2017. +# Lin Jen-Shin <godfat@godfat.org>, 2017. #zanata msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-06-19 15:50-0500\n" +"POT-Creation-Date: 2017-06-28 13:32+0200\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-06-28 11:13-0400\n" -"Last-Translator: Huang Tao <htve@outlook.com>\n" +"PO-Revision-Date: 2017-07-11 09:10-0400\n" +"Last-Translator: Lin Jen-Shin <godfat@godfat.org>\n" "Language-Team: Chinese (Taiwan) (https://translate.zanata.org/project/view/GitLab)\n" "Language: zh-TW\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=1; plural=0\n" -msgid "%d additional commit has been omitted to prevent performance issues." +msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "" -"%d additional commits have been omitted to prevent performance issues." -msgstr[0] "因效能考量,不顯示 %d 個更動 (commit)。" +"%s additional commits have been omitted to prevent performance issues." +msgstr[0] "因效能考量,不顯示 %s 個更動 (commit)。" msgid "%d commit" msgid_plural "%d commits" @@ -30,6 +30,13 @@ msgstr[0] "%d 個更動 (commit)" msgid "%{commit_author_link} committed %{commit_timeago}" msgstr "%{commit_author_link} 在 %{commit_timeago} 送交" +msgid "1 pipeline" +msgid_plural "%d pipelines" +msgstr[0] "%d 條流水線" + +msgid "A collection of graphs regarding Continuous Integration" +msgstr "持續整合 (CI) 相關的圖表" + msgid "About auto deploy" msgstr "關於自動部署" @@ -187,6 +194,9 @@ msgid "Commit" msgid_plural "Commits" msgstr[0] "更動記錄 (commit) " +msgid "Commit duration in minutes for last 30 commits" +msgstr "最近 30 次更動花費的時間(分鐘)" + msgid "Commit message" msgstr "更動說明 (commit) " @@ -226,6 +236,11 @@ msgstr "複製更動記錄 (commit) 的 SHA 值到剪貼簿" msgid "Create New Directory" msgstr "建立新目錄" +msgid "" +"Create a personal access token on your account to pull or push via " +"%{protocol}." +msgstr "建立個人存取憑證 (access token) 以使用 %{protocol} 來上傳 (push) 或下載 (pull) 。" + msgid "Create directory" msgstr "建立目錄" @@ -244,6 +259,9 @@ msgstr "分支 (fork) " msgid "CreateTag|Tag" msgstr "建立標籤" +msgid "CreateTokenToCloneLink|create a personal access token" +msgstr "建立個人存取憑證 (access token)" + msgid "Cron Timezone" msgstr "Cron 時區" @@ -408,6 +426,15 @@ msgstr "循環週期" msgid "Introducing Cycle Analytics" msgstr "週期分析簡介" +msgid "Jobs for last month" +msgstr "上個月的任務 (job) " + +msgid "Jobs for last week" +msgstr "上個星期的任務 (job) " + +msgid "Jobs for last year" +msgstr "去年的任務 (job) " + msgid "LFSStatus|Disabled" msgstr "停用" @@ -570,6 +597,21 @@ msgstr "流水線 (pipeline) 排程" msgid "Pipeline Schedules" msgstr "流水線 (pipeline) 排程" +msgid "PipelineCharts|Failed:" +msgstr "失敗:" + +msgid "PipelineCharts|Overall statistics" +msgstr "總體統計" + +msgid "PipelineCharts|Success ratio:" +msgstr "成功比率:" + +msgid "PipelineCharts|Successful:" +msgstr "成功:" + +msgid "PipelineCharts|Total:" +msgstr "總計:" + msgid "PipelineSchedules|Activated" msgstr "是否啟用" @@ -600,6 +642,18 @@ msgstr "目標" msgid "PipelineSheduleIntervalPattern|Custom" msgstr "自訂" +msgid "Pipelines" +msgstr "流水線 (pipeline) " + +msgid "Pipelines charts" +msgstr "流水線 (pipeline) 圖表" + +msgid "Pipeline|all" +msgstr "所有" + +msgid "Pipeline|success" +msgstr "成功" + msgid "Pipeline|with stage" msgstr "於階段" diff --git a/package.json b/package.json index 5a997e813f8..fd944531a6a 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "visibilityjs": "^1.2.4", "vue": "^2.2.6", "vue-loader": "^11.3.4", - "vue-resource": "^0.9.3", + "vue-resource": "^1.3.4", "vue-template-compiler": "^2.2.6", "webpack": "^2.6.1", "webpack-bundle-analyzer": "^2.8.2" diff --git a/public/ci/favicon.ico b/public/ci/favicon.ico Binary files differdeleted file mode 100644 index 9663d4d00b9..00000000000 --- a/public/ci/favicon.ico +++ /dev/null diff --git a/rubocop/cop/migration/hash_index.rb b/rubocop/cop/migration/hash_index.rb new file mode 100644 index 00000000000..2cc59691d84 --- /dev/null +++ b/rubocop/cop/migration/hash_index.rb @@ -0,0 +1,51 @@ +require 'set' +require_relative '../../migration_helpers' + +module RuboCop + module Cop + module Migration + # Cop that prevents the use of hash indexes in database migrations + class HashIndex < RuboCop::Cop::Cop + include MigrationHelpers + + MSG = 'hash indexes should be avoided at all costs since they are not ' \ + 'recorded in the PostgreSQL WAL, you should use a btree index instead'.freeze + + NAMES = Set.new([:add_index, :index, :add_concurrent_index]).freeze + + def on_send(node) + return unless in_migration?(node) + + name = node.children[1] + + return unless NAMES.include?(name) + + opts = node.children.last + + return unless opts && opts.type == :hash + + opts.each_node(:pair) do |pair| + next unless hash_key_type(pair) == :sym && + hash_key_name(pair) == :using + + if hash_key_value(pair).to_s == 'hash' + add_offense(pair, :expression) + end + end + end + + def hash_key_type(pair) + pair.children[0].type + end + + def hash_key_name(pair) + pair.children[0].children[0] + end + + def hash_key_value(pair) + pair.children[1].children[0] + end + end + end + end +end diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb index f76144275c9..3fbd5b0163c 100644 --- a/rubocop/rubocop.rb +++ b/rubocop/rubocop.rb @@ -13,6 +13,7 @@ require_relative 'cop/migration/add_concurrent_index' require_relative 'cop/migration/add_index' require_relative 'cop/migration/add_timestamps' require_relative 'cop/migration/datetime' +require_relative 'cop/migration/hash_index' require_relative 'cop/migration/remove_concurrent_index' require_relative 'cop/migration/remove_index' require_relative 'cop/migration/reversible_add_column_with_default' diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh index 03de59f27ad..39806901274 100644 --- a/scripts/prepare_build.sh +++ b/scripts/prepare_build.sh @@ -10,10 +10,7 @@ fi # Only install knapsack after bundle install! Otherwise oddly some native # gems could not be found under some circumstance. No idea why, hours wasted. -retry gem install knapsack fog-aws mime-types - -cp config/resque.yml.example config/resque.yml -sed -i 's/localhost/redis/g' config/resque.yml +retry gem install knapsack cp config/gitlab.yml.example config/gitlab.yml @@ -37,6 +34,18 @@ else # Assume it's mysql sed -i 's/# host:.*/host: mysql/g' config/database.yml fi +cp config/resque.yml.example config/resque.yml +sed -i 's/localhost/redis/g' config/resque.yml + +cp config/redis.cache.yml.example config/redis.cache.yml +sed -i 's/localhost/redis/g' config/redis.cache.yml + +cp config/redis.queues.yml.example config/redis.queues.yml +sed -i 's/localhost/redis/g' config/redis.queues.yml + +cp config/redis.shared_state.yml.example config/redis.shared_state.yml +sed -i 's/localhost/redis/g' config/redis.shared_state.yml + if [ "$SETUP_DB" != "false" ]; then bundle exec rake db:drop db:create db:schema:load db:migrate diff --git a/spec/config/mail_room_spec.rb b/spec/config/mail_room_spec.rb index 092048a6259..a31e44fa928 100644 --- a/spec/config/mail_room_spec.rb +++ b/spec/config/mail_room_spec.rb @@ -5,12 +5,12 @@ describe 'mail_room.yml' do let(:mailroom_config_path) { 'config/mail_room.yml' } let(:gitlab_config_path) { 'config/mail_room.yml' } - let(:redis_config_path) { 'config/resque.yml' } + let(:queues_config_path) { 'config/redis.queues.yml' } let(:configuration) do vars = { 'MAIL_ROOM_GITLAB_CONFIG_FILE' => absolute_path(gitlab_config_path), - 'GITLAB_REDIS_CONFIG_FILE' => absolute_path(redis_config_path) + 'GITLAB_REDIS_QUEUES_CONFIG_FILE' => absolute_path(queues_config_path) } cmd = "puts ERB.new(File.read(#{absolute_path(mailroom_config_path).inspect})).result" @@ -21,12 +21,12 @@ describe 'mail_room.yml' do end before(:each) do - stub_env('GITLAB_REDIS_CONFIG_FILE', absolute_path(redis_config_path)) - clear_redis_raw_config + stub_env('GITLAB_REDIS_QUEUES_CONFIG_FILE', absolute_path(queues_config_path)) + clear_queues_raw_config end after(:each) do - clear_redis_raw_config + clear_queues_raw_config end context 'when incoming email is disabled' do @@ -39,9 +39,9 @@ describe 'mail_room.yml' do context 'when incoming email is enabled' do let(:gitlab_config_path) { 'spec/fixtures/config/mail_room_enabled.yml' } - let(:redis_config_path) { 'spec/fixtures/config/redis_new_format_host.yml' } + let(:queues_config_path) { 'spec/fixtures/config/redis_queues_new_format_host.yml' } - let(:gitlab_redis) { Gitlab::Redis.new(Rails.env) } + let(:gitlab_redis_queues) { Gitlab::Redis::Queues.new(Rails.env) } it 'contains the intended configuration' do expect(configuration[:mailboxes].length).to eq(1) @@ -56,8 +56,8 @@ describe 'mail_room.yml' do expect(mailbox[:name]).to eq('inbox') expect(mailbox[:idle_timeout]).to eq(60) - redis_url = gitlab_redis.url - sentinels = gitlab_redis.sentinels + redis_url = gitlab_redis_queues.url + sentinels = gitlab_redis_queues.sentinels expect(mailbox[:delivery_options][:redis_url]).to be_present expect(mailbox[:delivery_options][:redis_url]).to eq(redis_url) @@ -73,8 +73,8 @@ describe 'mail_room.yml' do end end - def clear_redis_raw_config - Gitlab::Redis.remove_instance_variable(:@_raw_config) + def clear_queues_raw_config + Gitlab::Redis::Queues.remove_instance_variable(:@_raw_config) rescue NameError # raised if @_raw_config was not set; ignore end diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index a2720c9b81e..1641bddea11 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -30,6 +30,15 @@ describe ApplicationController do expect(controller).not_to receive(:redirect_to) controller.send(:check_password_expiration) end + + it 'does not redirect if the user is over their password expiry but sign-in is disabled' do + stub_application_setting(password_authentication_enabled: false) + user.password_expires_at = Time.new(2002) + allow(controller).to receive(:current_user).and_return(user) + expect(controller).not_to receive(:redirect_to) + + controller.send(:check_password_expiration) + end end describe "#authenticate_user_from_token!" do diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb index b40f647644d..58486f33229 100644 --- a/spec/controllers/autocomplete_controller_spec.rb +++ b/spec/controllers/autocomplete_controller_spec.rb @@ -97,6 +97,21 @@ describe AutocompleteController do it { expect(body.size).to eq User.count } end + context 'user order' do + it 'shows exact matches first' do + reported_user = create(:user, username: 'reported_user', name: 'Doug') + user = create(:user, username: 'user', name: 'User') + user1 = create(:user, username: 'user1', name: 'Ian') + + sign_in(user) + get(:users, search: 'user') + + response_usernames = JSON.parse(response.body).map { |user| user['username'] } + + expect(response_usernames.take(3)).to match_array([user.username, reported_user.username, user1.username]) + end + end + context 'limited users per page' do let(:per_page) { 2 } diff --git a/spec/controllers/dashboard/todos_controller_spec.rb b/spec/controllers/dashboard/todos_controller_spec.rb index 085f3fd8543..4a48621abe1 100644 --- a/spec/controllers/dashboard/todos_controller_spec.rb +++ b/spec/controllers/dashboard/todos_controller_spec.rb @@ -12,6 +12,36 @@ describe Dashboard::TodosController do end describe 'GET #index' do + context 'project authorization' do + it 'renders 404 when user does not have read access on given project' do + unauthorized_project = create(:empty_project, :private) + + get :index, project_id: unauthorized_project.id + + expect(response).to have_http_status(404) + end + + it 'renders 404 when given project does not exists' do + get :index, project_id: 999 + + expect(response).to have_http_status(404) + end + + it 'renders 200 when filtering for "any project" todos' do + get :index, project_id: '' + + expect(response).to have_http_status(200) + end + + it 'renders 200 when user has access on given project' do + authorized_project = create(:empty_project, :public) + + get :index, project_id: authorized_project.id + + expect(response).to have_http_status(200) + end + end + context 'when using pagination' do let(:last_page) { user.todos.page.total_pages } let!(:issues) { create_list(:issue, 2, project: project, assignees: [user]) } diff --git a/spec/controllers/health_check_controller_spec.rb b/spec/controllers/health_check_controller_spec.rb index 58c16cc57e6..03da6287774 100644 --- a/spec/controllers/health_check_controller_spec.rb +++ b/spec/controllers/health_check_controller_spec.rb @@ -3,52 +3,79 @@ require 'spec_helper' describe HealthCheckController do include StubENV - let(:token) { current_application_settings.health_check_access_token } let(:json_response) { JSON.parse(response.body) } let(:xml_response) { Hash.from_xml(response.body)['hash'] } + let(:token) { current_application_settings.health_check_access_token } + let(:whitelisted_ip) { '127.0.0.1' } + let(:not_whitelisted_ip) { '127.0.0.2' } before do + allow(Settings.monitoring).to receive(:ip_whitelist).and_return([whitelisted_ip]) stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') end describe 'GET #index' do - context 'when services are up but NO access token' do + context 'when services are up but accessed from outside whitelisted ips' do + before do + allow(Gitlab::RequestContext).to receive(:client_ip).and_return(not_whitelisted_ip) + end + it 'returns a not found page' do get :index + expect(response).to be_not_found end + + context 'when services are accessed with token' do + it 'supports passing the token in the header' do + request.headers['TOKEN'] = token + + get :index + + expect(response).to be_success + expect(response.content_type).to eq 'text/plain' + end + + it 'supports passing the token in query params' do + get :index, token: token + + expect(response).to be_success + expect(response.content_type).to eq 'text/plain' + end + end end - context 'when services are up and an access token is provided' do - it 'supports passing the token in the header' do - request.headers['TOKEN'] = token - get :index - expect(response).to be_success - expect(response.content_type).to eq 'text/plain' + context 'when services are up and accessed from whitelisted ips' do + before do + allow(Gitlab::RequestContext).to receive(:client_ip).and_return(whitelisted_ip) end - it 'supports successful plaintest response' do - get :index, token: token + it 'supports successful plaintext response' do + get :index + expect(response).to be_success expect(response.content_type).to eq 'text/plain' end it 'supports successful json response' do - get :index, token: token, format: :json + get :index, format: :json + expect(response).to be_success expect(response.content_type).to eq 'application/json' expect(json_response['healthy']).to be true end it 'supports successful xml response' do - get :index, token: token, format: :xml + get :index, format: :xml + expect(response).to be_success expect(response.content_type).to eq 'application/xml' expect(xml_response['healthy']).to be true end it 'supports successful responses for specific checks' do - get :index, token: token, checks: 'email', format: :json + get :index, checks: 'email', format: :json + expect(response).to be_success expect(response.content_type).to eq 'application/json' expect(json_response['healthy']).to be true @@ -58,33 +85,29 @@ describe HealthCheckController do context 'when a service is down but NO access token' do it 'returns a not found page' do get :index + expect(response).to be_not_found end end - context 'when a service is down and an access token is provided' do + context 'when a service is down and an endpoint is accessed from whitelisted ip' do before do allow(HealthCheck::Utils).to receive(:process_checks).with(['standard']).and_return('The server is on fire') allow(HealthCheck::Utils).to receive(:process_checks).with(['email']).and_return('Email is on fire') + allow(Gitlab::RequestContext).to receive(:client_ip).and_return(whitelisted_ip) end - it 'supports passing the token in the header' do - request.headers['TOKEN'] = token + it 'supports failure plaintext response' do get :index - expect(response).to have_http_status(500) - expect(response.content_type).to eq 'text/plain' - expect(response.body).to include('The server is on fire') - end - it 'supports failure plaintest response' do - get :index, token: token expect(response).to have_http_status(500) expect(response.content_type).to eq 'text/plain' expect(response.body).to include('The server is on fire') end it 'supports failure json response' do - get :index, token: token, format: :json + get :index, format: :json + expect(response).to have_http_status(500) expect(response.content_type).to eq 'application/json' expect(json_response['healthy']).to be false @@ -92,7 +115,8 @@ describe HealthCheckController do end it 'supports failure xml response' do - get :index, token: token, format: :xml + get :index, format: :xml + expect(response).to have_http_status(500) expect(response.content_type).to eq 'application/xml' expect(xml_response['healthy']).to be false @@ -100,7 +124,8 @@ describe HealthCheckController do end it 'supports failure responses for specific checks' do - get :index, token: token, checks: 'email', format: :json + get :index, checks: 'email', format: :json + expect(response).to have_http_status(500) expect(response.content_type).to eq 'application/json' expect(json_response['healthy']).to be false diff --git a/spec/controllers/health_controller_spec.rb b/spec/controllers/health_controller_spec.rb index e7c19b47a6a..cc389e554ad 100644 --- a/spec/controllers/health_controller_spec.rb +++ b/spec/controllers/health_controller_spec.rb @@ -3,55 +3,120 @@ require 'spec_helper' describe HealthController do include StubENV - let(:token) { current_application_settings.health_check_access_token } let(:json_response) { JSON.parse(response.body) } + let(:token) { current_application_settings.health_check_access_token } + let(:whitelisted_ip) { '127.0.0.1' } + let(:not_whitelisted_ip) { '127.0.0.2' } before do + allow(Settings.monitoring).to receive(:ip_whitelist).and_return([whitelisted_ip]) stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') end describe '#readiness' do - context 'authorization token provided' do - before do - request.headers['TOKEN'] = token - end + shared_context 'endpoint responding with readiness data' do + let(:request_params) { {} } + + subject { get :readiness, request_params } + + it 'responds with readiness checks data' do + subject - it 'returns proper response' do - get :readiness expect(json_response['db_check']['status']).to eq('ok') - expect(json_response['redis_check']['status']).to eq('ok') + expect(json_response['cache_check']['status']).to eq('ok') + expect(json_response['queues_check']['status']).to eq('ok') + expect(json_response['shared_state_check']['status']).to eq('ok') expect(json_response['fs_shards_check']['status']).to eq('ok') expect(json_response['fs_shards_check']['labels']['shard']).to eq('default') end end - context 'without authorization token' do - it 'returns proper response' do + context 'accessed from whitelisted ip' do + before do + allow(Gitlab::RequestContext).to receive(:client_ip).and_return(whitelisted_ip) + end + + it_behaves_like 'endpoint responding with readiness data' + end + + context 'accessed from not whitelisted ip' do + before do + allow(Gitlab::RequestContext).to receive(:client_ip).and_return(not_whitelisted_ip) + end + + it 'responds with resource not found' do get :readiness + expect(response.status).to eq(404) end + + context 'accessed with valid token' do + context 'token passed in request header' do + before do + request.headers['TOKEN'] = token + end + + it_behaves_like 'endpoint responding with readiness data' + end + end + + context 'token passed as URL param' do + it_behaves_like 'endpoint responding with readiness data' do + let(:request_params) { { token: token } } + end + end end end describe '#liveness' do - context 'authorization token provided' do - before do - request.headers['TOKEN'] = token - end + shared_context 'endpoint responding with liveness data' do + subject { get :liveness } + + it 'responds with liveness checks data' do + subject - it 'returns proper response' do - get :liveness expect(json_response['db_check']['status']).to eq('ok') - expect(json_response['redis_check']['status']).to eq('ok') + expect(json_response['cache_check']['status']).to eq('ok') + expect(json_response['queues_check']['status']).to eq('ok') + expect(json_response['shared_state_check']['status']).to eq('ok') expect(json_response['fs_shards_check']['status']).to eq('ok') end end - context 'without authorization token' do - it 'returns proper response' do + context 'accessed from whitelisted ip' do + before do + allow(Gitlab::RequestContext).to receive(:client_ip).and_return(whitelisted_ip) + end + + it_behaves_like 'endpoint responding with liveness data' + end + + context 'accessed from not whitelisted ip' do + before do + allow(Gitlab::RequestContext).to receive(:client_ip).and_return(not_whitelisted_ip) + end + + it 'responds with resource not found' do get :liveness + expect(response.status).to eq(404) end + + context 'accessed with valid token' do + context 'token passed in request header' do + before do + request.headers['TOKEN'] = token + end + + it_behaves_like 'endpoint responding with liveness data' + end + + context 'token passed as URL param' do + it_behaves_like 'endpoint responding with liveness data' do + subject { get :liveness, token: token } + end + end + end end end end diff --git a/spec/controllers/metrics_controller_spec.rb b/spec/controllers/metrics_controller_spec.rb index 044c9f179ed..7b0976e3e67 100644 --- a/spec/controllers/metrics_controller_spec.rb +++ b/spec/controllers/metrics_controller_spec.rb @@ -3,28 +3,28 @@ require 'spec_helper' describe MetricsController do include StubENV - let(:token) { current_application_settings.health_check_access_token } let(:json_response) { JSON.parse(response.body) } let(:metrics_multiproc_dir) { Dir.mktmpdir } + let(:whitelisted_ip) { '127.0.0.1' } + let(:whitelisted_ip_range) { '10.0.0.0/24' } + let(:ip_in_whitelisted_range) { '10.0.0.1' } + let(:not_whitelisted_ip) { '10.0.1.1' } before do stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') - stub_env('prometheus_multiproc_dir', metrics_multiproc_dir) + allow(Prometheus::Client.configuration).to receive(:multiprocess_files_dir).and_return(metrics_multiproc_dir) allow(Gitlab::Metrics).to receive(:prometheus_metrics_enabled?).and_return(true) + allow(Settings.monitoring).to receive(:ip_whitelist).and_return([whitelisted_ip, whitelisted_ip_range]) end describe '#index' do - context 'authorization token provided' do - before do - request.headers['TOKEN'] = token - end - + shared_examples_for 'endpoint providing metrics' do it 'returns DB ping metrics' do get :index expect(response.body).to match(/^db_ping_timeout 0$/) expect(response.body).to match(/^db_ping_success 1$/) - expect(response.body).to match(/^db_ping_latency [0-9\.]+$/) + expect(response.body).to match(/^db_ping_latency_seconds [0-9\.]+$/) end it 'returns Redis ping metrics' do @@ -32,17 +32,41 @@ describe MetricsController do expect(response.body).to match(/^redis_ping_timeout 0$/) expect(response.body).to match(/^redis_ping_success 1$/) - expect(response.body).to match(/^redis_ping_latency [0-9\.]+$/) + expect(response.body).to match(/^redis_ping_latency_seconds [0-9\.]+$/) + end + + it 'returns Caching ping metrics' do + get :index + + expect(response.body).to match(/^redis_cache_ping_timeout 0$/) + expect(response.body).to match(/^redis_cache_ping_success 1$/) + expect(response.body).to match(/^redis_cache_ping_latency_seconds [0-9\.]+$/) + end + + it 'returns Queues ping metrics' do + get :index + + expect(response.body).to match(/^redis_queues_ping_timeout 0$/) + expect(response.body).to match(/^redis_queues_ping_success 1$/) + expect(response.body).to match(/^redis_queues_ping_latency_seconds [0-9\.]+$/) + end + + it 'returns SharedState ping metrics' do + get :index + + expect(response.body).to match(/^redis_shared_state_ping_timeout 0$/) + expect(response.body).to match(/^redis_shared_state_ping_success 1$/) + expect(response.body).to match(/^redis_shared_state_ping_latency_seconds [0-9\.]+$/) end it 'returns file system check metrics' do get :index - expect(response.body).to match(/^filesystem_access_latency{shard="default"} [0-9\.]+$/) + expect(response.body).to match(/^filesystem_access_latency_seconds{shard="default"} [0-9\.]+$/) expect(response.body).to match(/^filesystem_accessible{shard="default"} 1$/) - expect(response.body).to match(/^filesystem_write_latency{shard="default"} [0-9\.]+$/) + expect(response.body).to match(/^filesystem_write_latency_seconds{shard="default"} [0-9\.]+$/) expect(response.body).to match(/^filesystem_writable{shard="default"} 1$/) - expect(response.body).to match(/^filesystem_read_latency{shard="default"} [0-9\.]+$/) + expect(response.body).to match(/^filesystem_read_latency_seconds{shard="default"} [0-9\.]+$/) expect(response.body).to match(/^filesystem_readable{shard="default"} 1$/) end @@ -59,7 +83,27 @@ describe MetricsController do end end - context 'without authorization token' do + context 'accessed from whitelisted ip' do + before do + allow(Gitlab::RequestContext).to receive(:client_ip).and_return(whitelisted_ip) + end + + it_behaves_like 'endpoint providing metrics' + end + + context 'accessed from ip in whitelisted range' do + before do + allow(Gitlab::RequestContext).to receive(:client_ip).and_return(ip_in_whitelisted_range) + end + + it_behaves_like 'endpoint providing metrics' + end + + context 'accessed from not whitelisted ip' do + before do + allow(Gitlab::RequestContext).to receive(:client_ip).and_return(not_whitelisted_ip) + end + it 'returns proper response' do get :index diff --git a/spec/controllers/passwords_controller_spec.rb b/spec/controllers/passwords_controller_spec.rb new file mode 100644 index 00000000000..2955d01fad0 --- /dev/null +++ b/spec/controllers/passwords_controller_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe PasswordsController do + describe '#check_password_authentication_available' do + before do + @request.env["devise.mapping"] = Devise.mappings[:user] + end + + context 'when password authentication is disabled' do + it 'prevents a password reset' do + stub_application_setting(password_authentication_enabled: false) + + post :create + + expect(flash[:alert]).to eq 'Password authentication is unavailable.' + end + end + + context 'when reset email belongs to an ldap user' do + let(:user) { create(:omniauth_user, provider: 'ldapmain', email: 'ldapuser@gitlab.com') } + + it 'prevents a password reset' do + post :create, user: { email: user.email } + + expect(flash[:alert]).to eq 'Password authentication is unavailable.' + end + end + end +end diff --git a/spec/controllers/profiles/accounts_controller_spec.rb b/spec/controllers/profiles/accounts_controller_spec.rb index 2f9d18e3a0e..d387aba227b 100644 --- a/spec/controllers/profiles/accounts_controller_spec.rb +++ b/spec/controllers/profiles/accounts_controller_spec.rb @@ -29,7 +29,7 @@ describe Profiles::AccountsController do end end - [:twitter, :facebook, :google_oauth2, :gitlab, :github, :bitbucket, :crowd, :auth0].each do |provider| + [:twitter, :facebook, :google_oauth2, :gitlab, :github, :bitbucket, :crowd, :auth0, :authentiq].each do |provider| describe "#{provider} provider" do let(:user) { create(:omniauth_user, provider: provider.to_s) } diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb index eb61a0c080c..df53863482d 100644 --- a/spec/controllers/projects/commit_controller_spec.rb +++ b/spec/controllers/projects/commit_controller_spec.rb @@ -343,7 +343,8 @@ describe Projects::CommitController do get_pipelines(id: commit.id, format: :json) expect(response).to be_ok - expect(JSON.parse(response.body)).not_to be_empty + expect(JSON.parse(response.body)['pipelines']).not_to be_empty + expect(JSON.parse(response.body)['count']['all']).to eq 1 end end end diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index 22aad0b3225..18d0be3c103 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -7,14 +7,16 @@ describe Projects::IssuesController do describe "GET #index" do context 'external issue tracker' do + let!(:service) do + create(:custom_issue_tracker_service, project: project, title: 'Custom Issue Tracker', project_url: 'http://test.com') + end + it 'redirects to the external issue tracker' do - external = double(project_path: 'https://example.com/project') - allow(project).to receive(:external_issue_tracker).and_return(external) controller.instance_variable_set(:@project, project) get :index, namespace_id: project.namespace, project_id: project - expect(response).to redirect_to('https://example.com/project') + expect(response).to redirect_to(service.issue_tracker_path) end end @@ -139,19 +141,21 @@ describe Projects::IssuesController do end context 'external issue tracker' do + let!(:service) do + create(:custom_issue_tracker_service, project: project, title: 'Custom Issue Tracker', new_issue_url: 'http://test.com') + end + before do sign_in(user) project.team << [user, :developer] end it 'redirects to the external issue tracker' do - external = double(new_issue_path: 'https://example.com/issues/new') - allow(project).to receive(:external_issue_tracker).and_return(external) controller.instance_variable_set(:@project, project) get :new, namespace_id: project.namespace, project_id: project - expect(response).to redirect_to('https://example.com/issues/new') + expect(response).to redirect_to('http://test.com') end end end @@ -512,6 +516,36 @@ describe Projects::IssuesController do end end + describe 'GET #realtime_changes' do + it_behaves_like 'restricted action', success: 200 + + def go(id:) + get :realtime_changes, + namespace_id: project.namespace.to_param, + project_id: project, + id: id + end + + context 'when an issue was edited by a deleted user' do + let(:deleted_user) { create(:user) } + + before do + project.team << [user, :developer] + + issue.update!(last_edited_by: deleted_user, last_edited_at: Time.now) + + deleted_user.destroy + sign_in(user) + end + + it 'returns 200' do + go(id: issue.iid) + + expect(response).to have_http_status(200) + end + end + end + describe 'GET #edit' do it_behaves_like 'restricted action', success: 200 diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 6f9ce60cf75..c193babead0 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -481,7 +481,8 @@ describe Projects::MergeRequestsController do end it 'responds with serialized pipelines' do - expect(json_response).not_to be_empty + expect(json_response['pipelines']).not_to be_empty + expect(json_response['count']['all']).to eq 1 end end diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index f96fe7ad5cb..192cca45d99 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -211,24 +211,43 @@ describe ProjectsController do let(:admin) { create(:admin) } let(:project) { create(:project, :repository) } - let(:new_path) { 'renamed_path' } - let(:project_params) { { path: new_path } } before do sign_in(admin) end - it "sets the repository to the right path after a rename" do - controller.instance_variable_set(:@project, project) + context 'when only renaming a project path' do + it "sets the repository to the right path after a rename" do + expect { update_project path: 'renamed_path' } + .to change { project.reload.path } - put :update, - namespace_id: project.namespace, - id: project.id, - project: project_params + expect(project.path).to include 'renamed_path' + expect(assigns(:repository).path).to include project.path + expect(response).to have_http_status(302) + end + end - expect(project.repository.path).to include(new_path) - expect(assigns(:repository).path).to eq(project.repository.path) - expect(response).to have_http_status(302) + context 'when project has container repositories with tags' do + before do + stub_container_registry_config(enabled: true) + stub_container_registry_tags(repository: /image/, tags: %w[rc1]) + create(:container_repository, project: project, name: :image) + end + + it 'does not allow to rename the project' do + expect { update_project path: 'renamed_path' } + .not_to change { project.reload.path } + + expect(controller).to set_flash[:alert].to(/container registry tags/) + expect(response).to have_http_status(200) + end + end + + def update_project(**parameters) + put :update, + namespace_id: project.namespace.path, + id: project.path, + project: parameters end end diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb index bf922260b2f..2b4e8723b48 100644 --- a/spec/controllers/sessions_controller_spec.rb +++ b/spec/controllers/sessions_controller_spec.rb @@ -47,7 +47,7 @@ describe SessionsController do end end - context 'when using valid password', :redis do + context 'when using valid password', :clean_gitlab_redis_shared_state do include UserActivitiesHelpers let(:user) { create(:user) } diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb index 15416a89017..475ceda11fe 100644 --- a/spec/controllers/snippets_controller_spec.rb +++ b/spec/controllers/snippets_controller_spec.rb @@ -186,8 +186,8 @@ describe SnippetsController do end context 'when the snippet description contains a file' do - let(:picture_file) { '/temp/secret56/picture.jpg' } - let(:text_file) { '/temp/secret78/text.txt' } + let(:picture_file) { '/system/temp/secret56/picture.jpg' } + let(:text_file) { '/system/temp/secret78/text.txt' } let(:description) do "Description with picture: ![picture](/uploads#{picture_file}) and "\ "text: [text.txt](/uploads#{text_file})" @@ -208,8 +208,8 @@ describe SnippetsController do snippet = subject expected_description = "Description with picture: "\ - "![picture](/uploads/personal_snippet/#{snippet.id}/secret56/picture.jpg) and "\ - "text: [text.txt](/uploads/personal_snippet/#{snippet.id}/secret78/text.txt)" + "![picture](/uploads/system/personal_snippet/#{snippet.id}/secret56/picture.jpg) and "\ + "text: [text.txt](/uploads/system/personal_snippet/#{snippet.id}/secret78/text.txt)" expect(snippet.description).to eq(expected_description) end diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb index 01a0659479b..96f719e2b82 100644 --- a/spec/controllers/uploads_controller_spec.rb +++ b/spec/controllers/uploads_controller_spec.rb @@ -102,7 +102,7 @@ describe UploadsController do subject expect(response.body).to match '\"alt\":\"rails_sample\"' - expect(response.body).to match "\"url\":\"/uploads/temp" + expect(response.body).to match "\"url\":\"/uploads/system/temp" end it 'does not create an Upload record' do @@ -119,7 +119,7 @@ describe UploadsController do subject expect(response.body).to match '\"alt\":\"doc_sample.txt\"' - expect(response.body).to match "\"url\":\"/uploads/temp" + expect(response.body).to match "\"url\":\"/uploads/system/temp" end it 'does not create an Upload record' do diff --git a/spec/factories/ci/triggers.rb b/spec/factories/ci/triggers.rb index c3a29d8bf04..40c4663c6d8 100644 --- a/spec/factories/ci/triggers.rb +++ b/spec/factories/ci/triggers.rb @@ -2,13 +2,6 @@ FactoryGirl.define do factory :ci_trigger_without_token, class: Ci::Trigger do factory :ci_trigger do sequence(:token) { |n| "token#{n}" } - - factory :ci_trigger_for_trigger_schedule do - token { SecureRandom.hex(15) } - owner factory: :user - project factory: :project - ref 'master' - end end end end diff --git a/spec/factories/commits.rb b/spec/factories/commits.rb index 36b9645438a..89e260cf65b 100644 --- a/spec/factories/commits.rb +++ b/spec/factories/commits.rb @@ -4,14 +4,19 @@ FactoryGirl.define do factory :commit do git_commit RepoHelpers.sample_commit project factory: :empty_project - author { build(:author) } initialize_with do new(git_commit, project) end + after(:build) do |commit| + allow(commit).to receive(:author).and_return build(:author) + end + trait :without_author do - author nil + after(:build) do |commit| + allow(commit).to receive(:author).and_return nil + end end end end diff --git a/spec/factories/uploads.rb b/spec/factories/uploads.rb index 1383420fb44..3222c41c3d8 100644 --- a/spec/factories/uploads.rb +++ b/spec/factories/uploads.rb @@ -1,7 +1,7 @@ FactoryGirl.define do factory :upload do model { build(:project) } - path { "uploads/system/project/avatar/avatar.jpg" } + path { "uploads/-/system/project/avatar/avatar.jpg" } size 100.kilobytes uploader "AvatarUploader" end diff --git a/spec/features/admin/admin_appearance_spec.rb b/spec/features/admin/admin_appearance_spec.rb index 1e2cb8569ec..b9e361328df 100644 --- a/spec/features/admin/admin_appearance_spec.rb +++ b/spec/features/admin/admin_appearance_spec.rb @@ -63,11 +63,11 @@ feature 'Admin Appearance', feature: true do end def logo_selector - '//img[@src^="/uploads/system/appearance/logo"]' + '//img[@src^="/uploads/-/system/appearance/logo"]' end def header_logo_selector - '//img[@src^="/uploads/system/appearance/header_logo"]' + '//img[@src^="/uploads/-/system/appearance/header_logo"]' end def logo_fixture diff --git a/spec/features/admin/admin_users_impersonation_tokens_spec.rb b/spec/features/admin/admin_users_impersonation_tokens_spec.rb index cd7040891e9..d01722805c4 100644 --- a/spec/features/admin/admin_users_impersonation_tokens_spec.rb +++ b/spec/features/admin/admin_users_impersonation_tokens_spec.rb @@ -8,8 +8,8 @@ describe 'Admin > Users > Impersonation Tokens', feature: true, js: true do find(".table.active-tokens") end - def inactive_impersonation_tokens - find(".table.inactive-tokens") + def no_personal_access_tokens_message + find(".settings-message") end before do @@ -60,15 +60,17 @@ describe 'Admin > Users > Impersonation Tokens', feature: true, js: true do click_on "Revoke" - expect(inactive_impersonation_tokens).to have_text(impersonation_token.name) + expect(page).to have_selector(".settings-message") + expect(no_personal_access_tokens_message).to have_text("This user has no active Impersonation Tokens.") end - it "moves expired tokens to the 'inactive' section" do + it "removes expired tokens from 'active' section" do impersonation_token.update(expires_at: 5.days.ago) visit admin_user_impersonation_tokens_path(user_id: user.username) - expect(inactive_impersonation_tokens).to have_text(impersonation_token.name) + expect(page).to have_selector(".settings-message") + expect(no_personal_access_tokens_message).to have_text("This user has no active Impersonation Tokens.") end end end diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index 3d7e26c7e19..b939fb5e89e 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -3,7 +3,8 @@ require 'rails_helper' describe 'Issue Boards', feature: true, js: true do include DragTo - let(:project) { create(:empty_project, :public) } + let(:group) { create(:group, :nested) } + let(:project) { create(:empty_project, :public, namespace: group) } let(:board) { create(:board, project: project) } let(:user) { create(:user) } let!(:user2) { create(:user) } diff --git a/spec/features/dashboard/activity_spec.rb b/spec/features/dashboard/activity_spec.rb index ebfe7340eb7..a96270c9147 100644 --- a/spec/features/dashboard/activity_spec.rb +++ b/spec/features/dashboard/activity_spec.rb @@ -1,13 +1,162 @@ require 'spec_helper' -RSpec.describe 'Dashboard Activity', feature: true do +feature 'Dashboard > Activity' do let(:user) { create(:user) } before do sign_in(user) - visit activity_dashboard_path end - it_behaves_like "it has an RSS button with current_user's RSS token" - it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" + context 'rss' do + before do + visit activity_dashboard_path + end + + it_behaves_like "it has an RSS button with current_user's RSS token" + it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" + end + + context 'event filters', :js do + let(:project) { create(:empty_project) } + + let(:merge_request) do + create(:merge_request, author: user, source_project: project, target_project: project) + end + + let(:push_event_data) do + { + before: Gitlab::Git::BLANK_SHA, + after: '0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e', + ref: 'refs/heads/new_design', + user_id: user.id, + user_name: user.name, + repository: { + name: project.name, + url: 'localhost/rubinius', + description: '', + homepage: 'localhost/rubinius', + private: true + } + } + end + + let(:note) { create(:note, project: project, noteable: merge_request) } + + let!(:push_event) do + create(:event, :pushed, data: push_event_data, project: project, author: user) + end + + let!(:merged_event) do + create(:event, :merged, project: project, target: merge_request, author: user) + end + + let!(:joined_event) do + create(:event, :joined, project: project, author: user) + end + + let!(:closed_event) do + create(:event, :closed, project: project, target: merge_request, author: user) + end + + let!(:comments_event) do + create(:event, :commented, project: project, target: note, author: user) + end + + before do + project.add_master(user) + + visit activity_dashboard_path + wait_for_requests + end + + scenario 'user should see all events' do + within '.content_list' do + expect(page).to have_content('pushed new branch') + expect(page).to have_content('joined') + expect(page).to have_content('accepted') + expect(page).to have_content('closed') + expect(page).to have_content('commented on') + end + end + + scenario 'user should see only pushed events' do + click_link('Push events') + wait_for_requests + + within '.content_list' do + expect(page).to have_content('pushed new branch') + expect(page).not_to have_content('joined') + expect(page).not_to have_content('accepted') + expect(page).not_to have_content('closed') + expect(page).not_to have_content('commented on') + end + end + + scenario 'user should see only merged events' do + click_link('Merge events') + wait_for_requests + + within '.content_list' do + expect(page).not_to have_content('pushed new branch') + expect(page).not_to have_content('joined') + expect(page).to have_content('accepted') + expect(page).not_to have_content('closed') + expect(page).not_to have_content('commented on') + end + end + + scenario 'user should see only issues events' do + click_link('Issue events') + wait_for_requests + + within '.content_list' do + expect(page).not_to have_content('pushed new branch') + expect(page).not_to have_content('joined') + expect(page).not_to have_content('accepted') + expect(page).to have_content('closed') + expect(page).not_to have_content('commented on') + end + end + + scenario 'user should see only comments events' do + click_link('Comments') + wait_for_requests + + within '.content_list' do + expect(page).not_to have_content('pushed new branch') + expect(page).not_to have_content('joined') + expect(page).not_to have_content('accepted') + expect(page).not_to have_content('closed') + expect(page).to have_content('commented on') + end + end + + scenario 'user should see only joined events' do + click_link('Team') + wait_for_requests + + within '.content_list' do + expect(page).not_to have_content('pushed new branch') + expect(page).to have_content('joined') + expect(page).not_to have_content('accepted') + expect(page).not_to have_content('closed') + expect(page).not_to have_content('commented on') + end + end + + scenario 'user see selected event after page reloading' do + click_link('Push events') + wait_for_requests + visit activity_dashboard_path + wait_for_requests + + within '.content_list' do + expect(page).to have_content('pushed new branch') + expect(page).not_to have_content('joined') + expect(page).not_to have_content('accepted') + expect(page).not_to have_content('closed') + expect(page).not_to have_content('commented on') + end + end + end end diff --git a/spec/features/dashboard/groups_list_spec.rb b/spec/features/dashboard/groups_list_spec.rb index 54a01e837de..533df7a325c 100644 --- a/spec/features/dashboard/groups_list_spec.rb +++ b/spec/features/dashboard/groups_list_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Dashboard Groups page', js: true, feature: true do +feature 'Dashboard Groups page', :js do let!(:user) { create :user } let!(:group) { create(:group) } let!(:nested_group) { create(:group, :nested) } @@ -41,7 +41,7 @@ describe 'Dashboard Groups page', js: true, feature: true do fill_in 'filter_groups', with: group.name wait_for_requests - fill_in 'filter_groups', with: "" + fill_in 'filter_groups', with: '' wait_for_requests expect(page).to have_content(group.full_name) diff --git a/spec/features/dashboard/issuables_counter_spec.rb b/spec/features/dashboard/issuables_counter_spec.rb index 285724f4b48..6b666934563 100644 --- a/spec/features/dashboard/issuables_counter_spec.rb +++ b/spec/features/dashboard/issuables_counter_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Navigation bar counter', feature: true, caching: true do +describe 'Navigation bar counter', :use_clean_rails_memory_store_caching, feature: true do let(:user) { create(:user) } let(:project) { create(:empty_project, namespace: user.namespace) } let(:issue) { create(:issue, project: project) } diff --git a/spec/features/dashboard_issues_spec.rb b/spec/features/dashboard/issues_filter_spec.rb index f235fef1aa4..9b84f67b555 100644 --- a/spec/features/dashboard_issues_spec.rb +++ b/spec/features/dashboard/issues_filter_spec.rb @@ -1,21 +1,23 @@ require 'spec_helper' -describe "Dashboard Issues filtering", feature: true, js: true do +feature 'Dashboard Issues filtering', js: true do + include SortingHelper + let(:user) { create(:user) } let(:project) { create(:empty_project) } let(:milestone) { create(:milestone, project: project) } - context 'filtering by milestone' do - before do - project.team << [user, :master] - sign_in(user) + let!(:issue) { create(:issue, project: project, author: user, assignees: [user]) } + let!(:issue2) { create(:issue, project: project, author: user, assignees: [user], milestone: milestone) } - create(:issue, project: project, author: user, assignees: [user]) - create(:issue, project: project, author: user, assignees: [user], milestone: milestone) + before do + project.add_master(user) + sign_in(user) - visit_issues - end + visit_issues + end + context 'filtering by milestone' do it 'shows all issues with no milestone' do show_milestone_dropdown @@ -62,6 +64,46 @@ describe "Dashboard Issues filtering", feature: true, js: true do end end + context 'filtering by label' do + let(:label) { create(:label, project: project) } + let!(:label_link) { create(:label_link, label: label, target: issue) } + + it 'shows all issues without filter' do + page.within 'ul.content-list' do + expect(page).to have_content issue.title + expect(page).to have_content issue2.title + end + end + + it 'shows all issues with the selected label' do + page.within '.labels-filter' do + find('.dropdown').click + click_link label.title + end + + page.within 'ul.content-list' do + expect(page).to have_content issue.title + expect(page).not_to have_content issue2.title + end + end + end + + context 'sorting' do + it 'shows sorted issues' do + sorting_by('Oldest updated') + visit_issues + + expect(find('.issues-filters')).to have_content('Oldest updated') + end + + it 'keeps sorting issues after visiting Projects Issues page' do + sorting_by('Oldest updated') + visit project_issues_path(project) + + expect(find('.issues-filters')).to have_content('Oldest updated') + end + end + def show_milestone_dropdown click_button 'Milestone' expect(page).to have_selector('.dropdown-content', visible: true) diff --git a/spec/features/dashboard/issues_spec.rb b/spec/features/dashboard/issues_spec.rb index 86ac24ea06e..69c1a2ed89a 100644 --- a/spec/features/dashboard/issues_spec.rb +++ b/spec/features/dashboard/issues_spec.rb @@ -62,7 +62,7 @@ RSpec.describe 'Dashboard Issues', feature: true do it 'state filter tabs work' do find('#state-closed').click - expect(page).to have_current_path(issues_dashboard_url(assignee_id: current_user.id, scope: 'all', state: 'closed'), url: true) + expect(page).to have_current_path(issues_dashboard_url(assignee_id: current_user.id, state: 'closed'), url: true) end it_behaves_like "it has an RSS button with current_user's RSS token" diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb index bb1fb5b3feb..42d6fadc0c1 100644 --- a/spec/features/dashboard/merge_requests_spec.rb +++ b/spec/features/dashboard/merge_requests_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' feature 'Dashboard Merge Requests' do include FilterItemSelectHelper + include SortingHelper let(:current_user) { create :user } let(:project) { create(:empty_project) } @@ -109,5 +110,21 @@ feature 'Dashboard Merge Requests' do expect(page).to have_content(assigned_merge_request_from_fork.title) expect(page).to have_content(other_merge_request.title) end + + it 'shows sorted merge requests' do + sorting_by('Oldest updated') + + visit merge_requests_dashboard_path(assignee_id: current_user.id) + + expect(find('.issues-filters')).to have_content('Oldest updated') + end + + it 'keeps sorting merge requests after visiting Projects MR page' do + sorting_by('Oldest updated') + + visit project_merge_requests_path(project) + + expect(find('.issues-filters')).to have_content('Oldest updated') + end end end diff --git a/spec/features/dashboard_milestones_spec.rb b/spec/features/dashboard/milestones_spec.rb index 7a6a448d4c2..7a6a448d4c2 100644 --- a/spec/features/dashboard_milestones_spec.rb +++ b/spec/features/dashboard/milestones_spec.rb diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb index 7ca002fc821..abb9e5eef96 100644 --- a/spec/features/dashboard/projects_spec.rb +++ b/spec/features/dashboard/projects_spec.rb @@ -61,7 +61,7 @@ feature 'Dashboard Projects' do end end - describe 'with a pipeline', redis: true do + describe 'with a pipeline', clean_gitlab_redis_shared_state: true do let(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit.sha) } before do @@ -74,7 +74,50 @@ feature 'Dashboard Projects' do it 'shows that the last pipeline passed' do visit dashboard_projects_path - expect(page).to have_xpath("//a[@href='#{pipelines_project_commit_path(project, project.commit)}']") + page.within('.controls') do + expect(page).to have_xpath("//a[@href='#{pipelines_project_commit_path(project, project.commit)}']") + expect(page).to have_css('.ci-status-link') + expect(page).to have_css('.ci-status-icon-success') + expect(page).to have_link('Commit: passed') + end + end + end + + context 'last push widget' do + let(:push_event_data) do + { + before: Gitlab::Git::BLANK_SHA, + after: '0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e', + ref: 'refs/heads/feature', + user_id: user.id, + user_name: user.name, + repository: { + name: project.name, + url: 'localhost/rubinius', + description: '', + homepage: 'localhost/rubinius', + private: true + } + } + end + let!(:push_event) { create(:event, :pushed, data: push_event_data, project: project, author: user) } + + before do + visit dashboard_projects_path + end + + scenario 'shows "Create merge request" button' do + expect(page).to have_content 'You pushed to feature' + + within('#content-body') do + find_link('Create merge request', visible: false).click + end + + expect(page).to have_selector('.merge-request-form') + expect(current_path).to eq project_new_merge_request_path(project) + expect(find('#merge_request_target_project_id').value).to eq project.id.to_s + expect(find('input#merge_request_source_branch').value).to eq 'feature' + expect(find('input#merge_request_target_branch').value).to eq 'master' end end end diff --git a/spec/features/groups/members/sort_members_spec.rb b/spec/features/groups/members/sort_members_spec.rb index 169827f5d0d..92ff45e0cdc 100644 --- a/spec/features/groups/members/sort_members_spec.rb +++ b/spec/features/groups/members/sort_members_spec.rb @@ -68,7 +68,7 @@ feature 'Groups > Members > Sort members', feature: true do expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, descending') end - scenario 'sorts by recent sign in', :redis do + scenario 'sorts by recent sign in', :clean_gitlab_redis_shared_state do visit_members_list(sort: :recent_sign_in) expect(first_member).to include(owner.name) @@ -76,7 +76,7 @@ feature 'Groups > Members > Sort members', feature: true do expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in') end - scenario 'sorts by oldest sign in', :redis do + scenario 'sorts by oldest sign in', :clean_gitlab_redis_shared_state do visit_members_list(sort: :oldest_sign_in) expect(first_member).to include(developer.name) diff --git a/spec/features/issues/issue_detail_spec.rb b/spec/features/issues/issue_detail_spec.rb new file mode 100644 index 00000000000..e1c55d246ab --- /dev/null +++ b/spec/features/issues/issue_detail_spec.rb @@ -0,0 +1,43 @@ +require 'rails_helper' + +feature 'Issue Detail', js: true, feature: true do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + let(:issue) { create(:issue, project: project, author: user) } + + context 'when user displays the issue' do + before do + visit project_issue_path(project, issue) + wait_for_requests + end + + it 'shows the issue' do + page.within('.issuable-details') do + expect(find('h2')).to have_content(issue.title) + end + end + end + + context 'when edited by a user who is later deleted' do + before do + sign_in(user) + visit project_issue_path(project, issue) + wait_for_requests + + click_link 'Edit' + fill_in 'issue-title', with: 'issue title' + click_button 'Save' + + visit profile_account_path + click_link 'Delete account' + + visit project_issue_path(project, issue) + end + + it 'shows the issue' do + page.within('.issuable-details') do + expect(find('h2')).to have_content(issue.reload.title) + end + end + end +end diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb index a8055b21cee..2a2213b67ed 100644 --- a/spec/features/login_spec.rb +++ b/spec/features/login_spec.rb @@ -41,7 +41,7 @@ feature 'Login', feature: true do expect(page).to have_content('Your account has been blocked.') end - it 'does not update Devise trackable attributes', :redis do + it 'does not update Devise trackable attributes', :clean_gitlab_redis_shared_state do user = create(:user, :blocked) expect { gitlab_sign_in(user) }.not_to change { user.reload.sign_in_count } @@ -55,7 +55,7 @@ feature 'Login', feature: true do expect(page).to have_content('Invalid Login or password.') end - it 'does not update Devise trackable attributes', :redis do + it 'does not update Devise trackable attributes', :clean_gitlab_redis_shared_state do expect { gitlab_sign_in(User.ghost) }.not_to change { User.ghost.reload.sign_in_count } end end diff --git a/spec/features/merge_requests/conflicts_spec.rb b/spec/features/merge_requests/conflicts_spec.rb index 3e01ea69122..5c0909b6a59 100644 --- a/spec/features/merge_requests/conflicts_spec.rb +++ b/spec/features/merge_requests/conflicts_spec.rb @@ -4,6 +4,11 @@ feature 'Merge request conflict resolution', js: true, feature: true do let(:user) { create(:user) } let(:project) { create(:project) } + before do + # In order to have the diffs collapsed, we need to disable the increase feature + stub_feature_flags(gitlab_git_diff_size_limit_increase: false) + end + def create_merge_request(source_branch) create(:merge_request, source_branch: source_branch, target_branch: 'conflict-start', source_project: project) do |mr| mr.mark_as_unmergeable diff --git a/spec/features/merge_requests/filter_merge_requests_spec.rb b/spec/features/merge_requests/filter_merge_requests_spec.rb index 2a161b83aa0..e8085ec36aa 100644 --- a/spec/features/merge_requests/filter_merge_requests_spec.rb +++ b/spec/features/merge_requests/filter_merge_requests_spec.rb @@ -132,19 +132,13 @@ describe 'Filter merge requests', feature: true do end end - describe 'for assignee and label from issues#index' do + describe 'for assignee and label from mr#index' do let(:search_query) { "assignee:@#{user.username} label:~#{label.title}" } before do - input_filtered_search("assignee:@#{user.username}") - - expect_mr_list_count(1) - expect_tokens([{ name: 'assignee', value: "@#{user.username}" }]) - expect_filtered_search_input_empty - - input_filtered_search_keys("label:~#{label.title}") + input_filtered_search(search_query) - expect_mr_list_count(1) + expect_mr_list_count(0) end context 'assignee and label', js: true do diff --git a/spec/features/participants_autocomplete_spec.rb b/spec/features/participants_autocomplete_spec.rb index 382d83ca051..81b0a2f541b 100644 --- a/spec/features/participants_autocomplete_spec.rb +++ b/spec/features/participants_autocomplete_spec.rb @@ -54,7 +54,8 @@ feature 'Member autocomplete', :js do let(:note) { create(:note_on_commit, project: project, commit_id: project.commit.id) } before do - allow_any_instance_of(Commit).to receive(:author).and_return(author) + allow(User).to receive(:find_by_any_email) + .with(noteable.author_email.downcase).and_return(author) visit project_commit_path(project, noteable) end diff --git a/spec/features/profiles/password_spec.rb b/spec/features/profiles/password_spec.rb index 67975a68ee2..26d6d6658aa 100644 --- a/spec/features/profiles/password_spec.rb +++ b/spec/features/profiles/password_spec.rb @@ -1,44 +1,74 @@ require 'spec_helper' describe 'Profile > Password', feature: true do - let(:user) { create(:user, password_automatically_set: true) } + context 'Password authentication enabled' do + let(:user) { create(:user, password_automatically_set: true) } - before do - sign_in(user) - visit edit_profile_password_path - end + before do + sign_in(user) + visit edit_profile_password_path + end - def fill_passwords(password, confirmation) - fill_in 'New password', with: password - fill_in 'Password confirmation', with: confirmation + def fill_passwords(password, confirmation) + fill_in 'New password', with: password + fill_in 'Password confirmation', with: confirmation - click_button 'Save password' - end + click_button 'Save password' + end + + context 'User with password automatically set' do + describe 'User puts different passwords in the field and in the confirmation' do + it 'shows an error message' do + fill_passwords('mypassword', 'mypassword2') - context 'User with password automatically set' do - describe 'User puts different passwords in the field and in the confirmation' do - it 'shows an error message' do - fill_passwords('mypassword', 'mypassword2') + page.within('.alert-danger') do + expect(page).to have_content("Password confirmation doesn't match Password") + end + end + + it 'does not contain the current password field after an error' do + fill_passwords('mypassword', 'mypassword2') - page.within('.alert-danger') do - expect(page).to have_content("Password confirmation doesn't match Password") + expect(page).to have_no_field('user[current_password]') end end - it 'does not contain the current password field after an error' do - fill_passwords('mypassword', 'mypassword2') + describe 'User puts the same passwords in the field and in the confirmation' do + it 'shows a success message' do + fill_passwords('mypassword', 'mypassword') - expect(page).to have_no_field('user[current_password]') + page.within('.flash-notice') do + expect(page).to have_content('Password was successfully updated. Please login with it') + end + end end end + end - describe 'User puts the same passwords in the field and in the confirmation' do - it 'shows a success message' do - fill_passwords('mypassword', 'mypassword') + context 'Password authentication unavailable' do + before do + gitlab_sign_in(user) + end - page.within('.flash-notice') do - expect(page).to have_content('Password was successfully updated. Please login with it') - end + context 'Regular user' do + let(:user) { create(:user) } + + it 'renders 404 when sign-in is disabled' do + stub_application_setting(password_authentication_enabled: false) + + visit edit_profile_password_path + + expect(page).to have_http_status(404) + end + end + + context 'LDAP user' do + let(:user) { create(:omniauth_user, provider: 'ldapmain') } + + it 'renders 404' do + visit edit_profile_password_path + + expect(page).to have_http_status(404) end end end diff --git a/spec/features/profiles/personal_access_tokens_spec.rb b/spec/features/profiles/personal_access_tokens_spec.rb index 44b7ee101c9..3c08b6bc091 100644 --- a/spec/features/profiles/personal_access_tokens_spec.rb +++ b/spec/features/profiles/personal_access_tokens_spec.rb @@ -7,8 +7,8 @@ describe 'Profile > Personal Access Tokens', feature: true, js: true do find(".table.active-tokens") end - def inactive_personal_access_tokens - find(".table.inactive-tokens") + def no_personal_access_tokens_message + find(".settings-message") end def created_personal_access_token @@ -80,14 +80,16 @@ describe 'Profile > Personal Access Tokens', feature: true, js: true do visit profile_personal_access_tokens_path click_on "Revoke" - expect(inactive_personal_access_tokens).to have_text(personal_access_token.name) + expect(page).to have_selector(".settings-message") + expect(no_personal_access_tokens_message).to have_text("This user has no active Personal Access Tokens.") end - it "moves expired tokens to the 'inactive' section" do + it "removes expired tokens from 'active' section" do personal_access_token.update(expires_at: 5.days.ago) visit profile_personal_access_tokens_path - expect(inactive_personal_access_tokens).to have_text(personal_access_token.name) + expect(page).to have_selector(".settings-message") + expect(no_personal_access_tokens_message).to have_text("This user has no active Personal Access Tokens.") end context "when revocation fails" do diff --git a/spec/features/projects/branches_spec.rb b/spec/features/projects/branches_spec.rb index 4fae324d8d5..d18cd3d6adc 100644 --- a/spec/features/projects/branches_spec.rb +++ b/spec/features/projects/branches_spec.rb @@ -24,7 +24,6 @@ describe 'Branches', feature: true do repository.branches_sorted_by(:name).first(20).each do |branch| expect(page).to have_content("#{branch.name}") end - expect(page).to have_content("Protected branches can be managed in project settings") end it 'sorts the branches by name' do @@ -130,6 +129,14 @@ describe 'Branches', feature: true do project.team << [user, :master] end + describe 'Initial branches page' do + it 'shows description for admin' do + visit project_branches_path(project) + + expect(page).to have_content("Protected branches can be managed in project settings") + end + end + describe 'Delete protected branch' do before do visit project_protected_branches_path(project) diff --git a/spec/features/projects/diffs/diff_show_spec.rb b/spec/features/projects/diffs/diff_show_spec.rb index b528b283495..4baccb24806 100644 --- a/spec/features/projects/diffs/diff_show_spec.rb +++ b/spec/features/projects/diffs/diff_show_spec.rb @@ -110,6 +110,10 @@ feature 'Diff file viewer', :js, feature: true do context 'binary file that appears to be text in the first 1024 bytes' do before do + # The file we're visiting is smaller than 10 KB and we want it collapsed + # so we need to disable the size increase feature. + stub_feature_flags(gitlab_git_diff_size_limit_increase: false) + visit_commit('7b1cf4336b528e0f3d1d140ee50cafdbc703597c') end diff --git a/spec/features/projects/issuable_counts_caching_spec.rb b/spec/features/projects/issuable_counts_caching_spec.rb new file mode 100644 index 00000000000..703d1cbd327 --- /dev/null +++ b/spec/features/projects/issuable_counts_caching_spec.rb @@ -0,0 +1,132 @@ +require 'spec_helper' + +describe 'Issuable counts caching', :use_clean_rails_memory_store_caching do + let!(:member) { create(:user) } + let!(:member_2) { create(:user) } + let!(:non_member) { create(:user) } + let!(:project) { create(:empty_project, :public) } + let!(:open_issue) { create(:issue, project: project) } + let!(:confidential_issue) { create(:issue, :confidential, project: project, author: non_member) } + let!(:closed_issue) { create(:issue, :closed, project: project) } + + before do + project.add_developer(member) + project.add_developer(member_2) + end + + it 'caches issuable counts correctly for non-members' do + # We can't use expect_any_instance_of because that uses a single instance. + counts = 0 + + allow_any_instance_of(IssuesFinder).to receive(:count_by_state).and_wrap_original do |m, *args| + counts += 1 + + m.call(*args) + end + + aggregate_failures 'only counts once on first load with no params, and caches for later loads' do + expect { visit project_issues_path(project) } + .to change { counts }.by(1) + + expect { visit project_issues_path(project) } + .not_to change { counts } + end + + aggregate_failures 'uses counts from cache on load from non-member' do + sign_in(non_member) + + expect { visit project_issues_path(project) } + .not_to change { counts } + + sign_out(non_member) + end + + aggregate_failures 'does not use the same cache for a member' do + sign_in(member) + + expect { visit project_issues_path(project) } + .to change { counts }.by(1) + + sign_out(member) + end + + aggregate_failures 'uses the same cache for all members' do + sign_in(member_2) + + expect { visit project_issues_path(project) } + .not_to change { counts } + + sign_out(member_2) + end + + aggregate_failures 'shares caches when params are passed' do + expect { visit project_issues_path(project, author_username: non_member.username) } + .to change { counts }.by(1) + + sign_in(member) + + expect { visit project_issues_path(project, author_username: non_member.username) } + .to change { counts }.by(1) + + sign_in(non_member) + + expect { visit project_issues_path(project, author_username: non_member.username) } + .not_to change { counts } + + sign_in(member_2) + + expect { visit project_issues_path(project, author_username: non_member.username) } + .not_to change { counts } + + sign_out(member_2) + end + + aggregate_failures 'resets caches on issue close' do + Issues::CloseService.new(project, member).execute(open_issue) + + expect { visit project_issues_path(project) } + .to change { counts }.by(1) + + sign_in(member) + + expect { visit project_issues_path(project) } + .to change { counts }.by(1) + + sign_in(non_member) + + expect { visit project_issues_path(project) } + .not_to change { counts } + + sign_in(member_2) + + expect { visit project_issues_path(project) } + .not_to change { counts } + + sign_out(member_2) + end + + aggregate_failures 'does not reset caches on issue update' do + Issues::UpdateService.new(project, member, title: 'new title').execute(open_issue) + + expect { visit project_issues_path(project) } + .not_to change { counts } + + sign_in(member) + + expect { visit project_issues_path(project) } + .not_to change { counts } + + sign_in(non_member) + + expect { visit project_issues_path(project) } + .not_to change { counts } + + sign_in(member_2) + + expect { visit project_issues_path(project) } + .not_to change { counts } + + sign_out(member_2) + end + end +end diff --git a/spec/features/projects/members/sorting_spec.rb b/spec/features/projects/members/sorting_spec.rb index afb613f034e..dc7236fa120 100644 --- a/spec/features/projects/members/sorting_spec.rb +++ b/spec/features/projects/members/sorting_spec.rb @@ -67,7 +67,7 @@ feature 'Projects > Members > Sorting', feature: true do expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, descending') end - scenario 'sorts by recent sign in', :redis do + scenario 'sorts by recent sign in', :clean_gitlab_redis_shared_state do visit_members_list(sort: :recent_sign_in) expect(first_member).to include(master.name) @@ -75,7 +75,7 @@ feature 'Projects > Members > Sorting', feature: true do expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in') end - scenario 'sorts by oldest sign in', :redis do + scenario 'sorts by oldest sign in', :clean_gitlab_redis_shared_state do visit_members_list(sort: :oldest_sign_in) expect(first_member).to include(developer.name) diff --git a/spec/features/projects/merge_request_button_spec.rb b/spec/features/projects/merge_request_button_spec.rb index 12b4747602d..8cbd26551bc 100644 --- a/spec/features/projects/merge_request_button_spec.rb +++ b/spec/features/projects/merge_request_button_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Merge Request button', feature: true do +feature 'Merge Request button' do shared_examples 'Merge request button only shown when allowed' do let(:user) { create(:user) } let(:project) { create(:project, :public) } @@ -10,16 +10,14 @@ feature 'Merge Request button', feature: true do it 'does not show Create merge request button' do visit url - within("#content-body") do - expect(page).not_to have_link(label) - end + expect(page).not_to have_link(label) end end context 'logged in as developer' do before do sign_in(user) - project.team << [user, :developer] + project.add_developer(user) end it 'shows Create merge request button' do @@ -29,7 +27,7 @@ feature 'Merge Request button', feature: true do visit url - within("#content-body") do + within('#content-body') do expect(page).to have_link(label, href: href) end end @@ -42,7 +40,7 @@ feature 'Merge Request button', feature: true do it 'does not show Create merge request button' do visit url - within("#content-body") do + within('#content-body') do expect(page).not_to have_link(label) end end @@ -57,7 +55,7 @@ feature 'Merge Request button', feature: true do it 'does not show Create merge request button' do visit url - within("#content-body") do + within('#content-body') do expect(page).not_to have_link(label) end end diff --git a/spec/features/projects/no_password_spec.rb b/spec/features/projects/no_password_spec.rb index 53ac18fa7cc..d22a6daac08 100644 --- a/spec/features/projects/no_password_spec.rb +++ b/spec/features/projects/no_password_spec.rb @@ -30,7 +30,7 @@ feature 'No Password Alert' do let(:user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'saml') } before do - stub_application_setting(signin_enabled?: false) + stub_application_setting(password_authentication_enabled?: false) stub_omniauth_saml_config(enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'], providers: [mock_saml_config]) end diff --git a/spec/features/projects/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb index 992a68b25a5..033ccf06124 100644 --- a/spec/features/projects/pipeline_schedules_spec.rb +++ b/spec/features/projects/pipeline_schedules_spec.rb @@ -9,188 +9,222 @@ feature 'Pipeline Schedules', :feature, js: true do let(:scope) { nil } let!(:user) { create(:user) } - before do - project.add_master(user) - sign_in(user) - end - - describe 'GET /projects/pipeline_schedules' do + context 'logged in as master' do before do - visit_pipelines_schedules + project.add_master(user) + gitlab_sign_in(user) end - describe 'The view' do - it 'displays the required information description' do - page.within('.pipeline-schedule-table-row') do - expect(page).to have_content('pipeline schedule') - expect(find(".next-run-cell time")['data-original-title']) - .to include(pipeline_schedule.real_next_run.strftime('%b %-d, %Y')) - expect(page).to have_link('master') - expect(page).to have_link("##{pipeline.id}") - end + describe 'GET /projects/pipeline_schedules' do + before do + visit_pipelines_schedules end - it 'creates a new scheduled pipeline' do - click_link 'New schedule' + describe 'The view' do + it 'displays the required information description' do + page.within('.pipeline-schedule-table-row') do + expect(page).to have_content('pipeline schedule') + expect(find(".next-run-cell time")['data-original-title']) + .to include(pipeline_schedule.real_next_run.strftime('%b %-d, %Y')) + expect(page).to have_link('master') + expect(page).to have_link("##{pipeline.id}") + end + end - expect(page).to have_content('Schedule a new pipeline') - end + it 'creates a new scheduled pipeline' do + click_link 'New schedule' - it 'changes ownership of the pipeline' do - click_link 'Take ownership' - page.within('.pipeline-schedule-table-row') do - expect(page).not_to have_content('No owner') - expect(page).to have_link('John Doe') + expect(page).to have_content('Schedule a new pipeline') end - end - it 'edits the pipeline' do - page.within('.pipeline-schedule-table-row') do - click_link 'Edit' + it 'changes ownership of the pipeline' do + click_link 'Take ownership' + page.within('.pipeline-schedule-table-row') do + expect(page).not_to have_content('No owner') + expect(page).to have_link('John Doe') + end end - expect(page).to have_content('Edit Pipeline Schedule') + it 'edits the pipeline' do + page.within('.pipeline-schedule-table-row') do + click_link 'Edit' + end + + expect(page).to have_content('Edit Pipeline Schedule') + end + + it 'deletes the pipeline' do + click_link 'Delete' + + expect(page).not_to have_css(".pipeline-schedule-table-row") + end end - it 'deletes the pipeline' do - click_link 'Delete' + context 'when ref is nil' do + before do + pipeline_schedule.update_attribute(:ref, nil) + visit_pipelines_schedules + end - expect(page).not_to have_css(".pipeline-schedule-table-row") + it 'shows a list of the pipeline schedules with empty ref column' do + expect(first('.branch-name-cell').text).to eq('') + end end end - context 'when ref is nil' do + describe 'POST /projects/pipeline_schedules/new' do before do - pipeline_schedule.update_attribute(:ref, nil) - visit_pipelines_schedules + visit_new_pipeline_schedule end - it 'shows a list of the pipeline schedules with empty ref column' do - expect(first('.branch-name-cell').text).to eq('') + it 'sets defaults for timezone and target branch' do + expect(page).to have_button('master') + expect(page).to have_button('UTC') end - end - end - describe 'POST /projects/pipeline_schedules/new' do - before do - visit_new_pipeline_schedule - end + it 'it creates a new scheduled pipeline' do + fill_in_schedule_form + save_pipeline_schedule - it 'sets defaults for timezone and target branch' do - expect(page).to have_button('master') - expect(page).to have_button('UTC') - end + expect(page).to have_content('my fancy description') + end - it 'it creates a new scheduled pipeline' do - fill_in_schedule_form - save_pipeline_schedule + it 'it prevents an invalid form from being submitted' do + save_pipeline_schedule - expect(page).to have_content('my fancy description') + expect(page).to have_content('This field is required') + end end - it 'it prevents an invalid form from being submitted' do - save_pipeline_schedule + describe 'PATCH /projects/pipelines_schedules/:id/edit' do + before do + edit_pipeline_schedule + end - expect(page).to have_content('This field is required') - end - end + it 'it displays existing properties' do + description = find_field('schedule_description').value + expect(description).to eq('pipeline schedule') + expect(page).to have_button('master') + expect(page).to have_button('UTC') + end - describe 'PATCH /projects/pipelines_schedules/:id/edit' do - before do - edit_pipeline_schedule - end + it 'edits the scheduled pipeline' do + fill_in 'schedule_description', with: 'my brand new description' - it 'it displays existing properties' do - description = find_field('schedule_description').value - expect(description).to eq('pipeline schedule') - expect(page).to have_button('master') - expect(page).to have_button('UTC') - end + save_pipeline_schedule - it 'edits the scheduled pipeline' do - fill_in 'schedule_description', with: 'my brand new description' + expect(page).to have_content('my brand new description') + end - save_pipeline_schedule + context 'when ref is nil' do + before do + pipeline_schedule.update_attribute(:ref, nil) + edit_pipeline_schedule + end - expect(page).to have_content('my brand new description') + it 'shows the pipeline schedule with default ref' do + page.within('.js-target-branch-dropdown') do + expect(first('.dropdown-toggle-text').text).to eq('master') + end + end + end end - context 'when ref is nil' do - before do - pipeline_schedule.update_attribute(:ref, nil) - edit_pipeline_schedule + context 'when user creates a new pipeline schedule with variables' do + background do + visit_pipelines_schedules + click_link 'New schedule' + fill_in_schedule_form + all('[name="schedule[variables_attributes][][key]"]')[0].set('AAA') + all('[name="schedule[variables_attributes][][value]"]')[0].set('AAA123') + all('[name="schedule[variables_attributes][][key]"]')[1].set('BBB') + all('[name="schedule[variables_attributes][][value]"]')[1].set('BBB123') + save_pipeline_schedule end - it 'shows the pipeline schedule with default ref' do - page.within('.js-target-branch-dropdown') do - expect(first('.dropdown-toggle-text').text).to eq('master') + scenario 'user sees the new variable in edit window' do + find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click + page.within('.pipeline-variable-list') do + expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-key-input").value).to eq('AAA') + expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-value-input").value).to eq('AAA123') + expect(find(".pipeline-variable-row:nth-child(2) .pipeline-variable-key-input").value).to eq('BBB') + expect(find(".pipeline-variable-row:nth-child(2) .pipeline-variable-value-input").value).to eq('BBB123') end end end - end - context 'when user creates a new pipeline schedule with variables' do - background do - visit_pipelines_schedules - click_link 'New schedule' - fill_in_schedule_form - all('[name="schedule[variables_attributes][][key]"]')[0].set('AAA') - all('[name="schedule[variables_attributes][][value]"]')[0].set('AAA123') - all('[name="schedule[variables_attributes][][key]"]')[1].set('BBB') - all('[name="schedule[variables_attributes][][value]"]')[1].set('BBB123') - save_pipeline_schedule - end + context 'when user edits a variable of a pipeline schedule' do + background do + create(:ci_pipeline_schedule, project: project, owner: user).tap do |pipeline_schedule| + create(:ci_pipeline_schedule_variable, key: 'AAA', value: 'AAA123', pipeline_schedule: pipeline_schedule) + end - scenario 'user sees the new variable in edit window' do - find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click - page.within('.pipeline-variable-list') do - expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-key-input").value).to eq('AAA') - expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-value-input").value).to eq('AAA123') - expect(find(".pipeline-variable-row:nth-child(2) .pipeline-variable-key-input").value).to eq('BBB') - expect(find(".pipeline-variable-row:nth-child(2) .pipeline-variable-value-input").value).to eq('BBB123') + visit_pipelines_schedules + find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click + all('[name="schedule[variables_attributes][][key]"]')[0].set('foo') + all('[name="schedule[variables_attributes][][value]"]')[0].set('bar') + click_button 'Save pipeline schedule' end - end - end - context 'when user edits a variable of a pipeline schedule' do - background do - create(:ci_pipeline_schedule, project: project, owner: user).tap do |pipeline_schedule| - create(:ci_pipeline_schedule_variable, key: 'AAA', value: 'AAA123', pipeline_schedule: pipeline_schedule) + scenario 'user sees the updated variable in edit window' do + find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click + page.within('.pipeline-variable-list') do + expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-key-input").value).to eq('foo') + expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-value-input").value).to eq('bar') + end end - - visit_pipelines_schedules - find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click - all('[name="schedule[variables_attributes][][key]"]')[0].set('foo') - all('[name="schedule[variables_attributes][][value]"]')[0].set('bar') - click_button 'Save pipeline schedule' end - scenario 'user sees the updated variable in edit window' do - find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click - page.within('.pipeline-variable-list') do - expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-key-input").value).to eq('foo') - expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-value-input").value).to eq('bar') + context 'when user removes a variable of a pipeline schedule' do + background do + create(:ci_pipeline_schedule, project: project, owner: user).tap do |pipeline_schedule| + create(:ci_pipeline_schedule_variable, key: 'AAA', value: 'AAA123', pipeline_schedule: pipeline_schedule) + end + + visit_pipelines_schedules + find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click + find('.pipeline-variable-list .pipeline-variable-row-remove-button').click + click_button 'Save pipeline schedule' + end + + scenario 'user does not see the removed variable in edit window' do + find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click + page.within('.pipeline-variable-list') do + expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-key-input").value).to eq('') + expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-value-input").value).to eq('') + end end end end - context 'when user removes a variable of a pipeline schedule' do - background do - create(:ci_pipeline_schedule, project: project, owner: user).tap do |pipeline_schedule| - create(:ci_pipeline_schedule_variable, key: 'AAA', value: 'AAA123', pipeline_schedule: pipeline_schedule) + context 'logged in as non-member' do + before do + gitlab_sign_in(user) + end + + describe 'GET /projects/pipeline_schedules' do + before do + visit_pipelines_schedules end - visit_pipelines_schedules - find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click - find('.pipeline-variable-list .pipeline-variable-row-remove-button').click - click_button 'Save pipeline schedule' + describe 'The view' do + it 'does not show create schedule button' do + expect(page).not_to have_link('New schedule') + end + end end + end + + context 'not logged in' do + describe 'GET /projects/pipeline_schedules' do + before do + visit_pipelines_schedules + end - scenario 'user does not see the removed variable in edit window' do - find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click - page.within('.pipeline-variable-list') do - expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-key-input").value).to eq('') - expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-value-input").value).to eq('') + describe 'The view' do + it 'does not show create schedule button' do + expect(page).not_to have_link('New schedule') + end end end end diff --git a/spec/features/projects/user_browses_files_spec.rb b/spec/features/projects/user_browses_files_spec.rb new file mode 100644 index 00000000000..263a3a29a66 --- /dev/null +++ b/spec/features/projects/user_browses_files_spec.rb @@ -0,0 +1,188 @@ +require 'spec_helper' + +describe 'User browses files' do + include DropzoneHelper + + let(:fork_message) do + "You're not allowed to make changes to this project directly. "\ + "A fork of this project has been created that you can make changes in, so you can submit a merge request." + end + let(:project) { create(:project, name: 'Shop') } + let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') } + let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) } + let(:tree_path_ref_6d39438) { project_tree_path(project, '6d39438') } + let(:tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) } + let(:user) { create(:user) } + + before do + project.team << [user, :master] + sign_in(user) + end + + context 'when browsing the master branch' do + before do + visit(tree_path_root_ref) + end + + it 'shows files from a repository' do + expect(page).to have_content('VERSION') + expect(page).to have_content('.gitignore') + expect(page).to have_content('LICENSE') + end + + it 'shows the "Browse Directory" link' do + click_link('files') + click_link('History') + + expect(page).to have_link('Browse Directory') + expect(page).not_to have_link('Browse Code') + end + + it 'shows the "Browse File" link' do + page.within('.tree-table') do + click_link('README.md') + end + click_link('History') + + expect(page).to have_link('Browse File') + expect(page).not_to have_link('Browse Files') + end + + it 'shows the "Browse Code" link' do + click_link('History') + + expect(page).to have_link('Browse Files') + expect(page).not_to have_link('Browse Directory') + end + + it 'redirects to the permalink URL' do + click_link('.gitignore') + click_link('Permalink') + + permalink_path = project_blob_path(project, "#{project.repository.commit.sha}/.gitignore") + + expect(current_path).to eq(permalink_path) + end + end + + context 'when browsing a specific ref' do + before do + visit(tree_path_ref_6d39438) + end + + it 'shows files from a repository for "6d39438"' do + expect(current_path).to eq(tree_path_ref_6d39438) + expect(page).to have_content('.gitignore') + expect(page).to have_content('LICENSE') + end + + it 'shows files from a repository with apostroph in its name', js: true do + first('.js-project-refs-dropdown').click + + page.within('.project-refs-form') do + click_link("'test'") + end + + expect(page).to have_selector('.dropdown-toggle-text', text: "'test'") + + visit(project_tree_path(project, "'test'")) + + expect(page).to have_css('.tree-commit-link', visible: true) + expect(page).not_to have_content('Loading commit data...') + end + + it 'shows the code with a leading dot in the directory', js: true do + first('.js-project-refs-dropdown').click + + page.within('.project-refs-form') do + click_link('fix') + end + + visit(project_tree_path(project, 'fix/.testdir')) + + expect(page).to have_css('.tree-commit-link', visible: true) + expect(page).not_to have_content('Loading commit data...') + end + + it 'does not show the permalink link' do + click_link('.gitignore') + + expect(page).not_to have_link('permalink') + end + end + + context 'when browsing a file content' do + before do + visit(tree_path_root_ref) + click_link('.gitignore') + end + + it 'shows a file content', js: true do + wait_for_requests + expect(page).to have_content('*.rbc') + end + end + + context 'when browsing a raw file' do + before do + visit(project_blob_path(project, File.join(RepoHelpers.sample_commit.id, RepoHelpers.sample_blob.path))) + end + + it 'shows a raw file content' do + click_link('Open raw') + expect(source).to eq('') # Body is filled in by gitlab-workhorse + end + end + + context 'when browsing an LFS object' do + before do + allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(true) + visit(project_tree_path(project, 'lfs')) + end + + it 'shows an LFS object' do + click_link('files') + click_link('lfs') + click_link('lfs_object.iso') + + expect(page).to have_content('Download (1.5 MB)') + expect(page).not_to have_content('version https://git-lfs.github.com/spec/v1') + expect(page).not_to have_content('oid sha256:91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897') + expect(page).not_to have_content('size 1575078') + + page.within('.content') do + expect(page).to have_content('Delete') + expect(page).to have_content('History') + expect(page).to have_content('Permalink') + expect(page).to have_content('Replace') + expect(page).not_to have_content('Annotate') + expect(page).not_to have_content('Blame') + expect(page).not_to have_content('Edit') + expect(page).to have_link('Download') + end + end + end + + context 'when previewing a file content' do + before do + visit(tree_path_root_ref) + end + + it 'shows a preview of a file content', js: true do + find('.add-to-tree').click + click_link('Upload file') + drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'logo_sample.svg')) + + page.within('#modal-upload-blob') do + fill_in(:commit_message, with: 'New commit message') + end + + fill_in(:branch_name, with: 'new_branch_name', visible: true) + click_button('Upload file') + + visit(project_blob_path(project, 'new_branch_name/logo_sample.svg')) + + expect(page).to have_css('.file-content img') + end + end +end diff --git a/spec/features/projects/user_create_dir_spec.rb b/spec/features/projects/user_create_dir_spec.rb deleted file mode 100644 index 5e302da8a63..00000000000 --- a/spec/features/projects/user_create_dir_spec.rb +++ /dev/null @@ -1,57 +0,0 @@ -require 'spec_helper' - -feature 'New directory creation', feature: true, js: true do - given(:user) { create(:user) } - given(:role) { :developer } - given(:project) { create(:project) } - - background do - sign_in(user) - project.team << [user, role] - visit project_tree_path(project, 'master') - open_new_directory_modal - fill_in 'dir_name', with: 'new_directory' - end - - def open_new_directory_modal - first('.add-to-tree').click - click_link 'New directory' - end - - def create_directory - click_button 'Create directory' - end - - context 'with default target branch' do - background do - create_directory - end - - scenario 'creates the directory in the default branch' do - expect(page).to have_content 'master' - expect(page).to have_content 'The directory has been successfully created' - expect(page).to have_content 'new_directory' - end - end - - context 'with a new target branch' do - given(:new_branch_name) { 'new-feature' } - - background do - fill_in :branch_name, with: new_branch_name - create_directory - end - - scenario 'creates the directory in the new branch' do - expect(page).to have_content new_branch_name - expect(page).to have_content 'The directory has been successfully created' - end - - scenario 'redirects to the merge request' do - expect(page).to have_content 'New Merge Request' - expect(page).to have_content "From #{new_branch_name} into master" - expect(page).to have_content 'Add new directory' - expect(current_path).to eq(project_new_merge_request_path(project)) - end - end -end diff --git a/spec/features/projects/user_creates_directory_spec.rb b/spec/features/projects/user_creates_directory_spec.rb new file mode 100644 index 00000000000..635bd4493dd --- /dev/null +++ b/spec/features/projects/user_creates_directory_spec.rb @@ -0,0 +1,87 @@ +require 'spec_helper' + +feature 'User creates a directory', js: true do + let(:fork_message) do + "You're not allowed to make changes to this project directly. "\ + "A fork of this project has been created that you can make changes in, so you can submit a merge request." + end + let(:project) { create(:project) } + let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') } + let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) } + let(:user) { create(:user) } + + before do + project.team << [user, :developer] + sign_in(user) + visit project_tree_path(project, 'master') + end + + context 'with default target branch' do + before do + first('.add-to-tree').click + click_link('New directory') + end + + it 'creates the directory in the default branch' do + fill_in(:dir_name, with: 'new_directory') + click_button('Create directory') + + expect(page).to have_content('master') + expect(page).to have_content('The directory has been successfully created') + expect(page).to have_content('new_directory') + end + + it 'does not create a directory with a name of already existed directory' do + fill_in(:dir_name, with: 'files') + fill_in(:commit_message, with: 'New commit message', visible: true) + click_button('Create directory') + + expect(page).to have_content('A directory with this name already exists') + expect(current_path).to eq(project_tree_path(project, 'master')) + end + end + + context 'with a new target branch' do + before do + first('.add-to-tree').click + click_link('New directory') + fill_in(:dir_name, with: 'new_directory') + fill_in(:branch_name, with: 'new-feature') + click_button('Create directory') + end + + it 'creates the directory in the new branch and redirect to the merge request' do + expect(page).to have_content('new-feature') + expect(page).to have_content('The directory has been successfully created') + expect(page).to have_content('New Merge Request') + expect(page).to have_content('From new-feature into master') + expect(page).to have_content('Add new directory') + + expect(current_path).to eq(project_new_merge_request_path(project)) + end + end + + context 'when an user does not have write access' do + before do + project2.team << [user, :reporter] + visit(project2_tree_path_root_ref) + end + + it 'creates a directory in a forked project' do + find('.add-to-tree').click + click_link('New directory') + + expect(page).to have_content(fork_message) + + find('.add-to-tree').click + click_link('New directory') + fill_in(:dir_name, with: 'new_directory') + fill_in(:commit_message, with: 'New commit message', visible: true) + click_button('Create directory') + + fork = user.fork_of(project2) + + expect(current_path).to eq(project_new_merge_request_path(fork)) + end + end +end diff --git a/spec/features/projects/user_creates_files_spec.rb b/spec/features/projects/user_creates_files_spec.rb new file mode 100644 index 00000000000..0c7f1a775c1 --- /dev/null +++ b/spec/features/projects/user_creates_files_spec.rb @@ -0,0 +1,153 @@ +require 'spec_helper' + +describe 'User creates files' do + let(:fork_message) do + "You're not allowed to make changes to this project directly. "\ + "A fork of this project has been created that you can make changes in, so you can submit a merge request." + end + let(:project) { create(:project, name: 'Shop') } + let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') } + let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) } + let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) } + let(:user) { create(:user) } + + before do + project.team << [user, :master] + sign_in(user) + end + + context 'without commiting a new file' do + context 'when an user has write access' do + before do + visit(project_tree_path_root_ref) + end + + it 'opens new file page' do + find('.add-to-tree').click + click_link('New file') + + expect(page).to have_content('New file') + expect(page).to have_content('Commit message') + end + end + + context 'when an user does not have write access' do + before do + project2.team << [user, :reporter] + visit(project2_tree_path_root_ref) + end + + it 'opens new file page on a forked project' do + find('.add-to-tree').click + click_link('New file') + + expect(page).to have_selector('.file-editor') + expect(page).to have_content(fork_message) + expect(page).to have_content('New file') + expect(page).to have_content('Commit message') + end + end + end + + context 'with commiting a new file' do + context 'when an user has write access' do + before do + visit(project_tree_path_root_ref) + + find('.add-to-tree').click + click_link('New file') + end + + it 'creates and commit a new file', js: true do + expect(page).to have_selector('.file-editor') + + execute_script("ace.edit('editor').setValue('*.rbca')") + fill_in(:file_name, with: 'not_a_file.md') + fill_in(:commit_message, with: 'New commit message', visible: true) + click_button('Commit changes') + + new_file_path = project_blob_path(project, 'master/not_a_file.md') + + expect(current_path).to eq(new_file_path) + + wait_for_requests + + expect(page).to have_content('*.rbca') + end + + it 'creates and commit a new file with new lines at the end of file', js: true do + execute_script('ace.edit("editor").setValue("Sample\n\n\n")') + fill_in(:file_name, with: 'not_a_file.md') + fill_in(:commit_message, with: 'New commit message', visible: true) + click_button('Commit changes') + + new_file_path = project_blob_path(project, 'master/not_a_file.md') + + expect(current_path).to eq(new_file_path) + + find('.js-edit-blob').click + + expect(evaluate_script('ace.edit("editor").getValue()')).to eq("Sample\n\n\n") + end + + it 'creates and commit a new file with a directory name', js: true do + fill_in(:file_name, with: 'foo/bar/baz.txt') + + expect(page).to have_selector('.file-editor') + + execute_script("ace.edit('editor').setValue('*.rbca')") + fill_in(:commit_message, with: 'New commit message', visible: true) + click_button('Commit changes') + + expect(current_path).to eq(project_blob_path(project, 'master/foo/bar/baz.txt')) + + wait_for_requests + + expect(page).to have_content('*.rbca') + end + + it 'creates and commit a new file specifying a new branch', js: true do + expect(page).to have_selector('.file-editor') + + execute_script("ace.edit('editor').setValue('*.rbca')") + fill_in(:file_name, with: 'not_a_file.md') + fill_in(:commit_message, with: 'New commit message', visible: true) + fill_in(:branch_name, with: 'new_branch_name', visible: true) + click_button('Commit changes') + + expect(current_path).to eq(project_new_merge_request_path(project)) + + click_link('Changes') + + wait_for_requests + + expect(page).to have_content('*.rbca') + end + end + + context 'when an user does not have write access' do + before do + project2.team << [user, :reporter] + visit(project2_tree_path_root_ref) + end + + it 'creates and commit new file in forked project', js: true do + find('.add-to-tree').click + click_link('New file') + + expect(page).to have_selector('.file-editor') + + execute_script("ace.edit('editor').setValue('*.rbca')") + + fill_in(:file_name, with: 'not_a_file.md') + fill_in(:commit_message, with: 'New commit message', visible: true) + click_button('Commit changes') + + fork = user.fork_of(project2) + + expect(current_path).to eq(project_new_merge_request_path(fork)) + expect(page).to have_content('New commit message') + end + end + end +end diff --git a/spec/features/projects/user_deletes_files_spec.rb b/spec/features/projects/user_deletes_files_spec.rb new file mode 100644 index 00000000000..97e60862b4f --- /dev/null +++ b/spec/features/projects/user_deletes_files_spec.rb @@ -0,0 +1,68 @@ +require 'spec_helper' + +describe 'User deletes files' do + let(:fork_message) do + "You're not allowed to make changes to this project directly. "\ + "A fork of this project has been created that you can make changes in, so you can submit a merge request." + end + let(:project) { create(:project, name: 'Shop') } + let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') } + let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) } + let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) } + let(:user) { create(:user) } + + before do + sign_in(user) + end + + context 'when an user has write access' do + before do + project.team << [user, :master] + visit(project_tree_path_root_ref) + end + + it 'deletes the file', js: true do + click_link('.gitignore') + + expect(page).to have_content('.gitignore') + + click_on('Delete') + fill_in(:commit_message, with: 'New commit message', visible: true) + click_button('Delete file') + + expect(current_path).to eq(project_tree_path(project, 'master')) + expect(page).not_to have_content('.gitignore') + end + end + + context 'when an user does not have write access' do + before do + project2.team << [user, :reporter] + visit(project2_tree_path_root_ref) + end + + it 'deletes the file in a forked project', js: true do + click_link('.gitignore') + + expect(page).to have_content('.gitignore') + + click_on('Delete') + + expect(page).to have_link('Fork') + expect(page).to have_button('Cancel') + + click_link('Fork') + + expect(page).to have_content(fork_message) + + click_on('Delete') + fill_in(:commit_message, with: 'New commit message', visible: true) + click_button('Delete file') + + fork = user.fork_of(project2) + + expect(current_path).to eq(project_new_merge_request_path(fork)) + expect(page).to have_content('New commit message') + end + end +end diff --git a/spec/features/projects/user_edits_files_spec.rb b/spec/features/projects/user_edits_files_spec.rb new file mode 100644 index 00000000000..eb26f1bc123 --- /dev/null +++ b/spec/features/projects/user_edits_files_spec.rb @@ -0,0 +1,122 @@ +require 'spec_helper' + +describe 'User edits files' do + let(:fork_message) do + "You're not allowed to make changes to this project directly. "\ + "A fork of this project has been created that you can make changes in, so you can submit a merge request." + end + let(:project) { create(:project, name: 'Shop') } + let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') } + let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) } + let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) } + let(:user) { create(:user) } + + before do + sign_in(user) + end + + context 'when an user has write access' do + before do + project.team << [user, :master] + visit(project_tree_path_root_ref) + end + + it 'inserts a content of a file', js: true do + click_link('.gitignore') + find('.js-edit-blob').click + execute_script("ace.edit('editor').setValue('*.rbca')") + + expect(evaluate_script('ace.edit("editor").getValue()')).to eq('*.rbca') + end + + it 'does not show the edit link if a file is binary' do + binary_file = File.join(project.repository.root_ref, 'files/images/logo-black.png') + visit(project_blob_path(project, binary_file)) + + expect(page).not_to have_link('edit') + end + + it 'commits an edited file', js: true do + click_link('.gitignore') + find('.js-edit-blob').click + execute_script("ace.edit('editor').setValue('*.rbca')") + fill_in(:commit_message, with: 'New commit message', visible: true) + click_button('Commit changes') + + expect(current_path).to eq(project_blob_path(project, 'master/.gitignore')) + + wait_for_requests + + expect(page).to have_content('*.rbca') + end + + it 'commits an edited file to a new branch', js: true do + click_link('.gitignore') + find('.js-edit-blob').click + execute_script("ace.edit('editor').setValue('*.rbca')") + fill_in(:commit_message, with: 'New commit message', visible: true) + fill_in(:branch_name, with: 'new_branch_name', visible: true) + click_button('Commit changes') + + expect(current_path).to eq(project_new_merge_request_path(project)) + + click_link('Changes') + + wait_for_requests + expect(page).to have_content('*.rbca') + end + + it 'shows the diff of an edited file', js: true do + click_link('.gitignore') + find('.js-edit-blob').click + execute_script("ace.edit('editor').setValue('*.rbca')") + click_link('Preview changes') + + expect(page).to have_css('.line_holder.new') + end + end + + context 'when an user does not have write access' do + before do + project2.team << [user, :reporter] + visit(project2_tree_path_root_ref) + end + + it 'inserts a content of a file in a forked project', js: true do + click_link('.gitignore') + find('.js-edit-blob').click + + expect(page).to have_link('Fork') + expect(page).to have_button('Cancel') + + click_link('Fork') + + expect(page).to have_content(fork_message) + + execute_script("ace.edit('editor').setValue('*.rbca')") + + expect(evaluate_script('ace.edit("editor").getValue()')).to eq('*.rbca') + end + + it 'commits an edited file in a forked project', js: true do + click_link('.gitignore') + find('.js-edit-blob').click + + expect(page).to have_link('Fork') + expect(page).to have_button('Cancel') + + click_link('Fork') + execute_script("ace.edit('editor').setValue('*.rbca')") + fill_in(:commit_message, with: 'New commit message', visible: true) + click_button('Commit changes') + + fork = user.fork_of(project2) + + expect(current_path).to eq(project_new_merge_request_path(fork)) + + wait_for_requests + + expect(page).to have_content('New commit message') + end + end +end diff --git a/spec/features/projects/user_replaces_files_spec.rb b/spec/features/projects/user_replaces_files_spec.rb new file mode 100644 index 00000000000..50f2ffc4bbf --- /dev/null +++ b/spec/features/projects/user_replaces_files_spec.rb @@ -0,0 +1,87 @@ +require 'spec_helper' + +describe 'User replaces files' do + include DropzoneHelper + + let(:fork_message) do + "You're not allowed to make changes to this project directly. "\ + "A fork of this project has been created that you can make changes in, so you can submit a merge request." + end + let(:project) { create(:project, name: 'Shop') } + let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') } + let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) } + let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) } + let(:user) { create(:user) } + + before do + sign_in(user) + end + + context 'when an user has write access' do + before do + project.team << [user, :master] + visit(project_tree_path_root_ref) + end + + it 'replaces an existed file with a new one', js: true do + click_link('.gitignore') + + expect(page).to have_content('.gitignore') + + click_on('Replace') + drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt')) + + page.within('#modal-upload-blob') do + fill_in(:commit_message, with: 'Replacement file commit message') + end + + click_button('Replace file') + + expect(page).to have_content('Lorem ipsum dolor sit amet') + expect(page).to have_content('Sed ut perspiciatis unde omnis') + expect(page).to have_content('Replacement file commit message') + end + end + + context 'when an user does not have write access' do + before do + project2.team << [user, :reporter] + visit(project2_tree_path_root_ref) + end + + it 'replaces an existed file with a new one in a forked project', js: true do + click_link('.gitignore') + + expect(page).to have_content('.gitignore') + + click_on('Replace') + + expect(page).to have_link('Fork') + expect(page).to have_button('Cancel') + + click_link('Fork') + + expect(page).to have_content(fork_message) + + click_on('Replace') + drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt')) + + page.within('#modal-upload-blob') do + fill_in(:commit_message, with: 'Replacement file commit message') + end + + click_button('Replace file') + + expect(page).to have_content('Replacement file commit message') + + fork = user.fork_of(project2) + + expect(current_path).to eq(project_new_merge_request_path(fork)) + + click_link('Changes') + + expect(page).to have_content('Lorem ipsum dolor sit amet') + expect(page).to have_content('Sed ut perspiciatis unde omnis') + end + end +end diff --git a/spec/features/projects/user_uploads_files_spec.rb b/spec/features/projects/user_uploads_files_spec.rb new file mode 100644 index 00000000000..64a1439badd --- /dev/null +++ b/spec/features/projects/user_uploads_files_spec.rb @@ -0,0 +1,82 @@ +require 'spec_helper' + +describe 'User uploads files' do + include DropzoneHelper + + let(:fork_message) do + "You're not allowed to make changes to this project directly. "\ + "A fork of this project has been created that you can make changes in, so you can submit a merge request." + end + let(:project) { create(:project, name: 'Shop') } + let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') } + let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) } + let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) } + let(:user) { create(:user) } + + before do + project.team << [user, :master] + sign_in(user) + end + + context 'when an user has write access' do + before do + visit(project_tree_path_root_ref) + end + + it 'uploads and commit a new file', js: true do + find('.add-to-tree').click + click_link('Upload file') + drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt')) + + page.within('#modal-upload-blob') do + fill_in(:commit_message, with: 'New commit message') + end + + fill_in(:branch_name, with: 'new_branch_name', visible: true) + click_button('Upload file') + + expect(page).to have_content('New commit message') + expect(current_path).to eq(project_new_merge_request_path(project)) + + click_link('Changes') + + expect(page).to have_content('Lorem ipsum dolor sit amet') + expect(page).to have_content('Sed ut perspiciatis unde omnis') + end + end + + context 'when an user does not have write access' do + before do + project2.team << [user, :reporter] + visit(project2_tree_path_root_ref) + end + + it 'uploads and commit a new fileto a forked project', js: true do + find('.add-to-tree').click + click_link('Upload file') + + expect(page).to have_content(fork_message) + + find('.add-to-tree').click + click_link('Upload file') + drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt')) + + page.within('#modal-upload-blob') do + fill_in(:commit_message, with: 'New commit message') + end + + click_button('Upload file') + + expect(page).to have_content('New commit message') + + fork = user.fork_of(project2) + + expect(current_path).to eq(project_new_merge_request_path(fork)) + + click_link('Changes') + + expect(page).to have_content('Lorem ipsum dolor sit amet') + expect(page).to have_content('Sed ut perspiciatis unde omnis') + end + end +end diff --git a/spec/features/snippets/user_creates_snippet_spec.rb b/spec/features/snippets/user_creates_snippet_spec.rb index 57dec14b480..698d3b5d3e3 100644 --- a/spec/features/snippets/user_creates_snippet_spec.rb +++ b/spec/features/snippets/user_creates_snippet_spec.rb @@ -41,7 +41,7 @@ feature 'User creates snippet', :js, feature: true do expect(page).to have_content('My Snippet') link = find('a.no-attachment-icon img[alt="banana_sample"]')['src'] - expect(link).to match(%r{/uploads/temp/\h{32}/banana_sample\.gif\z}) + expect(link).to match(%r{/uploads/system/temp/\h{32}/banana_sample\.gif\z}) visit(link) expect(page.status_code).to eq(200) @@ -59,7 +59,7 @@ feature 'User creates snippet', :js, feature: true do wait_for_requests link = find('a.no-attachment-icon img[alt="banana_sample"]')['src'] - expect(link).to match(%r{/uploads/personal_snippet/#{Snippet.last.id}/\h{32}/banana_sample\.gif\z}) + expect(link).to match(%r{/uploads/system/personal_snippet/#{Snippet.last.id}/\h{32}/banana_sample\.gif\z}) visit(link) expect(page.status_code).to eq(200) @@ -84,7 +84,7 @@ feature 'User creates snippet', :js, feature: true do end expect(page).to have_content('Hello World!') link = find('a.no-attachment-icon img[alt="banana_sample"]')['src'] - expect(link).to match(%r{/uploads/personal_snippet/#{Snippet.last.id}/\h{32}/banana_sample\.gif\z}) + expect(link).to match(%r{/uploads/system/personal_snippet/#{Snippet.last.id}/\h{32}/banana_sample\.gif\z}) visit(link) expect(page.status_code).to eq(200) diff --git a/spec/features/snippets/user_edits_snippet_spec.rb b/spec/features/snippets/user_edits_snippet_spec.rb index cff64423873..c9f9741b4bb 100644 --- a/spec/features/snippets/user_edits_snippet_spec.rb +++ b/spec/features/snippets/user_edits_snippet_spec.rb @@ -33,7 +33,7 @@ feature 'User edits snippet', :js, feature: true do wait_for_requests link = find('a.no-attachment-icon img[alt="banana_sample"]')['src'] - expect(link).to match(%r{/uploads/personal_snippet/#{snippet.id}/\h{32}/banana_sample\.gif\z}) + expect(link).to match(%r{/uploads/system/personal_snippet/#{snippet.id}/\h{32}/banana_sample\.gif\z}) end it 'updates the snippet to make it internal' do diff --git a/spec/features/uploads/user_uploads_avatar_to_group_spec.rb b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb index 32784de1613..5843f18d89f 100644 --- a/spec/features/uploads/user_uploads_avatar_to_group_spec.rb +++ b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb @@ -18,7 +18,7 @@ feature 'User uploads avatar to group', feature: true do visit group_path(group) - expect(page).to have_selector(%Q(img[src$="/uploads/system/group/avatar/#{group.id}/dk.png"])) + expect(page).to have_selector(%Q(img[src$="/uploads/-/system/group/avatar/#{group.id}/dk.png"])) # Cheating here to verify something that isn't user-facing, but is important expect(group.reload.avatar.file).to exist diff --git a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb index 82c356735b9..e8171dcaeb0 100644 --- a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb +++ b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb @@ -16,7 +16,7 @@ feature 'User uploads avatar to profile', feature: true do visit user_path(user) - expect(page).to have_selector(%Q(img[src$="/uploads/system/user/avatar/#{user.id}/dk.png"])) + expect(page).to have_selector(%Q(img[src$="/uploads/-/system/user/avatar/#{user.id}/dk.png"])) # Cheating here to verify something that isn't user-facing, but is important expect(user.reload.avatar.file).to exist diff --git a/spec/fixtures/api/schemas/entities/merge_request.json b/spec/fixtures/api/schemas/entities/merge_request.json index b6a59a6cc47..7ffa82fc4bd 100644 --- a/spec/fixtures/api/schemas/entities/merge_request.json +++ b/spec/fixtures/api/schemas/entities/merge_request.json @@ -75,6 +75,7 @@ "additionalProperties": false }, "target_branch_commits_path": { "type": "string" }, + "target_branch_tree_path": { "type": "string" }, "source_branch_path": { "type": "string" }, "conflict_resolution_path": { "type": ["string", "null"] }, "cancel_merge_when_pipeline_succeeds_path": { "type": "string" }, diff --git a/spec/fixtures/api/schemas/public_api/v4/user/admin.json b/spec/fixtures/api/schemas/public_api/v4/user/admin.json new file mode 100644 index 00000000000..f733914fbf8 --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/user/admin.json @@ -0,0 +1,34 @@ +{ + "type": "object", + "required": [ + "id", + "username", + "email", + "name", + "state", + "avatar_url", + "web_url", + "created_at", + "is_admin", + "bio", + "location", + "skype", + "linkedin", + "twitter", + "website_url", + "organization", + "last_sign_in_at", + "confirmed_at", + "color_scheme_id", + "projects_limit", + "current_sign_in_at", + "identities", + "can_create_group", + "can_create_project", + "two_factor_enabled", + "external" + ], + "properties": { + "$ref": "full.json" + } +} diff --git a/spec/fixtures/config/redis_cache_config_with_env.yml b/spec/fixtures/config/redis_cache_config_with_env.yml new file mode 100644 index 00000000000..52fd5a06460 --- /dev/null +++ b/spec/fixtures/config/redis_cache_config_with_env.yml @@ -0,0 +1,2 @@ +test: + url: <%= ENV['TEST_GITLAB_REDIS_CACHE_URL'] %> diff --git a/spec/fixtures/config/redis_cache_new_format_host.yml b/spec/fixtures/config/redis_cache_new_format_host.yml new file mode 100644 index 00000000000..a24f3716391 --- /dev/null +++ b/spec/fixtures/config/redis_cache_new_format_host.yml @@ -0,0 +1,29 @@ +# redis://[:password@]host[:port][/db-number][?option=value] +# more details: http://www.iana.org/assignments/uri-schemes/prov/redis +development: + url: redis://:mynewpassword@localhost:6380/10 + sentinels: + - + host: localhost + port: 26380 # point to sentinel, not to redis port + - + host: slave2 + port: 26380 # point to sentinel, not to redis port +test: + url: redis://:mynewpassword@localhost:6380/10 + sentinels: + - + host: localhost + port: 26380 # point to sentinel, not to redis port + - + host: slave2 + port: 26380 # point to sentinel, not to redis port +production: + url: redis://:mynewpassword@localhost:6380/10 + sentinels: + - + host: slave1 + port: 26380 # point to sentinel, not to redis port + - + host: slave2 + port: 26380 # point to sentinel, not to redis port diff --git a/spec/fixtures/config/redis_cache_new_format_socket.yml b/spec/fixtures/config/redis_cache_new_format_socket.yml new file mode 100644 index 00000000000..3634c550163 --- /dev/null +++ b/spec/fixtures/config/redis_cache_new_format_socket.yml @@ -0,0 +1,6 @@ +development: + url: unix:/path/to/redis.cache.sock +test: + url: unix:/path/to/redis.cache.sock +production: + url: unix:/path/to/redis.cache.sock diff --git a/spec/fixtures/config/redis_cache_old_format_host.yml b/spec/fixtures/config/redis_cache_old_format_host.yml new file mode 100644 index 00000000000..3609dcd022e --- /dev/null +++ b/spec/fixtures/config/redis_cache_old_format_host.yml @@ -0,0 +1,5 @@ +# redis://[:password@]host[:port][/db-number][?option=value] +# more details: http://www.iana.org/assignments/uri-schemes/prov/redis +development: redis://:mypassword@localhost:6380/10 +test: redis://:mypassword@localhost:6380/10 +production: redis://:mypassword@localhost:6380/10 diff --git a/spec/fixtures/config/redis_cache_old_format_socket.yml b/spec/fixtures/config/redis_cache_old_format_socket.yml new file mode 100644 index 00000000000..26fa0eda245 --- /dev/null +++ b/spec/fixtures/config/redis_cache_old_format_socket.yml @@ -0,0 +1,3 @@ +development: unix:/path/to/old/redis.cache.sock +test: unix:/path/to/old/redis.cache.sock +production: unix:/path/to/old/redis.cache.sock diff --git a/spec/fixtures/config/redis_new_format_host.yml b/spec/fixtures/config/redis_new_format_host.yml index 13772677a45..8d134d467e9 100644 --- a/spec/fixtures/config/redis_new_format_host.yml +++ b/spec/fixtures/config/redis_new_format_host.yml @@ -5,25 +5,25 @@ development: sentinels: - host: localhost - port: 26380 # point to sentinel, not to redis port + port: 26379 # point to sentinel, not to redis port - host: slave2 - port: 26381 # point to sentinel, not to redis port + port: 26379 # point to sentinel, not to redis port test: url: redis://:mynewpassword@localhost:6379/99 sentinels: - host: localhost - port: 26380 # point to sentinel, not to redis port + port: 26379 # point to sentinel, not to redis port - host: slave2 - port: 26381 # point to sentinel, not to redis port + port: 26379 # point to sentinel, not to redis port production: url: redis://:mynewpassword@localhost:6379/99 sentinels: - host: slave1 - port: 26380 # point to sentinel, not to redis port + port: 26379 # point to sentinel, not to redis port - host: slave2 - port: 26381 # point to sentinel, not to redis port + port: 26379 # point to sentinel, not to redis port diff --git a/spec/fixtures/config/redis_queues_config_with_env.yml b/spec/fixtures/config/redis_queues_config_with_env.yml new file mode 100644 index 00000000000..d16a9d8a7f8 --- /dev/null +++ b/spec/fixtures/config/redis_queues_config_with_env.yml @@ -0,0 +1,2 @@ +test: + url: <%= ENV['TEST_GITLAB_REDIS_QUEUES_URL'] %> diff --git a/spec/fixtures/config/redis_queues_new_format_host.yml b/spec/fixtures/config/redis_queues_new_format_host.yml new file mode 100644 index 00000000000..1535584d779 --- /dev/null +++ b/spec/fixtures/config/redis_queues_new_format_host.yml @@ -0,0 +1,29 @@ +# redis://[:password@]host[:port][/db-number][?option=value] +# more details: http://www.iana.org/assignments/uri-schemes/prov/redis +development: + url: redis://:mynewpassword@localhost:6381/11 + sentinels: + - + host: localhost + port: 26381 # point to sentinel, not to redis port + - + host: slave2 + port: 26381 # point to sentinel, not to redis port +test: + url: redis://:mynewpassword@localhost:6381/11 + sentinels: + - + host: localhost + port: 26381 # point to sentinel, not to redis port + - + host: slave2 + port: 26381 # point to sentinel, not to redis port +production: + url: redis://:mynewpassword@localhost:6381/11 + sentinels: + - + host: slave1 + port: 26381 # point to sentinel, not to redis port + - + host: slave2 + port: 26381 # point to sentinel, not to redis port diff --git a/spec/fixtures/config/redis_queues_new_format_socket.yml b/spec/fixtures/config/redis_queues_new_format_socket.yml new file mode 100644 index 00000000000..b488d84d022 --- /dev/null +++ b/spec/fixtures/config/redis_queues_new_format_socket.yml @@ -0,0 +1,6 @@ +development: + url: unix:/path/to/redis.queues.sock +test: + url: unix:/path/to/redis.queues.sock +production: + url: unix:/path/to/redis.queues.sock diff --git a/spec/fixtures/config/redis_queues_old_format_host.yml b/spec/fixtures/config/redis_queues_old_format_host.yml new file mode 100644 index 00000000000..6531748a8d7 --- /dev/null +++ b/spec/fixtures/config/redis_queues_old_format_host.yml @@ -0,0 +1,5 @@ +# redis://[:password@]host[:port][/db-number][?option=value] +# more details: http://www.iana.org/assignments/uri-schemes/prov/redis +development: redis://:mypassword@localhost:6381/11 +test: redis://:mypassword@localhost:6381/11 +production: redis://:mypassword@localhost:6381/11 diff --git a/spec/fixtures/config/redis_queues_old_format_socket.yml b/spec/fixtures/config/redis_queues_old_format_socket.yml new file mode 100644 index 00000000000..53f5db72758 --- /dev/null +++ b/spec/fixtures/config/redis_queues_old_format_socket.yml @@ -0,0 +1,3 @@ +development: unix:/path/to/old/redis.queues.sock +test: unix:/path/to/old/redis.queues.sock +production: unix:/path/to/old/redis.queues.sock diff --git a/spec/fixtures/config/redis_shared_state_config_with_env.yml b/spec/fixtures/config/redis_shared_state_config_with_env.yml new file mode 100644 index 00000000000..eab7203d0de --- /dev/null +++ b/spec/fixtures/config/redis_shared_state_config_with_env.yml @@ -0,0 +1,2 @@ +test: + url: <%= ENV['TEST_GITLAB_REDIS_SHARED_STATE_URL'] %> diff --git a/spec/fixtures/config/redis_shared_state_new_format_host.yml b/spec/fixtures/config/redis_shared_state_new_format_host.yml new file mode 100644 index 00000000000..1180b2b4a82 --- /dev/null +++ b/spec/fixtures/config/redis_shared_state_new_format_host.yml @@ -0,0 +1,29 @@ +# redis://[:password@]host[:port][/db-number][?option=value] +# more details: http://www.iana.org/assignments/uri-schemes/prov/redis +development: + url: redis://:mynewpassword@localhost:6382/12 + sentinels: + - + host: localhost + port: 26382 # point to sentinel, not to redis port + - + host: slave2 + port: 26382 # point to sentinel, not to redis port +test: + url: redis://:mynewpassword@localhost:6382/12 + sentinels: + - + host: localhost + port: 26382 # point to sentinel, not to redis port + - + host: slave2 + port: 26382 # point to sentinel, not to redis port +production: + url: redis://:mynewpassword@localhost:6382/12 + sentinels: + - + host: slave1 + port: 26382 # point to sentinel, not to redis port + - + host: slave2 + port: 26382 # point to sentinel, not to redis port diff --git a/spec/fixtures/config/redis_shared_state_new_format_socket.yml b/spec/fixtures/config/redis_shared_state_new_format_socket.yml new file mode 100644 index 00000000000..1b0e699729e --- /dev/null +++ b/spec/fixtures/config/redis_shared_state_new_format_socket.yml @@ -0,0 +1,6 @@ +development: + url: unix:/path/to/redis.shared_state.sock +test: + url: unix:/path/to/redis.shared_state.sock +production: + url: unix:/path/to/redis.shared_state.sock diff --git a/spec/fixtures/config/redis_shared_state_old_format_host.yml b/spec/fixtures/config/redis_shared_state_old_format_host.yml new file mode 100644 index 00000000000..fef5e768c5d --- /dev/null +++ b/spec/fixtures/config/redis_shared_state_old_format_host.yml @@ -0,0 +1,5 @@ +# redis://[:password@]host[:port][/db-number][?option=value] +# more details: http://www.iana.org/assignments/uri-schemes/prov/redis +development: redis://:mypassword@localhost:6382/12 +test: redis://:mypassword@localhost:6382/12 +production: redis://:mypassword@localhost:6382/12 diff --git a/spec/fixtures/config/redis_shared_state_old_format_socket.yml b/spec/fixtures/config/redis_shared_state_old_format_socket.yml new file mode 100644 index 00000000000..4746afbb5ef --- /dev/null +++ b/spec/fixtures/config/redis_shared_state_old_format_socket.yml @@ -0,0 +1,3 @@ +development: unix:/path/to/old/redis.shared_state.sock +test: unix:/path/to/old/redis.shared_state.sock +production: unix:/path/to/old/redis.shared_state.sock diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index e0cad1da86a..f5e139685e8 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -59,13 +59,13 @@ describe ApplicationHelper do describe 'project_icon' do it 'returns an url for the avatar' do project = create(:empty_project, avatar: File.open(uploaded_image_temp_path)) - avatar_url = "/uploads/system/project/avatar/#{project.id}/banana_sample.gif" + avatar_url = "/uploads/-/system/project/avatar/#{project.id}/banana_sample.gif" expect(helper.project_icon(project.full_path).to_s) .to eq "<img src=\"#{avatar_url}\" alt=\"Banana sample\" />" allow(ActionController::Base).to receive(:asset_host).and_return(gitlab_host) - avatar_url = "#{gitlab_host}/uploads/system/project/avatar/#{project.id}/banana_sample.gif" + avatar_url = "#{gitlab_host}/uploads/-/system/project/avatar/#{project.id}/banana_sample.gif" expect(helper.project_icon(project.full_path).to_s) .to eq "<img src=\"#{avatar_url}\" alt=\"Banana sample\" />" @@ -88,7 +88,7 @@ describe ApplicationHelper do context 'when there is a matching user' do it 'returns a relative URL for the avatar' do expect(helper.avatar_icon(user.email).to_s) - .to eq("/uploads/system/user/avatar/#{user.id}/banana_sample.gif") + .to eq("/uploads/-/system/user/avatar/#{user.id}/banana_sample.gif") end context 'when an asset_host is set in the config' do @@ -100,14 +100,14 @@ describe ApplicationHelper do it 'returns an absolute URL on that asset host' do expect(helper.avatar_icon(user.email, only_path: false).to_s) - .to eq("#{asset_host}/uploads/system/user/avatar/#{user.id}/banana_sample.gif") + .to eq("#{asset_host}/uploads/-/system/user/avatar/#{user.id}/banana_sample.gif") end end context 'when only_path is set to false' do it 'returns an absolute URL for the avatar' do expect(helper.avatar_icon(user.email, only_path: false).to_s) - .to eq("#{gitlab_host}/uploads/system/user/avatar/#{user.id}/banana_sample.gif") + .to eq("#{gitlab_host}/uploads/-/system/user/avatar/#{user.id}/banana_sample.gif") end end @@ -120,7 +120,7 @@ describe ApplicationHelper do it 'returns a relative URL with the correct prefix' do expect(helper.avatar_icon(user.email).to_s) - .to eq("/gitlab/uploads/system/user/avatar/#{user.id}/banana_sample.gif") + .to eq("/gitlab/uploads/-/system/user/avatar/#{user.id}/banana_sample.gif") end end end @@ -138,14 +138,14 @@ describe ApplicationHelper do context 'when only_path is true' do it 'returns a relative URL for the avatar' do expect(helper.avatar_icon(user, only_path: true).to_s) - .to eq("/uploads/system/user/avatar/#{user.id}/banana_sample.gif") + .to eq("/uploads/-/system/user/avatar/#{user.id}/banana_sample.gif") end end context 'when only_path is false' do it 'returns an absolute URL for the avatar' do expect(helper.avatar_icon(user, only_path: false).to_s) - .to eq("#{gitlab_host}/uploads/system/user/avatar/#{user.id}/banana_sample.gif") + .to eq("#{gitlab_host}/uploads/-/system/user/avatar/#{user.id}/banana_sample.gif") end end end diff --git a/spec/helpers/auth_helper_spec.rb b/spec/helpers/auth_helper_spec.rb index a0e1265efff..c94fedd615b 100644 --- a/spec/helpers/auth_helper_spec.rb +++ b/spec/helpers/auth_helper_spec.rb @@ -70,7 +70,7 @@ describe AuthHelper do end end - [:twitter, :facebook, :google_oauth2, :gitlab, :github, :bitbucket, :crowd, :auth0].each do |provider| + [:twitter, :facebook, :google_oauth2, :gitlab, :github, :bitbucket, :crowd, :auth0, :authentiq].each do |provider| it "returns false if the provider is #{provider}" do expect(helper.unlink_allowed?(provider)).to be true end diff --git a/spec/helpers/award_emoji_helper_spec.rb b/spec/helpers/award_emoji_helper_spec.rb index 7dfd6a3f6b4..035960ed96e 100644 --- a/spec/helpers/award_emoji_helper_spec.rb +++ b/spec/helpers/award_emoji_helper_spec.rb @@ -40,7 +40,7 @@ describe AwardEmojiHelper do it 'returns correct url' do @project = merge_request.project - expected_url = "/#{@project.namespace.path}/#{@project.path}/merge_requests/#{merge_request.id}/toggle_award_emoji" + expected_url = "/#{@project.namespace.path}/#{@project.path}/merge_requests/#{merge_request.iid}/toggle_award_emoji" expect(helper.toggle_award_url(merge_request)).to eq(expected_url) end @@ -52,7 +52,7 @@ describe AwardEmojiHelper do it 'returns correct url' do @project = issue.project - expected_url = "/#{@project.namespace.path}/#{@project.path}/issues/#{issue.id}/toggle_award_emoji" + expected_url = "/#{@project.namespace.path}/#{@project.path}/issues/#{issue.iid}/toggle_award_emoji" expect(helper.toggle_award_url(issue)).to eq(expected_url) end diff --git a/spec/helpers/button_helper_spec.rb b/spec/helpers/button_helper_spec.rb index 661327d4432..7ecb75da8ce 100644 --- a/spec/helpers/button_helper_spec.rb +++ b/spec/helpers/button_helper_spec.rb @@ -35,7 +35,7 @@ describe ButtonHelper do context 'with internal auth disabled' do before do - stub_application_setting(signin_enabled?: false) + stub_application_setting(password_authentication_enabled?: false) end context 'when user has no personal access tokens' do diff --git a/spec/helpers/emails_helper_spec.rb b/spec/helpers/emails_helper_spec.rb index c68e4f56b05..2390c1f3e5d 100644 --- a/spec/helpers/emails_helper_spec.rb +++ b/spec/helpers/emails_helper_spec.rb @@ -52,7 +52,7 @@ describe EmailsHelper do ) expect(header_logo).to eq( - %{<img style="height: 50px" src="/uploads/system/appearance/header_logo/#{appearance.id}/dk.png" alt="Dk" />} + %{<img style="height: 50px" src="/uploads/-/system/appearance/header_logo/#{appearance.id}/dk.png" alt="Dk" />} ) end end diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb index e3f9d9db9eb..3a246f10283 100644 --- a/spec/helpers/groups_helper_spec.rb +++ b/spec/helpers/groups_helper_spec.rb @@ -11,7 +11,7 @@ describe GroupsHelper do group.avatar = fixture_file_upload(avatar_file_path) group.save! expect(group_icon(group.path).to_s) - .to match("/uploads/system/group/avatar/#{group.id}/banana_sample.gif") + .to match("/uploads/-/system/group/avatar/#{group.id}/banana_sample.gif") end it 'gives default avatar_icon when no avatar is present' do diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb index d2e918ef014..7789cfa3554 100644 --- a/spec/helpers/issuables_helper_spec.rb +++ b/spec/helpers/issuables_helper_spec.rb @@ -60,7 +60,7 @@ describe IssuablesHelper do end end - describe 'counter caching based on issuable type and params', :caching do + describe 'counter caching based on issuable type and params', :use_clean_rails_memory_store_caching do let(:params) do { scope: 'created-by-me', @@ -244,5 +244,25 @@ describe IssuablesHelper do it { expect(helper.updated_at_by(unedited_issuable)).to eq({}) } it { expect(helper.updated_at_by(edited_issuable)).to eq(edited_updated_at_by) } + + context 'when updated by a deleted user' do + let(:edited_updated_at_by) do + { + updatedAt: edited_issuable.updated_at.to_time.iso8601, + updatedBy: { + name: User.ghost.name, + path: user_path(User.ghost) + } + } + end + + before do + user.destroy + end + + it 'returns "Ghost user" as edited_by' do + expect(helper.updated_at_by(edited_issuable.reload)).to eq(edited_updated_at_by) + end + end end end diff --git a/spec/helpers/page_layout_helper_spec.rb b/spec/helpers/page_layout_helper_spec.rb index 95b4032616e..9aca3987657 100644 --- a/spec/helpers/page_layout_helper_spec.rb +++ b/spec/helpers/page_layout_helper_spec.rb @@ -60,7 +60,7 @@ describe PageLayoutHelper do %w(project user group).each do |type| context "with @#{type} assigned" do it "uses #{type.titlecase} avatar if available" do - object = double(avatar_url: 'http://example.com/uploads/system/avatar.png') + object = double(avatar_url: 'http://example.com/uploads/-/system/avatar.png') assign(type, object) expect(helper.page_image).to eq object.avatar_url diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 487d9800707..45066a60f50 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -63,7 +63,7 @@ describe ProjectsHelper do end end - describe "#project_list_cache_key", redis: true do + describe "#project_list_cache_key", clean_gitlab_redis_shared_state: true do let(:project) { create(:project) } it "includes the route" do @@ -160,7 +160,7 @@ describe ProjectsHelper do context 'user requires a personal access token' do it 'returns true' do - stub_application_setting(signin_enabled?: false) + stub_application_setting(password_authentication_enabled?: false) expect(helper.show_no_password_message?).to be_truthy end @@ -184,7 +184,7 @@ describe ProjectsHelper do let(:user) { create(:user) } it 'returns link to create a personal access token' do - stub_application_setting(signin_enabled?: false) + stub_application_setting(password_authentication_enabled?: false) expect(helper.link_to_set_password).to match %r{<a href="#{profile_personal_access_tokens_path}">create a personal access token</a>} end diff --git a/spec/javascripts/commit/pipelines/pipelines_spec.js b/spec/javascripts/commit/pipelines/pipelines_spec.js index 694f94efcff..a34cadec0ab 100644 --- a/spec/javascripts/commit/pipelines/pipelines_spec.js +++ b/spec/javascripts/commit/pipelines/pipelines_spec.js @@ -85,6 +85,41 @@ describe('Pipelines table in Commits and Merge requests', () => { }, 0); }); }); + + describe('pipeline badge counts', () => { + const pipelinesResponse = (request, next) => { + next(request.respondWith(JSON.stringify([pipeline]), { + status: 200, + })); + }; + + beforeEach(() => { + Vue.http.interceptors.push(pipelinesResponse); + }); + + afterEach(() => { + Vue.http.interceptors = _.without(Vue.http.interceptors, pipelinesResponse); + this.component.$destroy(); + }); + + it('should receive update-pipelines-count event', (done) => { + const element = document.createElement('div'); + document.body.appendChild(element); + + element.addEventListener('update-pipelines-count', (event) => { + expect(event.detail.pipelines).toEqual([pipeline]); + done(); + }); + + this.component = new PipelinesTable({ + propsData: { + endpoint: 'endpoint', + helpPagePath: 'foo', + }, + }).$mount(); + element.appendChild(this.component.$el); + }); + }); }); describe('unsuccessfull request', () => { diff --git a/spec/javascripts/environments/environment_spec.js b/spec/javascripts/environments/environment_spec.js index 6639a6b5e7b..0c8817a8148 100644 --- a/spec/javascripts/environments/environment_spec.js +++ b/spec/javascripts/environments/environment_spec.js @@ -2,6 +2,7 @@ import Vue from 'vue'; import '~/flash'; import environmentsComponent from '~/environments/components/environment.vue'; import { environment, folder } from './mock_data'; +import { headersInterceptor } from '../helpers/vue_resource_helper'; describe('Environment', () => { preloadFixtures('static/environments/environments.html.raw'); @@ -25,12 +26,14 @@ describe('Environment', () => { beforeEach(() => { Vue.http.interceptors.push(environmentsEmptyResponseInterceptor); + Vue.http.interceptors.push(headersInterceptor); }); afterEach(() => { Vue.http.interceptors = _.without( Vue.http.interceptors, environmentsEmptyResponseInterceptor, ); + Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor); }); it('should render the empty state', (done) => { @@ -54,6 +57,10 @@ describe('Environment', () => { describe('with paginated environments', () => { const environmentsResponseInterceptor = (request, next) => { + next((response) => { + response.headers.set('X-nExt-pAge', '2'); + }); + next(request.respondWith(JSON.stringify({ environments: [environment], stopped_count: 1, @@ -73,6 +80,7 @@ describe('Environment', () => { beforeEach(() => { Vue.http.interceptors.push(environmentsResponseInterceptor); + Vue.http.interceptors.push(headersInterceptor); component = new EnvironmentsComponent({ el: document.querySelector('#environments-list-view'), }); @@ -82,6 +90,7 @@ describe('Environment', () => { Vue.http.interceptors = _.without( Vue.http.interceptors, environmentsResponseInterceptor, ); + Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor); }); it('should render a table with environments', (done) => { diff --git a/spec/javascripts/environments/folder/environments_folder_view_spec.js b/spec/javascripts/environments/folder/environments_folder_view_spec.js index 350078ad5f5..fdaea5c0b0c 100644 --- a/spec/javascripts/environments/folder/environments_folder_view_spec.js +++ b/spec/javascripts/environments/folder/environments_folder_view_spec.js @@ -2,6 +2,7 @@ import Vue from 'vue'; import '~/flash'; import environmentsFolderViewComponent from '~/environments/folder/environments_folder_view.vue'; import { environmentsList } from '../mock_data'; +import { headersInterceptor } from '../../helpers/vue_resource_helper'; describe('Environments Folder View', () => { preloadFixtures('static/environments/environments_folder_view.html.raw'); @@ -36,6 +37,8 @@ describe('Environments Folder View', () => { beforeEach(() => { Vue.http.interceptors.push(environmentsResponseInterceptor); + Vue.http.interceptors.push(headersInterceptor); + component = new EnvironmentsFolderViewComponent({ el: document.querySelector('#environments-folder-list-view'), }); @@ -45,6 +48,7 @@ describe('Environments Folder View', () => { Vue.http.interceptors = _.without( Vue.http.interceptors, environmentsResponseInterceptor, ); + Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor); }); it('should render a table with environments', (done) => { diff --git a/spec/javascripts/helpers/vue_resource_helper.js b/spec/javascripts/helpers/vue_resource_helper.js new file mode 100644 index 00000000000..0d1bf5e2e80 --- /dev/null +++ b/spec/javascripts/helpers/vue_resource_helper.js @@ -0,0 +1,11 @@ +// eslint-disable-next-line import/prefer-default-export +export const headersInterceptor = (request, next) => { + next((response) => { + const headers = {}; + response.headers.forEach((value, key) => { + headers[key] = value; + }); + // eslint-disable-next-line no-param-reassign + response.headers = headers; + }); +}; diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js index bc13373a27e..81ce18bf2fb 100644 --- a/spec/javascripts/issue_show/components/app_spec.js +++ b/spec/javascripts/issue_show/components/app_spec.js @@ -3,7 +3,6 @@ import '~/render_math'; import '~/render_gfm'; import issuableApp from '~/issue_show/components/app.vue'; import eventHub from '~/issue_show/event_hub'; -import Poll from '~/lib/utils/poll'; import issueShowData from '../mock_data'; function formatText(text) { @@ -11,16 +10,26 @@ function formatText(text) { } describe('Issuable output', () => { + let requestData = issueShowData.initialRequest; + document.body.innerHTML = '<span id="task_status"></span>'; + const interceptor = (request, next) => { + next(request.respondWith(JSON.stringify(requestData), { + status: 200, + })); + }; + let vm; - beforeEach(() => { + beforeEach((done) => { spyOn(eventHub, '$emit'); - spyOn(Poll.prototype, 'makeRequest'); const IssuableDescriptionComponent = Vue.extend(issuableApp); + requestData = issueShowData.initialRequest; + Vue.http.interceptors.push(interceptor); + vm = new IssuableDescriptionComponent({ propsData: { canUpdate: true, @@ -40,15 +49,17 @@ describe('Issuable output', () => { projectPath: '/', }, }).$mount(); + + setTimeout(done); }); - it('should render a title/description/edited and update title/description/edited on update', (done) => { - vm.poll.options.successCallback({ - json() { - return issueShowData.initialRequest; - }, - }); + afterEach(() => { + Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor); + vm.poll.stop(); + }); + + it('should render a title/description/edited and update title/description/edited on update', (done) => { let editedText; Vue.nextTick() .then(() => { @@ -64,13 +75,10 @@ describe('Issuable output', () => { expect(editedText.querySelector('time')).toBeTruthy(); }) .then(() => { - vm.poll.options.successCallback({ - json() { - return issueShowData.secondRequest; - }, - }); + requestData = issueShowData.secondRequest; + vm.poll.makeRequest(); }) - .then(Vue.nextTick) + .then(() => new Promise(resolve => setTimeout(resolve))) .then(() => { expect(document.querySelector('title').innerText).toContain('2 (#1)'); expect(vm.$el.querySelector('.title').innerHTML).toContain('<p>2</p>'); @@ -304,7 +312,7 @@ describe('Issuable output', () => { it('stops polling when deleting', (done) => { spyOn(gl.utils, 'visitUrl'); - spyOn(vm.poll, 'stop'); + spyOn(vm.poll, 'stop').and.callThrough(); spyOn(vm.service, 'deleteIssuable').and.callFake(() => new Promise((resolve) => { resolve({ json() { @@ -347,23 +355,14 @@ describe('Issuable output', () => { describe('open form', () => { it('shows locked warning if form is open & data is different', (done) => { - vm.poll.options.successCallback({ - json() { - return issueShowData.initialRequest; - }, - }); - Vue.nextTick() .then(() => { vm.openForm(); - vm.poll.options.successCallback({ - json() { - return issueShowData.secondRequest; - }, - }); + requestData = issueShowData.secondRequest; + vm.poll.makeRequest(); }) - .then(Vue.nextTick) + .then(() => new Promise(resolve => setTimeout(resolve))) .then(() => { expect( vm.formState.lockedWarningVisible, diff --git a/spec/javascripts/lib/utils/poll_spec.js b/spec/javascripts/lib/utils/poll_spec.js index 22f30191ab9..2aa7011ca51 100644 --- a/spec/javascripts/lib/utils/poll_spec.js +++ b/spec/javascripts/lib/utils/poll_spec.js @@ -25,23 +25,28 @@ function mockServiceCall(service, response, shouldFail = false) { describe('Poll', () => { const service = jasmine.createSpyObj('service', ['fetch']); - const callbacks = jasmine.createSpyObj('callbacks', ['success', 'error']); + const callbacks = jasmine.createSpyObj('callbacks', ['success', 'error', 'notification']); + + function setup() { + return new Poll({ + resource: service, + method: 'fetch', + successCallback: callbacks.success, + errorCallback: callbacks.error, + notificationCallback: callbacks.notification, + }).makeRequest(); + } afterEach(() => { callbacks.success.calls.reset(); callbacks.error.calls.reset(); + callbacks.notification.calls.reset(); service.fetch.calls.reset(); }); it('calls the success callback when no header for interval is provided', (done) => { mockServiceCall(service, { status: 200 }); - - new Poll({ - resource: service, - method: 'fetch', - successCallback: callbacks.success, - errorCallback: callbacks.error, - }).makeRequest(); + setup(); waitForAllCallsToFinish(service, 1, () => { expect(callbacks.success).toHaveBeenCalled(); @@ -51,15 +56,9 @@ describe('Poll', () => { }); }); - it('calls the error callback whe the http request returns an error', (done) => { + it('calls the error callback when the http request returns an error', (done) => { mockServiceCall(service, { status: 500 }, true); - - new Poll({ - resource: service, - method: 'fetch', - successCallback: callbacks.success, - errorCallback: callbacks.error, - }).makeRequest(); + setup(); waitForAllCallsToFinish(service, 1, () => { expect(callbacks.success).not.toHaveBeenCalled(); @@ -69,15 +68,22 @@ describe('Poll', () => { }); }); + it('skips the error callback when request is aborted', (done) => { + mockServiceCall(service, { status: 0 }, true); + setup(); + + waitForAllCallsToFinish(service, 1, () => { + expect(callbacks.success).not.toHaveBeenCalled(); + expect(callbacks.error).not.toHaveBeenCalled(); + expect(callbacks.notification).toHaveBeenCalled(); + + done(); + }); + }); + it('should call the success callback when the interval header is -1', (done) => { mockServiceCall(service, { status: 200, headers: { 'poll-interval': -1 } }); - - new Poll({ - resource: service, - method: 'fetch', - successCallback: callbacks.success, - errorCallback: callbacks.error, - }).makeRequest().then(() => { + setup().then(() => { expect(callbacks.success).toHaveBeenCalled(); expect(callbacks.error).not.toHaveBeenCalled(); diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js index 7f3eea7d2e5..06f89fabf42 100644 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js @@ -54,6 +54,7 @@ describe('MRWidgetHeader', () => { sourceBranch: 'mr-widget-refactor', sourceBranchLink: `<a href="${sourceBranchPath}">mr-widget-refactor</a>`, targetBranchPath: 'foo/bar/commits-path', + targetBranchTreePath: 'foo/bar/tree/path', targetBranch: 'master', isOpen: true, emailPatchesPath: '/mr/email-patches', @@ -69,12 +70,14 @@ describe('MRWidgetHeader', () => { expect(el.classList.contains('mr-source-target')).toBeTruthy(); const sourceBranchLink = el.querySelectorAll('.label-branch')[0]; const targetBranchLink = el.querySelectorAll('.label-branch')[1]; + const commitsCount = el.querySelector('.diverged-commits-count'); expect(sourceBranchLink.textContent).toContain(mr.sourceBranch); expect(targetBranchLink.textContent).toContain(mr.targetBranch); expect(sourceBranchLink.querySelector('a').getAttribute('href')).toEqual(sourceBranchPath); - expect(targetBranchLink.querySelector('a').getAttribute('href')).toEqual(mr.targetBranchPath); - expect(el.querySelector('.diverged-commits-count').textContent).toContain('12 commits behind'); + expect(targetBranchLink.querySelector('a').getAttribute('href')).toEqual(mr.targetBranchTreePath); + expect(commitsCount.textContent).toContain('12 commits behind'); + expect(commitsCount.querySelector('a').getAttribute('href')).toEqual(mr.targetBranchPath); expect(el.textContent).toContain('Check out branch'); expect(el.querySelectorAll('.dropdown li a')[0].getAttribute('href')).toEqual(mr.emailPatchesPath); diff --git a/spec/javascripts/vue_shared/components/commit_spec.js b/spec/javascripts/vue_shared/components/commit_spec.js index 1c3188cdda2..d5754aaa9e7 100644 --- a/spec/javascripts/vue_shared/components/commit_spec.js +++ b/spec/javascripts/vue_shared/components/commit_spec.js @@ -22,7 +22,7 @@ describe('Commit component', () => { shortSha: 'b7836edd', title: 'Commit message', author: { - avatar_url: 'https://gitlab.com/uploads/system/user/avatar/300478/avatar.png', + avatar_url: 'https://gitlab.com/uploads/-/system/user/avatar/300478/avatar.png', web_url: 'https://gitlab.com/jschatz1', path: '/jschatz1', username: 'jschatz1', @@ -45,7 +45,7 @@ describe('Commit component', () => { shortSha: 'b7836edd', title: 'Commit message', author: { - avatar_url: 'https://gitlab.com/uploads/system/user/avatar/300478/avatar.png', + avatar_url: 'https://gitlab.com/uploads/-/system/user/avatar/300478/avatar.png', web_url: 'https://gitlab.com/jschatz1', path: '/jschatz1', username: 'jschatz1', diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index ef58ef1b0cd..ea79389e67e 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -163,7 +163,10 @@ module Ci commands: "pwd\nrspec", coverage_regex: nil, tag_list: [], - options: {}, + options: { + before_script: ["pwd"], + script: ["rspec"] + }, allow_failure: false, when: "on_success", environment: nil, @@ -616,10 +619,12 @@ module Ci coverage_regex: nil, tag_list: [], options: { - image: { name: "ruby:2.1", entrypoint: ["/usr/local/bin/init", "run"] }, - services: [{ name: "mysql" }, - { name: "docker:dind", alias: "docker", entrypoint: ["/usr/local/bin/init", "run"], - command: ["/usr/local/bin/init", "run"] }] + before_script: ["pwd"], + script: ["rspec"], + image: { name: "ruby:2.1", entrypoint: ["/usr/local/bin/init", "run"] }, + services: [{ name: "mysql" }, + { name: "docker:dind", alias: "docker", entrypoint: ["/usr/local/bin/init", "run"], + command: ["/usr/local/bin/init", "run"] }] }, allow_failure: false, when: "on_success", @@ -649,10 +654,12 @@ module Ci coverage_regex: nil, tag_list: [], options: { - image: { name: "ruby:2.5", entrypoint: ["/usr/local/bin/init", "run"] }, - services: [{ name: "postgresql", alias: "db-pg", entrypoint: ["/usr/local/bin/init", "run"], - command: ["/usr/local/bin/init", "run"] }, - { name: "docker:dind" }] + before_script: ["pwd"], + script: ["rspec"], + image: { name: "ruby:2.5", entrypoint: ["/usr/local/bin/init", "run"] }, + services: [{ name: "postgresql", alias: "db-pg", entrypoint: ["/usr/local/bin/init", "run"], + command: ["/usr/local/bin/init", "run"] }, + { name: "docker:dind" }] }, allow_failure: false, when: "on_success", @@ -680,6 +687,8 @@ module Ci coverage_regex: nil, tag_list: [], options: { + before_script: ["pwd"], + script: ["rspec"], image: { name: "ruby:2.1" }, services: [{ name: "mysql" }, { name: "docker:dind" }] }, @@ -707,8 +716,10 @@ module Ci coverage_regex: nil, tag_list: [], options: { - image: { name: "ruby:2.5" }, - services: [{ name: "postgresql" }, { name: "docker:dind" }] + before_script: ["pwd"], + script: ["rspec"], + image: { name: "ruby:2.5" }, + services: [{ name: "postgresql" }, { name: "docker:dind" }] }, allow_failure: false, when: "on_success", @@ -951,6 +962,8 @@ module Ci coverage_regex: nil, tag_list: [], options: { + before_script: ["pwd"], + script: ["rspec"], image: { name: "ruby:2.1" }, services: [{ name: "mysql" }], artifacts: { @@ -1162,7 +1175,9 @@ module Ci commands: "test", coverage_regex: nil, tag_list: [], - options: {}, + options: { + script: ["test"] + }, when: "on_success", allow_failure: false, environment: nil, @@ -1208,7 +1223,9 @@ module Ci commands: "execute-script-for-job", coverage_regex: nil, tag_list: [], - options: {}, + options: { + script: ["execute-script-for-job"] + }, when: "on_success", allow_failure: false, environment: nil, @@ -1221,7 +1238,9 @@ module Ci commands: "execute-script-for-job", coverage_regex: nil, tag_list: [], - options: {}, + options: { + script: ["execute-script-for-job"] + }, when: "on_success", allow_failure: false, environment: nil, diff --git a/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb b/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb index fc72df575be..15b3db0ed3d 100644 --- a/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb +++ b/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Auth::UniqueIpsLimiter, :redis, lib: true do +describe Gitlab::Auth::UniqueIpsLimiter, :clean_gitlab_redis_shared_state, lib: true do include_context 'unique ips sign in limit' let(:user) { create(:user) } diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index d09da951869..55780518230 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -206,7 +206,7 @@ describe Gitlab::Auth, lib: true do end it 'throws an error suggesting user create a PAT when internal auth is disabled' do - allow_any_instance_of(ApplicationSetting).to receive(:signin_enabled?) { false } + allow_any_instance_of(ApplicationSetting).to receive(:password_authentication_enabled?) { false } expect { gl_auth.find_for_git_client('foo', 'bar', project: nil, ip: 'ip') }.to raise_error(Gitlab::Auth::MissingPersonalTokenError) end @@ -279,6 +279,16 @@ describe Gitlab::Auth, lib: true do gl_auth.find_with_user_password('ldap_user', 'password') end end + + context "with sign-in disabled" do + before do + stub_application_setting(password_authentication_enabled: false) + end + + it "does not find user by valid login/password" do + expect(gl_auth.find_with_user_password(username, password)).to be_nil + end + end end private diff --git a/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb b/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb new file mode 100644 index 00000000000..a910fb105a5 --- /dev/null +++ b/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe Gitlab::BackgroundMigration::MigrateSystemUploadsToNewFolder do + let(:migration) { described_class.new } + + before do + allow(migration).to receive(:logger).and_return(Logger.new(nil)) + end + + describe '#perform' do + it 'renames the path of system-uploads', truncate: true do + upload = create(:upload, model: create(:empty_project), path: 'uploads/system/project/avatar.jpg') + + migration.perform('uploads/system/', 'uploads/-/system/') + + expect(upload.reload.path).to eq('uploads/-/system/project/avatar.jpg') + end + end +end diff --git a/spec/lib/gitlab/background_migration_spec.rb b/spec/lib/gitlab/background_migration_spec.rb index 64f82fe27b2..cfa59280139 100644 --- a/spec/lib/gitlab/background_migration_spec.rb +++ b/spec/lib/gitlab/background_migration_spec.rb @@ -1,46 +1,120 @@ require 'spec_helper' describe Gitlab::BackgroundMigration do + describe '.queue' do + it 'returns background migration worker queue' do + expect(described_class.queue) + .to eq BackgroundMigrationWorker.sidekiq_options['queue'] + end + end + describe '.steal' do - it 'steals jobs from a queue' do - queue = [double(:job, args: ['Foo', [10, 20]])] + context 'when there are enqueued jobs present' do + let(:queue) do + [double(args: ['Foo', [10, 20]], queue: described_class.queue)] + end + + before do + allow(Sidekiq::Queue).to receive(:new) + .with(described_class.queue) + .and_return(queue) + end + + context 'when queue contains unprocessed jobs' do + it 'steals jobs from a queue' do + expect(queue[0]).to receive(:delete).and_return(true) + + expect(described_class).to receive(:perform) + .with('Foo', [10, 20], anything) + + described_class.steal('Foo') + end + + it 'does not steal job that has already been taken' do + expect(queue[0]).to receive(:delete).and_return(false) + + expect(described_class).not_to receive(:perform) + + described_class.steal('Foo') + end + + it 'does not steal jobs for a different migration' do + expect(described_class).not_to receive(:perform) - allow(Sidekiq::Queue).to receive(:new) - .with(BackgroundMigrationWorker.sidekiq_options['queue']) - .and_return(queue) + expect(queue[0]).not_to receive(:delete) - expect(queue[0]).to receive(:delete) + described_class.steal('Bar') + end + end - expect(described_class).to receive(:perform).with('Foo', [10, 20]) + context 'when one of the jobs raises an error' do + let(:migration) { spy(:migration) } - described_class.steal('Foo') + let(:queue) do + [double(args: ['Foo', [10, 20]], queue: described_class.queue), + double(args: ['Foo', [20, 30]], queue: described_class.queue)] + end + + before do + stub_const("#{described_class}::Foo", migration) + + allow(queue[0]).to receive(:delete).and_return(true) + allow(queue[1]).to receive(:delete).and_return(true) + end + + it 'enqueues the migration again and re-raises the error' do + allow(migration).to receive(:perform).with(10, 20) + .and_raise(Exception, 'Migration error').once + + expect(BackgroundMigrationWorker).to receive(:perform_async) + .with('Foo', [10, 20]).once + + expect { described_class.steal('Foo') }.to raise_error(Exception) + end + end end - it 'does not steal jobs for a different migration' do - queue = [double(:job, args: ['Foo', [10, 20]])] + context 'when there are scheduled jobs present', :sidekiq, :redis do + it 'steals all jobs from the scheduled sets' do + Sidekiq::Testing.disable! do + BackgroundMigrationWorker.perform_in(10.minutes, 'Object') - allow(Sidekiq::Queue).to receive(:new) - .with(BackgroundMigrationWorker.sidekiq_options['queue']) - .and_return(queue) + expect(Sidekiq::ScheduledSet.new).to be_one + expect(described_class).to receive(:perform).with('Object', any_args) - expect(described_class).not_to receive(:perform) + described_class.steal('Object') - expect(queue[0]).not_to receive(:delete) + expect(Sidekiq::ScheduledSet.new).to be_none + end + end + end - described_class.steal('Bar') + context 'when there are enqueued and scheduled jobs present', :sidekiq, :redis do + it 'steals from the scheduled sets queue first' do + Sidekiq::Testing.disable! do + expect(described_class).to receive(:perform) + .with('Object', [1], anything).ordered + expect(described_class).to receive(:perform) + .with('Object', [2], anything).ordered + + BackgroundMigrationWorker.perform_async('Object', [2]) + BackgroundMigrationWorker.perform_in(10.minutes, 'Object', [1]) + + described_class.steal('Object') + end + end end end describe '.perform' do - it 'performs a background migration' do - instance = double(:instance) - klass = double(:klass, new: instance) + let(:migration) { spy(:migration) } - expect(described_class).to receive(:const_get) - .with('Foo') - .and_return(klass) + before do + stub_const("#{described_class.name}::Foo", migration) + end - expect(instance).to receive(:perform).with(10, 20) + it 'performs a background migration' do + expect(migration).to receive(:perform).with(10, 20).once described_class.perform('Foo', [10, 20]) end diff --git a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb index 07db6c3a640..0daf41a7c86 100644 --- a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb +++ b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Cache::Ci::ProjectPipelineStatus, :redis do +describe Gitlab::Cache::Ci::ProjectPipelineStatus, :clean_gitlab_redis_cache do let!(:project) { create(:project) } let(:pipeline_status) { described_class.new(project) } let(:cache_key) { "projects/#{project.id}/pipeline_status" } @@ -28,8 +28,8 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :redis do expect(project.instance_variable_get('@pipeline_status')).to be_a(described_class) end - describe 'without a status in redis' do - it 'loads the status from a commit when it was not in redis' do + describe 'without a status in redis_cache' do + it 'loads the status from a commit when it was not in redis_cache' do empty_status = { sha: nil, status: nil, ref: nil } fake_pipeline = described_class.new( project_without_status, @@ -48,9 +48,9 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :redis do described_class.load_in_batch_for_projects([project_without_status]) end - it 'only connects to redis twice' do + it 'only connects to redis_cache twice' do # Once to load, once to store in the cache - expect(Gitlab::Redis).to receive(:with).exactly(2).and_call_original + expect(Gitlab::Redis::Cache).to receive(:with).exactly(2).and_call_original described_class.load_in_batch_for_projects([project_without_status]) @@ -58,9 +58,9 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :redis do end end - describe 'when a status was cached in redis' do + describe 'when a status was cached in redis_cache' do before do - Gitlab::Redis.with do |redis| + Gitlab::Redis::Cache.with do |redis| redis.mapped_hmset(cache_key, { sha: sha, status: status, ref: ref }) end @@ -76,8 +76,8 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :redis do expect(pipeline_status.ref).to eq(ref) end - it 'only connects to redis once' do - expect(Gitlab::Redis).to receive(:with).exactly(1).and_call_original + it 'only connects to redis_cache once' do + expect(Gitlab::Redis::Cache).to receive(:with).exactly(1).and_call_original described_class.load_in_batch_for_projects([project]) @@ -94,8 +94,8 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :redis do end describe '.cached_results_for_projects' do - it 'loads a status from redis for all projects' do - Gitlab::Redis.with do |redis| + it 'loads a status from caching for all projects' do + Gitlab::Redis::Cache.with do |redis| redis.mapped_hmset(cache_key, { sha: sha, status: status, ref: ref }) end @@ -183,7 +183,7 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :redis do end end - describe "#load_from_project" do + describe "#load_from_project", :clean_gitlab_redis_cache do let!(:pipeline) { create(:ci_pipeline, :success, project: project, sha: project.commit.sha) } it 'reads the status from the pipeline for the commit' do @@ -203,40 +203,40 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :redis do end end - describe "#store_in_cache", :redis do - it "sets the object in redis" do + describe "#store_in_cache", :clean_gitlab_redis_cache do + it "sets the object in caching" do pipeline_status.sha = '123456' pipeline_status.status = 'failed' pipeline_status.store_in_cache - read_sha, read_status = Gitlab::Redis.with { |redis| redis.hmget(cache_key, :sha, :status) } + read_sha, read_status = Gitlab::Redis::Cache.with { |redis| redis.hmget(cache_key, :sha, :status) } expect(read_sha).to eq('123456') expect(read_status).to eq('failed') end end - describe '#store_in_cache_if_needed', :redis do + describe '#store_in_cache_if_needed', :clean_gitlab_redis_cache do it 'stores the state in the cache when the sha is the HEAD of the project' do create(:ci_pipeline, :success, project: project, sha: project.commit.sha) pipeline_status = described_class.load_for_project(project) pipeline_status.store_in_cache_if_needed - sha, status, ref = Gitlab::Redis.with { |redis| redis.hmget(cache_key, :sha, :status, :ref) } + sha, status, ref = Gitlab::Redis::Cache.with { |redis| redis.hmget(cache_key, :sha, :status, :ref) } expect(sha).not_to be_nil expect(status).not_to be_nil expect(ref).not_to be_nil end - it "doesn't store the status in redis when the sha is not the head of the project" do + it "doesn't store the status in redis_cache when the sha is not the head of the project" do other_status = described_class.new( project, pipeline_info: { sha: "123456", status: "failed" } ) other_status.store_in_cache_if_needed - sha, status = Gitlab::Redis.with { |redis| redis.hmget(cache_key, :sha, :status) } + sha, status = Gitlab::Redis::Cache.with { |redis| redis.hmget(cache_key, :sha, :status) } expect(sha).to be_nil expect(status).to be_nil @@ -244,7 +244,7 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :redis do it "deletes the cache if the repository doesn't have a head commit" do empty_project = create(:empty_project) - Gitlab::Redis.with do |redis| + Gitlab::Redis::Cache.with do |redis| redis.mapped_hmset(cache_key, { sha: 'sha', status: 'pending', ref: 'master' }) end @@ -255,7 +255,7 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :redis do }) other_status.store_in_cache_if_needed - sha, status, ref = Gitlab::Redis.with { |redis| redis.hmget("projects/#{empty_project.id}/pipeline_status", :sha, :status, :ref) } + sha, status, ref = Gitlab::Redis::Cache.with { |redis| redis.hmget("projects/#{empty_project.id}/pipeline_status", :sha, :status, :ref) } expect(sha).to be_nil expect(status).to be_nil @@ -263,20 +263,20 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :redis do end end - describe "with a status in redis", :redis do + describe "with a status in caching", :clean_gitlab_redis_cache do let(:status) { 'success' } let(:sha) { '424d1b73bc0d3cb726eb7dc4ce17a4d48552f8c6' } let(:ref) { 'master' } before do - Gitlab::Redis.with do |redis| + Gitlab::Redis::Cache.with do |redis| redis.mapped_hmset(cache_key, { sha: sha, status: status, ref: ref }) end end describe '#load_from_cache' do - it 'reads the status from redis' do + it 'reads the status from redis_cache' do pipeline_status.load_from_cache expect(pipeline_status.sha).to eq(sha) @@ -292,10 +292,10 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :redis do end describe '#delete_from_cache' do - it 'deletes values from redis' do + it 'deletes values from redis_cache' do pipeline_status.delete_from_cache - key_exists = Gitlab::Redis.with { |redis| redis.exists(cache_key) } + key_exists = Gitlab::Redis::Cache.with { |redis| redis.exists(cache_key) } expect(key_exists).to be_falsy end diff --git a/spec/lib/gitlab/cache/request_cache_spec.rb b/spec/lib/gitlab/cache/request_cache_spec.rb new file mode 100644 index 00000000000..5b82c216a13 --- /dev/null +++ b/spec/lib/gitlab/cache/request_cache_spec.rb @@ -0,0 +1,133 @@ +require 'spec_helper' + +describe Gitlab::Cache::RequestCache do + let(:klass) do + Class.new do + extend Gitlab::Cache::RequestCache + + attr_accessor :id, :name, :result, :extra + + def self.name + 'ExpensiveAlgorithm' + end + + def initialize(id, name, result, extra = nil) + self.id = id + self.name = name + self.result = result + self.extra = nil + end + + request_cache def compute(arg) + result << arg + end + + request_cache def repute(arg) + result << arg + end + + def dispute(arg) + result << arg + end + request_cache(:dispute) { extra } + end + end + + let(:algorithm) { klass.new('id', 'name', []) } + + shared_examples 'cache for the same instance' do + it 'does not compute twice for the same argument' do + algorithm.compute(true) + result = algorithm.compute(true) + + expect(result).to eq([true]) + end + + it 'computes twice for the different argument' do + algorithm.compute(true) + result = algorithm.compute(false) + + expect(result).to eq([true, false]) + end + + it 'computes twice for the different class name' do + algorithm.compute(true) + allow(klass).to receive(:name).and_return('CheapAlgo') + result = algorithm.compute(true) + + expect(result).to eq([true, true]) + end + + it 'computes twice for the different method' do + algorithm.compute(true) + result = algorithm.repute(true) + + expect(result).to eq([true, true]) + end + + context 'when request_cache_key is provided' do + before do + klass.request_cache_key do + [id, name] + end + end + + it 'computes twice for the different keys, id' do + algorithm.compute(true) + algorithm.id = 'ad' + result = algorithm.compute(true) + + expect(result).to eq([true, true]) + end + + it 'computes twice for the different keys, name' do + algorithm.compute(true) + algorithm.name = 'same' + result = algorithm.compute(true) + + expect(result).to eq([true, true]) + end + + it 'uses extra method cache key if provided' do + algorithm.dispute(true) # miss + algorithm.extra = true + algorithm.dispute(true) # miss + result = algorithm.dispute(true) # hit + + expect(result).to eq([true, true]) + end + end + end + + context 'when RequestStore is active', :request_store do + it_behaves_like 'cache for the same instance' + + it 'computes once for different instances when keys are the same' do + algorithm.compute(true) + result = klass.new('id', 'name', algorithm.result).compute(true) + + expect(result).to eq([true]) + end + + it 'computes twice if RequestStore starts over' do + algorithm.compute(true) + RequestStore.end! + RequestStore.clear! + RequestStore.begin! + result = algorithm.compute(true) + + expect(result).to eq([true, true]) + end + end + + context 'when RequestStore is inactive' do + it_behaves_like 'cache for the same instance' + + it 'computes twice for different instances even if keys are the same' do + algorithm.compute(true) + result = klass.new('id', 'name', algorithm.result).compute(true) + + expect(result).to eq([true, true]) + end + end +end diff --git a/spec/lib/gitlab/ci/build/step_spec.rb b/spec/lib/gitlab/ci/build/step_spec.rb index 49457b129e3..5a21282712a 100644 --- a/spec/lib/gitlab/ci/build/step_spec.rb +++ b/spec/lib/gitlab/ci/build/step_spec.rb @@ -1,21 +1,50 @@ require 'spec_helper' describe Gitlab::Ci::Build::Step do - let(:job) { create(:ci_build, :no_options, commands: "ls -la\ndate") } - describe '#from_commands' do - subject { described_class.from_commands(job) } - - it 'fabricates an object' do - expect(subject.name).to eq(:script) - expect(subject.script).to eq(['ls -la', 'date']) - expect(subject.timeout).to eq(job.timeout) - expect(subject.when).to eq('on_success') - expect(subject.allow_failure).to be_falsey + shared_examples 'has correct script' do + subject { described_class.from_commands(job) } + + it 'fabricates an object' do + expect(subject.name).to eq(:script) + expect(subject.script).to eq(script) + expect(subject.timeout).to eq(job.timeout) + expect(subject.when).to eq('on_success') + expect(subject.allow_failure).to be_falsey + end + end + + context 'when commands are specified' do + it_behaves_like 'has correct script' do + let(:job) { create(:ci_build, :no_options, commands: "ls -la\ndate") } + let(:script) { ['ls -la', 'date'] } + end + end + + context 'when script option is specified' do + it_behaves_like 'has correct script' do + let(:job) { create(:ci_build, :no_options, options: { script: ["ls -la\necho aaa", "date"] }) } + let(:script) { ["ls -la\necho aaa", 'date'] } + end + end + + context 'when before and script option is specified' do + it_behaves_like 'has correct script' do + let(:job) do + create(:ci_build, options: { + before_script: ["ls -la\necho aaa"], + script: ["date"] + }) + end + + let(:script) { ["ls -la\necho aaa", 'date'] } + end end end describe '#from_after_script' do + let(:job) { create(:ci_build) } + subject { described_class.from_after_script(job) } context 'when after_script is empty' do diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb index bbb3f9912a3..13f0338b6aa 100644 --- a/spec/lib/gitlab/ci/trace/stream_spec.rb +++ b/spec/lib/gitlab/ci/trace/stream_spec.rb @@ -293,5 +293,12 @@ describe Gitlab::Ci::Trace::Stream do it { is_expected.to eq("65") } end + + context 'malicious regexp' do + let(:data) { malicious_text } + let(:regex) { malicious_regexp } + + include_examples 'malicious regexp' + end end end diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb index a566f24f6a6..d57ffcae8e1 100644 --- a/spec/lib/gitlab/current_settings_spec.rb +++ b/spec/lib/gitlab/current_settings_spec.rb @@ -27,10 +27,23 @@ describe Gitlab::CurrentSettings do end it 'falls back to DB if Redis fails' do + db_settings = ApplicationSetting.create!(ApplicationSetting.defaults) + expect(ApplicationSetting).to receive(:cached).and_raise(::Redis::BaseError) - expect(ApplicationSetting).to receive(:last).and_call_original + expect(Rails.cache).to receive(:fetch).with(ApplicationSetting::CACHE_KEY).and_raise(Redis::BaseError) - expect(current_application_settings).to be_a(ApplicationSetting) + expect(current_application_settings).to eq(db_settings) + end + + it 'creates default ApplicationSettings if none are present' do + expect(ApplicationSetting).to receive(:cached).and_raise(::Redis::BaseError) + expect(Rails.cache).to receive(:fetch).with(ApplicationSetting::CACHE_KEY).and_raise(Redis::BaseError) + + settings = current_application_settings + + expect(settings).to be_a(ApplicationSetting) + expect(settings).to be_persisted + expect(settings).to have_attributes(ApplicationSetting.defaults) end context 'with migrations pending' do diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index 4259be3f522..a2acd15c8fb 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -174,13 +174,23 @@ describe Gitlab::Database::MigrationHelpers, lib: true do allow(Gitlab::Database).to receive(:mysql?).and_return(false) end - it 'creates a concurrent foreign key' do + it 'creates a concurrent foreign key and validates it' do expect(model).to receive(:disable_statement_timeout) expect(model).to receive(:execute).ordered.with(/NOT VALID/) expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/) model.add_concurrent_foreign_key(:projects, :users, column: :user_id) end + + it 'appends a valid ON DELETE statement' do + expect(model).to receive(:disable_statement_timeout) + expect(model).to receive(:execute).with(/ON DELETE SET NULL/) + expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/) + + model.add_concurrent_foreign_key(:projects, :users, + column: :user_id, + on_delete: :nullify) + end end end end diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb index 8813f129ef5..df7d1b5d27a 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb @@ -236,7 +236,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :trunca subject.track_rename('namespace', 'path/to/namespace', 'path/to/renamed') old_path, new_path = [nil, nil] - Gitlab::Redis.with do |redis| + Gitlab::Redis::SharedState.with do |redis| rename_info = redis.lpop(key) old_path, new_path = JSON.parse(rename_info) end @@ -268,7 +268,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :trunca key = 'rename:FakeRenameReservedPathMigrationV1:project' stored_renames = nil rename_count = 0 - Gitlab::Redis.with do |redis| + Gitlab::Redis::SharedState.with do |redis| stored_renames = redis.lrange(key, 0, 1) rename_count = redis.llen(key) end diff --git a/spec/lib/gitlab/exclusive_lease_spec.rb b/spec/lib/gitlab/exclusive_lease_spec.rb index 81bbd70ffb8..590d6da4113 100644 --- a/spec/lib/gitlab/exclusive_lease_spec.rb +++ b/spec/lib/gitlab/exclusive_lease_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::ExclusiveLease, type: :redis do +describe Gitlab::ExclusiveLease, type: :clean_gitlab_redis_shared_state do let(:unique_key) { SecureRandom.hex(10) } describe '#try_obtain' do diff --git a/spec/lib/gitlab/fake_application_settings_spec.rb b/spec/lib/gitlab/fake_application_settings_spec.rb index b793176d84a..34322c2a693 100644 --- a/spec/lib/gitlab/fake_application_settings_spec.rb +++ b/spec/lib/gitlab/fake_application_settings_spec.rb @@ -1,25 +1,25 @@ require 'spec_helper' describe Gitlab::FakeApplicationSettings do - let(:defaults) { { signin_enabled: false, foobar: 'asdf', signup_enabled: true, 'test?' => 123 } } + let(:defaults) { { password_authentication_enabled: false, foobar: 'asdf', signup_enabled: true, 'test?' => 123 } } subject { described_class.new(defaults) } it 'wraps OpenStruct variables properly' do - expect(subject.signin_enabled).to be_falsey + expect(subject.password_authentication_enabled).to be_falsey expect(subject.signup_enabled).to be_truthy expect(subject.foobar).to eq('asdf') end it 'defines predicate methods' do - expect(subject.signin_enabled?).to be_falsey + expect(subject.password_authentication_enabled?).to be_falsey expect(subject.signup_enabled?).to be_truthy end it 'predicate method changes when value is updated' do - subject.signin_enabled = true + subject.password_authentication_enabled = true - expect(subject.signin_enabled?).to be_truthy + expect(subject.password_authentication_enabled?).to be_truthy end it 'does not define a predicate method' do diff --git a/spec/lib/gitlab/git/branch_spec.rb b/spec/lib/gitlab/git/branch_spec.rb index d1d7ed1d02a..cdf1b8beee3 100644 --- a/spec/lib/gitlab/git/branch_spec.rb +++ b/spec/lib/gitlab/git/branch_spec.rb @@ -7,51 +7,6 @@ describe Gitlab::Git::Branch, seed_helper: true do it { is_expected.to be_kind_of Array } - describe 'initialize' do - let(:commit_id) { 'f00' } - let(:commit_subject) { "My commit".force_encoding('ASCII-8BIT') } - let(:committer) do - Gitaly::FindLocalBranchCommitAuthor.new( - name: generate(:name), - email: generate(:email), - date: Google::Protobuf::Timestamp.new(seconds: 123) - ) - end - let(:author) do - Gitaly::FindLocalBranchCommitAuthor.new( - name: generate(:name), - email: generate(:email), - date: Google::Protobuf::Timestamp.new(seconds: 456) - ) - end - let(:gitaly_branch) do - Gitaly::FindLocalBranchResponse.new( - name: 'foo', commit_id: commit_id, commit_subject: commit_subject, - commit_author: author, commit_committer: committer - ) - end - let(:attributes) do - { - id: commit_id, - message: commit_subject, - authored_date: Time.at(author.date.seconds), - author_name: author.name, - author_email: author.email, - committed_date: Time.at(committer.date.seconds), - committer_name: committer.name, - committer_email: committer.email - } - end - let(:branch) { described_class.new(repository, 'foo', gitaly_branch) } - - it 'parses Gitaly::FindLocalBranchResponse correctly' do - expect(Gitlab::Git::Commit).to receive(:decorate) - .with(hash_including(attributes)).and_call_original - - expect(branch.dereferenced_target.message).to be_utf8 - end - end - describe '#size' do subject { super().size } it { is_expected.to eq(SeedRepo::Repo::BRANCHES.size) } diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb index f20a14155dc..60de91324f0 100644 --- a/spec/lib/gitlab/git/commit_spec.rb +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -64,6 +64,52 @@ describe Gitlab::Git::Commit, seed_helper: true do end end + describe "Commit info from gitaly commit" do + let(:id) { 'f00' } + let(:subject) { "My commit".force_encoding('ASCII-8BIT') } + let(:body) { subject + "My body".force_encoding('ASCII-8BIT') } + let(:committer) do + Gitaly::CommitAuthor.new( + name: generate(:name), + email: generate(:email), + date: Google::Protobuf::Timestamp.new(seconds: 123) + ) + end + let(:author) do + Gitaly::CommitAuthor.new( + name: generate(:name), + email: generate(:email), + date: Google::Protobuf::Timestamp.new(seconds: 456) + ) + end + let(:gitaly_commit) do + Gitaly::GitCommit.new( + id: id, + subject: subject, + body: body, + author: author, + committer: committer + ) + end + let(:commit) { described_class.new(gitaly_commit) } + + it { expect(commit.short_id).to eq(id[0..10]) } + it { expect(commit.id).to eq(id) } + it { expect(commit.sha).to eq(id) } + it { expect(commit.safe_message).to eq(body) } + it { expect(commit.created_at).to eq(Time.at(committer.date.seconds)) } + it { expect(commit.author_email).to eq(author.email) } + it { expect(commit.author_name).to eq(author.name) } + it { expect(commit.committer_name).to eq(committer.name) } + it { expect(commit.committer_email).to eq(committer.email) } + + context 'no body' do + let(:body) { "".force_encoding('ASCII-8BIT') } + + it { expect(commit.safe_message).to eq(subject) } + end + end + context 'Class methods' do describe '.find' do it "should return first head commit if without params" do diff --git a/spec/lib/gitlab/git/diff_spec.rb b/spec/lib/gitlab/git/diff_spec.rb index d97e85364c2..7ea3386ac2a 100644 --- a/spec/lib/gitlab/git/diff_spec.rb +++ b/spec/lib/gitlab/git/diff_spec.rb @@ -34,7 +34,7 @@ EOT describe 'size limit feature toggles' do context 'when the feature gitlab_git_diff_size_limit_increase is enabled' do before do - Feature.enable('gitlab_git_diff_size_limit_increase') + stub_feature_flags(gitlab_git_diff_size_limit_increase: true) end it 'returns 200 KB for size_limit' do @@ -48,7 +48,7 @@ EOT context 'when the feature gitlab_git_diff_size_limit_increase is disabled' do before do - Feature.disable('gitlab_git_diff_size_limit_increase') + stub_feature_flags(gitlab_git_diff_size_limit_increase: false) end it 'returns 100 KB for size_limit' do @@ -241,7 +241,7 @@ EOT end describe '.filter_diff_options' do - let(:options) { { max_size: 100, invalid_opt: true } } + let(:options) { { max_files: 100, invalid_opt: true } } context "without default options" do let(:filtered_options) { described_class.filter_diff_options(options) } @@ -253,7 +253,7 @@ EOT context "with default options" do let(:filtered_options) do - default_options = { max_size: 5, bad_opt: 1, ignore_whitespace: true } + default_options = { max_files: 5, bad_opt: 1, ignore_whitespace_change: true } described_class.filter_diff_options(options, default_options) end @@ -263,12 +263,12 @@ EOT end it "should merge with default options" do - expect(filtered_options).to have_key(:ignore_whitespace) + expect(filtered_options).to have_key(:ignore_whitespace_change) end it "should override default options" do - expect(filtered_options).to have_key(:max_size) - expect(filtered_options[:max_size]).to eq(100) + expect(filtered_options).to have_key(:max_files) + expect(filtered_options[:max_files]).to eq(100) end end end diff --git a/spec/lib/gitlab/git/hook_spec.rb b/spec/lib/gitlab/git/hook_spec.rb index 73518656bde..19f45ea1cb2 100644 --- a/spec/lib/gitlab/git/hook_spec.rb +++ b/spec/lib/gitlab/git/hook_spec.rb @@ -2,6 +2,12 @@ require 'spec_helper' require 'fileutils' describe Gitlab::Git::Hook, lib: true do + before do + # We need this because in the spec/spec_helper.rb we define it like this: + # allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, nil]) + allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_call_original + end + describe "#trigger" do let(:project) { create(:project, :repository) } let(:repo_path) { project.repository.path } diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index acffd335e43..83d067b2c31 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -45,11 +45,11 @@ describe Gitlab::Git::Repository, seed_helper: true do end it 'gets the branch name from GitalyClient' do - expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name) + expect_any_instance_of(Gitlab::GitalyClient::RefService).to receive(:default_branch_name) repository.root_ref end - it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::Ref, :default_branch_name do + it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RefService, :default_branch_name do subject { repository.root_ref } end end @@ -132,11 +132,11 @@ describe Gitlab::Git::Repository, seed_helper: true do it { is_expected.not_to include("branch-from-space") } it 'gets the branch names from GitalyClient' do - expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names) + expect_any_instance_of(Gitlab::GitalyClient::RefService).to receive(:branch_names) subject end - it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::Ref, :branch_names + it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RefService, :branch_names end describe '#tag_names' do @@ -160,11 +160,11 @@ describe Gitlab::Git::Repository, seed_helper: true do it { is_expected.not_to include("v5.0.0") } it 'gets the tag names from GitalyClient' do - expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names) + expect_any_instance_of(Gitlab::GitalyClient::RefService).to receive(:tag_names) subject end - it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::Ref, :tag_names + it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RefService, :tag_names end shared_examples 'archive check' do |extenstion| @@ -234,33 +234,6 @@ describe Gitlab::Git::Repository, seed_helper: true do it { expect(repository.bare?).to be_truthy } end - describe '#heads' do - let(:heads) { repository.heads } - subject { heads } - - it { is_expected.to be_kind_of Array } - - describe '#size' do - subject { super().size } - it { is_expected.to eq(SeedRepo::Repo::BRANCHES.size) } - end - - context :head do - subject { heads.first } - - describe '#name' do - subject { super().name } - it { is_expected.to eq("feature") } - end - - context :commit do - subject { heads.first.dereferenced_target.sha } - - it { is_expected.to eq("0b4bc9a49b562e85de7cc9e834518ea6828729b9") } - end - end - end - describe '#ref_names' do let(:ref_names) { repository.ref_names } subject { ref_names } @@ -278,42 +251,6 @@ describe Gitlab::Git::Repository, seed_helper: true do end end - describe '#search_files' do - let(:results) { repository.search_files('rails', 'master') } - subject { results } - - it { is_expected.to be_kind_of Array } - - describe '#first' do - subject { super().first } - it { is_expected.to be_kind_of Gitlab::Git::BlobSnippet } - end - - context 'blob result' do - subject { results.first } - - describe '#ref' do - subject { super().ref } - it { is_expected.to eq('master') } - end - - describe '#filename' do - subject { super().filename } - it { is_expected.to eq('CHANGELOG') } - end - - describe '#startline' do - subject { super().startline } - it { is_expected.to eq(35) } - end - - describe '#data' do - subject { super().data } - it { is_expected.to include "Ability to filter by multiple labels" } - end - end - end - describe '#submodule_url_for' do let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) } let(:ref) { 'master' } @@ -431,7 +368,7 @@ describe Gitlab::Git::Repository, seed_helper: true do context 'when Gitaly commit_count feature is enabled' do it_behaves_like 'counting commits' - it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::Commit, :commit_count do + it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::CommitService, :commit_count do subject { repository.commit_count('master') } end end @@ -521,7 +458,7 @@ describe Gitlab::Git::Repository, seed_helper: true do end it "should refresh the repo's #heads collection" do - head_names = @normal_repo.heads.map { |h| h.name } + head_names = @normal_repo.branches.map { |h| h.name } expect(head_names).to include(new_branch) end @@ -542,7 +479,7 @@ describe Gitlab::Git::Repository, seed_helper: true do eq(normal_repo.rugged.branches["master"].target.oid) ) - head_names = normal_repo.heads.map { |h| h.name } + head_names = normal_repo.branches.map { |h| h.name } expect(head_names).not_to include(new_branch) end @@ -589,10 +526,6 @@ describe Gitlab::Git::Repository, seed_helper: true do expect(@repo.rugged.branches["feature"]).to be_nil end - it "should update the repo's #heads collection" do - expect(@repo.heads).not_to include("feature") - end - after(:all) do FileUtils.rm_rf(TEST_MUTABLE_REPO_PATH) ensure_seeds @@ -705,9 +638,9 @@ describe Gitlab::Git::Repository, seed_helper: true do # Add new commits so that there's a renamed file in the commit history repo = Gitlab::Git::Repository.new('default', TEST_REPO_PATH).rugged - commit_with_old_name = new_commit_edit_old_file(repo) - rename_commit = new_commit_move_file(repo) - commit_with_new_name = new_commit_edit_new_file(repo) + commit_with_old_name = Gitlab::Git::Commit.decorate(new_commit_edit_old_file(repo)) + rename_commit = Gitlab::Git::Commit.decorate(new_commit_move_file(repo)) + commit_with_new_name = Gitlab::Git::Commit.decorate(new_commit_edit_new_file(repo)) end after(:context) do @@ -880,8 +813,8 @@ describe Gitlab::Git::Repository, seed_helper: true do context "compare results between log_by_walk and log_by_shell" do let(:options) { { ref: "master" } } - let(:commits_by_walk) { repository.log(options).map(&:oid) } - let(:commits_by_shell) { repository.log(options.merge({ disable_walk: true })).map(&:oid) } + let(:commits_by_walk) { repository.log(options).map(&:id) } + let(:commits_by_shell) { repository.log(options.merge({ disable_walk: true })).map(&:id) } it { expect(commits_by_walk).to eq(commits_by_shell) } @@ -924,7 +857,7 @@ describe Gitlab::Git::Repository, seed_helper: true do expect(commits.size).to be > 0 expect(commits).to satisfy do |commits| - commits.all? { |commit| commit.time >= options[:after] } + commits.all? { |commit| commit.committed_date >= options[:after] } end end end @@ -937,7 +870,7 @@ describe Gitlab::Git::Repository, seed_helper: true do expect(commits.size).to be > 0 expect(commits).to satisfy do |commits| - commits.all? { |commit| commit.time <= options[:before] } + commits.all? { |commit| commit.committed_date <= options[:before] } end end end @@ -946,7 +879,7 @@ describe Gitlab::Git::Repository, seed_helper: true do let(:options) { { ref: 'master', path: ['PROCESS.md', 'README.md'] } } def commit_files(commit) - commit.diff(commit.parent_ids.first).deltas.flat_map do |delta| + commit.diff_from_parent.deltas.flat_map do |delta| [delta.old_file[:path], delta.new_file[:path]].uniq.compact end end @@ -1292,12 +1225,12 @@ describe Gitlab::Git::Repository, seed_helper: true do end it 'gets the branches from GitalyClient' do - expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches) + expect_any_instance_of(Gitlab::GitalyClient::RefService).to receive(:local_branches) .and_return([]) @repo.local_branches end - it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::Ref, :local_branches do + it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RefService, :local_branches do subject { @repo.local_branches } end end diff --git a/spec/lib/gitlab/gitaly_client/commit_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb index dff5b25c712..93affb12f2b 100644 --- a/spec/lib/gitlab/gitaly_client/commit_spec.rb +++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' -describe Gitlab::GitalyClient::Commit do - let(:diff_stub) { double('Gitaly::Diff::Stub') } +describe Gitlab::GitalyClient::CommitService do let(:project) { create(:project, :repository) } let(:repository) { project.repository } let(:repository_message) { repository.gitaly_repository } @@ -16,7 +15,7 @@ describe Gitlab::GitalyClient::Commit do right_commit_id: commit.id ) - expect_any_instance_of(Gitaly::Diff::Stub).to receive(:commit_diff).with(request, kind_of(Hash)) + expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_diff).with(request, kind_of(Hash)) described_class.new(repository).diff_from_parent(commit) end @@ -31,7 +30,7 @@ describe Gitlab::GitalyClient::Commit do right_commit_id: initial_commit.id ) - expect_any_instance_of(Gitaly::Diff::Stub).to receive(:commit_diff).with(request, kind_of(Hash)) + expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_diff).with(request, kind_of(Hash)) described_class.new(repository).diff_from_parent(initial_commit) end @@ -61,7 +60,7 @@ describe Gitlab::GitalyClient::Commit do right_commit_id: commit.id ) - expect_any_instance_of(Gitaly::Diff::Stub).to receive(:commit_delta).with(request, kind_of(Hash)).and_return([]) + expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_delta).with(request, kind_of(Hash)).and_return([]) described_class.new(repository).commit_deltas(commit) end @@ -76,10 +75,25 @@ describe Gitlab::GitalyClient::Commit do right_commit_id: initial_commit.id ) - expect_any_instance_of(Gitaly::Diff::Stub).to receive(:commit_delta).with(request, kind_of(Hash)).and_return([]) + expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_delta).with(request, kind_of(Hash)).and_return([]) described_class.new(repository).commit_deltas(initial_commit) end end end + + describe '#between' do + let(:from) { 'master' } + let(:to) { '4b825dc642cb6eb9a060e54bf8d69288fbee4904' } + it 'sends an RPC request' do + request = Gitaly::CommitsBetweenRequest.new( + repository: repository_message, from: from, to: to + ) + + expect_any_instance_of(Gitaly::CommitService::Stub).to receive(:commits_between) + .with(request, kind_of(Hash)).and_return([]) + + described_class.new(repository).between(from, to) + end + end end diff --git a/spec/lib/gitlab/gitaly_client/notifications_spec.rb b/spec/lib/gitlab/gitaly_client/notification_service_spec.rb index 7404ffe0f06..d9597c4aa78 100644 --- a/spec/lib/gitlab/gitaly_client/notifications_spec.rb +++ b/spec/lib/gitlab/gitaly_client/notification_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::GitalyClient::Notifications do +describe Gitlab::GitalyClient::NotificationService do describe '#post_receive' do let(:project) { create(:empty_project) } let(:storage_name) { project.repository_storage } @@ -8,7 +8,7 @@ describe Gitlab::GitalyClient::Notifications do subject { described_class.new(project.repository) } it 'sends a post_receive message' do - expect_any_instance_of(Gitaly::Notifications::Stub) + expect_any_instance_of(Gitaly::NotificationService::Stub) .to receive(:post_receive).with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash)) subject.post_receive diff --git a/spec/lib/gitlab/gitaly_client/ref_spec.rb b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb index 7c090460764..1e8ed9d645b 100644 --- a/spec/lib/gitlab/gitaly_client/ref_spec.rb +++ b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::GitalyClient::Ref do +describe Gitlab::GitalyClient::RefService do let(:project) { create(:empty_project) } let(:storage_name) { project.repository_storage } let(:relative_path) { project.path_with_namespace + '.git' } @@ -8,7 +8,7 @@ describe Gitlab::GitalyClient::Ref do describe '#branch_names' do it 'sends a find_all_branch_names message' do - expect_any_instance_of(Gitaly::Ref::Stub) + expect_any_instance_of(Gitaly::RefService::Stub) .to receive(:find_all_branch_names) .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash)) .and_return([]) @@ -19,7 +19,7 @@ describe Gitlab::GitalyClient::Ref do describe '#tag_names' do it 'sends a find_all_tag_names message' do - expect_any_instance_of(Gitaly::Ref::Stub) + expect_any_instance_of(Gitaly::RefService::Stub) .to receive(:find_all_tag_names) .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash)) .and_return([]) @@ -30,7 +30,7 @@ describe Gitlab::GitalyClient::Ref do describe '#default_branch_name' do it 'sends a find_default_branch_name message' do - expect_any_instance_of(Gitaly::Ref::Stub) + expect_any_instance_of(Gitaly::RefService::Stub) .to receive(:find_default_branch_name) .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash)) .and_return(double(name: 'foo')) @@ -41,7 +41,7 @@ describe Gitlab::GitalyClient::Ref do describe '#local_branches' do it 'sends a find_local_branches message' do - expect_any_instance_of(Gitaly::Ref::Stub) + expect_any_instance_of(Gitaly::RefService::Stub) .to receive(:find_local_branches) .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash)) .and_return([]) @@ -50,7 +50,7 @@ describe Gitlab::GitalyClient::Ref do end it 'parses and sends the sort parameter' do - expect_any_instance_of(Gitaly::Ref::Stub) + expect_any_instance_of(Gitaly::RefService::Stub) .to receive(:find_local_branches) .with(gitaly_request_with_params(sort_by: :UPDATED_DESC), kind_of(Hash)) .and_return([]) @@ -59,7 +59,7 @@ describe Gitlab::GitalyClient::Ref do end it 'translates known mismatches on sort param values' do - expect_any_instance_of(Gitaly::Ref::Stub) + expect_any_instance_of(Gitaly::RefService::Stub) .to receive(:find_local_branches) .with(gitaly_request_with_params(sort_by: :NAME), kind_of(Hash)) .and_return([]) diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb index ce7b18b784a..558ddb3fbd6 100644 --- a/spec/lib/gitlab/gitaly_client_spec.rb +++ b/spec/lib/gitlab/gitaly_client_spec.rb @@ -16,9 +16,9 @@ describe Gitlab::GitalyClient, lib: true, skip_gitaly_mock: true do 'default' => { 'gitaly_address' => address } }) - expect(Gitaly::Commit::Stub).to receive(:new).with(address, any_args) + expect(Gitaly::CommitService::Stub).to receive(:new).with(address, any_args) - described_class.stub(:commit, 'default') + described_class.stub(:commit_service, 'default') end end @@ -31,9 +31,9 @@ describe Gitlab::GitalyClient, lib: true, skip_gitaly_mock: true do 'default' => { 'gitaly_address' => prefixed_address } }) - expect(Gitaly::Commit::Stub).to receive(:new).with(address, any_args) + expect(Gitaly::CommitService::Stub).to receive(:new).with(address, any_args) - described_class.stub(:commit, 'default') + described_class.stub(:commit_service, 'default') end end end diff --git a/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb b/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb index b333e162909..3de73a9ff65 100644 --- a/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb +++ b/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb @@ -109,9 +109,9 @@ describe Gitlab::HealthChecks::FsShardsCheck do expect(subject).to include(an_object_having_attributes(name: :filesystem_readable, value: 0)) expect(subject).to include(an_object_having_attributes(name: :filesystem_writable, value: 0)) - expect(subject).to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0)) - expect(subject).to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0)) - expect(subject).to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0)) + expect(subject).to include(an_object_having_attributes(name: :filesystem_access_latency_seconds, value: be >= 0)) + expect(subject).to include(an_object_having_attributes(name: :filesystem_read_latency_seconds, value: be >= 0)) + expect(subject).to include(an_object_having_attributes(name: :filesystem_write_latency_seconds, value: be >= 0)) end end @@ -127,9 +127,9 @@ describe Gitlab::HealthChecks::FsShardsCheck do expect(subject).to include(an_object_having_attributes(name: :filesystem_readable, value: 1)) expect(subject).to include(an_object_having_attributes(name: :filesystem_writable, value: 1)) - expect(subject).to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0)) - expect(subject).to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0)) - expect(subject).to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0)) + expect(subject).to include(an_object_having_attributes(name: :filesystem_access_latency_seconds, value: be >= 0)) + expect(subject).to include(an_object_having_attributes(name: :filesystem_read_latency_seconds, value: be >= 0)) + expect(subject).to include(an_object_having_attributes(name: :filesystem_write_latency_seconds, value: be >= 0)) end end end @@ -159,9 +159,9 @@ describe Gitlab::HealthChecks::FsShardsCheck do expect(subject).to include(an_object_having_attributes(name: :filesystem_readable, value: 0)) expect(subject).to include(an_object_having_attributes(name: :filesystem_writable, value: 0)) - expect(subject).to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0)) - expect(subject).to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0)) - expect(subject).to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0)) + expect(subject).to include(an_object_having_attributes(name: :filesystem_access_latency_seconds, value: be >= 0)) + expect(subject).to include(an_object_having_attributes(name: :filesystem_read_latency_seconds, value: be >= 0)) + expect(subject).to include(an_object_having_attributes(name: :filesystem_write_latency_seconds, value: be >= 0)) end end end diff --git a/spec/lib/gitlab/health_checks/redis/cache_check_spec.rb b/spec/lib/gitlab/health_checks/redis/cache_check_spec.rb new file mode 100644 index 00000000000..3693f52b51b --- /dev/null +++ b/spec/lib/gitlab/health_checks/redis/cache_check_spec.rb @@ -0,0 +1,6 @@ +require 'spec_helper' +require_relative '../simple_check_shared' + +describe Gitlab::HealthChecks::Redis::CacheCheck do + include_examples 'simple_check', 'redis_cache_ping', 'RedisCache', 'PONG' +end diff --git a/spec/lib/gitlab/health_checks/redis/queues_check_spec.rb b/spec/lib/gitlab/health_checks/redis/queues_check_spec.rb new file mode 100644 index 00000000000..c69443d205d --- /dev/null +++ b/spec/lib/gitlab/health_checks/redis/queues_check_spec.rb @@ -0,0 +1,6 @@ +require 'spec_helper' +require_relative '../simple_check_shared' + +describe Gitlab::HealthChecks::Redis::QueuesCheck do + include_examples 'simple_check', 'redis_queues_ping', 'RedisQueues', 'PONG' +end diff --git a/spec/lib/gitlab/health_checks/redis/redis_check_spec.rb b/spec/lib/gitlab/health_checks/redis/redis_check_spec.rb new file mode 100644 index 00000000000..03afc1cd761 --- /dev/null +++ b/spec/lib/gitlab/health_checks/redis/redis_check_spec.rb @@ -0,0 +1,6 @@ +require 'spec_helper' +require_relative '../simple_check_shared' + +describe Gitlab::HealthChecks::Redis::RedisCheck do + include_examples 'simple_check', 'redis_ping', 'Redis', 'PONG' +end diff --git a/spec/lib/gitlab/health_checks/redis/shared_state_check_spec.rb b/spec/lib/gitlab/health_checks/redis/shared_state_check_spec.rb new file mode 100644 index 00000000000..b72e152bbe2 --- /dev/null +++ b/spec/lib/gitlab/health_checks/redis/shared_state_check_spec.rb @@ -0,0 +1,6 @@ +require 'spec_helper' +require_relative '../simple_check_shared' + +describe Gitlab::HealthChecks::Redis::SharedStateCheck do + include_examples 'simple_check', 'redis_shared_state_ping', 'RedisSharedState', 'PONG' +end diff --git a/spec/lib/gitlab/health_checks/redis_check_spec.rb b/spec/lib/gitlab/health_checks/redis_check_spec.rb deleted file mode 100644 index 734cdcb893e..00000000000 --- a/spec/lib/gitlab/health_checks/redis_check_spec.rb +++ /dev/null @@ -1,6 +0,0 @@ -require 'spec_helper' -require_relative './simple_check_shared' - -describe Gitlab::HealthChecks::RedisCheck do - include_examples 'simple_check', 'redis_ping', 'Redis', 'PONG' -end diff --git a/spec/lib/gitlab/health_checks/simple_check_shared.rb b/spec/lib/gitlab/health_checks/simple_check_shared.rb index 3f871d66034..e2643458aca 100644 --- a/spec/lib/gitlab/health_checks/simple_check_shared.rb +++ b/spec/lib/gitlab/health_checks/simple_check_shared.rb @@ -8,7 +8,7 @@ shared_context 'simple_check' do |metrics_prefix, check_name, success_result| it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_success", value: 1)) } it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_timeout", value: 0)) } - it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_latency", value: be >= 0)) } + it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_latency_seconds", value: be >= 0)) } end context 'Check is misbehaving' do @@ -18,7 +18,7 @@ shared_context 'simple_check' do |metrics_prefix, check_name, success_result| it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_success", value: 0)) } it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_timeout", value: 0)) } - it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_latency", value: be >= 0)) } + it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_latency_seconds", value: be >= 0)) } end context 'Check is timeouting' do @@ -28,7 +28,7 @@ shared_context 'simple_check' do |metrics_prefix, check_name, success_result| it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_success", value: 0)) } it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_timeout", value: 1)) } - it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_latency", value: be >= 0)) } + it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_latency_seconds", value: be >= 0)) } end end @@ -47,7 +47,7 @@ shared_context 'simple_check' do |metrics_prefix, check_name, success_result| allow(described_class).to receive(:check).and_return 'error!' end - it { is_expected.to have_attributes(success: false, message: "unexpected #{check_name} check result: error!") } + it { is_expected.to have_attributes(success: false, message: "unexpected #{described_class.human_name} check result: error!") } end context 'Check is timeouting' do @@ -55,7 +55,7 @@ shared_context 'simple_check' do |metrics_prefix, check_name, success_result| allow(described_class).to receive(:check ).and_return Timeout::Error.new end - it { is_expected.to have_attributes(success: false, message: "#{check_name} check timed out") } + it { is_expected.to have_attributes(success: false, message: "#{described_class.human_name} check timed out") } end end diff --git a/spec/lib/gitlab/issuable_metadata_spec.rb b/spec/lib/gitlab/issuable_metadata_spec.rb new file mode 100644 index 00000000000..f9f4b290dbf --- /dev/null +++ b/spec/lib/gitlab/issuable_metadata_spec.rb @@ -0,0 +1,59 @@ +require 'spec_helper' + +describe Gitlab::IssuableMetadata, lib: true do + let(:user) { create(:user) } + let!(:project) { create(:project, :public, :repository, creator: user, namespace: user.namespace) } + + subject { Class.new { include Gitlab::IssuableMetadata }.new } + + it 'returns an empty Hash if an empty collection is provided' do + expect(subject.issuable_meta_data(Issue.none, 'Issue')).to eq({}) + end + + context 'issues' do + let!(:issue) { create(:issue, author: user, project: project) } + let!(:closed_issue) { create(:issue, state: :closed, author: user, project: project) } + let!(:downvote) { create(:award_emoji, :downvote, awardable: closed_issue) } + let!(:upvote) { create(:award_emoji, :upvote, awardable: issue) } + let!(:merge_request) { create(:merge_request, :simple, author: user, assignee: user, source_project: project, target_project: project, title: "Test") } + let!(:closing_issues) { create(:merge_requests_closing_issues, issue: issue, merge_request: merge_request) } + + it 'aggregates stats on issues' do + data = subject.issuable_meta_data(Issue.all, 'Issue') + + expect(data.count).to eq(2) + expect(data[issue.id].upvotes).to eq(1) + expect(data[issue.id].downvotes).to eq(0) + expect(data[issue.id].notes_count).to eq(0) + expect(data[issue.id].merge_requests_count).to eq(1) + + expect(data[closed_issue.id].upvotes).to eq(0) + expect(data[closed_issue.id].downvotes).to eq(1) + expect(data[closed_issue.id].notes_count).to eq(0) + expect(data[closed_issue.id].merge_requests_count).to eq(0) + end + end + + context 'merge requests' do + let!(:merge_request) { create(:merge_request, :simple, author: user, assignee: user, source_project: project, target_project: project, title: "Test") } + let!(:merge_request_closed) { create(:merge_request, state: "closed", source_project: project, target_project: project, title: "Closed Test") } + let!(:downvote) { create(:award_emoji, :downvote, awardable: merge_request) } + let!(:upvote) { create(:award_emoji, :upvote, awardable: merge_request) } + let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") } + + it 'aggregates stats on merge requests' do + data = subject.issuable_meta_data(MergeRequest.all, 'MergeRequest') + + expect(data.count).to eq(2) + expect(data[merge_request.id].upvotes).to eq(1) + expect(data[merge_request.id].downvotes).to eq(1) + expect(data[merge_request.id].notes_count).to eq(1) + expect(data[merge_request.id].merge_requests_count).to eq(0) + + expect(data[merge_request_closed.id].upvotes).to eq(0) + expect(data[merge_request_closed.id].downvotes).to eq(0) + expect(data[merge_request_closed.id].notes_count).to eq(0) + expect(data[merge_request_closed.id].merge_requests_count).to eq(0) + end + end +end diff --git a/spec/lib/gitlab/metrics/connection_rack_middleware_spec.rb b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb index 94251af305f..461b1e4182a 100644 --- a/spec/lib/gitlab/metrics/connection_rack_middleware_spec.rb +++ b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Metrics::ConnectionRackMiddleware do +describe Gitlab::Metrics::RequestsRackMiddleware do let(:app) { double('app') } subject { described_class.new(app) } @@ -22,14 +22,8 @@ describe Gitlab::Metrics::ConnectionRackMiddleware do allow(app).to receive(:call).and_return([200, nil, nil]) end - it 'increments response count with status label' do - expect(described_class).to receive_message_chain(:rack_response_count, :increment).with(include(status: 200, method: 'get')) - - subject.call(env) - end - it 'increments requests count' do - expect(described_class).to receive_message_chain(:rack_request_count, :increment).with(method: 'get') + expect(described_class).to receive_message_chain(:http_request_total, :increment).with(method: 'get') subject.call(env) end @@ -38,20 +32,21 @@ describe Gitlab::Metrics::ConnectionRackMiddleware do execution_time = 10 allow(app).to receive(:call) do |*args| Timecop.freeze(execution_time.seconds) + [200, nil, nil] end - expect(described_class).to receive_message_chain(:rack_execution_time, :observe).with({}, execution_time) + expect(described_class).to receive_message_chain(:http_request_duration_seconds, :observe).with({ status: 200, method: 'get' }, execution_time) subject.call(env) end end context '@app.call throws exception' do - let(:rack_response_count) { double('rack_response_count') } + let(:http_request_duration_seconds) { double('http_request_duration_seconds') } before do allow(app).to receive(:call).and_raise(StandardError) - allow(described_class).to receive(:rack_response_count).and_return(rack_response_count) + allow(described_class).to receive(:http_request_duration_seconds).and_return(http_request_duration_seconds) end it 'increments exceptions count' do @@ -61,25 +56,13 @@ describe Gitlab::Metrics::ConnectionRackMiddleware do end it 'increments requests count' do - expect(described_class).to receive_message_chain(:rack_request_count, :increment).with(method: 'get') - - expect { subject.call(env) }.to raise_error(StandardError) - end - - it "does't increment response count" do - expect(described_class.rack_response_count).not_to receive(:increment) + expect(described_class).to receive_message_chain(:http_request_total, :increment).with(method: 'get') expect { subject.call(env) }.to raise_error(StandardError) end - it 'measures execution time' do - execution_time = 10 - allow(app).to receive(:call) do |*args| - Timecop.freeze(execution_time.seconds) - raise StandardError - end - - expect(described_class).to receive_message_chain(:rack_execution_time, :observe).with({}, execution_time) + it "does't measure request execution time" do + expect(described_class.http_request_duration_seconds).not_to receive(:increment) expect { subject.call(env) }.to raise_error(StandardError) end diff --git a/spec/lib/gitlab/performance_bar_spec.rb b/spec/lib/gitlab/performance_bar_spec.rb index 8a586bdbf63..b8a2267f1a4 100644 --- a/spec/lib/gitlab/performance_bar_spec.rb +++ b/spec/lib/gitlab/performance_bar_spec.rb @@ -7,7 +7,7 @@ describe Gitlab::PerformanceBar do described_class.enabled?(user) end - it 'caches the allowed user IDs in cache', :caching do + it 'caches the allowed user IDs in cache', :use_clean_rails_memory_store_caching do expect do expect(described_class.enabled?(user)).to be_truthy end.not_to exceed_query_limit(0) diff --git a/spec/lib/gitlab/redis/cache_spec.rb b/spec/lib/gitlab/redis/cache_spec.rb new file mode 100644 index 00000000000..5a4f17cfcf6 --- /dev/null +++ b/spec/lib/gitlab/redis/cache_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe Gitlab::Redis::Cache do + let(:config_file_name) { "config/redis.cache.yml" } + let(:environment_config_file_name) { "GITLAB_REDIS_CACHE_CONFIG_FILE" } + let(:config_old_format_socket) { "spec/fixtures/config/redis_cache_old_format_socket.yml" } + let(:config_new_format_socket) { "spec/fixtures/config/redis_cache_new_format_socket.yml" } + let(:old_socket_path) {"/path/to/old/redis.cache.sock" } + let(:new_socket_path) {"/path/to/redis.cache.sock" } + let(:config_old_format_host) { "spec/fixtures/config/redis_cache_old_format_host.yml" } + let(:config_new_format_host) { "spec/fixtures/config/redis_cache_new_format_host.yml" } + let(:redis_port) { 6380 } + let(:redis_database) { 10 } + let(:sentinel_port) { redis_port + 20000 } + let(:config_with_environment_variable_inside) { "spec/fixtures/config/redis_cache_config_with_env.yml"} + let(:config_env_variable_url) {"TEST_GITLAB_REDIS_CACHE_URL"} + let(:class_redis_url) { Gitlab::Redis::Cache::DEFAULT_REDIS_CACHE_URL } + + include_examples "redis_shared_examples" +end diff --git a/spec/lib/gitlab/redis/queues_spec.rb b/spec/lib/gitlab/redis/queues_spec.rb new file mode 100644 index 00000000000..01ca25635a9 --- /dev/null +++ b/spec/lib/gitlab/redis/queues_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe Gitlab::Redis::Queues do + let(:config_file_name) { "config/redis.queues.yml" } + let(:environment_config_file_name) { "GITLAB_REDIS_QUEUES_CONFIG_FILE" } + let(:config_old_format_socket) { "spec/fixtures/config/redis_queues_old_format_socket.yml" } + let(:config_new_format_socket) { "spec/fixtures/config/redis_queues_new_format_socket.yml" } + let(:old_socket_path) {"/path/to/old/redis.queues.sock" } + let(:new_socket_path) {"/path/to/redis.queues.sock" } + let(:config_old_format_host) { "spec/fixtures/config/redis_queues_old_format_host.yml" } + let(:config_new_format_host) { "spec/fixtures/config/redis_queues_new_format_host.yml" } + let(:redis_port) { 6381 } + let(:redis_database) { 11 } + let(:sentinel_port) { redis_port + 20000 } + let(:config_with_environment_variable_inside) { "spec/fixtures/config/redis_queues_config_with_env.yml"} + let(:config_env_variable_url) {"TEST_GITLAB_REDIS_QUEUES_URL"} + let(:class_redis_url) { Gitlab::Redis::Queues::DEFAULT_REDIS_QUEUES_URL } + + include_examples "redis_shared_examples" +end diff --git a/spec/lib/gitlab/redis/shared_state_spec.rb b/spec/lib/gitlab/redis/shared_state_spec.rb new file mode 100644 index 00000000000..24b73745dc5 --- /dev/null +++ b/spec/lib/gitlab/redis/shared_state_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe Gitlab::Redis::SharedState do + let(:config_file_name) { "config/redis.shared_state.yml" } + let(:environment_config_file_name) { "GITLAB_REDIS_SHARED_STATE_CONFIG_FILE" } + let(:config_old_format_socket) { "spec/fixtures/config/redis_shared_state_old_format_socket.yml" } + let(:config_new_format_socket) { "spec/fixtures/config/redis_shared_state_new_format_socket.yml" } + let(:old_socket_path) {"/path/to/old/redis.shared_state.sock" } + let(:new_socket_path) {"/path/to/redis.shared_state.sock" } + let(:config_old_format_host) { "spec/fixtures/config/redis_shared_state_old_format_host.yml" } + let(:config_new_format_host) { "spec/fixtures/config/redis_shared_state_new_format_host.yml" } + let(:redis_port) { 6382 } + let(:redis_database) { 12 } + let(:sentinel_port) { redis_port + 20000 } + let(:config_with_environment_variable_inside) { "spec/fixtures/config/redis_shared_state_config_with_env.yml"} + let(:config_env_variable_url) {"TEST_GITLAB_REDIS_SHARED_STATE_URL"} + let(:class_redis_url) { Gitlab::Redis::SharedState::DEFAULT_REDIS_SHARED_STATE_URL } + + include_examples "redis_shared_examples" +end diff --git a/spec/lib/gitlab/redis/wrapper_spec.rb b/spec/lib/gitlab/redis/wrapper_spec.rb new file mode 100644 index 00000000000..e1becd0a614 --- /dev/null +++ b/spec/lib/gitlab/redis/wrapper_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe Gitlab::Redis::Wrapper do + let(:config_file_name) { "config/resque.yml" } + let(:environment_config_file_name) { "GITLAB_REDIS_CONFIG_FILE" } + let(:config_old_format_socket) { "spec/fixtures/config/redis_old_format_socket.yml" } + let(:config_new_format_socket) { "spec/fixtures/config/redis_new_format_socket.yml" } + let(:old_socket_path) {"/path/to/old/redis.sock" } + let(:new_socket_path) {"/path/to/redis.sock" } + let(:config_old_format_host) { "spec/fixtures/config/redis_old_format_host.yml" } + let(:config_new_format_host) { "spec/fixtures/config/redis_new_format_host.yml" } + let(:redis_port) { 6379 } + let(:redis_database) { 99 } + let(:sentinel_port) { redis_port + 20000 } + let(:config_with_environment_variable_inside) { "spec/fixtures/config/redis_config_with_env.yml"} + let(:config_env_variable_url) {"TEST_GITLAB_REDIS_URL"} + let(:class_redis_url) { Gitlab::Redis::Wrapper::DEFAULT_REDIS_URL } + + include_examples "redis_shared_examples" +end diff --git a/spec/lib/gitlab/route_map_spec.rb b/spec/lib/gitlab/route_map_spec.rb index 21c00c6e5b8..e8feb21e4d7 100644 --- a/spec/lib/gitlab/route_map_spec.rb +++ b/spec/lib/gitlab/route_map_spec.rb @@ -55,6 +55,19 @@ describe Gitlab::RouteMap, lib: true do end describe '#public_path_for_source_path' do + context 'malicious regexp' do + include_examples 'malicious regexp' + + subject do + map = described_class.new(<<-"MAP".strip_heredoc) + - source: '#{malicious_regexp}' + public: '/' + MAP + + map.public_path_for_source_path(malicious_text) + end + end + subject do described_class.new(<<-'MAP'.strip_heredoc) # Team data diff --git a/spec/lib/gitlab/sidekiq_status_spec.rb b/spec/lib/gitlab/sidekiq_status_spec.rb index 496e50fbae4..c2e77ef6b6c 100644 --- a/spec/lib/gitlab/sidekiq_status_spec.rb +++ b/spec/lib/gitlab/sidekiq_status_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::SidekiqStatus do - describe '.set', :redis do + describe '.set', :clean_gitlab_redis_shared_state do it 'stores the job ID' do described_class.set('123') @@ -14,7 +14,7 @@ describe Gitlab::SidekiqStatus do end end - describe '.unset', :redis do + describe '.unset', :clean_gitlab_redis_shared_state do it 'removes the job ID' do described_class.set('123') described_class.unset('123') @@ -27,7 +27,7 @@ describe Gitlab::SidekiqStatus do end end - describe '.all_completed?', :redis do + describe '.all_completed?', :clean_gitlab_redis_shared_state do it 'returns true if all jobs have been completed' do expect(described_class.all_completed?(%w(123))).to eq(true) end @@ -39,7 +39,7 @@ describe Gitlab::SidekiqStatus do end end - describe '.num_running', :redis do + describe '.num_running', :clean_gitlab_redis_shared_state do it 'returns 0 if all jobs have been completed' do expect(described_class.num_running(%w(123))).to eq(0) end @@ -52,7 +52,7 @@ describe Gitlab::SidekiqStatus do end end - describe '.num_completed', :redis do + describe '.num_completed', :clean_gitlab_redis_shared_state do it 'returns 1 if all jobs have been completed' do expect(described_class.num_completed(%w(123))).to eq(1) end @@ -74,7 +74,7 @@ describe Gitlab::SidekiqStatus do end end - describe 'completed', :redis do + describe 'completed', :clean_gitlab_redis_shared_state do it 'returns the completed job' do expect(described_class.completed_jids(%w(123))).to eq(['123']) end diff --git a/spec/lib/gitlab/untrusted_regexp_spec.rb b/spec/lib/gitlab/untrusted_regexp_spec.rb new file mode 100644 index 00000000000..66045917cb3 --- /dev/null +++ b/spec/lib/gitlab/untrusted_regexp_spec.rb @@ -0,0 +1,80 @@ +require 'spec_helper' + +describe Gitlab::UntrustedRegexp do + describe '#initialize' do + subject { described_class.new(pattern) } + + context 'invalid regexp' do + let(:pattern) { '[' } + + it { expect { subject }.to raise_error(RegexpError) } + end + end + + describe '#replace_all' do + it 'replaces all instances of the match in a string' do + result = described_class.new('foo').replace_all('foo bar foo', 'oof') + + expect(result).to eq('oof bar oof') + end + end + + describe '#replace' do + it 'replaces the first instance of the match in a string' do + result = described_class.new('foo').replace('foo bar foo', 'oof') + + expect(result).to eq('oof bar foo') + end + end + + describe '#===' do + it 'returns true for a match' do + result = described_class.new('foo') === 'a foo here' + + expect(result).to be_truthy + end + + it 'returns false for no match' do + result = described_class.new('foo') === 'a bar here' + + expect(result).to be_falsy + end + end + + describe '#scan' do + subject { described_class.new(regexp).scan(text) } + context 'malicious regexp' do + let(:text) { malicious_text } + let(:regexp) { malicious_regexp } + + include_examples 'malicious regexp' + end + + context 'no capture group' do + let(:regexp) { '.+' } + let(:text) { 'foo' } + + it 'returns the whole match' do + is_expected.to eq(['foo']) + end + end + + context 'one capture group' do + let(:regexp) { '(f).+' } + let(:text) { 'foo' } + + it 'returns the captured part' do + is_expected.to eq([%w[f]]) + end + end + + context 'two capture groups' do + let(:regexp) { '(f).(o)' } + let(:text) { 'foo' } + + it 'returns the captured parts' do + is_expected.to eq([%w[f o]]) + end + end + end +end diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index c6718827028..daf097f8d51 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -48,6 +48,7 @@ describe Gitlab::UsageData do milestones notes projects + projects_imported_from_github projects_prometheus_active pages_domains protected_branches diff --git a/spec/lib/gitlab/user_activities_spec.rb b/spec/lib/gitlab/user_activities_spec.rb index 187d88c8c58..a4ea0ac59e9 100644 --- a/spec/lib/gitlab/user_activities_spec.rb +++ b/spec/lib/gitlab/user_activities_spec.rb @@ -1,27 +1,27 @@ require 'spec_helper' -describe Gitlab::UserActivities, :redis, lib: true do +describe Gitlab::UserActivities, :clean_gitlab_redis_shared_state, lib: true do let(:now) { Time.now } describe '.record' do context 'with no time given' do - it 'uses Time.now and records an activity in Redis' do + it 'uses Time.now and records an activity in SharedState' do Timecop.freeze do now # eager-load now described_class.record(42) end - Gitlab::Redis.with do |redis| + Gitlab::Redis::SharedState.with do |redis| expect(redis.hscan(described_class::KEY, 0)).to eq(['0', [['42', now.to_i.to_s]]]) end end end context 'with a time given' do - it 'uses the given time and records an activity in Redis' do + it 'uses the given time and records an activity in SharedState' do described_class.record(42, now) - Gitlab::Redis.with do |redis| + Gitlab::Redis::SharedState.with do |redis| expect(redis.hscan(described_class::KEY, 0)).to eq(['0', [['42', now.to_i.to_s]]]) end end @@ -31,30 +31,30 @@ describe Gitlab::UserActivities, :redis, lib: true do describe '.delete' do context 'with a single key' do context 'and key exists' do - it 'removes the pair from Redis' do + it 'removes the pair from SharedState' do described_class.record(42, now) - Gitlab::Redis.with do |redis| + Gitlab::Redis::SharedState.with do |redis| expect(redis.hscan(described_class::KEY, 0)).to eq(['0', [['42', now.to_i.to_s]]]) end subject.delete(42) - Gitlab::Redis.with do |redis| + Gitlab::Redis::SharedState.with do |redis| expect(redis.hscan(described_class::KEY, 0)).to eq(['0', []]) end end end context 'and key does not exist' do - it 'removes the pair from Redis' do - Gitlab::Redis.with do |redis| + it 'removes the pair from SharedState' do + Gitlab::Redis::SharedState.with do |redis| expect(redis.hscan(described_class::KEY, 0)).to eq(['0', []]) end subject.delete(42) - Gitlab::Redis.with do |redis| + Gitlab::Redis::SharedState.with do |redis| expect(redis.hscan(described_class::KEY, 0)).to eq(['0', []]) end end @@ -63,33 +63,33 @@ describe Gitlab::UserActivities, :redis, lib: true do context 'with multiple keys' do context 'and all keys exist' do - it 'removes the pair from Redis' do + it 'removes the pair from SharedState' do described_class.record(41, now) described_class.record(42, now) - Gitlab::Redis.with do |redis| + Gitlab::Redis::SharedState.with do |redis| expect(redis.hscan(described_class::KEY, 0)).to eq(['0', [['41', now.to_i.to_s], ['42', now.to_i.to_s]]]) end subject.delete(41, 42) - Gitlab::Redis.with do |redis| + Gitlab::Redis::SharedState.with do |redis| expect(redis.hscan(described_class::KEY, 0)).to eq(['0', []]) end end end context 'and some keys does not exist' do - it 'removes the existing pair from Redis' do + it 'removes the existing pair from SharedState' do described_class.record(42, now) - Gitlab::Redis.with do |redis| + Gitlab::Redis::SharedState.with do |redis| expect(redis.hscan(described_class::KEY, 0)).to eq(['0', [['42', now.to_i.to_s]]]) end subject.delete(41, 42) - Gitlab::Redis.with do |redis| + Gitlab::Redis::SharedState.with do |redis| expect(redis.hscan(described_class::KEY, 0)).to eq(['0', []]) end end diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index 493ff3bb5fb..124f66a6e0e 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -276,7 +276,7 @@ describe Gitlab::Workhorse, lib: true do end it 'set and notify' do - expect_any_instance_of(Redis).to receive(:publish) + expect_any_instance_of(::Redis).to receive(:publish) .with(described_class::NOTIFICATION_CHANNEL, "test-key=test-value") subject @@ -310,11 +310,49 @@ describe Gitlab::Workhorse, lib: true do end it 'does not notify' do - expect_any_instance_of(Redis).not_to receive(:publish) + expect_any_instance_of(::Redis).not_to receive(:publish) subject end end end end + + describe '.send_git_blob' do + include FakeBlobHelpers + + let(:blob) { fake_blob } + + subject { described_class.send_git_blob(repository, blob) } + + context 'when Gitaly project_raw_show feature is enabled' do + it 'sets the header correctly' do + key, command, params = decode_workhorse_header(subject) + + expect(key).to eq('Gitlab-Workhorse-Send-Data') + expect(command).to eq('git-blob') + expect(params).to eq({ + 'GitalyServer' => { + address: Gitlab::GitalyClient.address(project.repository_storage), + token: Gitlab::GitalyClient.token(project.repository_storage) + }, + 'GetBlobRequest' => { + repository: repository.gitaly_repository.to_h, + oid: blob.id, + limit: -1 + } + }.deep_stringify_keys) + end + end + + context 'when Gitaly project_raw_show feature is disabled', skip_gitaly_mock: true do + it 'sets the header correctly' do + key, command, params = decode_workhorse_header(subject) + + expect(key).to eq('Gitlab-Workhorse-Send-Data') + expect(command).to eq('git-blob') + expect(params).to eq('RepoPath' => repository.path_to_repo, 'BlobId' => blob.id) + end + end + end end diff --git a/spec/migrations/add_foreign_key_to_merge_requests_spec.rb b/spec/migrations/add_foreign_key_to_merge_requests_spec.rb new file mode 100644 index 00000000000..d9ad9a585f0 --- /dev/null +++ b/spec/migrations/add_foreign_key_to_merge_requests_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' +require Rails.root.join('db', 'migrate', '20170713104829_add_foreign_key_to_merge_requests.rb') + +describe AddForeignKeyToMergeRequests, :migration do + let(:projects) { table(:projects) } + let(:merge_requests) { table(:merge_requests) } + let(:pipelines) { table(:ci_pipelines) } + + before do + projects.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce') + pipelines.create!(project_id: projects.first.id, + ref: 'some-branch', + sha: 'abc12345') + + # merge request without a pipeline + create_merge_request(head_pipeline_id: nil) + + # merge request with non-existent pipeline + create_merge_request(head_pipeline_id: 1234) + + # merge reqeust with existing pipeline assigned + create_merge_request(head_pipeline_id: pipelines.first.id) + end + + it 'correctly adds a foreign key to head_pipeline_id' do + migrate! + + expect(merge_requests.first.head_pipeline_id).to be_nil + expect(merge_requests.second.head_pipeline_id).to be_nil + expect(merge_requests.third.head_pipeline_id).to eq pipelines.first.id + end + + def create_merge_request(**opts) + merge_requests.create!(source_project_id: projects.first.id, + target_project_id: projects.first.id, + source_branch: 'some-branch', + target_branch: 'master', **opts) + end +end diff --git a/spec/migrations/clean_appearance_symlinks_spec.rb b/spec/migrations/clean_appearance_symlinks_spec.rb new file mode 100644 index 00000000000..9225dc0d894 --- /dev/null +++ b/spec/migrations/clean_appearance_symlinks_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20170613111224_clean_appearance_symlinks.rb') + +describe CleanAppearanceSymlinks do + let(:migration) { described_class.new } + let(:test_dir) { File.join(Rails.root, "tmp", "tests", "clean_appearance_test") } + let(:uploads_dir) { File.join(test_dir, "public", "uploads") } + let(:new_uploads_dir) { File.join(uploads_dir, "system") } + let(:original_path) { File.join(new_uploads_dir, 'appearance') } + let(:symlink_path) { File.join(uploads_dir, 'appearance') } + + before do + FileUtils.remove_dir(test_dir) if File.directory?(test_dir) + FileUtils.mkdir_p(uploads_dir) + allow(migration).to receive(:base_directory).and_return(test_dir) + allow(migration).to receive(:say) + end + + describe "#up" do + before do + FileUtils.mkdir_p(original_path) + FileUtils.ln_s(original_path, symlink_path) + end + + it 'removes the symlink' do + migration.up + + expect(File.symlink?(symlink_path)).to be(false) + end + end + + describe '#down' do + before do + FileUtils.mkdir_p(File.join(original_path)) + FileUtils.touch(File.join(original_path, 'dummy.file')) + end + + it 'creates a symlink' do + expected_path = File.join(symlink_path, "dummy.file") + migration.down + + expect(File.exist?(expected_path)).to be(true) + expect(File.symlink?(symlink_path)).to be(true) + end + end +end diff --git a/spec/migrations/cleanup_move_system_upload_folder_symlink_spec.rb b/spec/migrations/cleanup_move_system_upload_folder_symlink_spec.rb new file mode 100644 index 00000000000..3a9fa8c7113 --- /dev/null +++ b/spec/migrations/cleanup_move_system_upload_folder_symlink_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' +require Rails.root.join("db", "post_migrate", "20170717111152_cleanup_move_system_upload_folder_symlink.rb") + +describe CleanupMoveSystemUploadFolderSymlink do + let(:migration) { described_class.new } + let(:test_base) { File.join(Rails.root, 'tmp', 'tests', 'move-system-upload-folder') } + let(:test_folder) { File.join(test_base, '-', 'system') } + + before do + allow(migration).to receive(:base_directory).and_return(test_base) + FileUtils.rm_rf(test_base) + FileUtils.mkdir_p(test_folder) + allow(migration).to receive(:say) + end + + describe '#up' do + before do + FileUtils.ln_s(test_folder, File.join(test_base, 'system')) + end + + it 'removes the symlink' do + migration.up + + expect(File.exist?(File.join(test_base, 'system'))).to be_falsey + end + end + + describe '#down' do + it 'creates the symlink' do + migration.down + + expect(File.symlink?(File.join(test_base, 'system'))).to be_truthy + end + end +end diff --git a/spec/migrations/migrate_process_commit_worker_jobs_spec.rb b/spec/migrations/migrate_process_commit_worker_jobs_spec.rb index 4223d2337a8..5b633dd349b 100644 --- a/spec/migrations/migrate_process_commit_worker_jobs_spec.rb +++ b/spec/migrations/migrate_process_commit_worker_jobs_spec.rb @@ -54,7 +54,7 @@ describe MigrateProcessCommitWorkerJobs do end end - describe '#up', :redis do + describe '#up', :clean_gitlab_redis_shared_state do let(:migration) { described_class.new } def job_count @@ -172,7 +172,7 @@ describe MigrateProcessCommitWorkerJobs do end end - describe '#down', :redis do + describe '#down', :clean_gitlab_redis_shared_state do let(:migration) { described_class.new } def job_count diff --git a/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb b/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb index e3b42b5eac8..063829be546 100644 --- a/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb +++ b/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb @@ -3,13 +3,13 @@ require 'spec_helper' require Rails.root.join('db', 'post_migrate', '20170324160416_migrate_user_activities_to_users_last_activity_on.rb') -describe MigrateUserActivitiesToUsersLastActivityOn, :redis, :truncate do +describe MigrateUserActivitiesToUsersLastActivityOn, :clean_gitlab_redis_shared_state, :truncate do let(:migration) { described_class.new } let!(:user_active_1) { create(:user) } let!(:user_active_2) { create(:user) } def record_activity(user, time) - Gitlab::Redis.with do |redis| + Gitlab::Redis::SharedState.with do |redis| redis.zadd(described_class::USER_ACTIVITY_SET_KEY, time.to_i, user.username) end end diff --git a/spec/migrations/move_personal_snippets_files_spec.rb b/spec/migrations/move_personal_snippets_files_spec.rb new file mode 100644 index 00000000000..8505c7bf3e3 --- /dev/null +++ b/spec/migrations/move_personal_snippets_files_spec.rb @@ -0,0 +1,180 @@ +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20170612071012_move_personal_snippets_files.rb') + +describe MovePersonalSnippetsFiles do + let(:migration) { described_class.new } + let(:test_dir) { File.join(Rails.root, "tmp", "tests", "move_snippet_files_test") } + let(:uploads_dir) { File.join(test_dir, 'uploads') } + let(:new_uploads_dir) { File.join(uploads_dir, 'system') } + + before do + allow(CarrierWave).to receive(:root).and_return(test_dir) + allow(migration).to receive(:base_directory).and_return(test_dir) + FileUtils.remove_dir(test_dir) if File.directory?(test_dir) + allow(migration).to receive(:say) + end + + describe "#up" do + let(:snippet) do + snippet = create(:personal_snippet) + create_upload('picture.jpg', snippet) + snippet.update(description: markdown_linking_file('picture.jpg', snippet)) + snippet + end + + let(:snippet_with_missing_file) do + snippet = create(:snippet) + create_upload('picture.jpg', snippet, create_file: false) + snippet.update(description: markdown_linking_file('picture.jpg', snippet)) + snippet + end + + it 'moves the files' do + source_path = File.join(uploads_dir, model_file_path('picture.jpg', snippet)) + destination_path = File.join(new_uploads_dir, model_file_path('picture.jpg', snippet)) + + migration.up + + expect(File.exist?(source_path)).to be_falsy + expect(File.exist?(destination_path)).to be_truthy + end + + describe 'updating the markdown' do + it 'includes the new path when the file exists' do + secret = "secret#{snippet.id}" + file_location = "/uploads/system/personal_snippet/#{snippet.id}/#{secret}/picture.jpg" + + migration.up + + expect(snippet.reload.description).to include(file_location) + end + + it 'does not update the markdown when the file is missing' do + secret = "secret#{snippet_with_missing_file.id}" + file_location = "/uploads/personal_snippet/#{snippet_with_missing_file.id}/#{secret}/picture.jpg" + + migration.up + + expect(snippet_with_missing_file.reload.description).to include(file_location) + end + + it 'updates the note markdown' do + secret = "secret#{snippet.id}" + file_location = "/uploads/system/personal_snippet/#{snippet.id}/#{secret}/picture.jpg" + markdown = markdown_linking_file('picture.jpg', snippet) + note = create(:note_on_personal_snippet, noteable: snippet, note: "with #{markdown}") + + migration.up + + expect(note.reload.note).to include(file_location) + end + end + end + + describe "#down" do + let(:snippet) do + snippet = create(:personal_snippet) + create_upload('picture.jpg', snippet, in_new_path: true) + snippet.update(description: markdown_linking_file('picture.jpg', snippet, in_new_path: true)) + snippet + end + + let(:snippet_with_missing_file) do + snippet = create(:personal_snippet) + create_upload('picture.jpg', snippet, create_file: false, in_new_path: true) + snippet.update(description: markdown_linking_file('picture.jpg', snippet, in_new_path: true)) + snippet + end + + it 'moves the files' do + source_path = File.join(new_uploads_dir, model_file_path('picture.jpg', snippet)) + destination_path = File.join(uploads_dir, model_file_path('picture.jpg', snippet)) + + migration.down + + expect(File.exist?(source_path)).to be_falsey + expect(File.exist?(destination_path)).to be_truthy + end + + describe 'updating the markdown' do + it 'includes the new path when the file exists' do + secret = "secret#{snippet.id}" + file_location = "/uploads/personal_snippet/#{snippet.id}/#{secret}/picture.jpg" + + migration.down + + expect(snippet.reload.description).to include(file_location) + end + + it 'keeps the markdown as is when the file is missing' do + secret = "secret#{snippet_with_missing_file.id}" + file_location = "/uploads/system/personal_snippet/#{snippet_with_missing_file.id}/#{secret}/picture.jpg" + + migration.down + + expect(snippet_with_missing_file.reload.description).to include(file_location) + end + + it 'updates the note markdown' do + markdown = markdown_linking_file('picture.jpg', snippet, in_new_path: true) + secret = "secret#{snippet.id}" + file_location = "/uploads/personal_snippet/#{snippet.id}/#{secret}/picture.jpg" + note = create(:note_on_personal_snippet, noteable: snippet, note: "with #{markdown}") + + migration.down + + expect(note.reload.note).to include(file_location) + end + end + end + + describe '#update_markdown' do + it 'escapes sql in the snippet description' do + migration.instance_variable_set('@source_relative_location', '/uploads/personal_snippet') + migration.instance_variable_set('@destination_relative_location', '/uploads/system/personal_snippet') + + secret = '123456789' + filename = 'hello.jpg' + snippet = create(:personal_snippet) + + path_before = "/uploads/personal_snippet/#{snippet.id}/#{secret}/#{filename}" + path_after = "/uploads/system/personal_snippet/#{snippet.id}/#{secret}/#{filename}" + description_before = "Hello world; ![image](#{path_before})'; select * from users;" + description_after = "Hello world; ![image](#{path_after})'; select * from users;" + + migration.update_markdown(snippet.id, secret, filename, description_before) + + expect(snippet.reload.description).to eq(description_after) + end + end + + def create_upload(filename, snippet, create_file: true, in_new_path: false) + secret = "secret#{snippet.id}" + absolute_path = if in_new_path + File.join(new_uploads_dir, model_file_path(filename, snippet)) + else + File.join(uploads_dir, model_file_path(filename, snippet)) + end + + if create_file + FileUtils.mkdir_p(File.dirname(absolute_path)) + FileUtils.touch(absolute_path) + end + + create(:upload, model: snippet, path: "#{secret}/#{filename}", uploader: PersonalFileUploader) + end + + def markdown_linking_file(filename, snippet, in_new_path: false) + markdown = "![#{filename.split('.')[0]}]" + markdown += '(/uploads' + markdown += '/system' if in_new_path + markdown += "/#{model_file_path(filename, snippet)})" + markdown + end + + def model_file_path(filename, snippet) + secret = "secret#{snippet.id}" + + File.join('personal_snippet', snippet.id.to_s, secret, filename) + end +end diff --git a/spec/migrations/move_system_upload_folder_spec.rb b/spec/migrations/move_system_upload_folder_spec.rb new file mode 100644 index 00000000000..b622b4e9536 --- /dev/null +++ b/spec/migrations/move_system_upload_folder_spec.rb @@ -0,0 +1,62 @@ +require 'spec_helper' +require Rails.root.join("db", "migrate", "20170717074009_move_system_upload_folder.rb") + +describe MoveSystemUploadFolder do + let(:migration) { described_class.new } + let(:test_base) { File.join(Rails.root, 'tmp', 'tests', 'move-system-upload-folder') } + + before do + allow(migration).to receive(:base_directory).and_return(test_base) + FileUtils.rm_rf(test_base) + FileUtils.mkdir_p(test_base) + allow(migration).to receive(:say) + end + + describe '#up' do + let(:test_folder) { File.join(test_base, 'system') } + let(:test_file) { File.join(test_folder, 'file') } + + before do + FileUtils.mkdir_p(test_folder) + FileUtils.touch(test_file) + end + + it 'moves the related folder' do + migration.up + + expect(File.exist?(File.join(test_base, '-', 'system', 'file'))).to be_truthy + end + + it 'creates a symlink linking making the new folder available on the old path' do + migration.up + + expect(File.symlink?(File.join(test_base, 'system'))).to be_truthy + expect(File.exist?(File.join(test_base, 'system', 'file'))).to be_truthy + end + end + + describe '#down' do + let(:test_folder) { File.join(test_base, '-', 'system') } + let(:test_file) { File.join(test_folder, 'file') } + + before do + FileUtils.mkdir_p(test_folder) + FileUtils.touch(test_file) + end + + it 'moves the system folder back to the old location' do + migration.down + + expect(File.exist?(File.join(test_base, 'system', 'file'))).to be_truthy + end + + it 'removes the symlink if it existed' do + FileUtils.ln_s(test_folder, File.join(test_base, 'system')) + + migration.down + + expect(File.directory?(File.join(test_base, 'system'))).to be_truthy + expect(File.symlink?(File.join(test_base, 'system'))).to be_falsey + end + end +end diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index fb485d0b2c6..e600eab6565 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -155,6 +155,18 @@ describe ApplicationSetting, models: true do end end + describe '.current' do + context 'redis unavailable' do + it 'returns an ApplicationSetting' do + allow(Rails.cache).to receive(:fetch).and_call_original + allow(ApplicationSetting).to receive(:last).and_return(:last) + expect(Rails.cache).to receive(:fetch).with(ApplicationSetting::CACHE_KEY).and_raise(ArgumentError) + + expect(ApplicationSetting.current).to eq(:last) + end + end + end + context 'restricted signup domains' do it 'sets single domain' do setting.domain_whitelist_raw = 'example.com' diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index 76ce558eea0..4b9cce28e0e 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -276,14 +276,14 @@ describe Ci::Runner, models: true do it 'sets a new last_update value when it is called the first time' do last_update = runner.ensure_runner_queue_value - expect_value_in_redis.to eq(last_update) + expect_value_in_queues.to eq(last_update) end it 'does not change if it is not expired and called again' do last_update = runner.ensure_runner_queue_value expect(runner.ensure_runner_queue_value).to eq(last_update) - expect_value_in_redis.to eq(last_update) + expect_value_in_queues.to eq(last_update) end context 'updates runner queue after changing editable value' do @@ -294,7 +294,7 @@ describe Ci::Runner, models: true do end it 'sets a new last_update value' do - expect_value_in_redis.not_to eq(last_update) + expect_value_in_queues.not_to eq(last_update) end end @@ -306,12 +306,12 @@ describe Ci::Runner, models: true do end it 'has an old last_update value' do - expect_value_in_redis.to eq(last_update) + expect_value_in_queues.to eq(last_update) end end - def expect_value_in_redis - Gitlab::Redis.with do |redis| + def expect_value_in_queues + Gitlab::Redis::Queues.with do |redis| runner_queue_key = runner.send(:runner_queue_key) expect(redis.get(runner_queue_key)) end @@ -330,7 +330,7 @@ describe Ci::Runner, models: true do end it 'cleans up the queue' do - Gitlab::Redis.with do |redis| + Gitlab::Redis::Queues.with do |redis| expect(redis.get(queue_key)).to be_nil end end diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index 6056d78da4e..528b211c9d6 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -19,17 +19,15 @@ describe Commit, models: true do expect(commit.author).to eq(user) end - it 'caches the author' do - allow(RequestStore).to receive(:active?).and_return(true) + it 'caches the author', :request_store do user = create(:user, email: commit.author_email) - expect_any_instance_of(Commit).to receive(:find_author_by_any_email).and_call_original + expect(User).to receive(:find_by_any_email).and_call_original expect(commit.author).to eq(user) - key = "commit_author:#{commit.author_email}" + key = "Commit:author:#{commit.author_email.downcase}" expect(RequestStore.store[key]).to eq(user) expect(commit.author).to eq(user) - RequestStore.store.clear end end diff --git a/spec/models/concerns/reactive_caching_spec.rb b/spec/models/concerns/reactive_caching_spec.rb index 808247ebfd5..5f9b7e0a367 100644 --- a/spec/models/concerns/reactive_caching_spec.rb +++ b/spec/models/concerns/reactive_caching_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe ReactiveCaching, caching: true do +describe ReactiveCaching, :use_clean_rails_memory_store_caching do include ReactiveCachingHelpers class CacheTest diff --git a/spec/models/concerns/sortable_spec.rb b/spec/models/concerns/sortable_spec.rb deleted file mode 100644 index d1e17c4f684..00000000000 --- a/spec/models/concerns/sortable_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -require 'spec_helper' - -describe Sortable do - let(:relation) { Issue.all } - - describe '#where' do - it 'orders by id, descending' do - order_node = relation.where(iid: 1).order_values.first - expect(order_node).to be_a(Arel::Nodes::Descending) - expect(order_node.expr.name).to eq(:id) - end - end - - describe '#find_by' do - it 'does not order' do - expect(relation).to receive(:unscope).with(:order).and_call_original - - relation.find_by(iid: 1) - end - end -end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 066d7b9307f..770176451fe 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -189,7 +189,7 @@ describe Group, models: true do let!(:group) { create(:group, :access_requestable, :with_avatar) } let(:user) { create(:user) } let(:gitlab_host) { "http://#{Gitlab.config.gitlab.host}" } - let(:avatar_path) { "/uploads/system/group/avatar/#{group.id}/dk.png" } + let(:avatar_path) { "/uploads/-/system/group/avatar/#{group.id}/dk.png" } context 'when avatar file is uploaded' do before do diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 62c4ea01ce1..a4090b37f65 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -44,7 +44,7 @@ describe Namespace, models: true do end context "is case insensitive" do - let(:group) { build(:group, path: "System") } + let(:group) { build(:group, path: "Groups") } it { expect(group).not_to be_valid } end @@ -63,6 +63,14 @@ describe Namespace, models: true do it { is_expected.to respond_to(:has_parent?) } end + describe 'inclusions' do + it { is_expected.to include_module(Gitlab::VisibilityLevel) } + end + + describe '#visibility_level_field' do + it { expect(namespace.visibility_level_field).to eq(:visibility_level) } + end + describe '#to_param' do it { expect(namespace.to_param).to eq(namespace.full_path) } end diff --git a/spec/models/project_services/bamboo_service_spec.rb b/spec/models/project_services/bamboo_service_spec.rb index 7b1a554d1fb..99190d763f2 100644 --- a/spec/models/project_services/bamboo_service_spec.rb +++ b/spec/models/project_services/bamboo_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe BambooService, models: true, caching: true do +describe BambooService, :use_clean_rails_memory_store_caching, models: true do include ReactiveCachingHelpers let(:bamboo_url) { 'http://gitlab.com/bamboo' } diff --git a/spec/models/project_services/buildkite_service_spec.rb b/spec/models/project_services/buildkite_service_spec.rb index dd529597067..b4ee6691e67 100644 --- a/spec/models/project_services/buildkite_service_spec.rb +++ b/spec/models/project_services/buildkite_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe BuildkiteService, models: true, caching: true do +describe BuildkiteService, :use_clean_rails_memory_store_caching, models: true do include ReactiveCachingHelpers let(:project) { create(:empty_project) } diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb index 1400175427f..c9ac256ff38 100644 --- a/spec/models/project_services/drone_ci_service_spec.rb +++ b/spec/models/project_services/drone_ci_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe DroneCiService, models: true, caching: true do +describe DroneCiService, :use_clean_rails_memory_store_caching, models: true do include ReactiveCachingHelpers describe 'associations' do diff --git a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb index dcb70ee28a8..d45e0a441d4 100644 --- a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb +++ b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb @@ -23,38 +23,29 @@ describe GitlabIssueTrackerService, models: true do describe 'project and issue urls' do let(:project) { create(:empty_project) } + let(:service) { project.create_gitlab_issue_tracker_service(active: true) } context 'with absolute urls' do before do - GitlabIssueTrackerService.default_url_options[:script_name] = "/gitlab/root" - @service = project.create_gitlab_issue_tracker_service(active: true) - end - - after do - @service.destroy! + allow(GitlabIssueTrackerService).to receive(:default_url_options).and_return(script_name: "/gitlab/root") end it 'gives the correct path' do - expect(@service.project_url).to eq("http://#{Gitlab.config.gitlab.host}/gitlab/root/#{project.path_with_namespace}/issues") - expect(@service.new_issue_url).to eq("http://#{Gitlab.config.gitlab.host}/gitlab/root/#{project.path_with_namespace}/issues/new") - expect(@service.issue_url(432)).to eq("http://#{Gitlab.config.gitlab.host}/gitlab/root/#{project.path_with_namespace}/issues/432") + expect(service.project_url).to eq("http://#{Gitlab.config.gitlab.host}/gitlab/root/#{project.path_with_namespace}/issues") + expect(service.new_issue_url).to eq("http://#{Gitlab.config.gitlab.host}/gitlab/root/#{project.path_with_namespace}/issues/new") + expect(service.issue_url(432)).to eq("http://#{Gitlab.config.gitlab.host}/gitlab/root/#{project.path_with_namespace}/issues/432") end end context 'with relative urls' do before do - GitlabIssueTrackerService.default_url_options[:script_name] = "/gitlab/root" - @service = project.create_gitlab_issue_tracker_service(active: true) - end - - after do - @service.destroy! + allow(GitlabIssueTrackerService).to receive(:default_url_options).and_return(script_name: "/gitlab/root") end it 'gives the correct path' do - expect(@service.project_path).to eq("/gitlab/root/#{project.path_with_namespace}/issues") - expect(@service.new_issue_path).to eq("/gitlab/root/#{project.path_with_namespace}/issues/new") - expect(@service.issue_path(432)).to eq("/gitlab/root/#{project.path_with_namespace}/issues/432") + expect(service.issue_tracker_path).to eq("/gitlab/root/#{project.path_with_namespace}/issues") + expect(service.new_issue_path).to eq("/gitlab/root/#{project.path_with_namespace}/issues/new") + expect(service.issue_path(432)).to eq("/gitlab/root/#{project.path_with_namespace}/issues/432") end end end diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb index 5ba523a478a..b66bb5321ab 100644 --- a/spec/models/project_services/kubernetes_service_spec.rb +++ b/spec/models/project_services/kubernetes_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe KubernetesService, models: true, caching: true do +describe KubernetesService, :use_clean_rails_memory_store_caching, models: true do include KubernetesHelpers include ReactiveCachingHelpers diff --git a/spec/models/project_services/prometheus_service_spec.rb b/spec/models/project_services/prometheus_service_spec.rb index 37f23b1243c..3fb134ec3b7 100644 --- a/spec/models/project_services/prometheus_service_spec.rb +++ b/spec/models/project_services/prometheus_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe PrometheusService, models: true, caching: true do +describe PrometheusService, :use_clean_rails_memory_store_caching, models: true do include PrometheusHelpers include ReactiveCachingHelpers diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb index 6b004098510..3f3a74d0f96 100644 --- a/spec/models/project_services/teamcity_service_spec.rb +++ b/spec/models/project_services/teamcity_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe TeamcityService, models: true, caching: true do +describe TeamcityService, :use_clean_rails_memory_store_caching, models: true do include ReactiveCachingHelpers let(:teamcity_url) { 'http://gitlab.com/teamcity' } diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 99bfab70088..90769b580cd 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -807,7 +807,7 @@ describe Project, models: true do context 'when avatar file is uploaded' do let(:project) { create(:empty_project, :with_avatar) } - let(:avatar_path) { "/uploads/system/project/avatar/#{project.id}/dk.png" } + let(:avatar_path) { "/uploads/-/system/project/avatar/#{project.id}/dk.png" } let(:gitlab_host) { "http://#{Gitlab.config.gitlab.host}" } it 'shows correct url' do @@ -899,7 +899,7 @@ describe Project, models: true do end end - describe '.cached_count', caching: true do + describe '.cached_count', :use_clean_rails_memory_store_caching do let(:group) { create(:group, :public) } let!(:project1) { create(:empty_project, :public, group: group) } let!(:project2) { create(:empty_project, :public, group: group) } @@ -1236,7 +1236,7 @@ describe Project, models: true do subject { project.rename_repo } - it { expect{subject}.to raise_error(Exception) } + it { expect{subject}.to raise_error(StandardError) } end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index af305e9b234..7635b0868e7 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -561,7 +561,7 @@ describe Repository, models: true do end end - describe "#changelog", caching: true do + describe "#changelog", :use_clean_rails_memory_store_caching do it 'accepts changelog' do expect(repository.tree).to receive(:blobs).and_return([TestBlob.new('changelog')]) @@ -593,7 +593,7 @@ describe Repository, models: true do end end - describe "#license_blob", caching: true do + describe "#license_blob", :use_clean_rails_memory_store_caching do before do repository.delete_file( user, 'LICENSE', message: 'Remove LICENSE', branch_name: 'master') @@ -638,7 +638,7 @@ describe Repository, models: true do end end - describe '#license_key', caching: true do + describe '#license_key', :use_clean_rails_memory_store_caching do before do repository.delete_file(user, 'LICENSE', message: 'Remove LICENSE', branch_name: 'master') @@ -703,7 +703,7 @@ describe Repository, models: true do end end - describe "#gitlab_ci_yml", caching: true do + describe "#gitlab_ci_yml", :use_clean_rails_memory_store_caching do it 'returns valid file' do files = [TestBlob.new('file'), TestBlob.new('.gitlab-ci.yml'), TestBlob.new('copying')] expect(repository.tree).to receive(:blobs).and_return(files) @@ -1611,7 +1611,7 @@ describe Repository, models: true do end end - describe '#contribution_guide', caching: true do + describe '#contribution_guide', :use_clean_rails_memory_store_caching do it 'returns and caches the output' do expect(repository).to receive(:file_on_head) .with(:contributing) @@ -1625,7 +1625,7 @@ describe Repository, models: true do end end - describe '#gitignore', caching: true do + describe '#gitignore', :use_clean_rails_memory_store_caching do it 'returns and caches the output' do expect(repository).to receive(:file_on_head) .with(:gitignore) @@ -1638,7 +1638,7 @@ describe Repository, models: true do end end - describe '#koding_yml', caching: true do + describe '#koding_yml', :use_clean_rails_memory_store_caching do it 'returns and caches the output' do expect(repository).to receive(:file_on_head) .with(:koding) @@ -1651,7 +1651,7 @@ describe Repository, models: true do end end - describe '#readme', caching: true do + describe '#readme', :use_clean_rails_memory_store_caching do context 'with a non-existing repository' do it 'returns nil' do allow(repository).to receive(:tree).with(:head).and_return(nil) @@ -1822,7 +1822,7 @@ describe Repository, models: true do end end - describe '#cache_method_output', caching: true do + describe '#cache_method_output', :use_clean_rails_memory_store_caching do context 'with a non-existing repository' do let(:value) do repository.cache_method_output(:cats, fallback: 10) do diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 448555d2190..a1d6d7e6e0b 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -348,7 +348,7 @@ describe User, models: true do end end - describe '#update_tracked_fields!', :redis do + describe '#update_tracked_fields!', :clean_gitlab_redis_shared_state do let(:request) { OpenStruct.new(remote_ip: "127.0.0.1") } let(:user) { create(:user) } @@ -763,7 +763,7 @@ describe User, models: true do end it 'returns users with a partially matching name' do - expect(described_class.search(user.name[0..2])).to eq([user2, user]) + expect(described_class.search(user.name[0..2])).to eq([user, user2]) end it 'returns users with a matching name regardless of the casing' do @@ -777,7 +777,7 @@ describe User, models: true do end it 'returns users with a partially matching Email' do - expect(described_class.search(user.email[0..2])).to eq([user2, user]) + expect(described_class.search(user.email[0..2])).to eq([user, user2]) end it 'returns users with a matching Email regardless of the casing' do @@ -791,7 +791,7 @@ describe User, models: true do end it 'returns users with a partially matching username' do - expect(described_class.search(user.username[0..2])).to eq([user2, user]) + expect(described_class.search(user.username[0..2])).to eq([user, user2]) end it 'returns users with a matching username regardless of the casing' do @@ -1028,7 +1028,7 @@ describe User, models: true do context 'when avatar file is uploaded' do let(:gitlab_host) { "http://#{Gitlab.config.gitlab.host}" } - let(:avatar_path) { "/uploads/system/user/avatar/#{user.id}/dk.png" } + let(:avatar_path) { "/uploads/-/system/user/avatar/#{user.id}/dk.png" } it 'shows correct avatar url' do expect(user.avatar_url).to eq(avatar_path) @@ -1159,6 +1159,18 @@ describe User, models: true do end end + describe '#sanitize_attrs' do + let(:user) { build(:user, name: 'test & user', skype: 'test&user') } + + it 'encodes HTML entities in the Skype attribute' do + expect { user.sanitize_attrs }.to change { user.skype }.to('test&user') + end + + it 'does not encode HTML entities in the name attribute' do + expect { user.sanitize_attrs }.not_to change { user.name } + end + end + describe '#starred?' do it 'determines if user starred a project' do user = create :user @@ -1684,7 +1696,7 @@ describe User, models: true do end end - describe '#refresh_authorized_projects', redis: true do + describe '#refresh_authorized_projects', clean_gitlab_redis_shared_state: true do let(:project1) { create(:empty_project) } let(:project2) { create(:empty_project) } let(:user) { create(:user) } @@ -1920,4 +1932,26 @@ describe User, models: true do user.invalidate_merge_request_cache_counts end end + + describe '#allow_password_authentication?' do + context 'regular user' do + let(:user) { build(:user) } + + it 'returns true when sign-in is enabled' do + expect(user.allow_password_authentication?).to be_truthy + end + + it 'returns false when sign-in is disabled' do + stub_application_setting(password_authentication_enabled: false) + + expect(user.allow_password_authentication?).to be_falsey + end + end + + it 'returns false for ldap user' do + user = create(:omniauth_user, provider: 'ldapmain') + + expect(user.allow_password_authentication?).to be_falsey + end + end end diff --git a/spec/policies/ci/build_policy_spec.rb b/spec/policies/ci/build_policy_spec.rb index ace95ac7067..9f3212b1a63 100644 --- a/spec/policies/ci/build_policy_spec.rb +++ b/spec/policies/ci/build_policy_spec.rb @@ -103,12 +103,7 @@ describe Ci::BuildPolicy, :models do project.add_developer(user) end - context 'when branch build is assigned to is protected' do - before do - create(:protected_branch, :no_one_can_push, - name: 'some-ref', project: project) - end - + shared_examples 'protected ref' do context 'when build is a manual action' do let(:build) do create(:ci_build, :manual, ref: 'some-ref', pipeline: pipeline) @@ -130,6 +125,43 @@ describe Ci::BuildPolicy, :models do end end + context 'when build is against a protected branch' do + before do + create(:protected_branch, :no_one_can_push, + name: 'some-ref', project: project) + end + + it_behaves_like 'protected ref' + end + + context 'when build is against a protected tag' do + before do + create(:protected_tag, :no_one_can_create, + name: 'some-ref', project: project) + + build.update(tag: true) + end + + it_behaves_like 'protected ref' + end + + context 'when build is against a protected tag but it is not a tag' do + before do + create(:protected_tag, :no_one_can_create, + name: 'some-ref', project: project) + end + + context 'when build is a manual action' do + let(:build) do + create(:ci_build, :manual, ref: 'some-ref', pipeline: pipeline) + end + + it 'includes ability to update build' do + expect(policy).to be_allowed :update_build + end + end + end + context 'when branch build is assigned to is not protected' do context 'when build is a manual action' do let(:build) { create(:ci_build, :manual, pipeline: pipeline) } diff --git a/spec/presenters/merge_request_presenter_spec.rb b/spec/presenters/merge_request_presenter_spec.rb index f5a14b1d04d..c1a0313b13c 100644 --- a/spec/presenters/merge_request_presenter_spec.rb +++ b/spec/presenters/merge_request_presenter_spec.rb @@ -332,7 +332,31 @@ describe MergeRequestPresenter do end end - context 'when target branch does not exists' do + context 'when target branch does not exist' do + it 'returns nil' do + allow(resource).to receive(:target_branch_exists?) { false } + + is_expected.to be_nil + end + end + end + + describe '#target_branch_tree_path' do + subject do + described_class.new(resource, current_user: user) + .target_branch_tree_path + end + + context 'when target branch exists' do + it 'returns path' do + allow(resource).to receive(:target_branch_exists?) { true } + + is_expected + .to eq("/#{resource.target_project.full_path}/tree/#{resource.target_branch}") + end + end + + context 'when target branch does not exist' do it 'returns nil' do allow(resource).to receive(:target_branch_exists?) { false } @@ -355,7 +379,7 @@ describe MergeRequestPresenter do end end - context 'when source branch does not exists' do + context 'when source branch does not exist' do it 'returns nil' do allow(resource).to receive(:source_branch_exists?) { false } @@ -363,4 +387,17 @@ describe MergeRequestPresenter do end end end + + describe '#source_branch_with_namespace_link' do + subject do + described_class.new(resource, current_user: user).source_branch_with_namespace_link + end + + it 'returns link' do + allow(resource).to receive(:source_branch_exists?) { true } + + is_expected + .to eq("<a href=\"/#{resource.source_project.full_path}/tree/#{resource.source_branch}\">#{resource.source_branch}</a>") + end + end end diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index cde4fa888a0..cab3089c6b1 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -35,6 +35,17 @@ describe API::Internal do expect(json_response).to be_empty end end + + context 'nil broadcast message' do + it 'returns nothing' do + allow(BroadcastMessage).to receive(:current).and_return(nil) + + get api('/internal/broadcast_message'), secret_token: secret_token + + expect(response).to have_http_status(200) + expect(json_response).to be_empty + end + end end describe 'GET /internal/broadcast_messages' do @@ -168,7 +179,7 @@ describe API::Internal do end end - describe "POST /internal/allowed", :redis do + describe "POST /internal/allowed", :clean_gitlab_redis_shared_state do context "access granted" do before do project.team << [user, :developer] @@ -583,10 +594,10 @@ describe API::Internal do # end # # it "calls the Gitaly client with the project's repository" do - # expect(Gitlab::GitalyClient::Notifications). + # expect(Gitlab::GitalyClient::NotificationService). # to receive(:new).with(gitlab_git_repository_with(path: project.repository.path)). # and_call_original - # expect_any_instance_of(Gitlab::GitalyClient::Notifications). + # expect_any_instance_of(Gitlab::GitalyClient::NotificationService). # to receive(:post_receive) # # post api("/internal/notify_post_receive"), valid_params @@ -595,10 +606,10 @@ describe API::Internal do # end # # it "calls the Gitaly client with the wiki's repository if it's a wiki" do - # expect(Gitlab::GitalyClient::Notifications). + # expect(Gitlab::GitalyClient::NotificationService). # to receive(:new).with(gitlab_git_repository_with(path: project.wiki.repository.path)). # and_call_original - # expect_any_instance_of(Gitlab::GitalyClient::Notifications). + # expect_any_instance_of(Gitlab::GitalyClient::NotificationService). # to receive(:post_receive) # # post api("/internal/notify_post_receive"), valid_wiki_params @@ -607,7 +618,7 @@ describe API::Internal do # end # # it "returns 500 if the gitaly call fails" do - # expect_any_instance_of(Gitlab::GitalyClient::Notifications). + # expect_any_instance_of(Gitlab::GitalyClient::NotificationService). # to receive(:post_receive).and_raise(GRPC::Unavailable) # # post api("/internal/notify_post_receive"), valid_params @@ -625,10 +636,10 @@ describe API::Internal do # end # # it "calls the Gitaly client with the project's repository" do - # expect(Gitlab::GitalyClient::Notifications). + # expect(Gitlab::GitalyClient::NotificationService). # to receive(:new).with(gitlab_git_repository_with(path: project.repository.path)). # and_call_original - # expect_any_instance_of(Gitlab::GitalyClient::Notifications). + # expect_any_instance_of(Gitlab::GitalyClient::NotificationService). # to receive(:post_receive) # # post api("/internal/notify_post_receive"), valid_params @@ -637,10 +648,10 @@ describe API::Internal do # end # # it "calls the Gitaly client with the wiki's repository if it's a wiki" do - # expect(Gitlab::GitalyClient::Notifications). + # expect(Gitlab::GitalyClient::NotificationService). # to receive(:new).with(gitlab_git_repository_with(path: project.wiki.repository.path)). # and_call_original - # expect_any_instance_of(Gitlab::GitalyClient::Notifications). + # expect_any_instance_of(Gitlab::GitalyClient::NotificationService). # to receive(:post_receive) # # post api("/internal/notify_post_receive"), valid_wiki_params diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 4d0bd67c571..9098ae6bcda 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -16,7 +16,11 @@ describe API::MergeRequests do let!(:label) do create(:label, title: 'label', color: '#FFAABB', project: project) end + let!(:label2) { create(:label, title: 'a-test', color: '#FFFFFF', project: project) } let!(:label_link) { create(:label_link, label: label, target: merge_request) } + let!(:label_link2) { create(:label_link, label: label2, target: merge_request) } + let!(:downvote) { create(:award_emoji, :downvote, awardable: merge_request) } + let!(:upvote) { create(:award_emoji, :upvote, awardable: merge_request) } before do project.team << [user, :reporter] @@ -32,6 +36,18 @@ describe API::MergeRequests do end context "when authenticated" do + it 'avoids N+1 queries' do + control_count = ActiveRecord::QueryRecorder.new do + get api("/projects/#{project.id}/merge_requests", user) + end.count + + create(:merge_request, state: 'closed', milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: "Test", created_at: base_time) + + expect do + get api("/projects/#{project.id}/merge_requests", user) + end.not_to exceed_query_limit(control_count) + end + it "returns an array of all merge_requests" do get api("/projects/#{project.id}/merge_requests", user) @@ -44,12 +60,31 @@ describe API::MergeRequests do expect(json_response.last['sha']).to eq(merge_request.diff_head_sha) expect(json_response.last['merge_commit_sha']).to be_nil expect(json_response.last['merge_commit_sha']).to eq(merge_request.merge_commit_sha) + expect(json_response.last['downvotes']).to eq(1) + expect(json_response.last['upvotes']).to eq(1) + expect(json_response.last['labels']).to eq([label2.title, label.title]) expect(json_response.first['title']).to eq(merge_request_merged.title) expect(json_response.first['sha']).to eq(merge_request_merged.diff_head_sha) expect(json_response.first['merge_commit_sha']).not_to be_nil expect(json_response.first['merge_commit_sha']).to eq(merge_request_merged.merge_commit_sha) end + it "returns an array of all merge_requests using simple mode" do + get api("/projects/#{project.id}/merge_requests?view=simple", user) + + expect(response).to have_http_status(200) + expect(response).to include_pagination_headers + expect(json_response.last.keys).to match_array(%w(id iid title web_url created_at description project_id state updated_at)) + expect(json_response).to be_an Array + expect(json_response.length).to eq(3) + expect(json_response.last['iid']).to eq(merge_request.iid) + expect(json_response.last['title']).to eq(merge_request.title) + expect(json_response.last).to have_key('web_url') + expect(json_response.first['iid']).to eq(merge_request_merged.iid) + expect(json_response.first['title']).to eq(merge_request_merged.title) + expect(json_response.first).to have_key('web_url') + end + it "returns an array of all merge_requests" do get api("/projects/#{project.id}/merge_requests?state", user) @@ -145,7 +180,7 @@ describe API::MergeRequests do expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) - expect(json_response.first['labels']).to eq([label.title]) + expect(json_response.first['labels']).to eq([label2.title, label.title]) end it 'returns an array of labeled merge requests where all labels match' do @@ -236,8 +271,8 @@ describe API::MergeRequests do expect(json_response['author']).to be_a Hash expect(json_response['target_branch']).to eq(merge_request.target_branch) expect(json_response['source_branch']).to eq(merge_request.source_branch) - expect(json_response['upvotes']).to eq(0) - expect(json_response['downvotes']).to eq(0) + expect(json_response['upvotes']).to eq(1) + expect(json_response['downvotes']).to eq(1) expect(json_response['source_project_id']).to eq(merge_request.source_project.id) expect(json_response['target_project_id']).to eq(merge_request.target_project.id) expect(json_response['work_in_progress']).to be_falsy diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index fa704f23857..6dbde8bad31 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -442,7 +442,7 @@ describe API::Projects do post api('/projects', user), project project_id = json_response['id'] - expect(json_response['avatar_url']).to eq("http://localhost/uploads/system/project/avatar/#{project_id}/banana_sample.gif") + expect(json_response['avatar_url']).to eq("http://localhost/uploads/-/system/project/avatar/#{project_id}/banana_sample.gif") end it 'sets a project as allowing merge even if build fails' do diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index ede48b1c888..b71ac6c30b5 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -10,7 +10,7 @@ describe API::Settings, 'Settings' do expect(response).to have_http_status(200) expect(json_response).to be_an Hash expect(json_response['default_projects_limit']).to eq(42) - expect(json_response['signin_enabled']).to be_truthy + expect(json_response['password_authentication_enabled']).to be_truthy expect(json_response['repository_storage']).to eq('default') expect(json_response['koding_enabled']).to be_falsey expect(json_response['koding_url']).to be_nil @@ -32,7 +32,7 @@ describe API::Settings, 'Settings' do it "updates application settings" do put api("/application/settings", admin), default_projects_limit: 3, - signin_enabled: false, + password_authentication_enabled: false, repository_storage: 'custom', koding_enabled: true, koding_url: 'http://koding.example.com', @@ -46,7 +46,7 @@ describe API::Settings, 'Settings' do help_page_support_url: 'http://example.com/help' expect(response).to have_http_status(200) expect(json_response['default_projects_limit']).to eq(3) - expect(json_response['signin_enabled']).to be_falsey + expect(json_response['password_authentication_enabled']).to be_falsey expect(json_response['repository_storage']).to eq('custom') expect(json_response['repository_storages']).to eq(['custom']) expect(json_response['koding_enabled']).to be_truthy diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index c34b88f0741..877bde3b9a6 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -943,11 +943,11 @@ describe API::Users do expect(response).to have_http_status(403) end - it 'returns initial current user without private token when sudo not defined' do + it 'returns initial current user without private token but with is_admin when sudo not defined' do get api("/user?private_token=#{admin_personal_access_token}") expect(response).to have_http_status(200) - expect(response).to match_response_schema('public_api/v4/user/public') + expect(response).to match_response_schema('public_api/v4/user/admin') expect(json_response['id']).to eq(admin.id) end end @@ -961,11 +961,11 @@ describe API::Users do expect(json_response['id']).to eq(user.id) end - it 'returns initial current user without private token when sudo not defined' do + it 'returns initial current user without private token but with is_admin when sudo not defined' do get api("/user?private_token=#{admin.private_token}") expect(response).to have_http_status(200) - expect(response).to match_response_schema('public_api/v4/user/public') + expect(response).to match_response_schema('public_api/v4/user/admin') expect(json_response['id']).to eq(admin.id) end end @@ -1313,7 +1313,7 @@ describe API::Users do end end - context "user activities", :redis do + context "user activities", :clean_gitlab_redis_shared_state do let!(:old_active_user) { create(:user, last_activity_on: Time.utc(2000, 1, 1)) } let!(:newly_active_user) { create(:user, last_activity_on: 2.days.ago.midday) } diff --git a/spec/requests/api/v3/settings_spec.rb b/spec/requests/api/v3/settings_spec.rb index 41d039b7da0..291f6dcc2aa 100644 --- a/spec/requests/api/v3/settings_spec.rb +++ b/spec/requests/api/v3/settings_spec.rb @@ -10,7 +10,7 @@ describe API::V3::Settings, 'Settings' do expect(response).to have_http_status(200) expect(json_response).to be_an Hash expect(json_response['default_projects_limit']).to eq(42) - expect(json_response['signin_enabled']).to be_truthy + expect(json_response['password_authentication_enabled']).to be_truthy expect(json_response['repository_storage']).to eq('default') expect(json_response['koding_enabled']).to be_falsey expect(json_response['koding_url']).to be_nil @@ -28,11 +28,11 @@ describe API::V3::Settings, 'Settings' do it "updates application settings" do put v3_api("/application/settings", admin), - default_projects_limit: 3, signin_enabled: false, repository_storage: 'custom', koding_enabled: true, koding_url: 'http://koding.example.com', + default_projects_limit: 3, password_authentication_enabled: false, repository_storage: 'custom', koding_enabled: true, koding_url: 'http://koding.example.com', plantuml_enabled: true, plantuml_url: 'http://plantuml.example.com' expect(response).to have_http_status(200) expect(json_response['default_projects_limit']).to eq(3) - expect(json_response['signin_enabled']).to be_falsey + expect(json_response['password_authentication_enabled']).to be_falsey expect(json_response['repository_storage']).to eq('custom') expect(json_response['repository_storages']).to eq(['custom']) expect(json_response['koding_enabled']).to be_truthy diff --git a/spec/requests/api/version_spec.rb b/spec/requests/api/version_spec.rb index 8870d48bbc9..7bbf34422b8 100644 --- a/spec/requests/api/version_spec.rb +++ b/spec/requests/api/version_spec.rb @@ -6,7 +6,7 @@ describe API::Version do it 'returns authentication error' do get api('/version') - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end @@ -16,7 +16,7 @@ describe API::Version do it 'returns the version information' do get api('/version', user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['version']).to eq(Gitlab::VERSION) expect(json_response['revision']).to eq(Gitlab::REVISION) end diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 185679e1a0f..d043ab2a974 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -406,7 +406,7 @@ describe 'Git HTTP requests', lib: true do end end - it 'updates the user last activity', :redis do + it 'updates the user last activity', :clean_gitlab_redis_shared_state do expect(user_activity(user)).to be_nil download(path, env) do |response| @@ -463,7 +463,7 @@ describe 'Git HTTP requests', lib: true do context 'when internal auth is disabled' do before do - allow_any_instance_of(ApplicationSetting).to receive(:signin_enabled?) { false } + allow_any_instance_of(ApplicationSetting).to receive(:password_authentication_enabled?) { false } end it 'rejects pulls with personal access token error message' do diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb index 5e4cf05748e..8d79ea3dd40 100644 --- a/spec/requests/jwt_controller_spec.rb +++ b/spec/requests/jwt_controller_spec.rb @@ -101,7 +101,7 @@ describe JwtController do context 'when internal auth is disabled' do it 'rejects the authorization attempt with personal access token message' do - allow_any_instance_of(ApplicationSetting).to receive(:signin_enabled?) { false } + allow_any_instance_of(ApplicationSetting).to receive(:password_authentication_enabled?) { false } get '/jwt/auth', parameters, headers expect(response).to have_http_status(401) diff --git a/spec/requests/openid_connect_spec.rb b/spec/requests/openid_connect_spec.rb index 6d1f0b24196..a927de952d0 100644 --- a/spec/requests/openid_connect_spec.rb +++ b/spec/requests/openid_connect_spec.rb @@ -79,7 +79,7 @@ describe 'OpenID Connect requests' do 'email_verified' => true, 'website' => 'https://example.com', 'profile' => 'http://localhost/alice', - 'picture' => "http://localhost/uploads/system/user/avatar/#{user.id}/dk.png" + 'picture' => "http://localhost/uploads/-/system/user/avatar/#{user.id}/dk.png" }) end end @@ -98,7 +98,7 @@ describe 'OpenID Connect requests' do expect(@payload['sub']).to eq hashed_subject end - it 'includes the time of the last authentication', :redis do + it 'includes the time of the last authentication', :clean_gitlab_redis_shared_state do expect(@payload['auth_time']).to eq user.current_sign_in_at.to_i end diff --git a/spec/rubocop/cop/migration/hash_index_spec.rb b/spec/rubocop/cop/migration/hash_index_spec.rb new file mode 100644 index 00000000000..9a8576a19e5 --- /dev/null +++ b/spec/rubocop/cop/migration/hash_index_spec.rb @@ -0,0 +1,53 @@ +require 'spec_helper' + +require 'rubocop' +require 'rubocop/rspec/support' + +require_relative '../../../../rubocop/cop/migration/hash_index' + +describe RuboCop::Cop::Migration::HashIndex do + include CopHelper + + subject(:cop) { described_class.new } + + context 'in migration' do + before do + allow(cop).to receive(:in_migration?).and_return(true) + end + + it 'registers an offense when creating a hash index' do + inspect_source(cop, 'def change; add_index :table, :column, using: :hash; end') + + aggregate_failures do + expect(cop.offenses.size).to eq(1) + expect(cop.offenses.map(&:line)).to eq([1]) + end + end + + it 'registers an offense when creating a concurrent hash index' do + inspect_source(cop, 'def change; add_concurrent_index :table, :column, using: :hash; end') + + aggregate_failures do + expect(cop.offenses.size).to eq(1) + expect(cop.offenses.map(&:line)).to eq([1]) + end + end + + it 'registers an offense when creating a hash index using t.index' do + inspect_source(cop, 'def change; t.index :table, :column, using: :hash; end') + + aggregate_failures do + expect(cop.offenses.size).to eq(1) + expect(cop.offenses.map(&:line)).to eq([1]) + end + end + end + + context 'outside of migration' do + it 'registers no offense' do + inspect_source(cop, 'def change; index :table, :column, using: :hash; end') + + expect(cop.offenses.size).to eq(0) + end + end +end diff --git a/spec/serializers/merge_request_entity_spec.rb b/spec/serializers/merge_request_entity_spec.rb index d38433c2365..b3d58b2636f 100644 --- a/spec/serializers/merge_request_entity_spec.rb +++ b/spec/serializers/merge_request_entity_spec.rb @@ -47,7 +47,7 @@ describe MergeRequestEntity do :cancel_merge_when_pipeline_succeeds_path, :create_issue_to_resolve_discussions_path, :source_branch_path, :target_branch_commits_path, - :commits_count) + :target_branch_tree_path, :commits_count) end it 'has email_patches_path' do diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index 77c07b71c68..e71c462b99a 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -40,7 +40,7 @@ describe Ci::CreatePipelineService, :services do it 'increments the prometheus counter' do expect(Gitlab::Metrics).to receive(:counter) - .with(:pipelines_created_count, "Pipelines created count") + .with(:pipelines_created_total, "Counter of pipelines created") .and_call_original pipeline diff --git a/spec/services/event_create_service_spec.rb b/spec/services/event_create_service_spec.rb index b06cefe071d..8d067c194cc 100644 --- a/spec/services/event_create_service_spec.rb +++ b/spec/services/event_create_service_spec.rb @@ -113,7 +113,7 @@ describe EventCreateService, services: true do end end - describe '#push', :redis do + describe '#push', :clean_gitlab_redis_shared_state do let(:project) { create(:empty_project) } let(:user) { create(:user) } diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index 8e8816870e1..c493c08a7ae 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -108,7 +108,7 @@ describe GitPushService, services: true do it { is_expected.to include(id: @commit.id) } it { is_expected.to include(message: @commit.safe_message) } - it { is_expected.to include(timestamp: @commit.date.xmlschema) } + it { expect(subject[:timestamp].in_time_zone).to eq(@commit.date.in_time_zone) } it do is_expected.to include( url: [ @@ -163,7 +163,7 @@ describe GitPushService, services: true do execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' ) end end - + context "Sends System Push data" do it "when pushing on a branch" do expect(SystemHookPushWorker).to receive(:perform_async).with(@push_data, :push_hooks) @@ -527,14 +527,18 @@ describe GitPushService, services: true do let(:housekeeping) { Projects::HousekeepingService.new(project) } before do - # Flush any raw Redis data stored by the housekeeping code. - Gitlab::Redis.with { |conn| conn.flushall } + # Flush any raw key-value data stored by the housekeeping code. + Gitlab::Redis::Cache.with { |conn| conn.flushall } + Gitlab::Redis::Queues.with { |conn| conn.flushall } + Gitlab::Redis::SharedState.with { |conn| conn.flushall } allow(Projects::HousekeepingService).to receive(:new).and_return(housekeeping) end after do - Gitlab::Redis.with { |conn| conn.flushall } + Gitlab::Redis::Cache.with { |conn| conn.flushall } + Gitlab::Redis::Queues.with { |conn| conn.flushall } + Gitlab::Redis::SharedState.with { |conn| conn.flushall } end it 'does not perform housekeeping when not needed' do diff --git a/spec/services/milestones/destroy_service_spec.rb b/spec/services/milestones/destroy_service_spec.rb new file mode 100644 index 00000000000..8d1fe3ae2c1 --- /dev/null +++ b/spec/services/milestones/destroy_service_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +describe Milestones::DestroyService, services: true do + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:milestone) { create(:milestone, title: 'Milestone v1.0', project: project) } + let(:issue) { create(:issue, project: project, milestone: milestone) } + let(:merge_request) { create(:merge_request, source_project: project, milestone: milestone) } + + before do + project.team << [user, :master] + end + + def service + described_class.new(project, user, {}) + end + + describe '#execute' do + it 'deletes milestone' do + service.execute(milestone) + + expect { milestone.reload }.to raise_error ActiveRecord::RecordNotFound + end + + it 'deletes milestone id from issuables' do + service.execute(milestone) + + expect(issue.reload.milestone).to be_nil + expect(merge_request.reload.milestone).to be_nil + end + + context 'group milestones' do + let(:group) { create(:group) } + let(:group_milestone) { create(:milestone, group: group) } + + before do + project.update(namespace: group) + group.add_developer(user) + end + + it { expect(service.execute(group_milestone)).to be_nil } + + it 'does not update milestone issuables' do + expect(MergeRequests::UpdateService).not_to receive(:new) + expect(Issues::UpdateService).not_to receive(:new) + + service.execute(group_milestone) + end + end + end +end diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index f1e00c1163b..4fc5eb0a527 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -383,7 +383,7 @@ describe NotificationService, services: true do before do build_team(note.project) reset_delivered_emails! - allow_any_instance_of(Commit).to receive(:author).and_return(@u_committer) + allow(note.noteable).to receive(:author).and_return(@u_committer) update_custom_notification(:new_note, @u_guest_custom, resource: project) update_custom_notification(:new_note, @u_custom_global) end diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb index 697dc18feb0..b399d3402fd 100644 --- a/spec/services/projects/destroy_service_spec.rb +++ b/spec/services/projects/destroy_service_spec.rb @@ -60,14 +60,14 @@ describe Projects::DestroyService, services: true do before do new_user = create(:user) project.team.add_user(new_user, Gitlab::Access::DEVELOPER) - allow_any_instance_of(Projects::DestroyService).to receive(:flush_caches).and_raise(Redis::CannotConnectError) + allow_any_instance_of(Projects::DestroyService).to receive(:flush_caches).and_raise(::Redis::CannotConnectError) end it 'keeps project team intact upon an error' do Sidekiq::Testing.inline! do begin destroy_project(project, user, {}) - rescue Redis::CannotConnectError + rescue ::Redis::CannotConnectError end end diff --git a/spec/services/projects/participants_service_spec.rb b/spec/services/projects/participants_service_spec.rb index d75851134ee..3688f6d4e23 100644 --- a/spec/services/projects/participants_service_spec.rb +++ b/spec/services/projects/participants_service_spec.rb @@ -13,7 +13,7 @@ describe Projects::ParticipantsService, services: true do groups = participants.groups expect(groups.size).to eq 1 - expect(groups.first[:avatar_url]).to eq("/uploads/system/group/avatar/#{group.id}/dk.png") + expect(groups.first[:avatar_url]).to eq("/uploads/-/system/group/avatar/#{group.id}/dk.png") end it 'should return an url for the avatar with relative url' do @@ -24,7 +24,7 @@ describe Projects::ParticipantsService, services: true do groups = participants.groups expect(groups.size).to eq 1 - expect(groups.first[:avatar_url]).to eq("/gitlab/uploads/system/group/avatar/#{group.id}/dk.png") + expect(groups.first[:avatar_url]).to eq("/gitlab/uploads/-/system/group/avatar/#{group.id}/dk.png") end end end diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index 05b18fef061..fd4011ad606 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -1,11 +1,14 @@ require 'spec_helper' -describe Projects::UpdateService, services: true do +describe Projects::UpdateService, '#execute', :services do let(:user) { create(:user) } let(:admin) { create(:admin) } - let(:project) { create(:empty_project, creator_id: user.id, namespace: user.namespace) } - describe 'update_by_user' do + let(:project) do + create(:empty_project, creator: user, namespace: user.namespace) + end + + context 'when changing visibility level' do context 'when visibility_level is INTERNAL' do it 'updates the project to internal' do result = update_project(project, user, visibility_level: Gitlab::VisibilityLevel::INTERNAL) @@ -40,7 +43,7 @@ describe Projects::UpdateService, services: true do it 'does not update the project to public' do result = update_project(project, user, visibility_level: Gitlab::VisibilityLevel::PUBLIC) - expect(result).to eq({ status: :error, message: 'Visibility level unallowed' }) + expect(result).to eq({ status: :error, message: 'New visibility level not allowed!' }) expect(project).to be_private end @@ -55,12 +58,13 @@ describe Projects::UpdateService, services: true do end end - describe 'visibility_level' do + describe 'when updating project that has forks' do let(:project) { create(:empty_project, :internal) } let(:forked_project) { create(:forked_project_with_submodules, :internal) } before do - forked_project.build_forked_project_link(forked_to_project_id: forked_project.id, forked_from_project_id: project.id) + forked_project.build_forked_project_link(forked_to_project_id: forked_project.id, + forked_from_project_id: project.id) forked_project.save end @@ -89,10 +93,38 @@ describe Projects::UpdateService, services: true do end end - it 'returns an error result when record cannot be updated' do - result = update_project(project, admin, { name: 'foo&bar' }) + context 'when updating a default branch' do + let(:project) { create(:project, :repository) } + + it 'changes a default branch' do + update_project(project, admin, default_branch: 'feature') + + expect(Project.find(project.id).default_branch).to eq 'feature' + end + end - expect(result).to eq({ status: :error, message: 'Project could not be updated' }) + context 'when renaming project that contains container images' do + before do + stub_container_registry_config(enabled: true) + stub_container_registry_tags(repository: /image/, tags: %w[rc1]) + create(:container_repository, project: project, name: :image) + end + + it 'does not allow to rename the project' do + result = update_project(project, admin, path: 'renamed') + + expect(result).to include(status: :error) + expect(result[:message]).to match(/contains container registry tags/) + end + end + + context 'when passing invalid parameters' do + it 'returns an error result when record cannot be updated' do + result = update_project(project, admin, { name: 'foo&bar' }) + + expect(result).to eq({ status: :error, + message: 'Project could not be updated!' }) + end end def update_project(project, user, opts) diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb index 175a42a32d9..de41cbab14c 100644 --- a/spec/services/todo_service_spec.rb +++ b/spec/services/todo_service_spec.rb @@ -908,7 +908,7 @@ describe TodoService, services: true do end end - it 'caches the number of todos of a user', :caching do + it 'caches the number of todos of a user', :use_clean_rails_memory_store_caching do create(:todo, :mentioned, user: john_doe, target: issue, project: project) todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project) TodoService.new.mark_todos_as_done([todo], john_doe) diff --git a/spec/services/users/activity_service_spec.rb b/spec/services/users/activity_service_spec.rb index 2e009d4ce1c..e5330d1d3e4 100644 --- a/spec/services/users/activity_service_spec.rb +++ b/spec/services/users/activity_service_spec.rb @@ -7,7 +7,7 @@ describe Users::ActivityService, services: true do subject(:service) { described_class.new(user, 'type') } - describe '#execute', :redis do + describe '#execute', :clean_gitlab_redis_shared_state do context 'when last activity is nil' do before do service.execute diff --git a/spec/services/users/migrate_to_ghost_user_service_spec.rb b/spec/services/users/migrate_to_ghost_user_service_spec.rb index 9e1edf1ac30..e52ecd6d614 100644 --- a/spec/services/users/migrate_to_ghost_user_service_spec.rb +++ b/spec/services/users/migrate_to_ghost_user_service_spec.rb @@ -7,16 +7,32 @@ describe Users::MigrateToGhostUserService, services: true do context "migrating a user's associated records to the ghost user" do context 'issues' do - include_examples "migrating a deleted user's associated records to the ghost user", Issue do - let(:created_record) { create(:issue, project: project, author: user) } - let(:assigned_record) { create(:issue, project: project, assignee: user) } + context 'deleted user is present as both author and edited_user' do + include_examples "migrating a deleted user's associated records to the ghost user", Issue, [:author, :last_edited_by] do + let(:created_record) do + create(:issue, project: project, author: user, last_edited_by: user) + end + end + end + + context 'deleted user is present only as edited_user' do + include_examples "migrating a deleted user's associated records to the ghost user", Issue, [:last_edited_by] do + let(:created_record) { create(:issue, project: project, author: create(:user), last_edited_by: user) } + end end end context 'merge requests' do - include_examples "migrating a deleted user's associated records to the ghost user", MergeRequest do - let(:created_record) { create(:merge_request, source_project: project, author: user, target_branch: "first") } - let(:assigned_record) { create(:merge_request, source_project: project, assignee: user, target_branch: 'second') } + context 'deleted user is present as both author and merge_user' do + include_examples "migrating a deleted user's associated records to the ghost user", MergeRequest, [:author, :merge_user] do + let(:created_record) { create(:merge_request, source_project: project, author: user, merge_user: user, target_branch: "first") } + end + end + + context 'deleted user is present only as both merge_user' do + include_examples "migrating a deleted user's associated records to the ghost user", MergeRequest, [:merge_user] do + let(:created_record) { create(:merge_request, source_project: project, merge_user: user, target_branch: "first") } + end end end @@ -33,9 +49,8 @@ describe Users::MigrateToGhostUserService, services: true do end context 'award emoji' do - include_examples "migrating a deleted user's associated records to the ghost user", AwardEmoji do + include_examples "migrating a deleted user's associated records to the ghost user", AwardEmoji, [:user] do let(:created_record) { create(:award_emoji, user: user) } - let(:author_alias) { :user } context "when the awardable already has an award emoji of the same name assigned to the ghost user" do let(:awardable) { create(:issue) } diff --git a/spec/services/users/refresh_authorized_projects_service_spec.rb b/spec/services/users/refresh_authorized_projects_service_spec.rb index b65cadbb2f5..1c0f55d2965 100644 --- a/spec/services/users/refresh_authorized_projects_service_spec.rb +++ b/spec/services/users/refresh_authorized_projects_service_spec.rb @@ -8,7 +8,7 @@ describe Users::RefreshAuthorizedProjectsService do let(:user) { project.namespace.owner } let(:service) { described_class.new(user) } - describe '#execute', :redis do + describe '#execute', :clean_gitlab_redis_shared_state do it 'refreshes the authorizations using a lease' do expect_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain) .and_return('foo') diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index a497b8613bb..5d5715b10ff 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -3,7 +3,6 @@ SimpleCovEnv.start! ENV["RAILS_ENV"] ||= 'test' ENV["IN_MEMORY_APPLICATION_SETTINGS"] = 'true' -# ENV['prometheus_multiproc_dir'] = 'tmp/prometheus_multiproc_dir_test' require File.expand_path("../../config/environment", __FILE__) require 'rspec/rails' @@ -59,6 +58,7 @@ RSpec.configure do |config| config.include ApiHelpers, :api config.include Gitlab::Routing, type: :routing config.include MigrationsHelpers, :migration + config.include StubFeatureFlags config.infer_spec_type_from_file_location! @@ -76,6 +76,13 @@ RSpec.configure do |config| TestEnv.cleanup end + config.before(:example) do + # Skip pre-receive hook check so we can use the web editor and merge. + allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, nil]) + # Enable all features by default for testing + allow(Feature).to receive(:enabled?) { true } + end + config.before(:example, :request_store) do RequestStore.begin! end @@ -91,20 +98,30 @@ RSpec.configure do |config| end end - config.around(:each, :caching) do |example| + config.around(:each, :use_clean_rails_memory_store_caching) do |example| caching_store = Rails.cache - Rails.cache = ActiveSupport::Cache::MemoryStore.new if example.metadata[:caching] + Rails.cache = ActiveSupport::Cache::MemoryStore.new + example.run + Rails.cache = caching_store end - config.around(:each, :redis) do |example| - Gitlab::Redis.with(&:flushall) + config.around(:each, :clean_gitlab_redis_cache) do |example| + Gitlab::Redis::Cache.with(&:flushall) + + example.run + + Gitlab::Redis::Cache.with(&:flushall) + end + + config.around(:each, :clean_gitlab_redis_shared_state) do |example| + Gitlab::Redis::SharedState.with(&:flushall) Sidekiq.redis(&:flushall) example.run - Gitlab::Redis.with(&:flushall) + Gitlab::Redis::SharedState.with(&:flushall) Sidekiq.redis(&:flushall) end diff --git a/spec/support/dropzone_helper.rb b/spec/support/dropzone_helper.rb index 02fdeb08afe..fe72d320fcf 100644 --- a/spec/support/dropzone_helper.rb +++ b/spec/support/dropzone_helper.rb @@ -54,4 +54,23 @@ module DropzoneHelper loop until page.evaluate_script('window._dropzoneComplete === true') end end + + def drop_in_dropzone(file_path) + # Generate a fake input selector + page.execute_script <<-JS + var fakeFileInput = window.$('<input/>').attr( + {id: 'fakeFileInput', type: 'file'} + ).appendTo('body'); + JS + + # Attach the file to the fake input selector with Capybara + attach_file('fakeFileInput', file_path) + + # Add the file to a fileList array and trigger the fake drop event + page.execute_script <<-JS + var fileList = [$('#fakeFileInput')[0].files[0]]; + var e = jQuery.Event('drop', { dataTransfer : { files : fileList } }); + $('.dropzone')[0].dropzone.listeners[0].events.drop(e); + JS + end end diff --git a/spec/support/malicious_regexp_shared_examples.rb b/spec/support/malicious_regexp_shared_examples.rb new file mode 100644 index 00000000000..ac5d22298bb --- /dev/null +++ b/spec/support/malicious_regexp_shared_examples.rb @@ -0,0 +1,8 @@ +shared_examples 'malicious regexp' do + let(:malicious_text) { 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!' } + let(:malicious_regexp) { '(?i)^(([a-z])+.)+[A-Z]([a-z])+$' } + + it 'takes under a second' do + expect { Timeout.timeout(1) { subject } }.not_to raise_error + end +end diff --git a/spec/support/matchers/have_gitlab_http_status.rb b/spec/support/matchers/have_gitlab_http_status.rb new file mode 100644 index 00000000000..3198f1b9edd --- /dev/null +++ b/spec/support/matchers/have_gitlab_http_status.rb @@ -0,0 +1,14 @@ +RSpec::Matchers.define :have_gitlab_http_status do |expected| + match do |actual| + expect(actual).to have_http_status(expected) + end + + description do + "respond with numeric status code #{expected}" + end + + failure_message do |actual| + "expected the response to have status code #{expected.inspect}" \ + " but it was #{actual.response_code}. The response was: #{actual.body}" + end +end diff --git a/spec/lib/gitlab/redis_spec.rb b/spec/support/redis/redis_shared_examples.rb index 593aa5038ad..f9552e41894 100644 --- a/spec/lib/gitlab/redis_spec.rb +++ b/spec/support/redis/redis_shared_examples.rb @@ -1,12 +1,10 @@ -require 'spec_helper' - -describe Gitlab::Redis do +RSpec.shared_examples "redis_shared_examples" do include StubENV - let(:config) { 'config/resque.yml' } + let(:test_redis_url) { "redis://redishost:#{redis_port}"} before(:each) do - stub_env('GITLAB_REDIS_CONFIG_FILE', Rails.root.join(config).to_s) + stub_env(environment_config_file_name, Rails.root.join(config_file_name)) clear_raw_config end @@ -26,46 +24,40 @@ describe Gitlab::Redis do end context 'when url contains unix socket reference' do - let(:config_old) { 'spec/fixtures/config/redis_old_format_socket.yml' } - let(:config_new) { 'spec/fixtures/config/redis_new_format_socket.yml' } - context 'with old format' do - let(:config) { config_old } + let(:config_file_name) { config_old_format_socket } it 'returns path key instead' do - is_expected.to include(path: '/path/to/old/redis.sock') + is_expected.to include(path: old_socket_path) is_expected.not_to have_key(:url) end end context 'with new format' do - let(:config) { config_new } + let(:config_file_name) { config_new_format_socket } it 'returns path key instead' do - is_expected.to include(path: '/path/to/redis.sock') + is_expected.to include(path: new_socket_path) is_expected.not_to have_key(:url) end end end context 'when url is host based' do - let(:config_old) { 'spec/fixtures/config/redis_old_format_host.yml' } - let(:config_new) { 'spec/fixtures/config/redis_new_format_host.yml' } - context 'with old format' do - let(:config) { config_old } + let(:config_file_name) { config_old_format_host } it 'returns hash with host, port, db, and password' do - is_expected.to include(host: 'localhost', password: 'mypassword', port: 6379, db: 99) + is_expected.to include(host: 'localhost', password: 'mypassword', port: redis_port, db: redis_database) is_expected.not_to have_key(:url) end end context 'with new format' do - let(:config) { config_new } + let(:config_file_name) { config_new_format_host } it 'returns hash with host, port, db, and password' do - is_expected.to include(host: 'localhost', password: 'mynewpassword', port: 6379, db: 99) + is_expected.to include(host: 'localhost', password: 'mynewpassword', port: redis_port, db: redis_database) is_expected.not_to have_key(:url) end end @@ -73,30 +65,22 @@ describe Gitlab::Redis do end describe '.url' do - it 'withstands mutation' do - url1 = described_class.url - url2 = described_class.url - url1 << 'foobar' - - expect(url2).not_to end_with('foobar') - end - context 'when yml file with env variable' do - let(:config) { 'spec/fixtures/config/redis_config_with_env.yml' } + let(:config_file_name) { config_with_environment_variable_inside } before do - stub_env('TEST_GITLAB_REDIS_URL', 'redis://redishost:6379') + stub_env(config_env_variable_url, test_redis_url) end it 'reads redis url from env variable' do - expect(described_class.url).to eq 'redis://redishost:6379' + expect(described_class.url).to eq test_redis_url end end end describe '._raw_config' do subject { described_class._raw_config } - let(:config) { '/var/empty/doesnotexist' } + let(:config_file_name) { '/var/empty/doesnotexist' } it 'should be frozen' do expect(subject).to be_frozen @@ -105,6 +89,12 @@ describe Gitlab::Redis do it 'returns false when the file does not exist' do expect(subject).to eq(false) end + + it "returns false when the filename can't be determined" do + expect(described_class).to receive(:config_file_name).and_return(nil) + + expect(subject).to eq(false) + end end describe '.with' do @@ -124,7 +114,7 @@ describe Gitlab::Redis do it 'instantiates a connection pool with size 5' do expect(ConnectionPool).to receive(:new).with(size: 5).and_call_original - described_class.with { |_redis| true } + described_class.with { |_redis_shared_example| true } end end @@ -137,7 +127,7 @@ describe Gitlab::Redis do it 'instantiates a connection pool with a size based on the concurrency of the worker' do expect(ConnectionPool).to receive(:new).with(size: 18 + 5).and_call_original - described_class.with { |_redis| true } + described_class.with { |_redis_shared_example| true } end end end @@ -146,16 +136,16 @@ describe Gitlab::Redis do subject { described_class.new(Rails.env).sentinels } context 'when sentinels are defined' do - let(:config) { 'spec/fixtures/config/redis_new_format_host.yml' } + let(:config_file_name) { config_new_format_host } it 'returns an array of hashes with host and port keys' do - is_expected.to include(host: 'localhost', port: 26380) - is_expected.to include(host: 'slave2', port: 26381) + is_expected.to include(host: 'localhost', port: sentinel_port) + is_expected.to include(host: 'slave2', port: sentinel_port) end end context 'when sentinels are not defined' do - let(:config) { 'spec/fixtures/config/redis_old_format_host.yml' } + let(:config_file_name) { config_old_format_host } it 'returns nil' do is_expected.to be_nil @@ -167,7 +157,7 @@ describe Gitlab::Redis do subject { described_class.new(Rails.env).sentinels? } context 'when sentinels are defined' do - let(:config) { 'spec/fixtures/config/redis_new_format_host.yml' } + let(:config_file_name) { config_new_format_host } it 'returns true' do is_expected.to be_truthy @@ -175,7 +165,7 @@ describe Gitlab::Redis do end context 'when sentinels are not defined' do - let(:config) { 'spec/fixtures/config/redis_old_format_host.yml' } + let(:config_file_name) { config_old_format_host } it 'returns false' do is_expected.to be_falsey @@ -187,12 +177,12 @@ describe Gitlab::Redis do it 'returns default redis url when no config file is present' do expect(subject).to receive(:fetch_config) { false } - expect(subject.send(:raw_config_hash)).to eq(url: Gitlab::Redis::DEFAULT_REDIS_URL) + expect(subject.send(:raw_config_hash)).to eq(url: class_redis_url ) end it 'returns old-style single url config in a hash' do - expect(subject).to receive(:fetch_config) { 'redis://myredis:6379' } - expect(subject.send(:raw_config_hash)).to eq(url: 'redis://myredis:6379') + expect(subject).to receive(:fetch_config) { test_redis_url } + expect(subject.send(:raw_config_hash)).to eq(url: test_redis_url) end end @@ -200,7 +190,13 @@ describe Gitlab::Redis do it 'returns false when no config file is present' do allow(described_class).to receive(:_raw_config) { false } - expect(subject.send(:fetch_config)).to be_falsey + expect(subject.send(:fetch_config)).to eq false + end + + it 'returns false when config file is present but has invalid YAML' do + allow(described_class).to receive(:_raw_config) { "# development: true" } + + expect(subject.send(:fetch_config)).to eq false end end diff --git a/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb b/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb index dcc562c684b..855051921f0 100644 --- a/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb +++ b/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb @@ -1,6 +1,6 @@ require "spec_helper" -shared_examples "migrating a deleted user's associated records to the ghost user" do |record_class| +shared_examples "migrating a deleted user's associated records to the ghost user" do |record_class, fields| record_class_name = record_class.to_s.titleize.downcase let(:project) { create(:project) } @@ -11,6 +11,7 @@ shared_examples "migrating a deleted user's associated records to the ghost user context "for a #{record_class_name} the user has created" do let!(:record) { created_record } + let(:migrated_fields) { fields || [:author] } it "does not delete the #{record_class_name}" do service.execute @@ -18,22 +19,20 @@ shared_examples "migrating a deleted user's associated records to the ghost user expect(record_class.find_by_id(record.id)).to be_present end - it "migrates the #{record_class_name} so that the 'Ghost User' is the #{record_class_name} owner" do + it "blocks the user before migrating #{record_class_name}s to the 'Ghost User'" do service.execute - migrated_record = record_class.find_by_id(record.id) - - if migrated_record.respond_to?(:author) - expect(migrated_record.author).to eq(User.ghost) - else - expect(migrated_record.send(author_alias)).to eq(User.ghost) - end + expect(user).to be_blocked end - it "blocks the user before migrating #{record_class_name}s to the 'Ghost User'" do + it 'migrates all associated fields to te "Ghost user"' do service.execute - expect(user).to be_blocked + migrated_record = record_class.find_by_id(record.id) + + migrated_fields.each do |field| + expect(migrated_record.public_send(field)).to eq(User.ghost) + end end context "race conditions" do diff --git a/spec/support/sidekiq.rb b/spec/support/sidekiq.rb index 5478fea4e64..d143014692d 100644 --- a/spec/support/sidekiq.rb +++ b/spec/support/sidekiq.rb @@ -8,4 +8,8 @@ RSpec.configure do |config| config.after(:each, :sidekiq) do Sidekiq::Worker.clear_all end + + config.after(:each, :sidekiq, :redis) do + Sidekiq.redis { |redis| redis.flushdb } + end end diff --git a/spec/support/sorting_helper.rb b/spec/support/sorting_helper.rb new file mode 100644 index 00000000000..577518d726c --- /dev/null +++ b/spec/support/sorting_helper.rb @@ -0,0 +1,18 @@ +# Helper allows you to sort items +# +# Params +# value - value for sorting +# +# Usage: +# include SortingHelper +# +# sorting_by('Oldest updated') +# +module SortingHelper + def sorting_by(value) + find('button.dropdown-toggle').click + page.within('.content ul.dropdown-menu.dropdown-menu-align-right li') do + click_link value + end + end +end diff --git a/spec/support/stub_feature_flags.rb b/spec/support/stub_feature_flags.rb new file mode 100644 index 00000000000..b96338bf548 --- /dev/null +++ b/spec/support/stub_feature_flags.rb @@ -0,0 +1,8 @@ +module StubFeatureFlags + def stub_feature_flags(features) + features.each do |feature_name, enabled| + allow(Feature).to receive(:enabled?).with(feature_name) { enabled } + allow(Feature).to receive(:enabled?).with(feature_name.to_s) { enabled } + end + end +end diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index 0cae5620920..0a194ca4c90 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -206,6 +206,7 @@ module TestEnv # Otherwise they'd be created by the first test, often timing out and # causing a transient test failure def eager_load_driver_server + return unless ENV['CI'] return unless defined?(Capybara) puts "Starting the Capybara driver server..." diff --git a/spec/support/unique_ip_check_shared_examples.rb b/spec/support/unique_ip_check_shared_examples.rb index 1986d202c4a..ff0b47899f5 100644 --- a/spec/support/unique_ip_check_shared_examples.rb +++ b/spec/support/unique_ip_check_shared_examples.rb @@ -1,7 +1,9 @@ shared_context 'unique ips sign in limit' do include StubENV before(:each) do - Gitlab::Redis.with(&:flushall) + Gitlab::Redis::Cache.with(&:flushall) + Gitlab::Redis::Queues.with(&:flushall) + Gitlab::Redis::SharedState.with(&:flushall) end before do diff --git a/spec/uploaders/attachment_uploader_spec.rb b/spec/uploaders/attachment_uploader_spec.rb index d82dbe871d5..04ee6e9bfad 100644 --- a/spec/uploaders/attachment_uploader_spec.rb +++ b/spec/uploaders/attachment_uploader_spec.rb @@ -5,7 +5,7 @@ describe AttachmentUploader do describe "#store_dir" do it "stores in the system dir" do - expect(uploader.store_dir).to start_with("uploads/system/user") + expect(uploader.store_dir).to start_with("uploads/-/system/user") end it "uses the old path when using object storage" do diff --git a/spec/uploaders/avatar_uploader_spec.rb b/spec/uploaders/avatar_uploader_spec.rb index 201fe6949aa..1dc574699d8 100644 --- a/spec/uploaders/avatar_uploader_spec.rb +++ b/spec/uploaders/avatar_uploader_spec.rb @@ -5,7 +5,7 @@ describe AvatarUploader do describe "#store_dir" do it "stores in the system dir" do - expect(uploader.store_dir).to start_with("uploads/system/user") + expect(uploader.store_dir).to start_with("uploads/-/system/user") end it "uses the old path when using object storage" do diff --git a/spec/uploaders/file_mover_spec.rb b/spec/uploaders/file_mover_spec.rb index 896cb410ed5..d7c1b390f9a 100644 --- a/spec/uploaders/file_mover_spec.rb +++ b/spec/uploaders/file_mover_spec.rb @@ -4,11 +4,11 @@ describe FileMover do let(:filename) { 'banana_sample.gif' } let(:file) { fixture_file_upload(Rails.root.join('spec', 'fixtures', filename)) } let(:temp_description) do - 'test ![banana_sample](/uploads/temp/secret55/banana_sample.gif) same ![banana_sample]'\ - '(/uploads/temp/secret55/banana_sample.gif)' + 'test ![banana_sample](/uploads/system/temp/secret55/banana_sample.gif) same ![banana_sample]'\ + '(/uploads/system/temp/secret55/banana_sample.gif)' end let(:temp_file_path) { File.join('secret55', filename).to_s } - let(:file_path) { File.join('uploads', 'personal_snippet', snippet.id.to_s, 'secret55', filename).to_s } + let(:file_path) { File.join('uploads', 'system', 'personal_snippet', snippet.id.to_s, 'secret55', filename).to_s } let(:snippet) { create(:personal_snippet, description: temp_description) } @@ -28,8 +28,8 @@ describe FileMover do expect(snippet.reload.description) .to eq( - "test ![banana_sample](/uploads/personal_snippet/#{snippet.id}/secret55/banana_sample.gif)"\ - " same ![banana_sample](/uploads/personal_snippet/#{snippet.id}/secret55/banana_sample.gif)" + "test ![banana_sample](/uploads/system/personal_snippet/#{snippet.id}/secret55/banana_sample.gif)"\ + " same ![banana_sample](/uploads/system/personal_snippet/#{snippet.id}/secret55/banana_sample.gif)" ) end @@ -50,8 +50,8 @@ describe FileMover do expect(snippet.reload.description) .to eq( - "test ![banana_sample](/uploads/temp/secret55/banana_sample.gif)"\ - " same ![banana_sample](/uploads/temp/secret55/banana_sample.gif)" + "test ![banana_sample](/uploads/system/temp/secret55/banana_sample.gif)"\ + " same ![banana_sample](/uploads/system/temp/secret55/banana_sample.gif)" ) end diff --git a/spec/uploaders/personal_file_uploader_spec.rb b/spec/uploaders/personal_file_uploader_spec.rb index fb92f2ae3ab..eb55e8ebd24 100644 --- a/spec/uploaders/personal_file_uploader_spec.rb +++ b/spec/uploaders/personal_file_uploader_spec.rb @@ -10,7 +10,7 @@ describe PersonalFileUploader do dynamic_segment = "personal_snippet/#{snippet.id}" - expect(described_class.absolute_path(upload)).to end_with("#{dynamic_segment}/secret/foo.jpg") + expect(described_class.absolute_path(upload)).to end_with("/system/#{dynamic_segment}/secret/foo.jpg") end end @@ -19,7 +19,7 @@ describe PersonalFileUploader do uploader = described_class.new(snippet, 'secret') allow(uploader).to receive(:file).and_return(double(extension: 'txt', filename: 'file_name')) - expected_url = "/uploads/personal_snippet/#{snippet.id}/secret/file_name" + expected_url = "/uploads/system/personal_snippet/#{snippet.id}/secret/file_name" expect(uploader.to_h).to eq( alt: 'file_name', diff --git a/spec/workers/schedule_update_user_activity_worker_spec.rb b/spec/workers/schedule_update_user_activity_worker_spec.rb index e583c3203aa..32c59381b01 100644 --- a/spec/workers/schedule_update_user_activity_worker_spec.rb +++ b/spec/workers/schedule_update_user_activity_worker_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe ScheduleUpdateUserActivityWorker, :redis do +describe ScheduleUpdateUserActivityWorker, :clean_gitlab_redis_shared_state do let(:now) { Time.now } before do diff --git a/spec/workers/update_user_activity_worker_spec.rb b/spec/workers/update_user_activity_worker_spec.rb index 43e9511f116..268ca1d81f2 100644 --- a/spec/workers/update_user_activity_worker_spec.rb +++ b/spec/workers/update_user_activity_worker_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe UpdateUserActivityWorker, :redis do +describe UpdateUserActivityWorker, :clean_gitlab_redis_shared_state do let(:user_active_2_days_ago) { create(:user, current_sign_in_at: 10.months.ago) } let(:user_active_yesterday_1) { create(:user) } let(:user_active_yesterday_2) { create(:user) } @@ -25,7 +25,7 @@ describe UpdateUserActivityWorker, :redis do end end - it 'deletes the pairs from Redis' do + it 'deletes the pairs from SharedState' do data.each { |id, time| Gitlab::UserActivities.record(id, time) } subject.perform(data) diff --git a/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml b/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml index 5b6af7be8c4..eeefadaa019 100644 --- a/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml @@ -4,10 +4,21 @@ image: docker:latest services: - docker:dind +before_script: + - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY + +build-master: + stage: build + script: + - docker build --pull -t "$CI_REGISTRY_IMAGE" . + - docker push "$CI_REGISTRY_IMAGE" + only: + - master + build: stage: build - before_script: - - docker login -u "$CI_REGISTRY_USER" -p "CI_REGISTRY_PASSWORD" $CI_REGISTRY script: - - docker build --pull -t "$CI_REGISTRY_IMAGE:CI_COMMIT_REF_SLUG" . - - docker push "$CI_REGISTRY_IMAGE:CI_COMMIT_REF_SLUG" + - docker build --pull -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG" . + - docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG" + except: + - master diff --git a/vendor/gitlab-ci-yml/autodeploy/Kubernetes-with-canary.gitlab-ci.yml b/vendor/gitlab-ci-yml/autodeploy/Kubernetes-with-canary.gitlab-ci.yml index 555a51d35b9..06b0c84e516 100644 --- a/vendor/gitlab-ci-yml/autodeploy/Kubernetes-with-canary.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/autodeploy/Kubernetes-with-canary.gitlab-ci.yml @@ -28,7 +28,7 @@ canary: - command canary environment: name: production - url: http://$CI_PROJECT_NAME.$KUBE_DOMAIN + url: http://$CI_PROJECT_PATH_SLUG.$KUBE_DOMAIN when: manual only: - master @@ -39,7 +39,7 @@ production: - command deploy environment: name: production - url: http://$CI_PROJECT_NAME.$KUBE_DOMAIN + url: http://$CI_PROJECT_PATH_SLUG.$KUBE_DOMAIN when: manual only: - master @@ -50,7 +50,7 @@ staging: - command deploy environment: name: staging - url: http://$CI_PROJECT_NAME-staging.$KUBE_DOMAIN + url: http://$CI_PROJECT_PATH_SLUG-staging.$KUBE_DOMAIN only: - master @@ -60,7 +60,7 @@ review: - command deploy environment: name: review/$CI_COMMIT_REF_NAME - url: http://$CI_PROJECT_NAME-$CI_ENVIRONMENT_SLUG.$KUBE_DOMAIN + url: http://$CI_PROJECT_PATH_SLUG-$CI_ENVIRONMENT_SLUG.$KUBE_DOMAIN on_stop: stop_review only: - branches diff --git a/vendor/gitlab-ci-yml/autodeploy/Kubernetes.gitlab-ci.yml b/vendor/gitlab-ci-yml/autodeploy/Kubernetes.gitlab-ci.yml index ee830ec2eb0..722934b7981 100644 --- a/vendor/gitlab-ci-yml/autodeploy/Kubernetes.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/autodeploy/Kubernetes.gitlab-ci.yml @@ -27,7 +27,7 @@ production: - command deploy environment: name: production - url: http://$CI_PROJECT_NAME.$KUBE_DOMAIN + url: http://$CI_PROJECT_PATH_SLUG.$KUBE_DOMAIN when: manual only: - master @@ -38,7 +38,7 @@ staging: - command deploy environment: name: staging - url: http://$CI_PROJECT_NAME-staging.$KUBE_DOMAIN + url: http://$CI_PROJECT_PATH_SLUG-staging.$KUBE_DOMAIN only: - master @@ -48,7 +48,7 @@ review: - command deploy environment: name: review/$CI_COMMIT_REF_NAME - url: http://$CI_PROJECT_NAME-$CI_ENVIRONMENT_SLUG.$KUBE_DOMAIN + url: http://$CI_PROJECT_PATH_SLUG-$CI_ENVIRONMENT_SLUG.$KUBE_DOMAIN on_stop: stop_review only: - branches diff --git a/vendor/gitlab-ci-yml/autodeploy/OpenShift.gitlab-ci.yml b/vendor/gitlab-ci-yml/autodeploy/OpenShift.gitlab-ci.yml index 27c9107e0d7..acba718ebe4 100644 --- a/vendor/gitlab-ci-yml/autodeploy/OpenShift.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/autodeploy/OpenShift.gitlab-ci.yml @@ -23,38 +23,32 @@ build: production: stage: production - variables: - CI_ENVIRONMENT_URL: http://$CI_PROJECT_NAME.$KUBE_DOMAIN script: - command deploy environment: name: production - url: http://$CI_PROJECT_NAME.$KUBE_DOMAIN + url: http://$CI_PROJECT_PATH_SLUG.$KUBE_DOMAIN when: manual only: - master staging: stage: staging - variables: - CI_ENVIRONMENT_URL: http://$CI_PROJECT_NAME-staging.$KUBE_DOMAIN script: - command deploy environment: name: staging - url: http://$CI_PROJECT_NAME-staging.$KUBE_DOMAIN + url: http://$CI_PROJECT_PATH_SLUG-staging.$KUBE_DOMAIN only: - master review: stage: review - variables: - CI_ENVIRONMENT_URL: http://$CI_PROJECT_NAME-$CI_ENVIRONMENT_SLUG.$KUBE_DOMAIN script: - command deploy environment: name: review/$CI_COMMIT_REF_NAME - url: http://$CI_PROJECT_NAME-$CI_ENVIRONMENT_SLUG.$KUBE_DOMAIN + url: http://$CI_PROJECT_PATH_SLUG-$CI_ENVIRONMENT_SLUG.$KUBE_DOMAIN on_stop: stop_review only: - branches diff --git a/vendor/licenses.csv b/vendor/licenses.csv index 5dcac5947a1..5beb3e5e9bf 100644 --- a/vendor/licenses.csv +++ b/vendor/licenses.csv @@ -1,9 +1,9 @@ RedCloth,4.3.2,MIT -abbrev,1.0.9,ISC +abbrev,1.1.0,ISC accepts,1.3.3,MIT ace-rails-ap,4.1.2,MIT -acorn,5.0.3,MIT -acorn-dynamic-import,2.0.1,MIT +acorn,5.1.1,MIT +acorn-dynamic-import,2.0.2,MIT acorn-jsx,3.0.1,MIT actionmailer,4.2.8,MIT actionpack,4.2.8,MIT @@ -16,7 +16,7 @@ acts-as-taggable-on,4.0.0,MIT addressable,2.3.8,Apache 2.0 after,0.8.2,MIT after_commit_queue,1.3.0,MIT -ajv,4.11.2,MIT +ajv,4.11.8,MIT ajv-keywords,1.5.1,MIT akismet,2.0.0,MIT align-text,0.1.4,MIT @@ -26,16 +26,17 @@ amdefine,1.0.1,BSD-3-Clause OR MIT ansi-escapes,1.4.0,MIT ansi-html,0.0.5,"Apache, Version 2.0" ansi-regex,2.1.1,MIT -ansi-styles,2.2.1,MIT +ansi-styles,3.1.0,MIT anymatch,1.3.0,ISC append-transform,0.4.0,MIT -aproba,1.1.0,ISC -are-we-there-yet,1.1.2,ISC +aproba,1.1.2,ISC +are-we-there-yet,1.1.4,ISC arel,6.0.4,MIT argparse,1.0.9,MIT arr-diff,2.0.0,MIT -arr-flatten,1.0.1,MIT +arr-flatten,1.1.0,MIT array-find,1.0.0,MIT +array-find-index,1.0.2,MIT array-flatten,1.1.1,MIT array-slice,0.2.3,MIT array-union,1.0.2,MIT @@ -63,26 +64,27 @@ aws-sign2,0.6.0,Apache 2.0 aws4,1.6.0,MIT axiom-types,0.1.1,MIT babel-code-frame,6.22.0,MIT -babel-core,6.23.1,MIT -babel-generator,6.23.0,MIT -babel-helper-bindify-decorators,6.22.0,MIT -babel-helper-builder-binary-assignment-operator-visitor,6.22.0,MIT -babel-helper-call-delegate,6.22.0,MIT -babel-helper-define-map,6.23.0,MIT -babel-helper-explode-assignable-expression,6.22.0,MIT -babel-helper-explode-class,6.22.0,MIT -babel-helper-function-name,6.23.0,MIT -babel-helper-get-function-arity,6.22.0,MIT -babel-helper-hoist-variables,6.22.0,MIT -babel-helper-optimise-call-expression,6.23.0,MIT -babel-helper-regex,6.22.0,MIT -babel-helper-remap-async-to-generator,6.22.0,MIT -babel-helper-replace-supers,6.23.0,MIT -babel-helpers,6.23.0,MIT -babel-loader,6.2.10,MIT +babel-core,6.25.0,MIT +babel-eslint,7.2.3,MIT +babel-generator,6.25.0,MIT +babel-helper-bindify-decorators,6.24.1,MIT +babel-helper-builder-binary-assignment-operator-visitor,6.24.1,MIT +babel-helper-call-delegate,6.24.1,MIT +babel-helper-define-map,6.24.1,MIT +babel-helper-explode-assignable-expression,6.24.1,MIT +babel-helper-explode-class,6.24.1,MIT +babel-helper-function-name,6.24.1,MIT +babel-helper-get-function-arity,6.24.1,MIT +babel-helper-hoist-variables,6.24.1,MIT +babel-helper-optimise-call-expression,6.24.1,MIT +babel-helper-regex,6.24.1,MIT +babel-helper-remap-async-to-generator,6.24.1,MIT +babel-helper-replace-supers,6.24.1,MIT +babel-helpers,6.24.1,MIT +babel-loader,6.4.1,MIT babel-messages,6.23.0,MIT babel-plugin-check-es2015-constants,6.22.0,MIT -babel-plugin-istanbul,4.0.0,New BSD +babel-plugin-istanbul,4.1.4,New BSD babel-plugin-syntax-async-functions,6.13.0,MIT babel-plugin-syntax-async-generators,6.13.0,MIT babel-plugin-syntax-class-properties,6.13.0,MIT @@ -91,57 +93,57 @@ babel-plugin-syntax-dynamic-import,6.18.0,MIT babel-plugin-syntax-exponentiation-operator,6.13.0,MIT babel-plugin-syntax-object-rest-spread,6.13.0,MIT babel-plugin-syntax-trailing-function-commas,6.22.0,MIT -babel-plugin-transform-async-generator-functions,6.22.0,MIT -babel-plugin-transform-async-to-generator,6.22.0,MIT -babel-plugin-transform-class-properties,6.23.0,MIT -babel-plugin-transform-decorators,6.22.0,MIT -babel-plugin-transform-define,1.2.0,MIT +babel-plugin-transform-async-generator-functions,6.24.1,MIT +babel-plugin-transform-async-to-generator,6.24.1,MIT +babel-plugin-transform-class-properties,6.24.1,MIT +babel-plugin-transform-decorators,6.24.1,MIT +babel-plugin-transform-define,1.3.0,MIT babel-plugin-transform-es2015-arrow-functions,6.22.0,MIT babel-plugin-transform-es2015-block-scoped-functions,6.22.0,MIT -babel-plugin-transform-es2015-block-scoping,6.23.0,MIT -babel-plugin-transform-es2015-classes,6.23.0,MIT -babel-plugin-transform-es2015-computed-properties,6.22.0,MIT +babel-plugin-transform-es2015-block-scoping,6.24.1,MIT +babel-plugin-transform-es2015-classes,6.24.1,MIT +babel-plugin-transform-es2015-computed-properties,6.24.1,MIT babel-plugin-transform-es2015-destructuring,6.23.0,MIT -babel-plugin-transform-es2015-duplicate-keys,6.22.0,MIT +babel-plugin-transform-es2015-duplicate-keys,6.24.1,MIT babel-plugin-transform-es2015-for-of,6.23.0,MIT -babel-plugin-transform-es2015-function-name,6.22.0,MIT +babel-plugin-transform-es2015-function-name,6.24.1,MIT babel-plugin-transform-es2015-literals,6.22.0,MIT -babel-plugin-transform-es2015-modules-amd,6.24.0,MIT -babel-plugin-transform-es2015-modules-commonjs,6.24.0,MIT -babel-plugin-transform-es2015-modules-systemjs,6.23.0,MIT -babel-plugin-transform-es2015-modules-umd,6.24.0,MIT -babel-plugin-transform-es2015-object-super,6.22.0,MIT -babel-plugin-transform-es2015-parameters,6.23.0,MIT -babel-plugin-transform-es2015-shorthand-properties,6.22.0,MIT +babel-plugin-transform-es2015-modules-amd,6.24.1,MIT +babel-plugin-transform-es2015-modules-commonjs,6.24.1,MIT +babel-plugin-transform-es2015-modules-systemjs,6.24.1,MIT +babel-plugin-transform-es2015-modules-umd,6.24.1,MIT +babel-plugin-transform-es2015-object-super,6.24.1,MIT +babel-plugin-transform-es2015-parameters,6.24.1,MIT +babel-plugin-transform-es2015-shorthand-properties,6.24.1,MIT babel-plugin-transform-es2015-spread,6.22.0,MIT -babel-plugin-transform-es2015-sticky-regex,6.22.0,MIT +babel-plugin-transform-es2015-sticky-regex,6.24.1,MIT babel-plugin-transform-es2015-template-literals,6.22.0,MIT babel-plugin-transform-es2015-typeof-symbol,6.23.0,MIT -babel-plugin-transform-es2015-unicode-regex,6.22.0,MIT -babel-plugin-transform-exponentiation-operator,6.22.0,MIT +babel-plugin-transform-es2015-unicode-regex,6.24.1,MIT +babel-plugin-transform-exponentiation-operator,6.24.1,MIT babel-plugin-transform-object-rest-spread,6.23.0,MIT -babel-plugin-transform-regenerator,6.22.0,MIT -babel-plugin-transform-strict-mode,6.22.0,MIT -babel-preset-es2015,6.24.0,MIT -babel-preset-es2016,6.22.0,MIT -babel-preset-es2017,6.22.0,MIT -babel-preset-latest,6.24.0,MIT -babel-preset-stage-2,6.22.0,MIT -babel-preset-stage-3,6.22.0,MIT -babel-register,6.23.0,MIT -babel-runtime,6.22.0,MIT -babel-template,6.23.0,MIT -babel-traverse,6.23.1,MIT -babel-types,6.23.0,MIT +babel-plugin-transform-regenerator,6.24.1,MIT +babel-plugin-transform-strict-mode,6.24.1,MIT +babel-preset-es2015,6.24.1,MIT +babel-preset-es2016,6.24.1,MIT +babel-preset-es2017,6.24.1,MIT +babel-preset-latest,6.24.1,MIT +babel-preset-stage-2,6.24.1,MIT +babel-preset-stage-3,6.24.1,MIT +babel-register,6.24.1,MIT +babel-runtime,6.23.0,MIT +babel-template,6.25.0,MIT +babel-traverse,6.25.0,MIT +babel-types,6.25.0,MIT babosa,1.0.2,MIT -babylon,6.15.0,MIT +babylon,6.17.4,MIT backo2,1.0.2,MIT -balanced-match,0.4.2,MIT +balanced-match,1.0.0,MIT base32,0.3.2,MIT base64-arraybuffer,0.1.5,MIT -base64-js,1.2.0,MIT +base64-js,1.2.1,MIT base64id,1.0.0,MIT -batch,0.5.3,MIT +batch,0.6.1,MIT bcrypt,3.1.11,MIT bcrypt-pbkdf,1.0.1,New BSD better-assert,1.0.2,MIT @@ -150,24 +152,28 @@ binary-extensions,1.8.0,MIT bindata,2.3.5,ruby blob,0.0.4,unknown block-stream,0.0.9,ISC -bluebird,3.4.7,MIT -bn.js,4.11.6,MIT +bluebird,3.5.0,MIT +bn.js,4.11.7,MIT body-parser,1.17.2,MIT +bonjour,3.5.0,MIT boom,2.10.1,New BSD -bootsnap,1.0.0,MIT +bootsnap,1.1.1,MIT bootstrap-sass,3.3.6,MIT -brace-expansion,1.1.6,MIT +bootstrap-sass,3.3.7,MIT +bootstrap_form,2.7.0,MIT +brace-expansion,1.1.8,MIT braces,1.8.5,MIT -brorand,1.0.7,MIT +brorand,1.1.0,MIT browser,2.2.0,MIT browserify-aes,1.0.6,MIT browserify-cipher,1.0.0,MIT browserify-des,1.0.0,MIT browserify-rsa,4.0.1,MIT -browserify-sign,4.0.0,ISC +browserify-sign,4.0.4,ISC browserify-zlib,0.1.4,MIT browserslist,1.7.7,MIT buffer,4.9.1,MIT +buffer-indexof,1.1.0,MIT buffer-shims,1.0.0,MIT buffer-xor,1.0.3,MIT builder,3.2.3,MIT @@ -178,29 +184,30 @@ caller-path,0.1.0,MIT callsite,1.0.0,unknown callsites,0.2.0,MIT camelcase,1.2.1,MIT +camelcase-keys,2.1.0,MIT caniuse-api,1.6.1,MIT -caniuse-db,1.0.30000649,CC-BY-4.0 -carrierwave,1.0.0,MIT -caseless,0.11.0,Apache 2.0 +caniuse-db,1.0.30000699,CC-BY-4.0 +carrierwave,1.1.0,MIT +caseless,0.12.0,Apache 2.0 cause,0.1,MIT center-align,0.1.3,MIT chalk,1.1.3,MIT charlock_holmes,0.7.3,MIT -chokidar,1.6.1,MIT +chokidar,1.7.0,MIT chronic,0.10.2,MIT chronic_duration,0.10.6,MIT chunky_png,1.3.5,MIT -cipher-base,1.0.3,MIT +cipher-base,1.0.4,MIT circular-json,0.3.1,MIT citrus,3.0.2,MIT -clap,1.1.3,MIT +clap,1.2.0,MIT cli-cursor,1.0.2,MIT cli-width,2.1.0,ISC -clipboard,1.6.1,MIT +clipboard,1.7.1,MIT cliui,2.1.0,ISC clone,1.0.2,MIT co,4.6.0,MIT -coa,1.0.1,MIT +coa,1.0.4,MIT code-point-at,1.1.0,MIT coercible,1.0.0,MIT coffee-rails,4.1.1,MIT @@ -214,13 +221,13 @@ colormin,1.1.2,MIT colors,1.1.2,MIT combine-lists,1.0.1,MIT combined-stream,1.0.5,MIT -commander,2.9.0,MIT +commander,2.11.0,MIT commondir,1.0.1,MIT component-bind,1.0.0,unknown component-emitter,1.2.1,MIT component-inherit,0.0.3,unknown -compressible,2.0.9,MIT -compression,1.6.2,MIT +compressible,2.0.10,MIT +compression,1.7.0,MIT compression-webpack-plugin,0.3.2,MIT concat-map,0.0.1,MIT concat-stream,1.6.0,MIT @@ -237,38 +244,40 @@ constants-browserify,1.0.0,MIT contains-path,0.1.0,MIT content-disposition,0.5.2,MIT content-type,1.0.2,MIT -convert-source-map,1.3.0,MIT +convert-source-map,1.5.0,MIT cookie,0.3.1,MIT cookie-signature,1.0.6,MIT core-js,2.4.1,MIT core-util-is,1.0.2,MIT -cosmiconfig,2.1.1,MIT +cosmiconfig,2.1.3,MIT crack,0.4.3,MIT create-ecdh,4.0.0,MIT -create-hash,1.1.2,MIT -create-hmac,1.1.4,MIT +create-hash,1.1.3,MIT +create-hmac,1.1.6,MIT creole,0.5.0,ruby cryptiles,2.0.5,New BSD crypto-browserify,3.11.0,MIT css-color-names,0.0.4,MIT -css-loader,0.28.0,MIT -css-selector-tokenizer,0.7.0,MIT -css_parser,1.4.1,MIT +css-loader,0.28.4,MIT +css-selector-tokenizer,"",unknown +css_parser,1.5.0,MIT cssesc,0.1.0,MIT cssnano,3.10.0,MIT csso,2.3.2,MIT +currently-unhandled,0.4.1,MIT custom-event,1.0.1,MIT -d,0.1.1,MIT -d3,3.5.11,New BSD +d,1.0.0,MIT +d3,3.5.17,New BSD d3_rails,3.5.11,MIT dashdash,1.14.1,MIT date-now,0.1.4,MIT de-indent,1.0.2,MIT -debug,2.6.0,MIT +debug,2.6.8,MIT debugger-ruby_core_source,1.3.8,MIT decamelize,1.2.0,MIT deckar01-task_list,2.0.0,MIT -deep-extend,0.4.1,MIT +deep-equal,1.0.1,MIT +deep-extend,0.4.2,MIT deep-is,0.1.3,MIT default-require-extensions,1.0.0,MIT default_value_for,3.0.2,MIT @@ -276,31 +285,35 @@ defaults,1.0.3,MIT defined,1.0.0,MIT del,2.2.2,MIT delayed-stream,1.0.0,MIT -delegate,3.1.2,MIT +delegate,3.1.3,MIT delegates,1.0.0,MIT depd,1.1.0,MIT des.js,1.0.0,MIT descendants_tracker,0.0.4,MIT destroy,1.0.4,MIT detect-indent,4.0.0,MIT +detect-node,2.0.3,ISC devise,4.2.0,MIT devise-two-factor,3.0.0,MIT di,0.0.1,MIT diff-lcs,1.2.5,"MIT,Perl Artistic v2,GNU GPL v2" diffie-hellman,5.0.2,MIT diffy,3.1.0,MIT -doctrine,1.5.0,BSD -document-register-element,1.3.0,MIT +dns-equal,1.0.0,MIT +dns-packet,1.1.1,MIT +dns-txt,2.0.2,MIT +doctrine,2.0.0,Apache 2.0 +document-register-element,1.5.0,MIT dom-serialize,2.2.1,MIT dom-serializer,0.1.0,MIT domain-browser,1.1.7,MIT domain_name,0.5.20161021,"Simplified BSD,New BSD,Mozilla Public License 2.0" domelementtype,1.3.0,unknown -domhandler,2.3.0,unknown -domutils,1.5.1,unknown +domhandler,2.4.1,Simplified BSD +domutils,1.6.2,Simplified BSD doorkeeper,4.2.0,MIT doorkeeper-openid_connect,1.1.2,MIT -dropzone,4.2.0,MIT +dropzone,4.3.0,MIT dropzonejs-rails,0.7.2,MIT duplexer,0.1.1,MIT duplexify,3.5.0,MIT @@ -308,8 +321,8 @@ ecc-jsbn,0.1.1,MIT editorconfig,0.13.2,MIT ee-first,1.1.1,MIT ejs,2.5.6,Apache 2.0 -electron-to-chromium,1.3.3,ISC -elliptic,6.3.3,MIT +electron-to-chromium,1.3.15,ISC +elliptic,6.4.0,MIT email_reply_trimmer,0.1.6,MIT emoji-unicode-version,0.2.1,MIT emojis-list,2.1.0,MIT @@ -319,44 +332,45 @@ end-of-stream,1.0.0,MIT engine.io,1.8.3,MIT engine.io-client,1.8.3,MIT engine.io-parser,1.3.2,MIT -enhanced-resolve,3.1.0,MIT +enhanced-resolve,3.3.0,MIT ent,2.2.0,MIT entities,1.1.1,BSD-like equalizer,0.0.11,MIT errno,0.1.4,MIT -error-ex,1.3.0,MIT +error-ex,1.3.1,MIT erubis,2.7.0,MIT -es5-ext,0.10.12,MIT -es6-iterator,2.0.0,MIT -es6-map,0.1.4,MIT +es5-ext,0.10.24,MIT +es6-iterator,2.0.1,MIT +es6-map,0.1.5,MIT es6-promise,3.0.2,MIT -es6-set,0.1.4,MIT -es6-symbol,3.1.0,MIT -es6-weak-map,2.0.1,MIT +es6-set,0.1.5,MIT +es6-symbol,3.1.1,MIT +es6-weak-map,2.0.2,MIT escape-html,1.0.3,MIT escape-string-regexp,1.0.5,MIT escape_utils,1.1.1,MIT escodegen,1.8.1,Simplified BSD escope,3.6.0,Simplified BSD -eslint,3.15.0,MIT +eslint,3.19.0,MIT eslint-config-airbnb-base,10.0.1,MIT -eslint-import-resolver-node,0.2.3,MIT -eslint-import-resolver-webpack,0.8.1,MIT -eslint-module-utils,2.0.0,MIT -eslint-plugin-filenames,1.1.0,MIT -eslint-plugin-html,2.0.1,ISC -eslint-plugin-import,2.2.0,MIT -eslint-plugin-jasmine,2.2.0,MIT +eslint-import-resolver-node,0.3.1,MIT +eslint-import-resolver-webpack,0.8.3,MIT +eslint-module-utils,2.1.1,MIT +eslint-plugin-filenames,1.2.0,MIT +eslint-plugin-html,2.0.3,ISC +eslint-plugin-import,2.7.0,MIT +eslint-plugin-jasmine,2.7.1,MIT eslint-plugin-promise,3.5.0,ISC -espree,3.4.0,Simplified BSD -esprima,3.1.3,Simplified BSD -esrecurse,4.1.0,Simplified BSD -estraverse,4.1.1,Simplified BSD +espree,3.4.3,Simplified BSD +esprima,2.7.3,Simplified BSD +esquery,1.0.0,BSD +esrecurse,4.2.0,Simplified BSD +estraverse,4.2.0,Simplified BSD esutils,2.0.2,BSD et-orbi,1.0.3,MIT etag,1.8.0,MIT eve-raphael,0.5.0,Apache 2.0 -event-emitter,0.3.4,MIT +event-emitter,0.3.5,MIT event-stream,3.3.4,MIT eventemitter3,1.2.0,MIT events,1.1.1,MIT @@ -371,13 +385,14 @@ expand-range,1.8.2,MIT exports-loader,0.6.4,MIT express,4.15.3,MIT expression_parser,0.9.0,MIT -extend,3.0.0,MIT +extend,3.0.1,MIT extglob,0.3.2,MIT extlib,0.9.16,MIT extsprintf,1.0.2,MIT -faraday,0.11.0,MIT +faraday,0.12.1,MIT faraday_middleware,0.11.0.1,MIT faraday_middleware-multi_json,0.0.6,MIT +fast-deep-equal,1.0.0,MIT fast-levenshtein,2.0.6,MIT fast_gettext,1.4.0,"MIT,ruby" fastparse,1.1.1,MIT @@ -385,15 +400,15 @@ faye-websocket,0.7.3,MIT ffi,1.9.10,BSD figures,1.7.0,MIT file-entry-cache,2.0.0,MIT -file-loader,0.11.1,MIT -filename-regex,2.0.0,MIT +file-loader,0.11.2,MIT +filename-regex,2.0.1,MIT fileset,2.0.3,MIT filesize,3.3.0,New BSD fill-range,2.2.3,MIT finalhandler,1.0.3,MIT find-cache-dir,0.1.1,MIT find-root,0.1.2,MIT -find-up,2.1.0,MIT +find-up,1.1.2,MIT flat-cache,1.2.2,MIT flatten,1.0.2,MIT flipper,0.10.2,MIT @@ -409,41 +424,43 @@ fog-openstack,0.1.6,MIT fog-rackspace,0.1.1,MIT fog-xml,0.1.3,MIT font-awesome-rails,4.7.0.1,"MIT,SIL Open Font License" -for-in,0.1.6,MIT -for-own,0.1.4,MIT +for-in,1.0.2,MIT +for-own,0.1.5,MIT forever-agent,0.6.1,Apache 2.0 -form-data,2.1.2,MIT +form-data,2.1.4,MIT formatador,0.2.5,MIT forwarded,0.1.0,MIT fresh,0.5.0,MIT from,0.1.7,MIT +fs-access,1.0.1,MIT fs.realpath,1.0.0,ISC -fsevents,,unknown -fstream,1.0.10,ISC +fsevents,1.1.2,MIT +fstream,1.0.11,ISC fstream-ignore,1.0.5,ISC function-bind,1.1.0,MIT -gauge,2.7.2,ISC +gauge,2.7.4,ISC gemnasium-gitlab-service,0.2.6,MIT gemojione,3.0.1,MIT generate-function,2.0.0,MIT generate-object-property,1.2.0,MIT get-caller-file,1.0.2,ISC +get-stdin,4.0.1,MIT get_process_mem,0.2.0,MIT -getpass,0.1.6,MIT +getpass,0.1.7,MIT gettext_i18n_rails,1.8.0,MIT gettext_i18n_rails_js,1.2.0,MIT -gitaly,0.8.0,MIT +gitaly,0.14.0,MIT github-linguist,4.7.6,MIT github-markup,1.4.0,MIT gitlab-flowdock-git-hook,1.0.1,MIT gitlab-grit,2.8.1,MIT gitlab-markup,1.5.1,MIT gitlab_omniauth-ldap,1.2.1,MIT -glob,7.1.1,ISC +glob,7.1.2,ISC glob-base,0.3.0,MIT glob-parent,2.0.0,ISC globalid,0.3.7,MIT -globals,9.14.0,MIT +globals,9.18.0,MIT globby,5.0.0,MIT gollum-grit_adapter,1.0.1,MIT gollum-lib,4.2.1,MIT @@ -455,32 +472,34 @@ google-protobuf,3.2.0.2,New BSD googleauth,0.5.1,Apache 2.0 got,3.3.1,MIT graceful-fs,4.1.11,ISC -graceful-readlink,1.0.1,MIT grape,0.19.1,MIT grape-entity,0.6.0,MIT -grpc,1.2.5,New BSD +grpc,1.4.0,New BSD gzip-size,3.0.0,MIT hamlit,2.6.1,MIT handle-thing,1.2.5,MIT -handlebars,4.0.6,MIT -har-validator,2.0.6,ISC +handlebars,4.0.10,MIT +har-schema,1.0.5,ISC +har-validator,4.2.1,ISC has,1.0.1,MIT has-ansi,2.0.0,MIT has-binary,0.1.7,MIT has-cors,1.1.0,MIT -has-flag,1.0.0,MIT +has-flag,2.0.0,MIT has-unicode,2.0.1,ISC +hash-base,2.0.2,MIT hash-sum,1.0.2,MIT -hash.js,1.0.3,MIT +hash.js,1.1.3,MIT hashie,3.5.5,MIT hashie-forbidden_attributes,0.1.1,MIT hawk,3.1.3,New BSD he,1.1.1,MIT health_check,2.6.0,MIT hipchat,1.5.2,MIT +hmac-drbg,1.0.1,MIT hoek,2.16.3,New BSD home-or-tmp,2.0.0,MIT -hosted-git-info,2.2.0,ISC +hosted-git-info,2.5.0,ISC hpack.js,2.1.6,MIT html-comment-regex,1.1.1,MIT html-entities,1.2.0,MIT @@ -503,12 +522,14 @@ https-browserify,0.0.1,MIT i18n,0.8.1,MIT ice_nine,0.11.2,MIT iconv-lite,0.4.15,MIT -icss-replace-symbols,1.0.2,ISC +icss-replace-symbols,1.1.0,ISC +icss-utils,2.1.0,ISC ieee754,1.1.8,New BSD -ignore,3.2.2,MIT +ignore,3.3.3,MIT ignore-by-default,1.0.1,ISC immediate,3.0.6,MIT imurmurhash,0.1.4,MIT +indent-string,2.1.0,MIT indexes-of,1.0.1,MIT indexof,0.0.1,unknown infinity-agent,2.0.3,MIT @@ -517,25 +538,28 @@ influxdb,0.2.3,MIT inherits,2.0.3,ISC ini,1.3.4,ISC inquirer,0.12.0,MIT -interpret,1.0.1,MIT +internal-ip,1.2.0,MIT +interpret,1.0.3,MIT invariant,2.2.2,New BSD invert-kv,1.0.0,MIT +ip,1.1.5,MIT ipaddr.js,1.3.0,MIT ipaddress,0.8.3,MIT is-absolute,0.2.6,MIT is-absolute-url,2.1.0,MIT is-arrayish,0.2.1,MIT is-binary-path,1.0.1,MIT -is-buffer,1.1.4,MIT +is-buffer,1.1.5,MIT is-builtin-module,1.0.0,MIT -is-dotfile,1.0.2,MIT +is-directory,0.3.1,MIT +is-dotfile,1.0.3,MIT is-equal-shallow,0.1.3,MIT is-extendable,0.1.1,MIT is-extglob,1.0.0,MIT is-finite,1.0.2,MIT is-fullwidth-code-point,1.0.0,MIT is-glob,2.0.1,MIT -is-my-json-valid,2.15.0,MIT +is-my-json-valid,2.16.0,MIT is-npm,1.0.0,MIT is-number,2.1.0,MIT is-path-cwd,1.0.0,MIT @@ -556,56 +580,58 @@ is-utf8,0.2.1,MIT is-windows,0.2.0,MIT isarray,1.0.0,MIT isbinaryfile,3.0.2,MIT -isexe,1.1.2,ISC +isexe,2.0.0,ISC isobject,2.1.0,MIT isstream,0.1.2,MIT istanbul,0.4.5,New BSD -istanbul-api,1.1.1,New BSD -istanbul-lib-coverage,1.0.1,New BSD -istanbul-lib-hook,1.0.0,New BSD -istanbul-lib-instrument,1.4.2,New BSD -istanbul-lib-report,1.0.0-alpha.3,New BSD -istanbul-lib-source-maps,1.1.0,New BSD -istanbul-reports,1.0.1,New BSD -jasmine-core,2.6.3,MIT +istanbul-api,1.1.10,New BSD +istanbul-lib-coverage,1.1.1,New BSD +istanbul-lib-hook,1.0.7,New BSD +istanbul-lib-instrument,1.7.3,New BSD +istanbul-lib-report,1.1.1,New BSD +istanbul-lib-source-maps,1.2.1,New BSD +istanbul-reports,1.1.1,New BSD +jasmine-core,2.6.4,MIT jasmine-jquery,2.1.1,MIT jed,1.1.1,MIT jira-ruby,1.1.2,MIT jodid25519,1.0.2,MIT -jquery,2.2.1,MIT +jquery,2.2.4,MIT jquery-atwho-rails,1.3.2,MIT jquery-rails,4.1.1,MIT -jquery-ujs,1.2.1,MIT +jquery-ujs,1.2.2,MIT js-base64,2.1.9,BSD -js-beautify,1.6.12,MIT -js-cookie,2.1.3,MIT -js-tokens,3.0.1,MIT -js-yaml,3.7.0,MIT -jsbn,0.1.0,BSD +js-beautify,1.6.14,MIT +js-cookie,2.1.4,MIT +js-tokens,3.0.2,MIT +js-yaml,"",unknown +jsbn,0.1.1,MIT jsesc,1.3.0,MIT json,1.8.6,ruby json-jwt,1.7.1,MIT json-loader,0.5.4,MIT json-schema,0.2.3,"AFLv2.1,BSD" +json-schema-traverse,0.3.1,MIT json-stable-stringify,1.0.1,MIT json-stringify-safe,5.0.1,ISC json3,3.3.2,MIT json5,0.5.1,MIT jsonify,0.0.0,Public Domain jsonpointer,4.0.1,MIT -jsprim,1.3.1,MIT +jsprim,1.4.0,MIT jszip,3.1.3,(MIT OR GPL-3.0) jszip-utils,0.0.2,MIT or GPLv3 jwt,1.5.6,MIT kaminari,0.17.0,MIT karma,1.7.0,MIT -karma-coverage-istanbul-reporter,0.2.0,MIT +karma-chrome-launcher,2.2.0,MIT +karma-coverage-istanbul-reporter,0.2.3,MIT karma-jasmine,1.1.0,MIT -karma-mocha-reporter,2.2.2,MIT +karma-mocha-reporter,2.2.3,MIT karma-sourcemap-loader,0.3.7,MIT -karma-webpack,2.0.2,MIT +karma-webpack,2.0.4,MIT kgio,2.10.0,LGPL-2.1+ -kind-of,3.1.0,MIT +kind-of,3.2.2,MIT kubeclient,2.2.0,MIT latest-version,1.0.1,MIT launchy,2.4.3,ISC @@ -617,7 +643,7 @@ lie,3.1.1,MIT little-plugger,1.1.4,MIT load-json-file,1.1.0,MIT loader-runner,2.3.0,MIT -loader-utils,0.2.16,MIT +loader-utils,"",unknown locale,2.1.2,"ruby,LGPLv3+" locate-path,2.0.0,MIT lodash,4.17.4,MIT @@ -631,65 +657,69 @@ lodash._isiterateecall,3.0.9,MIT lodash._topath,3.8.1,MIT lodash.assign,3.2.0,MIT lodash.camelcase,4.3.0,MIT -lodash.capitalize,4.2.1,MIT lodash.cond,4.5.2,MIT -lodash.deburr,4.1.0,MIT lodash.defaults,3.1.2,MIT -lodash.get,4.4.2,MIT +lodash.get,3.7.0,MIT lodash.isarguments,3.1.0,MIT lodash.isarray,3.0.4,MIT -lodash.kebabcase,4.0.1,MIT +lodash.kebabcase,4.1.1,MIT lodash.keys,3.1.2,MIT lodash.memoize,4.1.2,MIT lodash.restparam,3.6.1,MIT -lodash.snakecase,4.0.1,MIT +lodash.snakecase,4.1.1,MIT lodash.uniq,4.5.0,MIT -lodash.words,4.2.0,MIT +lodash.upperfirst,4.3.1,MIT log4js,0.6.38,Apache 2.0 logging,2.2.2,MIT longest,1.0.1,MIT loofah,2.0.3,MIT loose-envify,1.3.1,MIT +loud-rejection,1.6.0,MIT lowercase-keys,1.0.0,MIT lru-cache,3.2.0,ISC macaddress,0.2.8,MIT mail,2.6.5,MIT mail_room,0.9.1,MIT +map-obj,1.0.1,MIT map-stream,0.1.0,unknown marked,0.3.6,MIT -math-expression-evaluator,1.2.16,MIT +math-expression-evaluator,1.2.17,MIT media-typer,0.3.0,MIT memoist,0.15.0,MIT memory-fs,0.4.1,MIT +meow,3.7.0,MIT merge-descriptors,1.0.1,MIT method_source,0.8.2,MIT methods,1.1.2,MIT micromatch,2.3.11,MIT miller-rabin,4.0.0,MIT -mime,1.3.4,MIT +mime,1.3.6,MIT mime-db,1.27.0,MIT +mime-types,2.1.15,MIT mime-types,2.99.3,"MIT,Artistic-2.0,GPL-2.0" mimemagic,0.3.0,MIT mini_portile2,2.1.0,MIT minimalistic-assert,1.0.0,ISC -minimatch,3.0.3,ISC +minimalistic-crypto-utils,1.0.1,MIT +minimatch,3.0.4,ISC minimist,0.0.8,MIT mkdirp,0.5.1,MIT -mmap2,2.2.6,ruby -moment,2.17.1,MIT -mousetrap,1.4.6,Apache 2.0 +mmap2,2.2.7,ruby +moment,2.18.1,MIT +mousetrap,1.6.1,Apache 2.0 mousetrap-rails,1.4.6,"MIT,Apache" -ms,0.7.2,MIT +ms,2.0.0,MIT msgpack,1.1.0,Apache 2.0 multi_json,1.12.1,MIT multi_xml,0.6.0,MIT +multicast-dns,6.1.1,MIT +multicast-dns-service-types,1.1.0,MIT multipart-post,2.0.0,MIT mustermann,0.4.0,MIT mustermann-grape,0.4.0,MIT mute-stream,0.0.5,ISC -mysql2,0.3.20,MIT name-all-modules-plugin,1.0.1,MIT -nan,2.5.1,MIT +nan,2.6.2,MIT natural-compare,1.4.0,MIT negotiator,0.6.1,MIT nested-error-stacks,1.0.2,MIT @@ -697,23 +727,25 @@ net-ldap,0.12.1,MIT net-ssh,3.0.1,MIT netrc,0.11.0,MIT node-ensure,0.0.0,MIT +node-forge,0.6.33,BSD node-libs-browser,2.0.0,MIT -node-pre-gyp,0.6.33,New BSD +node-pre-gyp,0.6.36,New BSD node-zopfli,2.0.2,MIT nodemon,1.11.0,MIT nokogiri,1.6.8.1,MIT -nopt,3.0.6,ISC -normalize-package-data,2.3.5,Simplified BSD -normalize-path,2.0.1,MIT +nopt,4.0.1,ISC +normalize-package-data,2.4.0,Simplified BSD +normalize-path,2.1.1,MIT normalize-range,0.1.2,MIT normalize-url,1.9.1,MIT -npmlog,4.0.2,ISC +npmlog,4.1.2,ISC +null-check,1.0.0,MIT num2fraction,1.2.2,MIT number-is-nan,1.0.1,MIT numerizer,0.1.1,MIT oauth,0.5.1,MIT oauth-sign,0.8.2,Apache 2.0 -oauth2,1.3.1,MIT +oauth2,1.4.0,MIT object-assign,4.1.1,MIT object-component,0.0.3,unknown object.omit,2.0.1,MIT @@ -740,7 +772,7 @@ omniauth-twitter,1.2.1,MIT omniauth_crowd,2.2.3,MIT on-finished,2.3.0,MIT on-headers,1.0.1,MIT -once,1.3.3,ISC +once,1.4.0,ISC onetime,1.1.0,MIT opener,1.4.3,(WTFPL OR MIT) opn,4.0.2,MIT @@ -758,10 +790,11 @@ os-tmpdir,1.0.2,MIT osenv,0.1.4,ISC p-limit,1.1.0,MIT p-locate,2.0.0,MIT +p-map,1.1.1,MIT package-json,1.2.0,MIT pako,1.0.5,(MIT AND Zlib) -paranoia,2.2.0,MIT -parse-asn1,5.0.0,ISC +paranoia,2.3.1,MIT +parse-asn1,5.1.0,ISC parse-glob,3.0.4,MIT parse-json,2.2.0,MIT parsejson,0.0.3,MIT @@ -769,36 +802,35 @@ parseqs,0.0.5,MIT parseuri,0.0.5,MIT parseurl,1.3.1,MIT path-browserify,0.0.0,MIT -path-exists,3.0.0,MIT +path-exists,2.1.0,MIT path-is-absolute,1.0.1,MIT path-is-inside,1.0.2,(WTFPL OR MIT) path-parse,1.0.5,MIT path-to-regexp,0.1.7,MIT path-type,1.1.0,MIT pause-stream,0.0.11,"MIT,Apache2" -pbkdf2,3.0.9,MIT -pdfjs-dist,1.8.252,Apache 2.0 +pbkdf2,3.0.12,MIT +pdfjs-dist,1.8.527,Apache 2.0 peek,1.0.1,MIT peek-gc,0.0.2,MIT peek-host,1.0.0,MIT -peek-mysql2,1.1.0,MIT peek-performance_bar,1.2.1,MIT peek-pg,1.3.0,MIT peek-rblineprof,0.2.0,MIT peek-redis,1.2.0,MIT peek-sidekiq,1.0.3,MIT +performance-now,0.2.0,MIT pg,0.18.4,"BSD,ruby,GPL" pify,2.3.0,MIT -pikaday,1.5.1,"BSD,MIT" +pikaday,1.6.1,(0BSD OR MIT) pinkie,2.0.4,MIT pinkie-promise,2.0.1,MIT pkg-dir,1.0.0,MIT -pkg-up,1.0.0,MIT pluralize,1.2.1,MIT po_to_json,1.0.1,MIT portfinder,1.0.13,MIT posix-spawn,0.3.11,"MIT,LGPL" -postcss,5.2.16,MIT +postcss,5.2.17,MIT postcss-calc,5.3.1,MIT postcss-colormin,2.2.2,MIT postcss-convert-values,2.6.1,MIT @@ -819,10 +851,10 @@ postcss-minify-font-values,1.0.5,MIT postcss-minify-gradients,1.0.5,MIT postcss-minify-params,1.2.2,MIT postcss-minify-selectors,2.1.1,MIT -postcss-modules-extract-imports,1.0.1,ISC -postcss-modules-local-by-default,1.1.1,MIT -postcss-modules-scope,1.0.2,ISC -postcss-modules-values,1.2.2,ISC +postcss-modules-extract-imports,1.1.0,ISC +postcss-modules-local-by-default,1.2.0,MIT +postcss-modules-scope,1.1.0,ISC +postcss-modules-values,1.3.0,ISC postcss-normalize-charset,1.1.1,MIT postcss-normalize-url,3.0.8,MIT postcss-ordered-values,2.2.3,MIT @@ -835,16 +867,16 @@ postcss-unique-selectors,2.0.2,MIT postcss-value-parser,3.3.0,MIT postcss-zindex,2.2.0,MIT prelude-ls,1.1.2,MIT -premailer,1.8.6,New BSD -premailer-rails,1.9.2,MIT +premailer,1.10.4,New BSD +premailer-rails,1.9.7,MIT prepend-http,1.0.4,MIT preserve,0.2.0,MIT prismjs,1.6.0,MIT private,0.1.7,MIT -process,0.11.9,MIT +process,0.11.10,MIT process-nextick-args,1.0.7,MIT progress,1.1.8,MIT -prometheus-client-mmap,0.7.0.beta5,Apache 2.0 +prometheus-client-mmap,0.7.0.beta8,Apache 2.0 proto-list,1.2.4,ISC proxy-addr,1.1.4,MIT prr,0.0.0,MIT @@ -855,8 +887,8 @@ punycode,1.4.1,MIT pyu-ruby-sasl,0.0.3.3,MIT q,1.5.0,MIT qjobs,1.1.5,MIT -qs,6.3.0,New BSD -query-string,4.3.2,MIT +qs,6.4.0,New BSD +query-string,4.3.4,MIT querystring,0.2.0,MIT querystring-es3,0.2.1,MIT querystringify,0.0.4,MIT @@ -872,24 +904,25 @@ rails,4.2.8,MIT rails-deprecated_sanitizer,1.0.3,MIT rails-dom-testing,1.0.8,MIT rails-html-sanitizer,1.0.3,MIT +rails-i18n,4.0.9,MIT railties,4.2.8,MIT -rainbow,2.1.0,MIT -raindrops,0.17.0,LGPL-2.1+ +rainbow,2.2.2,MIT +raindrops,0.18.0,LGPL-2.1+ rake,10.5.0,MIT -randomatic,1.1.6,MIT -randombytes,2.0.3,MIT +randomatic,1.1.7,MIT +randombytes,2.0.5,MIT range-parser,1.2.0,MIT raphael,2.2.7,MIT -raven-js,3.14.0,Simplified BSD +raven-js,3.16.1,Simplified BSD raw-body,2.2.0,MIT raw-loader,0.5.1,MIT -rc,1.1.6,(BSD-2-Clause OR MIT OR Apache-2.0) +rc,1.2.1,(BSD-2-Clause OR MIT OR Apache-2.0) rdoc,4.2.2,ruby react-dev-utils,0.5.2,New BSD read-all-stream,3.1.0,MIT read-pkg,1.1.0,MIT read-pkg-up,1.0.1,MIT -readable-stream,2.2.2,MIT +readable-stream,2.3.3,MIT readdirp,2.1.0,MIT readline2,1.0.1,MIT recaptcha,3.0.0,MIT @@ -897,6 +930,7 @@ rechoir,0.6.2,MIT recursive-open-struct,1.0.0,MIT recursive-readdir,2.1.1,MIT redcarpet,3.4.0,MIT +redent,1.0.0,MIT redis,3.3.3,MIT redis-actionpack,5.0.1,MIT redis-activesupport,5.0.1,MIT @@ -907,33 +941,34 @@ redis-store,1.2.0,MIT reduce-css-calc,1.3.0,MIT reduce-function-call,1.0.2,MIT regenerate,1.3.2,MIT -regenerator-runtime,0.10.1,MIT -regenerator-transform,0.9.8,BSD +regenerator-runtime,0.10.5,MIT +regenerator-transform,0.9.11,BSD regex-cache,0.4.3,MIT -regexpu-core,2.0.0,MIT +regexpu-core,"",unknown registry-url,3.1.0,MIT regjsgen,0.2.0,MIT regjsparser,0.1.5,BSD +remove-trailing-separator,1.0.2,ISC repeat-element,1.1.2,MIT repeat-string,1.6.1,MIT repeating,2.0.1,MIT -request,2.79.0,Apache 2.0 +request,2.81.0,Apache 2.0 request_store,1.3.1,MIT require-directory,2.1.1,MIT require-from-string,1.2.1,MIT require-main-filename,1.0.1,ISC require-uncached,1.0.3,MIT requires-port,1.0.0,MIT -resolve,1.2.0,MIT +resolve,1.3.3,MIT resolve-from,1.0.1,MIT responders,2.3.0,MIT rest-client,2.0.0,MIT restore-cursor,1.0.1,MIT retriable,1.4.1,MIT right-align,0.1.3,MIT -rimraf,2.5.4,ISC +rimraf,2.6.1,ISC rinku,2.0.0,ISC -ripemd160,1.0.1,New BSD +ripemd160,2.0.1,MIT rotp,2.1.2,MIT rouge,2.1.0,MIT rqrcode,0.7.0,MIT @@ -941,40 +976,42 @@ rqrcode-rails3,0.1.7,MIT ruby-fogbugz,0.2.1,MIT ruby-prof,0.16.2,Simplified BSD ruby-saml,1.4.1,MIT -ruby_parser,3.8.4,MIT +ruby_parser,3.9.0,MIT rubyntlm,0.5.2,MIT rubypants,0.2.0,BSD rufus-scheduler,3.4.0,MIT rugged,0.25.1.1,MIT run-async,0.1.0,MIT rx-lite,3.1.2,Apache 2.0 -safe-buffer,5.0.1,MIT +safe-buffer,5.1.1,MIT safe_yaml,1.0.4,MIT sanitize,2.1.0,MIT sass,3.4.22,MIT sass-rails,5.0.6,MIT sawyer,0.8.1,MIT -sax,1.2.2,ISC +sax,1.2.4,ISC +schema-utils,0.3.0,MIT securecompare,1.0.0,MIT seed-fu,2.3.6,MIT select,1.1.2,MIT select-hose,2.0.0,MIT select2,3.5.2-browserify,unknown select2-rails,3.5.9.3,MIT +selfsigned,1.9.1,MIT semver,5.3.0,ISC semver-diff,2.1.0,MIT send,0.15.3,MIT -sentry-raven,2.4.0,Apache 2.0 -serve-index,1.8.0,MIT +sentry-raven,2.5.3,Apache 2.0 +serve-index,1.9.0,MIT serve-static,1.12.3,MIT set-blocking,2.0.0,ISC set-immediate-shim,1.0.1,MIT setimmediate,1.0.5,MIT setprototypeof,1.0.3,ISC settingslogic,2.0.9,MIT -sexp_processor,4.8.0,MIT +sexp_processor,4.9.0,MIT sha.js,2.4.8,MIT -shelljs,0.7.6,New BSD +shelljs,0.7.8,New BSD sidekiq,5.0.0,LGPL sidekiq-cron,0.6.0,MIT sidekiq-limit_fetch,3.4.0,MIT @@ -995,18 +1032,18 @@ sockjs-client,1.0.1,MIT sort-keys,1.1.2,MIT source-list-map,0.1.8,MIT source-map,0.5.6,New BSD -source-map-support,0.4.11,MIT +source-map-support,0.4.15,MIT spdx-correct,1.0.2,Apache 2.0 spdx-expression-parse,1.0.4,(MIT AND CC-BY-3.0) spdx-license-ids,1.2.2,Unlicense -spdy,3.4.4,MIT -spdy-transport,2.0.18,MIT +spdy,3.4.7,MIT +spdy-transport,2.0.20,MIT split,0.3.3,MIT sprintf-js,1.0.3,New BSD sprockets,3.7.1,MIT sprockets-rails,3.2.0,MIT sql.js,0.4.0,MIT -sshpk,1.10.2,MIT +sshpk,1.13.1,MIT state_machines,0.4.0,MIT state_machines-activemodel,0.4.0,MIT state_machines-activerecord,0.4.0,MIT @@ -1014,7 +1051,7 @@ stats-webpack-plugin,0.4.3,MIT statuses,1.3.1,MIT stream-browserify,2.0.1,MIT stream-combiner,0.0.4,MIT -stream-http,2.6.3,MIT +stream-http,2.7.2,MIT stream-shift,1.0.0,MIT strict-uri-encode,1.1.0,MIT string-length,1.0.1,MIT @@ -1024,44 +1061,47 @@ stringex,2.5.2,MIT stringstream,0.0.5,MIT strip-ansi,3.0.1,MIT strip-bom,2.0.0,MIT -strip-json-comments,1.0.4,MIT -supports-color,0.2.0,MIT +strip-indent,1.0.1,MIT +strip-json-comments,2.0.1,MIT +supports-color,4.2.0,MIT svgo,0.7.2,MIT sys-filesystem,1.1.6,Artistic 2.0 table,3.8.3,New BSD tapable,0.2.6,MIT tar,2.2.1,ISC -tar-pack,3.3.0,Simplified BSD +tar-pack,3.4.0,Simplified BSD temple,0.7.7,MIT -test-exclude,4.0.0,ISC +test-exclude,4.1.1,ISC text,1.3.1,MIT text-table,0.2.0,MIT thor,0.19.4,MIT thread_safe,0.3.6,Apache 2.0 three,0.84.0,MIT three-orbit-controls,82.1.0,MIT -three-stl-loader,1.0.4,MIT +three-stl-loader,1.0.5,MIT through,2.3.8,MIT +thunky,0.1.0,unknown tilt,2.0.6,MIT timeago.js,2.0.5,MIT timed-out,2.0.0,MIT timers-browserify,2.0.2,MIT timfel-krb5-auth,0.8.3,LGPL -tiny-emitter,1.1.0,MIT +tiny-emitter,2.0.1,MIT tmp,0.0.31,MIT to-array,0.1.4,MIT to-arraybuffer,1.0.1,MIT -to-fast-properties,1.0.2,MIT +to-fast-properties,1.0.3,MIT toml-rb,0.3.15,MIT tool,0.2.3,MIT touch,1.0.0,ISC tough-cookie,2.3.2,New BSD traverse,0.6.6,MIT +trim-newlines,1.0.0,MIT trim-right,1.0.1,MIT truncato,0.7.8,MIT tryit,1.0.3,MIT tty-browserify,0.0.0,MIT -tunnel-agent,0.4.3,Apache 2.0 +tunnel-agent,0.6.0,Apache 2.0 tweetnacl,0.14.5,Unlicense type-check,0.3.2,MIT type-is,1.6.15,MIT @@ -1069,7 +1109,7 @@ typedarray,0.0.6,MIT tzinfo,1.2.2,MIT u2f,0.2.1,MIT uglifier,2.7.2,MIT -uglify-js,2.8.27,Simplified BSD +uglify-js,2.8.29,Simplified BSD uglify-to-browserify,1.0.2,MIT uid-number,0.0.6,ISC ultron,1.1.0,MIT @@ -1087,15 +1127,15 @@ uniqs,2.0.0,MIT unpipe,1.0.0,MIT update-notifier,0.5.0,Simplified BSD url,0.11.0,MIT -url-loader,0.5.8,MIT +url-loader,0.5.9,MIT url-parse,1.0.5,MIT url_safe_base64,0.2.2,MIT user-home,2.0.0,MIT -useragent,2.1.13,MIT +useragent,2.2.0,MIT util,0.10.3,MIT util-deprecate,1.0.2,MIT utils-merge,1.0.0,MIT -uuid,3.0.1,MIT +uuid,3.1.0,MIT validate-npm-package-license,3.0.1,Apache 2.0 validates_hostname,1.0.6,MIT vary,1.1.1,MIT @@ -1107,36 +1147,36 @@ visibilityjs,1.2.4,MIT vm-browserify,0.0.4,MIT vmstat,2.3.0,MIT void-elements,2.0.1,MIT -vue,2.2.6,MIT -vue-hot-reload-api,2.0.11,MIT +vue,2.3.4,MIT +vue-hot-reload-api,2.1.0,MIT vue-loader,11.3.4,MIT vue-resource,0.9.3,MIT vue-style-loader,2.0.5,MIT -vue-template-compiler,2.2.6,MIT -vue-template-es2015-compiler,1.5.1,MIT +vue-template-compiler,2.3.4,MIT +vue-template-es2015-compiler,1.5.3,MIT warden,1.2.6,MIT watchpack,1.3.1,MIT wbuf,1.7.2,MIT webpack,2.6.1,MIT webpack-bundle-analyzer,2.8.2,MIT -webpack-dev-middleware,1.10.0,MIT -webpack-dev-server,2.4.2,MIT +webpack-dev-middleware,1.11.0,MIT +webpack-dev-server,2.5.1,MIT webpack-rails,0.9.10,MIT -webpack-sources,0.1.4,MIT +webpack-sources,0.1.5,MIT websocket-driver,0.6.5,MIT websocket-extensions,0.1.1,MIT whet.extend,0.9.9,MIT -which,1.2.12,ISC +which,1.2.14,ISC which-module,1.0.0,ISC -wide-align,1.1.0,ISC +wide-align,1.1.2,ISC wikicloth,0.8.1,MIT window-size,0.1.0,MIT wordwrap,0.0.2,MIT/X11 -worker-loader,0.8.0,MIT +worker-loader,0.8.1,MIT wrap-ansi,2.1.0,MIT wrappy,1.0.2,ISC write,0.2.1,MIT -write-file-atomic,1.3.1,ISC +write-file-atomic,1.3.4,ISC ws,2.3.1,MIT wtf-8,1.0.0,MIT xdg-basedir,2.0.0,MIT diff --git a/yarn.lock b/yarn.lock index b04eebe60af..98da6a984d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1575,6 +1575,12 @@ deckar01-task_list@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/deckar01-task_list/-/deckar01-task_list-2.0.0.tgz#7f7a595430d21b3036ed5dfbf97d6b65de18e2c9" +decompress-response@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" + dependencies: + mimic-response "^1.0.0" + deep-extend@~0.4.0: version "0.4.1" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.1.tgz#efe4113d08085f4e6f9687759810f807469e2253" @@ -1712,6 +1718,10 @@ dropzone@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/dropzone/-/dropzone-4.2.0.tgz#fbe7acbb9918e0706489072ef663effeef8a79f3" +duplexer3@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" + duplexer@^0.1.1, duplexer@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" @@ -2445,6 +2455,10 @@ get-caller-file@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" +get-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" + getpass@^0.1.1: version "0.1.6" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.6.tgz#283ffd9fc1256840875311c1b60e8c40187110e6" @@ -2521,6 +2535,25 @@ got@^3.2.0: read-all-stream "^3.0.0" timed-out "^2.0.0" +got@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/got/-/got-7.1.0.tgz#05450fd84094e6bbea56f451a43a9c289166385a" + dependencies: + decompress-response "^3.2.0" + duplexer3 "^0.1.4" + get-stream "^3.0.0" + is-plain-obj "^1.1.0" + is-retry-allowed "^1.0.0" + is-stream "^1.0.0" + isurl "^1.0.0-alpha5" + lowercase-keys "^1.0.0" + p-cancelable "^0.3.0" + p-timeout "^1.1.1" + safe-buffer "^5.0.1" + timed-out "^4.0.0" + url-parse-lax "^1.0.0" + url-to-options "^1.0.1" + graceful-fs@^4.1.11, graceful-fs@^4.1.2: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" @@ -2578,6 +2611,16 @@ has-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" +has-symbol-support-x@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.3.0.tgz#588bd6927eaa0e296afae24160659167fc2be4f8" + +has-to-string-tag-x@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.3.0.tgz#78e3d98c3c0ec9413e970eb8d766249a1e13058f" + dependencies: + has-symbol-support-x "^1.3.0" + has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" @@ -2902,6 +2945,10 @@ is-number@^2.0.2, is-number@^2.1.0: dependencies: kind-of "^3.0.2" +is-object@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470" + is-path-cwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" @@ -2918,7 +2965,7 @@ is-path-inside@^1.0.0: dependencies: path-is-inside "^1.0.1" -is-plain-obj@^1.0.0: +is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" @@ -2950,6 +2997,10 @@ is-resolvable@^1.0.0: dependencies: tryit "^1.0.1" +is-retry-allowed@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34" + is-stream@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" @@ -3087,6 +3138,13 @@ istanbul@^0.4.5: which "^1.1.1" wordwrap "^1.0.0" +isurl@^1.0.0-alpha5: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isurl/-/isurl-1.0.0.tgz#b27f4f49f3cdaa3ea44a0a5b7f3462e6edc39d67" + dependencies: + has-to-string-tag-x "^1.2.0" + is-object "^1.0.1" + jasmine-core@^2.6.3: version "2.6.3" resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.6.3.tgz#45072950e4a42b1e322fe55c001100a465d77815" @@ -3633,6 +3691,10 @@ mime@1.3.4, mime@1.3.x, mime@^1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" +mimic-response@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.0.tgz#df3d3652a73fded6b9b0b24146e6fd052353458e" + minimalistic-assert@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz#702be2dda6b37f4836bcb3f5db56641b64a1d3d3" @@ -3981,6 +4043,14 @@ osenv@^0.1.0: os-homedir "^1.0.0" os-tmpdir "^1.0.0" +p-cancelable@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.3.0.tgz#b9e123800bcebb7ac13a479be195b507b98d30fa" + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + p-limit@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.1.0.tgz#b07ff2d9a5d88bec806035895a2bab66a27988bc" @@ -3991,6 +4061,12 @@ p-locate@^2.0.0: dependencies: p-limit "^1.1.0" +p-timeout@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-1.2.0.tgz#9820f99434c5817868b4f34809ee5291660d5b6c" + dependencies: + p-finally "^1.0.0" + package-json@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/package-json/-/package-json-1.2.0.tgz#c8ecac094227cdf76a316874ed05e27cc939a0e0" @@ -4419,7 +4495,7 @@ prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" -prepend-http@^1.0.0: +prepend-http@^1.0.0, prepend-http@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" @@ -5384,6 +5460,10 @@ timed-out@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-2.0.0.tgz#f38b0ae81d3747d628001f41dafc652ace671c0a" +timed-out@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" + timers-browserify@^1.4.2: version "1.4.2" resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-1.4.2.tgz#c9c58b575be8407375cb5e2462dacee74359f41d" @@ -5545,6 +5625,12 @@ url-loader@^0.5.8: loader-utils "^1.0.2" mime "1.3.x" +url-parse-lax@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73" + dependencies: + prepend-http "^1.0.1" + url-parse@1.0.x: version "1.0.5" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.0.5.tgz#0854860422afdcfefeb6c965c662d4800169927b" @@ -5559,6 +5645,10 @@ url-parse@^1.0.1, url-parse@^1.1.1: querystringify "0.0.x" requires-port "1.0.x" +url-to-options@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" + url@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" @@ -5657,9 +5747,11 @@ vue-loader@^11.3.4: vue-style-loader "^2.0.0" vue-template-es2015-compiler "^1.2.2" -vue-resource@^0.9.3: - version "0.9.3" - resolved "https://registry.yarnpkg.com/vue-resource/-/vue-resource-0.9.3.tgz#ab46e1c44ea219142dcc28ae4043b3b04c80959d" +vue-resource@^1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/vue-resource/-/vue-resource-1.3.4.tgz#9fc0bdf6a2f5cab430129fc99d347b3deae7b099" + dependencies: + got "^7.0.0" vue-style-loader@^2.0.0: version "2.0.5" |