Commit d1052289 authored by Matija Čupić's avatar Matija Čupić

Merge branch 'master' into...

Merge branch 'master' into 38759-fetch-available-parameters-directly-from-gke-when-creating-a-cluster
parents e911a0f8 87e592dc

Too many changes to show.

To preserve performance only 1000 of 1000+ files are displayed.

{ {
"presets": [ "presets": [["latest", { "es2015": { "modules": false } }], "stage-2"],
["latest", { "es2015": { "modules": false } }],
"stage-2"
],
"env": { "env": {
"coverage": { "coverage": {
"plugins": [ "plugins": [
["istanbul", { [
"exclude": [ "istanbul",
"spec/javascripts/**/*", {
"app/assets/javascripts/locale/**/app.js" "exclude": ["spec/javascripts/**/*", "app/assets/javascripts/locale/**/app.js"]
] }
}], ],
["transform-define", { [
"process.env.BABEL_ENV": "coverage" "transform-define",
}] {
"process.env.BABEL_ENV": "coverage"
}
]
] ]
} }
} }
......
...@@ -10,12 +10,6 @@ engines: ...@@ -10,12 +10,6 @@ engines:
- javascript - javascript
exclude_paths: exclude_paths:
- "lib/api/v3/*" - "lib/api/v3/*"
eslint:
enabled: true
channel: "eslint-4"
rubocop:
enabled: true
channel: "gitlab-rubocop-0-52-1"
ratings: ratings:
paths: paths:
- Gemfile.lock - Gemfile.lock
......
/app/assets/javascripts/locale/**/app.js
/config/
/builds/ /builds/
/coverage/ /coverage/
/coverage-javascript/ /coverage-javascript/
/node_modules/ /node_modules/
/public/ /public/
/scripts/
/tmp/ /tmp/
/vendor/ /vendor/
karma.config.js karma.config.js
webpack.config.js webpack.config.js
svg.config.js
/app/assets/javascripts/locale/**/app.js
...@@ -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
......
This diff is collapsed.
<!--
# Read me first!
Create this issue under https://dev.gitlab.org/gitlab/gitlabhq
Set the title to: `[Security] Description of the original issue`
-->
### Prior to the security release
- [ ] Read the [security process for developers] if you are not familiar with it.
- [ ] Link to the original issue adding it to the [links section](#links)
- [ ] Run `scripts/security-harness` in the CE, EE, and/or Omnibus to prevent pushing to any remote besides `dev.gitlab.org`
- [ ] Create an MR targetting `org` `master`, prefixing your branch with `security-`
- [ ] Label your MR with the ~security label, prefix the title with `WIP: [master]`
- [ ] Add a link to the MR to the [links section](#links)
- [ ] Add a link to an EE MR if required
- [ ] Make sure the MR remains in-progress and gets approved after the review cycle, **but never merged**.
- [ ] Assign the MR to a RM once is reviewed and ready to be merged. Check the [RM list] to see who to ping.
#### Backports
- [ ] Once the MR is ready to be merged, create MRs targetting the last 3 releases
- [ ] At this point, it might be easy to squash the commits from the MR into one
- You can use the script `bin/secpick` instead of the following steps, to help you cherry-picking. See the [seckpick documentation]
- [ ] Create the branch `security-X-Y` from `X-Y-stable` if it doesn't exist (and make sure it's up to date with stable)
- [ ] Create each MR targetting the security branch `security-X-Y`
- [ ] Add the ~security label and prefix with the version `WIP: [X.Y]` the title of the MR
- [ ] Make sure all MRs have a link in the [links section](#links) and are assigned to a Release Manager.
[seckpick documentation]: https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/process.md#secpick-script
#### Documentation and final details
- [ ] Check the topic on #security to see when the next release is going ot happen and add a link to the [links section](#links)
- [ ] Find out the versions affected (the Git history of the files affected may help you with this) and add them to the [details section](#details)
- [ ] Fill in any upgrade notes that users may need to take into account in the [details section](#details)
- [ ] Add Yes/No and further details if needed to the migration and settings columns in the [details section](#details)
- [ ] Add the nickname of the external user who found the issue (and/or HackerOne profile) to the Thanks row in the [details section](#details)
### Summary
#### Links
| Description | Link |
| -------- | -------- |
| Original issue | #TODO |
| Security release issue | #TODO |
| `master` MR | !TODO |
| `master` MR (EE) | !TODO |
| `Backport X.Y` MR | !TODO |
| `Backport X.Y` MR | !TODO |
| `Backport X.Y` MR | !TODO |
| `Backport X.Y` MR (EE) | !TODO |
| `Backport X.Y` MR (EE) | !TODO |
| `Backport X.Y` MR (EE) | !TODO |
#### Details
| Description | Details | Further details|
| -------- | -------- | -------- |
| Versions affected | X.Y | |
| Upgrade notes | | |
| GitLab Settings updated | Yes/No| |
| Migration required | Yes/No | |
| Thanks | | |
[security process for developers]: https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md
[RM list]: https://about.gitlab.com/release-managers/
/label ~security
...@@ -33,13 +33,16 @@ When removing columns, tables, indexes or other structures: ...@@ -33,13 +33,16 @@ When removing columns, tables, indexes or other structures:
## General Checklist ## General Checklist
- [ ] [Changelog entry](https://docs.gitlab.com/ce/development/changelog.html) added, if necessary - [ ] [Changelog entry](https://docs.gitlab.com/ee/development/changelog.html) added, if necessary
- [ ] [Documentation created/updated](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/doc_styleguide.md) - [ ] [Documentation created/updated](https://docs.gitlab.com/ee/development/doc_styleguide.html)
- [ ] API support added - [ ] API support added
- [ ] Tests added for this feature/bug - [ ] Tests added for this feature/bug
- Review - Review
- [ ] Has been reviewed by Backend - [ ] Has been reviewed by Backend
- [ ] Has been reviewed by Database - [ ] Has been reviewed by Database
- [ ] Conform by the [merge request performance guides](http://docs.gitlab.com/ce/development/merge_request_performance_guidelines.html) - [ ] Conform by the [merge request performance guides](https://docs.gitlab.com/ee/development/merge_request_performance_guidelines.html)
- [ ] Conform by the [style guides](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#style-guides) - [ ] Conform by the [style guides](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/CONTRIBUTING.md#style-guides)
- [ ] [Squashed related commits together](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits) - [ ] [Squashed related commits together](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)
- [ ] Internationalization required/considered
- [ ] If paid feature, have we considered GitLab.com plan and how it works for groups and is there a design for promoting it to users who aren't on the correct plan
- [ ] End-to-end tests pass (`package-and-qa` manual pipeline job)
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
/app/assets/javascripts/locale/**/app.js
/node_modules/
/public/
/vendor/
/tmp/
{ {
"printWidth": 100,
"singleQuote": true, "singleQuote": true,
"trailingComma": "all" "trailingComma": "es5",
"overrides": [
{
"files": ["**/app/**/*", "**/spec/**/*"],
"options": {
"trailingComma": "all"
}
}
]
} }
...@@ -118,6 +118,9 @@ Gitlab/ModuleWithInstanceVariables: ...@@ -118,6 +118,9 @@ Gitlab/ModuleWithInstanceVariables:
- spec/support/**/*.rb - spec/support/**/*.rb
- features/steps/**/*.rb - features/steps/**/*.rb
Gitlab/HTTParty:
Enabled: true
GitlabSecurity/PublicSend: GitlabSecurity/PublicSend:
Enabled: true Enabled: true
Exclude: Exclude:
......
...@@ -59,6 +59,8 @@ linters: ...@@ -59,6 +59,8 @@ linters:
# Reports when you define the same property twice in a single rule set. # Reports when you define the same property twice in a single rule set.
DuplicateProperty: DuplicateProperty:
enabled: true enabled: true
ignore_consecutive:
- cursor
# Separate rule, function, and mixin declarations with empty lines. # Separate rule, function, and mixin declarations with empty lines.
EmptyLineBetweenBlocks: EmptyLineBetweenBlocks:
......
This diff is collapsed.
...@@ -126,7 +126,7 @@ Most issues will have labels for at least one of the following: ...@@ -126,7 +126,7 @@ Most issues will have labels for at least one of the following:
- Type: ~"feature proposal", ~bug, ~customer, etc. - Type: ~"feature proposal", ~bug, ~customer, etc.
- Subject: ~wiki, ~"container registry", ~ldap, ~api, ~frontend, etc. - Subject: ~wiki, ~"container registry", ~ldap, ~api, ~frontend, etc.
- Team: ~"CI/CD", ~Discussion, ~Edge, ~Platform, etc. - Team: ~"CI/CD", ~Discussion, ~Edge, ~Platform, etc.
- Priority: ~Deliverable, ~Stretch, ~"Next Patch Release" - Milestone: ~Deliverable, ~Stretch, ~"Next Patch Release"
All labels, their meaning and priority are defined on the All labels, their meaning and priority are defined on the
[labels page][labels-page]. [labels page][labels-page].
...@@ -185,10 +185,10 @@ indicate if an issue needs backend work, frontend work, or both. ...@@ -185,10 +185,10 @@ indicate if an issue needs backend work, frontend work, or both.
Team labels are always capitalized so that they show up as the first label for Team labels are always capitalized so that they show up as the first label for
any issue. any issue.
### Priority labels (~Deliverable, ~Stretch, ~"Next Patch Release") ### Milestone labels (~Deliverable, ~Stretch, ~"Next Patch Release")
Priority labels help us clearly communicate expectations of the work for the Milestone labels help us clearly communicate expectations of the work for the
release. There are two levels of priority labels: release. There are three levels of Milestone labels:
- ~Deliverable: Issues that are expected to be delivered in the current - ~Deliverable: Issues that are expected to be delivered in the current
milestone. milestone.
...@@ -203,16 +203,46 @@ Each issue scheduled for the current milestone should be labeled ~Deliverable ...@@ -203,16 +203,46 @@ Each issue scheduled for the current milestone should be labeled ~Deliverable
or ~"Stretch". Any open issue for a previous milestone should be labeled or ~"Stretch". Any open issue for a previous milestone should be labeled
~"Next Patch Release", or otherwise rescheduled to a different milestone. ~"Next Patch Release", or otherwise rescheduled to a different milestone.
### Severity labels (~S1, ~S2, etc.) ### Bug Priority labels (~P1, ~P2, ~P3 & etc.)
Severity labels help us clearly communicate the impact of a ~bug on users. Bug Priority labels help us define the time a ~bug fix should be completed. Priority determines how quickly the defect turnaround time must be. If there are multiple defects, the priority decides which defect has to be fixed immediately versus later.
This label documents the planned timeline & urgency which is used to measure against our actual SLA on delivering ~bug fixes.
| Label | Meaning | Example | | Label | Meaning | Estimate time to fix | Guidance |
|-------|------------------------------------------|---------| |-------|-----------------|------------------------------------------------------------------|----------|
| ~S1 | Feature broken, no workaround | Unable to create an issue | | ~P1 | Urgent Priority | The current release | |
| ~S2 | Feature broken, workaround unacceptable | Can push commits, but only via the command line | | ~P2 | High Priority | The next release | |
| ~S3 | Feature broken, workaround acceptable | Can create merge requests only from the Merge Requests page, not through the Issue | | ~P3 | Medium Priority | Within the next 3 releases (approx one quarter) | |
| ~S4 | Cosmetic issue | Label colors are incorrect / not being displayed | | ~P4 | Low Priority | Anything outside the next 3 releases (approx beyond one quarter) | The issue is prominent but does not impact user workflow and a workaround is documented |
#### Specific Priority guidance
| Label | Availability / Performance |
|-------|--------------------------------------------------------------|
| ~P1 | |
| ~P2 | The issue is (almost) guaranteed to occur in the near future |
| ~P3 | The issue is likely to occur in the near future |
| ~P4 | The issue _may_ occur but it's not likely |
### Bug Severity labels (~S1, ~S2, ~S3 & etc.)
Severity labels help us clearly communicate the impact of a ~bug on users.
| Label | Meaning | Impact of the defect | Example |
|-------|-------------------|-------------------------------------------------------|---------|
| ~S1 | Blocker | Outage, broken feature with no workaround | Unable to create an issue. Data corruption/loss. Security breach. |
| ~S2 | Critical Severity | Broken Feature, workaround too complex & unacceptable | Can push commits, but only via the command line. |
| ~S3 | Major Severity | Broken Feature, workaround acceptable | Can create merge requests only from the Merge Requests page, not through the Issue. |
| ~S4 | Low Severity | Functionality inconvenience or cosmetic issue | Label colors are incorrect / not being displayed. |
#### Specific Severity guidance
| Label | Security Impact |
|-------|-------------------------------------------------------------------|
| ~S1 | >50% customers impacted (possible company extinction level event) |
| ~S2 | Multiple customers impacted (but not apocalyptic) |
| ~S3 | A single customer impacted |
| ~S4 | No customer impact, or expected impact within 30 days |
### Label for community contributors (~"Accepting Merge Requests") ### Label for community contributors (~"Accepting Merge Requests")
......
# --- Special code for migrating to Rails 5.0 ---
def rails5?
%w[1 true].include?(ENV["RAILS5"])
end
gem_versions = {}
gem_versions['activerecord_sane_schema_dumper'] = rails5? ? '1.0' : '0.2'
gem_versions['default_value_for'] = rails5? ? '~> 3.0.5' : '~> 3.0.0'
gem_versions['rails'] = rails5? ? '5.0.6' : '4.2.10'
gem_versions['rails-i18n'] = rails5? ? '~> 5.1' : '~> 4.0.9'
# --- The end of special code for migrating to Rails 5.0 ---
source 'https://rubygems.org' source 'https://rubygems.org'
gem 'rails', '4.2.10' gem 'rails', gem_versions['rails']
gem 'rails-deprecated_sanitizer', '~> 1.0.3' gem 'rails-deprecated_sanitizer', '~> 1.0.3'
# Responders respond_to and respond_with # Responders respond_to and respond_with
...@@ -9,36 +21,37 @@ gem 'responders', '~> 2.0' ...@@ -9,36 +21,37 @@ gem 'responders', '~> 2.0'
gem 'sprockets', '~> 3.7.0' gem 'sprockets', '~> 3.7.0'
# Default values for AR models # Default values for AR models
gem 'default_value_for', '~> 3.0.0' gem 'default_value_for', gem_versions['default_value_for']
# Supported DBs # Supported DBs
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'
# Authentication libraries # Authentication libraries
gem 'devise', '~> 4.2' gem 'devise', '~> 4.2'
gem 'doorkeeper', '~> 4.2.0' gem 'doorkeeper', '~> 4.3'
gem 'doorkeeper-openid_connect', '~> 1.2.0' gem 'doorkeeper-openid_connect', '~> 1.3'
gem 'omniauth', '~> 1.4.2' gem 'omniauth', '~> 1.8'
gem 'omniauth-auth0', '~> 1.4.1' gem 'omniauth-auth0', '~> 2.0.0'
gem 'omniauth-azure-oauth2', '~> 0.0.9' gem 'omniauth-azure-oauth2', '~> 0.0.9'
gem 'omniauth-cas3', '~> 1.1.4' 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.0' 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'
...@@ -49,7 +62,7 @@ gem 'akismet', '~> 2.0' ...@@ -49,7 +62,7 @@ gem 'akismet', '~> 2.0'
# Two-factor authentication # Two-factor authentication
gem 'devise-two-factor', '~> 3.0.0' gem 'devise-two-factor', '~> 3.0.0'
gem 'rqrcode-rails3', '~> 0.1.7' gem 'rqrcode-rails3', '~> 0.1.7'
gem 'attr_encrypted', '~> 3.0.0' gem 'attr_encrypted', '~> 3.1.0'
gem 'u2f', '~> 0.2.1' gem 'u2f', '~> 0.2.1'
# GitLab Pages # GitLab Pages
...@@ -69,16 +82,9 @@ gem 'net-ldap' ...@@ -69,16 +82,9 @@ gem 'net-ldap'
# Git Wiki # Git Wiki
# Required manually in config/initializers/gollum.rb to control load order # Required manually in config/initializers/gollum.rb to control load order
# Before updating this gem, check if gem 'gitlab-gollum-lib', '~> 4.2', require: false
# https://github.com/gollum/gollum-lib/pull/292 has been merged.
# If it has, then remove the monkey patch for update_page, rename_page and raw_data_in_committer
# in config/initializers/gollum.rb
gem 'gollum-lib', '~> 4.2', require: false
# Before updating this gem, check if gem 'gitlab-gollum-rugged_adapter', '~> 0.4.4', require: false
# https://github.com/gollum/rugged_adapter/pull/28 has been merged.
# If it has, then remove the monkey patch for tree_entry in config/initializers/gollum.rb
gem 'gollum-rugged_adapter', '~> 0.4.4', require: false
# Language detection # Language detection
gem 'github-linguist', '~> 5.3.3', require: 'linguist' gem 'github-linguist', '~> 5.3.3', require: 'linguist'
...@@ -104,16 +110,16 @@ gem 'carrierwave', '~> 1.2' ...@@ -104,16 +110,16 @@ 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'
gem 'fog-aliyun', '~> 0.2.0' gem 'fog-aliyun', '~> 0.2.0'
# for Google storage # for Google storage
gem 'google-api-client', '~> 0.13.6' gem 'google-api-client', '~> 0.19.8'
# for aws storage # for aws storage
gem 'unf', '~> 0.1.4' gem 'unf', '~> 0.1.4'
...@@ -122,7 +128,7 @@ gem 'unf', '~> 0.1.4' ...@@ -122,7 +128,7 @@ gem 'unf', '~> 0.1.4'
gem 'seed-fu', '~> 2.3.7' gem 'seed-fu', '~> 2.3.7'
# Markdown and HTML processing # Markdown and HTML processing
gem 'html-pipeline', '~> 1.11.0' gem 'html-pipeline', '~> 2.7.1'
gem 'deckar01-task_list', '2.0.0' gem 'deckar01-task_list', '2.0.0'
gem 'gitlab-markup', '~> 1.6.2' gem 'gitlab-markup', '~> 1.6.2'
gem 'redcarpet', '~> 3.4' gem 'redcarpet', '~> 3.4'
...@@ -132,9 +138,9 @@ gem 'rdoc', '~> 4.2' ...@@ -132,9 +138,9 @@ 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', '~> 3.1'
gem 'truncato', '~> 0.7.9' gem 'truncato', '~> 0.7.9'
gem 'bootstrap_form', '~> 2.7.0' gem 'bootstrap_form', '~> 2.7.0'
gem 'nokogiri', '~> 1.8.2' gem 'nokogiri', '~> 1.8.2'
...@@ -149,7 +155,7 @@ group :unicorn do ...@@ -149,7 +155,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'
...@@ -208,7 +214,7 @@ gem 'asana', '~> 0.6.0' ...@@ -208,7 +214,7 @@ gem 'asana', '~> 0.6.0'
gem 'ruby-fogbugz', '~> 0.2.1' gem 'ruby-fogbugz', '~> 0.2.1'
# Kubernetes integration # Kubernetes integration
gem 'kubeclient', '~> 2.2.0' gem 'kubeclient', '~> 3.0'
# d3 # d3
gem 'd3_rails', '~> 3.5.0' gem 'd3_rails', '~> 3.5.0'
...@@ -218,10 +224,10 @@ gem 'sanitize', '~> 2.0' ...@@ -218,10 +224,10 @@ gem 'sanitize', '~> 2.0'
gem 'babosa', '~> 1.0.2' gem 'babosa', '~> 1.0.2'
# Sanitizes SVG input # Sanitizes SVG input
gem 'loofah', '~> 2.0.3' gem 'loofah', '~> 2.2'
# Working with license # Working with license
gem 'licensee', '~> 8.7.0' gem 'licensee', '~> 8.9'
# Protect against bruteforcing # Protect against bruteforcing
gem 'rack-attack', '~> 4.4.1' gem 'rack-attack', '~> 4.4.1'
...@@ -235,9 +241,6 @@ gem 'mousetrap-rails', '~> 1.4.6' ...@@ -235,9 +241,6 @@ gem 'mousetrap-rails', '~> 1.4.6'
# Detect and convert string character encoding # Detect and convert string character encoding
gem 'charlock_holmes', '~> 0.7.5' gem 'charlock_holmes', '~> 0.7.5'
# Faster JSON
gem 'oj', '~> 2.17.4'
# Faster blank # Faster blank
gem 'fast_blank' gem 'fast_blank'
...@@ -269,9 +272,9 @@ gem 'premailer-rails', '~> 1.9.7' ...@@ -269,9 +272,9 @@ gem 'premailer-rails', '~> 1.9.7'
# I18n # I18n
gem 'ruby_parser', '~> 3.8', require: false gem 'ruby_parser', '~> 3.8', require: false
gem 'rails-i18n', '~> 4.0.9' gem 'rails-i18n', gem_versions['rails-i18n']
gem 'gettext_i18n_rails', '~> 1.8.0' gem 'gettext_i18n_rails', '~> 1.8.0'
gem 'gettext_i18n_rails_js', '~> 1.2.0' gem 'gettext_i18n_rails_js', '~> 1.3'
gem 'gettext', '~> 3.2.2', require: false, group: :development gem 'gettext', '~> 3.2.2', require: false, group: :development
gem 'batch-loader', '~> 1.2.1' gem 'batch-loader', '~> 1.2.1'
...@@ -279,7 +282,6 @@ gem 'batch-loader', '~> 1.2.1' ...@@ -279,7 +282,6 @@ gem 'batch-loader', '~> 1.2.1'
# Perf bar # Perf bar
gem 'peek', '~> 1.0.1' gem 'peek', '~> 1.0.1'
gem 'peek-gc', '~> 0.0.2' gem 'peek-gc', '~> 0.0.2'
gem 'peek-host', '~> 1.0.0'
gem 'peek-mysql2', '~> 1.1.0', group: :mysql gem 'peek-mysql2', '~> 1.1.0', group: :mysql
gem 'peek-performance_bar', '~> 1.3.0' gem 'peek-performance_bar', '~> 1.3.0'
gem 'peek-pg', '~> 1.3.0', group: :postgres gem 'peek-pg', '~> 1.3.0', group: :postgres
...@@ -300,7 +302,7 @@ end ...@@ -300,7 +302,7 @@ end
group :development do group :development do
gem 'foreman', '~> 0.84.0' gem 'foreman', '~> 0.84.0'
gem 'brakeman', '~> 3.6.0', require: false gem 'brakeman', '~> 4.2', require: false
gem 'letter_opener_web', '~> 1.3.0' gem 'letter_opener_web', '~> 1.3.0'
gem 'rblineprof', '~> 0.3.6', platform: :mri, require: false gem 'rblineprof', '~> 0.3.6', platform: :mri, require: false
...@@ -361,11 +363,13 @@ group :development, :test do ...@@ -361,11 +363,13 @@ group :development, :test do
gem 'license_finder', '~> 3.1', require: false gem 'license_finder', '~> 3.1', require: false
gem 'knapsack', '~> 1.16' gem 'knapsack', '~> 1.16'
gem 'activerecord_sane_schema_dumper', '0.2' gem 'activerecord_sane_schema_dumper', gem_versions['activerecord_sane_schema_dumper']
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
...@@ -373,9 +377,9 @@ group :test do ...@@ -373,9 +377,9 @@ group :test do
gem 'email_spec', '~> 1.6.0' gem 'email_spec', '~> 1.6.0'
gem 'json-schema', '~> 2.8.0' gem 'json-schema', '~> 2.8.0'
gem 'webmock', '~> 2.3.2' gem 'webmock', '~> 2.3.2'
gem 'test_after_commit', '~> 1.1' gem 'rails-controller-testing' if rails5? # Rails5 only gem.
gem 'test_after_commit', '~> 1.1' unless rails5? # Remove this gem when migrated to rails 5.0. It's been integrated to rails 5.0.
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
...@@ -411,7 +415,7 @@ group :ed25519 do ...@@ -411,7 +415,7 @@ group :ed25519 do
end end
# Gitaly GRPC client # Gitaly GRPC client
gem 'gitaly-proto', '~> 0.88.0', require: 'gitaly' gem 'gitaly-proto', '~> 0.97.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
......
This diff is collapsed.
# BUNDLE_GEMFILE=Gemfile.rails5 bundle install
ENV["RAILS5"] = "true"
gemfile = File.expand_path("../Gemfile", __FILE__)
eval(File.read(gemfile), nil, gemfile)
This diff is collapsed.
Copyright (c) 2011-2017 GitLab B.V. Copyright GitLab B.V.
With regard to the GitLab Software: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
Permission is hereby granted, free of charge, to any person obtaining a copy The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
all copies or substantial portions of the Software. \ No newline at end of file
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
For all third party components incorporated into the GitLab Software, those
components are licensed under the original license provided by the owner of the
applicable component.
\ No newline at end of file
...@@ -4,4 +4,3 @@ ...@@ -4,4 +4,3 @@
# #
web: RAILS_ENV=development bin/web start_foreground web: RAILS_ENV=development bin/web start_foreground
worker: RAILS_ENV=development bin/background_jobs start_foreground worker: RAILS_ENV=development bin/background_jobs start_foreground
# mail_room: bundle exec mail_room -q -c config/mail_room.yml
...@@ -67,6 +67,12 @@ You can access a new installation with the login **`root`** and password **`5ive ...@@ -67,6 +67,12 @@ You can access a new installation with the login **`root`** and password **`5ive
GitLab is an open source project and we are very happy to accept community contributions. Please refer to [CONTRIBUTING.md](/CONTRIBUTING.md) for details. GitLab is an open source project and we are very happy to accept community contributions. Please refer to [CONTRIBUTING.md](/CONTRIBUTING.md) for details.
## Licensing
GitLab Community Edition (CE) is available freely under the MIT Expat license.
All third party components incorporated into the GitLab Software are licensed under the original license provided by the owner of the applicable component.
## Install a development environment ## Install a development environment
To work on GitLab itself, we recommend setting up your development environment with [the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit). To work on GitLab itself, we recommend setting up your development environment with [the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit).
......
10.6.0-pre 10.7.0-pre
...@@ -10,6 +10,9 @@ const Api = { ...@@ -10,6 +10,9 @@ const Api = {
projectsPath: '/api/:version/projects.json', projectsPath: '/api/:version/projects.json',
projectPath: '/api/:version/projects/:id', projectPath: '/api/:version/projects/:id',
projectLabelsPath: '/:namespace_path/:project_path/labels', projectLabelsPath: '/:namespace_path/:project_path/labels',
mergeRequestPath: '/api/:version/projects/:id/merge_requests/:mrid',
mergeRequestChangesPath: '/api/:version/projects/:id/merge_requests/:mrid/changes',
mergeRequestVersionsPath: '/api/:version/projects/:id/merge_requests/:mrid/versions',
groupLabelsPath: '/groups/:namespace_path/-/labels', groupLabelsPath: '/groups/:namespace_path/-/labels',
licensePath: '/api/:version/templates/licenses/:key', licensePath: '/api/:version/templates/licenses/:key',
gitignorePath: '/api/:version/templates/gitignores/:key', gitignorePath: '/api/:version/templates/gitignores/:key',
...@@ -22,25 +25,27 @@ const Api = { ...@@ -22,25 +25,27 @@ const Api = {
createBranchPath: '/api/:version/projects/:id/repository/branches', createBranchPath: '/api/:version/projects/:id/repository/branches',
group(groupId, callback) { group(groupId, callback) {
const url = Api.buildUrl(Api.groupPath) const url = Api.buildUrl(Api.groupPath).replace(':id', groupId);
.replace(':id', groupId); return axios.get(url).then(({ data }) => {
return axios.get(url) callback(data);
.then(({ data }) => {
callback(data);
return data; return data;
}); });
}, },
// Return groups list. Filtered by query // Return groups list. Filtered by query
groups(query, options, callback = $.noop) { groups(query, options, callback = $.noop) {
const url = Api.buildUrl(Api.groupsPath); const url = Api.buildUrl(Api.groupsPath);
return axios.get(url, { return axios
params: Object.assign({ .get(url, {
search: query, params: Object.assign(
per_page: 20, {
}, options), search: query,
}) per_page: 20,
},
options,
),
})
.then(({ data }) => { .then(({ data }) => {
callback(data); callback(data);
...@@ -51,12 +56,13 @@ const Api = { ...@@ -51,12 +56,13 @@ const Api = {
// Return namespaces list. Filtered by query // Return namespaces list. Filtered by query
namespaces(query, callback) { namespaces(query, callback) {
const url = Api.buildUrl(Api.namespacesPath); const url = Api.buildUrl(Api.namespacesPath);
return axios.get(url, { return axios
params: { .get(url, {
search: query, params: {
per_page: 20, search: query,
}, per_page: 20,
}) },
})
.then(({ data }) => callback(data)); .then(({ data }) => callback(data));
}, },
...@@ -73,9 +79,10 @@ const Api = { ...@@ -73,9 +79,10 @@ const Api = {
defaults.membership = true; defaults.membership = true;
} }
return axios.get(url, { return axios
params: Object.assign(defaults, options), .get(url, {
}) params: Object.assign(defaults, options),
})
.then(({ data }) => { .then(({ data }) => {
callback(data); callback(data);
...@@ -85,8 +92,32 @@ const Api = { ...@@ -85,8 +92,32 @@ const Api = {
// Return single project // Return single project
project(projectPath) { project(projectPath) {
const url = Api.buildUrl(Api.projectPath) const url = Api.buildUrl(Api.projectPath).replace(':id', encodeURIComponent(projectPath));
.replace(':id', encodeURIComponent(projectPath));
return axios.get(url);
},
// Return Merge Request for project
mergeRequest(projectPath, mergeRequestId) {
const url = Api.buildUrl(Api.mergeRequestPath)
.replace(':id', encodeURIComponent(projectPath))
.replace(':mrid', mergeRequestId);
return axios.get(url);
},
mergeRequestChanges(projectPath, mergeRequestId) {
const url = Api.buildUrl(Api.mergeRequestChangesPath)
.replace(':id', encodeURIComponent(projectPath))
.replace(':mrid', mergeRequestId);
return axios.get(url);
},
mergeRequestVersions(projectPath, mergeRequestId) {
const url = Api.buildUrl(Api.mergeRequestVersionsPath)
.replace(':id', encodeURIComponent(projectPath))
.replace(':mrid', mergeRequestId);
return axios.get(url); return axios.get(url);
}, },
...@@ -102,30 +133,30 @@ const Api = { ...@@ -102,30 +133,30 @@ const Api = {
url = Api.buildUrl(Api.groupLabelsPath).replace(':namespace_path', namespacePath); url = Api.buildUrl(Api.groupLabelsPath).replace(':namespace_path', namespacePath);
} }
return axios.post(url, { return axios
label: data, .post(url, {
}) label: data,
})
.then(res => callback(res.data)) .then(res => callback(res.data))
.catch(e => callback(e.response.data)); .catch(e => callback(e.response.data));
}, },
// Return group projects list. Filtered by query // Return group projects list. Filtered by query
groupProjects(groupId, query, callback) { groupProjects(groupId, query, callback) {
const url = Api.buildUrl(Api.groupProjectsPath) const url = Api.buildUrl(Api.groupProjectsPath).replace(':id', groupId);
.replace(':id', groupId); return axios
return axios.get(url, { .get(url, {
params: { params: {
search: query, search: query,
per_page: 20, per_page: 20,
}, },
}) })
.then(({ data }) => callback(data)); .then(({ data }) => callback(data));
}, },
commitMultiple(id, data) { commitMultiple(id, data) {
// see https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions // see https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions
const url = Api.buildUrl(Api.commitPath) const url = Api.buildUrl(Api.commitPath).replace(':id', encodeURIComponent(id));
.replace(':id', encodeURIComponent(id));
return axios.post(url, JSON.stringify(data), { return axios.post(url, JSON.stringify(data), {
headers: { headers: {
'Content-Type': 'application/json; charset=utf-8', 'Content-Type': 'application/json; charset=utf-8',
...@@ -136,39 +167,34 @@ const Api = { ...@@ -136,39 +167,34 @@ const Api = {
branchSingle(id, branch) { branchSingle(id, branch) {
const url = Api.buildUrl(Api.branchSinglePath) const url = Api.buildUrl(Api.branchSinglePath)
.replace(':id', encodeURIComponent(id)) .replace(':id', encodeURIComponent(id))
.replace(':branch', branch); .replace(':branch', encodeURIComponent(branch));
return axios.get(url); return axios.get(url);
}, },
// Return text for a specific license // Return text for a specific license
licenseText(key, data, callback) { licenseText(key, data, callback) {
const url = Api.buildUrl(Api.licensePath) const url = Api.buildUrl(Api.licensePath).replace(':key', key);
.replace(':key', key); return axios
return axios.get(url, { .get(url, {
params: data, params: data,
}) })
.then(res => callback(res.data)); .then(res => callback(res.data));
}, },
gitignoreText(key, callback) { gitignoreText(key, callback) {
const url = Api.buildUrl(Api.gitignorePath) const url = Api.buildUrl(Api.gitignorePath).replace(':key', key);
.replace(':key', key); return axios.get(url).then(({ data }) => callback(data));
return axios.get(url)
.then(({ data }) => callback(data));
}, },
gitlabCiYml(key, callback) { gitlabCiYml(key, callback) {
const url = Api.buildUrl(Api.gitlabCiYmlPath) const url = Api.buildUrl(Api.gitlabCiYmlPath).replace(':key', key);
.replace(':key', key); return axios.get(url).then(({ data }) => callback(data));
return axios.get(url)
.then(({ data }) => callback(data));
}, },
dockerfileYml(key, callback) { dockerfileYml(key, callback) {
const url = Api.buildUrl(Api.dockerfilePath).replace(':key', key); const url = Api.buildUrl(Api.dockerfilePath).replace(':key', key);
return axios.get(url) return axios.get(url).then(({ data }) => callback(data));
.then(({ data }) => callback(data));
}, },
issueTemplate(namespacePath, projectPath, key, type, callback) { issueTemplate(namespacePath, projectPath, key, type, callback) {
...@@ -177,7 +203,8 @@ const Api = { ...@@ -177,7 +203,8 @@ const Api = {
.replace(':type', type) .replace(':type', type)
.replace(':project_path', projectPath) .replace(':project_path', projectPath)
.replace(':namespace_path', namespacePath); .replace(':namespace_path', namespacePath);
return axios.get(url) return axios
.get(url)
.then(({ data }) => callback(null, data)) .then(({ data }) => callback(null, data))
.catch(callback); .catch(callback);
}, },
...@@ -185,10 +212,13 @@ const Api = { ...@@ -185,10 +212,13 @@ const Api = {
users(query, options) { users(query, options) {
const url = Api.buildUrl(this.usersPath); const url = Api.buildUrl(this.usersPath);
return axios.get(url, { return axios.get(url, {
params: Object.assign({ params: Object.assign(
search: query, {
per_page: 20, search: query,
}, options), per_page: 20,
},
options,
),
}); });
}, },
......
...@@ -4,7 +4,8 @@ import $ from 'jquery'; ...@@ -4,7 +4,8 @@ import $ from 'jquery';
import _ from 'underscore'; import _ from 'underscore';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import { __ } from './locale'; import { __ } from './locale';
import { isInIssuePage, isInMRPage, hasVueMRDiscussionsCookie, updateTooltipTitle } from './lib/utils/common_utils'; import { updateTooltipTitle } from './lib/utils/common_utils';
import { isInVueNoteablePage } from './lib/utils/dom_utils';
import flash from './flash'; import flash from './flash';
import axios from './lib/utils/axios_utils'; import axios from './lib/utils/axios_utils';
...@@ -243,7 +244,7 @@ class AwardsHandler { ...@@ -243,7 +244,7 @@ class AwardsHandler {
addAward(votesBlock, awardUrl, emoji, checkMutuality, callback) { addAward(votesBlock, awardUrl, emoji, checkMutuality, callback) {
const isMainAwardsBlock = votesBlock.closest('.js-noteable-awards').length; const isMainAwardsBlock = votesBlock.closest('.js-noteable-awards').length;
if (this.isInVueNoteablePage() && !isMainAwardsBlock) { if (isInVueNoteablePage() && !isMainAwardsBlock) {
const id = votesBlock.attr('id').replace('note_', ''); const id = votesBlock.attr('id').replace('note_', '');
this.hideMenuElement($('.emoji-menu')); this.hideMenuElement($('.emoji-menu'));
...@@ -295,16 +296,8 @@ class AwardsHandler { ...@@ -295,16 +296,8 @@ class AwardsHandler {
} }
} }
isVueMRDiscussions() {
return isInMRPage() && hasVueMRDiscussionsCookie() && !$('#diffs').is(':visible');
}
isInVueNoteablePage() {
return isInIssuePage() || this.isVueMRDiscussions();
}
getVotesBlock() { getVotesBlock() {
if (this.isInVueNoteablePage()) { if (isInVueNoteablePage()) {
const $el = $('.js-add-award.is-active').closest('.note.timeline-entry'); const $el = $('.js-add-award.is-active').closest('.note.timeline-entry');
if ($el.length) { if ($el.length) {
......
<script>
import Icon from '~/vue_shared/components/icon.vue';
import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
import Tooltip from '~/vue_shared/directives/tooltip';
export default {
name: 'Badge',
components: {
Icon,
LoadingIcon,
Tooltip,
},
directives: {
Tooltip,
},
props: {
imageUrl: {
type: String,
required: true,
},
linkUrl: {
type: String,
required: true,
},
},
data() {
return {
hasError: false,
isLoading: true,
numRetries: 0,
};
},
computed: {
imageUrlWithRetries() {
if (this.numRetries === 0) {
return this.imageUrl;
}
return `${this.imageUrl}#retries=${this.numRetries}`;
},
},
watch: {
imageUrl() {
this.hasError = false;
this.isLoading = true;
this.numRetries = 0;
},
},
methods: {
onError() {
this.isLoading = false;
this.hasError = true;
},
onLoad() {
this.isLoading = false;
},
reloadImage() {
this.hasError = false;
this.isLoading = true;
this.numRetries += 1;
},
},
};
</script>
<template>
<div>
<a
v-show="!isLoading && !hasError"
:href="linkUrl"
target="_blank"
rel="noopener noreferrer"
>
<img
class="project-badge"
:src="imageUrlWithRetries"
@load="onLoad"
@error="onError"
aria-hidden="true"
/>
</a>
<loading-icon
v-show="isLoading"
:inline="true"
/>
<div
v-show="hasError"
class="btn-group"
>
<div class="btn btn-default btn-xs disabled">
<icon
class="prepend-left-8 append-right-8"
name="doc_image"
:size="16"
aria-hidden="true"
/>
</div>
<div
class="btn btn-default btn-xs disabled"
>
<span class="prepend-left-8 append-right-8">{{ s__('Badges|No badge image') }}</span>
</div>
</div>
<button
v-show="hasError"
class="btn btn-transparent btn-xs text-primary"
type="button"
v-tooltip
:title="s__('Badges|Reload badge image')"
@click="reloadImage"
>
<icon
name="retry"
:size="16"
/>
</button>
</div>
</template>
<script>
import _ from 'underscore';
import { mapActions, mapState } from 'vuex';
import createFlash from '~/flash';
import { s__, sprintf } from '~/locale';
import LoadingButton from '~/vue_shared/components/loading_button.vue';
import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
import createEmptyBadge from '../empty_badge';
import Badge from './badge.vue';
const badgePreviewDelayInMilliseconds = 1500;
export default {
name: 'BadgeForm',
components: {
Badge,
LoadingButton,
LoadingIcon,
},
props: {
isEditing: {
type: Boolean,
required: true,
},
},
computed: {
...mapState([
'badgeInAddForm',
'badgeInEditForm',
'docsUrl',
'isRendering',
'isSaving',
'renderedBadge',
]),
badge() {
if (this.isEditing) {
return this.badgeInEditForm;
}
return this.badgeInAddForm;
},
canSubmit() {
return (
this.badge !== null &&
this.badge.imageUrl &&
this.badge.imageUrl.trim() !== '' &&
this.badge.linkUrl &&
this.badge.linkUrl.trim() !== '' &&
!this.isSaving
);
},
helpText() {
const placeholders = ['project_path', 'project_id', 'default_branch', 'commit_sha']
.map(placeholder => `<code>%{${placeholder}}</code>`)
.join(', ');
return sprintf(
s__('Badges|The %{docsLinkStart}variables%{docsLinkEnd} GitLab supports: %{placeholders}'),
{
docsLinkEnd: '</a>',
docsLinkStart: `<a href="${_.escape(this.docsUrl)}">`,
placeholders,
},
false,
);
},
renderedImageUrl() {
return this.renderedBadge ? this.renderedBadge.renderedImageUrl : '';
},
renderedLinkUrl() {
return this.renderedBadge ? this.renderedBadge.renderedLinkUrl : '';
},
imageUrl: {
get() {
return this.badge ? this.badge.imageUrl : '';
},
set(imageUrl) {
const badge = this.badge || createEmptyBadge();
this.updateBadgeInForm({
...badge,
imageUrl,
});
},
},
linkUrl: {
get() {
return this.badge ? this.badge.linkUrl : '';
},
set(linkUrl) {
const badge = this.badge || createEmptyBadge();
this.updateBadgeInForm({
...badge,
linkUrl,
});
},
},
submitButtonLabel() {
if (this.isEditing) {
return s__('Badges|Save changes');
}
return s__('Badges|Add badge');
},
},
methods: {
...mapActions(['addBadge', 'renderBadge', 'saveBadge', 'stopEditing', 'updateBadgeInForm']),
debouncedPreview: _.debounce(function preview() {
this.renderBadge();
}, badgePreviewDelayInMilliseconds),
onCancel() {
this.stopEditing();
},
onSubmit() {
if (!this.canSubmit) {
return Promise.resolve();
}
if (this.isEditing) {
return this.saveBadge()
.then(() => {
createFlash(s__('Badges|The badge was saved.'), 'notice');
})
.catch(error => {
createFlash(
s__('Badges|Saving the badge failed, please check the entered URLs and try again.'),
);
throw error;
});
}
return this.addBadge()
.then(() => {
createFlash(s__('Badges|A new badge was added.'), 'notice');
})
.catch(error => {
createFlash(
s__('Badges|Adding the badge failed, please check the entered URLs and try again.'),
);
throw error;
});
},
},
badgeImageUrlPlaceholder:
'https://example.gitlab.com/%{project_path}/badges/%{default_branch}/<badge>.svg',
badgeLinkUrlPlaceholder: 'https://example.gitlab.com/%{project_path}',
};
</script>
<template>
<form
class="prepend-top-default append-bottom-default"
@submit.prevent.stop="onSubmit"
>
<div class="form-group">
<label for="badge-link-url">{{ s__('Badges|Link') }}</label>
<input
id="badge-link-url"
type="text"
class="form-control"
v-model="linkUrl"
:placeholder="$options.badgeLinkUrlPlaceholder"
@input="debouncedPreview"
/>
<span
class="help-block"
v-html="helpText"
></span>
</div>
<div class="form-group">
<label for="badge-image-url">{{ s__('Badges|Badge image URL') }}</label>
<input
id="badge-image-url"
type="text"
class="form-control"
v-model="imageUrl"
:placeholder="$options.badgeImageUrlPlaceholder"
@input="debouncedPreview"
/>
<span
class="help-block"
v-html="helpText"
></span>
</div>
<div class="form-group">
<label for="badge-preview">{{ s__('Badges|Badge image preview') }}</label>
<badge
id="badge-preview"
v-show="renderedBadge && !isRendering"
:image-url="renderedImageUrl"
:link-url="renderedLinkUrl"
/>
<p v-show="isRendering">
<loading-icon
:inline="true"
/>
</p>
<p
v-show="!renderedBadge && !isRendering"
class="disabled-content"
>{{ s__('Badges|No image to preview') }}</p>
</div>
<div class="row-content-block">
<loading-button
type="submit"
container-class="btn btn-success"
:disabled="!canSubmit"
:loading="isSaving"
:label="submitButtonLabel"
/>
<button
class="btn btn-cancel"
type="button"
v-if="isEditing"
@click="onCancel"
>{{ __('Cancel') }}</button>
</div>
</form>
</template>
<script>
import { mapState } from 'vuex';
import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
import BadgeListRow from './badge_list_row.vue';
import { GROUP_BADGE } from '../constants';
export default {
name: 'BadgeList',
components: {
BadgeListRow,
LoadingIcon,
},
computed: {
...mapState(['badges', 'isLoading', 'kind']),
hasNoBadges() {
return !this.isLoading && (!this.badges || !this.badges.length);
},
isGroupBadge() {
return this.kind === GROUP_BADGE;
},
},
};
</script>
<template>
<div class="panel panel-default">
<div class="panel-heading">
{{ s__('Badges|Your badges') }}
<span
v-show="!isLoading"
class="badge"
>{{ badges.length }}</span>
</div>
<loading-icon
v-show="isLoading"
class="panel-body"
size="2"
/>
<div
v-if="hasNoBadges"
class="panel-body"
>
<span v-if="isGroupBadge">{{ s__('Badges|This group has no badges') }}</span>
<span v-else>{{ s__('Badges|This project has no badges') }}</span>
</div>
<div
v-else
class="panel-body"
>
<badge-list-row
v-for="badge in badges"
:key="badge.id"
:badge="badge"
/>
</div>
</div>
</template>
<script>
import { mapActions, mapState } from 'vuex';
import { s__ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
import { PROJECT_BADGE } from '../constants';
import Badge from './badge.vue';
export default {
name: 'BadgeListRow',
components: {
Badge,
Icon,
LoadingIcon,
},
props: {
badge: {
type: Object,
required: true,
},
},
computed: {
...mapState(['kind']),
badgeKindText() {
if (this.badge.kind === PROJECT_BADGE) {
return s__('Badges|Project Badge');
}
return s__('Badges|Group Badge');
},
canEditBadge() {
return this.badge.kind === this.kind;
},
},
methods: {
...mapActions(['editBadge', 'updateBadgeInModal']),
},
};
</script>
<template>
<div class="gl-responsive-table-row-layout gl-responsive-table-row">
<badge
class="table-section section-30"
:image-url="badge.renderedImageUrl"
:link-url="badge.renderedLinkUrl"
/>
<span class="table-section section-50 str-truncated">{{ badge.linkUrl }}</span>
<div class="table-section section-10">
<span class="badge">{{ badgeKindText }}</span>
</div>
<div class="table-section section-10 table-button-footer">
<div
v-if="canEditBadge"
class="table-action-buttons">
<button
class="btn btn-default append-right-8"
type="button"
:disabled="badge.isDeleting"
@click="editBadge(badge)"
>
<icon
name="pencil"
:size="16"
:aria-label="__('Edit')"
/>
</button>
<button
class="btn btn-danger"
type="button"
data-toggle="modal"
data-target="#delete-badge-modal"
:disabled="badge.isDeleting"
@click="updateBadgeInModal(badge)"
>
<icon
name="remove"
:size="16"
:aria-label="__('Delete')"
/>
</button>
<loading-icon
v-show="badge.isDeleting"
:inline="true"
/>
</div>
</div>
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex';
import createFlash from '~/flash';
import { s__ } from '~/locale';
import GlModal from '~/vue_shared/components/gl_modal.vue';
import Badge from './badge.vue';
import BadgeForm from './badge_form.vue';
import BadgeList from './badge_list.vue';
export default {
name: 'BadgeSettings',
components: {
Badge,
BadgeForm,
BadgeList,
GlModal,
},
computed: {
...mapState(['badgeInModal', 'isEditing']),
deleteModalText() {
return s__(
'Badges|You are going to delete this badge. Deleted badges <strong>cannot</strong> be restored.',
);
},
},
methods: {
...mapActions(['deleteBadge']),
onSubmitModal() {
this.deleteBadge(this.badgeInModal)
.then(() => {
createFlash(s__('Badges|The badge was deleted.'), 'notice');
})
.catch(error => {
createFlash(s__('Badges|Deleting the badge failed, please try again.'));
throw error;
});
},
},
};
</script>
<template>
<div class="badge-settings">
<gl-modal
id="delete-badge-modal"
:header-title-text="s__('Badges|Delete badge?')"
footer-primary-button-variant="danger"
:footer-primary-button-text="s__('Badges|Delete badge')"
@submit="onSubmitModal">
<div class="well">
<badge
:image-url="badgeInModal ? badgeInModal.renderedImageUrl : ''"
:link-url="badgeInModal ? badgeInModal.renderedLinkUrl : ''"
/>
</div>
<p v-html="deleteModalText"></p>
</gl-modal>
<badge-form
v-show="isEditing"
:is-editing="true"
/>
<badge-form
v-show="!isEditing"
:is-editing="false"
/>
<badge-list v-show="!isEditing" />
</div>
</template>
export const GROUP_BADGE = 'group';
export const PROJECT_BADGE = 'project';
export default () => ({
imageUrl: '',
isDeleting: false,
linkUrl: '',
renderedImageUrl: '',
renderedLinkUrl: '',
});
import axios from '~/lib/utils/axios_utils';
import types from './mutation_types';
export const transformBackendBadge = badge => ({
id: badge.id,
imageUrl: badge.image_url,
kind: badge.kind,
linkUrl: badge.link_url,
renderedImageUrl: badge.rendered_image_url,
renderedLinkUrl: badge.rendered_link_url,
isDeleting: false,
});
export default {
requestNewBadge({ commit }) {
commit(types.REQUEST_NEW_BADGE);
},
receiveNewBadge({ commit }, newBadge) {
commit(types.RECEIVE_NEW_BADGE, newBadge);
},
receiveNewBadgeError({ commit }) {
commit(types.RECEIVE_NEW_BADGE_ERROR);
},
addBadge({ dispatch, state }) {
const newBadge = state.badgeInAddForm;
const endpoint = state.apiEndpointUrl;
dispatch('requestNewBadge');
return axios
.post(endpoint, {
image_url: newBadge.imageUrl,
link_url: newBadge.linkUrl,
})
.catch(error => {
dispatch('receiveNewBadgeError');
throw error;
})
.then(res => {
dispatch('receiveNewBadge', transformBackendBadge(res.data));
});
},
requestDeleteBadge({ commit }, badgeId) {
commit(types.REQUEST_DELETE_BADGE, badgeId);
},
receiveDeleteBadge({ commit }, badgeId) {
commit(types.RECEIVE_DELETE_BADGE, badgeId);
},
receiveDeleteBadgeError({ commit }, badgeId) {
commit(types.RECEIVE_DELETE_BADGE_ERROR, badgeId);
},
deleteBadge({ dispatch, state }, badge) {
const badgeId = badge.id;
dispatch('requestDeleteBadge', badgeId);
const endpoint = `${state.apiEndpointUrl}/${badgeId}`;
return axios
.delete(endpoint)
.catch(error => {
dispatch('receiveDeleteBadgeError', badgeId);
throw error;
})
.then(() => {
dispatch('receiveDeleteBadge', badgeId);
});
},
editBadge({ commit }, badge) {
commit(types.START_EDITING, badge);
},
requestLoadBadges({ commit }, data) {
commit(types.REQUEST_LOAD_BADGES, data);
},
receiveLoadBadges({ commit }, badges) {
commit(types.RECEIVE_LOAD_BADGES, badges);
},
receiveLoadBadgesError({ commit }) {
commit(types.RECEIVE_LOAD_BADGES_ERROR);
},
loadBadges({ dispatch, state }, data) {
dispatch('requestLoadBadges', data);
const endpoint = state.apiEndpointUrl;
return axios
.get(endpoint)
.catch(error => {
dispatch('receiveLoadBadgesError');
throw error;
})
.then(res => {
dispatch('receiveLoadBadges', res.data.map(transformBackendBadge));
});
},
requestRenderedBadge({ commit }) {
commit(types.REQUEST_RENDERED_BADGE);
},
receiveRenderedBadge({ commit }, renderedBadge) {
commit(types.RECEIVE_RENDERED_BADGE, renderedBadge);
},
receiveRenderedBadgeError({ commit }) {
commit(types.RECEIVE_RENDERED_BADGE_ERROR);
},
renderBadge({ dispatch, state }) {
const badge = state.isEditing ? state.badgeInEditForm : state.badgeInAddForm;
const { linkUrl, imageUrl } = badge;
if (!linkUrl || linkUrl.trim() === '' || !imageUrl || imageUrl.trim() === '') {
return Promise.resolve(badge);
}
dispatch('requestRenderedBadge');
const parameters = [
`link_url=${encodeURIComponent(linkUrl)}`,
`image_url=${encodeURIComponent(imageUrl)}`,
].join('&');
const renderEndpoint = `${state.apiEndpointUrl}/render?${parameters}`;
return axios
.get(renderEndpoint)
.catch(error => {
dispatch('receiveRenderedBadgeError');
throw error;
})
.then(res => {
dispatch('receiveRenderedBadge', transformBackendBadge(res.data));
});
},
requestUpdatedBadge({ commit }) {
commit(types.REQUEST_UPDATED_BADGE);
},
receiveUpdatedBadge({ commit }, updatedBadge) {
commit(types.RECEIVE_UPDATED_BADGE, updatedBadge);
},
receiveUpdatedBadgeError({ commit }) {
commit(types.RECEIVE_UPDATED_BADGE_ERROR);
},
saveBadge({ dispatch, state }) {
const badge = state.badgeInEditForm;
const endpoint = `${state.apiEndpointUrl}/${badge.id}`;
dispatch('requestUpdatedBadge');
return axios
.put(endpoint, {
image_url: badge.imageUrl,
link_url: badge.linkUrl,
})
.catch(error => {
dispatch('receiveUpdatedBadgeError');
throw error;
})
.then(res => {
dispatch('receiveUpdatedBadge', transformBackendBadge(res.data));
});
},
stopEditing({ commit }) {
commit(types.STOP_EDITING);
},
updateBadgeInForm({ commit }, badge) {
commit(types.UPDATE_BADGE_IN_FORM, badge);
},
updateBadgeInModal({ commit }, badge) {
commit(types.UPDATE_BADGE_IN_MODAL, badge);
},
};
import Vue from 'vue';
import Vuex from 'vuex';
import createState from './state';
import actions from './actions';
import mutations from './mutations';
Vue.use(Vuex);
export default new Vuex.Store({
state: createState(),
actions,
mutations,
});
export default {
RECEIVE_DELETE_BADGE: 'RECEIVE_DELETE_BADGE',
RECEIVE_DELETE_BADGE_ERROR: 'RECEIVE_DELETE_BADGE_ERROR',
RECEIVE_LOAD_BADGES: 'RECEIVE_LOAD_BADGES',
RECEIVE_LOAD_BADGES_ERROR: 'RECEIVE_LOAD_BADGES_ERROR',
RECEIVE_NEW_BADGE: 'RECEIVE_NEW_BADGE',
RECEIVE_NEW_BADGE_ERROR: 'RECEIVE_NEW_BADGE_ERROR',
RECEIVE_RENDERED_BADGE: 'RECEIVE_RENDERED_BADGE',
RECEIVE_RENDERED_BADGE_ERROR: 'RECEIVE_RENDERED_BADGE_ERROR',
RECEIVE_UPDATED_BADGE: 'RECEIVE_UPDATED_BADGE',
RECEIVE_UPDATED_BADGE_ERROR: 'RECEIVE_UPDATED_BADGE_ERROR',
REQUEST_DELETE_BADGE: 'REQUEST_DELETE_BADGE',
REQUEST_LOAD_BADGES: 'REQUEST_LOAD_BADGES',
REQUEST_NEW_BADGE: 'REQUEST_NEW_BADGE',
REQUEST_RENDERED_BADGE: 'REQUEST_RENDERED_BADGE',
REQUEST_UPDATED_BADGE: 'REQUEST_UPDATED_BADGE',
START_EDITING: 'START_EDITING',
STOP_EDITING: 'STOP_EDITING',
UPDATE_BADGE_IN_FORM: 'UPDATE_BADGE_IN_FORM',
UPDATE_BADGE_IN_MODAL: 'UPDATE_BADGE_IN_MODAL',
};
import types from './mutation_types';
import { PROJECT_BADGE } from '../constants';
const reorderBadges = badges =>
badges.sort((a, b) => {
if (a.kind !== b.kind) {
return a.kind === PROJECT_BADGE ? 1 : -1;
}
return a.id - b.id;
});
export default {
[types.RECEIVE_NEW_BADGE](state, newBadge) {
Object.assign(state, {
badgeInAddForm: null,
badges: reorderBadges(state.badges.concat(newBadge)),
isSaving: false,
renderedBadge: null,
});
},
[types.RECEIVE_NEW_BADGE_ERROR](state) {
Object.assign(state, {
isSaving: false,
});
},
[types.REQUEST_NEW_BADGE](state) {
Object.assign(state, {
isSaving: true,
});
},
[types.RECEIVE_UPDATED_BADGE](state, updatedBadge) {
const badges = state.badges.map(badge => {
if (badge.id === updatedBadge.id) {
return updatedBadge;
}
return badge;
});
Object.assign(state, {
badgeInEditForm: null,
badges,
isEditing: false,
isSaving: false,
renderedBadge: null,
});
},
[types.RECEIVE_UPDATED_BADGE_ERROR](state) {
Object.assign(state, {
isSaving: false,
});
},
[types.REQUEST_UPDATED_BADGE](state) {
Object.assign(state, {
isSaving: true,
});
},
[types.RECEIVE_LOAD_BADGES](state, badges) {
Object.assign(state, {
badges: reorderBadges(badges),
isLoading: false,
});
},
[types.RECEIVE_LOAD_BADGES_ERROR](state) {
Object.assign(state, {
isLoading: false,
});
},
[types.REQUEST_LOAD_BADGES](state, data) {
Object.assign(state, {
kind: data.kind, // project or group
apiEndpointUrl: data.apiEndpointUrl,
docsUrl: data.docsUrl,
isLoading: true,
});
},
[types.RECEIVE_DELETE_BADGE](state, badgeId) {
const badges = state.badges.filter(badge => badge.id !== badgeId);
Object.assign(state, {
badges,
});
},
[types.RECEIVE_DELETE_BADGE_ERROR](state, badgeId) {
const badges = state.badges.map(badge => {
if (badge.id === badgeId) {
return {
...badge,
isDeleting: false,
};
}
return badge;
});
Object.assign(state, {
badges,
});
},
[types.REQUEST_DELETE_BADGE](state, badgeId) {
const badges = state.badges.map(badge => {
if (badge.id === badgeId) {
return {
...badge,
isDeleting: true,
};
}
return badge;
});
Object.assign(state, {
badges,
});
},
[types.RECEIVE_RENDERED_BADGE](state, renderedBadge) {
Object.assign(state, { isRendering: false, renderedBadge });
},
[types.RECEIVE_RENDERED_BADGE_ERROR](state) {
Object.assign(state, { isRendering: false });
},
[types.REQUEST_RENDERED_BADGE](state) {
Object.assign(state, { isRendering: true });
},
[types.START_EDITING](state, badge) {
Object.assign(state, {
badgeInEditForm: { ...badge },
isEditing: true,
renderedBadge: { ...badge },
});
},
[types.STOP_EDITING](state) {
Object.assign(state, {
badgeInEditForm: null,
isEditing: false,
renderedBadge: null,
});
},
[types.UPDATE_BADGE_IN_FORM](state, badge) {
if (state.isEditing) {
Object.assign(state, {
badgeInEditForm: badge,
});
} else {
Object.assign(state, {
badgeInAddForm: badge,
});
}
},
[types.UPDATE_BADGE_IN_MODAL](state, badge) {
Object.assign(state, {
badgeInModal: badge,
});
},
};
export default () => ({
apiEndpointUrl: null,
badgeInAddForm: null,
badgeInEditForm: null,
badgeInModal: null,
badges: [],
docsUrl: null,
renderedBadge: null,
isEditing: false,
isLoading: false,
isRendering: false,
isSaving: false,
});
This diff is collapsed.
import './autosize'; import './autosize';
import './bind_in_out'; import './bind_in_out';
import initCopyAsGFM from './copy_as_gfm'; import './markdown/render_gfm';
import initCopyAsGFM from './markdown/copy_as_gfm';
import initCopyToClipboard from './copy_to_clipboard'; import initCopyToClipboard from './copy_to_clipboard';
import './details_behavior'; import './details_behavior';
import installGlEmojiElement from './gl_emoji'; import installGlEmojiElement from './gl_emoji';
......
This diff is collapsed.
import $ from 'jquery';
import syntaxHighlight from '~/syntax_highlight';
import renderMath from './render_math';
import renderMermaid from './render_mermaid';
// Render Gitlab flavoured Markdown
//
// Delegates to syntax highlight and render math & mermaid diagrams.
//
$.fn.renderGFM = function renderGFM() {
syntaxHighlight(this.find('.js-syntax-highlight'));
renderMath(this.find('.js-render-math'));
renderMermaid(this.find('.js-render-mermaid'));
return this;
};
$(() => $('body').renderGFM());
import $ from 'jquery';
import { __ } from '~/locale';
import flash from '~/flash';
// Renders math using KaTeX in any element with the
// `js-render-math` class
//
// ### Example Markup
//
// <code class="js-render-math"></div>
//
// Loop over all math elements and render math
function renderWithKaTeX(elements, katex) {
elements.each(function katexElementsLoop() {
const mathNode = $('<span></span>');
const $this = $(this);
const display = $this.attr('data-math-style') === 'display';
try {
katex.render($this.text(), mathNode.get(0), { displayMode: display, throwOnError: false });
mathNode.insertAfter($this);
$this.remove();
} catch (err) {
throw err;
}
});
}
export default function renderMath($els) {
if (!$els.length) return;
Promise.all([
import(/* webpackChunkName: 'katex' */ 'katex'),
import(/* webpackChunkName: 'katex' */ 'katex/dist/katex.min.css'),
]).then(([katex]) => {
renderWithKaTeX($els, katex);
}).catch(() => flash(__('An error occurred while rendering KaTeX')));
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
// 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';
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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