Commit 6412c4c5 authored by Sean McGivern's avatar Sean McGivern

Merge branch 'master' into stuartnelson3/gitlab-ce-stn/issue-due-email

parents ddb23d3b fa1eabe8
...@@ -8,3 +8,4 @@ lib/gitlab/redis/*.rb ...@@ -8,3 +8,4 @@ lib/gitlab/redis/*.rb
lib/gitlab/gitaly_client/operation_service.rb lib/gitlab/gitaly_client/operation_service.rb
lib/gitlab/background_migration/* lib/gitlab/background_migration/*
app/models/project_services/kubernetes_service.rb app/models/project_services/kubernetes_service.rb
lib/gitlab/workhorse.rb
...@@ -50,6 +50,7 @@ eslint-report.html ...@@ -50,6 +50,7 @@ eslint-report.html
/db/data.yml /db/data.yml
/doc/code/* /doc/code/*
/dump.rdb /dump.rdb
/jsconfig.json
/log/*.log* /log/*.log*
/node_modules/ /node_modules/
/nohup.out /nohup.out
......
See the general Documentation guidelines http://docs.gitlab.com/ce/development/doc_styleguide.html <!--See the general Documentation guidelines https://docs.gitlab.com/ce/development/writing_documentation.html -->
## What does this MR do? ## What does this MR do?
(briefly describe what this MR is about) <!-- Briefly describe what this MR is about -->
## Related issues
<!-- Mention the issue(s) this MR closes or is related to -->
Closes
## Moving docs to a new location? ## Moving docs to a new location?
See the guidelines: http://docs.gitlab.com/ce/development/doc_styleguide.html#changing-document-location Read the guidelines:
https://docs.gitlab.com/ce/development/writing_documentation.html#changing-document-location
- [ ] Make sure the old link is not removed and has its contents replaced with a link to the new location. - [ ] Make sure the old link is not removed and has its contents replaced with
a link to the new location.
- [ ] Make sure internal links pointing to the document in question are not broken. - [ ] Make sure internal links pointing to the document in question are not broken.
- [ ] Search and replace any links referring to old docs in GitLab Rails app, specifically under the `app/views/` directory. - [ ] Search and replace any links referring to old docs in GitLab Rails app,
- [ ] Make sure to add [`redirect_from`](https://docs.gitlab.com/ee/development/doc_styleguide.html#redirections-for-pages-with-disqus-comments) to the new document if there are any Disqus comments on the old document thread. specifically under the `app/views/` and `ee/app/views` (for GitLab EE) directories.
- [ ] If working on CE, submit an MR to EE with the changes as well. - [ ] Make sure to add [`redirect_from`](https://docs.gitlab.com/ce/development/writing_documentation.html#redirections-for-pages-with-disqus-comments)
to the new document if there are any Disqus comments on the old document thread.
- [ ] If working on CE and the `ee-compat-check` jobs fails, submit an MR to EE
with the changes as well (https://docs.gitlab.com/ce/development/writing_documentation.html#cherry-picking-from-ce-to-ee).
- [ ] Ping one of the technical writers for review. - [ ] Ping one of the technical writers for review.
/label ~Documentation
...@@ -2,6 +2,34 @@ ...@@ -2,6 +2,34 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 10.6.2 (2018-03-29)
### Fixed (2 changes, 1 of them is from the community)
- Don't capture trailing punctuation when autolinking. !17965
- Cloning a repository over HTTPS with LDAP credentials causes a HTTP 401 Access denied. (Horatiu Eugen Vlad)
## 10.6.1 (2018-03-27)
### Security (1 change)
- Bump rails-html-sanitizer to 1.0.4.
### Fixed (2 changes)
- Prevent auto-retry AccessDenied error from stopping transition to failed. !17862
- Fix 500 error when trying to resolve non-ASCII conflicts in the editor. !17962
### Performance (1 change)
- Add indexes for user activity queries. !17890
### Other (1 change)
- Add documentation for runner IP address (#44232). !17837
## 10.6.0 (2018-03-22) ## 10.6.0 (2018-03-22)
### Security (4 changes) ### Security (4 changes)
...@@ -168,13 +196,17 @@ entry. ...@@ -168,13 +196,17 @@ entry.
- Add one group board to Libre. - Add one group board to Libre.
- Add support for filtering by source and target branch to merge requests API. - Add support for filtering by source and target branch to merge requests API.
### Other (14 changes, 3 of them are from the community) ### Other (18 changes, 7 of them are from the community)
- Group MRs on issue page by project and namespace. !8494 (Jeff Stubler)
- Make oauth provider login generic. !8809 (Horatiu Eugen Vlad)
- Add email button to new issue by email. !10942 (Islam Wazery)
- Update vue component naming guidelines. !17018 (George Tsiolis) - Update vue component naming guidelines. !17018 (George Tsiolis)
- Added new design for promotion modals. !17197 - Added new design for promotion modals. !17197
- Update to github-linguist 5.3.x. !17241 (Ken Ding) - Update to github-linguist 5.3.x. !17241 (Ken Ding)
- update toml-rb to 1.0.0. !17259 (Ken Ding) - update toml-rb to 1.0.0. !17259 (Ken Ding)
- Keep track of projects a user interacted with. !17327 - Keep track of projects a user interacted with. !17327
- Moved o_auth/saml/ldap modules under gitlab/auth. !17359 (Horatiu Eugen Vlad)
- Enables eslint in codeclimate job. !17392 - Enables eslint in codeclimate job. !17392
- Port Labels Select dropdown to Vue. !17411 - Port Labels Select dropdown to Vue. !17411
- Add NOT NULL constraint to projects.namespace_id. !17448 - Add NOT NULL constraint to projects.namespace_id. !17448
......
...@@ -28,7 +28,7 @@ gem 'default_value_for', gem_versions['default_value_for'] ...@@ -28,7 +28,7 @@ gem 'default_value_for', gem_versions['default_value_for']
gem 'mysql2', '~> 0.4.10', group: :mysql gem 'mysql2', '~> 0.4.10', group: :mysql
gem 'pg', '~> 0.18.2', group: :postgres gem 'pg', '~> 0.18.2', group: :postgres
gem 'rugged', '~> 0.26.0' gem 'rugged', '~> 0.27'
gem 'grape-route-helpers', '~> 2.1.0' gem 'grape-route-helpers', '~> 2.1.0'
gem 'faraday', '~> 0.12' gem 'faraday', '~> 0.12'
...@@ -44,14 +44,15 @@ gem 'omniauth-cas3', '~> 1.1.4' ...@@ -44,14 +44,15 @@ gem 'omniauth-cas3', '~> 1.1.4'
gem 'omniauth-facebook', '~> 4.0.0' gem 'omniauth-facebook', '~> 4.0.0'
gem 'omniauth-github', '~> 1.1.1' gem 'omniauth-github', '~> 1.1.1'
gem 'omniauth-gitlab', '~> 1.0.2' gem 'omniauth-gitlab', '~> 1.0.2'
gem 'omniauth-google-oauth2', '~> 0.5.2' gem 'omniauth-google-oauth2', '~> 0.5.3'
gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
gem 'omniauth-oauth2-generic', '~> 0.2.2' gem 'omniauth-oauth2-generic', '~> 0.2.2'
gem 'omniauth-saml', '~> 1.10' gem 'omniauth-saml', '~> 1.10'
gem 'omniauth-shibboleth', '~> 1.2.0' gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-twitter', '~> 1.2.0' gem 'omniauth-twitter', '~> 1.4'
gem 'omniauth_crowd', '~> 2.2.0' gem 'omniauth_crowd', '~> 2.2.0'
gem 'omniauth-authentiq', '~> 0.3.1' gem 'omniauth-authentiq', '~> 0.3.1'
gem 'omniauth-jwt', '~> 0.0.2'
gem 'rack-oauth2', '~> 1.2.1' gem 'rack-oauth2', '~> 1.2.1'
gem 'jwt', '~> 1.5.6' gem 'jwt', '~> 1.5.6'
...@@ -117,9 +118,9 @@ gem 'carrierwave', '~> 1.2' ...@@ -117,9 +118,9 @@ gem 'carrierwave', '~> 1.2'
gem 'dropzonejs-rails', '~> 0.7.1' gem 'dropzonejs-rails', '~> 0.7.1'
# for backups # for backups
gem 'fog-aws', '~> 2.0' gem 'fog-aws', '~> 2.0.1'
gem 'fog-core', '~> 1.44' gem 'fog-core', '~> 1.44'
gem 'fog-google', '~> 0.5' gem 'fog-google', '~> 1.3.3'
gem 'fog-local', '~> 0.3' gem 'fog-local', '~> 0.3'
gem 'fog-openstack', '~> 0.1' gem 'fog-openstack', '~> 0.1'
gem 'fog-rackspace', '~> 0.1.1' gem 'fog-rackspace', '~> 0.1.1'
...@@ -145,8 +146,8 @@ gem 'rdoc', '~> 4.2' ...@@ -145,8 +146,8 @@ gem 'rdoc', '~> 4.2'
gem 'org-ruby', '~> 0.9.12' gem 'org-ruby', '~> 0.9.12'
gem 'creole', '~> 0.5.0' gem 'creole', '~> 0.5.0'
gem 'wikicloth', '0.8.1' gem 'wikicloth', '0.8.1'
gem 'asciidoctor', '~> 1.5.2' gem 'asciidoctor', '~> 1.5.6'
gem 'asciidoctor-plantuml', '0.0.7' gem 'asciidoctor-plantuml', '0.0.8'
gem 'rouge', '~> 2.0' gem 'rouge', '~> 2.0'
gem 'truncato', '~> 0.7.9' gem 'truncato', '~> 0.7.9'
gem 'bootstrap_form', '~> 2.7.0' gem 'bootstrap_form', '~> 2.7.0'
...@@ -162,7 +163,7 @@ group :unicorn do ...@@ -162,7 +163,7 @@ group :unicorn do
end end
# State machine # State machine
gem 'state_machines-activerecord', '~> 0.4.0' gem 'state_machines-activerecord', '~> 0.5.1'
# Issue tags # Issue tags
gem 'acts-as-taggable-on', '~> 5.0' gem 'acts-as-taggable-on', '~> 5.0'
...@@ -375,6 +376,8 @@ group :development, :test do ...@@ -375,6 +376,8 @@ group :development, :test do
gem 'stackprof', '~> 0.2.10', require: false gem 'stackprof', '~> 0.2.10', require: false
gem 'simple_po_parser', '~> 1.1.2', require: false gem 'simple_po_parser', '~> 1.1.2', require: false
gem 'timecop', '~> 0.8.0'
end end
group :test do group :test do
...@@ -384,7 +387,6 @@ group :test do ...@@ -384,7 +387,6 @@ group :test do
gem 'webmock', '~> 2.3.2' gem 'webmock', '~> 2.3.2'
gem 'test_after_commit', '~> 1.1' gem 'test_after_commit', '~> 1.1'
gem 'sham_rack', '~> 1.3.6' gem 'sham_rack', '~> 1.3.6'
gem 'timecop', '~> 0.8.0'
gem 'concurrent-ruby', '~> 1.0.5' gem 'concurrent-ruby', '~> 1.0.5'
gem 'test-prof', '~> 0.2.5' gem 'test-prof', '~> 0.2.5'
end end
...@@ -420,7 +422,7 @@ group :ed25519 do ...@@ -420,7 +422,7 @@ group :ed25519 do
end end
# Gitaly GRPC client # Gitaly GRPC client
gem 'gitaly-proto', '~> 0.88.0', require: 'gitaly' gem 'gitaly-proto', '~> 0.91.0', require: 'gitaly'
gem 'grpc', '~> 1.10.0' gem 'grpc', '~> 1.10.0'
# Locked until https://github.com/google/protobuf/issues/4210 is closed # Locked until https://github.com/google/protobuf/issues/4210 is closed
......
...@@ -56,8 +56,8 @@ GEM ...@@ -56,8 +56,8 @@ GEM
faraday_middleware (~> 0.9) faraday_middleware (~> 0.9)
faraday_middleware-multi_json (~> 0.0) faraday_middleware-multi_json (~> 0.0)
oauth2 (~> 1.0) oauth2 (~> 1.0)
asciidoctor (1.5.3) asciidoctor (1.5.6.2)
asciidoctor-plantuml (0.0.7) asciidoctor-plantuml (0.0.8)
asciidoctor (~> 1.5) asciidoctor (~> 1.5)
asset_sync (2.2.0) asset_sync (2.2.0)
activemodel (>= 4.1.0) activemodel (>= 4.1.0)
...@@ -244,10 +244,11 @@ GEM ...@@ -244,10 +244,11 @@ GEM
builder builder
excon (~> 0.58) excon (~> 0.58)
formatador (~> 0.2) formatador (~> 0.2)
fog-google (0.5.3) fog-google (1.3.3)
fog-core fog-core
fog-json fog-json
fog-xml fog-xml
google-api-client (~> 0.19.1)
fog-json (1.0.2) fog-json (1.0.2)
fog-core (~> 1.0) fog-core (~> 1.0)
multi_json (~> 1.10) multi_json (~> 1.10)
...@@ -289,7 +290,7 @@ GEM ...@@ -289,7 +290,7 @@ GEM
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
rails (>= 3.2.0) rails (>= 3.2.0)
gherkin-ruby (0.3.2) gherkin-ruby (0.3.2)
gitaly-proto (0.88.0) gitaly-proto (0.91.0)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
grpc (~> 1.0) grpc (~> 1.0)
github-linguist (5.3.3) github-linguist (5.3.3)
...@@ -357,7 +358,7 @@ GEM ...@@ -357,7 +358,7 @@ GEM
signet (~> 0.7) signet (~> 0.7)
gpgme (2.0.13) gpgme (2.0.13)
mini_portile2 (~> 2.1) mini_portile2 (~> 2.1)
grape (1.0.0) grape (1.0.2)
activesupport activesupport
builder builder
mustermann-grape (~> 1.0.0) mustermann-grape (~> 1.0.0)
...@@ -507,7 +508,7 @@ GEM ...@@ -507,7 +508,7 @@ GEM
multi_json (1.13.1) multi_json (1.13.1)
multi_xml (0.6.0) multi_xml (0.6.0)
multipart-post (2.0.0) multipart-post (2.0.0)
mustermann (1.0.0) mustermann (1.0.2)
mustermann-grape (1.0.0) mustermann-grape (1.0.0)
mustermann (~> 1.0.0) mustermann (~> 1.0.0)
mysql2 (0.4.10) mysql2 (0.4.10)
...@@ -517,7 +518,7 @@ GEM ...@@ -517,7 +518,7 @@ GEM
nokogiri (1.8.2) nokogiri (1.8.2)
mini_portile2 (~> 2.3.0) mini_portile2 (~> 2.3.0)
numerizer (0.1.1) numerizer (0.1.1)
oauth (0.5.1) oauth (0.5.4)
oauth2 (1.4.0) oauth2 (1.4.0)
faraday (>= 0.8, < 0.13) faraday (>= 0.8, < 0.13)
jwt (~> 1.0) jwt (~> 1.0)
...@@ -549,11 +550,13 @@ GEM ...@@ -549,11 +550,13 @@ GEM
omniauth-gitlab (1.0.2) omniauth-gitlab (1.0.2)
omniauth (~> 1.0) omniauth (~> 1.0)
omniauth-oauth2 (~> 1.0) omniauth-oauth2 (~> 1.0)
omniauth-google-oauth2 (0.5.2) omniauth-google-oauth2 (0.5.3)
jwt (~> 1.5) jwt (>= 1.5)
multi_json (~> 1.3)
omniauth (>= 1.1.1) omniauth (>= 1.1.1)
omniauth-oauth2 (>= 1.3.1) omniauth-oauth2 (>= 1.5)
omniauth-jwt (0.0.2)
jwt
omniauth (~> 1.1)
omniauth-kerberos (0.3.0) omniauth-kerberos (0.3.0)
omniauth-multipassword omniauth-multipassword
timfel-krb5-auth (~> 0.8) timfel-krb5-auth (~> 0.8)
...@@ -562,8 +565,8 @@ GEM ...@@ -562,8 +565,8 @@ GEM
omniauth-oauth (1.1.0) omniauth-oauth (1.1.0)
oauth oauth
omniauth (~> 1.0) omniauth (~> 1.0)
omniauth-oauth2 (1.4.0) omniauth-oauth2 (1.5.0)
oauth2 (~> 1.0) oauth2 (~> 1.1)
omniauth (~> 1.2) omniauth (~> 1.2)
omniauth-oauth2-generic (0.2.2) omniauth-oauth2-generic (0.2.2)
omniauth-oauth2 (~> 1.0) omniauth-oauth2 (~> 1.0)
...@@ -572,9 +575,9 @@ GEM ...@@ -572,9 +575,9 @@ GEM
ruby-saml (~> 1.7) ruby-saml (~> 1.7)
omniauth-shibboleth (1.2.1) omniauth-shibboleth (1.2.1)
omniauth (>= 1.0.0) omniauth (>= 1.0.0)
omniauth-twitter (1.2.1) omniauth-twitter (1.4.0)
json (~> 1.3)
omniauth-oauth (~> 1.1) omniauth-oauth (~> 1.1)
rack
omniauth_crowd (2.2.3) omniauth_crowd (2.2.3)
activesupport activesupport
nokogiri (>= 1.4.4) nokogiri (>= 1.4.4)
...@@ -810,7 +813,7 @@ GEM ...@@ -810,7 +813,7 @@ GEM
rubyzip (1.2.1) rubyzip (1.2.1)
rufus-scheduler (3.4.0) rufus-scheduler (3.4.0)
et-orbi (~> 1.0) et-orbi (~> 1.0)
rugged (0.26.0) rugged (0.27.0)
safe_yaml (1.0.4) safe_yaml (1.0.4)
sanitize (2.1.0) sanitize (2.1.0)
nokogiri (>= 1.4.4) nokogiri (>= 1.4.4)
...@@ -897,13 +900,13 @@ GEM ...@@ -897,13 +900,13 @@ GEM
sqlite3 (1.3.13) sqlite3 (1.3.13)
sshkey (1.9.0) sshkey (1.9.0)
stackprof (0.2.10) stackprof (0.2.10)
state_machines (0.4.0) state_machines (0.5.0)
state_machines-activemodel (0.4.0) state_machines-activemodel (0.5.1)
activemodel (>= 4.1, < 5.1) activemodel (>= 4.1, < 6.0)
state_machines (>= 0.4.0) state_machines (>= 0.5.0)
state_machines-activerecord (0.4.0) state_machines-activerecord (0.5.1)
activerecord (>= 4.1, < 5.1) activerecord (>= 4.1, < 6.0)
state_machines-activemodel (>= 0.3.0) state_machines-activemodel (>= 0.5.0)
stringex (2.7.1) stringex (2.7.1)
sys-filesystem (1.1.6) sys-filesystem (1.1.6)
ffi ffi
...@@ -995,8 +998,8 @@ DEPENDENCIES ...@@ -995,8 +998,8 @@ DEPENDENCIES
akismet (~> 2.0) akismet (~> 2.0)
allocations (~> 1.0) allocations (~> 1.0)
asana (~> 0.6.0) asana (~> 0.6.0)
asciidoctor (~> 1.5.2) asciidoctor (~> 1.5.6)
asciidoctor-plantuml (= 0.0.7) asciidoctor-plantuml (= 0.0.8)
asset_sync (~> 2.2.0) asset_sync (~> 2.2.0)
attr_encrypted (~> 3.0.0) attr_encrypted (~> 3.0.0)
awesome_print (~> 1.2.0) awesome_print (~> 1.2.0)
...@@ -1044,9 +1047,9 @@ DEPENDENCIES ...@@ -1044,9 +1047,9 @@ DEPENDENCIES
flipper-active_record (~> 0.13.0) flipper-active_record (~> 0.13.0)
flipper-active_support_cache_store (~> 0.13.0) flipper-active_support_cache_store (~> 0.13.0)
fog-aliyun (~> 0.2.0) fog-aliyun (~> 0.2.0)
fog-aws (~> 2.0) fog-aws (~> 2.0.1)
fog-core (~> 1.44) fog-core (~> 1.44)
fog-google (~> 0.5) fog-google (~> 1.3.3)
fog-local (~> 0.3) fog-local (~> 0.3)
fog-openstack (~> 0.1) fog-openstack (~> 0.1)
fog-rackspace (~> 0.1.1) fog-rackspace (~> 0.1.1)
...@@ -1058,7 +1061,7 @@ DEPENDENCIES ...@@ -1058,7 +1061,7 @@ DEPENDENCIES
gettext (~> 3.2.2) gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3) gettext_i18n_rails_js (~> 1.3)
gitaly-proto (~> 0.88.0) gitaly-proto (~> 0.91.0)
github-linguist (~> 5.3.3) github-linguist (~> 5.3.3)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.6.2) gitlab-markup (~> 1.6.2)
...@@ -1114,12 +1117,13 @@ DEPENDENCIES ...@@ -1114,12 +1117,13 @@ DEPENDENCIES
omniauth-facebook (~> 4.0.0) omniauth-facebook (~> 4.0.0)
omniauth-github (~> 1.1.1) omniauth-github (~> 1.1.1)
omniauth-gitlab (~> 1.0.2) omniauth-gitlab (~> 1.0.2)
omniauth-google-oauth2 (~> 0.5.2) omniauth-google-oauth2 (~> 0.5.3)
omniauth-jwt (~> 0.0.2)
omniauth-kerberos (~> 0.3.0) omniauth-kerberos (~> 0.3.0)
omniauth-oauth2-generic (~> 0.2.2) omniauth-oauth2-generic (~> 0.2.2)
omniauth-saml (~> 1.10) omniauth-saml (~> 1.10)
omniauth-shibboleth (~> 1.2.0) omniauth-shibboleth (~> 1.2.0)
omniauth-twitter (~> 1.2.0) omniauth-twitter (~> 1.4)
omniauth_crowd (~> 2.2.0) omniauth_crowd (~> 2.2.0)
org-ruby (~> 0.9.12) org-ruby (~> 0.9.12)
peek (~> 1.0.1) peek (~> 1.0.1)
...@@ -1169,7 +1173,7 @@ DEPENDENCIES ...@@ -1169,7 +1173,7 @@ DEPENDENCIES
ruby-prof (~> 0.17.0) ruby-prof (~> 0.17.0)
ruby_parser (~> 3.8) ruby_parser (~> 3.8)
rufus-scheduler (~> 3.4) rufus-scheduler (~> 3.4)
rugged (~> 0.26.0) rugged (~> 0.27)
sanitize (~> 2.0) sanitize (~> 2.0)
sass-rails (~> 5.0.6) sass-rails (~> 5.0.6)
scss_lint (~> 0.56.0) scss_lint (~> 0.56.0)
...@@ -1194,7 +1198,7 @@ DEPENDENCIES ...@@ -1194,7 +1198,7 @@ DEPENDENCIES
sprockets (~> 3.7.0) sprockets (~> 3.7.0)
sshkey (~> 1.9.0) sshkey (~> 1.9.0)
stackprof (~> 0.2.10) stackprof (~> 0.2.10)
state_machines-activerecord (~> 0.4.0) state_machines-activerecord (~> 0.5.1)
sys-filesystem (~> 1.1.6) sys-filesystem (~> 1.1.6)
test-prof (~> 0.2.5) test-prof (~> 0.2.5)
test_after_commit (~> 1.1) test_after_commit (~> 1.1)
......
...@@ -43,8 +43,8 @@ GEM ...@@ -43,8 +43,8 @@ GEM
i18n (~> 0.7) i18n (~> 0.7)
minitest (~> 5.1) minitest (~> 5.1)
tzinfo (~> 1.1) tzinfo (~> 1.1)
acts-as-taggable-on (4.0.0) acts-as-taggable-on (5.0.0)
activerecord (>= 4.0) activerecord (>= 4.2.8)
adamantium (0.2.0) adamantium (0.2.0)
ice_nine (~> 0.11.0) ice_nine (~> 0.11.0)
memoizable (~> 0.4.0) memoizable (~> 0.4.0)
...@@ -178,10 +178,10 @@ GEM ...@@ -178,10 +178,10 @@ GEM
docile (1.1.5) docile (1.1.5)
domain_name (0.5.20170404) domain_name (0.5.20170404)
unf (>= 0.0.5, < 1.0.0) unf (>= 0.0.5, < 1.0.0)
doorkeeper (4.2.6) doorkeeper (4.3.1)
railties (>= 4.2) railties (>= 4.2)
doorkeeper-openid_connect (1.2.0) doorkeeper-openid_connect (1.3.0)
doorkeeper (~> 4.0) doorkeeper (~> 4.3)
json-jwt (~> 1.6) json-jwt (~> 1.6)
dropzonejs-rails (0.7.4) dropzonejs-rails (0.7.4)
rails (> 3.1) rails (> 3.1)
...@@ -220,13 +220,13 @@ GEM ...@@ -220,13 +220,13 @@ GEM
path_expander (~> 1.0) path_expander (~> 1.0)
ruby_parser (~> 3.0) ruby_parser (~> 3.0)
sexp_processor (~> 4.0) sexp_processor (~> 4.0)
flipper (0.11.0) flipper (0.13.0)
flipper-active_record (0.11.0) flipper-active_record (0.13.0)
activerecord (>= 3.2, < 6) activerecord (>= 3.2, < 6)
flipper (~> 0.11.0) flipper (~> 0.13.0)
flipper-active_support_cache_store (0.11.0) flipper-active_support_cache_store (0.13.0)
activesupport (>= 3.2, < 6) activesupport (>= 3.2, < 6)
flipper (~> 0.11.0) flipper (~> 0.13.0)
flowdock (0.7.1) flowdock (0.7.1)
httparty (~> 0.7) httparty (~> 0.7)
multi_json multi_json
...@@ -235,7 +235,7 @@ GEM ...@@ -235,7 +235,7 @@ GEM
fog-json (~> 1.0) fog-json (~> 1.0)
ipaddress (~> 0.8) ipaddress (~> 0.8)
xml-simple (~> 1.1) xml-simple (~> 1.1)
fog-aws (1.4.1) fog-aws (2.0.1)
fog-core (~> 1.38) fog-core (~> 1.38)
fog-json (~> 1.0) fog-json (~> 1.0)
fog-xml (~> 0.1) fog-xml (~> 0.1)
...@@ -267,7 +267,7 @@ GEM ...@@ -267,7 +267,7 @@ GEM
nokogiri (>= 1.5.11, < 2.0.0) nokogiri (>= 1.5.11, < 2.0.0)
font-awesome-rails (4.7.0.3) font-awesome-rails (4.7.0.3)
railties (>= 3.2, < 5.2) railties (>= 3.2, < 5.2)
foreman (0.78.0) foreman (0.84.0)
thor (~> 0.19.1) thor (~> 0.19.1)
formatador (0.2.5) formatador (0.2.5)
fuubar (2.2.0) fuubar (2.2.0)
...@@ -283,7 +283,7 @@ GEM ...@@ -283,7 +283,7 @@ GEM
text (>= 1.3.0) text (>= 1.3.0)
gettext_i18n_rails (1.8.0) gettext_i18n_rails (1.8.0)
fast_gettext (>= 0.9.0) fast_gettext (>= 0.9.0)
gettext_i18n_rails_js (1.2.0) gettext_i18n_rails_js (1.3.0)
gettext (>= 3.0.2) gettext (>= 3.0.2)
gettext_i18n_rails (>= 0.7.1) gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
...@@ -337,9 +337,9 @@ GEM ...@@ -337,9 +337,9 @@ GEM
json json
multi_json multi_json
request_store (>= 1.0) request_store (>= 1.0)
google-api-client (0.13.6) google-api-client (0.19.8)
addressable (~> 2.5, >= 2.5.1) addressable (~> 2.5, >= 2.5.1)
googleauth (~> 0.5) googleauth (>= 0.5, < 0.7.0)
httpclient (>= 2.8.1, < 3.0) httpclient (>= 2.8.1, < 3.0)
mime-types (~> 3.0) mime-types (~> 3.0)
representable (~> 3.0) representable (~> 3.0)
...@@ -404,7 +404,7 @@ GEM ...@@ -404,7 +404,7 @@ GEM
html2text (0.2.1) html2text (0.2.1)
nokogiri (~> 1.6) nokogiri (~> 1.6)
htmlentities (4.3.4) htmlentities (4.3.4)
http (0.9.8) http (2.2.2)
addressable (~> 2.3) addressable (~> 2.3)
http-cookie (~> 1.0) http-cookie (~> 1.0)
http-form_data (~> 1.0.1) http-form_data (~> 1.0.1)
...@@ -427,10 +427,6 @@ GEM ...@@ -427,10 +427,6 @@ GEM
multipart-post multipart-post
oauth (~> 0.5, >= 0.5.0) oauth (~> 0.5, >= 0.5.0)
jquery-atwho-rails (1.3.2) jquery-atwho-rails (1.3.2)
jquery-rails (4.3.1)
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
json (1.8.6) json (1.8.6)
json-jwt (1.9.2) json-jwt (1.9.2)
activesupport activesupport
...@@ -454,13 +450,12 @@ GEM ...@@ -454,13 +450,12 @@ GEM
kaminari-core (= 1.1.1) kaminari-core (= 1.1.1)
kaminari-core (1.1.1) kaminari-core (1.1.1)
kgio (2.11.2) kgio (2.11.2)
knapsack (1.11.1) knapsack (1.16.0)
rake rake
timecop (>= 0.1.0) kubeclient (3.0.0)
kubeclient (2.2.0) http (~> 2.2.2)
http (= 0.9.8) recursive-open-struct (~> 1.0.4)
recursive-open-struct (= 1.0.0) rest-client (~> 2.0)
rest-client
launchy (2.4.3) launchy (2.4.3)
addressable (~> 2.3) addressable (~> 2.3)
letter_opener (1.6.0) letter_opener (1.6.0)
...@@ -477,7 +472,7 @@ GEM ...@@ -477,7 +472,7 @@ GEM
toml (= 0.1.2) toml (= 0.1.2)
with_env (> 1.0) with_env (> 1.0)
xml-simple xml-simple
licensee (8.7.0) licensee (8.9.2)
rugged (~> 0.24) rugged (~> 0.24)
little-plugger (1.1.4) little-plugger (1.1.4)
locale (2.1.2) locale (2.1.2)
...@@ -514,7 +509,7 @@ GEM ...@@ -514,7 +509,7 @@ GEM
mustermann (~> 1.0.0) mustermann (~> 1.0.0)
mysql2 (0.4.10) mysql2 (0.4.10)
net-ldap (0.16.1) net-ldap (0.16.1)
net-ssh (4.1.0) net-ssh (4.2.0)
netrc (0.11.0) netrc (0.11.0)
nio4r (2.2.0) nio4r (2.2.0)
nokogiri (1.8.2) nokogiri (1.8.2)
...@@ -527,11 +522,10 @@ GEM ...@@ -527,11 +522,10 @@ GEM
multi_json (~> 1.3) multi_json (~> 1.3)
multi_xml (~> 0.5) multi_xml (~> 0.5)
rack (>= 1.2, < 3) rack (>= 1.2, < 3)
octokit (4.6.2) octokit (4.8.0)
sawyer (~> 0.8.0, >= 0.5.3) sawyer (~> 0.8.0, >= 0.5.3)
oj (2.17.5) omniauth (1.8.1)
omniauth (1.4.3) hashie (>= 3.4.6, < 3.6.0)
hashie (>= 1.2, < 4)
rack (>= 1.6.2, < 3) rack (>= 1.6.2, < 3)
omniauth-auth0 (1.4.2) omniauth-auth0 (1.4.2)
omniauth-oauth2 (~> 1.1) omniauth-oauth2 (~> 1.1)
...@@ -570,9 +564,9 @@ GEM ...@@ -570,9 +564,9 @@ GEM
omniauth (~> 1.2) omniauth (~> 1.2)
omniauth-oauth2-generic (0.2.4) omniauth-oauth2-generic (0.2.4)
omniauth-oauth2 (~> 1.0) omniauth-oauth2 (~> 1.0)
omniauth-saml (1.7.0) omniauth-saml (1.10.0)
omniauth (~> 1.3) omniauth (~> 1.3, >= 1.3.2)
ruby-saml (~> 1.4) ruby-saml (~> 1.7)
omniauth-shibboleth (1.2.1) omniauth-shibboleth (1.2.1)
omniauth (>= 1.0.0) omniauth (>= 1.0.0)
omniauth-twitter (1.2.1) omniauth-twitter (1.2.1)
...@@ -598,8 +592,6 @@ GEM ...@@ -598,8 +592,6 @@ GEM
railties (>= 4.0.0) railties (>= 4.0.0)
peek-gc (0.0.2) peek-gc (0.0.2)
peek peek
peek-host (1.0.0)
peek
peek-mysql2 (1.1.0) peek-mysql2 (1.1.0)
atomic (>= 1.0.0) atomic (>= 1.0.0)
mysql2 mysql2
...@@ -713,7 +705,7 @@ GEM ...@@ -713,7 +705,7 @@ GEM
re2 (1.1.1) re2 (1.1.1)
recaptcha (3.4.0) recaptcha (3.4.0)
json json
recursive-open-struct (1.0.0) recursive-open-struct (1.0.5)
redcarpet (3.4.0) redcarpet (3.4.0)
redis (3.3.5) redis (3.3.5)
redis-actionpack (5.0.2) redis-actionpack (5.0.2)
...@@ -805,7 +797,7 @@ GEM ...@@ -805,7 +797,7 @@ GEM
i18n i18n
ruby-fogbugz (0.2.1) ruby-fogbugz (0.2.1)
crack (~> 0.4) crack (~> 0.4)
ruby-prof (0.16.2) ruby-prof (0.17.0)
ruby-progressbar (1.9.0) ruby-progressbar (1.9.0)
ruby-saml (1.7.2) ruby-saml (1.7.2)
nokogiri (>= 1.5.10) nokogiri (>= 1.5.10)
...@@ -846,7 +838,7 @@ GEM ...@@ -846,7 +838,7 @@ GEM
selenium-webdriver (3.11.0) selenium-webdriver (3.11.0)
childprocess (~> 0.5) childprocess (~> 0.5)
rubyzip (~> 1.2) rubyzip (~> 1.2)
sentry-raven (2.5.3) sentry-raven (2.7.2)
faraday (>= 0.7.6, < 1.0) faraday (>= 0.7.6, < 1.0)
settingslogic (2.0.9) settingslogic (2.0.9)
sexp_processor (4.10.1) sexp_processor (4.10.1)
...@@ -903,12 +895,12 @@ GEM ...@@ -903,12 +895,12 @@ GEM
sshkey (1.9.0) sshkey (1.9.0)
stackprof (0.2.11) stackprof (0.2.11)
state_machines (0.5.0) state_machines (0.5.0)
state_machines-activemodel (0.5.0) state_machines-activemodel (0.5.1)
activemodel (>= 4.1, < 5.2) activemodel (>= 4.1, < 6.0)
state_machines (>= 0.5.0) state_machines (>= 0.5.0)
state_machines-activerecord (0.4.1) state_machines-activerecord (0.5.1)
activerecord (>= 4.1, < 5.2) activerecord (>= 4.1, < 6.0)
state_machines-activemodel (>= 0.3.0) state_machines-activemodel (>= 0.5.0)
stringex (2.8.4) stringex (2.8.4)
sys-filesystem (1.1.9) sys-filesystem (1.1.9)
ffi ffi
...@@ -998,7 +990,7 @@ DEPENDENCIES ...@@ -998,7 +990,7 @@ DEPENDENCIES
RedCloth (~> 4.3.2) RedCloth (~> 4.3.2)
ace-rails-ap (~> 4.1.0) ace-rails-ap (~> 4.1.0)
activerecord_sane_schema_dumper (= 1.0) activerecord_sane_schema_dumper (= 1.0)
acts-as-taggable-on (~> 4.0) acts-as-taggable-on (~> 5.0)
addressable (~> 2.5.2) addressable (~> 2.5.2)
akismet (~> 2.0) akismet (~> 2.0)
allocations (~> 1.0) allocations (~> 1.0)
...@@ -1038,8 +1030,8 @@ DEPENDENCIES ...@@ -1038,8 +1030,8 @@ DEPENDENCIES
devise (~> 4.2) devise (~> 4.2)
devise-two-factor (~> 3.0.0) devise-two-factor (~> 3.0.0)
diffy (~> 3.1.0) diffy (~> 3.1.0)
doorkeeper (~> 4.2.0) doorkeeper (~> 4.3)
doorkeeper-openid_connect (~> 1.2.0) doorkeeper-openid_connect (~> 1.3)
dropzonejs-rails (~> 0.7.1) dropzonejs-rails (~> 0.7.1)
email_reply_trimmer (~> 0.1) email_reply_trimmer (~> 0.1)
email_spec (~> 1.6.0) email_spec (~> 1.6.0)
...@@ -1048,24 +1040,24 @@ DEPENDENCIES ...@@ -1048,24 +1040,24 @@ DEPENDENCIES
fast_blank fast_blank
ffaker (~> 2.4) ffaker (~> 2.4)
flay (~> 2.10.0) flay (~> 2.10.0)
flipper (~> 0.11.0) flipper (~> 0.13.0)
flipper-active_record (~> 0.11.0) flipper-active_record (~> 0.13.0)
flipper-active_support_cache_store (~> 0.11.0) flipper-active_support_cache_store (~> 0.13.0)
fog-aliyun (~> 0.2.0) fog-aliyun (~> 0.2.0)
fog-aws (~> 1.4) fog-aws (~> 2.0)
fog-core (~> 1.44) fog-core (~> 1.44)
fog-google (~> 0.5) fog-google (~> 0.5)
fog-local (~> 0.3) fog-local (~> 0.3)
fog-openstack (~> 0.1) fog-openstack (~> 0.1)
fog-rackspace (~> 0.1.1) fog-rackspace (~> 0.1.1)
font-awesome-rails (~> 4.7) font-awesome-rails (~> 4.7)
foreman (~> 0.78.0) foreman (~> 0.84.0)
fuubar (~> 2.2.0) fuubar (~> 2.2.0)
gemnasium-gitlab-service (~> 0.2) gemnasium-gitlab-service (~> 0.2)
gemojione (~> 3.3) gemojione (~> 3.3)
gettext (~> 3.2.2) gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0) gettext_i18n_rails_js (~> 1.3)
gitaly-proto (~> 0.88.0) gitaly-proto (~> 0.88.0)
github-linguist (~> 5.3.3) github-linguist (~> 5.3.3)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
...@@ -1075,7 +1067,7 @@ DEPENDENCIES ...@@ -1075,7 +1067,7 @@ DEPENDENCIES
gollum-lib (~> 4.2) gollum-lib (~> 4.2)
gollum-rugged_adapter (~> 0.4.4) gollum-rugged_adapter (~> 0.4.4)
gon (~> 6.1.0) gon (~> 6.1.0)
google-api-client (~> 0.13.6) google-api-client (~> 0.19.8)
google-protobuf (= 3.5.1) google-protobuf (= 3.5.1)
gpgme gpgme
grape (~> 1.0) grape (~> 1.0)
...@@ -1094,15 +1086,14 @@ DEPENDENCIES ...@@ -1094,15 +1086,14 @@ DEPENDENCIES
influxdb (~> 0.2) influxdb (~> 0.2)
jira-ruby (~> 1.4) jira-ruby (~> 1.4)
jquery-atwho-rails (~> 1.3.2) jquery-atwho-rails (~> 1.3.2)
jquery-rails (~> 4.3.1)
json-schema (~> 2.8.0) json-schema (~> 2.8.0)
jwt (~> 1.5.6) jwt (~> 1.5.6)
kaminari (~> 1.0) kaminari (~> 1.0)
knapsack (~> 1.11.0) knapsack (~> 1.16)
kubeclient (~> 2.2.0) kubeclient (~> 3.0)
letter_opener_web (~> 1.3.0) letter_opener_web (~> 1.3.0)
license_finder (~> 3.1) license_finder (~> 3.1)
licensee (~> 8.7.0) licensee (~> 8.9)
lograge (~> 0.5) lograge (~> 0.5)
loofah (~> 2.0.3) loofah (~> 2.0.3)
mail_room (~> 0.9.1) mail_room (~> 0.9.1)
...@@ -1111,12 +1102,11 @@ DEPENDENCIES ...@@ -1111,12 +1102,11 @@ DEPENDENCIES
mousetrap-rails (~> 1.4.6) mousetrap-rails (~> 1.4.6)
mysql2 (~> 0.4.10) mysql2 (~> 0.4.10)
net-ldap net-ldap
net-ssh (~> 4.1.0) net-ssh (~> 4.2.0)
nokogiri (~> 1.8.2) nokogiri (~> 1.8.2)
oauth2 (~> 1.4) oauth2 (~> 1.4)
octokit (~> 4.6.2) octokit (~> 4.8)
oj (~> 2.17.4) omniauth (~> 1.8)
omniauth (~> 1.4.2)
omniauth-auth0 (~> 1.4.1) omniauth-auth0 (~> 1.4.1)
omniauth-authentiq (~> 0.3.1) omniauth-authentiq (~> 0.3.1)
omniauth-azure-oauth2 (~> 0.0.9) omniauth-azure-oauth2 (~> 0.0.9)
...@@ -1127,14 +1117,13 @@ DEPENDENCIES ...@@ -1127,14 +1117,13 @@ DEPENDENCIES
omniauth-google-oauth2 (~> 0.5.2) omniauth-google-oauth2 (~> 0.5.2)
omniauth-kerberos (~> 0.3.0) omniauth-kerberos (~> 0.3.0)
omniauth-oauth2-generic (~> 0.2.2) omniauth-oauth2-generic (~> 0.2.2)
omniauth-saml (~> 1.7.0) omniauth-saml (~> 1.10)
omniauth-shibboleth (~> 1.2.0) omniauth-shibboleth (~> 1.2.0)
omniauth-twitter (~> 1.2.0) omniauth-twitter (~> 1.2.0)
omniauth_crowd (~> 2.2.0) omniauth_crowd (~> 2.2.0)
org-ruby (~> 0.9.12) org-ruby (~> 0.9.12)
peek (~> 1.0.1) peek (~> 1.0.1)
peek-gc (~> 0.0.2) peek-gc (~> 0.0.2)
peek-host (~> 1.0.0)
peek-mysql2 (~> 1.1.0) peek-mysql2 (~> 1.1.0)
peek-performance_bar (~> 1.3.0) peek-performance_bar (~> 1.3.0)
peek-pg (~> 1.3.0) peek-pg (~> 1.3.0)
...@@ -1177,7 +1166,7 @@ DEPENDENCIES ...@@ -1177,7 +1166,7 @@ DEPENDENCIES
rubocop (~> 0.52.1) rubocop (~> 0.52.1)
rubocop-rspec (~> 1.22.1) rubocop-rspec (~> 1.22.1)
ruby-fogbugz (~> 0.2.1) ruby-fogbugz (~> 0.2.1)
ruby-prof (~> 0.16.2) ruby-prof (~> 0.17.0)
ruby_parser (~> 3.8) ruby_parser (~> 3.8)
rufus-scheduler (~> 3.4) rufus-scheduler (~> 3.4)
rugged (~> 0.26.0) rugged (~> 0.26.0)
...@@ -1187,7 +1176,7 @@ DEPENDENCIES ...@@ -1187,7 +1176,7 @@ DEPENDENCIES
seed-fu (~> 2.3.7) seed-fu (~> 2.3.7)
select2-rails (~> 3.5.9) select2-rails (~> 3.5.9)
selenium-webdriver (~> 3.5) selenium-webdriver (~> 3.5)
sentry-raven (~> 2.5.3) sentry-raven (~> 2.7)
settingslogic (~> 2.0.9) settingslogic (~> 2.0.9)
sham_rack (~> 1.3.6) sham_rack (~> 1.3.6)
shoulda-matchers (~> 3.1.2) shoulda-matchers (~> 3.1.2)
...@@ -1205,7 +1194,7 @@ DEPENDENCIES ...@@ -1205,7 +1194,7 @@ DEPENDENCIES
sprockets (~> 3.7.0) sprockets (~> 3.7.0)
sshkey (~> 1.9.0) sshkey (~> 1.9.0)
stackprof (~> 0.2.10) stackprof (~> 0.2.10)
state_machines-activerecord (~> 0.4.0) state_machines-activerecord (~> 0.5.1)
sys-filesystem (~> 1.1.6) sys-filesystem (~> 1.1.6)
test-prof (~> 0.2.5) test-prof (~> 0.2.5)
test_after_commit (~> 1.1) test_after_commit (~> 1.1)
......
...@@ -31,7 +31,7 @@ export default function renderMath($els) { ...@@ -31,7 +31,7 @@ export default function renderMath($els) {
if (!$els.length) return; if (!$els.length) return;
Promise.all([ Promise.all([
import(/* webpackChunkName: 'katex' */ 'katex'), import(/* webpackChunkName: 'katex' */ 'katex'),
import(/* webpackChunkName: 'katex' */ 'katex/dist/katex.css'), import(/* webpackChunkName: 'katex' */ 'katex/dist/katex.min.css'),
]).then(([katex]) => { ]).then(([katex]) => {
renderWithKaTeX($els, katex); renderWithKaTeX($els, katex);
}).catch(() => flash(__('An error occurred while rendering KaTeX'))); }).catch(() => flash(__('An error occurred while rendering KaTeX')));
......
...@@ -33,7 +33,7 @@ export default class VariableList { ...@@ -33,7 +33,7 @@ export default class VariableList {
selector: '.js-ci-variable-input-key', selector: '.js-ci-variable-input-key',
default: '', default: '',
}, },
value: { secret_value: {
selector: '.js-ci-variable-input-value', selector: '.js-ci-variable-input-value',
default: '', default: '',
}, },
...@@ -105,7 +105,7 @@ export default class VariableList { ...@@ -105,7 +105,7 @@ export default class VariableList {
setupToggleButtons($row[0]); setupToggleButtons($row[0]);
// Reset the resizable textarea // Reset the resizable textarea
$row.find(this.inputMap.value.selector).css('height', ''); $row.find(this.inputMap.secret_value.selector).css('height', '');
const $environmentSelect = $row.find('.js-variable-environment-toggle'); const $environmentSelect = $row.find('.js-variable-environment-toggle');
if ($environmentSelect.length) { if ($environmentSelect.length) {
......
// ECMAScript polyfills // ECMAScript polyfills
import 'core-js/fn/array/fill';
import 'core-js/fn/array/find'; import 'core-js/fn/array/find';
import 'core-js/fn/array/find-index'; import 'core-js/fn/array/find-index';
import 'core-js/fn/array/from'; import 'core-js/fn/array/from';
......
...@@ -54,6 +54,7 @@ class GfmAutoComplete { ...@@ -54,6 +54,7 @@ class GfmAutoComplete {
alias: 'commands', alias: 'commands',
searchKey: 'search', searchKey: 'search',
skipSpecialCharacterTest: true, skipSpecialCharacterTest: true,
skipMarkdownCharacterTest: true,
data: GfmAutoComplete.defaultLoadingData, data: GfmAutoComplete.defaultLoadingData,
displayTpl(value) { displayTpl(value) {
if (GfmAutoComplete.isLoading(value)) return GfmAutoComplete.Loading.template; if (GfmAutoComplete.isLoading(value)) return GfmAutoComplete.Loading.template;
...@@ -376,15 +377,23 @@ class GfmAutoComplete { ...@@ -376,15 +377,23 @@ class GfmAutoComplete {
return $.fn.atwho.default.callbacks.filter(query, data, searchKey); return $.fn.atwho.default.callbacks.filter(query, data, searchKey);
}, },
beforeInsert(value) { beforeInsert(value) {
let resultantValue = value; let withoutAt = value.substring(1);
const at = value.charAt();
if (value && !this.setting.skipSpecialCharacterTest) { if (value && !this.setting.skipSpecialCharacterTest) {
const withoutAt = value.substring(1); const regex = at === '~' ? /\W|^\d+$/ : /\W/;
const regex = value.charAt() === '~' ? /\W|^\d+$/ : /\W/;
if (withoutAt && regex.test(withoutAt)) { if (withoutAt && regex.test(withoutAt)) {
resultantValue = `${value.charAt()}"${withoutAt}"`; withoutAt = `"${withoutAt}"`;
} }
} }
return resultantValue;
// We can ignore this for quick actions because they are processed
// before Markdown.
if (!this.setting.skipMarkdownCharacterTest) {
withoutAt = withoutAt.replace(/([~\-_*`])/g, '\\$&');
}
return `${at}${withoutAt}`;
}, },
matcher(flag, subtext) { matcher(flag, subtext) {
const match = GfmAutoComplete.defaultMatcher(flag, subtext, this.app.controllers); const match = GfmAutoComplete.defaultMatcher(flag, subtext, this.app.controllers);
......
...@@ -753,7 +753,7 @@ GitLabDropdown = (function() { ...@@ -753,7 +753,7 @@ GitLabDropdown = (function() {
} }
if (this.options.isSelectable && !this.options.isSelectable(selectedObject, el)) { if (this.options.isSelectable && !this.options.isSelectable(selectedObject, el)) {
return; return [selectedObject];
} }
if (el.hasClass(ACTIVE_CLASS) && value !== 0) { if (el.hasClass(ACTIVE_CLASS) && value !== 0) {
......
...@@ -65,6 +65,10 @@ export default class Editor { ...@@ -65,6 +65,10 @@ export default class Editor {
(this.instance = this.monaco.editor.createDiffEditor(domElement, { (this.instance = this.monaco.editor.createDiffEditor(domElement, {
...defaultEditorOptions, ...defaultEditorOptions,
readOnly: true, readOnly: true,
quickSuggestions: false,
occurrencesHighlight: false,
renderLineHighlight: 'none',
hideCursorInOverviewRuler: true,
})), })),
); );
......
...@@ -6,6 +6,7 @@ export const defaultEditorOptions = { ...@@ -6,6 +6,7 @@ export const defaultEditorOptions = {
minimap: { minimap: {
enabled: false, enabled: false,
}, },
wordWrap: 'bounded',
}; };
export default [ export default [
......
...@@ -11,11 +11,19 @@ ...@@ -11,11 +11,19 @@
type: String, type: String,
required: true, required: true,
}, },
helpUrl: {
type: String,
required: false,
default: '',
},
}, },
computed: { computed: {
hasTitle() { hasTitle() {
return this.title.length > 0; return this.title.length > 0;
}, },
hasHelpURL() {
return this.helpUrl.length > 0;
},
}, },
}; };
</script> </script>
...@@ -28,5 +36,21 @@ ...@@ -28,5 +36,21 @@
{{ title }}: {{ title }}:
</span> </span>
{{ value }} {{ value }}
<span
v-if="hasHelpURL"
class="help-button pull-right"
>
<a
:href="helpUrl"
target="_blank"
rel="noopener noreferrer nofollow"
>
<i
class="fa fa-question-circle"
aria-hidden="true"
></i>
</a>
</span>
</p> </p>
</template> </template>
...@@ -22,6 +22,11 @@ ...@@ -22,6 +22,11 @@
type: Boolean, type: Boolean,
required: true, required: true,
}, },
runnerHelpUrl: {
type: String,
required: false,
default: '',
},
}, },
computed: { computed: {
shouldRenderContent() { shouldRenderContent() {
...@@ -39,6 +44,21 @@ ...@@ -39,6 +44,21 @@
runnerId() { runnerId() {
return `#${this.job.runner.id}`; return `#${this.job.runner.id}`;
}, },
hasTimeout() {
return this.job.metadata != null && this.job.metadata.timeout_human_readable !== '';
},
timeout() {
if (this.job.metadata == null) {
return '';
}
let t = this.job.metadata.timeout_human_readable;
if (this.job.metadata.timeout_source !== '') {
t += ` (from ${this.job.metadata.timeout_source})`;
}
return t;
},
renderBlock() { renderBlock() {
return this.job.merge_request || return this.job.merge_request ||
this.job.duration || this.job.duration ||
...@@ -114,6 +134,13 @@ ...@@ -114,6 +134,13 @@
title="Queued" title="Queued"
:value="queued" :value="queued"
/> />
<detail-row
class="js-job-timeout"
v-if="hasTimeout"
title="Timeout"
:help-url="runnerHelpUrl"
:value="timeout"
/>
<detail-row <detail-row
class="js-job-runner" class="js-job-runner"
v-if="job.runner" v-if="job.runner"
......
...@@ -51,6 +51,7 @@ export default () => { ...@@ -51,6 +51,7 @@ export default () => {
props: { props: {
isLoading: this.mediator.state.isLoading, isLoading: this.mediator.state.isLoading,
job: this.mediator.store.state.job, job: this.mediator.store.state.job,
runnerHelpUrl: dataset.runnerHelpUrl,
}, },
}); });
}, },
......
<script> <script>
import _ from 'underscore'; import _ from 'underscore';
import Flash from '../../flash'; import Flash from '../../flash';
import MonitoringService from '../services/monitoring_service'; import MonitoringService from '../services/monitoring_service';
import GraphGroup from './graph_group.vue'; import GraphGroup from './graph_group.vue';
import Graph from './graph.vue'; import Graph from './graph.vue';
import EmptyState from './empty_state.vue'; import EmptyState from './empty_state.vue';
import MonitoringStore from '../stores/monitoring_store'; import MonitoringStore from '../stores/monitoring_store';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
export default { export default {
components: { components: {
Graph, Graph,
GraphGroup, GraphGroup,
EmptyState, EmptyState,
},
props: {
hasMetrics: {
type: Boolean,
required: false,
default: true,
}, },
showLegend: {
props: { type: Boolean,
hasMetrics: { required: false,
type: Boolean, default: true,
required: false,
default: true,
},
showLegend: {
type: Boolean,
required: false,
default: true,
},
showPanels: {
type: Boolean,
required: false,
default: true,
},
forceSmallGraph: {
type: Boolean,
required: false,
default: false,
},
documentationPath: {
type: String,
required: true,
},
settingsPath: {
type: String,
required: true,
},
clustersPath: {
type: String,
required: true,
},
tagsPath: {
type: String,
required: true,
},
projectPath: {
type: String,
required: true,
},
metricsEndpoint: {
type: String,
required: true,
},
deploymentEndpoint: {
type: String,
required: false,
default: null,
},
emptyGettingStartedSvgPath: {
type: String,
required: true,
},
emptyLoadingSvgPath: {
type: String,
required: true,
},
emptyNoDataSvgPath: {
type: String,
required: true,
},
emptyUnableToConnectSvgPath: {
type: String,
required: true,
},
}, },
showPanels: {
data() { type: Boolean,
return { required: false,
store: new MonitoringStore(), default: true,
state: 'gettingStarted',
showEmptyState: true,
updateAspectRatio: false,
updatedAspectRatios: 0,
hoverData: {},
resizeThrottled: {},
};
}, },
forceSmallGraph: {
created() { type: Boolean,
this.service = new MonitoringService({ required: false,
metricsEndpoint: this.metricsEndpoint, default: false,
deploymentEndpoint: this.deploymentEndpoint,
});
eventHub.$on('toggleAspectRatio', this.toggleAspectRatio);
eventHub.$on('hoverChanged', this.hoverChanged);
}, },
documentationPath: {
beforeDestroy() { type: String,
eventHub.$off('toggleAspectRatio', this.toggleAspectRatio); required: true,
eventHub.$off('hoverChanged', this.hoverChanged);
window.removeEventListener('resize', this.resizeThrottled, false);
}, },
settingsPath: {
mounted() { type: String,
this.resizeThrottled = _.throttle(this.resize, 600); required: true,
if (!this.hasMetrics) { },
this.state = 'gettingStarted'; clustersPath: {
} else { type: String,
this.getGraphsData(); required: true,
window.addEventListener('resize', this.resizeThrottled, false); },
tagsPath: {
type: String,
required: true,
},
projectPath: {
type: String,
required: true,
},
metricsEndpoint: {
type: String,
required: true,
},
deploymentEndpoint: {
type: String,
required: false,
default: null,
},
emptyGettingStartedSvgPath: {
type: String,
required: true,
},
emptyLoadingSvgPath: {
type: String,
required: true,
},
emptyNoDataSvgPath: {
type: String,
required: true,
},
emptyUnableToConnectSvgPath: {
type: String,
required: true,
},
},
data() {
return {
store: new MonitoringStore(),
state: 'gettingStarted',
showEmptyState: true,
updateAspectRatio: false,
updatedAspectRatios: 0,
hoverData: {},
resizeThrottled: {},
};
},
created() {
this.service = new MonitoringService({
metricsEndpoint: this.metricsEndpoint,
deploymentEndpoint: this.deploymentEndpoint,
});
eventHub.$on('toggleAspectRatio', this.toggleAspectRatio);
eventHub.$on('hoverChanged', this.hoverChanged);
},
beforeDestroy() {
eventHub.$off('toggleAspectRatio', this.toggleAspectRatio);
eventHub.$off('hoverChanged', this.hoverChanged);
window.removeEventListener('resize', this.resizeThrottled, false);
},
mounted() {
this.resizeThrottled = _.throttle(this.resize, 600);
if (!this.hasMetrics) {
this.state = 'gettingStarted';
} else {
this.getGraphsData();
window.addEventListener('resize', this.resizeThrottled, false);
}
},
methods: {
getGraphsData() {
this.state = 'loading';
Promise.all([
this.service.getGraphsData().then(data => this.store.storeMetrics(data)),
this.service
.getDeploymentData()
.then(data => this.store.storeDeploymentData(data))
.catch(() => new Flash('Error getting deployment information.')),
])
.then(() => {
if (this.store.groups.length < 1) {
this.state = 'noData';
return;
}
this.showEmptyState = false;
})
.catch(() => {
this.state = 'unableToConnect';
});
},
resize() {
this.updateAspectRatio = true;
},
toggleAspectRatio() {
this.updatedAspectRatios = this.updatedAspectRatios += 1;
if (this.store.getMetricsCount() === this.updatedAspectRatios) {
this.updateAspectRatio = !this.updateAspectRatio;
this.updatedAspectRatios = 0;
} }
}, },
hoverChanged(data) {
methods: { this.hoverData = data;
getGraphsData() {
this.state = 'loading';
Promise.all([
this.service.getGraphsData()
.then(data => this.store.storeMetrics(data)),
this.service.getDeploymentData()
.then(data => this.store.storeDeploymentData(data))
.catch(() => new Flash('Error getting deployment information.')),
])
.then(() => {
if (this.store.groups.length < 1) {
this.state = 'noData';
return;
}
this.showEmptyState = false;
})
.catch(() => { this.state = 'unableToConnect'; });
},
resize() {
this.updateAspectRatio = true;
},
toggleAspectRatio() {
this.updatedAspectRatios = this.updatedAspectRatios += 1;
if (this.store.getMetricsCount() === this.updatedAspectRatios) {
this.updateAspectRatio = !this.updateAspectRatio;
this.updatedAspectRatios = 0;
}
},
hoverChanged(data) {
this.hoverData = data;
},
}, },
}; },
};
</script> </script>
<template> <template>
......
<script> <script>
export default { export default {
props: { props: {
documentationPath: { documentationPath: {
type: String, type: String,
required: true, required: true,
}, },
settingsPath: { settingsPath: {
type: String, type: String,
required: false, required: false,
default: '', default: '',
}, },
clustersPath: { clustersPath: {
type: String, type: String,
required: false, required: false,
default: '', default: '',
}, },
selectedState: { selectedState: {
type: String, type: String,
required: true, required: true,
},
emptyGettingStartedSvgPath: {
type: String,
required: true,
},
emptyLoadingSvgPath: {
type: String,
required: true,
},
emptyNoDataSvgPath: {
type: String,
required: true,
},
emptyUnableToConnectSvgPath: {
type: String,
required: true,
},
}, },
data() { emptyGettingStartedSvgPath: {
return { type: String,
states: { required: true,
gettingStarted: { },
svgUrl: this.emptyGettingStartedSvgPath, emptyLoadingSvgPath: {
title: 'Get started with performance monitoring', type: String,
description: `Stay updated about the performance and health required: true,
},
emptyNoDataSvgPath: {
type: String,
required: true,
},
emptyUnableToConnectSvgPath: {
type: String,
required: true,
},
},
data() {
return {
states: {
gettingStarted: {
svgUrl: this.emptyGettingStartedSvgPath,
title: 'Get started with performance monitoring',
description: `Stay updated about the performance and health
of your environment by configuring Prometheus to monitor your deployments.`, of your environment by configuring Prometheus to monitor your deployments.`,
buttonText: 'Install Prometheus on clusters', buttonText: 'Install Prometheus on clusters',
buttonPath: this.clustersPath, buttonPath: this.clustersPath,
secondaryButtonText: 'Configure existing Prometheus', secondaryButtonText: 'Configure existing Prometheus',
secondaryButtonPath: this.settingsPath, secondaryButtonPath: this.settingsPath,
}, },
loading: { loading: {
svgUrl: this.emptyLoadingSvgPath, svgUrl: this.emptyLoadingSvgPath,
title: 'Waiting for performance data', title: 'Waiting for performance data',
description: `Creating graphs uses the data from the Prometheus server. description: `Creating graphs uses the data from the Prometheus server.
If this takes a long time, ensure that data is available.`, If this takes a long time, ensure that data is available.`,
buttonText: 'View documentation', buttonText: 'View documentation',
buttonPath: this.documentationPath, buttonPath: this.documentationPath,
}, },
noData: { noData: {
svgUrl: this.emptyNoDataSvgPath, svgUrl: this.emptyNoDataSvgPath,
title: 'No data found', title: 'No data found',
description: `You are connected to the Prometheus server, but there is currently description: `You are connected to the Prometheus server, but there is currently
no data to display.`, no data to display.`,
buttonText: 'Configure Prometheus', buttonText: 'Configure Prometheus',
buttonPath: this.settingsPath, buttonPath: this.settingsPath,
}, },
unableToConnect: { unableToConnect: {
svgUrl: this.emptyUnableToConnectSvgPath, svgUrl: this.emptyUnableToConnectSvgPath,
title: 'Unable to connect to Prometheus server', title: 'Unable to connect to Prometheus server',
description: 'Ensure connectivity is available from the GitLab server to the ', description: 'Ensure connectivity is available from the GitLab server to the ',
buttonText: 'View documentation', buttonText: 'View documentation',
buttonPath: this.documentationPath, buttonPath: this.documentationPath,
},
}, },
};
},
computed: {
currentState() {
return this.states[this.selectedState];
},
showButtonDescription() {
if (this.selectedState === 'unableToConnect') return true;
return false;
}, },
};
},
computed: {
currentState() {
return this.states[this.selectedState];
},
showButtonDescription() {
if (this.selectedState === 'unableToConnect') return true;
return false;
}, },
}; },
};
</script> </script>
<template> <template>
......
<script> <script>
export default { export default {
props: { props: {
deploymentData: { deploymentData: {
type: Array, type: Array,
required: true, required: true,
},
graphHeight: {
type: Number,
required: true,
},
graphHeightOffset: {
type: Number,
required: true,
},
}, },
graphHeight: {
computed: { type: Number,
calculatedHeight() { required: true,
return this.graphHeight - this.graphHeightOffset;
},
}, },
graphHeightOffset: {
methods: { type: Number,
transformDeploymentGroup(deployment) { required: true,
return `translate(${Math.floor(deployment.xPos) - 5}, 20)`;
},
}, },
}; },
computed: {
calculatedHeight() {
return this.graphHeight - this.graphHeightOffset;
},
},
methods: {
transformDeploymentGroup(deployment) {
return `translate(${Math.floor(deployment.xPos) - 5}, 20)`;
},
},
};
</script> </script>
<template> <template>
<g class="deploy-info"> <g class="deploy-info">
......
<script> <script>
import { dateFormat, timeFormat } from '../../utils/date_time_formatters'; import { dateFormat, timeFormat } from '../../utils/date_time_formatters';
import { formatRelevantDigits } from '../../../lib/utils/number_utils'; import { formatRelevantDigits } from '../../../lib/utils/number_utils';
import icon from '../../../vue_shared/components/icon.vue'; import icon from '../../../vue_shared/components/icon.vue';
export default { export default {
components: { components: {
icon, icon,
}, },
props: { props: {
currentXCoordinate: { currentXCoordinate: {
type: Number, type: Number,
required: true, required: true,
},
currentData: {
type: Object,
required: true,
},
deploymentFlagData: {
type: Object,
required: false,
default: null,
},
graphHeight: {
type: Number,
required: true,
},
graphHeightOffset: {
type: Number,
required: true,
},
realPixelRatio: {
type: Number,
required: true,
},
showFlagContent: {
type: Boolean,
required: true,
},
timeSeries: {
type: Array,
required: true,
},
unitOfDisplay: {
type: String,
required: true,
},
currentDataIndex: {
type: Number,
required: true,
},
legendTitle: {
type: String,
required: true,
},
}, },
currentData: {
computed: { type: Object,
formatTime() { required: true,
return this.deploymentFlagData ?
timeFormat(this.deploymentFlagData.time) :
timeFormat(this.currentData.time);
},
formatDate() {
return this.deploymentFlagData ?
dateFormat(this.deploymentFlagData.time) :
dateFormat(this.currentData.time);
},
cursorStyle() {
const xCoordinate = this.deploymentFlagData ?
this.deploymentFlagData.xPos :
this.currentXCoordinate;
const offsetTop = 20 * this.realPixelRatio;
const offsetLeft = (70 + xCoordinate) * this.realPixelRatio;
const height = (this.graphHeight - this.graphHeightOffset) * this.realPixelRatio;
return {
top: `${offsetTop}px`,
left: `${offsetLeft}px`,
height: `${height}px`,
};
},
flagOrientation() {
if (this.currentXCoordinate * this.realPixelRatio > 120) {
return 'left';
}
return 'right';
},
}, },
deploymentFlagData: {
type: Object,
required: false,
default: null,
},
graphHeight: {
type: Number,
required: true,
},
graphHeightOffset: {
type: Number,
required: true,
},
realPixelRatio: {
type: Number,
required: true,
},
showFlagContent: {
type: Boolean,
required: true,
},
timeSeries: {
type: Array,
required: true,
},
unitOfDisplay: {
type: String,
required: true,
},
currentDataIndex: {
type: Number,
required: true,
},
legendTitle: {
type: String,
required: true,
},
},
computed: {
formatTime() {
return this.deploymentFlagData
? timeFormat(this.deploymentFlagData.time)
: timeFormat(this.currentData.time);
},
formatDate() {
return this.deploymentFlagData
? dateFormat(this.deploymentFlagData.time)
: dateFormat(this.currentData.time);
},
cursorStyle() {
const xCoordinate = this.deploymentFlagData
? this.deploymentFlagData.xPos
: this.currentXCoordinate;
methods: { const offsetTop = 20 * this.realPixelRatio;
seriesMetricValue(series) { const offsetLeft = (70 + xCoordinate) * this.realPixelRatio;
const index = this.deploymentFlagData ? const height = (this.graphHeight - this.graphHeightOffset) * this.realPixelRatio;
this.deploymentFlagData.seriesIndex :
this.currentDataIndex;
const value = series.values[index] &&
series.values[index].value;
if (isNaN(value)) {
return '-';
}
return `${formatRelevantDigits(value)}${this.unitOfDisplay}`;
},
seriesMetricLabel(index, series) {
if (this.timeSeries.length < 2) {
return this.legendTitle;
}
if (series.metricTag) {
return series.metricTag;
}
return `series ${index + 1}`;
},
strokeDashArray(type) { return {
if (type === 'dashed') return '6, 3'; top: `${offsetTop}px`,
if (type === 'dotted') return '3, 3'; left: `${offsetLeft}px`,
return null; height: `${height}px`,
}, };
},
flagOrientation() {
if (this.currentXCoordinate * this.realPixelRatio > 120) {
return 'left';
}
return 'right';
},
},
methods: {
seriesMetricValue(series) {
const index = this.deploymentFlagData
? this.deploymentFlagData.seriesIndex
: this.currentDataIndex;
const value = series.values[index] && series.values[index].value;
if (isNaN(value)) {
return '-';
}
return `${formatRelevantDigits(value)}${this.unitOfDisplay}`;
},
seriesMetricLabel(index, series) {
if (this.timeSeries.length < 2) {
return this.legendTitle;
}
if (series.metricTag) {
return series.metricTag;
}
return `series ${index + 1}`;
},
strokeDashArray(type) {
if (type === 'dashed') return '6, 3';
if (type === 'dotted') return '3, 3';
return null;
}, },
}; },
};
</script> </script>
<template> <template>
......
<script> <script>
import { formatRelevantDigits } from '../../../lib/utils/number_utils'; import { formatRelevantDigits } from '../../../lib/utils/number_utils';
export default {
props: {
graphWidth: {
type: Number,
required: true,
},
graphHeight: {
type: Number,
required: true,
},
margin: {
type: Object,
required: true,
},
measurements: {
type: Object,
required: true,
},
legendTitle: {
type: String,
required: true,
},
yAxisLabel: {
type: String,
required: true,
},
timeSeries: {
type: Array,
required: true,
},
unitOfDisplay: {
type: String,
required: true,
},
currentDataIndex: {
type: Number,
required: true,
},
showLegendGroup: {
type: Boolean,
required: false,
default: true,
},
},
data() {
return {
yLabelWidth: 0,
yLabelHeight: 0,
seriesXPosition: 0,
metricUsageXPosition: 0,
};
},
computed: {
textTransform() {
const yCoordinate = (((this.graphHeight - this.margin.top)
+ this.measurements.axisLabelLineOffset) / 2) || 0;
return `translate(15, ${yCoordinate}) rotate(-90)`;
},
rectTransform() {
const yCoordinate = (((this.graphHeight - this.margin.top)
+ this.measurements.axisLabelLineOffset) / 2)
+ (this.yLabelWidth / 2) || 0;
return `translate(0, ${yCoordinate}) rotate(-90)`;
},
xPosition() {
return (((this.graphWidth + this.measurements.axisLabelLineOffset) / 2)
- this.margin.right) || 0;
},
yPosition() {
return ((this.graphHeight - this.margin.top) + this.measurements.axisLabelLineOffset) || 0;
},
export default {
props: {
graphWidth: {
type: Number,
required: true,
}, },
mounted() { graphHeight: {
this.$nextTick(() => { type: Number,
const bbox = this.$refs.ylabel.getBBox(); required: true,
this.metricUsageXPosition = 0; },
this.seriesXPosition = 0; margin: {
if (this.$refs.legendTitleSvg != null) { type: Object,
this.seriesXPosition = this.$refs.legendTitleSvg[0].getBBox().width; required: true,
} },
if (this.$refs.seriesTitleSvg != null) { measurements: {
this.metricUsageXPosition = this.$refs.seriesTitleSvg[0].getBBox().width; type: Object,
} required: true,
this.yLabelWidth = bbox.width + 10; // Added some padding },
this.yLabelHeight = bbox.height + 5; legendTitle: {
}); type: String,
}, required: true,
methods: { },
translateLegendGroup(index) { yAxisLabel: {
return `translate(0, ${12 * (index)})`; type: String,
}, required: true,
},
formatMetricUsage(series) { timeSeries: {
const value = series.values[this.currentDataIndex] && type: Array,
series.values[this.currentDataIndex].value; required: true,
if (isNaN(value)) { },
return '-'; unitOfDisplay: {
} type: String,
return `${formatRelevantDigits(value)} ${this.unitOfDisplay}`; required: true,
}, },
currentDataIndex: {
type: Number,
required: true,
},
showLegendGroup: {
type: Boolean,
required: false,
default: true,
},
},
data() {
return {
yLabelWidth: 0,
yLabelHeight: 0,
seriesXPosition: 0,
metricUsageXPosition: 0,
};
},
computed: {
textTransform() {
const yCoordinate =
(this.graphHeight - this.margin.top + this.measurements.axisLabelLineOffset) / 2 || 0;
createSeriesString(index, series) { return `translate(15, ${yCoordinate}) rotate(-90)`;
if (series.metricTag) { },
return `${series.metricTag} ${this.formatMetricUsage(series)}`; rectTransform() {
} const yCoordinate =
return `${this.legendTitle} series ${index + 1} ${this.formatMetricUsage(series)}`; (this.graphHeight - this.margin.top + this.measurements.axisLabelLineOffset) / 2 +
}, this.yLabelWidth / 2 || 0;
strokeDashArray(type) { return `translate(0, ${yCoordinate}) rotate(-90)`;
if (type === 'dashed') return '6, 3'; },
if (type === 'dotted') return '3, 3'; xPosition() {
return null; return (this.graphWidth + this.measurements.axisLabelLineOffset) / 2 - this.margin.right || 0;
}, },
yPosition() {
return this.graphHeight - this.margin.top + this.measurements.axisLabelLineOffset || 0;
},
},
mounted() {
this.$nextTick(() => {
const bbox = this.$refs.ylabel.getBBox();
this.metricUsageXPosition = 0;
this.seriesXPosition = 0;
if (this.$refs.legendTitleSvg != null) {
this.seriesXPosition = this.$refs.legendTitleSvg[0].getBBox().width;
}
if (this.$refs.seriesTitleSvg != null) {
this.metricUsageXPosition = this.$refs.seriesTitleSvg[0].getBBox().width;
}
this.yLabelWidth = bbox.width + 10; // Added some padding
this.yLabelHeight = bbox.height + 5;
});
},
methods: {
translateLegendGroup(index) {
return `translate(0, ${12 * index})`;
},
formatMetricUsage(series) {
const value =
series.values[this.currentDataIndex] && series.values[this.currentDataIndex].value;
if (isNaN(value)) {
return '-';
}
return `${formatRelevantDigits(value)} ${this.unitOfDisplay}`;
},
createSeriesString(index, series) {
if (series.metricTag) {
return `${series.metricTag} ${this.formatMetricUsage(series)}`;
}
return `${this.legendTitle} series ${index + 1} ${this.formatMetricUsage(series)}`;
},
strokeDashArray(type) {
if (type === 'dashed') return '6, 3';
if (type === 'dotted') return '3, 3';
return null;
}, },
}; },
};
</script> </script>
<template> <template>
<g class="axis-label-container"> <g class="axis-label-container">
......
<script> <script>
export default { export default {
props: { props: {
generatedLinePath: { generatedLinePath: {
type: String, type: String,
required: true, required: true,
},
generatedAreaPath: {
type: String,
required: true,
},
lineStyle: {
type: String,
required: false,
default: '',
},
lineColor: {
type: String,
required: true,
},
areaColor: {
type: String,
required: true,
},
}, },
computed: { generatedAreaPath: {
strokeDashArray() { type: String,
if (this.lineStyle === 'dashed') return '3, 1'; required: true,
if (this.lineStyle === 'dotted') return '1, 1';
return null;
},
}, },
}; lineStyle: {
type: String,
required: false,
default: '',
},
lineColor: {
type: String,
required: true,
},
areaColor: {
type: String,
required: true,
},
},
computed: {
strokeDashArray() {
if (this.lineStyle === 'dashed') return '3, 1';
if (this.lineStyle === 'dotted') return '1, 1';
return null;
},
},
};
</script> </script>
<template> <template>
<g> <g>
......
<script> <script>
export default { export default {
props: { props: {
name: { name: {
type: String, type: String,
required: true, required: true,
},
showPanels: {
type: Boolean,
required: false,
default: true,
},
}, },
}; showPanels: {
type: Boolean,
required: false,
default: true,
},
},
};
</script> </script>
<template> <template>
......
...@@ -292,10 +292,12 @@ Please check your network connection and try again.`; ...@@ -292,10 +292,12 @@ Please check your network connection and try again.`;
</button> </button>
</div> </div>
<div <div
v-if="note.resolvable"
class="btn-group discussion-actions" class="btn-group discussion-actions"
role="group"> role="group"
>
<div <div
v-if="note.resolvable && !discussionResolved" v-if="!discussionResolved"
class="btn-group" class="btn-group"
role="group"> role="group">
<a <a
......
import initSettingsPanels from '~/settings_panels';
document.addEventListener('DOMContentLoaded', () => {
// Initialize expandable settings panels
initSettingsPanels();
});
...@@ -19,15 +19,19 @@ ...@@ -19,15 +19,19 @@
type: String, type: String,
required: true, required: true,
}, },
groupName: {
type: String,
required: true,
},
}, },
computed: { computed: {
title() { title() {
return sprintf(s__('Milestones|Promote %{milestoneTitle} to group milestone?'), { milestoneTitle: this.milestoneTitle }); return sprintf(s__('Milestones|Promote %{milestoneTitle} to group milestone?'), { milestoneTitle: this.milestoneTitle });
}, },
text() { text() {
return s__(`Milestones|Promoting this milestone will make it available for all projects inside the group. return sprintf(s__(`Milestones|Promoting %{milestoneTitle} will make it available for all projects inside %{groupName}.
Existing project milestones with the same title will be merged. Existing project milestones with the same title will be merged.
This action cannot be reversed.`); This action cannot be reversed.`), { milestoneTitle: this.milestoneTitle, groupName: this.groupName });
}, },
}, },
methods: { methods: {
......
...@@ -25,6 +25,7 @@ export default () => { ...@@ -25,6 +25,7 @@ export default () => {
const modalProps = { const modalProps = {
milestoneTitle: button.dataset.milestoneTitle, milestoneTitle: button.dataset.milestoneTitle,
url: button.dataset.url, url: button.dataset.url,
groupName: button.dataset.groupName,
}; };
eventHub.$once('promoteMilestoneModal.requestStarted', onRequestStarted); eventHub.$once('promoteMilestoneModal.requestStarted', onRequestStarted);
eventHub.$emit('promoteMilestoneModal.props', modalProps); eventHub.$emit('promoteMilestoneModal.props', modalProps);
...@@ -54,6 +55,7 @@ export default () => { ...@@ -54,6 +55,7 @@ export default () => {
return { return {
modalProps: { modalProps: {
milestoneTitle: '', milestoneTitle: '',
groupName: '',
url: '', url: '',
}, },
}; };
......
<script> <script>
import _ from 'underscore';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash'; import createFlash from '~/flash';
import GlModal from '~/vue_shared/components/gl_modal.vue'; import GlModal from '~/vue_shared/components/gl_modal.vue';
...@@ -27,19 +28,26 @@ ...@@ -27,19 +28,26 @@
type: String, type: String,
required: true, required: true,
}, },
groupName: {
type: String,
required: true,
},
}, },
computed: { computed: {
text() { text() {
return s__(`Milestones|Promoting this label will make it available for all projects inside the group. return sprintf(s__(`Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}.
Existing project labels with the same title will be merged. This action cannot be reversed.`); Existing project labels with the same title will be merged. This action cannot be reversed.`), {
labelTitle: this.labelTitle,
groupName: this.groupName,
});
}, },
title() { title() {
const label = `<span const label = `<span
class="label color-label" class="label color-label"
style="background-color: ${this.labelColor}; color: ${this.labelTextColor};" style="background-color: ${this.labelColor}; color: ${this.labelTextColor};"
>${this.labelTitle}</span>`; >${_.escape(this.labelTitle)}</span>`;
return sprintf(s__('Labels|Promote label %{labelTitle} to Group Label?'), { return sprintf(s__('Labels|<span>Promote label</span> %{labelTitle} <span>to Group Label?</span>'), {
labelTitle: label, labelTitle: label,
}, false); }, false);
}, },
...@@ -69,6 +77,7 @@ ...@@ -69,6 +77,7 @@
> >
<div <div
slot="title" slot="title"
class="modal-title-with-label"
v-html="title" v-html="title"
> >
{{ title }} {{ title }}
......
...@@ -30,6 +30,7 @@ const initLabelIndex = () => { ...@@ -30,6 +30,7 @@ const initLabelIndex = () => {
labelColor: button.dataset.labelColor, labelColor: button.dataset.labelColor,
labelTextColor: button.dataset.labelTextColor, labelTextColor: button.dataset.labelTextColor,
url: button.dataset.url, url: button.dataset.url,
groupName: button.dataset.groupName,
}; };
eventHub.$once('promoteLabelModal.requestStarted', onRequestStarted); eventHub.$once('promoteLabelModal.requestStarted', onRequestStarted);
eventHub.$emit('promoteLabelModal.props', modalProps); eventHub.$emit('promoteLabelModal.props', modalProps);
...@@ -62,6 +63,7 @@ const initLabelIndex = () => { ...@@ -62,6 +63,7 @@ const initLabelIndex = () => {
labelColor: '', labelColor: '',
labelTextColor: '', labelTextColor: '',
url: '', url: '',
groupName: '',
}, },
}; };
}, },
......
<script>
import { parseSeconds, stringifyTime } from '../../../lib/utils/pretty_time'; import { parseSeconds, stringifyTime } from '../../../lib/utils/pretty_time';
export default { export default {
name: 'time-tracking-comparison-pane', name: 'TimeTrackingComparisonPane',
props: { props: {
timeSpent: { timeSpent: {
type: Number, type: Number,
...@@ -43,47 +44,50 @@ export default { ...@@ -43,47 +44,50 @@ export default {
return this.timeEstimate >= this.timeSpent ? 'within_estimate' : 'over_estimate'; return this.timeEstimate >= this.timeSpent ? 'within_estimate' : 'over_estimate';
}, },
}, },
template: ` };
<div class="time-tracking-comparison-pane"> </script>
<template>
<div class="time-tracking-comparison-pane">
<div
class="compare-meter"
data-toggle="tooltip"
data-placement="top"
role="timeRemainingDisplay"
:aria-valuenow="timeRemainingTooltip"
:title="timeRemainingTooltip"
:data-original-title="timeRemainingTooltip"
:class="timeRemainingStatusClass"
>
<div <div
class="compare-meter" class="meter-container"
data-toggle="tooltip" role="timeSpentPercent"
data-placement="top" :aria-valuenow="timeRemainingPercent"
role="timeRemainingDisplay"
:aria-valuenow="timeRemainingTooltip"
:title="timeRemainingTooltip"
:data-original-title="timeRemainingTooltip"
:class="timeRemainingStatusClass"
> >
<div <div
class="meter-container" :style="{ width: timeRemainingPercent }"
role="timeSpentPercent" class="meter-fill"
:aria-valuenow="timeRemainingPercent"
> >
<div
:style="{ width: timeRemainingPercent }"
class="meter-fill"
/>
</div> </div>
<div class="compare-display-container"> </div>
<div class="compare-display pull-left"> <div class="compare-display-container">
<span class="compare-label"> <div class="compare-display pull-left">
<span class="compare-label">
{{ s__('TimeTracking|Spent') }} {{ s__('TimeTracking|Spent') }}
</span> </span>
<span class="compare-value spent"> <span class="compare-value spent">
{{ timeSpentHumanReadable }} {{ timeSpentHumanReadable }}
</span> </span>
</div> </div>
<div class="compare-display estimated pull-right"> <div class="compare-display estimated pull-right">
<span class="compare-label"> <span class="compare-label">
{{ s__('TimeTrackingEstimated|Est') }} {{ s__('TimeTrackingEstimated|Est') }}
</span> </span>
<span class="compare-value"> <span class="compare-value">
{{ timeEstimateHumanReadable }} {{ timeEstimateHumanReadable }}
</span> </span>
</div>
</div> </div>
</div> </div>
</div> </div>
`, </div>
}; </template>
...@@ -4,7 +4,7 @@ import TimeTrackingCollapsedState from './collapsed_state.vue'; ...@@ -4,7 +4,7 @@ import TimeTrackingCollapsedState from './collapsed_state.vue';
import timeTrackingSpentOnlyPane from './spent_only_pane'; import timeTrackingSpentOnlyPane from './spent_only_pane';
import timeTrackingNoTrackingPane from './no_tracking_pane'; import timeTrackingNoTrackingPane from './no_tracking_pane';
import timeTrackingEstimateOnlyPane from './estimate_only_pane'; import timeTrackingEstimateOnlyPane from './estimate_only_pane';
import timeTrackingComparisonPane from './comparison_pane'; import TimeTrackingComparisonPane from './comparison_pane.vue';
import eventHub from '../../event_hub'; import eventHub from '../../event_hub';
...@@ -15,7 +15,7 @@ export default { ...@@ -15,7 +15,7 @@ export default {
'time-tracking-estimate-only-pane': timeTrackingEstimateOnlyPane, 'time-tracking-estimate-only-pane': timeTrackingEstimateOnlyPane,
'time-tracking-spent-only-pane': timeTrackingSpentOnlyPane, 'time-tracking-spent-only-pane': timeTrackingSpentOnlyPane,
'time-tracking-no-tracking-pane': timeTrackingNoTrackingPane, 'time-tracking-no-tracking-pane': timeTrackingNoTrackingPane,
'time-tracking-comparison-pane': timeTrackingComparisonPane, TimeTrackingComparisonPane,
'time-tracking-help-state': timeTrackingHelpState, 'time-tracking-help-state': timeTrackingHelpState,
}, },
props: { props: {
......
...@@ -17,8 +17,8 @@ export default { ...@@ -17,8 +17,8 @@ export default {
/> />
<div class="media-body space-children"> <div class="media-body space-children">
<span class="bold"> <span class="bold">
The source branch HEAD has recently changed. {{ s__(`mrWidget|The source branch HEAD has recently changed.
Please reload the page and review the changes before merging. Please reload the page and review the changes before merging`) }}
</span> </span>
</div> </div>
</div> </div>
......
...@@ -2,7 +2,15 @@ ...@@ -2,7 +2,15 @@
* Styles the GitLab application with a specific color theme * Styles the GitLab application with a specific color theme
*/ */
@mixin gitlab-theme($color-100, $color-200, $color-500, $color-700, $color-800, $color-900, $color-alternate) { @mixin gitlab-theme(
$color-100,
$color-200,
$color-500,
$color-700,
$color-800,
$color-900,
$color-alternate
) {
// Header // Header
.navbar-gitlab { .navbar-gitlab {
...@@ -23,7 +31,7 @@ ...@@ -23,7 +31,7 @@
> li { > li {
> a:hover, > a:hover,
> a:focus { > a:focus {
background-color: rgba($color-200, .2); background-color: rgba($color-200, 0.2);
} }
&.active > a, &.active > a,
...@@ -33,7 +41,7 @@ ...@@ -33,7 +41,7 @@
} }
&.line-separator { &.line-separator {
border-left: 1px solid rgba($color-200, .2); border-left: 1px solid rgba($color-200, 0.2);
} }
} }
} }
...@@ -56,7 +64,7 @@ ...@@ -56,7 +64,7 @@
&:hover, &:hover,
&:focus { &:focus {
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
background-color: rgba($color-200, .2); background-color: rgba($color-200, 0.2);
} }
svg { svg {
...@@ -91,34 +99,34 @@ ...@@ -91,34 +99,34 @@
> a { > a {
&:hover, &:hover,
&:focus { &:focus {
background-color: rgba($color-200, .2); background-color: rgba($color-200, 0.2);
} }
} }
} }
.search { .search {
form { form {
background-color: rgba($color-200, .2); background-color: rgba($color-200, 0.2);
&:hover { &:hover {
background-color: rgba($color-200, .3); background-color: rgba($color-200, 0.3);
} }
} }
.location-badge { .location-badge {
color: $color-100; color: $color-100;
background-color: rgba($color-200, .1); background-color: rgba($color-200, 0.1);
border-right: 1px solid $color-800; border-right: 1px solid $color-800;
} }
.search-input::placeholder { .search-input::placeholder {
color: rgba($color-200, .8); color: rgba($color-200, 0.8);
} }
.search-input-wrap { .search-input-wrap {
.search-icon, .search-icon,
.clear-icon { .clear-icon {
fill: rgba($color-200, .8); fill: rgba($color-200, 0.8);
} }
} }
...@@ -133,7 +141,7 @@ ...@@ -133,7 +141,7 @@
.search-input-wrap { .search-input-wrap {
.search-icon { .search-icon {
fill: rgba($color-200, .8); fill: rgba($color-200, 0.8);
} }
} }
} }
...@@ -144,7 +152,6 @@ ...@@ -144,7 +152,6 @@
color: $color-900; color: $color-900;
} }
// Sidebar // Sidebar
.nav-sidebar li.active { .nav-sidebar li.active {
box-shadow: inset 4px 0 0 $color-700; box-shadow: inset 4px 0 0 $color-700;
...@@ -169,28 +176,94 @@ ...@@ -169,28 +176,94 @@
font-weight: $gl-font-weight-bold; font-weight: $gl-font-weight-bold;
} }
} }
}
// Web IDE
.ide-sidebar-link {
color: $color-200;
background-color: $color-700;
&:hover,
&:focus {
background-color: $color-500;
}
&:active {
background: $color-800;
}
}
.branch-container {
border-left-color: $color-700;
}
.branch-header-title {
color: $color-700;
}
.ide-file-list .file.file-active {
color: $color-700;
}
}
body { body {
&.ui_indigo { &.ui_indigo {
@include gitlab-theme($indigo-100, $indigo-200, $indigo-500, $indigo-700, $indigo-800, $indigo-900, $white-light); @include gitlab-theme(
$indigo-100,
$indigo-200,
$indigo-500,
$indigo-700,
$indigo-800,
$indigo-900,
$white-light
);
} }
&.ui_dark { &.ui_dark {
@include gitlab-theme($theme-gray-100, $theme-gray-200, $theme-gray-500, $theme-gray-700, $theme-gray-800, $theme-gray-900, $white-light); @include gitlab-theme(
$theme-gray-100,
$theme-gray-200,
$theme-gray-500,
$theme-gray-700,
$theme-gray-800,
$theme-gray-900,
$white-light
);
} }
&.ui_blue { &.ui_blue {
@include gitlab-theme($theme-blue-100, $theme-blue-200, $theme-blue-500, $theme-blue-700, $theme-blue-800, $theme-blue-900, $white-light); @include gitlab-theme(
$theme-blue-100,
$theme-blue-200,
$theme-blue-500,
$theme-blue-700,
$theme-blue-800,
$theme-blue-900,
$white-light
);
} }
&.ui_green { &.ui_green {
@include gitlab-theme($theme-green-100, $theme-green-200, $theme-green-500, $theme-green-700, $theme-green-800, $theme-green-900, $white-light); @include gitlab-theme(
$theme-green-100,
$theme-green-200,
$theme-green-500,
$theme-green-700,
$theme-green-800,
$theme-green-900,
$white-light
);
} }
&.ui_light { &.ui_light {
@include gitlab-theme($theme-gray-900, $theme-gray-700, $theme-gray-800, $theme-gray-700, $theme-gray-700, $theme-gray-100, $theme-gray-700); @include gitlab-theme(
$theme-gray-900,
$theme-gray-700,
$theme-gray-800,
$theme-gray-700,
$theme-gray-700,
$theme-gray-100,
$theme-gray-700
);
.navbar-gitlab { .navbar-gitlab {
background-color: $theme-gray-100; background-color: $theme-gray-100;
...@@ -270,5 +343,9 @@ body { ...@@ -270,5 +343,9 @@ body {
.sidebar-top-level-items > li.active .badge { .sidebar-top-level-items > li.active .badge {
color: $theme-gray-900; color: $theme-gray-900;
} }
.ide-sidebar-link {
color: $white-light;
}
} }
} }
...@@ -4,9 +4,15 @@ ...@@ -4,9 +4,15 @@
.page-title, .page-title,
.modal-title { .modal-title {
.modal-title-with-label span {
vertical-align: middle;
display: inline-block;
}
.color-label { .color-label {
font-size: $gl-font-size; font-size: $gl-font-size;
padding: $gl-vert-padding $label-padding-modal; padding: $gl-vert-padding $label-padding-modal;
vertical-align: middle;
} }
} }
......
.ci-body {
.incorrect-syntax {
font-size: 18px;
color: $lint-incorrect-color;
}
.correct-syntax {
font-size: 18px;
color: $lint-correct-color;
}
}
.ci-linter {
.ci-editor {
height: 400px;
}
.ci-template pre {
white-space: pre-wrap;
}
}
...@@ -1121,3 +1121,25 @@ pre.light-well { ...@@ -1121,3 +1121,25 @@ pre.light-well {
padding-top: $gl-padding; padding-top: $gl-padding;
padding-bottom: 37px; padding-bottom: 37px;
} }
.project-ci-body {
.incorrect-syntax {
font-size: 18px;
color: $lint-incorrect-color;
}
.correct-syntax {
font-size: 18px;
color: $lint-correct-color;
}
}
.project-ci-linter {
.ci-editor {
height: 400px;
}
.ci-template pre {
white-space: pre-wrap;
}
}
...@@ -20,7 +20,6 @@ ...@@ -20,7 +20,6 @@
display: flex; display: flex;
height: calc(100vh - #{$header-height}); height: calc(100vh - #{$header-height});
margin-top: 40px; margin-top: 40px;
color: $almost-black;
border-top: 1px solid $white-dark; border-top: 1px solid $white-dark;
border-bottom: 1px solid $white-dark; border-bottom: 1px solid $white-dark;
...@@ -43,7 +42,11 @@ ...@@ -43,7 +42,11 @@
cursor: pointer; cursor: pointer;
&.file-open { &.file-open {
background: $white-normal; background: $link-active-background;
}
&.file-active {
font-weight: $gl-font-weight-bold;
} }
.ide-file-name { .ide-file-name {
...@@ -72,7 +75,10 @@ ...@@ -72,7 +75,10 @@
margin-right: -8px; margin-right: -8px;
} }
&:hover { &:hover,
&:focus {
background: $link-active-background;
.ide-new-btn { .ide-new-btn {
display: block; display: block;
} }
...@@ -290,6 +296,10 @@ ...@@ -290,6 +296,10 @@
.margin-view-overlays .delete-sign { .margin-view-overlays .delete-sign {
opacity: 0.4; opacity: 0.4;
} }
.cursors-layer {
display: none;
}
} }
} }
...@@ -398,7 +408,7 @@ ...@@ -398,7 +408,7 @@
} }
.branch-container { .branch-container {
border-left: 4px solid $indigo-700; border-left: 4px solid;
margin-bottom: $gl-bar-padding; margin-bottom: $gl-bar-padding;
} }
...@@ -410,7 +420,6 @@ ...@@ -410,7 +420,6 @@
.branch-header-title { .branch-header-title {
flex: 1; flex: 1;
padding: $grid-size $gl-padding; padding: $grid-size $gl-padding;
color: $indigo-700;
font-weight: $gl-font-weight-bold; font-weight: $gl-font-weight-bold;
svg { svg {
...@@ -717,9 +726,7 @@ ...@@ -717,9 +726,7 @@
} }
.ide-view { .ide-view {
height: calc( height: calc(100vh - #{$header-height + $performance-bar-height + $flash-height});
100vh - #{$header-height + $performance-bar-height + $flash-height}
);
} }
} }
} }
...@@ -763,20 +770,7 @@ ...@@ -763,20 +770,7 @@
.ide-sidebar-link { .ide-sidebar-link {
padding: $gl-padding-8 $gl-padding; padding: $gl-padding-8 $gl-padding;
background: $indigo-700;
color: $white-light;
text-decoration: none;
display: flex; display: flex;
align-items: center; align-items: center;
font-weight: $gl-font-weight-bold;
&:focus,
&:hover {
color: $white-light;
text-decoration: underline;
background: $indigo-500;
}
&:active {
background: $indigo-800;
}
} }
...@@ -4,20 +4,5 @@ module Ci ...@@ -4,20 +4,5 @@ module Ci
def show def show
end end
def create
@content = params[:content]
@error = Gitlab::Ci::YamlProcessor.validation_message(@content)
@status = @error.blank?
if @error.blank?
@config_processor = Gitlab::Ci::YamlProcessor.new(@content)
@stages = @config_processor.stages
@builds = @config_processor.builds
@jobs = @config_processor.jobs
end
render :show
end
end end
end end
module SendFileUpload
def send_upload(file_upload, send_params: {}, redirect_params: {}, attachment: nil, disposition: 'attachment')
if attachment
redirect_params[:query] = { "response-content-disposition" => "#{disposition};filename=#{attachment.inspect}" }
send_params.merge!(filename: attachment, disposition: disposition)
end
if file_upload.file_storage?
send_file file_upload.path, send_params
elsif file_upload.class.proxy_download_enabled?
headers.store(*Gitlab::Workhorse.send_url(file_upload.url(**redirect_params)))
head :ok
else
redirect_to file_upload.url(**redirect_params)
end
end
end
module UploadsActions module UploadsActions
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
include SendFileUpload
UPLOAD_MOUNTS = %w(avatar attachment file logo header_logo).freeze UPLOAD_MOUNTS = %w(avatar attachment file logo header_logo).freeze
...@@ -26,14 +27,11 @@ module UploadsActions ...@@ -26,14 +27,11 @@ module UploadsActions
def show def show
return render_404 unless uploader&.exists? return render_404 unless uploader&.exists?
if uploader.file_storage? expires_in 0.seconds, must_revalidate: true, private: true
disposition = uploader.image_or_video? ? 'inline' : 'attachment'
expires_in 0.seconds, must_revalidate: true, private: true
send_file uploader.file.path, disposition: disposition disposition = uploader.image_or_video? ? 'inline' : 'attachment'
else
redirect_to uploader.url send_upload(uploader, attachment: uploader.filename, disposition: disposition)
end
end end
private private
...@@ -62,19 +60,27 @@ module UploadsActions ...@@ -62,19 +60,27 @@ module UploadsActions
end end
def build_uploader_from_upload def build_uploader_from_upload
return nil unless params[:secret] && params[:filename] return unless uploader = build_uploader
upload_path = uploader_class.upload_path(params[:secret], params[:filename]) upload_paths = uploader.upload_paths(params[:filename])
upload = Upload.find_by(uploader: uploader_class.to_s, path: upload_path) upload = Upload.find_by(uploader: uploader_class.to_s, path: upload_paths)
upload&.build_uploader upload&.build_uploader
end end
def build_uploader_from_params def build_uploader_from_params
return unless uploader = build_uploader
uploader.retrieve_from_store!(params[:filename])
uploader
end
def build_uploader
return unless params[:secret] && params[:filename]
uploader = uploader_class.new(model, secret: params[:secret]) uploader = uploader_class.new(model, secret: params[:secret])
return nil unless uploader.model_valid? return unless uploader.model_valid?
uploader.retrieve_from_store!(params[:filename])
uploader uploader
end end
......
...@@ -39,7 +39,7 @@ module Groups ...@@ -39,7 +39,7 @@ module Groups
end end
def variable_params_attributes def variable_params_attributes
%i[id key value protected _destroy] %i[id key secret_value protected _destroy]
end end
def authorize_admin_build! def authorize_admin_build!
......
class Projects::ArtifactsController < Projects::ApplicationController class Projects::ArtifactsController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
include RendersBlob include RendersBlob
include SendFileUpload
layout 'project' layout 'project'
before_action :authorize_read_build! before_action :authorize_read_build!
...@@ -10,11 +11,7 @@ class Projects::ArtifactsController < Projects::ApplicationController ...@@ -10,11 +11,7 @@ class Projects::ArtifactsController < Projects::ApplicationController
before_action :entry, only: [:file] before_action :entry, only: [:file]
def download def download
if artifacts_file.file_storage? send_upload(artifacts_file, attachment: artifacts_file.filename)
send_file artifacts_file.path, disposition: 'attachment'
else
redirect_to artifacts_file.url
end
end end
def browse def browse
...@@ -45,8 +42,7 @@ class Projects::ArtifactsController < Projects::ApplicationController ...@@ -45,8 +42,7 @@ class Projects::ArtifactsController < Projects::ApplicationController
end end
def raw def raw
path = Gitlab::Ci::Build::Artifacts::Path path = Gitlab::Ci::Build::Artifacts::Path.new(params[:path])
.new(params[:path])
send_artifacts_entry(build, path) send_artifacts_entry(build, path)
end end
...@@ -75,7 +71,7 @@ class Projects::ArtifactsController < Projects::ApplicationController ...@@ -75,7 +71,7 @@ class Projects::ArtifactsController < Projects::ApplicationController
end end
def validate_artifacts! def validate_artifacts!
render_404 unless build && build.artifacts? render_404 unless build&.artifacts?
end end
def build def build
......
...@@ -21,17 +21,13 @@ class Projects::BranchesController < Projects::ApplicationController ...@@ -21,17 +21,13 @@ class Projects::BranchesController < Projects::ApplicationController
fetch_branches_by_mode fetch_branches_by_mode
@refs_pipelines = @project.pipelines.latest_successful_for_refs(@branches.map(&:name)) @refs_pipelines = @project.pipelines.latest_successful_for_refs(@branches.map(&:name))
@merged_branch_names = @merged_branch_names = repository.merged_branch_names(@branches.map(&:name))
repository.merged_branch_names(@branches.map(&:name)) @max_commits = @branches.reduce(0) do |memo, branch|
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37429 diverging_commit_counts = repository.diverging_commit_counts(branch)
Gitlab::GitalyClient.allow_n_plus_1_calls do [memo, diverging_commit_counts[:behind], diverging_commit_counts[:ahead]].max
@max_commits = @branches.reduce(0) do |memo, branch|
diverging_commit_counts = repository.diverging_commit_counts(branch)
[memo, diverging_commit_counts[:behind], diverging_commit_counts[:ahead]].max
end
render
end end
render
end end
format.json do format.json do
branches = BranchesFinder.new(@repository, params).execute branches = BranchesFinder.new(@repository, params).execute
......
class Projects::Ci::LintsController < Projects::ApplicationController
before_action :authorize_create_pipeline!
def show
end
def create
@content = params[:content]
@error = Gitlab::Ci::YamlProcessor.validation_message(@content, yaml_processor_options)
@status = @error.blank?
if @error.blank?
@config_processor = Gitlab::Ci::YamlProcessor.new(@content, yaml_processor_options)
@stages = @config_processor.stages
@builds = @config_processor.builds
@jobs = @config_processor.jobs
end
render :show
end
private
def yaml_processor_options
{ project: @project, sha: project.repository.commit.sha }
end
end
class Projects::JobsController < Projects::ApplicationController class Projects::JobsController < Projects::ApplicationController
include SendFileUpload
before_action :build, except: [:index, :cancel_all] before_action :build, except: [:index, :cancel_all]
before_action :authorize_read_build!, before_action :authorize_read_build!,
...@@ -117,11 +119,17 @@ class Projects::JobsController < Projects::ApplicationController ...@@ -117,11 +119,17 @@ class Projects::JobsController < Projects::ApplicationController
end end
def raw def raw
build.trace.read do |stream| if trace_artifact_file
if stream.file? send_upload(trace_artifact_file,
send_file stream.path, type: 'text/plain; charset=utf-8', disposition: 'inline' send_params: raw_send_params,
else redirect_params: raw_redirect_params)
render_404 else
build.trace.read do |stream|
if stream.file?
send_file stream.path, type: 'text/plain; charset=utf-8', disposition: 'inline'
else
render_404
end
end end
end end
end end
...@@ -136,9 +144,21 @@ class Projects::JobsController < Projects::ApplicationController ...@@ -136,9 +144,21 @@ class Projects::JobsController < Projects::ApplicationController
return access_denied! unless can?(current_user, :erase_build, build) return access_denied! unless can?(current_user, :erase_build, build)
end end
def raw_send_params
{ type: 'text/plain; charset=utf-8', disposition: 'inline' }
end
def raw_redirect_params
{ query: { 'response-content-type' => 'text/plain; charset=utf-8', 'response-content-disposition' => 'inline' } }
end
def trace_artifact_file
@trace_artifact_file ||= build.job_artifacts_trace&.file
end
def build def build
@build ||= project.builds.find(params[:id]) @build ||= project.builds.find(params[:id])
.present(current_user: current_user) .present(current_user: current_user)
end end
def build_path(build) def build_path(build)
......
...@@ -112,7 +112,7 @@ class Projects::LabelsController < Projects::ApplicationController ...@@ -112,7 +112,7 @@ class Projects::LabelsController < Projects::ApplicationController
begin begin
return render_404 unless promote_service.execute(@label) return render_404 unless promote_service.execute(@label)
flash[:notice] = "#{@label.title} promoted to group label." flash[:notice] = "#{@label.title} promoted to <a href=\"#{group_labels_path(@project.group)}\">group label</a>.".html_safe
respond_to do |format| respond_to do |format|
format.html do format.html do
redirect_to(project_labels_path(@project), status: 303) redirect_to(project_labels_path(@project), status: 303)
......
class Projects::LfsStorageController < Projects::GitHttpClientController class Projects::LfsStorageController < Projects::GitHttpClientController
include LfsRequest include LfsRequest
include WorkhorseRequest include WorkhorseRequest
include SendFileUpload
skip_before_action :verify_workhorse_api!, only: [:download, :upload_finalize] skip_before_action :verify_workhorse_api!, only: [:download, :upload_finalize]
...@@ -11,25 +12,28 @@ class Projects::LfsStorageController < Projects::GitHttpClientController ...@@ -11,25 +12,28 @@ class Projects::LfsStorageController < Projects::GitHttpClientController
return return
end end
send_file lfs_object.file.path, content_type: "application/octet-stream" send_upload(lfs_object.file, send_params: { content_type: "application/octet-stream" })
end end
def upload_authorize def upload_authorize
set_workhorse_internal_api_content_type set_workhorse_internal_api_content_type
render json: Gitlab::Workhorse.lfs_upload_ok(oid, size)
authorized = LfsObjectUploader.workhorse_authorize
authorized.merge!(LfsOid: oid, LfsSize: size)
render json: authorized
end end
def upload_finalize def upload_finalize
unless tmp_filename if store_file!(oid, size)
render_lfs_forbidden
return
end
if store_file(oid, size, tmp_filename)
head 200 head 200
else else
render plain: 'Unprocessable entity', status: 422 render plain: 'Unprocessable entity', status: 422
end end
rescue ActiveRecord::RecordInvalid
render_400
rescue ObjectStorage::RemoteStoreError
render_lfs_forbidden
end end
private private
...@@ -50,38 +54,28 @@ class Projects::LfsStorageController < Projects::GitHttpClientController ...@@ -50,38 +54,28 @@ class Projects::LfsStorageController < Projects::GitHttpClientController
params[:size].to_i params[:size].to_i
end end
def tmp_filename def store_file!(oid, size)
name = request.headers['X-Gitlab-Lfs-Tmp'] object = LfsObject.find_by(oid: oid, size: size)
return if name.include?('/') unless object&.file&.exists?
return unless oid.present? && name.start_with?(oid) object = create_file!(oid, size)
end
name
end
def store_file(oid, size, tmp_file) return unless object
# Define tmp_file_path early because we use it in "ensure"
tmp_file_path = File.join(LfsObjectUploader.workhorse_upload_path, tmp_file)
object = LfsObject.find_or_create_by(oid: oid, size: size) link_to_project!(object)
file_exists = object.file.exists? || move_tmp_file_to_storage(object, tmp_file_path)
file_exists && link_to_project(object)
ensure
FileUtils.rm_f(tmp_file_path)
end end
def move_tmp_file_to_storage(object, path) def create_file!(oid, size)
File.open(path) do |f| LfsObject.new(oid: oid, size: size).tap do |object|
object.file = f object.file.store_workhorse_file!(params, :file)
object.save!
end end
object.file.store!
object.save
end end
def link_to_project(object) def link_to_project!(object)
if object && !object.projects.exists?(storage_project.id) if object && !object.projects.exists?(storage_project.id)
object.projects << storage_project object.projects << storage_project
object.save object.save!
end end
end end
end end
...@@ -42,6 +42,10 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -42,6 +42,10 @@ class Projects::MilestonesController < Projects::ApplicationController
def show def show
@project_namespace = @project.namespace.becomes(Namespace) @project_namespace = @project.namespace.becomes(Namespace)
respond_to do |format|
format.html
end
end end
def create def create
...@@ -70,9 +74,9 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -70,9 +74,9 @@ class Projects::MilestonesController < Projects::ApplicationController
end end
def promote def promote
Milestones::PromoteService.new(project, current_user).execute(milestone) promoted_milestone = Milestones::PromoteService.new(project, current_user).execute(milestone)
flash[:notice] = "#{milestone.title} promoted to group milestone" flash[:notice] = "#{milestone.title} promoted to <a href=\"#{group_milestone_path(project.group, promoted_milestone.iid)}\">group milestone</a>.".html_safe
respond_to do |format| respond_to do |format|
format.html do format.html do
redirect_to project_milestones_path(project) redirect_to project_milestones_path(project)
......
...@@ -92,7 +92,7 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController ...@@ -92,7 +92,7 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
def schedule_params def schedule_params
params.require(:schedule) params.require(:schedule)
.permit(:description, :cron, :cron_timezone, :ref, :active, .permit(:description, :cron, :cron_timezone, :ref, :active,
variables_attributes: [:id, :key, :value, :_destroy] ) variables_attributes: [:id, :key, :secret_value, :_destroy] )
end end
def authorize_play_pipeline_schedule! def authorize_play_pipeline_schedule!
......
...@@ -5,12 +5,8 @@ class Projects::ProtectedBranchesController < Projects::ProtectedRefsController ...@@ -5,12 +5,8 @@ class Projects::ProtectedBranchesController < Projects::ProtectedRefsController
@project.repository.branches @project.repository.branches
end end
def create_service_class def service_namespace
::ProtectedBranches::CreateService ::ProtectedBranches
end
def update_service_class
::ProtectedBranches::UpdateService
end end
def load_protected_ref def load_protected_ref
......
...@@ -37,7 +37,7 @@ class Projects::ProtectedRefsController < Projects::ApplicationController ...@@ -37,7 +37,7 @@ class Projects::ProtectedRefsController < Projects::ApplicationController
end end
def destroy def destroy
@protected_ref.destroy destroy_service_class.new(@project, current_user).execute(@protected_ref)
respond_to do |format| respond_to do |format|
format.html { redirect_to_repository_settings(@project) } format.html { redirect_to_repository_settings(@project) }
...@@ -47,6 +47,18 @@ class Projects::ProtectedRefsController < Projects::ApplicationController ...@@ -47,6 +47,18 @@ class Projects::ProtectedRefsController < Projects::ApplicationController
protected protected
def create_service_class
service_namespace::CreateService
end
def update_service_class
service_namespace::UpdateService
end
def destroy_service_class
service_namespace::DestroyService
end
def access_level_attributes def access_level_attributes
%i(access_level id) %i(access_level id)
end end
......
...@@ -5,12 +5,8 @@ class Projects::ProtectedTagsController < Projects::ProtectedRefsController ...@@ -5,12 +5,8 @@ class Projects::ProtectedTagsController < Projects::ProtectedRefsController
@project.repository.tags @project.repository.tags
end end
def create_service_class def service_namespace
::ProtectedTags::CreateService ::ProtectedTags
end
def update_service_class
::ProtectedTags::UpdateService
end end
def load_protected_ref def load_protected_ref
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
class Projects::RawController < Projects::ApplicationController class Projects::RawController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
include BlobHelper include BlobHelper
include SendFileUpload
before_action :require_non_empty_project before_action :require_non_empty_project
before_action :assign_ref_vars before_action :assign_ref_vars
...@@ -31,7 +32,7 @@ class Projects::RawController < Projects::ApplicationController ...@@ -31,7 +32,7 @@ class Projects::RawController < Projects::ApplicationController
lfs_object = find_lfs_object lfs_object = find_lfs_object
if lfs_object && lfs_object.project_allowed_access?(@project) if lfs_object && lfs_object.project_allowed_access?(@project)
send_file lfs_object.file.path, filename: @blob.name, disposition: 'attachment' send_upload(lfs_object.file, attachment: @blob.name)
else else
render_404 render_404
end end
......
...@@ -29,12 +29,12 @@ module Projects ...@@ -29,12 +29,12 @@ module Projects
@project_runners = @project.runners.ordered @project_runners = @project.runners.ordered
@assignable_runners = current_user.ci_authorized_runners @assignable_runners = current_user.ci_authorized_runners
.assignable_for(project).ordered.page(params[:page]).per(20) .assignable_for(project).ordered.page(params[:page]).per(20)
@shared_runners = Ci::Runner.shared.active @shared_runners = ::Ci::Runner.shared.active
@shared_runners_count = @shared_runners.count(:all) @shared_runners_count = @shared_runners.count(:all)
end end
def define_secret_variables def define_secret_variables
@variable = Ci::Variable.new(project: project) @variable = ::Ci::Variable.new(project: project)
.present(current_user: current_user) .present(current_user: current_user)
@variables = project.variables.order_key_asc @variables = project.variables.order_key_asc
.map { |variable| variable.present(current_user: current_user) } .map { |variable| variable.present(current_user: current_user) }
...@@ -42,7 +42,7 @@ module Projects ...@@ -42,7 +42,7 @@ module Projects
def define_triggers_variables def define_triggers_variables
@triggers = @project.triggers @triggers = @project.triggers
@trigger = Ci::Trigger.new @trigger = ::Ci::Trigger.new
end end
def define_badges_variables def define_badges_variables
......
...@@ -36,6 +36,6 @@ class Projects::VariablesController < Projects::ApplicationController ...@@ -36,6 +36,6 @@ class Projects::VariablesController < Projects::ApplicationController
end end
def variable_params_attributes def variable_params_attributes
%i[id key value protected _destroy] %i[id key secret_value protected _destroy]
end end
end end
...@@ -42,6 +42,10 @@ class RootController < Dashboard::ProjectsController ...@@ -42,6 +42,10 @@ class RootController < Dashboard::ProjectsController
redirect_to(dashboard_groups_path) redirect_to(dashboard_groups_path)
when 'todos' when 'todos'
redirect_to(dashboard_todos_path) redirect_to(dashboard_todos_path)
when 'issues'
redirect_to(issues_dashboard_path(assignee_id: current_user.id))
when 'merge_requests'
redirect_to(merge_requests_dashboard_path(assignee_id: current_user.id))
end end
end end
......
...@@ -31,7 +31,7 @@ module NamespacesHelper ...@@ -31,7 +31,7 @@ module NamespacesHelper
def namespace_icon(namespace, size = 40) def namespace_icon(namespace, size = 40)
if namespace.is_a?(Group) if namespace.is_a?(Group)
group_icon(namespace) group_icon_url(namespace)
else else
avatar_icon_for_user(namespace.owner, size) avatar_icon_for_user(namespace.owner, size)
end end
......
...@@ -39,7 +39,10 @@ module PageLayoutHelper ...@@ -39,7 +39,10 @@ module PageLayoutHelper
end end
def favicon def favicon
Rails.env.development? ? 'favicon-blue.ico' : 'favicon.ico' return 'favicon-yellow.ico' if Gitlab::Utils.to_boolean(ENV['CANARY'])
return 'favicon-blue.ico' if Rails.env.development?
'favicon.ico'
end end
def page_image def page_image
......
...@@ -9,12 +9,14 @@ module PreferencesHelper ...@@ -9,12 +9,14 @@ module PreferencesHelper
# Maps `dashboard` values to more user-friendly option text # Maps `dashboard` values to more user-friendly option text
DASHBOARD_CHOICES = { DASHBOARD_CHOICES = {
projects: 'Your Projects (default)', projects: _("Your Projects (default)"),
stars: 'Starred Projects', stars: _("Starred Projects"),
project_activity: "Your Projects' Activity", project_activity: _("Your Projects' Activity"),
starred_project_activity: "Starred Projects' Activity", starred_project_activity: _("Starred Projects' Activity"),
groups: "Your Groups", groups: _("Your Groups"),
todos: "Your Todos" todos: _("Your Todos"),
issues: _("Assigned Issues"),
merge_requests: _("Assigned Merge Requests")
}.with_indifferent_access.freeze }.with_indifferent_access.freeze
# Returns an Array usable by a select field for more user-friendly option text # Returns an Array usable by a select field for more user-friendly option text
......
...@@ -11,6 +11,14 @@ module Emails ...@@ -11,6 +11,14 @@ module Emails
mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason)) mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason))
end end
def push_to_merge_request_email(recipient_id, merge_request_id, updated_by_user_id, reason = nil, new_commits: [], existing_commits: [])
setup_merge_request_mail(merge_request_id, recipient_id)
@new_commits = new_commits
@existing_commits = existing_commits
mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason))
end
def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id, updated_by_user_id, reason = nil) def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id, updated_by_user_id, reason = nil)
setup_merge_request_mail(merge_request_id, recipient_id) setup_merge_request_mail(merge_request_id, recipient_id)
......
class Appearance < ActiveRecord::Base class Appearance < ActiveRecord::Base
include CacheMarkdownField include CacheMarkdownField
include AfterCommitQueue
include ObjectStorage::BackgroundMove
cache_markdown_field :description cache_markdown_field :description
cache_markdown_field :new_project_guidelines cache_markdown_field :new_project_guidelines
......
...@@ -3,6 +3,7 @@ module Ci ...@@ -3,6 +3,7 @@ module Ci
prepend ArtifactMigratable prepend ArtifactMigratable
include TokenAuthenticatable include TokenAuthenticatable
include AfterCommitQueue include AfterCommitQueue
include ObjectStorage::BackgroundMove
include Presentable include Presentable
include Importable include Importable
...@@ -23,6 +24,10 @@ module Ci ...@@ -23,6 +24,10 @@ module Ci
has_one :job_artifacts_metadata, -> { where(file_type: Ci::JobArtifact.file_types[:metadata]) }, class_name: 'Ci::JobArtifact', inverse_of: :job, foreign_key: :job_id has_one :job_artifacts_metadata, -> { where(file_type: Ci::JobArtifact.file_types[:metadata]) }, class_name: 'Ci::JobArtifact', inverse_of: :job, foreign_key: :job_id
has_one :job_artifacts_trace, -> { where(file_type: Ci::JobArtifact.file_types[:trace]) }, class_name: 'Ci::JobArtifact', inverse_of: :job, foreign_key: :job_id has_one :job_artifacts_trace, -> { where(file_type: Ci::JobArtifact.file_types[:trace]) }, class_name: 'Ci::JobArtifact', inverse_of: :job, foreign_key: :job_id
has_one :metadata, class_name: 'Ci::BuildMetadata'
delegate :timeout, to: :metadata, prefix: true, allow_nil: true
# The "environment" field for builds is a String, and is the unexpanded name # The "environment" field for builds is a String, and is the unexpanded name
def persisted_environment def persisted_environment
@persisted_environment ||= Environment.find_by( @persisted_environment ||= Environment.find_by(
...@@ -45,6 +50,7 @@ module Ci ...@@ -45,6 +50,7 @@ module Ci
where('(artifacts_file IS NOT NULL AND artifacts_file <> ?) OR EXISTS (?)', where('(artifacts_file IS NOT NULL AND artifacts_file <> ?) OR EXISTS (?)',
'', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').archive) '', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').archive)
end end
scope :with_artifacts_stored_locally, -> { with_artifacts_archive.where(artifacts_file_store: [nil, LegacyArtifactUploader::Store::LOCAL]) }
scope :with_artifacts_not_expired, ->() { with_artifacts_archive.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) } scope :with_artifacts_not_expired, ->() { with_artifacts_archive.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) }
scope :with_expired_artifacts, ->() { with_artifacts_archive.where('artifacts_expire_at < ?', Time.now) } scope :with_expired_artifacts, ->() { with_artifacts_archive.where('artifacts_expire_at < ?', Time.now) }
scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) } scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) }
...@@ -151,6 +157,14 @@ module Ci ...@@ -151,6 +157,14 @@ module Ci
before_transition any => [:running] do |build| before_transition any => [:running] do |build|
build.validates_dependencies! unless Feature.enabled?('ci_disable_validates_dependencies') build.validates_dependencies! unless Feature.enabled?('ci_disable_validates_dependencies')
end end
before_transition pending: :running do |build|
build.ensure_metadata.update_timeout_state
end
end
def ensure_metadata
metadata || build_metadata(project: project)
end end
def detailed_status(current_user) def detailed_status(current_user)
...@@ -229,10 +243,6 @@ module Ci ...@@ -229,10 +243,6 @@ module Ci
latest_builds.where('stage_idx < ?', stage_idx) latest_builds.where('stage_idx < ?', stage_idx)
end end
def timeout
project.build_timeout
end
def triggered_by?(current_user) def triggered_by?(current_user)
user == current_user user == current_user
end end
...@@ -365,13 +375,19 @@ module Ci ...@@ -365,13 +375,19 @@ module Ci
project.running_or_pending_build_count(force: true) project.running_or_pending_build_count(force: true)
end end
def browsable_artifacts?
artifacts_metadata?
end
def artifacts_metadata_entry(path, **options) def artifacts_metadata_entry(path, **options)
metadata = Gitlab::Ci::Build::Artifacts::Metadata.new( artifacts_metadata.use_file do |metadata_path|
artifacts_metadata.path, metadata = Gitlab::Ci::Build::Artifacts::Metadata.new(
path, metadata_path,
**options) path,
**options)
metadata.to_entry metadata.to_entry
end
end end
def erase_artifacts! def erase_artifacts!
......
module Ci
# The purpose of this class is to store Build related data that can be disposed.
# Data that should be persisted forever, should be stored with Ci::Build model.
class BuildMetadata < ActiveRecord::Base
extend Gitlab::Ci::Model
include Presentable
include ChronicDurationAttribute
self.table_name = 'ci_builds_metadata'
belongs_to :build, class_name: 'Ci::Build'
belongs_to :project
validates :build, presence: true
validates :project, presence: true
chronic_duration_attr_reader :timeout_human_readable, :timeout
enum timeout_source: {
unknown_timeout_source: 1,
project_timeout_source: 2,
runner_timeout_source: 3
}
def update_timeout_state
return unless build.runner.present?
project_timeout = project&.build_timeout
timeout = [project_timeout, build.runner.maximum_timeout].compact.min
timeout_source = timeout < project_timeout ? :runner_timeout_source : :project_timeout_source
update(timeout: timeout, timeout_source: timeout_source)
end
end
end
...@@ -6,6 +6,8 @@ module Ci ...@@ -6,6 +6,8 @@ module Ci
belongs_to :group belongs_to :group
alias_attribute :secret_value, :value
validates :key, uniqueness: { validates :key, uniqueness: {
scope: :group_id, scope: :group_id,
message: "(%{value}) has already been taken" message: "(%{value}) has already been taken"
......
module Ci module Ci
class JobArtifact < ActiveRecord::Base class JobArtifact < ActiveRecord::Base
include AfterCommitQueue
include ObjectStorage::BackgroundMove
extend Gitlab::Ci::Model extend Gitlab::Ci::Model
belongs_to :project belongs_to :project
...@@ -7,9 +9,11 @@ module Ci ...@@ -7,9 +9,11 @@ module Ci
before_save :set_size, if: :file_changed? before_save :set_size, if: :file_changed?
scope :with_files_stored_locally, -> { where(file_store: [nil, ::JobArtifactUploader::Store::LOCAL]) }
mount_uploader :file, JobArtifactUploader mount_uploader :file, JobArtifactUploader
delegate :open, :exists?, to: :file delegate :exists?, :open, to: :file
enum file_type: { enum file_type: {
archive: 1, archive: 1,
...@@ -21,6 +25,10 @@ module Ci ...@@ -21,6 +25,10 @@ module Ci
self.where(project: project).sum(:size) self.where(project: project).sum(:size)
end end
def local_store?
[nil, ::JobArtifactUploader::Store::LOCAL].include?(self.file_store)
end
def set_size def set_size
self.size = file.size self.size = file.size
end end
......
...@@ -6,6 +6,7 @@ module Ci ...@@ -6,6 +6,7 @@ module Ci
include AfterCommitQueue include AfterCommitQueue
include Presentable include Presentable
include Gitlab::OptimisticLocking include Gitlab::OptimisticLocking
include Gitlab::Utils::StrongMemoize
belongs_to :project, inverse_of: :pipelines belongs_to :project, inverse_of: :pipelines
belongs_to :user belongs_to :user
...@@ -361,21 +362,23 @@ module Ci ...@@ -361,21 +362,23 @@ module Ci
def stage_seeds def stage_seeds
return [] unless config_processor return [] unless config_processor
@stage_seeds ||= config_processor.stage_seeds(self) strong_memoize(:stage_seeds) do
seeds = config_processor.stages_attributes.map do |attributes|
Gitlab::Ci::Pipeline::Seed::Stage.new(self, attributes)
end
seeds.select(&:included?)
end
end end
def seeds_size def seeds_size
@seeds_size ||= stage_seeds.sum(&:size) stage_seeds.sum(&:size)
end end
def has_kubernetes_active? def has_kubernetes_active?
project.deployment_platform&.active? project.deployment_platform&.active?
end end
def has_stage_seeds?
stage_seeds.any?
end
def has_warnings? def has_warnings?
builds.latest.failed_but_allowed.any? builds.latest.failed_but_allowed.any?
end end
...@@ -388,6 +391,9 @@ module Ci ...@@ -388,6 +391,9 @@ module Ci
end end
end end
##
# TODO, setting yaml_errors should be moved to the pipeline creation chain.
#
def config_processor def config_processor
return unless ci_yaml_file return unless ci_yaml_file
return @config_processor if defined?(@config_processor) return @config_processor if defined?(@config_processor)
...@@ -472,6 +478,14 @@ module Ci ...@@ -472,6 +478,14 @@ module Ci
end end
end end
def protected_ref?
strong_memoize(:protected_ref) { project.protected_for?(ref) }
end
def legacy_trigger
strong_memoize(:legacy_trigger) { trigger_requests.first }
end
def predefined_variables def predefined_variables
Gitlab::Ci::Variables::Collection.new Gitlab::Ci::Variables::Collection.new
.append(key: 'CI_PIPELINE_ID', value: id.to_s) .append(key: 'CI_PIPELINE_ID', value: id.to_s)
......
...@@ -5,6 +5,8 @@ module Ci ...@@ -5,6 +5,8 @@ module Ci
belongs_to :pipeline_schedule belongs_to :pipeline_schedule
alias_attribute :secret_value, :value
validates :key, uniqueness: { scope: :pipeline_schedule_id } validates :key, uniqueness: { scope: :pipeline_schedule_id }
end end
end end
...@@ -3,12 +3,13 @@ module Ci ...@@ -3,12 +3,13 @@ module Ci
extend Gitlab::Ci::Model extend Gitlab::Ci::Model
include Gitlab::SQL::Pattern include Gitlab::SQL::Pattern
include RedisCacheable include RedisCacheable
include ChronicDurationAttribute
RUNNER_QUEUE_EXPIRY_TIME = 60.minutes RUNNER_QUEUE_EXPIRY_TIME = 60.minutes
ONLINE_CONTACT_TIMEOUT = 1.hour ONLINE_CONTACT_TIMEOUT = 1.hour
UPDATE_DB_RUNNER_INFO_EVERY = 40.minutes UPDATE_DB_RUNNER_INFO_EVERY = 40.minutes
AVAILABLE_SCOPES = %w[specific shared active paused online].freeze AVAILABLE_SCOPES = %w[specific shared active paused online].freeze
FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level].freeze FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level maximum_timeout_human_readable].freeze
has_many :builds has_many :builds
has_many :runner_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :runner_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
...@@ -51,6 +52,12 @@ module Ci ...@@ -51,6 +52,12 @@ module Ci
cached_attr_reader :version, :revision, :platform, :architecture, :contacted_at, :ip_address cached_attr_reader :version, :revision, :platform, :architecture, :contacted_at, :ip_address
chronic_duration_attr :maximum_timeout_human_readable, :maximum_timeout
validates :maximum_timeout, allow_nil: true,
numericality: { greater_than_or_equal_to: 600,
message: 'needs to be at least 10 minutes' }
# Searches for runners matching the given query. # Searches for runners matching the given query.
# #
# This method uses ILIKE on PostgreSQL and LIKE on MySQL. # This method uses ILIKE on PostgreSQL and LIKE on MySQL.
......
...@@ -6,6 +6,8 @@ module Ci ...@@ -6,6 +6,8 @@ module Ci
belongs_to :project belongs_to :project
alias_attribute :secret_value, :value
validates :key, uniqueness: { validates :key, uniqueness: {
scope: [:project_id, :environment_scope], scope: [:project_id, :environment_scope],
message: "(%{value}) has already been taken" message: "(%{value}) has already been taken"
......
...@@ -10,6 +10,7 @@ module Clusters ...@@ -10,6 +10,7 @@ module Clusters
Applications::Prometheus.application_name => Applications::Prometheus, Applications::Prometheus.application_name => Applications::Prometheus,
Applications::Runner.application_name => Applications::Runner Applications::Runner.application_name => Applications::Runner
}.freeze }.freeze
DEFAULT_ENVIRONMENT = '*'.freeze
belongs_to :user belongs_to :user
...@@ -50,6 +51,7 @@ module Clusters ...@@ -50,6 +51,7 @@ module Clusters
scope :enabled, -> { where(enabled: true) } scope :enabled, -> { where(enabled: true) }
scope :disabled, -> { where(enabled: false) } scope :disabled, -> { where(enabled: false) }
scope :default_environment, -> { where(environment_scope: DEFAULT_ENVIRONMENT) }
def status_name def status_name
if provider if provider
......
...@@ -175,7 +175,7 @@ class Commit ...@@ -175,7 +175,7 @@ class Commit
if safe_message.blank? if safe_message.blank?
no_commit_message no_commit_message
else else
safe_message.split("\n", 2).first safe_message.split(/[\r\n]/, 2).first
end end
end end
......
...@@ -3,6 +3,7 @@ module Avatarable ...@@ -3,6 +3,7 @@ module Avatarable
included do included do
prepend ShadowMethods prepend ShadowMethods
include ObjectStorage::BackgroundMove
validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? } validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
validates :avatar, file_size: { maximum: 200.kilobytes.to_i } validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
......
module ChronicDurationAttribute
extend ActiveSupport::Concern
class_methods do
def chronic_duration_attr_reader(virtual_attribute, source_attribute)
define_method(virtual_attribute) do
chronic_duration_attributes[virtual_attribute] || output_chronic_duration_attribute(source_attribute)
end
end
def chronic_duration_attr_writer(virtual_attribute, source_attribute)
chronic_duration_attr_reader(virtual_attribute, source_attribute)
define_method("#{virtual_attribute}=") do |value|
chronic_duration_attributes[virtual_attribute] = value.presence || ''
begin
new_value = ChronicDuration.parse(value).to_i if value.present?
assign_attributes(source_attribute => new_value)
rescue ChronicDuration::DurationParseError
# ignore error as it will be caught by validation
end
end
validates virtual_attribute, allow_nil: true, duration: true
end
alias_method :chronic_duration_attr, :chronic_duration_attr_writer
end
def chronic_duration_attributes
@chronic_duration_attributes ||= {}
end
def output_chronic_duration_attribute(source_attribute)
value = attributes[source_attribute.to_s]
ChronicDuration.output(value, format: :short) if value
end
end
module DeploymentPlatform module DeploymentPlatform
# EE would override this and utilize the extra argument # EE would override this and utilize environment argument
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def deployment_platform(environment: nil) def deployment_platform(environment: nil)
@deployment_platform ||= @deployment_platform ||= {}
find_cluster_platform_kubernetes ||
find_kubernetes_service_integration || @deployment_platform[environment] ||= find_deployment_platform(environment)
build_cluster_and_deployment_platform
end end
private private
def find_cluster_platform_kubernetes def find_deployment_platform(environment)
clusters.find_by(enabled: true)&.platform_kubernetes find_cluster_platform_kubernetes(environment: environment) ||
find_kubernetes_service_integration ||
build_cluster_and_deployment_platform
end
# EE would override this and utilize environment argument
def find_cluster_platform_kubernetes(environment: nil)
clusters.enabled.default_environment
.last&.platform_kubernetes
end end
def find_kubernetes_service_integration def find_kubernetes_service_integration
......
...@@ -27,6 +27,10 @@ class DeployKey < Key ...@@ -27,6 +27,10 @@ class DeployKey < Key
self.private? self.private?
end end
def user
super || User.ghost
end
def has_access_to?(project) def has_access_to?(project)
deploy_keys_project_for(project).present? deploy_keys_project_for(project).present?
end end
......
...@@ -52,12 +52,12 @@ class Event < ActiveRecord::Base ...@@ -52,12 +52,12 @@ class Event < ActiveRecord::Base
belongs_to :target, -> { belongs_to :target, -> {
# If the association for "target" defines an "author" association we want to # If the association for "target" defines an "author" association we want to
# eager-load this so Banzai & friends don't end up performing N+1 queries to # eager-load this so Banzai & friends don't end up performing N+1 queries to
# get the authors of notes, issues, etc. # get the authors of notes, issues, etc. (likewise for "noteable").
if reflections['events'].active_record.reflect_on_association(:author) incs = %i(author noteable).select do |a|
includes(:author) reflections['events'].active_record.reflect_on_association(a)
else
self
end end
incs.reduce(self) { |obj, a| obj.includes(a) }
}, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations }, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
has_one :push_event_payload has_one :push_event_payload
......
class LfsObject < ActiveRecord::Base class LfsObject < ActiveRecord::Base
include AfterCommitQueue
include ObjectStorage::BackgroundMove
has_many :lfs_objects_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :lfs_objects_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :projects, through: :lfs_objects_projects has_many :projects, through: :lfs_objects_projects
scope :with_files_stored_locally, -> { where(file_store: [nil, LfsObjectUploader::Store::LOCAL]) }
validates :oid, presence: true, uniqueness: true validates :oid, presence: true, uniqueness: true
mount_uploader :file, LfsObjectUploader mount_uploader :file, LfsObjectUploader
before_save :update_file_store
def update_file_store
self.file_store = file.object_store
end
def project_allowed_access?(project) def project_allowed_access?(project)
projects.exists?(project.lfs_storage_project.id) projects.exists?(project.lfs_storage_project.id)
end end
def local_store?
[nil, LfsObjectUploader::Store::LOCAL].include?(self.file_store)
end
def self.destroy_unreferenced def self.destroy_unreferenced
joins("LEFT JOIN lfs_objects_projects ON lfs_objects_projects.lfs_object_id = #{table_name}.id") joins("LEFT JOIN lfs_objects_projects ON lfs_objects_projects.lfs_object_id = #{table_name}.id")
.where(lfs_objects_projects: { id: nil }) .where(lfs_objects_projects: { id: nil })
......
...@@ -536,18 +536,25 @@ class MergeRequest < ActiveRecord::Base ...@@ -536,18 +536,25 @@ class MergeRequest < ActiveRecord::Base
merge_request_diff(true) merge_request_diff(true)
end end
def viewable_diffs
@viewable_diffs ||= merge_request_diffs.viewable.to_a
end
def merge_request_diff_for(diff_refs_or_sha) def merge_request_diff_for(diff_refs_or_sha)
@merge_request_diffs_by_diff_refs_or_sha ||= Hash.new do |h, diff_refs_or_sha| matcher =
diffs = merge_request_diffs.viewable if diff_refs_or_sha.is_a?(Gitlab::Diff::DiffRefs)
h[diff_refs_or_sha] = {
if diff_refs_or_sha.is_a?(Gitlab::Diff::DiffRefs) 'start_commit_sha' => diff_refs_or_sha.start_sha,
diffs.find_by_diff_refs(diff_refs_or_sha) 'head_commit_sha' => diff_refs_or_sha.head_sha,
else 'base_commit_sha' => diff_refs_or_sha.base_sha
diffs.find_by(head_commit_sha: diff_refs_or_sha) }
end else
end { 'head_commit_sha' => diff_refs_or_sha }
end
@merge_request_diffs_by_diff_refs_or_sha[diff_refs_or_sha] viewable_diffs.find do |diff|
diff.attributes.slice(*matcher.keys) == matcher
end
end end
def version_params_for(diff_refs) def version_params_for(diff_refs)
......
...@@ -48,7 +48,7 @@ class NotificationRecipient ...@@ -48,7 +48,7 @@ class NotificationRecipient
when :custom when :custom
custom_enabled? || %i[participating mention].include?(@type) custom_enabled? || %i[participating mention].include?(@type)
when :watch, :participating when :watch, :participating
!excluded_watcher_action? !action_excluded?
when :mention when :mention
@type == :mention @type == :mention
else else
...@@ -96,13 +96,22 @@ class NotificationRecipient ...@@ -96,13 +96,22 @@ class NotificationRecipient
end end
end end
def action_excluded?
excluded_watcher_action? || excluded_participating_action?
end
def excluded_watcher_action? def excluded_watcher_action?
return false unless @custom_action return false unless @custom_action && notification_level == :watch
return false if notification_level == :custom
NotificationSetting::EXCLUDED_WATCHER_EVENTS.include?(@custom_action) NotificationSetting::EXCLUDED_WATCHER_EVENTS.include?(@custom_action)
end end
def excluded_participating_action?
return false unless @custom_action && notification_level == :participating
NotificationSetting::EXCLUDED_PARTICIPATING_EVENTS.include?(@custom_action)
end
private private
def read_ability def read_ability
......
...@@ -33,6 +33,7 @@ class NotificationSetting < ActiveRecord::Base ...@@ -33,6 +33,7 @@ class NotificationSetting < ActiveRecord::Base
:close_issue, :close_issue,
:reassign_issue, :reassign_issue,
:new_merge_request, :new_merge_request,
:push_to_merge_request,
:reopen_merge_request, :reopen_merge_request,
:close_merge_request, :close_merge_request,
:reassign_merge_request, :reassign_merge_request,
...@@ -41,10 +42,14 @@ class NotificationSetting < ActiveRecord::Base ...@@ -41,10 +42,14 @@ class NotificationSetting < ActiveRecord::Base
:success_pipeline :success_pipeline
].freeze ].freeze
EXCLUDED_WATCHER_EVENTS = [ EXCLUDED_PARTICIPATING_EVENTS = [
:success_pipeline :success_pipeline
].freeze ].freeze
EXCLUDED_WATCHER_EVENTS = [
:push_to_merge_request
].push(*EXCLUDED_PARTICIPATING_EVENTS).freeze
def self.find_or_create_for(source) def self.find_or_create_for(source)
setting = find_or_initialize_by(source: source) setting = find_or_initialize_by(source: source)
......
...@@ -36,7 +36,7 @@ class GemnasiumService < Service ...@@ -36,7 +36,7 @@ class GemnasiumService < Service
after: data[:after], after: data[:after],
token: token, token: token,
api_key: api_key, api_key: api_key,
repo: project.repository.path_to_repo repo: project.repository.path_to_repo # Gitaly: fixed by https://gitlab.com/gitlab-org/security-products/gemnasium-migration/issues/9
) )
end end
end end
...@@ -17,32 +17,4 @@ class RedirectRoute < ActiveRecord::Base ...@@ -17,32 +17,4 @@ class RedirectRoute < ActiveRecord::Base
where(wheres, path, "#{sanitize_sql_like(path)}/%") where(wheres, path, "#{sanitize_sql_like(path)}/%")
end end
scope :permanent, -> do
if column_permanent_exists?
where(permanent: true)
else
none
end
end
scope :temporary, -> do
if column_permanent_exists?
where(permanent: [false, nil])
else
all
end
end
default_value_for :permanent, false
def permanent=(value)
if self.class.column_permanent_exists?
super
end
end
def self.column_permanent_exists?
ActiveRecord::Base.connection.column_exists?(:redirect_routes, :permanent)
end
end end
...@@ -93,10 +93,6 @@ class Repository ...@@ -93,10 +93,6 @@ class Repository
"#<#{self.class.name}:#{@disk_path}>" "#<#{self.class.name}:#{@disk_path}>"
end end
def create_hooks
Gitlab::Git::Repository.create_hooks(path_to_repo, Gitlab.config.gitlab_shell.hooks_path)
end
def commit(ref = 'HEAD') def commit(ref = 'HEAD')
return nil unless exists? return nil unless exists?
return ref if ref.is_a?(::Commit) return ref if ref.is_a?(::Commit)
...@@ -253,13 +249,13 @@ class Repository ...@@ -253,13 +249,13 @@ class Repository
end end
def diverging_commit_counts(branch) def diverging_commit_counts(branch)
root_ref_hash = raw_repository.commit(root_ref).id @root_ref_hash ||= raw_repository.commit(root_ref).id
cache.fetch(:"diverging_commit_counts_#{branch.name}") do cache.fetch(:"diverging_commit_counts_#{branch.name}") do
# Rugged seems to throw a `ReferenceError` when given branch_names rather # Rugged seems to throw a `ReferenceError` when given branch_names rather
# than SHA-1 hashes # than SHA-1 hashes
number_commits_behind, number_commits_ahead = number_commits_behind, number_commits_ahead =
raw_repository.count_commits_between( raw_repository.count_commits_between(
root_ref_hash, @root_ref_hash,
branch.dereferenced_target.sha, branch.dereferenced_target.sha,
left_right: true, left_right: true,
max_count: MAX_DIVERGING_COUNT) max_count: MAX_DIVERGING_COUNT)
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment