diff options
author | Mike Greiling <mike@pixelcog.com> | 2018-02-16 16:00:03 -0600 |
---|---|---|
committer | Mike Greiling <mike@pixelcog.com> | 2018-02-16 16:00:03 -0600 |
commit | 8e65c13a586031928c681c4926d059df23ad5753 (patch) | |
tree | df99f6a592a2d3f7f5fabb4c85c6b90f0343ca68 | |
parent | fa260ac8400b16bc19acc5740b47c596c1c903c0 (diff) | |
parent | b236348388c46c0550ec6844df35ec2689c4060b (diff) | |
download | gitlab-ce-8e65c13a586031928c681c4926d059df23ad5753.tar.gz |
Merge branch 'master' into chart.html.haml-refactorchart.html.haml-refactor
* master: (484 commits)
migrate admin:users:* to static bundle
correct for missing break statement in dispatcher.js
alias create and update actions to new and edit
migrate projects:merge_requests:edit to static bundle
migrate projects:merge_requests:creations:diffs to static bundle
migrate projects:merge_requests:creations:new to static bundle
migrate projects:issues:new and projects:issues:edit to static bundle
migrate projects:branches:index to static bundle
migrate projects:branches:new to static bundle
migrate projects:compare:show to static bundle
migrate projects:environments:metrics to static bundle
migrate projects:milestones:* and groups:milestones:* to static bundle
migrate explore:groups:index to static bundle
migrate explore:projects:* to static bundle
migrate dashboard:projects:* to static bundle
migrate admin:jobs:index to static bundle
migrate dashboard:todos:index to static bundle
migrate groups:merge_requests to static bundle
migrate groups:issues to static bundle
migrate dashboard:merge_requests to static bundle
...
950 files changed, 33427 insertions, 18360 deletions
diff --git a/.eslintrc b/.eslintrc index ad5eaebccae..8f9cdfb14ac 100644 --- a/.eslintrc +++ b/.eslintrc @@ -36,7 +36,7 @@ "import/no-commonjs": "error", "no-multiple-empty-lines": ["error", { "max": 1 }], "promise/catch-or-return": "error", - "no-underscore-dangle": ["error", { "allow": ["__"]}], + "no-underscore-dangle": ["error", { "allow": ["__", "_links"]}], "vue/html-self-closing": ["error", { "html": { "void": "always", diff --git a/.rubocop.yml b/.rubocop.yml index 563a00db6c0..24edb641657 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -10,15 +10,26 @@ AllCops: Exclude: - 'vendor/**/*' - 'node_modules/**/*' - - 'db/*' + - 'db/**/*' - 'db/fixtures/**/*' - - 'db/geo/*' + - 'ee/db/**/*' - 'tmp/**/*' - 'bin/**/*' - 'generator_templates/**/*' - 'builds/**/*' CacheRootDirectory: tmp +# This cop checks whether some constant value isn't a +# mutable literal (e.g. array or hash). +Style/MutableConstant: + Enabled: true + Exclude: + - 'db/migrate/**/*' + - 'db/post_migrate/**/*' + - 'ee/db/migrate/**/*' + - 'ee/db/post_migrate/**/*' + - 'ee/db/geo/migrate/**/*' + # Gitlab ################################################################### Gitlab/ModuleWithInstanceVariables: @@ -33,3 +44,16 @@ Gitlab/ModuleWithInstanceVariables: # We ignore spec helpers because it usually doesn't matter - spec/support/**/*.rb - features/steps/**/*.rb + +GitlabSecurity/PublicSend: + Enabled: true + Exclude: + - 'config/**/*' + - 'db/**/*' + - 'features/**/*' + - 'lib/**/*.rake' + - 'qa/**/*' + - 'spec/**/*' + - 'ee/db/**/*' + - 'ee/lib/**/*.rake' + - 'ee/spec/**/*' diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fc97c06f7c..c3bb93fbe3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,35 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 10.4.4 (2018-02-16) + +### Security (1 change) + +- Update nokogiri to 1.8.2. !16807 + +### Fixed (9 changes) + +- Fix 500 error when loading a merge request with an invalid comment. !16795 +- Cleanup new branch/merge request form in issues. !16854 +- Fix GitLab import leaving group_id on ProjectLabel. !16877 +- Fix forking projects when no restricted visibility levels are defined applicationwide. !16881 +- Resolve PrepareUntrackedUploads PostgreSQL syntax error. !17019 +- Fixed error 500 when removing an identity with synced attributes and visiting the profile page. !17054 +- Validate user namespace before saving so that errors persist on model. +- LDAP Person no longer throws exception on invalid entry. +- Fix JIRA not working when a trailing slash is included. + + +## 10.4.3 (2018-02-05) + +### Security (4 changes) + +- Fix namespace access issue for GitHub, BitBucket, and GitLab.com project importers. +- Fix stored XSS in code blocks that ignore highlighting. +- Fix wilcard protected tags protecting all branches. +- Restrict Todo API mark_as_done endpoint to the user's todos only. + + ## 10.4.2 (2018-01-30) ### Fixed (6 changes) @@ -197,6 +226,16 @@ entry. - Use a background migration for issues.closed_at. +## 10.3.7 (2018-02-05) + +### Security (4 changes) + +- Fix namespace access issue for GitHub, BitBucket, and GitLab.com project importers. +- Fix stored XSS in code blocks that ignore highlighting. +- Fix wilcard protected tags protecting all branches. +- Restrict Todo API mark_as_done endpoint to the user's todos only. + + ## 10.3.6 (2018-01-22) ### Fixed (17 changes, 2 of them are from the community) @@ -415,6 +454,16 @@ entry. - Clean up schema of the "merge_requests" table. +## 10.2.8 (2018-02-07) + +### Security (4 changes) + +- Fix namespace access issue for GitHub, BitBucket, and GitLab.com project importers. +- Fix stored XSS in code blocks that ignore highlighting. +- Fix wilcard protected tags protecting all branches. +- Restrict Todo API mark_as_done endpoint to the user's todos only. + + ## 10.2.7 (2018-01-18) - No changes. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ed56da0353d..dfe4bf65f9f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -174,7 +174,7 @@ Assigning a team label makes sure issues get the attention of the appropriate people. The current team labels are ~Build, ~"CI/CD", ~Discussion, ~Documentation, ~Edge, -~Geo, ~Gitaly, ~Platform, ~Monitoring, ~Release, and ~"UX". +~Geo, ~Gitaly, ~Monitoring, ~Platform, ~Release, ~"Security Products" and ~"UX". The descriptions on the [labels page][labels-page] explain what falls under the responsibility of each team. diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index b53d377d909..9a55e28031d 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0.80.0 +0.81.0 diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index 9b9a244206f..090ea9dad19 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -6.0.2 +6.0.3 @@ -294,7 +294,7 @@ group :metrics do gem 'influxdb', '~> 0.2', require: false # Prometheus - gem 'prometheus-client-mmap', '~> 0.7.0.beta44' + gem 'prometheus-client-mmap', '~> 0.9.1' gem 'raindrops', '~> 0.18' end @@ -410,7 +410,7 @@ group :ed25519 do end # Gitaly GRPC client -gem 'gitaly-proto', '~> 0.83.0', require: 'gitaly' +gem 'gitaly-proto', '~> 0.84.0', require: 'gitaly' # Locked until https://github.com/google/protobuf/issues/4210 is closed gem 'google-protobuf', '= 3.5.1' diff --git a/Gemfile.lock b/Gemfile.lock index e78c3c5f794..22c4fc0ef28 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -285,7 +285,7 @@ GEM po_to_json (>= 1.0.0) rails (>= 3.2.0) gherkin-ruby (0.3.2) - gitaly-proto (0.83.0) + gitaly-proto (0.84.0) google-protobuf (~> 3.1) grpc (~> 1.0) github-linguist (4.7.6) @@ -636,7 +636,7 @@ GEM parser unparser procto (0.0.3) - prometheus-client-mmap (0.7.0.beta44) + prometheus-client-mmap (0.9.1) pry (0.10.4) coderay (~> 1.1.0) method_source (~> 0.8.1) @@ -1056,7 +1056,7 @@ DEPENDENCIES gettext (~> 3.2.2) gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails_js (~> 1.2.0) - gitaly-proto (~> 0.83.0) + gitaly-proto (~> 0.84.0) github-linguist (~> 4.7.0) gitlab-flowdock-git-hook (~> 1.0.1) gitlab-markup (~> 1.6.2) @@ -1132,7 +1132,7 @@ DEPENDENCIES peek-sidekiq (~> 1.0.3) pg (~> 0.18.2) premailer-rails (~> 1.9.7) - prometheus-client-mmap (~> 0.7.0.beta44) + prometheus-client-mmap (~> 0.9.1) pry-byebug (~> 3.4.1) pry-rails (~> 0.3.4) rack-attack (~> 4.4.1) diff --git a/PROCESS.md b/PROCESS.md index 99af3be7f14..c24210341e0 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -71,11 +71,15 @@ star, smile, etc.). Some good tips about code reviews can be found in our ## Feature freeze on the 7th for the release on the 22nd -After the 7th (Pacific Standard Time Zone) of each month, RC1 of the upcoming release (to be shipped on the 22nd) is created and deployed to GitLab.com and the stable branch for this release is frozen, which means master is no longer merged into it. +After 7th at 23:59 (Pacific Standard Time Zone) of each month, RC1 of the upcoming release (to be shipped on the 22nd) is created and deployed to GitLab.com and the stable branch for this release is frozen, which means master is no longer merged into it. Merge requests may still be merged into master during this period, but they will go into the _next_ release, unless they are manually cherry-picked into the stable branch. + By freezing the stable branches 2 weeks prior to a release, we reduce the risk of a last minute merge request potentially breaking things. +Any release candidate that gets created after this date can become a final release, +hence the name release candidate. + ### Between the 1st and the 7th These types of merge requests for the upcoming release need special consideration: @@ -193,11 +197,10 @@ to be backported down to the `9.5` release, you will need to assign it the ### Asking for an exception If you think a merge request should go into an RC or patch even though it does not meet these requirements, -you can ask for an exception to be made. Exceptions require sign-off from 3 people besides the developer: +you can ask for an exception to be made. -1. a Release Manager -2. an Engineering Lead -3. an Engineering Director, the VP of Engineering, or the CTO +Go to [Release tasks issue tracker](https://gitlab.com/gitlab-org/release/tasks/issues/new) and create an issue +using the `Exception-request` issue template. You can find who is who on the [team page](https://about.gitlab.com/team/). diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index 87109a802e5..3283ce5ec36 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -50,10 +50,8 @@ class AwardsHandler { this.registerEventListener('on', $('html'), 'click', (e) => { const $target = $(e.target); - if (!$target.closest('.emoji-menu-content').length) { - $('.js-awards-block.current').removeClass('current'); - } if (!$target.closest('.emoji-menu').length) { + $('.js-awards-block.current').removeClass('current'); if ($('.emoji-menu').is(':visible')) { $('.js-add-award.is-active').removeClass('is-active'); this.hideMenuElement($('.emoji-menu')); diff --git a/app/assets/javascripts/boards/components/board.js b/app/assets/javascripts/boards/components/board.js index a8dafd31f12..9c4cc2338c8 100644 --- a/app/assets/javascripts/boards/components/board.js +++ b/app/assets/javascripts/boards/components/board.js @@ -2,7 +2,7 @@ import Sortable from 'vendor/Sortable'; import Vue from 'vue'; import AccessorUtilities from '../../lib/utils/accessor'; -import boardList from './board_list'; +import boardList from './board_list.vue'; import boardBlankState from './board_blank_state'; import './board_delete'; diff --git a/app/assets/javascripts/boards/components/board_list.js b/app/assets/javascripts/boards/components/board_list.vue index 591f1dc8313..9a0442e2afe 100644 --- a/app/assets/javascripts/boards/components/board_list.js +++ b/app/assets/javascripts/boards/components/board_list.vue @@ -1,3 +1,4 @@ +<script> import Sortable from 'vendor/Sortable'; import boardNewIssue from './board_new_issue'; import boardCard from './board_card.vue'; @@ -8,6 +9,11 @@ const Store = gl.issueBoards.BoardsStore; export default { name: 'BoardList', + components: { + boardCard, + boardNewIssue, + loadingIcon, + }, props: { disabled: { type: Boolean, @@ -42,46 +48,6 @@ export default { showIssueForm: false, }; }, - components: { - boardCard, - boardNewIssue, - loadingIcon, - }, - methods: { - listHeight() { - return this.$refs.list.getBoundingClientRect().height; - }, - scrollHeight() { - return this.$refs.list.scrollHeight; - }, - scrollTop() { - return this.$refs.list.scrollTop + this.listHeight(); - }, - scrollToTop() { - this.$refs.list.scrollTop = 0; - }, - loadNextPage() { - const getIssues = this.list.nextPage(); - const loadingDone = () => { - this.list.loadingMore = false; - }; - - if (getIssues) { - this.list.loadingMore = true; - getIssues - .then(loadingDone) - .catch(loadingDone); - } - }, - toggleForm() { - this.showIssueForm = !this.showIssueForm; - }, - onScroll() { - if (!this.loadingMore && (this.scrollTop() > this.scrollHeight() - this.scrollOffset)) { - this.loadNextPage(); - } - }, - }, watch: { filters: { handler() { @@ -157,51 +123,90 @@ export default { eventHub.$off(`scroll-board-list-${this.list.id}`, this.scrollToTop); this.$refs.list.removeEventListener('scroll', this.onScroll); }, - template: ` - <div class="board-list-component"> - <div - class="board-list-loading text-center" - aria-label="Loading issues" - v-if="loading"> - <loading-icon /> - </div> - <board-new-issue - :list="list" - v-if="list.type !== 'closed' && showIssueForm"/> - <ul - class="board-list" - v-show="!loading" - ref="list" - :data-board="list.id" - :class="{ 'is-smaller': showIssueForm }"> - <board-card - v-for="(issue, index) in issues" - ref="issue" - :index="index" - :list="list" - :issue="issue" - :issue-link-base="issueLinkBase" - :root-path="rootPath" - :disabled="disabled" - :key="issue.id" /> - <li - class="board-list-count text-center" - v-if="showCount" - data-issue-id="-1"> + methods: { + listHeight() { + return this.$refs.list.getBoundingClientRect().height; + }, + scrollHeight() { + return this.$refs.list.scrollHeight; + }, + scrollTop() { + return this.$refs.list.scrollTop + this.listHeight(); + }, + scrollToTop() { + this.$refs.list.scrollTop = 0; + }, + loadNextPage() { + const getIssues = this.list.nextPage(); + const loadingDone = () => { + this.list.loadingMore = false; + }; - <loading-icon - v-show="list.loadingMore" - label="Loading more issues" - /> + if (getIssues) { + this.list.loadingMore = true; + getIssues + .then(loadingDone) + .catch(loadingDone); + } + }, + toggleForm() { + this.showIssueForm = !this.showIssueForm; + }, + onScroll() { + if (!this.loadingMore && (this.scrollTop() > this.scrollHeight() - this.scrollOffset)) { + this.loadNextPage(); + } + }, + }, +}; +</script> - <span v-if="list.issues.length === list.issuesSize"> - Showing all issues - </span> - <span v-else> - Showing {{ list.issues.length }} of {{ list.issuesSize }} issues - </span> - </li> - </ul> +<template> + <div class="board-list-component"> + <div + class="board-list-loading text-center" + aria-label="Loading issues" + v-if="loading"> + <loading-icon /> </div> - `, -}; + <board-new-issue + :list="list" + v-if="list.type !== 'closed' && showIssueForm"/> + <ul + class="board-list" + v-show="!loading" + ref="list" + :data-board="list.id" + :class="{ 'is-smaller': showIssueForm }"> + <board-card + v-for="(issue, index) in issues" + ref="issue" + :index="index" + :list="list" + :issue="issue" + :issue-link-base="issueLinkBase" + :root-path="rootPath" + :disabled="disabled" + :key="issue.id" /> + <li + class="board-list-count text-center" + v-if="showCount" + data-issue-id="-1"> + <loading-icon + v-show="list.loadingMore" + label="Loading more issues" + /> + <span + v-if="list.issues.length === list.issuesSize" + > + Showing all issues + </span> + <span + v-else + > + Showing {{ list.issues.length }} of {{ list.issuesSize }} issues + </span> + </li> + </ul> + </div> +</template> diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js index 983429550f0..add24303e7b 100644 --- a/app/assets/javascripts/boards/components/board_sidebar.js +++ b/app/assets/javascripts/boards/components/board_sidebar.js @@ -2,6 +2,7 @@ import Vue from 'vue'; import Flash from '../../flash'; +import { __ } from '../../locale'; import Sidebar from '../../right_sidebar'; import eventHub from '../../sidebar/event_hub'; import assigneeTitle from '../../sidebar/components/assignees/assignee_title'; @@ -95,7 +96,7 @@ gl.issueBoards.BoardSidebar = Vue.extend({ }) .catch(() => { this.loadingAssignees = false; - return new Flash('An error occurred while saving assignees'); + Flash(__('An error occurred while saving assignees')); }); }, }, diff --git a/app/assets/javascripts/boards/components/modal/footer.js b/app/assets/javascripts/boards/components/modal/footer.js index 182957113a2..03cd7ef65cb 100644 --- a/app/assets/javascripts/boards/components/modal/footer.js +++ b/app/assets/javascripts/boards/components/modal/footer.js @@ -1,7 +1,6 @@ -/* eslint-disable no-new */ - import Vue from 'vue'; import Flash from '../../../flash'; +import { __ } from '../../../locale'; import './lists_dropdown'; import { pluralize } from '../../../lib/utils/text_utility'; @@ -36,7 +35,7 @@ gl.issueBoards.ModalFooter = Vue.extend({ gl.boardService.bulkUpdate(issueIds, { add_label_ids: [list.label.id], }).catch(() => { - new Flash('Failed to update issues, please try again.', 'alert'); + Flash(__('Failed to update issues, please try again.')); selectedIssues.forEach((issue) => { list.removeIssue(issue); diff --git a/app/assets/javascripts/boards/components/new_list_dropdown.js b/app/assets/javascripts/boards/components/new_list_dropdown.js index c19c989680d..cf0bb5f5376 100644 --- a/app/assets/javascripts/boards/components/new_list_dropdown.js +++ b/app/assets/javascripts/boards/components/new_list_dropdown.js @@ -1,5 +1,6 @@ /* eslint-disable func-names, no-new, space-before-function-paren, one-var, promise/catch-or-return */ +import axios from '~/lib/utils/axios_utils'; import _ from 'underscore'; import CreateLabelDropdown from '../../create_label'; @@ -28,9 +29,9 @@ gl.issueBoards.newListDropdownInit = () => { $this.glDropdown({ data(term, callback) { - $.get($this.attr('data-list-labels-path')) - .then((resp) => { - callback(resp); + axios.get($this.attr('data-list-labels-path')) + .then(({ data }) => { + callback(data); }); }, renderRow (label) { diff --git a/app/assets/javascripts/boards/components/sidebar/remove_issue.js b/app/assets/javascripts/boards/components/sidebar/remove_issue.js index 1ad97211934..0ae32bb4d0a 100644 --- a/app/assets/javascripts/boards/components/sidebar/remove_issue.js +++ b/app/assets/javascripts/boards/components/sidebar/remove_issue.js @@ -1,7 +1,6 @@ -/* eslint-disable no-new */ - import Vue from 'vue'; import Flash from '../../../flash'; +import { __ } from '../../../locale'; const Store = gl.issueBoards.BoardsStore; @@ -45,7 +44,7 @@ gl.issueBoards.RemoveIssueBtn = Vue.extend({ }, }; Vue.http.patch(this.updateUrl, data).catch(() => { - new Flash('Failed to remove issue from board, please try again.', 'alert'); + Flash(__('Failed to remove issue from board, please try again.')); lists.forEach((list) => { list.addIssue(issue); diff --git a/app/assets/javascripts/ci_variable_list/ajax_variable_list.js b/app/assets/javascripts/ci_variable_list/ajax_variable_list.js new file mode 100644 index 00000000000..b33adff609f --- /dev/null +++ b/app/assets/javascripts/ci_variable_list/ajax_variable_list.js @@ -0,0 +1,117 @@ +import _ from 'underscore'; +import axios from '../lib/utils/axios_utils'; +import { s__ } from '../locale'; +import Flash from '../flash'; +import { convertPermissionToBoolean } from '../lib/utils/common_utils'; +import statusCodes from '../lib/utils/http_status'; +import VariableList from './ci_variable_list'; + +function generateErrorBoxContent(errors) { + const errorList = [].concat(errors).map(errorString => ` + <li> + ${_.escape(errorString)} + </li> + `); + + return ` + <p> + ${s__('CiVariable|Validation failed')} + </p> + <ul> + ${errorList.join('')} + </ul> + `; +} + +// Used for the variable list on CI/CD projects/groups settings page +export default class AjaxVariableList { + constructor({ + container, + saveButton, + errorBox, + formField = 'variables', + saveEndpoint, + }) { + this.container = container; + this.saveButton = saveButton; + this.errorBox = errorBox; + this.saveEndpoint = saveEndpoint; + + this.variableList = new VariableList({ + container: this.container, + formField, + }); + + this.bindEvents(); + this.variableList.init(); + } + + bindEvents() { + this.saveButton.addEventListener('click', this.onSaveClicked.bind(this)); + } + + onSaveClicked() { + const loadingIcon = this.saveButton.querySelector('.js-secret-variables-save-loading-icon'); + loadingIcon.classList.toggle('hide', false); + this.errorBox.classList.toggle('hide', true); + // We use this to prevent a user from changing a key before we have a chance + // to match it up in `updateRowsWithPersistedVariables` + this.variableList.toggleEnableRow(false); + + return axios.patch(this.saveEndpoint, { + variables_attributes: this.variableList.getAllData(), + }, { + // We want to be able to process the `res.data` from a 400 error response + // and print the validation messages such as duplicate variable keys + validateStatus: status => ( + status >= statusCodes.OK && + status < statusCodes.MULTIPLE_CHOICES + ) || + status === statusCodes.BAD_REQUEST, + }) + .then((res) => { + loadingIcon.classList.toggle('hide', true); + this.variableList.toggleEnableRow(true); + + if (res.status === statusCodes.OK && res.data) { + this.updateRowsWithPersistedVariables(res.data.variables); + this.variableList.hideValues(); + } else if (res.status === statusCodes.BAD_REQUEST) { + // Validation failed + this.errorBox.innerHTML = generateErrorBoxContent(res.data); + this.errorBox.classList.toggle('hide', false); + } + }) + .catch(() => { + loadingIcon.classList.toggle('hide', true); + this.variableList.toggleEnableRow(true); + Flash(s__('CiVariable|Error occured while saving variables')); + }); + } + + updateRowsWithPersistedVariables(persistedVariables = []) { + const persistedVariableMap = [].concat(persistedVariables).reduce((variableMap, variable) => ({ + ...variableMap, + [variable.key]: variable, + }), {}); + + this.container.querySelectorAll('.js-row').forEach((row) => { + // If we submitted a row that was destroyed, remove it so we don't try + // to destroy it again which would cause a BE error + const destroyInput = row.querySelector('.js-ci-variable-input-destroy'); + if (convertPermissionToBoolean(destroyInput.value)) { + row.remove(); + // Update the ID input so any future edits and `_destroy` will apply on the BE + } else { + const key = row.querySelector('.js-ci-variable-input-key').value; + const persistedVariable = persistedVariableMap[key]; + + if (persistedVariable) { + // eslint-disable-next-line no-param-reassign + row.querySelector('.js-ci-variable-input-id').value = persistedVariable.id; + row.setAttribute('data-is-persisted', 'true'); + } + } + }); + } +} diff --git a/app/assets/javascripts/ci_variable_list/ci_variable_list.js b/app/assets/javascripts/ci_variable_list/ci_variable_list.js index e46478ddb98..745f3404295 100644 --- a/app/assets/javascripts/ci_variable_list/ci_variable_list.js +++ b/app/assets/javascripts/ci_variable_list/ci_variable_list.js @@ -11,7 +11,7 @@ function createEnvironmentItem(value) { return { title: value === '*' ? ALL_ENVIRONMENTS_STRING : value, id: value, - text: value, + text: value === '*' ? s__('CiVariable|* (All environments)') : value, }; } @@ -39,13 +39,13 @@ export default class VariableList { }, protected: { selector: '.js-ci-variable-input-protected', - default: 'true', + default: 'false', }, - environment: { + environment_scope: { // We can't use a `.js-` class here because // gl_dropdown replaces the <input> and doesn't copy over the class // See https://gitlab.com/gitlab-org/gitlab-ce/issues/42458 - selector: `input[name="${this.formField}[variables_attributes][][environment]"]`, + selector: `input[name="${this.formField}[variables_attributes][][environment_scope]"]`, default: '*', }, _destroy: { @@ -104,12 +104,15 @@ export default class VariableList { setupToggleButtons($row[0]); + // Reset the resizable textarea + $row.find(this.inputMap.value.selector).css('height', ''); + const $environmentSelect = $row.find('.js-variable-environment-toggle'); if ($environmentSelect.length) { const createItemDropdown = new CreateItemDropdown({ $dropdown: $environmentSelect, defaultToggleLabel: ALL_ENVIRONMENTS_STRING, - fieldName: `${this.formField}[variables_attributes][][environment]`, + fieldName: `${this.formField}[variables_attributes][][environment_scope]`, getData: (term, callback) => callback(this.getEnvironmentValues()), createNewItemFromValue: createEnvironmentItem, onSelect: () => { @@ -117,7 +120,7 @@ export default class VariableList { // so they have the new value we just picked this.refreshDropdownData(); - $row.find(this.inputMap.environment.selector).trigger('trigger-change'); + $row.find(this.inputMap.environment_scope.selector).trigger('trigger-change'); }, }); @@ -143,7 +146,8 @@ export default class VariableList { $row.after($rowClone); } - removeRow($row) { + removeRow(row) { + const $row = $(row); const isPersisted = convertPermissionToBoolean($row.attr('data-is-persisted')); if (isPersisted) { @@ -155,6 +159,10 @@ export default class VariableList { } else { $row.remove(); } + + // Refresh the other dropdowns in the variable list + // so any value with the variable deleted is gone + this.refreshDropdownData(); } checkIfRowTouched($row) { @@ -165,6 +173,15 @@ export default class VariableList { }); } + toggleEnableRow(isEnabled = true) { + this.$container.find(this.inputMap.key.selector).attr('disabled', !isEnabled); + this.$container.find('.js-row-remove-button').attr('disabled', !isEnabled); + } + + hideValues() { + this.secretValues.updateDom(false); + } + getAllData() { // Ignore the last empty row because we don't want to try persist // a blank variable and run into validation problems. @@ -185,7 +202,7 @@ export default class VariableList { } getEnvironmentValues() { - const valueMap = this.$container.find(this.inputMap.environment.selector).toArray() + const valueMap = this.$container.find(this.inputMap.environment_scope.selector).toArray() .reduce((prevValueMap, envInput) => ({ ...prevValueMap, [envInput.value]: envInput.value, diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js index 3d6ec37e6dd..b070a59cf15 100644 --- a/app/assets/javascripts/clusters/clusters_bundle.js +++ b/app/assets/javascripts/clusters/clusters_bundle.js @@ -32,6 +32,7 @@ export default class Clusters { installIngressPath, installRunnerPath, installPrometheusPath, + managePrometheusPath, clusterStatus, clusterStatusReason, helpPath, @@ -40,6 +41,7 @@ export default class Clusters { this.store = new ClustersStore(); this.store.setHelpPaths(helpPath, ingressHelpPath); + this.store.setManagePrometheusPath(managePrometheusPath); this.store.updateStatus(clusterStatus); this.store.updateStatusReason(clusterStatusReason); this.service = new ClustersService({ @@ -95,6 +97,7 @@ export default class Clusters { applications: this.state.applications, helpPath: this.state.helpPath, ingressHelpPath: this.state.ingressHelpPath, + managePrometheusPath: this.state.managePrometheusPath, }, }); }, diff --git a/app/assets/javascripts/clusters/components/application_row.vue b/app/assets/javascripts/clusters/components/application_row.vue index c13bbcee863..50e35bbbba5 100644 --- a/app/assets/javascripts/clusters/components/application_row.vue +++ b/app/assets/javascripts/clusters/components/application_row.vue @@ -32,6 +32,10 @@ type: String, required: false, }, + manageLink: { + type: String, + required: false, + }, description: { type: String, required: true, @@ -89,6 +93,12 @@ return label; }, + showManageButton() { + return this.manageLink && this.status === APPLICATION_INSTALLED; + }, + manageButtonLabel() { + return s__('ClusterIntegration|Manage'); + }, hasError() { return this.status === APPLICATION_ERROR || this.requestStatus === REQUEST_FAILURE; @@ -141,9 +151,21 @@ <div v-html="description"></div> </div> <div - class="table-section table-button-footer section-15 section-align-top" + class="table-section table-button-footer section-align-top" + :class="{ 'section-20': showManageButton, 'section-15': !showManageButton }" role="gridcell" > + <div + v-if="showManageButton" + class="btn-group table-action-buttons" + > + <a + class="btn" + :href="manageLink" + > + {{ manageButtonLabel }} + </a> + </div> <div class="btn-group table-action-buttons"> <loading-button class="js-cluster-application-install-button" diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue index f4259700370..978881a4831 100644 --- a/app/assets/javascripts/clusters/components/applications.vue +++ b/app/assets/javascripts/clusters/components/applications.vue @@ -23,13 +23,19 @@ required: false, default: '', }, + managePrometheusPath: { + type: String, + required: false, + default: '', + }, }, computed: { generalApplicationDescription() { return sprintf( - _.escape(s__(`ClusterIntegration|Install applications on your Kubernetes cluster. - Read more about %{helpLink}`)), - { + _.escape(s__( + `ClusterIntegration|Install applications on your Kubernetes cluster. + Read more about %{helpLink}`, + )), { helpLink: `<a href="${this.helpPath}"> ${_.escape(s__('ClusterIntegration|installing applications'))} </a>`, @@ -96,11 +102,12 @@ }, prometheusDescription() { return sprintf( - _.escape(s__(`ClusterIntegration|Prometheus is an open-source monitoring system - with %{gitlabIntegrationLink} to monitor deployed applications.`)), - { + _.escape(s__( + `ClusterIntegration|Prometheus is an open-source monitoring system + with %{gitlabIntegrationLink} to monitor deployed applications.`, + )), { gitlabIntegrationLink: `<a href="https://docs.gitlab.com/ce/user/project/integrations/prometheus.html" -target="_blank" rel="noopener noreferrer"> + target="_blank" rel="noopener noreferrer"> ${_.escape(s__('ClusterIntegration|GitLab Integration'))}</a>`, }, false, @@ -149,6 +156,7 @@ target="_blank" rel="noopener noreferrer"> id="prometheus" :title="applications.prometheus.title" title-link="https://prometheus.io/docs/introduction/overview/" + :manage-link="managePrometheusPath" :description="prometheusDescription" :status="applications.prometheus.status" :status-reason="applications.prometheus.statusReason" diff --git a/app/assets/javascripts/clusters/stores/clusters_store.js b/app/assets/javascripts/clusters/stores/clusters_store.js index 49c3d184ef9..904ee5fd475 100644 --- a/app/assets/javascripts/clusters/stores/clusters_store.js +++ b/app/assets/javascripts/clusters/stores/clusters_store.js @@ -45,6 +45,10 @@ export default class ClusterStore { this.state.ingressHelpPath = ingressHelpPath; } + setManagePrometheusPath(managePrometheusPath) { + this.state.managePrometheusPath = managePrometheusPath; + } + updateStatus(status) { this.state.status = status; } diff --git a/app/assets/javascripts/commit/image_file.js b/app/assets/javascripts/commit/image_file.js index 525fbf9dac9..6504a0bbbfc 100644 --- a/app/assets/javascripts/commit/image_file.js +++ b/app/assets/javascripts/commit/image_file.js @@ -1,5 +1,4 @@ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-use-before-define, prefer-arrow-callback, no-else-return, consistent-return, prefer-template, quotes, one-var, one-var-declaration-per-line, no-unused-vars, no-return-assign, comma-dangle, quote-props, no-unused-expressions, no-sequences, object-shorthand, max-len */ -import 'vendor/jquery.waitforimages'; // Width where images must fits in, for 2-up this gets divided by 2 const availWidth = 900; diff --git a/app/assets/javascripts/commits.js b/app/assets/javascripts/commits.js index 4b2f75fffde..2be63bd8c76 100644 --- a/app/assets/javascripts/commits.js +++ b/app/assets/javascripts/commits.js @@ -1,52 +1,36 @@ -/* eslint-disable func-names, wrap-iife, consistent-return, - no-return-assign, no-param-reassign, one-var-declaration-per-line, no-unused-vars, - prefer-template, object-shorthand, prefer-arrow-callback */ - import { pluralize } from './lib/utils/text_utility'; import { localTimeAgo } from './lib/utils/datetime_utility'; import Pager from './pager'; import axios from './lib/utils/axios_utils'; -export default (function () { - const CommitsList = {}; - - CommitsList.timer = null; +export default class CommitsList { + constructor(limit = 0) { + this.timer = null; - CommitsList.init = function (limit) { this.$contentList = $('.content_list'); - $('body').on('click', '.day-commits-table li.commit', function (e) { - if (e.target.nodeName !== 'A') { - location.href = $(this).attr('url'); - e.stopPropagation(); - return false; - } - }); - - Pager.init(parseInt(limit, 10), false, false, this.processCommits); + Pager.init(parseInt(limit, 10), false, false, this.processCommits.bind(this)); this.content = $('#commits-list'); this.searchField = $('#commits-search'); this.lastSearch = this.searchField.val(); - return this.initSearch(); - }; + this.initSearch(); + } - CommitsList.initSearch = function () { + initSearch() { this.timer = null; - return this.searchField.keyup((function (_this) { - return function () { - clearTimeout(_this.timer); - return _this.timer = setTimeout(_this.filterResults, 500); - }; - })(this)); - }; + this.searchField.on('keyup', () => { + clearTimeout(this.timer); + this.timer = setTimeout(this.filterResults.bind(this), 500); + }); + } - CommitsList.filterResults = function () { + filterResults() { const form = $('.commits-search-form'); - const search = CommitsList.searchField.val(); - if (search === CommitsList.lastSearch) return Promise.resolve(); - const commitsUrl = form.attr('action') + '?' + form.serialize(); - CommitsList.content.fadeTo('fast', 0.5); + const search = this.searchField.val(); + if (search === this.lastSearch) return Promise.resolve(); + const commitsUrl = `${form.attr('action')}?${form.serialize()}`; + this.content.fadeTo('fast', 0.5); const params = form.serializeArray().reduce((acc, obj) => Object.assign(acc, { [obj.name]: obj.value, }), {}); @@ -55,9 +39,9 @@ export default (function () { params, }) .then(({ data }) => { - CommitsList.lastSearch = search; - CommitsList.content.html(data.html); - CommitsList.content.fadeTo('fast', 1.0); + this.lastSearch = search; + this.content.html(data.html); + this.content.fadeTo('fast', 1.0); // Change url so if user reload a page - search results are saved history.replaceState({ @@ -65,16 +49,16 @@ export default (function () { }, document.title, commitsUrl); }) .catch(() => { - CommitsList.content.fadeTo('fast', 1.0); - CommitsList.lastSearch = null; + this.content.fadeTo('fast', 1.0); + this.lastSearch = null; }); - }; + } // Prepare loaded data. - CommitsList.processCommits = (data) => { + processCommits(data) { let processedData = data; const $processedData = $(processedData); - const $commitsHeadersLast = CommitsList.$contentList.find('li.js-commit-header').last(); + const $commitsHeadersLast = this.$contentList.find('li.js-commit-header').last(); const lastShownDay = $commitsHeadersLast.data('day'); const $loadedCommitsHeadersFirst = $processedData.filter('li.js-commit-header').first(); const loadedShownDayFirst = $loadedCommitsHeadersFirst.data('day'); @@ -97,7 +81,5 @@ export default (function () { localTimeAgo($processedData.find('.js-timeago')); return processedData; - }; - - return CommitsList; -})(); + } +} diff --git a/app/assets/javascripts/commons/jquery.js b/app/assets/javascripts/commons/jquery.js index b93e94a3c97..a7ed175f7a4 100644 --- a/app/assets/javascripts/commons/jquery.js +++ b/app/assets/javascripts/commons/jquery.js @@ -6,5 +6,5 @@ import 'vendor/jquery.endless-scroll'; import 'vendor/jquery.caret'; import 'vendor/jquery.atwho'; import 'vendor/jquery.scrollTo'; -import 'vendor/jquery.waitforimages'; +import 'jquery.waitforimages'; import 'select2/select2'; diff --git a/app/assets/javascripts/commons/polyfills/element.js b/app/assets/javascripts/commons/polyfills/element.js index 9a1f73bf2ac..b593bde6aa2 100644 --- a/app/assets/javascripts/commons/polyfills/element.js +++ b/app/assets/javascripts/commons/polyfills/element.js @@ -18,3 +18,22 @@ Element.prototype.matches = Element.prototype.matches || while (i >= 0 && elms.item(i) !== this) { i -= 1; } return i > -1; }; + +// From the polyfill on MDN, https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/remove#Polyfill +((arr) => { + arr.forEach((item) => { + if (Object.prototype.hasOwnProperty.call(item, 'remove')) { + return; + } + Object.defineProperty(item, 'remove', { + configurable: true, + enumerable: true, + writable: true, + value: function remove() { + if (this.parentNode !== null) { + this.parentNode.removeChild(this); + } + }, + }); + }); +})([Element.prototype, CharacterData.prototype, DocumentType.prototype]); diff --git a/app/assets/javascripts/create_merge_request_dropdown.js b/app/assets/javascripts/create_merge_request_dropdown.js index 482d83621e2..fb1fc9cd32e 100644 --- a/app/assets/javascripts/create_merge_request_dropdown.js +++ b/app/assets/javascripts/create_merge_request_dropdown.js @@ -180,6 +180,7 @@ export default class CreateMergeRequestDropdown { valueAttribute: 'data-text', }, ], + hideOnClick: false, }; } diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js index a162424b3cf..3ab8f3ab7ad 100644 --- a/app/assets/javascripts/diff.js +++ b/app/assets/javascripts/diff.js @@ -1,3 +1,6 @@ +import axios from '~/lib/utils/axios_utils'; +import flash from '~/flash'; +import { __ } from '~/locale'; import { getLocationHash } from './lib/utils/url_utility'; import FilesCommentButton from './files_comment_button'; import SingleFileDiff from './single_file_diff'; @@ -69,7 +72,9 @@ export default class Diff { const view = file.data('view'); const params = { since, to, bottom, offset, unfold, view }; - $.get(link, params, response => $target.parent().replaceWith(response)); + axios.get(link, { params }) + .then(({ data }) => $target.parent().replaceWith(data)) + .catch(() => flash(__('An error occurred while loading diff'))); } openAnchoredDiff(cb) { diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index ab28b7d8d44..7e750d15d3d 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -1,15 +1,9 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, wrap-iife, no-shadow, consistent-return, one-var, one-var-declaration-per-line, camelcase, default-case, no-new, quotes, no-duplicate-case, no-case-declarations, no-fallthrough, max-len */ -import MergeRequest from './merge_request'; import Flash from './flash'; import GfmAutoComplete from './gfm_auto_complete'; -import ZenMode from './zen_mode'; -import initNotes from './init_notes'; -import initIssuableSidebar from './init_issuable_sidebar'; import { convertPermissionToBoolean } from './lib/utils/common_utils'; import GlFieldErrors from './gl_field_errors'; import Shortcuts from './shortcuts'; -import ShortcutsIssuable from './shortcuts_issuable'; -import Diff from './diff'; import SearchAutocomplete from './search_autocomplete'; var Dispatcher; @@ -49,154 +43,14 @@ var Dispatcher; }); switch (page) { - case 'projects:environments:metrics': - import('./pages/projects/environments/metrics') - .then(callDefault) - .catch(fail); - break; case 'projects:merge_requests:index': case 'projects:issues:index': case 'projects:issues:show': - shortcut_handler = true; - break; - case 'projects:milestones:index': - import('./pages/projects/milestones/index') - .then(callDefault) - .catch(fail); - break; - case 'projects:milestones:show': - import('./pages/projects/milestones/show') - .then(callDefault) - .catch(fail); - break; - case 'groups:milestones:show': - import('./pages/groups/milestones/show') - .then(callDefault) - .catch(fail); - break; - case 'dashboard:milestones:show': - import('./pages/dashboard/milestones/show') - .then(callDefault) - .catch(fail); - break; - case 'dashboard:issues': - import('./pages/dashboard/issues') - .then(callDefault) - .catch(fail); - break; - case 'dashboard:merge_requests': - import('./pages/dashboard/merge_requests') - .then(callDefault) - .catch(fail); - break; - case 'groups:issues': - import('./pages/groups/issues') - .then(callDefault) - .catch(fail); - break; - case 'groups:merge_requests': - import('./pages/groups/merge_requests') - .then(callDefault) - .catch(fail); - break; - case 'dashboard:todos:index': - import('./pages/dashboard/todos/index') - .then(callDefault) - .catch(fail); - break; - case 'admin:jobs:index': - import('./pages/admin/jobs/index') - .then(callDefault) - .catch(fail); - break; - case 'dashboard:projects:index': - case 'dashboard:projects:starred': - import('./pages/dashboard/projects') - .then(callDefault) - .catch(fail); - break; - case 'explore:projects:index': - case 'explore:projects:trending': - case 'explore:projects:starred': - import('./pages/explore/projects') - .then(callDefault) - .catch(fail); - break; - case 'explore:groups:index': - import('./pages/explore/groups') - .then(callDefault) - .catch(fail); - break; - case 'projects:milestones:new': - case 'projects:milestones:create': - import('./pages/projects/milestones/new') - .then(callDefault) - .catch(fail); - break; - case 'projects:milestones:edit': - case 'projects:milestones:update': - import('./pages/projects/milestones/edit') - .then(callDefault) - .catch(fail); - break; - case 'groups:milestones:new': - case 'groups:milestones:create': - import('./pages/groups/milestones/new') - .then(callDefault) - .catch(fail); - break; - case 'groups:milestones:edit': - case 'groups:milestones:update': - import('./pages/groups/milestones/edit') - .then(callDefault) - .catch(fail); - break; - case 'projects:compare:show': - import('./pages/projects/compare/show') - .then(callDefault) - .catch(fail); - break; - case 'projects:branches:new': - import('./pages/projects/branches/new') - .then(callDefault) - .catch(fail); - break; - case 'projects:branches:create': - import('./pages/projects/branches/new') - .then(callDefault) - .catch(fail); - break; - case 'projects:branches:index': - import('./pages/projects/branches/index') - .then(callDefault) - .catch(fail); - break; case 'projects:issues:new': - import('./pages/projects/issues/new') - .then(callDefault) - .catch(fail); - shortcut_handler = true; - break; case 'projects:issues:edit': - import('./pages/projects/issues/edit') - .then(callDefault) - .catch(fail); - shortcut_handler = true; - break; case 'projects:merge_requests:creations:new': - import('./pages/projects/merge_requests/creations/new') - .then(callDefault) - .catch(fail); case 'projects:merge_requests:creations:diffs': - import('./pages/projects/merge_requests/creations/diffs') - .then(callDefault) - .catch(fail); - shortcut_handler = true; - break; case 'projects:merge_requests:edit': - import('./pages/projects/merge_requests/edit') - .then(callDefault) - .catch(fail); shortcut_handler = true; break; case 'projects:tags:new': @@ -215,6 +69,11 @@ var Dispatcher; .then(callDefault) .catch(fail); break; + case 'projects:services:edit': + import('./pages/projects/services/edit') + .then(callDefault) + .catch(fail); + break; case 'projects:snippets:edit': case 'projects:snippets:update': import('./pages/projects/snippets/edit') @@ -247,17 +106,10 @@ var Dispatcher; .catch(fail); break; case 'projects:merge_requests:show': - new Diff(); - new ZenMode(); - - initIssuableSidebar(); - initNotes(); - - const mrShowNode = document.querySelector('.merge-request'); - window.mergeRequest = new MergeRequest({ - action: mrShowNode.dataset.mrAction, - }); - shortcut_handler = new ShortcutsIssuable(true); + import('./pages/projects/merge_requests/show') + .then(callDefault) + .catch(fail); + shortcut_handler = true; break; case 'dashboard:activity': import('./pages/dashboard/activity') @@ -460,11 +312,6 @@ var Dispatcher; .then(callDefault) .catch(fail); break; - case 'users:show': - import('./pages/users/show') - .then(callDefault) - .catch(fail); - break; case 'admin:conversational_development_index:show': import('./pages/admin/conversational_development_index/show') .then(callDefault) diff --git a/app/assets/javascripts/droplab/constants.js b/app/assets/javascripts/droplab/constants.js index 673e9bb4c0f..868d47e91b3 100644 --- a/app/assets/javascripts/droplab/constants.js +++ b/app/assets/javascripts/droplab/constants.js @@ -3,7 +3,6 @@ const DATA_DROPDOWN = 'data-dropdown'; const SELECTED_CLASS = 'droplab-item-selected'; const ACTIVE_CLASS = 'droplab-item-active'; const IGNORE_CLASS = 'droplab-item-ignore'; -const IGNORE_HIDING_CLASS = 'droplab-item-ignore-hiding'; // Matches `{{anything}}` and `{{ everything }}`. const TEMPLATE_REGEX = /\{\{(.+?)\}\}/g; @@ -14,5 +13,4 @@ export { ACTIVE_CLASS, TEMPLATE_REGEX, IGNORE_CLASS, - IGNORE_HIDING_CLASS, }; diff --git a/app/assets/javascripts/droplab/drop_down.js b/app/assets/javascripts/droplab/drop_down.js index 5eb0a339a1c..3cc316c3f3e 100644 --- a/app/assets/javascripts/droplab/drop_down.js +++ b/app/assets/javascripts/droplab/drop_down.js @@ -1,13 +1,14 @@ import utils from './utils'; -import { SELECTED_CLASS, IGNORE_CLASS, IGNORE_HIDING_CLASS } from './constants'; +import { SELECTED_CLASS, IGNORE_CLASS } from './constants'; class DropDown { - constructor(list, config = {}) { + constructor(list, config = { }) { this.currentIndex = 0; this.hidden = true; this.list = typeof list === 'string' ? document.querySelector(list) : list; this.items = []; this.eventWrapper = {}; + this.hideOnClick = config.hideOnClick !== false; if (config.addActiveClassToDropdownButton) { this.dropdownToggle = this.list.parentNode.querySelector('.js-dropdown-toggle'); @@ -37,15 +38,17 @@ class DropDown { clickEvent(e) { if (e.target.tagName === 'UL') return; - if (e.target.classList.contains(IGNORE_CLASS)) return; + if (e.target.closest(`.${IGNORE_CLASS}`)) return; - const selected = utils.closest(e.target, 'LI'); + const selected = e.target.closest('li'); if (!selected) return; this.addSelectedClass(selected); e.preventDefault(); - if (!e.target.classList.contains(IGNORE_HIDING_CLASS)) this.hide(); + if (this.hideOnClick) { + this.hide(); + } const listEvent = new CustomEvent('click.dl', { detail: { diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue index a9d554e549e..79326ca3487 100644 --- a/app/assets/javascripts/environments/components/environment_item.vue +++ b/app/assets/javascripts/environments/components/environment_item.vue @@ -1,8 +1,9 @@ <script> import Timeago from 'timeago.js'; import _ from 'underscore'; - import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; - import { humanize } from '../../lib/utils/text_utility'; + import tooltip from '~/vue_shared/directives/tooltip'; + import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; + import { humanize } from '~/lib/utils/text_utility'; import ActionsComponent from './environment_actions.vue'; import ExternalUrlComponent from './environment_external_url.vue'; import StopComponent from './environment_stop.vue'; @@ -21,14 +22,18 @@ export default { components: { - userAvatarLink, - 'commit-component': CommitComponent, - 'actions-component': ActionsComponent, - 'external-url-component': ExternalUrlComponent, - 'stop-component': StopComponent, - 'rollback-component': RollbackComponent, - 'terminal-button-component': TerminalButtonComponent, - 'monitoring-button-component': MonitoringButtonComponent, + UserAvatarLink, + CommitComponent, + ActionsComponent, + ExternalUrlComponent, + StopComponent, + RollbackComponent, + TerminalButtonComponent, + MonitoringButtonComponent, + }, + + directives: { + tooltip, }, props: { @@ -443,7 +448,11 @@ v-if="!model.isFolder" class="environment-name flex-truncate-parent table-mobile-content" :href="environmentPath"> - <span class="flex-truncate-child">{{ model.name }}</span> + <span + class="flex-truncate-child" + v-tooltip + :title="model.name" + >{{ model.name }}</span> </a> <span v-else diff --git a/app/assets/javascripts/filtered_search/filtered_search_bundle.js b/app/assets/javascripts/filtered_search/filtered_search_bundle.js index 6d5dd747224..293154917fa 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_bundle.js +++ b/app/assets/javascripts/filtered_search/filtered_search_bundle.js @@ -3,7 +3,6 @@ import './dropdown_hint'; import './dropdown_non_user'; import './dropdown_user'; import './dropdown_utils'; -import './filtered_search_token_keys'; import './filtered_search_dropdown_manager'; import './filtered_search_dropdown'; import './filtered_search_manager'; diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js index ff046aa286a..b2add862051 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js @@ -3,11 +3,11 @@ import DropLab from '~/droplab/drop_lab'; import FilteredSearchContainer from './container'; class FilteredSearchDropdownManager { - constructor(baseEndpoint = '', tokenizer, page) { + constructor(baseEndpoint = '', tokenizer, page, isGroup, filteredSearchTokenKeys) { this.container = FilteredSearchContainer.container; this.baseEndpoint = baseEndpoint.replace(/\/$/, ''); this.tokenizer = tokenizer; - this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys; + this.filteredSearchTokenKeys = filteredSearchTokenKeys; this.filteredSearchInput = this.container.querySelector('.filtered-search'); this.page = page; @@ -29,7 +29,15 @@ class FilteredSearchDropdownManager { } setupMapping() { - this.mapping = { + const supportedTokens = this.filteredSearchTokenKeys.getKeys(); + const allowedMappings = { + hint: { + reference: null, + gl: 'DropdownHint', + element: this.container.querySelector('#js-dropdown-hint'), + }, + }; + const availableMappings = { author: { reference: null, gl: 'DropdownUser', @@ -64,12 +72,15 @@ class FilteredSearchDropdownManager { gl: 'DropdownEmoji', element: this.container.querySelector('#js-dropdown-my-reaction'), }, - hint: { - reference: null, - gl: 'DropdownHint', - element: this.container.querySelector('#js-dropdown-hint'), - }, }; + + supportedTokens.forEach((type) => { + if (availableMappings[type]) { + allowedMappings[type] = availableMappings[type]; + } + }); + + this.mapping = allowedMappings; } static addWordToInput(tokenName, tokenValue = '', clicked = false) { diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js index 58ed0012f01..532a5fe1090 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js @@ -3,20 +3,33 @@ import { visitUrl } from '../lib/utils/url_utility'; import Flash from '../flash'; import FilteredSearchContainer from './container'; import RecentSearchesRoot from './recent_searches_root'; +import FilteredSearchTokenKeys from './filtered_search_token_keys'; import RecentSearchesStore from './stores/recent_searches_store'; import RecentSearchesService from './services/recent_searches_service'; import eventHub from './event_hub'; import { addClassIfElementExists } from '../lib/utils/dom_utils'; class FilteredSearchManager { - constructor(page) { + constructor({ + page, + filteredSearchTokenKeys = FilteredSearchTokenKeys, + stateFiltersSelector = '.issues-state-filters', + }) { + this.isGroup = false; + this.states = ['opened', 'closed', 'merged', 'all']; + this.page = page; this.container = FilteredSearchContainer.container; this.filteredSearchInput = this.container.querySelector('.filtered-search'); this.filteredSearchInputForm = this.filteredSearchInput.form; this.clearSearchButton = this.container.querySelector('.clear-search'); this.tokensContainer = this.container.querySelector('.tokens-container'); - this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys; + this.filteredSearchTokenKeys = filteredSearchTokenKeys; + this.stateFiltersSelector = stateFiltersSelector; + this.recentsStorageKeyNames = { + issues: 'issue-recent-searches', + merge_requests: 'merge-request-recent-searches', + }; this.recentSearchesStore = new RecentSearchesStore({ isLocalStorageAvailable: RecentSearchesService.isAvailable(), @@ -25,11 +38,7 @@ class FilteredSearchManager { this.searchHistoryDropdownElement = document.querySelector('.js-filtered-search-history-dropdown'); const fullPath = this.searchHistoryDropdownElement ? this.searchHistoryDropdownElement.dataset.fullPath : 'project'; - let recentSearchesPagePrefix = 'issue-recent-searches'; - if (this.page === 'merge_requests') { - recentSearchesPagePrefix = 'merge-request-recent-searches'; - } - const recentSearchesKey = `${fullPath}-${recentSearchesPagePrefix}`; + const recentSearchesKey = `${fullPath}-${this.recentsStorageKeyNames[this.page]}`; this.recentSearchesService = new RecentSearchesService(recentSearchesKey); } @@ -58,7 +67,13 @@ class FilteredSearchManager { if (this.filteredSearchInput) { this.tokenizer = gl.FilteredSearchTokenizer; - this.dropdownManager = new gl.FilteredSearchDropdownManager(this.filteredSearchInput.getAttribute('data-base-endpoint') || '', this.tokenizer, this.page); + this.dropdownManager = new gl.FilteredSearchDropdownManager( + this.filteredSearchInput.getAttribute('data-base-endpoint') || '', + this.tokenizer, + this.page, + this.isGroup, + this.filteredSearchTokenKeys, + ); this.recentSearchesRoot = new RecentSearchesRoot( this.recentSearchesStore, @@ -86,40 +101,33 @@ class FilteredSearchManager { } bindStateEvents() { - this.stateFilters = document.querySelector('.container-fluid .issues-state-filters'); + this.stateFilters = document.querySelector(`.container-fluid ${this.stateFiltersSelector}`); if (this.stateFilters) { this.searchStateWrapper = this.searchState.bind(this); - this.stateFilters.querySelector('[data-state="opened"]') - .addEventListener('click', this.searchStateWrapper); - this.stateFilters.querySelector('[data-state="closed"]') - .addEventListener('click', this.searchStateWrapper); - this.stateFilters.querySelector('[data-state="all"]') - .addEventListener('click', this.searchStateWrapper); - - this.mergedState = this.stateFilters.querySelector('[data-state="merged"]'); - if (this.mergedState) { - this.mergedState.addEventListener('click', this.searchStateWrapper); - } + this.applyToStateFilters((filterEl) => { + filterEl.addEventListener('click', this.searchStateWrapper); + }); } } unbindStateEvents() { if (this.stateFilters) { - this.stateFilters.querySelector('[data-state="opened"]') - .removeEventListener('click', this.searchStateWrapper); - this.stateFilters.querySelector('[data-state="closed"]') - .removeEventListener('click', this.searchStateWrapper); - this.stateFilters.querySelector('[data-state="all"]') - .removeEventListener('click', this.searchStateWrapper); - - if (this.mergedState) { - this.mergedState.removeEventListener('click', this.searchStateWrapper); - } + this.applyToStateFilters((filterEl) => { + filterEl.removeEventListener('click', this.searchStateWrapper); + }); } } + applyToStateFilters(callback) { + this.stateFilters.querySelectorAll('a[data-state]').forEach((filterEl) => { + if (this.states.indexOf(filterEl.dataset.state) > -1) { + callback(filterEl); + } + }); + } + bindEvents() { this.handleFormSubmit = this.handleFormSubmit.bind(this); this.setDropdownWrapper = this.dropdownManager.setDropdown.bind(this.dropdownManager); diff --git a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js index be595d7df1a..087ef5cd6f2 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js +++ b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js @@ -71,7 +71,7 @@ const conditions = [{ value: 'none', }]; -class FilteredSearchTokenKeys { +export default class FilteredSearchTokenKeys { static get() { return tokenKeys; } @@ -121,6 +121,3 @@ class FilteredSearchTokenKeys { .find(condition => condition.tokenKey === key && condition.value === value) || null; } } - -window.gl = window.gl || {}; -gl.FilteredSearchTokenKeys = FilteredSearchTokenKeys; diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js index d0f9e6af0f8..d200044b79f 100644 --- a/app/assets/javascripts/gl_form.js +++ b/app/assets/javascripts/gl_form.js @@ -1,5 +1,4 @@ -/* global autosize */ - +import autosize from 'autosize'; import GfmAutoComplete from './gfm_auto_complete'; import dropzoneInput from './dropzone_input'; import textUtils from './lib/utils/text_markdown'; diff --git a/app/assets/javascripts/gpg_badges.js b/app/assets/javascripts/gpg_badges.js index 7ac9dcd1112..b33165f9402 100644 --- a/app/assets/javascripts/gpg_badges.js +++ b/app/assets/javascripts/gpg_badges.js @@ -1,3 +1,8 @@ +import { parseQueryStringIntoObject } from '~/lib/utils/common_utils'; +import axios from '~/lib/utils/axios_utils'; +import flash from '~/flash'; +import { __ } from '~/locale'; + export default class GpgBadges { static fetch() { const badges = $('.js-loading-gpg-badge'); @@ -5,13 +10,13 @@ export default class GpgBadges { badges.html('<i class="fa fa-spinner fa-spin"></i>'); - $.get({ - url: form.data('signatures-path'), - data: form.serialize(), - }).done((response) => { - response.signatures.forEach((signature) => { + const params = parseQueryStringIntoObject(form.serialize()); + return axios.get(form.data('signatures-path'), { params }) + .then(({ data }) => { + data.signatures.forEach((signature) => { badges.filter(`[data-commit-sha="${signature.commit_sha}"]`).replaceWith(signature.html); }); - }); + }) + .catch(() => flash(__('An error occurred while loading commits'))); } } diff --git a/app/assets/javascripts/groups/components/app.vue b/app/assets/javascripts/groups/components/app.vue index e035ba462db..b8f0566f48c 100644 --- a/app/assets/javascripts/groups/components/app.vue +++ b/app/assets/javascripts/groups/components/app.vue @@ -152,14 +152,14 @@ export default { showLeaveGroupModal(group, parentGroup) { this.targetGroup = group; this.targetParentGroup = parentGroup; - this.showModal = true; + this.updateModal = true; this.groupLeaveConfirmationMessage = s__(`GroupsTree|Are you sure you want to leave the "${group.fullName}" group?`); }, hideLeaveGroupModal() { - this.showModal = false; + this.updateModal = false; }, leaveGroup() { - this.showModal = false; + this.updateModal = false; this.targetGroup.isBeingRemoved = true; this.service.leaveGroup(this.targetGroup.leavePath) .then(res => res.json()) diff --git a/app/assets/javascripts/groups_select.js b/app/assets/javascripts/groups_select.js index a69a0bde17b..65a2395fe29 100644 --- a/app/assets/javascripts/groups_select.js +++ b/app/assets/javascripts/groups_select.js @@ -1,5 +1,6 @@ +import axios from './lib/utils/axios_utils'; import Api from './api'; -import { normalizeCRLFHeaders } from './lib/utils/common_utils'; +import { normalizeHeaders } from './lib/utils/common_utils'; export default function groupsSelect() { // Needs to be accessible in rspec @@ -17,24 +18,23 @@ export default function groupsSelect() { dataType: 'json', quietMillis: 250, transport(params) { - return $.ajax(params) - .then((data, status, xhr) => { - const results = data || []; - - const headers = normalizeCRLFHeaders(xhr.getAllResponseHeaders()); + axios[params.type.toLowerCase()](params.url, { + params: params.data, + }) + .then((res) => { + const results = res.data || []; + const headers = normalizeHeaders(res.headers); const currentPage = parseInt(headers['X-PAGE'], 10) || 0; const totalPages = parseInt(headers['X-TOTAL-PAGES'], 10) || 0; const more = currentPage < totalPages; - return { + params.success({ results, pagination: { more, }, - }; - }) - .then(params.success) - .fail(params.error); + }); + }).catch(params.error); }, data(search, page) { return { diff --git a/app/assets/javascripts/ide/monaco_loader.js b/app/assets/javascripts/ide/monaco_loader.js index af83a1ec0b4..142a220097b 100644 --- a/app/assets/javascripts/ide/monaco_loader.js +++ b/app/assets/javascripts/ide/monaco_loader.js @@ -6,6 +6,11 @@ monacoContext.require.config({ }, }); +// ignore CDN config and use local assets path for service worker which cannot be cross-domain +const relativeRootPath = (gon && gon.relative_url_root) || ''; +const monacoPath = `${relativeRootPath}/assets/webpack/monaco-editor/vs`; +window.MonacoEnvironment = { getWorkerUrl: () => `${monacoPath}/base/worker/workerMain.js` }; + // eslint-disable-next-line no-underscore-dangle window.__monaco_context__ = monacoContext; export default monacoContext.require; diff --git a/app/assets/javascripts/importer_status.js b/app/assets/javascripts/importer_status.js index 1dc70872d92..35094f8e73b 100644 --- a/app/assets/javascripts/importer_status.js +++ b/app/assets/javascripts/importer_status.js @@ -1,3 +1,7 @@ +import { __ } from './locale'; +import axios from './lib/utils/axios_utils'; +import flash from './flash'; + class ImporterStatus { constructor(jobsUrl, importUrl) { this.jobsUrl = jobsUrl; @@ -9,29 +13,7 @@ class ImporterStatus { initStatusPage() { $('.js-add-to-import') .off('click') - .on('click', (event) => { - const $btn = $(event.currentTarget); - const $tr = $btn.closest('tr'); - const $targetField = $tr.find('.import-target'); - const $namespaceInput = $targetField.find('.js-select-namespace option:selected'); - const id = $tr.attr('id').replace('repo_', ''); - let targetNamespace; - let newName; - if ($namespaceInput.length > 0) { - targetNamespace = $namespaceInput[0].innerHTML; - newName = $targetField.find('#path').prop('value'); - $targetField.empty().append(`${targetNamespace}/${newName}`); - } - $btn.disable().addClass('is-loading'); - - return $.post(this.importUrl, { - repo_id: id, - target_namespace: targetNamespace, - new_name: newName, - }, { - dataType: 'script', - }); - }); + .on('click', this.addToImport.bind(this)); $('.js-import-all') .off('click') @@ -44,34 +26,74 @@ class ImporterStatus { }); } - setAutoUpdate() { - return setInterval(() => $.get(this.jobsUrl, data => $.each(data, (i, job) => { - const jobItem = $(`#project_${job.id}`); - const statusField = jobItem.find('.job-status'); + addToImport(event) { + const $btn = $(event.currentTarget); + const $tr = $btn.closest('tr'); + const $targetField = $tr.find('.import-target'); + const $namespaceInput = $targetField.find('.js-select-namespace option:selected'); + const id = $tr.attr('id').replace('repo_', ''); + let targetNamespace; + let newName; + if ($namespaceInput.length > 0) { + targetNamespace = $namespaceInput[0].innerHTML; + newName = $targetField.find('#path').prop('value'); + $targetField.empty().append(`${targetNamespace}/${newName}`); + } + $btn.disable().addClass('is-loading'); + + return axios.post(this.importUrl, { + repo_id: id, + target_namespace: targetNamespace, + new_name: newName, + }) + .then(({ data }) => { + const job = $(`tr#repo_${id}`); + job.attr('id', `project_${data.id}`); - const spinner = '<i class="fa fa-spinner fa-spin"></i>'; + job.find('.import-target').html(`<a href="${data.full_path}">${data.full_path}</a>`); + $('table.import-jobs tbody').prepend(job); - switch (job.import_status) { - case 'finished': - jobItem.removeClass('active').addClass('success'); - statusField.html('<span><i class="fa fa-check"></i> done</span>'); - break; - case 'scheduled': - statusField.html(`${spinner} scheduled`); - break; - case 'started': - statusField.html(`${spinner} started`); - break; - default: - statusField.html(job.import_status); - break; - } - })), 4000); + job.addClass('active'); + job.find('.import-actions').html('<i class="fa fa-spinner fa-spin" aria-label="importing"></i> started'); + }) + .catch(() => flash(__('An error occurred while importing project'))); + } + + autoUpdate() { + return axios.get(this.jobsUrl) + .then(({ data = [] }) => { + data.forEach((job) => { + const jobItem = $(`#project_${job.id}`); + const statusField = jobItem.find('.job-status'); + + const spinner = '<i class="fa fa-spinner fa-spin"></i>'; + + switch (job.import_status) { + case 'finished': + jobItem.removeClass('active').addClass('success'); + statusField.html('<span><i class="fa fa-check"></i> done</span>'); + break; + case 'scheduled': + statusField.html(`${spinner} scheduled`); + break; + case 'started': + statusField.html(`${spinner} started`); + break; + default: + statusField.html(job.import_status); + break; + } + }); + }); + } + + setAutoUpdate() { + setInterval(this.autoUpdate.bind(this), 4000); } } // eslint-disable-next-line consistent-return -export default function initImporterStatus() { +function initImporterStatus() { const importerStatus = document.querySelector('.js-importer-status'); if (importerStatus) { @@ -79,3 +101,8 @@ export default function initImporterStatus() { return new ImporterStatus(data.jobsImportPath, data.importPath); } } + +export { + initImporterStatus as default, + ImporterStatus, +}; diff --git a/app/assets/javascripts/integrations/index.js b/app/assets/javascripts/integrations/index.js deleted file mode 100644 index 10fe6bac0e8..00000000000 --- a/app/assets/javascripts/integrations/index.js +++ /dev/null @@ -1,7 +0,0 @@ -/* eslint-disable no-new */ -import IntegrationSettingsForm from './integration_settings_form'; - -$(() => { - const integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form'); - integrationSettingsForm.init(); -}); diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js index ff65ea99e9a..333bbd9e0ba 100644 --- a/app/assets/javascripts/issue.js +++ b/app/assets/javascripts/issue.js @@ -1,5 +1,4 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, no-underscore-dangle, one-var-declaration-per-line, object-shorthand, no-unused-vars, no-new, comma-dangle, consistent-return, quotes, dot-notation, quote-props, prefer-arrow-callback, max-len */ -import 'vendor/jquery.waitforimages'; import axios from './lib/utils/axios_utils'; import { addDelimiter } from './lib/utils/text_utility'; import flash from './flash'; @@ -25,6 +24,51 @@ export default class Issue { if (Issue.createMrDropdownWrap) { this.createMergeRequestDropdown = new CreateMergeRequestDropdown(Issue.createMrDropdownWrap); } + + // Listen to state changes in the Vue app + document.addEventListener('issuable_vue_app:change', (event) => { + this.updateTopState(event.detail.isClosed, event.detail.data); + }); + } + + /** + * This method updates the top area of the issue. + * + * Once the issue state changes, either through a click on the top area (jquery) + * or a click on the bottom area (Vue) we need to update the top area. + * + * @param {Boolean} isClosed + * @param {Array} data + * @param {String} issueFailMessage + */ + updateTopState(isClosed, data, issueFailMessage = 'Unable to update this issue at this time.') { + if ('id' in data) { + const isClosedBadge = $('div.status-box-issue-closed'); + const isOpenBadge = $('div.status-box-open'); + const projectIssuesCounter = $('.issue_counter'); + + isClosedBadge.toggleClass('hidden', !isClosed); + isOpenBadge.toggleClass('hidden', isClosed); + + $(document).trigger('issuable:change', isClosed); + this.toggleCloseReopenButton(isClosed); + + let numProjectIssues = Number(projectIssuesCounter.first().text().trim().replace(/[^\d]/, '')); + numProjectIssues = isClosed ? numProjectIssues - 1 : numProjectIssues + 1; + projectIssuesCounter.text(addDelimiter(numProjectIssues)); + + if (this.createMergeRequestDropdown) { + if (isClosed) { + this.createMergeRequestDropdown.unavailable(); + this.createMergeRequestDropdown.disable(); + } else { + // We should check in case a branch was created in another tab + this.createMergeRequestDropdown.checkAbilityToCreateBranch(); + } + } + } else { + flash(issueFailMessage); + } } initIssueBtnEventListeners() { @@ -45,34 +89,8 @@ export default class Issue { url = $button.attr('href'); return axios.put(url) .then(({ data }) => { - const isClosedBadge = $('div.status-box-issue-closed'); - const isOpenBadge = $('div.status-box-open'); - const projectIssuesCounter = $('.issue_counter'); - - if ('id' in data) { - const isClosed = $button.hasClass('btn-close'); - isClosedBadge.toggleClass('hidden', !isClosed); - isOpenBadge.toggleClass('hidden', isClosed); - - $(document).trigger('issuable:change', isClosed); - this.toggleCloseReopenButton(isClosed); - - let numProjectIssues = Number(projectIssuesCounter.first().text().trim().replace(/[^\d]/, '')); - numProjectIssues = isClosed ? numProjectIssues - 1 : numProjectIssues + 1; - projectIssuesCounter.text(addDelimiter(numProjectIssues)); - - if (this.createMergeRequestDropdown) { - if (isClosed) { - this.createMergeRequestDropdown.unavailable(); - this.createMergeRequestDropdown.disable(); - } else { - // We should check in case a branch was created in another tab - this.createMergeRequestDropdown.checkAbilityToCreateBranch(); - } - } - } else { - flash(issueFailMessage); - } + const isClosed = $button.hasClass('btn-close'); + this.updateTopState(isClosed, data); }) .catch(() => flash(issueFailMessage)) .then(() => { diff --git a/app/assets/javascripts/job.js b/app/assets/javascripts/job.js index d0b7ea75082..f39ae764d3c 100644 --- a/app/assets/javascripts/job.js +++ b/app/assets/javascripts/job.js @@ -217,7 +217,7 @@ export default class Job { } this.isLogComplete = log.complete; - if (!log.complete) { + if (log.complete === false) { this.timeout = setTimeout(() => { this.getBuildTrace(); }, 4000); diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index 5811d059e0b..7d2cf4b634f 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -1,5 +1,6 @@ import axios from './axios_utils'; import { getLocationHash } from './url_utility'; +import { convertToCamelCase } from './text_utility'; export const getPagePath = (index = 0) => $('body').attr('data-page').split(':')[index]; @@ -395,6 +396,26 @@ export const spriteIcon = (icon, className = '') => { return `<svg ${classAttribute}><use xlink:href="${gon.sprite_icons}#${icon}" /></svg>`; }; +/** + * This method takes in object with snake_case property names + * and returns new object with camelCase property names + * + * Reasoning for this method is to ensure consistent property + * naming conventions across JS code. + */ +export const convertObjectPropsToCamelCase = (obj = {}) => { + if (obj === null) { + return {}; + } + + return Object.keys(obj).reduce((acc, prop) => { + const result = acc; + + result[convertToCamelCase(prop)] = obj[prop]; + return acc; + }, {}); +}; + export const imagePath = imgUrl => `${gon.asset_host || ''}${gon.relative_url_root || ''}/assets/${imgUrl}`; window.gl = window.gl || {}; diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js index 1fa6715180e..d6cccbef42b 100644 --- a/app/assets/javascripts/lib/utils/datetime_utility.js +++ b/app/assets/javascripts/lib/utils/datetime_utility.js @@ -10,6 +10,20 @@ window.timeago = timeago; window.dateFormat = dateFormat; /** + * Returns i18n month names array. + * If `abbreviated` is provided, returns abbreviated + * name. + * + * @param {Boolean} abbreviated + */ +const getMonthNames = (abbreviated) => { + if (abbreviated) { + return [s__('Jan'), s__('Feb'), s__('Mar'), s__('Apr'), s__('May'), s__('Jun'), s__('Jul'), s__('Aug'), s__('Sep'), s__('Oct'), s__('Nov'), s__('Dec')]; + } + return [s__('January'), s__('February'), s__('March'), s__('April'), s__('May'), s__('June'), s__('July'), s__('August'), s__('September'), s__('October'), s__('November'), s__('December')]; +}; + +/** * Given a date object returns the day of the week in English * @param {date} date * @returns {String} @@ -143,7 +157,6 @@ export const getDayDifference = (a, b) => { * @param {Number} seconds * @return {String} */ -// eslint-disable-next-line import/prefer-default-export export function timeIntervalInWords(intervalInSeconds) { const secondsInteger = parseInt(intervalInSeconds, 10); const minutes = Math.floor(secondsInteger / 60); @@ -158,7 +171,7 @@ export function timeIntervalInWords(intervalInSeconds) { return text; } -export function dateInWords(date, abbreviated = false) { +export function dateInWords(date, abbreviated = false, hideYear = false) { if (!date) return date; const month = date.getMonth(); @@ -169,9 +182,115 @@ export function dateInWords(date, abbreviated = false) { const monthName = abbreviated ? monthNamesAbbr[month] : monthNames[month]; + if (hideYear) { + return `${monthName} ${date.getDate()}`; + } + return `${monthName} ${date.getDate()}, ${year}`; } +/** + * Returns month name based on provided date. + * + * @param {Date} date + * @param {Boolean} abbreviated + */ +export const monthInWords = (date, abbreviated = false) => { + if (!date) { + return ''; + } + + return getMonthNames(abbreviated)[date.getMonth()]; +}; + +/** + * Returns number of days in a month for provided date. + * courtesy: https://stacko(verflow.com/a/1185804/414749 + * + * @param {Date} date + */ +export const totalDaysInMonth = (date) => { + if (!date) { + return 0; + } + return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); +}; + +/** + * Returns list of Dates referring to Sundays of the month + * based on provided date + * + * @param {Date} date + */ +export const getSundays = (date) => { + if (!date) { + return []; + } + + const daysToSunday = ['Saturday', 'Friday', 'Thursday', 'Wednesday', 'Tuesday', 'Monday', 'Sunday']; + + const month = date.getMonth(); + const year = date.getFullYear(); + const sundays = []; + const dateOfMonth = new Date(year, month, 1); + + while (dateOfMonth.getMonth() === month) { + const dayName = getDayName(dateOfMonth); + if (dayName === 'Sunday') { + sundays.push(new Date(dateOfMonth.getTime())); + } + + const daysUntilNextSunday = daysToSunday.indexOf(dayName) + 1; + dateOfMonth.setDate(dateOfMonth.getDate() + daysUntilNextSunday); + } + + return sundays; +}; + +/** + * Returns list of Dates representing a timeframe of Months from month of provided date (inclusive) + * up to provided length + * + * For eg; + * If current month is January 2018 and `length` provided is `6` + * Then this method will return list of Date objects as follows; + * + * [ October 2017, November 2017, December 2017, January 2018, February 2018, March 2018 ] + * + * If current month is March 2018 and `length` provided is `3` + * Then this method will return list of Date objects as follows; + * + * [ February 2018, March 2018, April 2018 ] + * + * @param {Number} length + * @param {Date} date + */ +export const getTimeframeWindow = (length, date) => { + if (!length) { + return []; + } + + const currentDate = date instanceof Date ? date : new Date(); + const currentMonthIndex = Math.floor(length / 2); + const timeframe = []; + + // Move date object backward to the first month of timeframe + currentDate.setDate(1); + currentDate.setMonth(currentDate.getMonth() - currentMonthIndex); + + // Iterate and update date for the size of length + // and push date reference to timeframe list + for (let i = 0; i < length; i += 1) { + timeframe.push(new Date(currentDate.getTime())); + currentDate.setMonth(currentDate.getMonth() + 1); + } + + // Change date of last timeframe item to last date of the month + timeframe[length - 1].setDate(totalDaysInMonth(timeframe[length - 1])); + + return timeframe; +}; + window.gl = window.gl || {}; window.gl.utils = { ...(window.gl.utils || {}), diff --git a/app/assets/javascripts/lib/utils/http_status.js b/app/assets/javascripts/lib/utils/http_status.js index 625e53ee9de..bb151929431 100644 --- a/app/assets/javascripts/lib/utils/http_status.js +++ b/app/assets/javascripts/lib/utils/http_status.js @@ -6,4 +6,6 @@ export default { ABORTED: 0, NO_CONTENT: 204, OK: 200, + MULTIPLE_CHOICES: 300, + BAD_REQUEST: 400, }; diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js index 62d80c4a649..94d03621bff 100644 --- a/app/assets/javascripts/lib/utils/text_utility.js +++ b/app/assets/javascripts/lib/utils/text_utility.js @@ -73,3 +73,10 @@ export function capitalizeFirstCharacter(text) { * @returns {String} */ export const stripHtml = (string, replace = '') => string.replace(/<[^>]*>/g, replace); + +/** + * Converts snake_case string to camelCase + * + * @param {*} string + */ +export const convertToCamelCase = string => string.replace(/(_\w)/g, s => s[1].toUpperCase()); diff --git a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js index 93f8f6ee926..2cb238529aa 100644 --- a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js +++ b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js @@ -2,7 +2,9 @@ /* global ace */ import Vue from 'vue'; -import Flash from '../../flash'; +import axios from '~/lib/utils/axios_utils'; +import flash from '~/flash'; +import { __ } from '~/locale'; ((global) => { global.mergeConflicts = global.mergeConflicts || {}; @@ -49,27 +51,26 @@ import Flash from '../../flash'; loadEditor() { this.loading = true; - $.get(this.file.content_path) - .done((file) => { + axios.get(this.file.content_path) + .then(({ data }) => { const content = this.$el.querySelector('pre'); - const fileContent = document.createTextNode(file.content); + const fileContent = document.createTextNode(data.content); content.textContent = fileContent.textContent; - this.originalContent = file.content; + this.originalContent = data.content; this.fileLoaded = true; this.editor = ace.edit(content); this.editor.$blockScrolling = Infinity; // Turn off annoying warning - this.editor.getSession().setMode(`ace/mode/${file.blob_ace_mode}`); + this.editor.getSession().setMode(`ace/mode/${data.blob_ace_mode}`); this.editor.on('change', () => { this.saveDiffResolution(); }); this.saveDiffResolution(); + this.loading = false; }) - .fail(() => { - new Flash('Failed to load the file, please try again.'); - }) - .always(() => { + .catch(() => { + flash(__('An error occurred while loading the file')); this.loading = false; }); }, diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js index bedd50de1bb..a64093afcf4 100644 --- a/app/assets/javascripts/merge_request.js +++ b/app/assets/javascripts/merge_request.js @@ -1,6 +1,4 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, no-underscore-dangle, one-var, one-var-declaration-per-line, consistent-return, dot-notation, quote-props, comma-dangle, object-shorthand, max-len, prefer-arrow-callback */ - -import 'vendor/jquery.waitforimages'; import { __ } from '~/locale'; import TaskList from './task_list'; import MergeRequestTabs from './merge_request_tabs'; diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue index 5afae93724b..031badc7026 100644 --- a/app/assets/javascripts/monitoring/components/dashboard.vue +++ b/app/assets/javascripts/monitoring/components/dashboard.vue @@ -27,6 +27,7 @@ hasMetrics: convertPermissionToBoolean(metricsData.hasMetrics), documentationPath: metricsData.documentationPath, settingsPath: metricsData.settingsPath, + clustersPath: metricsData.clustersPath, tagsPath: metricsData.tagsPath, projectPath: metricsData.projectPath, metricsEndpoint: metricsData.additionalMetrics, @@ -132,6 +133,7 @@ :selected-state="state" :documentation-path="documentationPath" :settings-path="settingsPath" + :clusters-path="clustersPath" :empty-getting-started-svg-path="emptyGettingStartedSvgPath" :empty-loading-svg-path="emptyLoadingSvgPath" :empty-unable-to-connect-svg-path="emptyUnableToConnectSvgPath" diff --git a/app/assets/javascripts/monitoring/components/empty_state.vue b/app/assets/javascripts/monitoring/components/empty_state.vue index 56cd60c583b..9517b8ccb67 100644 --- a/app/assets/javascripts/monitoring/components/empty_state.vue +++ b/app/assets/javascripts/monitoring/components/empty_state.vue @@ -10,6 +10,11 @@ required: false, default: '', }, + clustersPath: { + type: String, + required: false, + default: '', + }, selectedState: { type: String, required: true, @@ -35,7 +40,10 @@ title: 'Get started with performance monitoring', description: `Stay updated about the performance and health of your environment by configuring Prometheus to monitor your deployments.`, - buttonText: 'Configure Prometheus', + buttonText: 'Install Prometheus on clusters', + buttonPath: this.clustersPath, + secondaryButtonText: 'Configure existing Prometheus', + secondaryButtonPath: this.settingsPath, }, loading: { svgUrl: this.emptyLoadingSvgPath, @@ -43,6 +51,7 @@ description: `Creating graphs uses the data from the Prometheus server. If this takes a long time, ensure that data is available.`, buttonText: 'View documentation', + buttonPath: this.documentationPath, }, noData: { svgUrl: this.emptyUnableToConnectSvgPath, @@ -50,12 +59,14 @@ description: `You are connected to the Prometheus server, but there is currently no data to display.`, buttonText: 'Configure Prometheus', + buttonPath: this.settingsPath, }, unableToConnect: { svgUrl: this.emptyUnableToConnectSvgPath, title: 'Unable to connect to Prometheus server', description: 'Ensure connectivity is available from the GitLab server to the ', buttonText: 'View documentation', + buttonPath: this.documentationPath, }, }, }; @@ -65,13 +76,6 @@ return this.states[this.selectedState]; }, - buttonPath() { - if (this.selectedState === 'gettingStarted') { - return this.settingsPath; - } - return this.documentationPath; - }, - showButtonDescription() { if (this.selectedState === 'unableToConnect') return true; return false; @@ -99,11 +103,21 @@ </p> <div class="state-button"> <a + v-if="currentState.buttonPath" class="btn btn-success" - :href="buttonPath" + :href="currentState.buttonPath" > {{ currentState.buttonText }} </a> </div> + <div class="state-button"> + <a + v-if="currentState.secondaryButtonPath" + class="btn" + :href="currentState.secondaryButtonPath" + > + {{ currentState.secondaryButtonText }} + </a> + </div> </div> </template> diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue index 3c8452ac808..df796050e0d 100644 --- a/app/assets/javascripts/notes/components/comment_form.vue +++ b/app/assets/javascripts/notes/components/comment_form.vue @@ -2,16 +2,18 @@ import { mapActions, mapGetters } from 'vuex'; import _ from 'underscore'; import Autosize from 'autosize'; + import { __ } from '~/locale'; import Flash from '../../flash'; import Autosave from '../../autosave'; import TaskList from '../../task_list'; import * as constants from '../constants'; import eventHub from '../event_hub'; import issueWarning from '../../vue_shared/components/issue/issue_warning.vue'; - import noteSignedOutWidget from './note_signed_out_widget.vue'; - import discussionLockedWidget from './discussion_locked_widget.vue'; import markdownField from '../../vue_shared/components/markdown/field.vue'; import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; + import loadingButton from '../../vue_shared/components/loading_button.vue'; + import noteSignedOutWidget from './note_signed_out_widget.vue'; + import discussionLockedWidget from './discussion_locked_widget.vue'; import issuableStateMixin from '../mixins/issuable_state'; export default { @@ -22,6 +24,7 @@ discussionLockedWidget, markdownField, userAvatarLink, + loadingButton, }, mixins: [ issuableStateMixin, @@ -30,9 +33,6 @@ return { note: '', noteType: constants.COMMENT, - // Can't use mapGetters, - // this needs to be in the data object because it belongs to the state - issueState: this.$store.getters.getNoteableData.state, isSubmitting: false, isSubmitButtonDisabled: true, }; @@ -43,6 +43,7 @@ 'getUserData', 'getNoteableData', 'getNotesData', + 'issueState', ]), isLoggedIn() { return this.getUserData.id; @@ -105,7 +106,7 @@ mounted() { // jQuery is needed here because it is a custom event being dispatched with jQuery. $(document).on('issuable:change', (e, isClosed) => { - this.issueState = isClosed ? constants.CLOSED : constants.REOPENED; + this.toggleIssueLocalState(isClosed ? constants.CLOSED : constants.REOPENED); }); this.initAutoSave(); @@ -117,6 +118,9 @@ 'stopPolling', 'restartPolling', 'removePlaceholderNotes', + 'closeIssue', + 'reopenIssue', + 'toggleIssueLocalState', ]), setIsSubmitButtonDisabled(note, isSubmitting) { if (!_.isEmpty(note) && !isSubmitting) { @@ -126,6 +130,8 @@ } }, handleSave(withIssueAction) { + this.isSubmitting = true; + if (this.note.length) { const noteData = { endpoint: this.endpoint, @@ -142,7 +148,6 @@ if (this.noteType === constants.DISCUSSION) { noteData.data.note.type = constants.DISCUSSION_NOTE; } - this.isSubmitting = true; this.note = ''; // Empty textarea while being requested. Repopulate in catch this.resizeTextarea(); this.stopPolling(); @@ -184,13 +189,25 @@ Please check your network connection and try again.`; this.toggleIssueState(); } }, + enableButton() { + this.isSubmitting = false; + }, toggleIssueState() { - this.issueState = this.isIssueOpen ? constants.CLOSED : constants.REOPENED; - - // This is out of scope for the Notes Vue component. - // It was the shortest path to update the issue state and relevant places. - const btnClass = this.isIssueOpen ? 'btn-reopen' : 'btn-close'; - $(`.js-btn-issue-action.${btnClass}:visible`).trigger('click'); + if (this.isIssueOpen) { + this.closeIssue() + .then(() => this.enableButton()) + .catch(() => { + this.enableButton(); + Flash(__('Something went wrong while closing the issue. Please try again later')); + }); + } else { + this.reopenIssue() + .then(() => this.enableButton()) + .catch(() => { + this.enableButton(); + Flash(__('Something went wrong while reopening the issue. Please try again later')); + }); + } }, discard(shouldClear = true) { // `blur` is needed to clear slash commands autocomplete cache if event fired. @@ -367,15 +384,19 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown" </li> </ul> </div> - <button - type="button" - @click="handleSave(true)" + + <loading-button v-if="canUpdateIssue" - :class="actionButtonClassNames" + :loading="isSubmitting" + @click="handleSave(true)" + :container-class="[ + actionButtonClassNames, + 'btn btn-comment btn-comment-and-close js-action-button' + ]" :disabled="isSubmitting" - class="btn btn-comment btn-comment-and-close js-action-button"> - {{ issueActionButtonTitle }} - </button> + :label="issueActionButtonTitle" + /> + <button type="button" v-if="note.length" diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue index 30e7ccc8229..045077de383 100644 --- a/app/assets/javascripts/notes/components/noteable_note.vue +++ b/app/assets/javascripts/notes/components/noteable_note.vue @@ -102,6 +102,7 @@ .then(() => { this.isEditing = false; this.isRequesting = false; + this.oldContent = null; $(this.$refs.noteBody.$el).renderGFM(); this.$refs.noteBody.resetAutoSave(); callback(); diff --git a/app/assets/javascripts/notes/index.js b/app/assets/javascripts/notes/index.js index d250dd8d25b..48e7cfddb63 100644 --- a/app/assets/javascripts/notes/index.js +++ b/app/assets/javascripts/notes/index.js @@ -28,6 +28,8 @@ document.addEventListener('DOMContentLoaded', () => new Vue({ notesPath: notesDataset.notesPath, markdownDocsPath: notesDataset.markdownDocsPath, quickActionsDocsPath: notesDataset.quickActionsDocsPath, + closeIssuePath: notesDataset.closeIssuePath, + reopenIssuePath: notesDataset.reopenIssuePath, }, }; }, diff --git a/app/assets/javascripts/notes/services/notes_service.js b/app/assets/javascripts/notes/services/notes_service.js index b51b0cb2013..b8e7ffc8c46 100644 --- a/app/assets/javascripts/notes/services/notes_service.js +++ b/app/assets/javascripts/notes/services/notes_service.js @@ -32,4 +32,7 @@ export default { toggleAward(endpoint, data) { return Vue.http.post(endpoint, data, { emulateJSON: true }); }, + toggleIssueState(endpoint, data) { + return Vue.http.put(endpoint, data); + }, }; diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js index 085b18642ba..4c846d69b86 100644 --- a/app/assets/javascripts/notes/stores/actions.js +++ b/app/assets/javascripts/notes/stores/actions.js @@ -61,6 +61,39 @@ export const createNewNote = ({ commit }, { endpoint, data }) => service export const removePlaceholderNotes = ({ commit }) => commit(types.REMOVE_PLACEHOLDER_NOTES); +export const closeIssue = ({ commit, dispatch, state }) => service + .toggleIssueState(state.notesData.closeIssuePath) + .then(res => res.json()) + .then((data) => { + commit(types.CLOSE_ISSUE); + dispatch('emitStateChangedEvent', data); + }); + +export const reopenIssue = ({ commit, dispatch, state }) => service + .toggleIssueState(state.notesData.reopenIssuePath) + .then(res => res.json()) + .then((data) => { + commit(types.REOPEN_ISSUE); + dispatch('emitStateChangedEvent', data); + }); + +export const emitStateChangedEvent = ({ commit, getters }, data) => { + const event = new CustomEvent('issuable_vue_app:change', { detail: { + data, + isClosed: getters.issueState === constants.CLOSED, + } }); + + document.dispatchEvent(event); +}; + +export const toggleIssueLocalState = ({ commit }, newState) => { + if (newState === constants.CLOSED) { + commit(types.CLOSE_ISSUE); + } else if (newState === constants.REOPENED) { + commit(types.REOPEN_ISSUE); + } +}; + export const saveNote = ({ commit, dispatch }, noteData) => { const { note } = noteData.data.note; let placeholderText = note; diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js index e18b277119e..82024104d73 100644 --- a/app/assets/javascripts/notes/stores/getters.js +++ b/app/assets/javascripts/notes/stores/getters.js @@ -8,6 +8,7 @@ export const getNotesDataByProp = state => prop => state.notesData[prop]; export const getNoteableData = state => state.noteableData; export const getNoteableDataByProp = state => prop => state.noteableData[prop]; +export const issueState = state => state.noteableData.state; export const getUserData = state => state.userData || {}; export const getUserDataByProp = state => prop => state.userData && state.userData[prop]; diff --git a/app/assets/javascripts/notes/stores/mutation_types.js b/app/assets/javascripts/notes/stores/mutation_types.js index d520c197407..6d7c3bbae0f 100644 --- a/app/assets/javascripts/notes/stores/mutation_types.js +++ b/app/assets/javascripts/notes/stores/mutation_types.js @@ -12,3 +12,7 @@ export const SHOW_PLACEHOLDER_NOTE = 'SHOW_PLACEHOLDER_NOTE'; export const TOGGLE_AWARD = 'TOGGLE_AWARD'; export const TOGGLE_DISCUSSION = 'TOGGLE_DISCUSSION'; export const UPDATE_NOTE = 'UPDATE_NOTE'; + +// Issue +export const CLOSE_ISSUE = 'CLOSE_ISSUE'; +export const REOPEN_ISSUE = 'REOPEN_ISSUE'; diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js index 20f81a430c2..b3f66578c9a 100644 --- a/app/assets/javascripts/notes/stores/mutations.js +++ b/app/assets/javascripts/notes/stores/mutations.js @@ -152,4 +152,12 @@ export default { noteObj.notes.splice(noteObj.notes.indexOf(comment), 1, note); } }, + + [types.CLOSE_ISSUE](state) { + Object.assign(state.noteableData, { state: constants.CLOSED }); + }, + + [types.REOPEN_ISSUE](state) { + Object.assign(state.noteableData, { state: constants.REOPENED }); + }, }; diff --git a/app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue b/app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue index 555725cbe12..ba1d8e4d8db 100644 --- a/app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue +++ b/app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue @@ -1,13 +1,13 @@ <script> import axios from '~/lib/utils/axios_utils'; - import Flash from '~/flash'; - import modal from '~/vue_shared/components/modal.vue'; - import { s__ } from '~/locale'; + import createFlash from '~/flash'; + import GlModal from '~/vue_shared/components/gl_modal.vue'; import { redirectTo } from '~/lib/utils/url_utility'; + import { s__ } from '~/locale'; export default { components: { - modal, + GlModal, }, props: { url: { @@ -17,7 +17,7 @@ }, computed: { text() { - return s__('AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running.'); + return s__('AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running.'); }, }, methods: { @@ -28,7 +28,7 @@ redirectTo(response.request.responseURL); }) .catch((error) => { - Flash(s__('AdminArea|Stopping jobs failed')); + createFlash(s__('AdminArea|Stopping jobs failed')); throw error; }); }, @@ -37,11 +37,13 @@ </script> <template> - <modal + <gl-modal id="stop-jobs-modal" - :title="s__('AdminArea|Stop all jobs?')" - :text="text" - kind="danger" - :primary-button-label="s__('AdminArea|Stop jobs')" - @submit="onSubmit" /> + :header-title-text="s__('AdminArea|Stop all jobs?')" + footer-primary-button-variant="danger" + :footer-primary-button-text="s__('AdminArea|Stop jobs')" + @submit="onSubmit" + > + {{ text }} + </gl-modal> </template> diff --git a/app/assets/javascripts/pages/admin/jobs/index/index.js b/app/assets/javascripts/pages/admin/jobs/index/index.js index 0e004bd9174..5a4f8c6e745 100644 --- a/app/assets/javascripts/pages/admin/jobs/index/index.js +++ b/app/assets/javascripts/pages/admin/jobs/index/index.js @@ -1,29 +1,28 @@ import Vue from 'vue'; - import Translate from '~/vue_shared/translate'; - import stopJobsModal from './components/stop_jobs_modal.vue'; Vue.use(Translate); -export default () => { +document.addEventListener('DOMContentLoaded', () => { const stopJobsButton = document.getElementById('stop-jobs-button'); - - // eslint-disable-next-line no-new - new Vue({ - el: '#stop-jobs-modal', - components: { - stopJobsModal, - }, - mounted() { - stopJobsButton.classList.remove('disabled'); - }, - render(createElement) { - return createElement('stop-jobs-modal', { - props: { - url: stopJobsButton.dataset.url, - }, - }); - }, - }); -}; + if (stopJobsButton) { + // eslint-disable-next-line no-new + new Vue({ + el: '#stop-jobs-modal', + components: { + stopJobsModal, + }, + mounted() { + stopJobsButton.classList.remove('disabled'); + }, + render(createElement) { + return createElement('stop-jobs-modal', { + props: { + url: stopJobsButton.dataset.url, + }, + }); + }, + }); + } +}); diff --git a/app/assets/javascripts/pages/admin/projects/index/components/delete_project_modal.vue b/app/assets/javascripts/pages/admin/projects/index/components/delete_project_modal.vue new file mode 100644 index 00000000000..14315d5492e --- /dev/null +++ b/app/assets/javascripts/pages/admin/projects/index/components/delete_project_modal.vue @@ -0,0 +1,125 @@ +<script> + import _ from 'underscore'; + import modal from '~/vue_shared/components/modal.vue'; + import { s__, sprintf } from '~/locale'; + + export default { + components: { + modal, + }, + props: { + deleteProjectUrl: { + type: String, + required: false, + default: '', + }, + projectName: { + type: String, + required: false, + default: '', + }, + csrfToken: { + type: String, + required: false, + default: '', + }, + }, + data() { + return { + enteredProjectName: '', + }; + }, + computed: { + title() { + return sprintf(s__('AdminProjects|Delete Project %{projectName}?'), + { + projectName: `'${_.escape(this.projectName)}'`, + }, + false, + ); + }, + text() { + return sprintf(s__(`AdminProjects| + You’re about to permanently delete the project %{projectName}, its repository, + and all related resources including issues, merge requests, etc.. Once you confirm and press + %{strong_start}Delete project%{strong_end}, it cannot be undone or recovered.`), + { + projectName: `<strong>${_.escape(this.projectName)}</strong>`, + strong_start: '<strong>', + strong_end: '</strong>', + }, + false, + ); + }, + confirmationTextLabel() { + return sprintf(s__('AdminUsers|To confirm, type %{projectName}'), + { + projectName: `<code>${_.escape(this.projectName)}</code>`, + }, + false, + ); + }, + primaryButtonLabel() { + return s__('AdminProjects|Delete project'); + }, + canSubmit() { + return this.enteredProjectName === this.projectName; + }, + }, + methods: { + onCancel() { + this.enteredProjectName = ''; + }, + onSubmit() { + this.$refs.form.submit(); + this.enteredProjectName = ''; + }, + }, + }; +</script> + +<template> + <modal + id="delete-project-modal" + :title="title" + :text="text" + kind="danger" + :primary-button-label="primaryButtonLabel" + :submit-disabled="!canSubmit" + @submit="onSubmit" + @cancel="onCancel" + > + <template + slot="body" + slot-scope="props" + > + <p v-html="props.text"></p> + <p v-html="confirmationTextLabel"></p> + <form + ref="form" + :action="deleteProjectUrl" + method="post" + > + <input + ref="method" + type="hidden" + name="_method" + value="delete" + /> + <input + type="hidden" + name="authenticity_token" + :value="csrfToken" + /> + <input + name="projectName" + class="form-control" + type="text" + v-model="enteredProjectName" + aria-labelledby="input-label" + autocomplete="off" + /> + </form> + </template> + </modal> +</template> diff --git a/app/assets/javascripts/pages/admin/projects/index/index.js b/app/assets/javascripts/pages/admin/projects/index/index.js new file mode 100644 index 00000000000..3c597a1093e --- /dev/null +++ b/app/assets/javascripts/pages/admin/projects/index/index.js @@ -0,0 +1,37 @@ +import Vue from 'vue'; + +import Translate from '~/vue_shared/translate'; +import csrf from '~/lib/utils/csrf'; + +import deleteProjectModal from './components/delete_project_modal.vue'; + +document.addEventListener('DOMContentLoaded', () => { + Vue.use(Translate); + + const deleteProjectModalEl = document.getElementById('delete-project-modal'); + + const deleteModal = new Vue({ + el: deleteProjectModalEl, + data: { + deleteProjectUrl: '', + projectName: '', + }, + render(createElement) { + return createElement(deleteProjectModal, { + props: { + deleteProjectUrl: this.deleteProjectUrl, + projectName: this.projectName, + csrfToken: csrf.token, + }, + }); + }, + }); + + $(document).on('shown.bs.modal', (event) => { + if (event.relatedTarget.classList.contains('delete-project-button')) { + const buttonProps = event.relatedTarget.dataset; + deleteModal.deleteProjectUrl = buttonProps.deleteProjectUrl; + deleteModal.projectName = buttonProps.projectName; + } + }); +}); diff --git a/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue b/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue new file mode 100644 index 00000000000..7b5e333011e --- /dev/null +++ b/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue @@ -0,0 +1,174 @@ +<script> + import _ from 'underscore'; + import modal from '~/vue_shared/components/modal.vue'; + import { s__, sprintf } from '~/locale'; + + export default { + components: { + modal, + }, + props: { + deleteUserUrl: { + type: String, + required: false, + default: '', + }, + blockUserUrl: { + type: String, + required: false, + default: '', + }, + deleteContributions: { + type: Boolean, + required: false, + default: false, + }, + username: { + type: String, + required: false, + default: '', + }, + csrfToken: { + type: String, + required: false, + default: '', + }, + }, + data() { + return { + enteredUsername: '', + }; + }, + computed: { + title() { + const keepContributionsTitle = s__('AdminUsers|Delete User %{username}?'); + const deleteContributionsTitle = s__('AdminUsers|Delete User %{username} and contributions?'); + + return sprintf( + this.deleteContributions ? deleteContributionsTitle : keepContributionsTitle, { + username: `'${_.escape(this.username)}'`, + }, false); + }, + text() { + const keepContributionsText = s__(`AdminArea| + You are about to permanently delete the user %{username}. + This will delete all of the issues, merge requests, and groups linked to them. + To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. + Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered.`); + + const deleteContributionsText = s__(`AdminArea| + You are about to permanently delete the user %{username}. + Issues, merge requests, and groups linked to them will be transferred to a system-wide "Ghost-user". + To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. + Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered.`); + + return sprintf(this.deleteContributions ? deleteContributionsText : keepContributionsText, + { + username: `<strong>${_.escape(this.username)}</strong>`, + strong_start: '<strong>', + strong_end: '</strong>', + }, + false, + ); + }, + confirmationTextLabel() { + return sprintf(s__('AdminUsers|To confirm, type %{username}'), + { + username: `<code>${_.escape(this.username)}</code>`, + }, + false, + ); + }, + primaryButtonLabel() { + const keepContributionsLabel = s__('AdminUsers|Delete user'); + const deleteContributionsLabel = s__('AdminUsers|Delete user and contributions'); + + return this.deleteContributions ? deleteContributionsLabel : keepContributionsLabel; + }, + secondaryButtonLabel() { + return s__('AdminUsers|Block user'); + }, + canSubmit() { + return this.enteredUsername === this.username; + }, + }, + methods: { + onCancel() { + this.enteredUsername = ''; + }, + onSecondaryAction() { + const form = this.$refs.form; + + form.action = this.blockUserUrl; + this.$refs.method.value = 'put'; + + form.submit(); + }, + onSubmit() { + this.$refs.form.submit(); + this.enteredUsername = ''; + }, + }, + }; +</script> + +<template> + <modal + id="delete-user-modal" + :title="title" + :text="text" + kind="danger" + :primary-button-label="primaryButtonLabel" + :secondary-button-label="secondaryButtonLabel" + :submit-disabled="!canSubmit" + @submit="onSubmit" + @cancel="onCancel" + > + <template + slot="body" + slot-scope="props" + > + <p v-html="props.text"></p> + <p v-html="confirmationTextLabel"></p> + <form + ref="form" + :action="deleteUserUrl" + method="post" + > + <input + ref="method" + type="hidden" + name="_method" + value="delete" + /> + <input + type="hidden" + name="authenticity_token" + :value="csrfToken" + /> + <input + type="text" + name="username" + class="form-control" + v-model="enteredUsername" + aria-labelledby="input-label" + autocomplete="off" + /> + </form> + </template> + <template + slot="secondary-button" + slot-scope="props" + > + <button + type="button" + class="btn js-secondary-button btn-warning" + :disabled="!canSubmit" + @click="onSecondaryAction" + data-dismiss="modal" + > + {{ secondaryButtonLabel }} + </button> + </template> + </modal> +</template> diff --git a/app/assets/javascripts/pages/admin/users/index.js b/app/assets/javascripts/pages/admin/users/index.js new file mode 100644 index 00000000000..4f5d6b55031 --- /dev/null +++ b/app/assets/javascripts/pages/admin/users/index.js @@ -0,0 +1,43 @@ +import Vue from 'vue'; + +import Translate from '~/vue_shared/translate'; +import csrf from '~/lib/utils/csrf'; + +import deleteUserModal from './components/delete_user_modal.vue'; + +document.addEventListener('DOMContentLoaded', () => { + Vue.use(Translate); + + const deleteUserModalEl = document.getElementById('delete-user-modal'); + + const deleteModal = new Vue({ + el: deleteUserModalEl, + data: { + deleteUserUrl: '', + blockUserUrl: '', + deleteContributions: '', + username: '', + }, + render(createElement) { + return createElement(deleteUserModal, { + props: { + deleteUserUrl: this.deleteUserUrl, + blockUserUrl: this.blockUserUrl, + deleteContributions: this.deleteContributions, + username: this.username, + csrfToken: csrf.token, + }, + }); + }, + }); + + $(document).on('shown.bs.modal', (event) => { + if (event.relatedTarget.classList.contains('delete-user-button')) { + const buttonProps = event.relatedTarget.dataset; + deleteModal.deleteUserUrl = buttonProps.deleteUserUrl; + deleteModal.blockUserUrl = buttonProps.blockUserUrl; + deleteModal.deleteContributions = event.relatedTarget.hasAttribute('data-delete-contributions'); + deleteModal.username = buttonProps.username; + } + }); +}); diff --git a/app/assets/javascripts/pages/ci/lints/ci_lint_editor.js b/app/assets/javascripts/pages/ci/lints/ci_lint_editor.js index b9469e5b7cb..9ab73be80a0 100644 --- a/app/assets/javascripts/pages/ci/lints/ci_lint_editor.js +++ b/app/assets/javascripts/pages/ci/lints/ci_lint_editor.js @@ -2,11 +2,18 @@ export default class CILintEditor { constructor() { this.editor = window.ace.edit('ci-editor'); this.textarea = document.querySelector('#content'); + this.clearYml = document.querySelector('.clear-yml'); this.editor.getSession().setMode('ace/mode/yaml'); this.editor.on('input', () => { const content = this.editor.getSession().getValue(); this.textarea.value = content; }); + + this.clearYml.addEventListener('click', this.clear.bind(this)); + } + + clear() { + this.editor.setValue(''); } } diff --git a/app/assets/javascripts/pages/dashboard/issues/index.js b/app/assets/javascripts/pages/dashboard/issues/index.js index b7353669e65..c4901dd1cb6 100644 --- a/app/assets/javascripts/pages/dashboard/issues/index.js +++ b/app/assets/javascripts/pages/dashboard/issues/index.js @@ -1,7 +1,7 @@ import projectSelect from '~/project_select'; import initLegacyFilters from '~/init_legacy_filters'; -export default () => { +document.addEventListener('DOMContentLoaded', () => { projectSelect(); initLegacyFilters(); -}; +}); diff --git a/app/assets/javascripts/pages/dashboard/merge_requests/index.js b/app/assets/javascripts/pages/dashboard/merge_requests/index.js index b7353669e65..c4901dd1cb6 100644 --- a/app/assets/javascripts/pages/dashboard/merge_requests/index.js +++ b/app/assets/javascripts/pages/dashboard/merge_requests/index.js @@ -1,7 +1,7 @@ import projectSelect from '~/project_select'; import initLegacyFilters from '~/init_legacy_filters'; -export default () => { +document.addEventListener('DOMContentLoaded', () => { projectSelect(); initLegacyFilters(); -}; +}); diff --git a/app/assets/javascripts/pages/dashboard/milestones/show/index.js b/app/assets/javascripts/pages/dashboard/milestones/show/index.js index 2e7a08a369c..06195d73c0a 100644 --- a/app/assets/javascripts/pages/dashboard/milestones/show/index.js +++ b/app/assets/javascripts/pages/dashboard/milestones/show/index.js @@ -1,7 +1,7 @@ import Milestone from '~/milestone'; import Sidebar from '~/right_sidebar'; -export default () => { +document.addEventListener('DOMContentLoaded', () => { new Milestone(); // eslint-disable-line no-new new Sidebar(); // eslint-disable-line no-new -}; +}); diff --git a/app/assets/javascripts/pages/dashboard/projects/index.js b/app/assets/javascripts/pages/dashboard/projects/index.js index c88cbf1a6ba..0c585e162cb 100644 --- a/app/assets/javascripts/pages/dashboard/projects/index.js +++ b/app/assets/javascripts/pages/dashboard/projects/index.js @@ -1,3 +1,3 @@ import ProjectsList from '~/projects_list'; -export default () => new ProjectsList(); +document.addEventListener('DOMContentLoaded', () => new ProjectsList()); diff --git a/app/assets/javascripts/pages/dashboard/todos/index/index.js b/app/assets/javascripts/pages/dashboard/todos/index/index.js index 77c23685943..9d2c2f2994f 100644 --- a/app/assets/javascripts/pages/dashboard/todos/index/index.js +++ b/app/assets/javascripts/pages/dashboard/todos/index/index.js @@ -1,3 +1,3 @@ import Todos from './todos'; -export default () => new Todos(); +document.addEventListener('DOMContentLoaded', () => new Todos()); diff --git a/app/assets/javascripts/pages/explore/groups/index.js b/app/assets/javascripts/pages/explore/groups/index.js index e59c38b8bc4..3c7edbdd7c7 100644 --- a/app/assets/javascripts/pages/explore/groups/index.js +++ b/app/assets/javascripts/pages/explore/groups/index.js @@ -2,7 +2,7 @@ import GroupsList from '~/groups_list'; import Landing from '~/landing'; import initGroupsList from '../../../groups'; -export default function () { +document.addEventListener('DOMContentLoaded', () => { new GroupsList(); // eslint-disable-line no-new initGroupsList(); const landingElement = document.querySelector('.js-explore-groups-landing'); @@ -13,4 +13,4 @@ export default function () { 'explore_groups_landing_dismissed', ); exploreGroupsLanding.toggle(); -} +}); diff --git a/app/assets/javascripts/pages/explore/projects/index.js b/app/assets/javascripts/pages/explore/projects/index.js index c88cbf1a6ba..0c585e162cb 100644 --- a/app/assets/javascripts/pages/explore/projects/index.js +++ b/app/assets/javascripts/pages/explore/projects/index.js @@ -1,3 +1,3 @@ import ProjectsList from '~/projects_list'; -export default () => new ProjectsList(); +document.addEventListener('DOMContentLoaded', () => new ProjectsList()); diff --git a/app/assets/javascripts/pages/groups/issues/index.js b/app/assets/javascripts/pages/groups/issues/index.js index 78db543a64d..d149b307e7f 100644 --- a/app/assets/javascripts/pages/groups/issues/index.js +++ b/app/assets/javascripts/pages/groups/issues/index.js @@ -2,7 +2,9 @@ import projectSelect from '~/project_select'; import initFilteredSearch from '~/pages/search/init_filtered_search'; import { FILTERED_SEARCH } from '~/pages/constants'; -export default () => { - initFilteredSearch(FILTERED_SEARCH.ISSUES); +document.addEventListener('DOMContentLoaded', () => { + initFilteredSearch({ + page: FILTERED_SEARCH.ISSUES, + }); projectSelect(); -}; +}); diff --git a/app/assets/javascripts/pages/groups/merge_requests/index.js b/app/assets/javascripts/pages/groups/merge_requests/index.js index 9b3af4537e7..a5cc1f34b63 100644 --- a/app/assets/javascripts/pages/groups/merge_requests/index.js +++ b/app/assets/javascripts/pages/groups/merge_requests/index.js @@ -2,7 +2,9 @@ import projectSelect from '~/project_select'; import initFilteredSearch from '~/pages/search/init_filtered_search'; import { FILTERED_SEARCH } from '~/pages/constants'; -export default () => { - initFilteredSearch(FILTERED_SEARCH.MERGE_REQUESTS); +document.addEventListener('DOMContentLoaded', () => { + initFilteredSearch({ + page: FILTERED_SEARCH.MERGE_REQUESTS, + }); projectSelect(); -}; +}); diff --git a/app/assets/javascripts/pages/groups/milestones/edit/index.js b/app/assets/javascripts/pages/groups/milestones/edit/index.js index 5c99c90e24d..ddd10fe5062 100644 --- a/app/assets/javascripts/pages/groups/milestones/edit/index.js +++ b/app/assets/javascripts/pages/groups/milestones/edit/index.js @@ -1,3 +1,3 @@ import initForm from '../../../../shared/milestones/form'; -export default () => initForm(false); +document.addEventListener('DOMContentLoaded', () => initForm(false)); diff --git a/app/assets/javascripts/pages/groups/milestones/new/index.js b/app/assets/javascripts/pages/groups/milestones/new/index.js index 5c99c90e24d..ddd10fe5062 100644 --- a/app/assets/javascripts/pages/groups/milestones/new/index.js +++ b/app/assets/javascripts/pages/groups/milestones/new/index.js @@ -1,3 +1,3 @@ import initForm from '../../../../shared/milestones/form'; -export default () => initForm(false); +document.addEventListener('DOMContentLoaded', () => initForm(false)); diff --git a/app/assets/javascripts/pages/groups/milestones/show/index.js b/app/assets/javascripts/pages/groups/milestones/show/index.js index c9a18353f2e..88f40b5278e 100644 --- a/app/assets/javascripts/pages/groups/milestones/show/index.js +++ b/app/assets/javascripts/pages/groups/milestones/show/index.js @@ -1,3 +1,3 @@ import initMilestonesShow from '~/pages/milestones/shared/init_milestones_show'; -export default initMilestonesShow; +document.addEventListener('DOMContentLoaded', initMilestonesShow); diff --git a/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js b/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js index f26c7360fbe..ad79f7e09ac 100644 --- a/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js +++ b/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js @@ -1,11 +1,12 @@ -import SecretValues from '~/behaviors/secret_values'; +import AjaxVariableList from '~/ci_variable_list/ajax_variable_list'; export default () => { - const secretVariableTable = document.querySelector('.js-secret-variable-table'); - if (secretVariableTable) { - const secretVariableTableValues = new SecretValues({ - container: secretVariableTable, - }); - secretVariableTableValues.init(); - } + const variableListEl = document.querySelector('.js-ci-variable-list-section'); + // eslint-disable-next-line no-new + new AjaxVariableList({ + container: variableListEl, + saveButton: variableListEl.querySelector('.js-secret-variables-save-button'), + errorBox: variableListEl.querySelector('.js-ci-variable-error-box'), + saveEndpoint: variableListEl.dataset.saveEndpoint, + }); }; diff --git a/app/assets/javascripts/pages/projects/branches/index/index.js b/app/assets/javascripts/pages/projects/branches/index/index.js index cee0f19bf2a..8fa266a37ce 100644 --- a/app/assets/javascripts/pages/projects/branches/index/index.js +++ b/app/assets/javascripts/pages/projects/branches/index/index.js @@ -1,7 +1,7 @@ import AjaxLoadingSpinner from '~/ajax_loading_spinner'; import DeleteModal from '~/branches/branches_delete_modal'; -export default () => { +document.addEventListener('DOMContentLoaded', () => { AjaxLoadingSpinner.init(); new DeleteModal(); // eslint-disable-line no-new -}; +}); diff --git a/app/assets/javascripts/pages/projects/branches/new/index.js b/app/assets/javascripts/pages/projects/branches/new/index.js index ae5e033e97e..d32d5c6cb29 100644 --- a/app/assets/javascripts/pages/projects/branches/new/index.js +++ b/app/assets/javascripts/pages/projects/branches/new/index.js @@ -1,3 +1,5 @@ import NewBranchForm from '~/new_branch_form'; -export default () => new NewBranchForm($('.js-create-branch-form'), JSON.parse(document.getElementById('availableRefs').innerHTML)); +document.addEventListener('DOMContentLoaded', () => ( + new NewBranchForm($('.js-create-branch-form'), JSON.parse(document.getElementById('availableRefs').innerHTML)) +)); diff --git a/app/assets/javascripts/pages/projects/commits/show/index.js b/app/assets/javascripts/pages/projects/commits/show/index.js index 90b5882a24f..6110fda17de 100644 --- a/app/assets/javascripts/pages/projects/commits/show/index.js +++ b/app/assets/javascripts/pages/projects/commits/show/index.js @@ -3,7 +3,7 @@ import GpgBadges from '~/gpg_badges'; import ShortcutsNavigation from '~/shortcuts_navigation'; export default () => { - CommitsList.init(document.querySelector('.js-project-commits-show').dataset.commitsLimit); + new CommitsList(document.querySelector('.js-project-commits-show').dataset.commitsLimit); // eslint-disable-line no-new new ShortcutsNavigation(); // eslint-disable-line no-new GpgBadges.fetch(); }; diff --git a/app/assets/javascripts/pages/projects/compare/show/index.js b/app/assets/javascripts/pages/projects/compare/show/index.js index 6b8d4503568..2b4fd3c47c0 100644 --- a/app/assets/javascripts/pages/projects/compare/show/index.js +++ b/app/assets/javascripts/pages/projects/compare/show/index.js @@ -1,8 +1,8 @@ import Diff from '~/diff'; import initChangesDropdown from '~/init_changes_dropdown'; -export default () => { +document.addEventListener('DOMContentLoaded', () => { new Diff(); // eslint-disable-line no-new const paddingTop = 16; initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight - paddingTop); -}; +}); diff --git a/app/assets/javascripts/pages/projects/environments/metrics/index.js b/app/assets/javascripts/pages/projects/environments/metrics/index.js index f4760cb2720..0b644780ad4 100644 --- a/app/assets/javascripts/pages/projects/environments/metrics/index.js +++ b/app/assets/javascripts/pages/projects/environments/metrics/index.js @@ -1,3 +1,3 @@ import monitoringBundle from '~/monitoring/monitoring_bundle'; -export default monitoringBundle; +document.addEventListener('DOMContentLoaded', monitoringBundle); diff --git a/app/assets/javascripts/pages/projects/issues/edit/index.js b/app/assets/javascripts/pages/projects/issues/edit/index.js index 7f27f379d8c..ffc84dc106b 100644 --- a/app/assets/javascripts/pages/projects/issues/edit/index.js +++ b/app/assets/javascripts/pages/projects/issues/edit/index.js @@ -1,5 +1,3 @@ import initForm from '../form'; -export default () => { - initForm(); -}; +document.addEventListener('DOMContentLoaded', initForm); diff --git a/app/assets/javascripts/pages/projects/issues/index/index.js b/app/assets/javascripts/pages/projects/issues/index/index.js index 39c043edc38..70fdb0ef40d 100644 --- a/app/assets/javascripts/pages/projects/issues/index/index.js +++ b/app/assets/javascripts/pages/projects/issues/index/index.js @@ -8,7 +8,9 @@ import { FILTERED_SEARCH } from '~/pages/constants'; import { ISSUABLE_INDEX } from '~/pages/projects/constants'; document.addEventListener('DOMContentLoaded', () => { - initFilteredSearch(FILTERED_SEARCH.ISSUES); + initFilteredSearch({ + page: FILTERED_SEARCH.ISSUES, + }); new IssuableIndex(ISSUABLE_INDEX.ISSUE); new ShortcutsNavigation(); diff --git a/app/assets/javascripts/pages/projects/issues/new/index.js b/app/assets/javascripts/pages/projects/issues/new/index.js index 7f27f379d8c..ffc84dc106b 100644 --- a/app/assets/javascripts/pages/projects/issues/new/index.js +++ b/app/assets/javascripts/pages/projects/issues/new/index.js @@ -1,5 +1,3 @@ import initForm from '../form'; -export default () => { - initForm(); -}; +document.addEventListener('DOMContentLoaded', initForm); diff --git a/app/assets/javascripts/pages/projects/merge_requests/creations/diffs/index.js b/app/assets/javascripts/pages/projects/merge_requests/creations/index.js index 734d01ae6f2..febfecebbd2 100644 --- a/app/assets/javascripts/pages/projects/merge_requests/creations/diffs/index.js +++ b/app/assets/javascripts/pages/projects/merge_requests/creations/index.js @@ -1,3 +1,3 @@ import initMergeRequest from '~/pages/projects/merge_requests/init_merge_request'; -export default initMergeRequest; +document.addEventListener('DOMContentLoaded', initMergeRequest); diff --git a/app/assets/javascripts/pages/projects/merge_requests/creations/new/index.js b/app/assets/javascripts/pages/projects/merge_requests/creations/new/index.js index ccd0b54c5ed..1d5aec4001d 100644 --- a/app/assets/javascripts/pages/projects/merge_requests/creations/new/index.js +++ b/app/assets/javascripts/pages/projects/merge_requests/creations/new/index.js @@ -1,7 +1,7 @@ import Compare from '~/compare'; import MergeRequest from '~/merge_request'; -export default () => { +document.addEventListener('DOMContentLoaded', () => { const mrNewCompareNode = document.querySelector('.js-merge-request-new-compare'); if (mrNewCompareNode) { new Compare({ // eslint-disable-line no-new @@ -15,4 +15,4 @@ export default () => { action: mrNewSubmitNode.dataset.mrSubmitAction, }); } -}; +}); diff --git a/app/assets/javascripts/pages/projects/merge_requests/edit/index.js b/app/assets/javascripts/pages/projects/merge_requests/edit/index.js index 734d01ae6f2..febfecebbd2 100644 --- a/app/assets/javascripts/pages/projects/merge_requests/edit/index.js +++ b/app/assets/javascripts/pages/projects/merge_requests/edit/index.js @@ -1,3 +1,3 @@ import initMergeRequest from '~/pages/projects/merge_requests/init_merge_request'; -export default initMergeRequest; +document.addEventListener('DOMContentLoaded', initMergeRequest); diff --git a/app/assets/javascripts/pages/projects/merge_requests/index/index.js b/app/assets/javascripts/pages/projects/merge_requests/index/index.js index adadbf28e49..a7aa616319f 100644 --- a/app/assets/javascripts/pages/projects/merge_requests/index/index.js +++ b/app/assets/javascripts/pages/projects/merge_requests/index/index.js @@ -6,7 +6,9 @@ import { FILTERED_SEARCH } from '~/pages/constants'; import { ISSUABLE_INDEX } from '~/pages/projects/constants'; document.addEventListener('DOMContentLoaded', () => { - initFilteredSearch(FILTERED_SEARCH.MERGE_REQUESTS); + initFilteredSearch({ + page: FILTERED_SEARCH.MERGE_REQUESTS, + }); new IssuableIndex(ISSUABLE_INDEX.MERGE_REQUEST); // eslint-disable-line no-new new ShortcutsNavigation(); // eslint-disable-line no-new new UsersSelect(); // eslint-disable-line no-new diff --git a/app/assets/javascripts/pages/projects/merge_requests/show/index.js b/app/assets/javascripts/pages/projects/merge_requests/show/index.js new file mode 100644 index 00000000000..c3463c266e3 --- /dev/null +++ b/app/assets/javascripts/pages/projects/merge_requests/show/index.js @@ -0,0 +1,24 @@ +import MergeRequest from '~/merge_request'; +import ZenMode from '~/zen_mode'; +import initNotes from '~/init_notes'; +import initIssuableSidebar from '~/init_issuable_sidebar'; +import ShortcutsIssuable from '~/shortcuts_issuable'; +import Diff from '~/diff'; +import { handleLocationHash } from '~/lib/utils/common_utils'; + +export default () => { + new Diff(); // eslint-disable-line no-new + new ZenMode(); // eslint-disable-line no-new + + initIssuableSidebar(); // eslint-disable-line no-new + initNotes(); // eslint-disable-line no-new + + const mrShowNode = document.querySelector('.merge-request'); + + window.mergeRequest = new MergeRequest({ + action: mrShowNode.dataset.mrAction, + }); + + new ShortcutsIssuable(true); // eslint-disable-line no-new + handleLocationHash(); +}; diff --git a/app/assets/javascripts/pages/projects/milestones/edit/index.js b/app/assets/javascripts/pages/projects/milestones/edit/index.js index 10e3979a36e..9a4ebf9890d 100644 --- a/app/assets/javascripts/pages/projects/milestones/edit/index.js +++ b/app/assets/javascripts/pages/projects/milestones/edit/index.js @@ -1,3 +1,3 @@ import initForm from '../../../../shared/milestones/form'; -export default () => initForm(); +document.addEventListener('DOMContentLoaded', () => initForm()); diff --git a/app/assets/javascripts/pages/projects/milestones/index/index.js b/app/assets/javascripts/pages/projects/milestones/index/index.js index 8fb4d83d8a3..38789365a67 100644 --- a/app/assets/javascripts/pages/projects/milestones/index/index.js +++ b/app/assets/javascripts/pages/projects/milestones/index/index.js @@ -1,3 +1,3 @@ import milestones from '~/pages/milestones/shared'; -export default milestones; +document.addEventListener('DOMContentLoaded', milestones); diff --git a/app/assets/javascripts/pages/projects/milestones/new/index.js b/app/assets/javascripts/pages/projects/milestones/new/index.js index 10e3979a36e..9a4ebf9890d 100644 --- a/app/assets/javascripts/pages/projects/milestones/new/index.js +++ b/app/assets/javascripts/pages/projects/milestones/new/index.js @@ -1,3 +1,3 @@ import initForm from '../../../../shared/milestones/form'; -export default () => initForm(); +document.addEventListener('DOMContentLoaded', () => initForm()); diff --git a/app/assets/javascripts/pages/projects/milestones/show/index.js b/app/assets/javascripts/pages/projects/milestones/show/index.js index 35b5c9c2ced..84a52421598 100644 --- a/app/assets/javascripts/pages/projects/milestones/show/index.js +++ b/app/assets/javascripts/pages/projects/milestones/show/index.js @@ -1,7 +1,7 @@ import initMilestonesShow from '~/pages/milestones/shared/init_milestones_show'; import milestones from '~/pages/milestones/shared'; -export default () => { +document.addEventListener('DOMContentLoaded', () => { initMilestonesShow(); milestones(); -}; +}); diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/create/index.js b/app/assets/javascripts/pages/projects/pipeline_schedules/create/index.js new file mode 100644 index 00000000000..d65be6bc69e --- /dev/null +++ b/app/assets/javascripts/pages/projects/pipeline_schedules/create/index.js @@ -0,0 +1,3 @@ +import initForm from '../shared/init_form'; + +document.addEventListener('DOMContentLoaded', initForm); diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/edit/index.js b/app/assets/javascripts/pages/projects/pipeline_schedules/edit/index.js new file mode 100644 index 00000000000..d65be6bc69e --- /dev/null +++ b/app/assets/javascripts/pages/projects/pipeline_schedules/edit/index.js @@ -0,0 +1,3 @@ +import initForm from '../shared/init_form'; + +document.addEventListener('DOMContentLoaded', initForm); diff --git a/app/assets/javascripts/pipeline_schedules/pipeline_schedules_index_bundle.js b/app/assets/javascripts/pages/projects/pipeline_schedules/index/index.js index a6c945e22b0..544360dcd51 100644 --- a/app/assets/javascripts/pipeline_schedules/pipeline_schedules_index_bundle.js +++ b/app/assets/javascripts/pages/projects/pipeline_schedules/index/index.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import PipelineSchedulesCallout from './components/pipeline_schedules_callout.vue'; +import PipelineSchedulesCallout from '../shared/components/pipeline_schedules_callout.vue'; document.addEventListener('DOMContentLoaded', () => new Vue({ el: '#pipeline-schedules-callout', diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/new/index.js b/app/assets/javascripts/pages/projects/pipeline_schedules/new/index.js new file mode 100644 index 00000000000..d65be6bc69e --- /dev/null +++ b/app/assets/javascripts/pages/projects/pipeline_schedules/new/index.js @@ -0,0 +1,3 @@ +import initForm from '../shared/init_form'; + +document.addEventListener('DOMContentLoaded', initForm); diff --git a/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.vue b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue index 2d18fa2044b..2d18fa2044b 100644 --- a/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.vue +++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue diff --git a/app/assets/javascripts/pipeline_schedules/components/pipeline_schedules_callout.vue b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue index aa04a0ac47a..77508e62cef 100644 --- a/app/assets/javascripts/pipeline_schedules/components/pipeline_schedules_callout.vue +++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue @@ -1,7 +1,7 @@ <script> import Vue from 'vue'; import Cookies from 'js-cookie'; - import Translate from '../../vue_shared/translate'; + import Translate from '../../../../../vue_shared/translate'; import illustrationSvg from '../icons/intro_illustration.svg'; Vue.use(Translate); diff --git a/app/assets/javascripts/pipeline_schedules/components/target_branch_dropdown.js b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/target_branch_dropdown.js index 0c3926d76b5..0c3926d76b5 100644 --- a/app/assets/javascripts/pipeline_schedules/components/target_branch_dropdown.js +++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/target_branch_dropdown.js diff --git a/app/assets/javascripts/pipeline_schedules/components/timezone_dropdown.js b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js index 95ed9c7dc21..95ed9c7dc21 100644 --- a/app/assets/javascripts/pipeline_schedules/components/timezone_dropdown.js +++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js diff --git a/app/assets/javascripts/pipeline_schedules/icons/intro_illustration.svg b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/icons/intro_illustration.svg index 26d1ff97b3e..26d1ff97b3e 100644 --- a/app/assets/javascripts/pipeline_schedules/icons/intro_illustration.svg +++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/icons/intro_illustration.svg diff --git a/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js index 0b1a81bae13..cfd30d6053f 100644 --- a/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js +++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js @@ -1,10 +1,10 @@ import Vue from 'vue'; -import Translate from '../vue_shared/translate'; -import GlFieldErrors from '../gl_field_errors'; +import Translate from '../../../../vue_shared/translate'; +import GlFieldErrors from '../../../../gl_field_errors'; import intervalPatternInput from './components/interval_pattern_input.vue'; import TimezoneDropdown from './components/timezone_dropdown'; import TargetBranchDropdown from './components/target_branch_dropdown'; -import setupNativeFormVariableList from '../ci_variable_list/native_form_variable_list'; +import setupNativeFormVariableList from '../../../../ci_variable_list/native_form_variable_list'; Vue.use(Translate); @@ -27,7 +27,7 @@ function initIntervalPatternInput() { }); } -document.addEventListener('DOMContentLoaded', () => { +export default () => { /* Most of the form is written in haml, but for fields with more complex behaviors, * you should mount individual Vue components here. If at some point components need * to share state, it may make sense to refactor the whole form to Vue */ @@ -46,4 +46,4 @@ document.addEventListener('DOMContentLoaded', () => { container: $('.js-ci-variable-list-section'), formField: 'schedule', }); -}); +}; diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/update/index.js b/app/assets/javascripts/pages/projects/pipeline_schedules/update/index.js new file mode 100644 index 00000000000..d65be6bc69e --- /dev/null +++ b/app/assets/javascripts/pages/projects/pipeline_schedules/update/index.js @@ -0,0 +1,3 @@ +import initForm from '../shared/init_form'; + +document.addEventListener('DOMContentLoaded', initForm); diff --git a/app/assets/javascripts/pages/projects/pipelines/charts/index.js b/app/assets/javascripts/pages/projects/pipelines/charts/index.js new file mode 100644 index 00000000000..bb92f4e1459 --- /dev/null +++ b/app/assets/javascripts/pages/projects/pipelines/charts/index.js @@ -0,0 +1,56 @@ +import Chart from 'chart.js'; + +const options = { + scaleOverlay: true, + responsive: true, + maintainAspectRatio: false, +}; + +const buildChart = (chartScope) => { + const data = { + labels: chartScope.labels, + datasets: [{ + fillColor: '#707070', + strokeColor: '#707070', + pointColor: '#707070', + pointStrokeColor: '#EEE', + data: chartScope.totalValues, + }, + { + fillColor: '#1aaa55', + strokeColor: '#1aaa55', + pointColor: '#1aaa55', + pointStrokeColor: '#fff', + data: chartScope.successValues, + }, + ], + }; + const ctx = $(`#${chartScope.scope}Chart`).get(0).getContext('2d'); + + new Chart(ctx).Line(data, options); +}; + +document.addEventListener('DOMContentLoaded', () => { + const chartTimesData = JSON.parse(document.getElementById('pipelinesTimesChartsData').innerHTML); + const chartsData = JSON.parse(document.getElementById('pipelinesChartsData').innerHTML); + const data = { + labels: chartTimesData.labels, + datasets: [{ + fillColor: 'rgba(220,220,220,0.5)', + strokeColor: 'rgba(220,220,220,1)', + barStrokeWidth: 1, + barValueSpacing: 1, + barDatasetSpacing: 1, + data: chartTimesData.values, + }], + }; + + if (window.innerWidth < 768) { + // Scale fonts if window width lower than 768px (iPad portrait) + options.scaleFontSize = 8; + } + + new Chart($('#build_timesChart').get(0).getContext('2d')).Bar(data, options); + + chartsData.forEach(scope => buildChart(scope)); +}); diff --git a/app/assets/javascripts/pages/projects/services/edit/index.js b/app/assets/javascripts/pages/projects/services/edit/index.js new file mode 100644 index 00000000000..5c73171e62e --- /dev/null +++ b/app/assets/javascripts/pages/projects/services/edit/index.js @@ -0,0 +1,13 @@ +import IntegrationSettingsForm from '~/integrations/integration_settings_form'; +import PrometheusMetrics from '~/prometheus_metrics/prometheus_metrics'; + +export default () => { + const prometheusSettingsWrapper = document.querySelector('.js-prometheus-metrics-monitoring'); + const integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form'); + integrationSettingsForm.init(); + + if (prometheusSettingsWrapper) { + const prometheusMetrics = new PrometheusMetrics('.js-prometheus-metrics-monitoring'); + prometheusMetrics.loadActiveMetrics(); + } +}; diff --git a/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js index 18dc1dc03a5..a563d0f9961 100644 --- a/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js +++ b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js @@ -1,9 +1,11 @@ import initSettingsPanels from '~/settings_panels'; import SecretValues from '~/behaviors/secret_values'; +import AjaxVariableList from '~/ci_variable_list/ajax_variable_list'; export default function () { // Initialize expandable settings panels initSettingsPanels(); + const runnerToken = document.querySelector('.js-secret-runner-token'); if (runnerToken) { const runnerTokenSecretValue = new SecretValues({ @@ -12,11 +14,12 @@ export default function () { runnerTokenSecretValue.init(); } - const secretVariableTable = document.querySelector('.js-secret-variable-table'); - if (secretVariableTable) { - const secretVariableTableValues = new SecretValues({ - container: secretVariableTable, - }); - secretVariableTableValues.init(); - } + const variableListEl = document.querySelector('.js-ci-variable-list-section'); + // eslint-disable-next-line no-new + new AjaxVariableList({ + container: variableListEl, + saveButton: variableListEl.querySelector('.js-secret-variables-save-button'), + errorBox: variableListEl.querySelector('.js-ci-variable-error-box'), + saveEndpoint: variableListEl.dataset.saveEndpoint, + }); } diff --git a/app/assets/javascripts/pages/projects/tree/show/index.js b/app/assets/javascripts/pages/projects/tree/show/index.js index c4b3356e478..cba57058380 100644 --- a/app/assets/javascripts/pages/projects/tree/show/index.js +++ b/app/assets/javascripts/pages/projects/tree/show/index.js @@ -14,7 +14,7 @@ export default () => { $('#tree-slider').waitForImages(() => ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath)); - const commitPipelineStatusEl = document.getElementById('commit-pipeline-status'); + const commitPipelineStatusEl = document.querySelector('.js-commit-pipeline-status'); const statusLink = document.querySelector('.commit-actions .ci-status-link'); if (statusLink != null) { statusLink.remove(); diff --git a/app/assets/javascripts/pages/search/init_filtered_search.js b/app/assets/javascripts/pages/search/init_filtered_search.js index 44853636aea..250f9d992ab 100644 --- a/app/assets/javascripts/pages/search/init_filtered_search.js +++ b/app/assets/javascripts/pages/search/init_filtered_search.js @@ -1,7 +1,7 @@ -export default (page) => { +export default ({ page }) => { const filteredSearchEnabled = gl.FilteredSearchManager && document.querySelector('.filtered-search'); if (filteredSearchEnabled) { - const filteredSearchManager = new gl.FilteredSearchManager(page); + const filteredSearchManager = new gl.FilteredSearchManager({ page }); filteredSearchManager.setup(); } }; diff --git a/app/assets/javascripts/users/activity_calendar.js b/app/assets/javascripts/pages/users/activity_calendar.js index 57306322aa4..57306322aa4 100644 --- a/app/assets/javascripts/users/activity_calendar.js +++ b/app/assets/javascripts/pages/users/activity_calendar.js diff --git a/app/assets/javascripts/users/index.js b/app/assets/javascripts/pages/users/index.js index 9fd8452a2b6..899dcd42e37 100644 --- a/app/assets/javascripts/users/index.js +++ b/app/assets/javascripts/pages/users/index.js @@ -1,3 +1,4 @@ +import UserCallout from '~/user_callout'; import Cookies from 'js-cookie'; import UserTabs from './user_tabs'; @@ -22,4 +23,5 @@ document.addEventListener('DOMContentLoaded', () => { const page = $('body').attr('data-page'); const action = page.split(':')[1]; initUserProfile(action); + new UserCallout(); // eslint-disable-line no-new }); diff --git a/app/assets/javascripts/pages/users/show/index.js b/app/assets/javascripts/pages/users/show/index.js deleted file mode 100644 index f18f98b4e9a..00000000000 --- a/app/assets/javascripts/pages/users/show/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import UserCallout from '~/user_callout'; - -export default () => new UserCallout(); diff --git a/app/assets/javascripts/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js index e13b9839a20..c1217623467 100644 --- a/app/assets/javascripts/users/user_tabs.js +++ b/app/assets/javascripts/pages/users/user_tabs.js @@ -1,9 +1,9 @@ -import axios from '../lib/utils/axios_utils'; -import Activities from '../activities'; +import axios from '~/lib/utils/axios_utils'; +import Activities from '~/activities'; +import { localTimeAgo } from '~/lib/utils/datetime_utility'; +import { __ } from '~/locale'; +import flash from '~/flash'; import ActivityCalendar from './activity_calendar'; -import { localTimeAgo } from '../lib/utils/datetime_utility'; -import { __ } from '../locale'; -import flash from '../flash'; /** * UserTabs diff --git a/app/assets/javascripts/pipelines/components/async_button.vue b/app/assets/javascripts/pipelines/components/async_button.vue index 77553ca67cc..a5f22c4ec80 100644 --- a/app/assets/javascripts/pipelines/components/async_button.vue +++ b/app/assets/javascripts/pipelines/components/async_button.vue @@ -31,10 +31,9 @@ type: String, required: true, }, - confirmActionMessage: { - type: String, - required: false, - default: '', + id: { + type: Number, + required: true, }, }, data() { @@ -49,11 +48,10 @@ }, methods: { onClick() { - if (this.confirmActionMessage !== '' && confirm(this.confirmActionMessage)) { - this.makeRequest(); - } else if (this.confirmActionMessage === '') { - this.makeRequest(); - } + eventHub.$emit('actionConfirmationModal', { + id: this.id, + callback: this.makeRequest, + }); }, makeRequest() { this.isLoading = true; diff --git a/app/assets/javascripts/pipelines/components/pipelines_table.vue b/app/assets/javascripts/pipelines/components/pipelines_table.vue index 6681b89e629..62fe479fdf4 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_table.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_table.vue @@ -1,5 +1,7 @@ <script> import pipelinesTableRowComponent from './pipelines_table_row.vue'; + import stopConfirmationModal from './stop_confirmation_modal.vue'; + import retryConfirmationModal from './retry_confirmation_modal.vue'; /** * Pipelines Table Component. @@ -9,6 +11,8 @@ export default { components: { pipelinesTableRowComponent, + stopConfirmationModal, + retryConfirmationModal, }, props: { pipelines: { @@ -70,5 +74,7 @@ :auto-devops-help-path="autoDevopsHelpPath" :view-type="viewType" /> + <stop-confirmation-modal /> + <retry-confirmation-modal /> </div> </template> diff --git a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue index d0e4cf7ff40..0e3a10ed7f4 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue @@ -305,6 +305,9 @@ css-class="js-pipelines-retry-button btn-default btn-retry" title="Retry" icon="repeat" + :id="pipeline.id" + data-toggle="modal" + data-target="#retry-confirmation-modal" /> <async-button-component @@ -313,7 +316,9 @@ css-class="js-pipelines-cancel-button btn-remove" title="Cancel" icon="close" - confirm-action-message="Are you sure you want to cancel this pipeline?" + :id="pipeline.id" + data-toggle="modal" + data-target="#stop-confirmation-modal" /> </div> </div> diff --git a/app/assets/javascripts/pipelines/components/retry_confirmation_modal.vue b/app/assets/javascripts/pipelines/components/retry_confirmation_modal.vue new file mode 100644 index 00000000000..e2ac08d67bc --- /dev/null +++ b/app/assets/javascripts/pipelines/components/retry_confirmation_modal.vue @@ -0,0 +1,65 @@ +<script> + import modal from '~/vue_shared/components/modal.vue'; + import { s__, sprintf } from '~/locale'; + import eventHub from '../event_hub'; + + export default { + components: { + modal, + }, + data() { + return { + id: '', + callback: () => {}, + }; + }, + computed: { + title() { + return sprintf(s__('Pipeline|Retry pipeline #%{id}?'), { + id: `'${this.id}'`, + }, false); + }, + text() { + return sprintf(s__('Pipeline|You’re about to retry pipeline %{id}.'), { + id: `<strong>#${this.id}</strong>`, + }, false); + }, + primaryButtonLabel() { + return s__('Pipeline|Retry pipeline'); + }, + }, + created() { + eventHub.$on('actionConfirmationModal', this.updateModal); + }, + beforeDestroy() { + eventHub.$off('actionConfirmationModal', this.updateModal); + }, + methods: { + updateModal(action) { + this.id = action.id; + this.callback = action.callback; + }, + onSubmit() { + this.callback(); + }, + }, + }; +</script> + +<template> + <modal + id="retry-confirmation-modal" + :title="title" + :text="text" + kind="danger" + :primary-button-label="primaryButtonLabel" + @submit="onSubmit" + > + <template + slot="body" + slot-scope="props" + > + <p v-html="props.text"></p> + </template> + </modal> +</template> diff --git a/app/assets/javascripts/pipelines/components/stop_confirmation_modal.vue b/app/assets/javascripts/pipelines/components/stop_confirmation_modal.vue new file mode 100644 index 00000000000..d737d567787 --- /dev/null +++ b/app/assets/javascripts/pipelines/components/stop_confirmation_modal.vue @@ -0,0 +1,65 @@ +<script> + import modal from '~/vue_shared/components/modal.vue'; + import { s__, sprintf } from '~/locale'; + import eventHub from '../event_hub'; + + export default { + components: { + modal, + }, + data() { + return { + id: '', + callback: () => {}, + }; + }, + computed: { + title() { + return sprintf(s__('Pipeline|Stop pipeline #%{id}?'), { + id: `'${this.id}'`, + }, false); + }, + text() { + return sprintf(s__('Pipeline|You’re about to stop pipeline %{id}.'), { + id: `<strong>#${this.id}</strong>`, + }, false); + }, + primaryButtonLabel() { + return s__('Pipeline|Stop pipeline'); + }, + }, + created() { + eventHub.$on('actionConfirmationModal', this.updateModal); + }, + beforeDestroy() { + eventHub.$off('actionConfirmationModal', this.updateModal); + }, + methods: { + updateModal(action) { + this.id = action.id; + this.callback = action.callback; + }, + onSubmit() { + this.callback(); + }, + }, + }; +</script> + +<template> + <modal + id="stop-confirmation-modal" + :title="title" + :text="text" + kind="danger" + :primary-button-label="primaryButtonLabel" + @submit="onSubmit" + > + <template + slot="body" + slot-scope="props" + > + <p v-html="props.text"></p> + </template> + </modal> +</template> diff --git a/app/assets/javascripts/pipelines/pipelines_charts.js b/app/assets/javascripts/pipelines/pipelines_charts.js deleted file mode 100644 index 2fce1945906..00000000000 --- a/app/assets/javascripts/pipelines/pipelines_charts.js +++ /dev/null @@ -1,38 +0,0 @@ -import Chart from 'chart.js'; - -document.addEventListener('DOMContentLoaded', () => { - const chartData = JSON.parse(document.getElementById('pipelinesChartsData').innerHTML); - const buildChart = (chartScope) => { - const data = { - labels: chartScope.labels, - datasets: [{ - fillColor: '#707070', - strokeColor: '#707070', - pointColor: '#707070', - pointStrokeColor: '#EEE', - data: chartScope.totalValues, - }, - { - fillColor: '#1aaa55', - strokeColor: '#1aaa55', - pointColor: '#1aaa55', - pointStrokeColor: '#fff', - data: chartScope.successValues, - }, - ], - }; - const ctx = $(`#${chartScope.scope}Chart`).get(0).getContext('2d'); - const options = { - scaleOverlay: true, - responsive: true, - maintainAspectRatio: false, - }; - if (window.innerWidth < 768) { - // Scale fonts if window width lower than 768px (iPad portrait) - options.scaleFontSize = 8; - } - new Chart(ctx).Line(data, options); - }; - - chartData.forEach(scope => buildChart(scope)); -}); diff --git a/app/assets/javascripts/pipelines/pipelines_times.js b/app/assets/javascripts/pipelines/pipelines_times.js deleted file mode 100644 index 14a0eca77e9..00000000000 --- a/app/assets/javascripts/pipelines/pipelines_times.js +++ /dev/null @@ -1,27 +0,0 @@ -import Chart from 'chart.js'; - -document.addEventListener('DOMContentLoaded', () => { - const chartData = JSON.parse(document.getElementById('pipelinesTimesChartsData').innerHTML); - const data = { - labels: chartData.labels, - datasets: [{ - fillColor: 'rgba(220,220,220,0.5)', - strokeColor: 'rgba(220,220,220,1)', - barStrokeWidth: 1, - barValueSpacing: 1, - barDatasetSpacing: 1, - data: chartData.values, - }], - }; - const ctx = $('#build_timesChart').get(0).getContext('2d'); - const options = { - scaleOverlay: true, - responsive: true, - maintainAspectRatio: false, - }; - if (window.innerWidth < 768) { - // Scale fonts if window width lower than 768px (iPad portrait) - options.scaleFontSize = 8; - } - new Chart(ctx).Bar(data, options); -}); diff --git a/app/assets/javascripts/profile/profile.js b/app/assets/javascripts/profile/profile.js index ba4ac850346..930f0fb381e 100644 --- a/app/assets/javascripts/profile/profile.js +++ b/app/assets/javascripts/profile/profile.js @@ -1,7 +1,9 @@ /* eslint-disable comma-dangle, no-unused-vars, class-methods-use-this, quotes, consistent-return, func-names, prefer-arrow-callback, space-before-function-paren, max-len */ import Cookies from 'js-cookie'; -import Flash from '../flash'; -import { getPagePath } from '../lib/utils/common_utils'; +import { getPagePath } from '~/lib/utils/common_utils'; +import axios from '~/lib/utils/axios_utils'; +import { __ } from '~/locale'; +import flash from '../flash'; ((global) => { class Profile { @@ -31,9 +33,6 @@ import { getPagePath } from '../lib/utils/common_utils'; $('input[name="user[multi_file]"]').on('change', this.setNewRepoCookie); $('#user_notification_email').on('change', this.submitForm); $('#user_notified_of_own_activity').on('change', this.submitForm); - $('.update-username').on('ajax:before', this.beforeUpdateUsername); - $('.update-username').on('ajax:complete', this.afterUpdateUsername); - $('.update-notifications').on('ajax:success', this.onUpdateNotifs); this.form.on('submit', this.onSubmitForm); } @@ -46,21 +45,6 @@ import { getPagePath } from '../lib/utils/common_utils'; return this.saveForm(); } - beforeUpdateUsername() { - $('.loading-username', this).removeClass('hidden'); - } - - afterUpdateUsername() { - $('.loading-username', this).addClass('hidden'); - $('button[type=submit]', this).enable(); - } - - onUpdateNotifs(e, data) { - return data.saved ? - new Flash("Notification settings saved", "notice") : - new Flash("Failed to save new settings", "alert"); - } - saveForm() { const self = this; const formData = new FormData(this.form[0]); @@ -70,21 +54,18 @@ import { getPagePath } from '../lib/utils/common_utils'; formData.append('user[avatar]', avatarBlob, 'avatar.png'); } - return $.ajax({ + axios({ + method: this.form.attr('method'), url: this.form.attr('action'), - type: this.form.attr('method'), data: formData, - dataType: "json", - processData: false, - contentType: false, - success: response => new Flash(response.message, 'notice'), - error: jqXHR => new Flash(jqXHR.responseJSON.message, 'alert'), - complete: () => { - window.scrollTo(0, 0); - // Enable submit button after requests ends - return self.form.find(':input[disabled]').enable(); - } - }); + }) + .then(({ data }) => flash(data.message, 'notice')) + .then(() => { + window.scrollTo(0, 0); + // Enable submit button after requests ends + self.form.find(':input[disabled]').enable(); + }) + .catch(error => flash(error.message)); } setNewRepoCookie() { diff --git a/app/assets/javascripts/prometheus_metrics/index.js b/app/assets/javascripts/prometheus_metrics/index.js deleted file mode 100644 index a0c43c5abe1..00000000000 --- a/app/assets/javascripts/prometheus_metrics/index.js +++ /dev/null @@ -1,6 +0,0 @@ -import PrometheusMetrics from './prometheus_metrics'; - -$(() => { - const prometheusMetrics = new PrometheusMetrics('.js-prometheus-metrics-monitoring'); - prometheusMetrics.loadActiveMetrics(); -}); diff --git a/app/assets/javascripts/render_math.js b/app/assets/javascripts/render_math.js index 73b6aafdd12..eabdb01b2a9 100644 --- a/app/assets/javascripts/render_math.js +++ b/app/assets/javascripts/render_math.js @@ -1,4 +1,5 @@ -/* global katex */ +import { __ } from './locale'; +import flash from './flash'; // Renders math using KaTeX in any element with the // `js-render-math` class @@ -8,15 +9,8 @@ // <code class="js-render-math"></div> // -import { __ } from './locale'; -import axios from './lib/utils/axios_utils'; -import flash from './flash'; - -// Only load once -let katexLoaded = false; - // Loop over all math elements and render math -function renderWithKaTeX(elements) { +function renderWithKaTeX(elements, katex) { elements.each(function katexElementsLoop() { const mathNode = $('<span></span>'); const $this = $(this); @@ -34,30 +28,10 @@ function renderWithKaTeX(elements) { export default function renderMath($els) { if (!$els.length) return; - - if (katexLoaded) { - renderWithKaTeX($els); - } else { - axios.get(gon.katex_css_url) - .then(() => { - const css = $('<link>', { - rel: 'stylesheet', - type: 'text/css', - href: gon.katex_css_url, - }); - css.appendTo('head'); - }) - .then(() => axios.get(gon.katex_js_url, { - responseType: 'text', - })) - .then(({ data }) => { - // Add katex js to our document - $.globalEval(data); - }) - .then(() => { - katexLoaded = true; - renderWithKaTeX($els); // Run KaTeX - }) - .catch(() => flash(__('An error occurred while rendering KaTeX'))); - } + Promise.all([ + import(/* webpackChunkName: 'katex' */ 'katex'), + import(/* webpackChunkName: 'katex' */ 'katex/dist/katex.css'), + ]).then(([katex]) => { + renderWithKaTeX($els, katex); + }).catch(() => flash(__('An error occurred while rendering KaTeX'))); } diff --git a/app/assets/javascripts/render_mermaid.js b/app/assets/javascripts/render_mermaid.js index 31c7a772cf4..d4f18955bd2 100644 --- a/app/assets/javascripts/render_mermaid.js +++ b/app/assets/javascripts/render_mermaid.js @@ -30,6 +30,9 @@ export default function renderMermaid($els) { $els.each((i, el) => { const source = el.textContent; + // Remove any extra spans added by the backend syntax highlighting. + Object.assign(el, { textContent: source }); + mermaid.init(undefined, el, (id) => { const svg = document.getElementById(id); diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js index 98b524f7e3f..8f4a8704c3b 100644 --- a/app/assets/javascripts/search_autocomplete.js +++ b/app/assets/javascripts/search_autocomplete.js @@ -1,4 +1,5 @@ /* eslint-disable no-return-assign, one-var, no-var, no-underscore-dangle, one-var-declaration-per-line, no-unused-vars, no-cond-assign, consistent-return, object-shorthand, prefer-arrow-callback, func-names, space-before-function-paren, prefer-template, quotes, class-methods-use-this, no-sequences, wrap-iife, no-lonely-if, no-else-return, no-param-reassign, vars-on-top, max-len */ +import axios from './lib/utils/axios_utils'; import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from './lib/utils/common_utils'; /** @@ -146,23 +147,25 @@ export default class SearchAutocomplete { this.loadingSuggestions = true; - return $.get(this.autocompletePath, { - project_id: this.projectId, - project_ref: this.projectRef, - term: term, - }, (response) => { - var firstCategory, i, lastCategory, len, suggestion; + return axios.get(this.autocompletePath, { + params: { + project_id: this.projectId, + project_ref: this.projectRef, + term: term, + }, + }).then((response) => { // Hide dropdown menu if no suggestions returns - if (!response.length) { + if (!response.data.length) { this.disableAutocomplete(); return; } const data = []; // List results - firstCategory = true; - for (i = 0, len = response.length; i < len; i += 1) { - suggestion = response[i]; + let firstCategory = true; + let lastCategory; + for (let i = 0, len = response.data.length; i < len; i += 1) { + const suggestion = response.data[i]; // Add group header before list each group if (lastCategory !== suggestion.category) { if (!firstCategory) { @@ -177,7 +180,7 @@ export default class SearchAutocomplete { lastCategory = suggestion.category; } data.push({ - id: (suggestion.category.toLowerCase()) + "-" + suggestion.id, + id: `${suggestion.category.toLowerCase()}-${suggestion.id}`, category: suggestion.category, text: suggestion.label, url: suggestion.url, @@ -187,13 +190,17 @@ export default class SearchAutocomplete { if (data.length) { data.push('separator'); data.push({ - text: "Result name contains \"" + term + "\"", - url: "/search?search=" + term + "&project_id=" + (this.projectInputEl.val()) + "&group_id=" + (this.groupInputEl.val()), + text: `Result name contains "${term}"`, + url: `/search?search=${term}&project_id=${this.projectInputEl.val()}&group_id=${this.groupInputEl.val()}`, }); } - return callback(data); - }) - .always(() => { this.loadingSuggestions = false; }); + + callback(data); + + this.loadingSuggestions = false; + }).catch(() => { + this.loadingSuggestions = false; + }); } getCategoryContents() { diff --git a/app/assets/javascripts/settings_panels.js b/app/assets/javascripts/settings_panels.js index d34a21b37e1..d0e4f533d8a 100644 --- a/app/assets/javascripts/settings_panels.js +++ b/app/assets/javascripts/settings_panels.js @@ -42,7 +42,7 @@ export default function initSettingsPanels() { if (location.hash) { const $target = $(location.hash); - if ($target.length && $target.hasClass('.settings')) { + if ($target.length && $target.hasClass('settings')) { expandSection($target); } } diff --git a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue index 02153fb86a5..8a86c409b62 100644 --- a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue +++ b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue @@ -2,6 +2,7 @@ import Flash from '../../../flash'; import editForm from './edit_form.vue'; import Icon from '../../../vue_shared/components/icon.vue'; + import { __ } from '../../../locale'; export default { components: { @@ -40,8 +41,7 @@ this.service.update('issue', { confidential }) .then(() => location.reload()) .catch(() => { - Flash(`Something went wrong trying to - change the confidentiality of this issue`); + Flash(__('Something went wrong trying to change the confidentiality of this issue')); }); }, }, @@ -58,7 +58,7 @@ /> </div> <div class="title hide-collapsed"> - Confidentiality + {{ __('Confidentiality') }} <a v-if="isEditable" class="pull-right confidential-edit" @@ -84,7 +84,7 @@ aria-hidden="true" class="sidebar-item-icon inline" /> - Not confidential + {{ __('Not confidential') }} </div> <div v-else @@ -95,7 +95,7 @@ aria-hidden="true" class="sidebar-item-icon inline is-active" /> - This issue is confidential + {{ __('This issue is confidential') }} </div> </div> </div> diff --git a/app/assets/javascripts/sidebar/components/confidential/edit_form.vue b/app/assets/javascripts/sidebar/components/confidential/edit_form.vue index 6a81235a1a7..c569843b05f 100644 --- a/app/assets/javascripts/sidebar/components/confidential/edit_form.vue +++ b/app/assets/javascripts/sidebar/components/confidential/edit_form.vue @@ -1,5 +1,6 @@ <script> import editFormButtons from './edit_form_buttons.vue'; + import { s__ } from '../../../locale'; export default { components: { @@ -19,6 +20,14 @@ type: Function, }, }, + computed: { + confidentialityOnWarning() { + return s__('confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue.'); + }, + confidentialityOffWarning() { + return s__('confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue.'); + }, + }, }; </script> @@ -26,15 +35,13 @@ <div class="dropdown open"> <div class="dropdown-menu sidebar-item-warning-message"> <div> - <p v-if="!isConfidential"> - You are going to turn on the confidentiality. This means that only team members with - <strong>at least Reporter access</strong> - are able to see and leave comments on the issue. + <p + v-if="!isConfidential" + v-html="confidentialityOnWarning"> </p> - <p v-else> - You are going to turn off the confidentiality. This means - <strong>everyone</strong> - will be able to see and leave a comment on this issue. + <p + v-else + v-html="confidentialityOffWarning"> </p> <edit-form-buttons :is-confidential="isConfidential" diff --git a/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue b/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue index 7ed0619ee6b..49d5dfeea1a 100644 --- a/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue +++ b/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue @@ -32,7 +32,7 @@ export default { class="btn btn-default append-right-10" @click="toggleForm" > - Cancel + {{ __('Cancel') }} </button> <button type="button" diff --git a/app/assets/javascripts/sidebar/components/lock/edit_form.vue b/app/assets/javascripts/sidebar/components/lock/edit_form.vue index e7a87636aa7..bc32e974bc3 100644 --- a/app/assets/javascripts/sidebar/components/lock/edit_form.vue +++ b/app/assets/javascripts/sidebar/components/lock/edit_form.vue @@ -1,6 +1,7 @@ <script> import editFormButtons from './edit_form_buttons.vue'; import issuableMixin from '../../../vue_shared/mixins/issuable'; + import { __, sprintf } from '../../../locale'; export default { components: { @@ -25,6 +26,14 @@ type: Function, }, }, + computed: { + lockWarning() { + return sprintf(__('Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment.'), { issuableDisplayName: this.issuableDisplayName }); + }, + unlockWarning() { + return sprintf(__('Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment.'), { issuableDisplayName: this.issuableDisplayName }); + }, + }, }; </script> @@ -33,19 +42,14 @@ <div class="dropdown-menu sidebar-item-warning-message"> <p class="text" - v-if="isLocked"> - Unlock this {{ issuableDisplayName }}? - <strong>Everyone</strong> - will be able to comment. + v-if="isLocked" + v-html="unlockWarning"> </p> <p class="text" - v-else> - Lock this {{ issuableDisplayName }}? - Only - <strong>project members</strong> - will be able to comment. + v-else + v-html="lockWarning"> </p> <edit-form-buttons diff --git a/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue b/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue index 02876a6c175..9d22b9d77be 100644 --- a/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue +++ b/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue @@ -72,10 +72,10 @@ </div> <div class="title hide-collapsed"> - Lock {{ issuableDisplayName }} + {{ sprintf(__('Lock %{issuableDisplayName}'), { issuableDisplayName: issuableDisplayName }) }} <button v-if="isEditable" - class="pull-right lock-edit btn btn-blank" + class="pull-right lock-edit" type="button" @click.prevent="toggleForm" > diff --git a/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.js b/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.js index fd0d4570d68..b5ebccd3795 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.js +++ b/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.js @@ -68,7 +68,7 @@ export default { <div class="compare-display-container"> <div class="compare-display pull-left"> <span class="compare-label"> - Spent + {{ s__('TimeTracking|Spent') }} </span> <span class="compare-value spent"> {{ timeSpentHumanReadable }} @@ -76,7 +76,7 @@ export default { </div> <div class="compare-display estimated pull-right"> <span class="compare-label"> - Est + {{ s__('TimeTrackingEstimated|Est') }} </span> <span class="compare-value"> {{ timeEstimateHumanReadable }} diff --git a/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.js b/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.js index ad1b9179db0..2d324c71379 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.js +++ b/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.js @@ -9,7 +9,7 @@ export default { template: ` <div class="time-tracking-estimate-only-pane"> <span class="bold"> - Estimated: + {{ s__('TimeTracking|Estimated:') }} </span> {{ timeEstimateHumanReadable }} </div> diff --git a/app/assets/javascripts/sidebar/components/time_tracking/help_state.js b/app/assets/javascripts/sidebar/components/time_tracking/help_state.js index 142ad437509..19f74ad3c6d 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/help_state.js +++ b/app/assets/javascripts/sidebar/components/time_tracking/help_state.js @@ -1,3 +1,5 @@ +import { sprintf, s__ } from '../../../locale'; + export default { name: 'time-tracking-help-state', props: { @@ -10,33 +12,39 @@ export default { href() { return `${this.rootPath}help/workflow/time_tracking.md`; }, + estimateText() { + return sprintf( + s__('estimateCommand|%{slash_command} will update the estimated time with the latest command.'), { + slash_command: '<code>/estimate</code>', + }, false, + ); + }, + spendText() { + return sprintf( + s__('spendCommand|%{slash_command} will update the sum of the time spent.'), { + slash_command: '<code>/spend</code>', + }, false, + ); + }, }, template: ` <div class="time-tracking-help-state"> <div class="time-tracking-info"> <h4> - Track time with quick actions + {{ __('Track time with quick actions') }} </h4> <p> - Quick actions can be used in the issues description and comment boxes. + {{ __('Quick actions can be used in the issues description and comment boxes.') }} </p> - <p> - <code> - /estimate - </code> - will update the estimated time with the latest command. + <p v-html="estimateText"> </p> - <p> - <code> - /spend - </code> - will update the sum of the time spent. + <p v-html="spendText"> </p> <a class="btn btn-default learn-more-button" :href="href" > - Learn more + {{ __('Learn more') }} </a> </div> </div> diff --git a/app/assets/javascripts/sidebar/components/time_tracking/no_tracking_pane.js b/app/assets/javascripts/sidebar/components/time_tracking/no_tracking_pane.js index d1dd1dcdd27..38da76c6771 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/no_tracking_pane.js +++ b/app/assets/javascripts/sidebar/components/time_tracking/no_tracking_pane.js @@ -3,7 +3,7 @@ export default { template: ` <div class="time-tracking-no-tracking-pane"> <span class="no-value"> - No estimate or time spent + {{ __('No estimate or time spent') }} </span> </div> `, diff --git a/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.js b/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.js index d32fe4abc7d..782e4ba4fad 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.js +++ b/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.js @@ -2,7 +2,7 @@ import _ from 'underscore'; import '~/smart_interval'; -import timeTracker from './time_tracker'; +import IssuableTimeTracker from './time_tracker.vue'; import Store from '../../stores/sidebar_store'; import Mediator from '../../sidebar_mediator'; @@ -16,7 +16,7 @@ export default { }; }, components: { - 'issuable-time-tracker': timeTracker, + IssuableTimeTracker, }, methods: { listenForQuickActions() { diff --git a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.js b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue index ed0d71a4f79..230736a56b8 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.js +++ b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue @@ -1,3 +1,4 @@ +<script> import timeTrackingHelpState from './help_state'; import timeTrackingCollapsedState from './collapsed_state'; import timeTrackingSpentOnlyPane from './spent_only_pane'; @@ -8,7 +9,15 @@ import timeTrackingComparisonPane from './comparison_pane'; import eventHub from '../../event_hub'; export default { - name: 'issuable-time-tracker', + name: 'IssuableTimeTracker', + components: { + 'time-tracking-collapsed-state': timeTrackingCollapsedState, + 'time-tracking-estimate-only-pane': timeTrackingEstimateOnlyPane, + 'time-tracking-spent-only-pane': timeTrackingSpentOnlyPane, + 'time-tracking-no-tracking-pane': timeTrackingNoTrackingPane, + 'time-tracking-comparison-pane': timeTrackingComparisonPane, + 'time-tracking-help-state': timeTrackingHelpState, + }, props: { time_estimate: { type: Number, @@ -38,14 +47,6 @@ export default { showHelp: false, }; }, - components: { - 'time-tracking-collapsed-state': timeTrackingCollapsedState, - 'time-tracking-estimate-only-pane': timeTrackingEstimateOnlyPane, - 'time-tracking-spent-only-pane': timeTrackingSpentOnlyPane, - 'time-tracking-no-tracking-pane': timeTrackingNoTrackingPane, - 'time-tracking-comparison-pane': timeTrackingComparisonPane, - 'time-tracking-help-state': timeTrackingHelpState, - }, computed: { timeSpent() { return this.time_spent; @@ -81,6 +82,9 @@ export default { return !!this.showHelp; }, }, + created() { + eventHub.$on('timeTracker:updateData', this.update); + }, methods: { toggleHelpState(show) { this.showHelp = show; @@ -92,72 +96,73 @@ export default { this.human_time_spent = data.human_time_spent; }, }, - created() { - eventHub.$on('timeTracker:updateData', this.update); - }, - template: ` - <div - class="time_tracker time-tracking-component-wrap" - v-cloak - > - <time-tracking-collapsed-state - :show-comparison-state="showComparisonState" - :show-no-time-tracking-state="showNoTimeTrackingState" - :show-help-state="showHelpState" - :show-spent-only-state="showSpentOnlyState" - :show-estimate-only-state="showEstimateOnlyState" +}; +</script> + +<template> + <div + class="time_tracker time-tracking-component-wrap" + v-cloak + > + <time-tracking-collapsed-state + :show-comparison-state="showComparisonState" + :show-no-time-tracking-state="showNoTimeTrackingState" + :show-help-state="showHelpState" + :show-spent-only-state="showSpentOnlyState" + :show-estimate-only-state="showEstimateOnlyState" + :time-spent-human-readable="timeSpentHumanReadable" + :time-estimate-human-readable="timeEstimateHumanReadable" + /> + <div class="title hide-collapsed"> + {{ __('Time tracking') }} + <div + class="help-button pull-right" + v-if="!showHelpState" + @click="toggleHelpState(true)" + > + <i + class="fa fa-question-circle" + aria-hidden="true" + > + </i> + </div> + <div + class="close-help-button pull-right" + v-if="showHelpState" + @click="toggleHelpState(false)" + > + <i + class="fa fa-close" + aria-hidden="true" + > + </i> + </div> + </div> + <div class="time-tracking-content hide-collapsed"> + <time-tracking-estimate-only-pane + v-if="showEstimateOnlyState" + :time-estimate-human-readable="timeEstimateHumanReadable" + /> + <time-tracking-spent-only-pane + v-if="showSpentOnlyState" + :time-spent-human-readable="timeSpentHumanReadable" + /> + <time-tracking-no-tracking-pane + v-if="showNoTimeTrackingState" + /> + <time-tracking-comparison-pane + v-if="showComparisonState" + :time-estimate="timeEstimate" + :time-spent="timeSpent" :time-spent-human-readable="timeSpentHumanReadable" :time-estimate-human-readable="timeEstimateHumanReadable" /> - <div class="title hide-collapsed"> - Time tracking - <div - class="help-button pull-right" - v-if="!showHelpState" - @click="toggleHelpState(true)" - > - <i - class="fa fa-question-circle" - aria-hidden="true" - /> - </div> - <div - class="close-help-button pull-right" + <transition name="help-state-toggle"> + <time-tracking-help-state v-if="showHelpState" - @click="toggleHelpState(false)" - > - <i - class="fa fa-close" - aria-hidden="true" - /> - </div> - </div> - <div class="time-tracking-content hide-collapsed"> - <time-tracking-estimate-only-pane - v-if="showEstimateOnlyState" - :time-estimate-human-readable="timeEstimateHumanReadable" + :root-path="rootPath" /> - <time-tracking-spent-only-pane - v-if="showSpentOnlyState" - :time-spent-human-readable="timeSpentHumanReadable" - /> - <time-tracking-no-tracking-pane - v-if="showNoTimeTrackingState" - /> - <time-tracking-comparison-pane - v-if="showComparisonState" - :time-estimate="timeEstimate" - :time-spent="timeSpent" - :time-spent-human-readable="timeSpentHumanReadable" - :time-estimate-human-readable="timeEstimateHumanReadable" - /> - <transition name="help-state-toggle"> - <time-tracking-help-state - v-if="showHelpState" - :rootPath="rootPath" - /> - </transition> - </div> + </transition> </div> - `, -}; + </div> +</template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue index 40c3cb500bb..ebaf2b972eb 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue @@ -44,7 +44,10 @@ type="button" class="btn btn-xs btn-default" > - <loading-icon v-if="isRefreshing" /> + <loading-icon + v-if="isRefreshing" + :inline="true" + /> {{ s__("mrWidget|Refresh") }} </button> </div> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.js deleted file mode 100644 index 7733fb74afe..00000000000 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.js +++ /dev/null @@ -1,43 +0,0 @@ -import statusIcon from '../mr_widget_status_icon.vue'; -import tooltip from '../../../vue_shared/directives/tooltip'; -import mrWidgetMergeHelp from '../../components/mr_widget_merge_help.vue'; - -export default { - name: 'MRWidgetMissingBranch', - props: { - mr: { type: Object, required: true }, - }, - directives: { - tooltip, - }, - components: { - 'mr-widget-merge-help': mrWidgetMergeHelp, - statusIcon, - }, - computed: { - missingBranchName() { - return this.mr.sourceBranchRemoved ? 'source' : 'target'; - }, - message() { - return `If the ${this.missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line`; - }, - }, - template: ` - <div class="mr-widget-body media"> - <status-icon status="warning" :show-disabled-button="true" /> - <div class="media-body space-children"> - <span class="bold js-branch-text"> - <span class="capitalize"> - {{missingBranchName}} - </span> branch does not exist. - Please restore it or use a different {{missingBranchName}} branch - <i - v-tooltip - class="fa fa-question-circle" - :title="message" - :aria-label="message"></i> - </span> - </div> - </div> - `, -}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue new file mode 100644 index 00000000000..718c0e4b3c6 --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue @@ -0,0 +1,62 @@ +<script> + import { sprintf, s__ } from '~/locale'; + import tooltip from '~/vue_shared/directives/tooltip'; + import statusIcon from '../mr_widget_status_icon.vue'; + import mrWidgetMergeHelp from '../../components/mr_widget_merge_help.vue'; + + export default { + name: 'MRWidgetMissingBranch', + directives: { + tooltip, + }, + components: { + mrWidgetMergeHelp, + statusIcon, + }, + props: { + mr: { + type: Object, + required: true, + }, + }, + computed: { + missingBranchName() { + return this.mr.sourceBranchRemoved ? 'source' : 'target'; + }, + missingBranchNameMessage() { + return sprintf(s__('mrWidget| Please restore it or use a different %{missingBranchName} branch'), { + missingBranchName: this.missingBranchName, + }); + }, + message() { + return sprintf(s__('mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line'), { + missingBranchName: this.missingBranchName, + }); + }, + }, + }; +</script> +<template> + <div class="mr-widget-body media"> + <status-icon + status="warning" + :show-disabled-button="true" + /> + + <div class="media-body space-children"> + <span class="bold js-branch-text"> + <span class="capitalize"> + {{ missingBranchName }} + </span> {{ s__("mrWidget|branch does not exist.") }} + {{ missingBranchNameMessage }} + <i + v-tooltip + class="fa fa-question-circle" + :title="message" + :aria-label="message" + > + </i> + </span> + </div> + </div> +</template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.js deleted file mode 100644 index cea3d97fa88..00000000000 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.js +++ /dev/null @@ -1,19 +0,0 @@ -import statusIcon from '../mr_widget_status_icon.vue'; - -export default { - name: 'MRWidgetNotAllowed', - components: { - statusIcon, - }, - template: ` - <div class="mr-widget-body media"> - <status-icon status="success" :show-disabled-button="true" /> - <div class="media-body space-children"> - <span class="bold"> - Ready to be merged automatically. - Ask someone with write access to this repository to merge this request - </span> - </div> - </div> - `, -}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.vue new file mode 100644 index 00000000000..e4af50b09f8 --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.vue @@ -0,0 +1,25 @@ +<script> + import StatusIcon from '../mr_widget_status_icon.vue'; + + export default { + name: 'MRWidgetNotAllowed', + components: { + StatusIcon, + }, + }; +</script> + +<template> + <div class="mr-widget-body media"> + <status-icon + status="success" + :show-disabled-button="true" + /> + <div class="media-body space-children"> + <span class="bold"> + {{ s__(`mrWidget|Ready to be merged automatically. +Ask someone with write access to this repository to merge this request`) }} + </span> + </div> + </div> +</template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.js deleted file mode 100644 index e66ce071ab4..00000000000 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.js +++ /dev/null @@ -1,18 +0,0 @@ -import statusIcon from '../mr_widget_status_icon.vue'; - -export default { - name: 'MRWidgetPipelineBlocked', - components: { - statusIcon, - }, - template: ` - <div class="mr-widget-body media"> - <status-icon status="warning" :show-disabled-button="true" /> - <div class="media-body space-children"> - <span class="bold"> - Pipeline blocked. The pipeline for this merge request requires a manual action to proceed - </span> - </div> - </div> - `, -}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue new file mode 100644 index 00000000000..6d7cc03f7ad --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue @@ -0,0 +1,24 @@ +<script> + import StatusIcon from '../mr_widget_status_icon.vue'; + + export default { + name: 'MRWidgetPipelineBlocked', + components: { + StatusIcon, + }, + }; +</script> +<template> + <div class="mr-widget-body media"> + <status-icon + status="warning" + :show-disabled-button="true" + /> + <div class="media-body space-children"> + <span class="bold"> + {{ s__(`mrWidget|Pipeline blocked. +The pipeline for this merge request requires a manual action to proceed`) }} + </span> + </div> + </div> +</template> diff --git a/app/assets/javascripts/vue_merge_request_widget/dependencies.js b/app/assets/javascripts/vue_merge_request_widget/dependencies.js index 7ca15537719..edb3baa39e4 100644 --- a/app/assets/javascripts/vue_merge_request_widget/dependencies.js +++ b/app/assets/javascripts/vue_merge_request_widget/dependencies.js @@ -24,12 +24,12 @@ export { default as WipState } from './components/states/mr_widget_wip'; export { default as ArchivedState } from './components/states/mr_widget_archived.vue'; export { default as ConflictsState } from './components/states/mr_widget_conflicts.vue'; export { default as NothingToMergeState } from './components/states/mr_widget_nothing_to_merge'; -export { default as MissingBranchState } from './components/states/mr_widget_missing_branch'; -export { default as NotAllowedState } from './components/states/mr_widget_not_allowed'; +export { default as MissingBranchState } from './components/states/mr_widget_missing_branch.vue'; +export { default as NotAllowedState } from './components/states/mr_widget_not_allowed.vue'; export { default as ReadyToMergeState } from './components/states/mr_widget_ready_to_merge'; export { default as SHAMismatchState } from './components/states/mr_widget_sha_mismatch'; export { default as UnresolvedDiscussionsState } from './components/states/mr_widget_unresolved_discussions'; -export { default as PipelineBlockedState } from './components/states/mr_widget_pipeline_blocked'; +export { default as PipelineBlockedState } from './components/states/mr_widget_pipeline_blocked.vue'; export { default as PipelineFailedState } from './components/states/mr_widget_pipeline_failed'; export { default as MergeWhenPipelineSucceedsState } from './components/states/mr_widget_merge_when_pipeline_succeeds.vue'; export { default as RebaseState } from './components/states/mr_widget_rebase.vue'; diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js index edf67fcd0a7..d8f0442ef9d 100644 --- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js +++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js @@ -152,6 +152,7 @@ export default { }, handleNotification(data) { if (data.ci_status === this.mr.ciStatus) return; + if (!data.pipeline) return; const label = data.pipeline.details.status.label; const title = `Pipeline ${label}`; diff --git a/app/assets/javascripts/vue_shared/components/confirmation_input.vue b/app/assets/javascripts/vue_shared/components/confirmation_input.vue deleted file mode 100644 index 1aa03ea6317..00000000000 --- a/app/assets/javascripts/vue_shared/components/confirmation_input.vue +++ /dev/null @@ -1,62 +0,0 @@ -<script> - import _ from 'underscore'; - import { __, sprintf } from '~/locale'; - - export default { - props: { - inputId: { - type: String, - required: true, - }, - confirmationKey: { - type: String, - required: true, - }, - confirmationValue: { - type: String, - required: true, - }, - shouldEscapeConfirmationValue: { - type: Boolean, - required: false, - default: true, - }, - }, - computed: { - inputLabel() { - let value = this.confirmationValue; - if (this.shouldEscapeConfirmationValue) { - value = _.escape(value); - } - - return sprintf( - __('Type %{value} to confirm:'), - { value: `<code>${value}</code>` }, - false, - ); - }, - }, - methods: { - hasCorrectValue() { - return this.$refs.enteredValue.value === this.confirmationValue; - }, - }, - }; -</script> - -<template> - <div> - <label - v-html="inputLabel" - :for="inputId" - > - </label> - <input - :id="inputId" - :name="confirmationKey" - type="text" - ref="enteredValue" - class="form-control" - /> - </div> -</template> diff --git a/app/assets/javascripts/vue_shared/components/gl_modal.vue b/app/assets/javascripts/vue_shared/components/gl_modal.vue new file mode 100644 index 00000000000..67c9181c7b1 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/gl_modal.vue @@ -0,0 +1,106 @@ +<script> + const buttonVariants = [ + 'danger', + 'primary', + 'success', + 'warning', + ]; + + export default { + name: 'GlModal', + + props: { + id: { + type: String, + required: false, + default: null, + }, + headerTitleText: { + type: String, + required: false, + default: '', + }, + footerPrimaryButtonVariant: { + type: String, + required: false, + default: 'primary', + validator: value => buttonVariants.indexOf(value) !== -1, + }, + footerPrimaryButtonText: { + type: String, + required: false, + default: '', + }, + }, + + methods: { + emitCancel(event) { + this.$emit('cancel', event); + }, + emitSubmit(event) { + this.$emit('submit', event); + }, + }, + }; +</script> + +<template> + <div + :id="id" + class="modal fade" + tabindex="-1" + role="dialog" + > + <div + class="modal-dialog" + role="document" + > + <div class="modal-content"> + <div class="modal-header"> + <slot name="header"> + <button + type="button" + class="close" + data-dismiss="modal" + :aria-label="s__('Modal|Close')" + @click="emitCancel($event)" + > + <span aria-hidden="true">×</span> + </button> + <h4 class="modal-title"> + <slot name="title"> + {{ headerTitleText }} + </slot> + </h4> + </slot> + </div> + + <div class="modal-body"> + <slot></slot> + </div> + + <div class="modal-footer"> + <slot name="footer"> + <button + type="button" + class="btn" + data-dismiss="modal" + @click="emitCancel($event)" + > + {{ s__('Modal|Cancel') }} + </button> + <button + type="button" + class="btn" + :class="`btn-${footerPrimaryButtonVariant}`" + data-dismiss="modal" + @click="emitSubmit($event)" + > + {{ footerPrimaryButtonText }} + </button> + </slot> + </div> + </div> + </div> + </div> +</template> diff --git a/app/assets/javascripts/vue_shared/components/loading_button.vue b/app/assets/javascripts/vue_shared/components/loading_button.vue index ff8c0f7c1d2..6ae6b179f7f 100644 --- a/app/assets/javascripts/vue_shared/components/loading_button.vue +++ b/app/assets/javascripts/vue_shared/components/loading_button.vue @@ -40,7 +40,7 @@ required: false, }, containerClass: { - type: String, + type: [String, Array, Object], required: false, default: 'btn btn-align-content', }, diff --git a/app/assets/javascripts/vue_shared/components/modal.vue b/app/assets/javascripts/vue_shared/components/modal.vue index 8227428d8ba..5f1364421aa 100644 --- a/app/assets/javascripts/vue_shared/components/modal.vue +++ b/app/assets/javascripts/vue_shared/components/modal.vue @@ -46,6 +46,11 @@ required: false, default: '', }, + secondaryButtonLabel: { + type: String, + required: false, + default: '', + }, submitDisabled: { type: Boolean, required: false, @@ -129,6 +134,21 @@ > {{ closeButtonLabel }} </button> + + <slot + v-if="secondaryButtonLabel" + name="secondary-button" + > + <button + v-if="secondaryButtonLabel" + type="button" + class="btn" + data-dismiss="modal" + > + {{ secondaryButtonLabel }} + </button> + </slot> + <button v-if="primaryButtonLabel" type="button" diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss index 887879ab715..2fccfa4011c 100644 --- a/app/assets/stylesheets/framework.scss +++ b/app/assets/stylesheets/framework.scss @@ -21,7 +21,7 @@ @import "framework/flash"; @import "framework/forms"; @import "framework/gfm"; -@import "framework/gitlab-theme"; +@import "framework/gitlab_theme"; @import "framework/header"; @import "framework/highlight"; @import "framework/issue_box"; @@ -35,10 +35,10 @@ @import "framework/pagination"; @import "framework/panels"; @import "framework/popup"; -@import "framework/secondary-navigation-elements"; +@import "framework/secondary_navigation_elements"; @import "framework/selects"; @import "framework/sidebar"; -@import "framework/contextual-sidebar"; +@import "framework/contextual_sidebar"; @import "framework/tables"; @import "framework/notes"; @import "framework/tabs"; @@ -49,16 +49,16 @@ @import "framework/zen"; @import "framework/blank"; @import "framework/wells"; -@import "framework/page-header"; +@import "framework/page_header"; @import "framework/awards"; @import "framework/images"; -@import "framework/broadcast-messages"; +@import "framework/broadcast_messages"; @import "framework/emojis"; -@import "framework/emoji-sprites"; +@import "framework/emoji_sprites"; @import "framework/icons"; @import "framework/snippets"; @import "framework/memory_graph"; @import "framework/responsive_tables"; -@import "framework/stacked-progress-bar"; +@import "framework/stacked_progress_bar"; @import "framework/ci_variable_list"; @import "framework/feature_highlight"; diff --git a/app/assets/stylesheets/framework/broadcast-messages.scss b/app/assets/stylesheets/framework/broadcast_messages.scss index 9b54fb94cdc..9b54fb94cdc 100644 --- a/app/assets/stylesheets/framework/broadcast-messages.scss +++ b/app/assets/stylesheets/framework/broadcast_messages.scss diff --git a/app/assets/stylesheets/framework/ci_variable_list.scss b/app/assets/stylesheets/framework/ci_variable_list.scss index 8f654ab363c..5fe835dd8f9 100644 --- a/app/assets/stylesheets/framework/ci_variable_list.scss +++ b/app/assets/stylesheets/framework/ci_variable_list.scss @@ -8,7 +8,11 @@ .ci-variable-row { display: flex; - align-items: flex-end; + align-items: flex-start; + + @media (max-width: $screen-xs-max) { + align-items: flex-end; + } &:not(:last-child) { margin-bottom: $gl-btn-padding; @@ -41,6 +45,7 @@ .ci-variable-row-body { display: flex; + align-items: flex-start; width: 100%; @media (max-width: $screen-xs-max) { @@ -65,6 +70,8 @@ flex: 0 1 auto; display: flex; align-items: center; + padding-top: 5px; + padding-bottom: 5px; } .ci-variable-row-remove-button { @@ -85,4 +92,8 @@ outline: none; color: $gl-text-color; } + + &[disabled] { + color: $gl-text-color-disabled; + } } diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 73524d5cf60..ae517c41cb2 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -449,9 +449,11 @@ img.emoji { .prepend-top-10 { margin-top: 10px; } .prepend-top-15 { margin-top: 15px; } .prepend-top-default { margin-top: $gl-padding !important; } +.prepend-top-16 { margin-top: 16px; } .prepend-top-20 { margin-top: 20px; } .prepend-left-4 { margin-left: 4px; } .prepend-left-5 { margin-left: 5px; } +.prepend-left-8 { margin-left: 8px; } .prepend-left-10 { margin-left: 10px; } .prepend-left-default { margin-left: $gl-padding; } .prepend-left-20 { margin-left: 20px; } diff --git a/app/assets/stylesheets/framework/contextual-sidebar.scss b/app/assets/stylesheets/framework/contextual_sidebar.scss index 1acde98c3ae..1acde98c3ae 100644 --- a/app/assets/stylesheets/framework/contextual-sidebar.scss +++ b/app/assets/stylesheets/framework/contextual_sidebar.scss diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 691df098c70..1d7b0b602cc 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -736,10 +736,6 @@ } } -.droplab-item-ignore { - pointer-events: none; -} - .pika-single.animate-picker.is-bound, .pika-single.animate-picker.is-bound.is-hidden { /* diff --git a/app/assets/stylesheets/framework/emoji-sprites.scss b/app/assets/stylesheets/framework/emoji_sprites.scss index 0174e17b660..0174e17b660 100644 --- a/app/assets/stylesheets/framework/emoji-sprites.scss +++ b/app/assets/stylesheets/framework/emoji_sprites.scss diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index be96c8ee964..a2ea155a10e 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -182,6 +182,7 @@ label { .help-block { margin-bottom: 0; + margin-top: #{$grid-size / 2}; } .gl-field-error { diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab_theme.scss index db36e27fa74..db36e27fa74 100644 --- a/app/assets/stylesheets/framework/gitlab-theme.scss +++ b/app/assets/stylesheets/framework/gitlab_theme.scss diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index dcd98cb522f..7e829826eba 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -255,8 +255,6 @@ ul.controls { } .author_link { - display: inline-block; - .avatar-inline { margin-left: 0; margin-right: 0; diff --git a/app/assets/stylesheets/framework/page-header.scss b/app/assets/stylesheets/framework/page_header.scss index 0c879f40930..0c879f40930 100644 --- a/app/assets/stylesheets/framework/page-header.scss +++ b/app/assets/stylesheets/framework/page_header.scss diff --git a/app/assets/stylesheets/framework/secondary-navigation-elements.scss b/app/assets/stylesheets/framework/secondary_navigation_elements.scss index 17c31d6b184..17c31d6b184 100644 --- a/app/assets/stylesheets/framework/secondary-navigation-elements.scss +++ b/app/assets/stylesheets/framework/secondary_navigation_elements.scss diff --git a/app/assets/stylesheets/framework/stacked-progress-bar.scss b/app/assets/stylesheets/framework/stacked_progress_bar.scss index 4869cda73e5..4869cda73e5 100644 --- a/app/assets/stylesheets/framework/stacked-progress-bar.scss +++ b/app/assets/stylesheets/framework/stacked_progress_bar.scss diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 0d21a9f5f77..25ee081ea9c 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -214,6 +214,7 @@ $tooltip-font-size: 12px; * Padding */ $gl-padding: 16px; +$gl-padding-8: 8px; $gl-col-padding: 15px; $gl-btn-padding: 10px; $gl-input-padding: 10px; diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index 794bc668562..884665d35c7 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -121,6 +121,10 @@ width: 100%; text-align: left; } + + .environment-child-row { + padding-left: 20px; + } } } @@ -205,7 +209,7 @@ } .prometheus-state { - max-width: 430px; + max-width: 460px; margin: 10px auto; text-align: center; @@ -213,6 +217,10 @@ max-width: 80vw; margin: 0 auto; } + + .state-button { + padding: $gl-padding / 2; + } } .environments-actions { diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 759719a72da..3fe95b34e01 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -197,11 +197,18 @@ margin-left: 0; } + a.edit-link:not([href]):hover { + color: rgba($avatar-border, .2); + } + + .lock-edit, // uses same style, different js behaviour .edit-link { + @extend .btn-blank; color: $gl-text-color; - &:not([href]):hover { - color: rgba($avatar-border, .2); + &:hover { + text-decoration: underline; + color: $md-link-color; } } } diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index c48e58af691..6763af4e98b 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -181,11 +181,6 @@ ul.related-merge-requests > li { } .create-mr-dropdown-wrap { - .branch-message, - .ref-message { - display: none; - } - .ref::selection { color: $placeholder-text-color; } @@ -216,6 +211,17 @@ ul.related-merge-requests > li { transform: translateY(0); display: none; margin-top: 4px; + + // override dropdown item styles + .btn.btn-success { + @include btn-default; + @include btn-green; + + border-style: solid; + border-width: 1px; + line-height: $line-height-base; + width: auto; + } } .create-merge-request-dropdown-toggle { @@ -225,66 +231,6 @@ ul.related-merge-requests > li { margin-left: 0; } } - - .droplab-item-ignore { - pointer-events: auto; - } - - .create-item { - cursor: pointer; - margin: 0 1px; - - &:hover, - &:focus { - background-color: $dropdown-item-hover-bg; - color: $gl-text-color; - } - } - - li.divider { - margin: 8px 10px; - } - - li:not(.divider) { - padding: 8px 9px; - - &:last-child { - padding-bottom: 8px; - } - - &.droplab-item-selected { - .icon-container { - i { - visibility: visible; - } - } - - .description { - display: block; - } - } - - &.droplab-item-ignore { - padding-top: 8px; - } - - .icon-container { - float: left; - - i { - visibility: hidden; - } - } - - .description { - padding-left: 22px; - } - - input, - span { - margin: 4px 0 0; - } - } } .discussion-reply-holder .note-edit-form { diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss index 6353482ede7..47672783d5a 100644 --- a/app/assets/stylesheets/pages/settings.scss +++ b/app/assets/stylesheets/pages/settings.scss @@ -135,6 +135,17 @@ padding-top: 0; } +.integration-settings-form { + .well { + padding: $gl-padding / 2; + box-shadow: none; + } + + .svg-container { + max-width: 150px; + } +} + .token-token-container { #impersonation-token-token { width: 80%; diff --git a/app/controllers/ci/lints_controller.rb b/app/controllers/ci/lints_controller.rb index be667687c18..e9bd1689a1e 100644 --- a/app/controllers/ci/lints_controller.rb +++ b/app/controllers/ci/lints_controller.rb @@ -16,10 +16,7 @@ module Ci @builds = @config_processor.builds @jobs = @config_processor.jobs end - rescue - @error = 'Undefined error' - @status = false - ensure + render :show end end diff --git a/app/controllers/concerns/lfs_request.rb b/app/controllers/concerns/lfs_request.rb index 4311f9d4db9..5e4e8a87153 100644 --- a/app/controllers/concerns/lfs_request.rb +++ b/app/controllers/concerns/lfs_request.rb @@ -10,6 +10,8 @@ module LfsRequest extend ActiveSupport::Concern + CONTENT_TYPE = 'application/vnd.git-lfs+json'.freeze + included do before_action :require_lfs_enabled! before_action :lfs_check_access! @@ -50,7 +52,7 @@ module LfsRequest message: 'Access forbidden. Check your access level.', documentation_url: help_url }, - content_type: "application/vnd.git-lfs+json", + content_type: CONTENT_TYPE, status: 403 ) end @@ -61,7 +63,7 @@ module LfsRequest message: 'Not found.', documentation_url: help_url }, - content_type: "application/vnd.git-lfs+json", + content_type: CONTENT_TYPE, status: 404 ) end diff --git a/app/controllers/concerns/service_params.rb b/app/controllers/concerns/service_params.rb index 3d61458c064..c1acb50b76c 100644 --- a/app/controllers/concerns/service_params.rb +++ b/app/controllers/concerns/service_params.rb @@ -32,6 +32,7 @@ module ServiceParams :issues_events, :issues_url, :jira_issue_transition_id, + :manual_configuration, :merge_requests_events, :mock_service_url, :namespace, diff --git a/app/controllers/groups/variables_controller.rb b/app/controllers/groups/variables_controller.rb index 10038ff3ad9..913e13bf734 100644 --- a/app/controllers/groups/variables_controller.rb +++ b/app/controllers/groups/variables_controller.rb @@ -1,60 +1,43 @@ module Groups class VariablesController < Groups::ApplicationController - before_action :variable, only: [:show, :update, :destroy] before_action :authorize_admin_build! - def index - redirect_to group_settings_ci_cd_path(group) - end - def show + respond_to do |format| + format.json do + render status: :ok, json: { variables: GroupVariableSerializer.new.represent(@group.variables) } + end + end end def update - if variable.update(variable_params) - redirect_to group_variables_path(group), - notice: 'Variable was successfully updated.' + if @group.update(group_variables_params) + respond_to do |format| + format.json { return render_group_variables } + end else - render "show" + respond_to do |format| + format.json { render_error } + end end end - def create - @variable = group.variables.create(variable_params) - .present(current_user: current_user) + private - if @variable.persisted? - redirect_to group_settings_ci_cd_path(group), - notice: 'Variable was successfully created.' - else - render "show" - end + def render_group_variables + render status: :ok, json: { variables: GroupVariableSerializer.new.represent(@group.variables) } end - def destroy - if variable.destroy - redirect_to group_settings_ci_cd_path(group), - status: 302, - notice: 'Variable was successfully removed.' - else - redirect_to group_settings_ci_cd_path(group), - status: 302, - notice: 'Failed to remove the variable.' - end + def render_error + render status: :bad_request, json: @group.errors.full_messages end - private - - def variable_params - params.require(:variable).permit(*variable_params_attributes) + def group_variables_params + params.permit(variables_attributes: [*variable_params_attributes]) end def variable_params_attributes - %i[key value protected] - end - - def variable - @variable ||= group.variables.find(params[:id]).present(current_user: current_user) + %i[id key value protected _destroy] end def authorize_admin_build! diff --git a/app/controllers/import/base_controller.rb b/app/controllers/import/base_controller.rb index 9de0297ecfd..c84fc2d305d 100644 --- a/app/controllers/import/base_controller.rb +++ b/app/controllers/import/base_controller.rb @@ -2,26 +2,16 @@ class Import::BaseController < ApplicationController private def find_or_create_namespace(names, owner) - return current_user.namespace if names == owner - return current_user.namespace unless current_user.can_create_group? - names = params[:target_namespace].presence || names - full_path_namespace = Namespace.find_by_full_path(names) - return full_path_namespace if full_path_namespace + return current_user.namespace if names == owner + + group = Groups::NestedCreateService.new(current_user, group_path: names).execute - names.split('/').inject(nil) do |parent, name| - begin - namespace = Group.create!(name: name, - path: name, - owner: current_user, - parent: parent) - namespace.add_owner(current_user) + group.errors.any? ? current_user.namespace : group + rescue => e + Gitlab::AppLogger.error(e) - namespace - rescue ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid - Namespace.where(parent: parent).find_by_path_or_name(name) - end - end + current_user.namespace end end diff --git a/app/controllers/import/bitbucket_controller.rb b/app/controllers/import/bitbucket_controller.rb index 5ad1e116e4e..13ea736688d 100644 --- a/app/controllers/import/bitbucket_controller.rb +++ b/app/controllers/import/bitbucket_controller.rb @@ -37,24 +37,30 @@ class Import::BitbucketController < Import::BaseController def create bitbucket_client = Bitbucket::Client.new(credentials) - @repo_id = params[:repo_id].to_s - name = @repo_id.gsub('___', '/') + repo_id = params[:repo_id].to_s + name = repo_id.gsub('___', '/') repo = bitbucket_client.repo(name) - @project_name = params[:new_name].presence || repo.name + project_name = params[:new_name].presence || repo.name repo_owner = repo.owner repo_owner = current_user.username if repo_owner == bitbucket_client.user.username namespace_path = params[:new_namespace].presence || repo_owner + target_namespace = find_or_create_namespace(namespace_path, current_user) - @target_namespace = find_or_create_namespace(namespace_path, current_user) - - if current_user.can?(:create_projects, @target_namespace) + if current_user.can?(:create_projects, target_namespace) # The token in a session can be expired, we need to get most recent one because # Bitbucket::Connection class refreshes it. session[:bitbucket_token] = bitbucket_client.connection.token - @project = Gitlab::BitbucketImport::ProjectCreator.new(repo, @project_name, @target_namespace, current_user, credentials).execute + + project = Gitlab::BitbucketImport::ProjectCreator.new(repo, project_name, target_namespace, current_user, credentials).execute + + if project.persisted? + render json: ProjectSerializer.new.represent(project) + else + render json: { errors: project.errors.full_messages }, status: :unprocessable_entity + end else - render 'unauthorized' + render json: { errors: 'This namespace has already been taken! Please choose another one.' }, status: :unprocessable_entity end end diff --git a/app/controllers/import/fogbugz_controller.rb b/app/controllers/import/fogbugz_controller.rb index 5df6bd34185..669eb31a995 100644 --- a/app/controllers/import/fogbugz_controller.rb +++ b/app/controllers/import/fogbugz_controller.rb @@ -58,17 +58,17 @@ class Import::FogbugzController < Import::BaseController end def create - @repo_id = params[:repo_id] - repo = client.repo(@repo_id) + repo = client.repo(params[:repo_id]) fb_session = { uri: session[:fogbugz_uri], token: session[:fogbugz_token] } - @target_namespace = current_user.namespace - @project_name = repo.name - - namespace = @target_namespace - umap = session[:fogbugz_user_map] || client.user_map - @project = Gitlab::FogbugzImport::ProjectCreator.new(repo, fb_session, namespace, current_user, umap).execute + project = Gitlab::FogbugzImport::ProjectCreator.new(repo, fb_session, current_user.namespace, current_user, umap).execute + + if project.persisted? + render json: ProjectSerializer.new.represent(project) + else + render json: { errors: project.errors.full_messages }, status: :unprocessable_entity + end end private diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb index b8ba7921613..69fb8121ded 100644 --- a/app/controllers/import/github_controller.rb +++ b/app/controllers/import/github_controller.rb @@ -36,16 +36,21 @@ class Import::GithubController < Import::BaseController end def create - @repo_id = params[:repo_id].to_i - repo = client.repo(@repo_id) - @project_name = params[:new_name].presence || repo.name + repo = client.repo(params[:repo_id].to_i) + project_name = params[:new_name].presence || repo.name namespace_path = params[:target_namespace].presence || current_user.namespace_path - @target_namespace = find_or_create_namespace(namespace_path, current_user.namespace_path) + target_namespace = find_or_create_namespace(namespace_path, current_user.namespace_path) - if can?(current_user, :create_projects, @target_namespace) - @project = Gitlab::LegacyGithubImport::ProjectCreator.new(repo, @project_name, @target_namespace, current_user, access_params, type: provider).execute + if can?(current_user, :create_projects, target_namespace) + project = Gitlab::LegacyGithubImport::ProjectCreator.new(repo, project_name, target_namespace, current_user, access_params, type: provider).execute + + if project.persisted? + render json: ProjectSerializer.new.represent(project) + else + render json: { errors: project.errors.full_messages }, status: :unprocessable_entity + end else - render 'unauthorized' + render json: { errors: 'This namespace has already been taken! Please choose another one.' }, status: :unprocessable_entity end end diff --git a/app/controllers/import/gitlab_controller.rb b/app/controllers/import/gitlab_controller.rb index 407154e59a0..18f1d20f5a9 100644 --- a/app/controllers/import/gitlab_controller.rb +++ b/app/controllers/import/gitlab_controller.rb @@ -24,15 +24,19 @@ class Import::GitlabController < Import::BaseController end def create - @repo_id = params[:repo_id].to_i - repo = client.project(@repo_id) - @project_name = repo['name'] - @target_namespace = find_or_create_namespace(repo['namespace']['path'], client.user['username']) + repo = client.project(params[:repo_id].to_i) + target_namespace = find_or_create_namespace(repo['namespace']['path'], client.user['username']) - if current_user.can?(:create_projects, @target_namespace) - @project = Gitlab::GitlabImport::ProjectCreator.new(repo, @target_namespace, current_user, access_params).execute + if current_user.can?(:create_projects, target_namespace) + project = Gitlab::GitlabImport::ProjectCreator.new(repo, target_namespace, current_user, access_params).execute + + if project.persisted? + render json: ProjectSerializer.new.represent(project) + else + render json: { errors: project.errors.full_messages }, status: :unprocessable_entity + end else - render 'unauthorized' + render json: { errors: 'This namespace has already been taken! Please choose another one.' }, status: :unprocessable_entity end end diff --git a/app/controllers/import/google_code_controller.rb b/app/controllers/import/google_code_controller.rb index 7d7f13ce5d5..baa19fb383d 100644 --- a/app/controllers/import/google_code_controller.rb +++ b/app/controllers/import/google_code_controller.rb @@ -85,16 +85,16 @@ class Import::GoogleCodeController < Import::BaseController end def create - @repo_id = params[:repo_id] - repo = client.repo(@repo_id) - @target_namespace = current_user.namespace - @project_name = repo.name - - namespace = @target_namespace - + repo = client.repo(params[:repo_id]) user_map = session[:google_code_user_map] - @project = Gitlab::GoogleCodeImport::ProjectCreator.new(repo, namespace, current_user, user_map).execute + project = Gitlab::GoogleCodeImport::ProjectCreator.new(repo, current_user.namespace, current_user, user_map).execute + + if project.persisted? + render json: ProjectSerializer.new.represent(project) + else + render json: { errors: project.errors.full_messages }, status: :unprocessable_entity + end end private diff --git a/app/controllers/projects/clusters/gcp_controller.rb b/app/controllers/projects/clusters/gcp_controller.rb index 94d33b91562..0f41af7d87b 100644 --- a/app/controllers/projects/clusters/gcp_controller.rb +++ b/app/controllers/projects/clusters/gcp_controller.rb @@ -39,12 +39,12 @@ class Projects::Clusters::GcpController < Projects::ApplicationController def verify_billing case google_project_billing_status - when 'true' - return - when 'false' - flash[:alert] = _('Please <a href=%{link_to_billing} target="_blank" rel="noopener noreferrer">enable billing for one of your projects to be able to create a Kubernetes cluster</a>, then try again.').html_safe % { link_to_billing: "https://console.cloud.google.com/freetrial?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral" } - else + when nil flash[:alert] = _('We could not verify that one of your projects on GCP has billing enabled. Please try again.') + when false + flash[:alert] = _('Please <a href=%{link_to_billing} target="_blank" rel="noopener noreferrer">enable billing for one of your projects to be able to create a Kubernetes cluster</a>, then try again.').html_safe % { link_to_billing: "https://console.cloud.google.com/freetrial?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral" } + when true + return end @cluster = ::Clusters::Cluster.new(create_params) @@ -81,9 +81,7 @@ class Projects::Clusters::GcpController < Projects::ApplicationController end def google_project_billing_status - Gitlab::Redis::SharedState.with do |redis| - redis.get(CheckGcpProjectBillingWorker.redis_shared_state_key_for(token_in_session)) - end + CheckGcpProjectBillingWorker.get_billing_state(token_in_session) end def token_in_session diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 515cb08f1fc..33fced99132 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -122,8 +122,7 @@ class Projects::IssuesController < Projects::ApplicationController end def referenced_merge_requests - @merge_requests = @issue.referenced_merge_requests(current_user) - @closed_by_merge_requests = @issue.closed_by_merge_requests(current_user) + @merge_requests, @closed_by_merge_requests = ::Issues::FetchReferencedMergeRequestsService.new(project, current_user).execute(issue) respond_to do |format| format.json do diff --git a/app/controllers/projects/lfs_api_controller.rb b/app/controllers/projects/lfs_api_controller.rb index 536f908d2c5..c77f10ef1dd 100644 --- a/app/controllers/projects/lfs_api_controller.rb +++ b/app/controllers/projects/lfs_api_controller.rb @@ -98,7 +98,7 @@ class Projects::LfsApiController < Projects::GitHttpClientController json: { message: lfs_read_only_message }, - content_type: 'application/vnd.git-lfs+json', + content_type: LfsRequest::CONTENT_TYPE, status: 403 ) end diff --git a/app/controllers/projects/lfs_locks_api_controller.rb b/app/controllers/projects/lfs_locks_api_controller.rb new file mode 100644 index 00000000000..3fff0fd69ae --- /dev/null +++ b/app/controllers/projects/lfs_locks_api_controller.rb @@ -0,0 +1,70 @@ +class Projects::LfsLocksApiController < Projects::GitHttpClientController + include LfsRequest + + def create + @result = Lfs::LockFileService.new(project, user, params).execute + + render_json(@result[:lock]) + end + + def unlock + @result = Lfs::UnlockFileService.new(project, user, params).execute + + render_json(@result[:lock]) + end + + def index + @result = Lfs::LocksFinderService.new(project, user, params).execute + + render_json(@result[:locks]) + end + + def verify + @result = Lfs::LocksFinderService.new(project, user, {}).execute + + ours, theirs = split_by_owner(@result[:locks]) + + render_json({ ours: ours, theirs: theirs }, false) + end + + private + + def render_json(data, process = true) + render json: build_payload(data, process), + content_type: LfsRequest::CONTENT_TYPE, + status: @result[:http_status] + end + + def build_payload(data, process) + data = LfsFileLockSerializer.new.represent(data) if process + + return data if @result[:status] == :success + + # When the locking failed due to an existent Lock, the existent record + # is returned in `@result[:lock]` + error_payload(@result[:message], @result[:lock] ? data : {}) + end + + def error_payload(message, custom_attrs = {}) + custom_attrs.merge({ + message: message, + documentation_url: help_url + }) + end + + def split_by_owner(locks) + groups = locks.partition { |lock| lock.user_id == user.id } + + groups.map! do |records| + LfsFileLockSerializer.new.represent(records, root: false) + end + end + + def download_request? + params[:action] == 'index' + end + + def upload_request? + %w(create unlock verify).include?(params[:action]) + end +end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 8af4e379f0a..8eed957d9fe 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -50,10 +50,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo set_pipeline_variables - # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37432 - Gitlab::GitalyClient.allow_n_plus_1_calls do - render - end + render end format.json do diff --git a/app/controllers/projects/variables_controller.rb b/app/controllers/projects/variables_controller.rb index 6a825137564..7eb509e2e64 100644 --- a/app/controllers/projects/variables_controller.rb +++ b/app/controllers/projects/variables_controller.rb @@ -1,60 +1,41 @@ class Projects::VariablesController < Projects::ApplicationController - before_action :variable, only: [:show, :update, :destroy] before_action :authorize_admin_build! - layout 'project_settings' - - def index - redirect_to project_settings_ci_cd_path(@project) - end - def show + respond_to do |format| + format.json do + render status: :ok, json: { variables: VariableSerializer.new.represent(@project.variables) } + end + end end def update - if variable.update(variable_params) - redirect_to project_variables_path(project), - notice: 'Variable was successfully updated.' + if @project.update(variables_params) + respond_to do |format| + format.json { return render_variables } + end else - render "show" + respond_to do |format| + format.json { render_error } + end end end - def create - @variable = project.variables.create(variable_params) - .present(current_user: current_user) + private - if @variable.persisted? - redirect_to project_settings_ci_cd_path(project), - notice: 'Variable was successfully created.' - else - render "show" - end + def render_variables + render status: :ok, json: { variables: VariableSerializer.new.represent(@project.variables) } end - def destroy - if variable.destroy - redirect_to project_settings_ci_cd_path(project), - status: 302, - notice: 'Variable was successfully removed.' - else - redirect_to project_settings_ci_cd_path(project), - status: 302, - notice: 'Failed to remove the variable.' - end + def render_error + render status: :bad_request, json: @project.errors.full_messages end - private - - def variable_params - params.require(:variable).permit(*variable_params_attributes) + def variables_params + params.permit(variables_attributes: [*variable_params_attributes]) end def variable_params_attributes %i[id key value protected _destroy] end - - def variable - @variable ||= project.variables.find(params[:id]).present(current_user: current_user) - end end diff --git a/app/controllers/root_controller.rb b/app/controllers/root_controller.rb index 8acefd58e77..63e5fdb1da5 100644 --- a/app/controllers/root_controller.rb +++ b/app/controllers/root_controller.rb @@ -13,10 +13,7 @@ class RootController < Dashboard::ProjectsController before_action :redirect_logged_user, if: -> { current_user.present? } def index - # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37434 - Gitlab::GitalyClient.allow_n_plus_1_calls do - super - end + super end private diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb index 98831f5be4a..83245aadf6e 100644 --- a/app/finders/issues_finder.rb +++ b/app/finders/issues_finder.rb @@ -21,7 +21,7 @@ class IssuesFinder < IssuableFinder CONFIDENTIAL_ACCESS_LEVEL = Gitlab::Access::REPORTER def klass - Issue + Issue.includes(:author) end def with_confidentiality_access_check diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb index 12157818bcd..33ee1e975b9 100644 --- a/app/finders/notes_finder.rb +++ b/app/finders/notes_finder.rb @@ -57,7 +57,7 @@ class NotesFinder types = %w(commit issue merge_request snippet) note_relations = types.map { |t| notes_for_type(t) } note_relations.map! { |notes| search(notes) } - UnionFinder.new.find_union(note_relations, Note) + UnionFinder.new.find_union(note_relations, Note.includes(:author)) end def noteables_for_type(noteable_type) diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb index c04f61de79c..33359fa1efb 100644 --- a/app/finders/snippets_finder.rb +++ b/app/finders/snippets_finder.rb @@ -1,14 +1,28 @@ +# Snippets Finder +# +# Used to filter Snippets collections by a set of params +# +# Arguments. +# +# current_user - The current user, nil also can be used. +# params: +# visibility (integer) - Individual snippet visibility: Public(20), internal(10) or private(0). +# project (Project) - Project related. +# author (User) - Author related. +# +# params are optional class SnippetsFinder < UnionFinder - attr_accessor :current_user, :params + include Gitlab::Allowable + attr_accessor :current_user, :params, :project def initialize(current_user, params = {}) @current_user = current_user @params = params + @project = params[:project] end def execute items = init_collection - items = by_project(items) items = by_author(items) items = by_visibility(items) @@ -18,25 +32,42 @@ class SnippetsFinder < UnionFinder private def init_collection - items = Snippet.all + if project.present? + authorized_snippets_from_project + else + authorized_snippets + end + end - accessible(items) + def authorized_snippets_from_project + if can?(current_user, :read_project_snippet, project) + if project.team.member?(current_user) + project.snippets + else + project.snippets.public_to_user(current_user) + end + else + Snippet.none + end end - def accessible(items) - segments = [] - segments << items.public_to_user(current_user) - segments << authorized_to_user(items) if current_user + def authorized_snippets + Snippet.where(feature_available_projects.or(not_project_related)).public_or_visible_to_user(current_user) + end - find_union(segments, Snippet) + def feature_available_projects + projects = Project.public_or_visible_to_user(current_user) + .with_feature_available_for_user(:snippets, current_user).select(:id) + arel_query = Arel::Nodes::SqlLiteral.new(projects.to_sql) + table[:project_id].in(arel_query) end - def authorized_to_user(items) - items.where( - 'author_id = :author_id - OR project_id IN (:project_ids)', - author_id: current_user.id, - project_ids: current_user.authorized_projects.select(:id)) + def not_project_related + table[:project_id].eq(nil) + end + + def table + Snippet.arel_table end def by_visibility(items) @@ -53,12 +84,6 @@ class SnippetsFinder < UnionFinder items.where(author_id: params[:author].id) end - def by_project(items) - return items unless params[:project] - - items.where(project_id: params[:project].id) - end - def visibility_from_scope case params[:scope].to_s when 'are_private' diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 6530327698b..a6011eb9f30 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -68,18 +68,32 @@ module ApplicationHelper end end - def avatar_icon(user_or_email = nil, size = nil, scale = 2, only_path: true) - user = - if user_or_email.is_a?(User) - user_or_email - else - User.find_by_any_email(user_or_email.try(:downcase)) - end + # Takes both user and email and returns the avatar_icon by + # user (preferred) or email. + def avatar_icon_for(user = nil, email = nil, size = nil, scale = 2, only_path: true) + if user + avatar_icon_for_user(user, size, scale, only_path: only_path) + elsif email + avatar_icon_for_email(email, size, scale, only_path: only_path) + else + default_avatar + end + end + + def avatar_icon_for_email(email = nil, size = nil, scale = 2, only_path: true) + user = User.find_by_any_email(email.try(:downcase)) + if user + avatar_icon_for_user(user, size, scale, only_path: only_path) + else + gravatar_icon(email, size, scale) + end + end + def avatar_icon_for_user(user = nil, size = nil, scale = 2, only_path: true) if user user.avatar_url(size: size, only_path: only_path) || default_avatar else - gravatar_icon(user_or_email, size, scale) + gravatar_icon(nil, size, scale) end end diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 7548bc30247..e293b3ef329 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -146,6 +146,7 @@ module ApplicationSettingsHelper :akismet_enabled, :authorized_keys_enabled, :auto_devops_enabled, + :auto_devops_domain, :circuitbreaker_access_retries, :circuitbreaker_check_interval, :circuitbreaker_failure_count_threshold, diff --git a/app/helpers/auto_devops_helper.rb b/app/helpers/auto_devops_helper.rb index d72457efec0..16451993e93 100644 --- a/app/helpers/auto_devops_helper.rb +++ b/app/helpers/auto_devops_helper.rb @@ -9,21 +9,28 @@ module AutoDevopsHelper end def auto_devops_warning_message(project) - missing_domain = !project.auto_devops&.has_domain? - missing_service = !project.deployment_platform&.active? - - if missing_service + if missing_auto_devops_service?(project) params = { kubernetes: link_to('Kubernetes cluster', project_clusters_path(project)) } - if missing_domain + if missing_auto_devops_domain?(project) _('Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly.') % params else _('Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly.') % params end - elsif missing_domain + elsif missing_auto_devops_domain?(project) _('Auto Review Apps and Auto Deploy need a domain name to work correctly.') end end + + private + + def missing_auto_devops_domain?(project) + !(project.auto_devops || project.build_auto_devops)&.has_domain? + end + + def missing_auto_devops_service?(project) + !project.deployment_platform&.active? + end end diff --git a/app/helpers/avatars_helper.rb b/app/helpers/avatars_helper.rb index be11d453898..21b6c0a8ad5 100644 --- a/app/helpers/avatars_helper.rb +++ b/app/helpers/avatars_helper.rb @@ -8,10 +8,22 @@ module AvatarsHelper })) end + def user_avatar_url_for(options = {}) + if options[:url] + options[:url] + elsif options[:user] + avatar_icon_for_user(options[:user], options[:size]) + else + avatar_icon_for_email(options[:user_email], options[:size]) + end + end + def user_avatar_without_link(options = {}) avatar_size = options[:size] || 16 user_name = options[:user].try(:name) || options[:user_name] - avatar_url = options[:url] || avatar_icon(options[:user] || options[:user_email], avatar_size) + + avatar_url = user_avatar_url_for(options.merge(size: avatar_size)) + has_tooltip = options[:has_tooltip].nil? ? true : options[:has_tooltip] data_attributes = options[:data] || {} css_class = %W[avatar s#{avatar_size}].push(*options[:css_class]) diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index 0f5fc2823a3..b5ca39711bc 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -160,7 +160,7 @@ module DiffHelper end def diff_file_changed_icon(diff_file) - if diff_file.deleted_file? || diff_file.renamed_file? + if diff_file.deleted_file? "file-deletion" elsif diff_file.new_file? "file-addition" diff --git a/app/helpers/graph_helper.rb b/app/helpers/graph_helper.rb index 6d303ba857d..1022070ab6f 100644 --- a/app/helpers/graph_helper.rb +++ b/app/helpers/graph_helper.rb @@ -1,10 +1,6 @@ module GraphHelper - def get_refs(repo, commit) - refs = "" - # Commit::ref_names already strips the refs/XXX from important refs (e.g. refs/heads/XXX) - # so anything leftover is internally used by GitLab - commit_refs = commit.ref_names(repo).reject { |name| name.starts_with?('refs/') } - refs << commit_refs.join(' ') + def refs(repo, commit) + refs = commit.ref_names(repo).join(' ') # append note count notes_count = @graph.notes[commit.id] diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb index b78d3072186..40ca666f1bf 100644 --- a/app/helpers/namespaces_helper.rb +++ b/app/helpers/namespaces_helper.rb @@ -33,7 +33,7 @@ module NamespacesHelper if namespace.is_a?(Group) group_icon(namespace) else - avatar_icon(namespace.owner.email, size) + avatar_icon_for_user(namespace.owner, size) end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 6512617a02d..b97b72d62c3 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -19,7 +19,7 @@ module ProjectsHelper classes = %W[avatar avatar-inline s#{opts[:size]}] classes << opts[:avatar_class] if opts[:avatar_class] - avatar = avatar_icon(author, opts[:size]) + avatar = avatar_icon_for_user(author, opts[:size]) src = opts[:lazy_load] ? nil : avatar image_tag(src, width: opts[:size], class: classes, alt: '', "data-src" => avatar) @@ -296,6 +296,10 @@ module ProjectsHelper nav_tabs << :pipelines end + if project.external_issue_tracker + nav_tabs << :external_issue_tracker + end + tab_ability_map.each do |tab, ability| if can?(current_user, ability, project) nav_tabs << tab diff --git a/app/helpers/webpack_helper.rb b/app/helpers/webpack_helper.rb index 9d071f2d59a..8bcced70d63 100644 --- a/app/helpers/webpack_helper.rb +++ b/app/helpers/webpack_helper.rb @@ -7,17 +7,24 @@ module WebpackHelper def webpack_controller_bundle_tags bundles = [] - segments = [*controller.controller_path.split('/'), controller.action_name].compact - until segments.empty? + action = case controller.action_name + when 'create' then 'new' + when 'update' then 'edit' + else controller.action_name + end + + route = [*controller.controller_path.split('/'), action].compact + + until route.empty? begin - asset_paths = gitlab_webpack_asset_paths("pages.#{segments.join('.')}", extension: 'js') + asset_paths = gitlab_webpack_asset_paths("pages.#{route.join('.')}", extension: 'js') bundles.unshift(*asset_paths) rescue Webpack::Rails::Manifest::EntryPointMissingError # no bundle exists for this path end - segments.pop + route.pop end javascript_include_tag(*bundles) diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 80bda7f22ff..0dee6df525d 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -117,6 +117,11 @@ class ApplicationSetting < ActiveRecord::Base validates :repository_storages, presence: true validate :check_repository_storages + validates :auto_devops_domain, + allow_blank: true, + hostname: { allow_numeric_hostname: true, require_valid_tld: true }, + if: :auto_devops_enabled? + validates :enabled_git_access_protocol, inclusion: { in: %w(ssh http), allow_blank: true, allow_nil: true } diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 20534b8eed0..490edf4ac57 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -41,12 +41,41 @@ module Ci scope :unstarted, ->() { where(runner_id: nil) } scope :ignore_failures, ->() { where(allow_failure: false) } + + # This convoluted mess is because we need to handle two cases of + # artifact files during the migration. And a simple OR clause + # makes it impossible to optimize. + + # Instead we want to use UNION ALL and do two carefully + # constructed disjoint queries. But Rails cannot handle UNION or + # UNION ALL queries so we do the query in a subquery and wrap it + # in an otherwise redundant WHERE IN query (IN is fine for + # non-null columns). + + # This should all be ripped out when the migration is finished and + # replaced with just the new storage to avoid the extra work. + scope :with_artifacts, ->() do - where('(artifacts_file IS NOT NULL AND artifacts_file <> ?) OR EXISTS (?)', - '', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id')) + old = Ci::Build.select(:id).where(%q[artifacts_file <> '']) + new = Ci::Build.select(:id).where(%q[(artifacts_file IS NULL OR artifacts_file = '') AND EXISTS (?)], + Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id')) + where('ci_builds.id IN (? UNION ALL ?)', old, new) end - scope :with_artifacts_not_expired, ->() { with_artifacts.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) } - scope :with_expired_artifacts, ->() { with_artifacts.where('artifacts_expire_at < ?', Time.now) } + + scope :with_artifacts_not_expired, ->() do + old = Ci::Build.select(:id).where(%q[artifacts_file <> '' AND (artifacts_expire_at IS NULL OR artifacts_expire_at > ?)], Time.now) + new = Ci::Build.select(:id).where(%q[(artifacts_file IS NULL OR artifacts_file = '') AND EXISTS (?)], + Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id AND (expire_at IS NULL OR expire_at > ?)', Time.now)) + where('ci_builds.id IN (? UNION ALL ?)', old, new) + end + + scope :with_expired_artifacts, ->() do + old = Ci::Build.select(:id).where(%q[artifacts_file <> '' AND artifacts_expire_at < ?], Time.now) + new = Ci::Build.select(:id).where(%q[(artifacts_file IS NULL OR artifacts_file = '') AND EXISTS (?)], + Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id AND expire_at < ?', Time.now)) + where('ci_builds.id IN (? UNION ALL ?)', old, new) + end + scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) } scope :manual_actions, ->() { where(when: :manual, status: COMPLETED_STATUSES + [:manual]) } scope :ref_protected, -> { where(protected: true) } @@ -543,6 +572,7 @@ module Ci variables = [ { key: 'CI', value: 'true', public: true }, { key: 'GITLAB_CI', value: 'true', public: true }, + { key: 'GITLAB_FEATURES', value: project.namespace.features.join(','), public: true }, { key: 'CI_SERVER_NAME', value: 'GitLab', public: true }, { key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true }, { key: 'CI_SERVER_REVISION', value: Gitlab::REVISION, public: true }, diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index f84bf132854..2abe90dd181 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -394,7 +394,7 @@ module Ci @config_processor ||= begin Gitlab::Ci::YamlProcessor.new(ci_yaml_file) - rescue Gitlab::Ci::YamlProcessor::ValidationError, Psych::SyntaxError => e + rescue Gitlab::Ci::YamlProcessor::ValidationError => e self.yaml_errors = e.message nil rescue diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index dcbb397fb78..13c784bea0d 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -2,9 +2,11 @@ module Ci class Runner < ActiveRecord::Base extend Gitlab::Ci::Model include Gitlab::SQL::Pattern + include RedisCacheable RUNNER_QUEUE_EXPIRY_TIME = 60.minutes ONLINE_CONTACT_TIMEOUT = 1.hour + UPDATE_DB_RUNNER_INFO_EVERY = 40.minutes AVAILABLE_SCOPES = %w[specific shared active paused online].freeze FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level].freeze @@ -47,6 +49,8 @@ module Ci ref_protected: 1 } + cached_attr_reader :version, :revision, :platform, :architecture, :contacted_at + # Searches for runners matching the given query. # # This method uses ILIKE on PostgreSQL and LIKE on MySQL. @@ -152,6 +156,18 @@ module Ci ensure_runner_queue_value == value if value.present? end + def update_cached_info(values) + values = values&.slice(:version, :revision, :platform, :architecture) || {} + values[:contacted_at] = Time.now + + cache_attributes(values) + + if persist_cached_data? + self.assign_attributes(values) + self.save if self.changed? + end + end + private def cleanup_runner_queue @@ -164,6 +180,17 @@ module Ci "runner:build_queue:#{self.token}" end + def persist_cached_data? + # Use a random threshold to prevent beating DB updates. + # It generates a distribution between [40m, 80m]. + + contacted_at_max_age = UPDATE_DB_RUNNER_INFO_EVERY + Random.rand(UPDATE_DB_RUNNER_INFO_EVERY) + + real_contacted_at = read_attribute(:contacted_at) + real_contacted_at.nil? || + (Time.now - real_contacted_at) >= contacted_at_max_age + end + def tag_constraints unless has_tags? || run_untagged? errors.add(:tags_list, diff --git a/app/models/clusters/applications/prometheus.rb b/app/models/clusters/applications/prometheus.rb index 9b0787ee6ca..aa22e9d5d58 100644 --- a/app/models/clusters/applications/prometheus.rb +++ b/app/models/clusters/applications/prometheus.rb @@ -10,10 +10,26 @@ module Clusters default_value_for :version, VERSION + state_machine :status do + after_transition any => [:installed] do |application| + application.cluster.projects.each do |project| + project.find_or_initialize_service('prometheus').update(active: true) + end + end + end + def chart 'stable/prometheus' end + def service_name + 'prometheus-prometheus-server' + end + + def service_port + 80 + end + def chart_values_file "#{Rails.root}/vendor/#{name}/values.yaml" end @@ -21,6 +37,22 @@ module Clusters def install_command Gitlab::Kubernetes::Helm::InstallCommand.new(name, chart: chart, chart_values_file: chart_values_file) end + + def proxy_client + return unless kube_client + + proxy_url = kube_client.proxy_url('service', service_name, service_port, Gitlab::Kubernetes::Helm::NAMESPACE) + + # ensures headers containing auth data are appended to original k8s client options + options = kube_client.rest_client.options.merge(headers: kube_client.headers) + RestClient::Resource.new(proxy_url, options) + end + + private + + def kube_client + cluster&.kubeclient + end end end end diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index 5ecbd4cbceb..8678f70f78c 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -49,6 +49,9 @@ module Clusters scope :enabled, -> { where(enabled: true) } scope :disabled, -> { where(enabled: false) } + scope :for_environment, -> (env) { where(environment_scope: ['*', '', env.slug]) } + scope :for_all_environments, -> { where(environment_scope: ['*', '']) } + def status_name if provider provider.status_name diff --git a/app/models/commit.rb b/app/models/commit.rb index 2d2d89af030..8c960389652 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -116,6 +116,10 @@ class Commit raw.id end + def project_id + project.id + end + def ==(other) other.is_a?(self.class) && raw == other.raw end diff --git a/app/models/concerns/redis_cacheable.rb b/app/models/concerns/redis_cacheable.rb new file mode 100644 index 00000000000..b889f4202dc --- /dev/null +++ b/app/models/concerns/redis_cacheable.rb @@ -0,0 +1,41 @@ +module RedisCacheable + extend ActiveSupport::Concern + include Gitlab::Utils::StrongMemoize + + CACHED_ATTRIBUTES_EXPIRY_TIME = 24.hours + + class_methods do + def cached_attr_reader(*attributes) + attributes.each do |attribute| + define_method("#{attribute}") do + cached_attribute(attribute) || read_attribute(attribute) + end + end + end + end + + def cached_attribute(attribute) + (cached_attributes || {})[attribute] + end + + def cache_attributes(values) + Gitlab::Redis::SharedState.with do |redis| + redis.set(cache_attribute_key, values.to_json, ex: CACHED_ATTRIBUTES_EXPIRY_TIME) + end + end + + private + + def cache_attribute_key + "cache:#{self.class.name}:#{self.id}:attributes" + end + + def cached_attributes + strong_memoize(:cached_attributes) do + Gitlab::Redis::SharedState.with do |redis| + data = redis.get(cache_attribute_key) + JSON.parse(data, symbolize_names: true) if data + end + end + end +end diff --git a/app/models/event.rb b/app/models/event.rb index 8a79100de5a..75538ba196c 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -96,10 +96,6 @@ class Event < ActiveRecord::Base self.inheritance_column = 'action' - # "data" will be removed in 10.0 but it may be possible that JOINs happen that - # include this column, hence we're ignoring it as well. - ignore_column :data - class << self def model_name ActiveModel::Name.new(self, nil, 'event') diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb index 2aaba2e4c90..282fd7edcb7 100644 --- a/app/models/external_issue.rb +++ b/app/models/external_issue.rb @@ -39,7 +39,7 @@ class ExternalIssue end def to_reference(_from = nil, full: nil) - id + reference_link_text end def reference_link_text(from = nil) diff --git a/app/models/group.rb b/app/models/group.rb index 5b7f1b38612..75bf013ecd2 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -31,9 +31,12 @@ class Group < Namespace has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + accepts_nested_attributes_for :variables, allow_destroy: true + validate :visibility_level_allowed_by_projects validate :visibility_level_allowed_by_sub_groups validate :visibility_level_allowed_by_parent + validates :variables, variable_duplicates: true validates :two_factor_grace_period, presence: true, numericality: { greater_than_or_equal_to: 0 } diff --git a/app/models/identity.rb b/app/models/identity.rb index b3fa7d8176a..2b433e9b988 100644 --- a/app/models/identity.rb +++ b/app/models/identity.rb @@ -9,6 +9,7 @@ class Identity < ActiveRecord::Base validates :user_id, uniqueness: { scope: :provider } before_save :ensure_normalized_extern_uid, if: :extern_uid_changed? + after_destroy :clear_user_synced_attributes, if: :user_synced_attributes_metadata_from_provider? scope :with_provider, ->(provider) { where(provider: provider) } scope :with_extern_uid, ->(provider, extern_uid) do @@ -34,4 +35,12 @@ class Identity < ActiveRecord::Base self.extern_uid = Identity.normalize_uid(self.provider, self.extern_uid) end + + def user_synced_attributes_metadata_from_provider? + user.user_synced_attributes_metadata&.provider == provider + end + + def clear_user_synced_attributes + user.user_synced_attributes_metadata&.destroy + end end diff --git a/app/models/key.rb b/app/models/key.rb index ae5769c0627..7406c98c99e 100644 --- a/app/models/key.rb +++ b/app/models/key.rb @@ -33,8 +33,9 @@ class Key < ActiveRecord::Base after_destroy :refresh_user_cache def key=(value) - write_attribute(:key, value.present? ? Gitlab::SSHPublicKey.sanitize(value) : nil) - + value&.delete!("\n\r") + value.strip! unless value.blank? + write_attribute(:key, value) @public_key = nil end @@ -96,7 +97,7 @@ class Key < ActiveRecord::Base def generate_fingerprint self.fingerprint = nil - return unless public_key.valid? + return unless self.key.present? self.fingerprint = public_key.fingerprint end diff --git a/app/models/lfs_file_lock.rb b/app/models/lfs_file_lock.rb new file mode 100644 index 00000000000..50bb6ca382d --- /dev/null +++ b/app/models/lfs_file_lock.rb @@ -0,0 +1,12 @@ +class LfsFileLock < ActiveRecord::Base + belongs_to :project + belongs_to :user + + validates :project_id, :user_id, :path, presence: true + + def can_be_unlocked_by?(current_user, forced = false) + return true if current_user.id == user_id + + forced && current_user.can?(:admin_project, project) + end +end diff --git a/app/models/member.rb b/app/models/member.rb index c47145667b5..2d17795e62d 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -314,7 +314,7 @@ class Member < ActiveRecord::Base end def notification_setting - @notification_setting ||= user.notification_settings_for(source) + @notification_setting ||= user&.notification_settings_for(source) end def notifiable?(type, opts = {}) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index d025062f562..5bec68ce4f6 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -158,10 +158,12 @@ class MergeRequest < ActiveRecord::Base end def rebase_in_progress? - # The source project can be deleted - return false unless source_project + strong_memoize(:rebase_in_progress) do + # The source project can be deleted + next false unless source_project - source_project.repository.rebase_in_progress?(id) + source_project.repository.rebase_in_progress?(id) + end end # Use this method whenever you need to make sure the head_pipeline is synced with the diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 69a846da9be..c1c27ccf3e5 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -290,7 +290,7 @@ class MergeRequestDiff < ActiveRecord::Base end def keep_around_commits - [repository, merge_request.source_project.repository].each do |repo| + [repository, merge_request.source_project.repository].uniq.each do |repo| repo.keep_around(start_commit_sha) repo.keep_around(head_commit_sha) repo.keep_around(base_commit_sha) diff --git a/app/models/namespace.rb b/app/models/namespace.rb index d95489ee9f2..db274ea8172 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -243,6 +243,10 @@ class Namespace < ActiveRecord::Base all_projects.with_storage_feature(:repository).find_each(&:remove_exports) end + def features + [] + end + private def path_or_parent_changed? diff --git a/app/models/network/graph.rb b/app/models/network/graph.rb index c351d2012c6..1e0d1f9edcb 100644 --- a/app/models/network/graph.rb +++ b/app/models/network/graph.rb @@ -61,11 +61,8 @@ module Network @reserved[i] = [] end - # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37436 - Gitlab::GitalyClient.allow_n_plus_1_calls do - commits_sort_by_ref.each do |commit| - place_chain(commit) - end + commits_sort_by_ref.each do |commit| + place_chain(commit) end # find parent spaces for not overlap lines diff --git a/app/models/project.rb b/app/models/project.rb index 3edb6109fb8..2ba6a863500 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -179,6 +179,7 @@ class Project < ActiveRecord::Base has_many :releases has_many :lfs_objects_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :lfs_objects, through: :lfs_objects_projects + has_many :lfs_file_locks has_many :project_group_links has_many :invited_groups, through: :project_group_links, source: :group has_many :pages_domains @@ -260,6 +261,7 @@ class Project < ActiveRecord::Base validates :repository_storage, presence: true, inclusion: { in: ->(_object) { Gitlab.config.repositories.storages.keys } } + validates :variables, variable_duplicates: { scope: :environment_scope } has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent @@ -1587,8 +1589,11 @@ class Project < ActiveRecord::Base end def protected_for?(ref) - ProtectedBranch.protected?(self, ref) || + if repository.branch_exists?(ref) + ProtectedBranch.protected?(self, ref) + elsif repository.tag_exists?(ref) ProtectedTag.protected?(self, ref) + end end def deployment_variables @@ -1600,7 +1605,7 @@ class Project < ActiveRecord::Base def auto_devops_variables return [] unless auto_devops_enabled? - auto_devops&.variables || [] + (auto_devops || build_auto_devops)&.variables end def append_or_update_attribute(name, value) diff --git a/app/models/project_auto_devops.rb b/app/models/project_auto_devops.rb index 9a52edbff8e..112ed7ed434 100644 --- a/app/models/project_auto_devops.rb +++ b/app/models/project_auto_devops.rb @@ -6,13 +6,17 @@ class ProjectAutoDevops < ActiveRecord::Base validates :domain, allow_blank: true, hostname: { allow_numeric_hostname: true } + def instance_domain + Gitlab::CurrentSettings.auto_devops_domain + end + def has_domain? - domain.present? + domain.present? || instance_domain.present? end def variables variables = [] - variables << { key: 'AUTO_DEVOPS_DOMAIN', value: domain, public: true } if domain.present? + variables << { key: 'AUTO_DEVOPS_DOMAIN', value: domain.presence || instance_domain, public: true } if has_domain? variables end end diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index 30eafe31454..436a870b0c4 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -10,6 +10,8 @@ class JiraService < IssueTrackerService before_update :reset_password + alias_method :project_url, :url + # This is confusing, but JiraService does not really support these events. # The values here are required to display correct options in the service # configuration screen. diff --git a/app/models/project_services/prometheus_service.rb b/app/models/project_services/prometheus_service.rb index fa7b3f2bcaf..1bb576ff971 100644 --- a/app/models/project_services/prometheus_service.rb +++ b/app/models/project_services/prometheus_service.rb @@ -7,11 +7,14 @@ class PrometheusService < MonitoringService # Access to prometheus is directly through the API prop_accessor :api_url + boolean_accessor :manual_configuration - with_options presence: true, if: :activated? do + with_options presence: true, if: :manual_configuration? do validates :api_url, url: true end + before_save :synchronize_service_state! + after_save :clear_reactive_cache! def initialize_properties @@ -20,12 +23,20 @@ class PrometheusService < MonitoringService end end + def show_active_box? + false + end + + def editable? + manual_configuration? || !prometheus_installed? + end + def title 'Prometheus' end def description - s_('PrometheusService|Prometheus monitoring') + s_('PrometheusService|Time-series monitoring service') end def self.to_param @@ -33,8 +44,16 @@ class PrometheusService < MonitoringService end def fields + return [] unless editable? + [ { + type: 'checkbox', + name: 'manual_configuration', + title: s_('PrometheusService|Active'), + required: true + }, + { type: 'text', name: 'api_url', title: 'API URL', @@ -59,7 +78,7 @@ class PrometheusService < MonitoringService end def deployment_metrics(deployment) - metrics = with_reactive_cache(Gitlab::Prometheus::Queries::DeploymentQuery.name, deployment.id, &method(:rename_data_to_metrics)) + metrics = with_reactive_cache(Gitlab::Prometheus::Queries::DeploymentQuery.name, deployment.environment.id, deployment.id, &method(:rename_data_to_metrics)) metrics&.merge(deployment_time: deployment.created_at.to_i) || {} end @@ -68,7 +87,7 @@ class PrometheusService < MonitoringService end def additional_deployment_metrics(deployment) - with_reactive_cache(Gitlab::Prometheus::Queries::AdditionalMetricsDeploymentQuery.name, deployment.id, &:itself) + with_reactive_cache(Gitlab::Prometheus::Queries::AdditionalMetricsDeploymentQuery.name, deployment.environment.id, deployment.id, &:itself) end def matched_metrics @@ -79,6 +98,9 @@ class PrometheusService < MonitoringService def calculate_reactive_cache(query_class_name, *args) return unless active? && project && !project.pending_delete? + environment_id = args.first + client = client(environment_id) + data = Kernel.const_get(query_class_name).new(client).query(*args) { success: true, @@ -89,14 +111,55 @@ class PrometheusService < MonitoringService { success: false, result: err.message } end - def client - @prometheus ||= Gitlab::PrometheusClient.new(api_url: api_url) + def client(environment_id = nil) + if manual_configuration? + Gitlab::PrometheusClient.new(RestClient::Resource.new(api_url)) + else + cluster = cluster_with_prometheus(environment_id) + raise Gitlab::PrometheusError, "couldn't find cluster with Prometheus installed" unless cluster + + rest_client = client_from_cluster(cluster) + raise Gitlab::PrometheusError, "couldn't create proxy Prometheus client" unless rest_client + + Gitlab::PrometheusClient.new(rest_client) + end + end + + def prometheus_installed? + return false if template? + return false unless project + + project.clusters.enabled.any? { |cluster| cluster.application_prometheus&.installed? } end private + def cluster_with_prometheus(environment_id = nil) + clusters = if environment_id + ::Environment.find_by(id: environment_id).try do |env| + # sort results by descending order based on environment_scope being longer + # thus more closely matching environment slug + project.clusters.enabled.for_environment(env).sort_by { |c| c.environment_scope&.length }.reverse! + end + else + project.clusters.enabled.for_all_environments + end + + clusters&.detect { |cluster| cluster.application_prometheus&.installed? } + end + + def client_from_cluster(cluster) + cluster.application_prometheus.proxy_client + end + def rename_data_to_metrics(metrics) metrics[:metrics] = metrics.delete :data metrics end + + def synchronize_service_state! + self.active = prometheus_installed? || manual_configuration? + + true + end end diff --git a/app/models/repository.rb b/app/models/repository.rb index 3d6f8f0c305..4f754b11da4 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -164,6 +164,13 @@ class Repository commits end + # Returns a list of commits that are not present in any reference + def new_commits(newrev) + refs = ::Gitlab::Git::RevList.new(raw, newrev: newrev).new_refs + + refs.map { |sha| commit(sha.strip) } + end + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/384 def find_commits_by_message(query, ref = nil, path = nil, limit = 1000, offset = 0) unless exists? && has_visible_content? && query.present? @@ -586,7 +593,15 @@ class Repository def license_key return unless exists? - Licensee.license(path).try(:key) + # The licensee gem creates a Rugged object from the path: + # https://github.com/benbalter/licensee/blob/v8.7.0/lib/licensee/projects/git_project.rb + begin + Licensee.license(path).try(:key) + # Normally we would rescue Rugged::Error, but that is banned by lint-rugged + # and we need to migrate this endpoint to Gitaly: + # https://gitlab.com/gitlab-org/gitaly/issues/1026 + rescue + end end cache_method :license_key diff --git a/app/models/snippet.rb b/app/models/snippet.rb index 7c8716f8c18..a58c208279e 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -74,6 +74,27 @@ class Snippet < ActiveRecord::Base @link_reference_pattern ||= super("snippets", /(?<snippet>\d+)/) end + # Returns a collection of snippets that are either public or visible to the + # logged in user. + # + # This method does not verify the user actually has the access to the project + # the snippet is in, so it should be only used on a relation that's already scoped + # for project access + def self.public_or_visible_to_user(user = nil) + if user + authorized = user + .project_authorizations + .select(1) + .where('project_authorizations.project_id = snippets.project_id') + + levels = Gitlab::VisibilityLevel.levels_for_user(user) + + where('EXISTS (?) OR snippets.visibility_level IN (?) or snippets.author_id = (?)', authorized, levels, user.id) + else + public_to_user + end + end + def to_reference(from = nil, full: false) reference = "#{self.class.reference_prefix}#{id}" diff --git a/app/models/user.rb b/app/models/user.rb index 05c93d3cb17..5e84d2da805 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -249,7 +249,7 @@ class User < ActiveRecord::Base def find_for_database_authentication(warden_conditions) conditions = warden_conditions.dup if login = conditions.delete(:login) - where(conditions).find_by("lower(username) = :value OR lower(email) = :value", value: login.downcase) + where(conditions).find_by("lower(username) = :value OR lower(email) = :value", value: login.downcase.strip) else find_by(conditions) end @@ -551,7 +551,7 @@ class User < ActiveRecord::Base gpg_keys.each(&:update_invalid_gpg_signatures) end - # Returns the groups a user has access to + # Returns the groups a user has access to, either through a membership or a project authorization def authorized_groups union = Gitlab::SQL::Union .new([groups.select(:id), authorized_projects.select(:namespace_id)]) @@ -559,6 +559,11 @@ class User < ActiveRecord::Base Group.where("namespaces.id IN (#{union.to_sql})") # rubocop:disable GitlabSecurity/SqlInjection end + # Returns the groups a user is a member of, either directly or through a parent group + def membership_groups + Gitlab::GroupHierarchy.new(groups).base_and_descendants + end + # Returns a relation of groups the user has access to, including their parent # and child groups (recursively). def all_expanded_groups diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 1dd8f0a25a9..61a7bf02675 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -119,7 +119,6 @@ class ProjectPolicy < BasePolicy enable :create_note enable :upload_file enable :read_cycle_analytics - enable :read_project_snippet end rule { can?(:reporter_access) }.policy do diff --git a/app/presenters/ci/group_variable_presenter.rb b/app/presenters/ci/group_variable_presenter.rb index 81fea106a5c..98d68bc7a83 100644 --- a/app/presenters/ci/group_variable_presenter.rb +++ b/app/presenters/ci/group_variable_presenter.rb @@ -7,19 +7,15 @@ module Ci end def form_path - if variable.persisted? - group_variable_path(group, variable) - else - group_variables_path(group) - end + group_settings_ci_cd_path(group) end def edit_path - group_variable_path(group, variable) + group_variables_path(group) end def delete_path - group_variable_path(group, variable) + group_variables_path(group) end end end diff --git a/app/presenters/ci/variable_presenter.rb b/app/presenters/ci/variable_presenter.rb index 5d7998393a6..96159f88c59 100644 --- a/app/presenters/ci/variable_presenter.rb +++ b/app/presenters/ci/variable_presenter.rb @@ -7,19 +7,15 @@ module Ci end def form_path - if variable.persisted? - project_variable_path(project, variable) - else - project_variables_path(project) - end + project_settings_ci_cd_path(project) end def edit_path - project_variable_path(project, variable) + project_variables_path(project) end def delete_path - project_variable_path(project, variable) + project_variables_path(project) end end end diff --git a/app/presenters/merge_request_presenter.rb b/app/presenters/merge_request_presenter.rb index c6806b7cc26..08ae49562c7 100644 --- a/app/presenters/merge_request_presenter.rb +++ b/app/presenters/merge_request_presenter.rb @@ -3,6 +3,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated include GitlabRoutingHelper include MarkupHelper include TreeHelper + include Gitlab::Utils::StrongMemoize presents :merge_request @@ -43,7 +44,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated end def revert_in_fork_path - if user_can_fork_project? && can_be_reverted?(current_user) + if user_can_fork_project? && cached_can_be_reverted? continue_params = { to: merge_request_path(merge_request), notice: "#{edit_in_new_fork_notice} Try to cherry-pick this commit again.", @@ -151,7 +152,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated end def can_revert_on_current_merge_request? - user_can_collaborate_with_project? && can_be_reverted?(current_user) + user_can_collaborate_with_project? && cached_can_be_reverted? end def can_cherry_pick_on_current_merge_request? @@ -164,6 +165,12 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated private + def cached_can_be_reverted? + strong_memoize(:can_be_reverted) do + can_be_reverted?(current_user) + end + end + def conflicts @conflicts ||= MergeRequests::Conflicts::ListService.new(merge_request) end diff --git a/app/serializers/group_variable_entity.rb b/app/serializers/group_variable_entity.rb new file mode 100644 index 00000000000..62cf0b21e1e --- /dev/null +++ b/app/serializers/group_variable_entity.rb @@ -0,0 +1,7 @@ +class GroupVariableEntity < Grape::Entity + expose :id + expose :key + expose :value + + expose :protected?, as: :protected +end diff --git a/app/serializers/group_variable_serializer.rb b/app/serializers/group_variable_serializer.rb new file mode 100644 index 00000000000..8f8205924aa --- /dev/null +++ b/app/serializers/group_variable_serializer.rb @@ -0,0 +1,3 @@ +class GroupVariableSerializer < BaseSerializer + entity GroupVariableEntity +end diff --git a/app/serializers/lfs_file_lock_entity.rb b/app/serializers/lfs_file_lock_entity.rb new file mode 100644 index 00000000000..264a77adc3f --- /dev/null +++ b/app/serializers/lfs_file_lock_entity.rb @@ -0,0 +1,11 @@ +class LfsFileLockEntity < Grape::Entity + root 'locks', 'lock' + + expose :path + expose(:id) { |entity| entity.id.to_s } + expose(:created_at, as: :locked_at) { |entity| entity.created_at.to_s(:iso8601) } + + expose :owner do + expose(:name) { |entity| entity.user&.name } + end +end diff --git a/app/serializers/lfs_file_lock_serializer.rb b/app/serializers/lfs_file_lock_serializer.rb new file mode 100644 index 00000000000..ba8fb1a461d --- /dev/null +++ b/app/serializers/lfs_file_lock_serializer.rb @@ -0,0 +1,3 @@ +class LfsFileLockSerializer < BaseSerializer + entity LfsFileLockEntity +end diff --git a/app/serializers/project_serializer.rb b/app/serializers/project_serializer.rb new file mode 100644 index 00000000000..74de1e79a8f --- /dev/null +++ b/app/serializers/project_serializer.rb @@ -0,0 +1,3 @@ +class ProjectSerializer < BaseSerializer + entity ProjectEntity +end diff --git a/app/serializers/variable_entity.rb b/app/serializers/variable_entity.rb new file mode 100644 index 00000000000..d576745c073 --- /dev/null +++ b/app/serializers/variable_entity.rb @@ -0,0 +1,7 @@ +class VariableEntity < Grape::Entity + expose :id + expose :key + expose :value + + expose :protected?, as: :protected +end diff --git a/app/serializers/variable_serializer.rb b/app/serializers/variable_serializer.rb new file mode 100644 index 00000000000..32ae82ab51c --- /dev/null +++ b/app/serializers/variable_serializer.rb @@ -0,0 +1,3 @@ +class VariableSerializer < BaseSerializer + entity VariableEntity +end diff --git a/app/services/ci/ensure_stage_service.rb b/app/services/ci/ensure_stage_service.rb index dc2f49e8db1..87f19b333de 100644 --- a/app/services/ci/ensure_stage_service.rb +++ b/app/services/ci/ensure_stage_service.rb @@ -7,6 +7,8 @@ module Ci # stage. # class EnsureStageService < BaseService + EnsureStageError = Class.new(StandardError) + def execute(build) @build = build @@ -22,8 +24,16 @@ module Ci private - def ensure_stage + def ensure_stage(attempts: 2) find_stage || create_stage + rescue ActiveRecord::RecordNotUnique + retry if (attempts -= 1) > 0 + + raise EnsureStageError, <<~EOS + We failed to find or create a unique pipeline stage after 2 retries. + This should never happen and is most likely the result of a bug in + the database load balancing code. + EOS end def find_stage diff --git a/app/services/ci/retry_build_service.rb b/app/services/ci/retry_build_service.rb index c552193e66b..6128b2a8fbb 100644 --- a/app/services/ci/retry_build_service.rb +++ b/app/services/ci/retry_build_service.rb @@ -1,7 +1,7 @@ module Ci class RetryBuildService < ::BaseService CLONE_ACCESSORS = %i[pipeline project ref tag options commands name - allow_failure stage_id stage stage_idx trigger_request + allow_failure stage stage_id stage_idx trigger_request yaml_variables when environment coverage_regex description tag_list protected].freeze diff --git a/app/services/delete_merged_branches_service.rb b/app/services/delete_merged_branches_service.rb index cb235a85daf..c98d1e3c540 100644 --- a/app/services/delete_merged_branches_service.rb +++ b/app/services/delete_merged_branches_service.rb @@ -6,18 +6,14 @@ class DeleteMergedBranchesService < BaseService def execute raise Gitlab::Access::AccessDeniedError unless can?(current_user, :push_code, project) - # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37438 - Gitlab::GitalyClient.allow_n_plus_1_calls do - branches = project.repository.branch_names - branches = branches.select { |branch| project.repository.merged_to_root_ref?(branch) } - # Prevent deletion of branches relevant to open merge requests - branches -= merge_request_branch_names - # Prevent deletion of protected branches - branches = branches.reject { |branch| ProtectedBranch.protected?(project, branch) } + branches = project.repository.merged_branch_names + # Prevent deletion of branches relevant to open merge requests + branches -= merge_request_branch_names + # Prevent deletion of protected branches + branches = branches.reject { |branch| ProtectedBranch.protected?(project, branch) } - branches.each do |branch| - DeleteBranchService.new(project, current_user).execute(branch) - end + branches.each do |branch| + DeleteBranchService.new(project, current_user).execute(branch) end end diff --git a/app/services/groups/nested_create_service.rb b/app/services/groups/nested_create_service.rb index d6f08fc3cce..5c337a9faa5 100644 --- a/app/services/groups/nested_create_service.rb +++ b/app/services/groups/nested_create_service.rb @@ -11,8 +11,8 @@ module Groups def execute return nil unless group_path - if group = Group.find_by_full_path(group_path) - return group + if namespace = namespace_or_group(group_path) + return namespace end if group_path.include?('/') && !Group.supports_nested_groups? @@ -40,10 +40,14 @@ module Groups ) new_params[:visibility_level] ||= Gitlab::CurrentSettings.current_application_settings.default_group_visibility - last_group = Group.find_by_full_path(partial_path) || Groups::CreateService.new(current_user, new_params).execute + last_group = namespace_or_group(partial_path) || Groups::CreateService.new(current_user, new_params).execute end last_group end + + def namespace_or_group(group_path) + Namespace.find_by_full_path(group_path) + end end end diff --git a/app/services/issues/fetch_referenced_merge_requests_service.rb b/app/services/issues/fetch_referenced_merge_requests_service.rb new file mode 100644 index 00000000000..39c8ded9df4 --- /dev/null +++ b/app/services/issues/fetch_referenced_merge_requests_service.rb @@ -0,0 +1,12 @@ +module Issues + class FetchReferencedMergeRequestsService < Issues::BaseService + def execute(issue) + referenced_merge_requests = issue.referenced_merge_requests(current_user) + referenced_merge_requests = Gitlab::IssuableSorter.sort(project, referenced_merge_requests) { |i| i.iid.to_s } + closed_by_merge_requests = issue.closed_by_merge_requests(current_user) + closed_by_merge_requests = Gitlab::IssuableSorter.sort(project, closed_by_merge_requests) { |i| i.iid.to_s } + + [referenced_merge_requests, closed_by_merge_requests] + end + end +end diff --git a/app/services/issues/move_service.rb b/app/services/issues/move_service.rb index 2f511ab44b7..299b9c6215f 100644 --- a/app/services/issues/move_service.rb +++ b/app/services/issues/move_service.rb @@ -19,19 +19,10 @@ module Issues # on rewriting notes (unfolding references) # ActiveRecord::Base.transaction do - # New issue tasks - # @new_issue = create_new_issue - rewrite_notes - rewrite_issue_award_emoji - add_note_moved_from - - # Old issue tasks - # - add_note_moved_to - close_issue - mark_as_moved + update_new_issue + update_old_issue end notify_participants @@ -41,6 +32,18 @@ module Issues private + def update_new_issue + rewrite_notes + rewrite_issue_award_emoji + add_note_moved_from + end + + def update_old_issue + add_note_moved_to + close_issue + mark_as_moved + end + def create_new_issue new_params = { id: nil, iid: nil, label_ids: cloneable_label_ids, milestone_id: cloneable_milestone_id, diff --git a/app/services/lfs/lock_file_service.rb b/app/services/lfs/lock_file_service.rb new file mode 100644 index 00000000000..bbe10f84ef4 --- /dev/null +++ b/app/services/lfs/lock_file_service.rb @@ -0,0 +1,39 @@ +module Lfs + class LockFileService < BaseService + def execute + unless can?(current_user, :push_code, project) + raise Gitlab::GitAccess::UnauthorizedError, 'You have no permissions' + end + + create_lock! + rescue ActiveRecord::RecordNotUnique + error('already locked', 409, current_lock) + rescue Gitlab::GitAccess::UnauthorizedError => ex + error(ex.message, 403) + rescue => ex + error(ex.message, 500) + end + + private + + def current_lock + project.lfs_file_locks.find_by(path: params[:path]) + end + + def create_lock! + lock = project.lfs_file_locks.create!(user: current_user, + path: params[:path]) + + success(http_status: 201, lock: lock) + end + + def error(message, http_status, lock = nil) + { + status: :error, + message: message, + http_status: http_status, + lock: lock + } + end + end +end diff --git a/app/services/lfs/locks_finder_service.rb b/app/services/lfs/locks_finder_service.rb new file mode 100644 index 00000000000..13c6cc6f81c --- /dev/null +++ b/app/services/lfs/locks_finder_service.rb @@ -0,0 +1,17 @@ +module Lfs + class LocksFinderService < BaseService + def execute + success(locks: find_locks) + rescue => ex + error(ex.message, 500) + end + + private + + def find_locks + options = params.slice(:id, :path).compact.symbolize_keys + + project.lfs_file_locks.where(options) + end + end +end diff --git a/app/services/lfs/unlock_file_service.rb b/app/services/lfs/unlock_file_service.rb new file mode 100644 index 00000000000..6c93dc69bb0 --- /dev/null +++ b/app/services/lfs/unlock_file_service.rb @@ -0,0 +1,43 @@ +module Lfs + class UnlockFileService < BaseService + def execute + unless can?(current_user, :push_code, project) + raise Gitlab::GitAccess::UnauthorizedError, 'You have no permissions' + end + + unlock_file + rescue Gitlab::GitAccess::UnauthorizedError => ex + error(ex.message, 403) + rescue ActiveRecord::RecordNotFound + error('Lock not found', 404) + rescue => ex + error(ex.message, 500) + end + + private + + def unlock_file + forced = params[:force] == true + + if lock.can_be_unlocked_by?(current_user, forced) + lock.destroy! + + success(lock: lock, http_status: :ok) + elsif forced + error('You must have master access to force delete a lock', 403) + else + error("#{lock.path} is locked by GitLab User #{lock.user_id}", 403) + end + end + + def lock + return @lock if defined?(@lock) + + @lock = if params[:id].present? + project.lfs_file_locks.find(params[:id]) + elsif params[:path].present? + project.lfs_file_locks.find_by!(path: params[:path]) + end + end + end +end diff --git a/app/services/members/authorized_destroy_service.rb b/app/services/members/authorized_destroy_service.rb index de3a252d6c6..2e89f00dad8 100644 --- a/app/services/members/authorized_destroy_service.rb +++ b/app/services/members/authorized_destroy_service.rb @@ -11,6 +11,7 @@ module Members Member.transaction do unassign_issues_and_merge_requests(member) unless member.invite? + member.notification_setting&.destroy member.destroy end diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index 2ae855d078b..4b186d93772 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -134,7 +134,7 @@ module MergeRequests end def append_closes_description - return unless issue + return unless issue&.to_reference.present? closes_issue = "Closes #{issue.to_reference}" @@ -160,10 +160,12 @@ module MergeRequests merge_request.title = "Resolve \"#{issue.title}\"" if issue.is_a?(Issue) - unless merge_request.title + return if merge_request.title.present? + + if issue_iid.present? + merge_request.title = "Resolve #{issue.to_reference}" branch_title = source_branch.downcase.remove(issue_iid.downcase).titleize.humanize - merge_request.title = "Resolve #{issue_iid}" - merge_request.title += " \"#{branch_title}\"" unless branch_title.empty? + merge_request.title += " \"#{branch_title}\"" if branch_title.present? end end diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb index 634bf3bd690..a18b1c90765 100644 --- a/app/services/merge_requests/create_service.rb +++ b/app/services/merge_requests/create_service.rb @@ -9,10 +9,7 @@ module MergeRequests merge_request.source_branch = params[:source_branch] merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch) - # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37439 - Gitlab::GitalyClient.allow_n_plus_1_calls do - create(merge_request) - end + create(merge_request) end def before_create(merge_request) diff --git a/app/services/projects/create_from_template_service.rb b/app/services/projects/create_from_template_service.rb index 87d9ed7a0e6..a549cfbabea 100644 --- a/app/services/projects/create_from_template_service.rb +++ b/app/services/projects/create_from_template_service.rb @@ -5,11 +5,15 @@ module Projects end def execute - params[:file] = Gitlab::ProjectTemplate.find(params[:template_name]).file + template_name = params.delete(:template_name) + file = Gitlab::ProjectTemplate.find(template_name).file + + params[:file] = file + + GitlabProjectsImportService.new(current_user, params).execute - GitlabProjectsImportService.new(@current_user, @params).execute ensure - params[:file]&.close + file&.close end end end diff --git a/app/services/projects/gitlab_projects_import_service.rb b/app/services/projects/gitlab_projects_import_service.rb index a3d7f5cbed5..a68ecb4abe1 100644 --- a/app/services/projects/gitlab_projects_import_service.rb +++ b/app/services/projects/gitlab_projects_import_service.rb @@ -11,12 +11,14 @@ module Projects def execute FileUtils.mkdir_p(File.dirname(import_upload_path)) + + file = params.delete(:file) FileUtils.copy_entry(file.path, import_upload_path) - Gitlab::ImportExport::ProjectCreator.new(params[:namespace_id], - current_user, - import_upload_path, - params[:path]).execute + params[:import_type] = 'gitlab_project' + params[:import_source] = import_upload_path + + ::Projects::CreateService.new(current_user, params).execute end private @@ -28,9 +30,5 @@ module Projects def tmp_filename SecureRandom.hex end - - def file - params[:file] - end end end diff --git a/app/validators/variable_duplicates_validator.rb b/app/validators/variable_duplicates_validator.rb index 8a9d8892e9b..4bfa3c45303 100644 --- a/app/validators/variable_duplicates_validator.rb +++ b/app/validators/variable_duplicates_validator.rb @@ -1,13 +1,28 @@ # VariableDuplicatesValidator # -# This validtor is designed for especially the following condition +# This validator is designed for especially the following condition # - Use `accepts_nested_attributes_for :xxx` in a parent model # - Use `validates :xxx, uniqueness: { scope: :xxx_id }` in a child model class VariableDuplicatesValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) - duplicates = value.reject(&:marked_for_destruction?).group_by(&:key).select { |_, v| v.many? }.map(&:first) + if options[:scope] + scoped = value.group_by do |variable| + Array(options[:scope]).map { |attr| variable.send(attr) } # rubocop:disable GitlabSecurity/PublicSend + end + scoped.each_value { |scope| validate_duplicates(record, attribute, scope) } + else + validate_duplicates(record, attribute, value) + end + end + + private + + def validate_duplicates(record, attribute, values) + duplicates = values.reject(&:marked_for_destruction?).group_by(&:key).select { |_, v| v.many? }.map(&:first) if duplicates.any? - record.errors.add(attribute, "Duplicate variables: #{duplicates.join(", ")}") + error_message = "have duplicate values (#{duplicates.join(", ")})" + error_message += " for #{values.first.send(options[:scope])} scope" if options[:scope] # rubocop:disable GitlabSecurity/PublicSend + record.errors.add(attribute, error_message) end end end diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index fb5e6f337a7..60f12030f98 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -249,7 +249,12 @@ .help-block It will automatically build, test, and deploy applications based on a predefined CI/CD configuration = link_to icon('question-circle'), help_page_path('topics/autodevops/index.md') - + .form-group + = f.label :auto_devops_domain, class: 'control-label col-sm-2' + .col-sm-10 + = f.text_field :auto_devops_domain, class: 'form-control', placeholder: 'domain.com' + .help-block + = s_("AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages.") .form-group .col-sm-offset-2.col-sm-10 .checkbox diff --git a/app/views/admin/jobs/index.html.haml b/app/views/admin/jobs/index.html.haml index a01676d82a8..4e3e2f7a475 100644 --- a/app/views/admin/jobs/index.html.haml +++ b/app/views/admin/jobs/index.html.haml @@ -7,10 +7,9 @@ - build_path_proc = ->(scope) { admin_jobs_path(scope: scope) } = render "shared/builds/tabs", build_path_proc: build_path_proc, all_builds: @all_builds, scope: @scope - .nav-controls - - if @all_builds.running_or_pending.any? - #stop-jobs-modal - + - if @all_builds.running_or_pending.any? + #stop-jobs-modal + .nav-controls %button#stop-jobs-button.btn.btn-danger{ data: { toggle: 'modal', target: '#stop-jobs-modal', url: cancel_all_admin_jobs_path } } diff --git a/app/views/admin/projects/_projects.html.haml b/app/views/admin/projects/_projects.html.haml index c69c2761189..b5d7b889504 100644 --- a/app/views/admin/projects/_projects.html.haml +++ b/app/views/admin/projects/_projects.html.haml @@ -5,7 +5,12 @@ %li.project-row{ class: ('no-description' if project.description.blank?) } .controls = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn" - = link_to 'Delete', [project.namespace.becomes(Namespace), project], data: { confirm: remove_project_message(project) }, method: :delete, class: "btn btn-remove" + %button.delete-project-button.btn.btn-danger{ data: { toggle: 'modal', + target: '#delete-project-modal', + delete_project_url: project_path(project), + project_name: project.name }, type: 'button' } + = s_('AdminProjects|Delete') + .stats %span.badge = storage_counter(project.statistics.storage_size) @@ -31,3 +36,5 @@ = paginate @projects, theme: 'gitlab' - else .nothing-here-block No projects found + + #delete-project-modal diff --git a/app/views/admin/users/_user.html.haml b/app/views/admin/users/_user.html.haml index ca6e43e091c..bbfeceff5b9 100644 --- a/app/views/admin/users/_user.html.haml +++ b/app/views/admin/users/_user.html.haml @@ -1,6 +1,6 @@ %li.flex-row .user-avatar - = image_tag avatar_icon(user), class: "avatar", alt: '' + = image_tag avatar_icon_for_user(user), class: "avatar", alt: '' .row-main-content .user-name.row-title.str-truncated-100 = link_to user.name, [:admin, user] @@ -38,12 +38,19 @@ %li.divider - if user.can_be_removed? %li - = link_to 'Remove user', admin_user_path(user), - data: { confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?" }, - class: 'text-danger', - method: :delete + %button.delete-user-button.btn.text-danger{ data: { toggle: 'modal', + target: '#delete-user-modal', + delete_user_url: admin_user_path(user), + block_user_url: block_admin_user_path(user), + username: user.name, + delete_contributions: 'false' }, type: 'button' } + = s_('AdminUsers|Delete user') + %li - = link_to 'Remove user and contributions', admin_user_path(user, hard_delete: true), - data: { confirm: "USER #{user.name} WILL BE REMOVED! All issues, merge requests and comments authored by this user, and groups owned solely by them, will also be removed! Are you sure?" }, - class: 'text-danger', - method: :delete + %button.delete-user-button.btn.text-danger{ data: { toggle: 'modal', + target: '#delete-user-modal', + delete_user_url: admin_user_path(user, hard_delete: true), + block_user_url: block_admin_user_path(user), + username: user.name, + delete_contributions: 'true' }, type: 'button' } + = s_('AdminUsers|Delete user and contributions') diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index 38ce1564eff..0ef4b71f4fe 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -76,3 +76,6 @@ = render partial: 'admin/users/user', collection: @users = paginate @users, theme: "gitlab" + +#delete-user-modal + diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index 63c5a15de1c..ec3be869797 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -10,7 +10,7 @@ = @user.name %ul.well-list %li - = image_tag avatar_icon(@user, 60), class: "avatar s60" + = image_tag avatar_icon_for_user(@user, 60), class: "avatar s60" %li %span.light Profile page: %strong @@ -172,13 +172,19 @@ .panel.panel-danger .panel-heading - Remove user + = s_('AdminUsers|Delete user') .panel-body - if @user.can_be_removed? && can?(current_user, :destroy_user, @user) %p Deleting a user has the following effects: = render 'users/deletion_guidance', user: @user %br - = link_to 'Remove user', admin_user_path(@user), data: { confirm: "USER #{@user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-remove" + %button.delete-user-button.btn.text-danger{ data: { toggle: 'modal', + target: '#delete-user-modal', + delete_user_url: admin_user_path(@user), + block_user_url: block_admin_user_path(@user), + username: @user.name, + delete_contributions: 'false' }, type: 'button' } + = s_('AdminUsers|Delete user') - else - if @user.solo_owned_groups.present? %p @@ -192,7 +198,7 @@ .panel.panel-danger .panel-heading - Remove user and contributions + = s_('AdminUsers|Delete user and contributions') .panel-body - if can?(current_user, :destroy_user, @user) %p @@ -204,7 +210,15 @@ the user, and projects in them, will also be removed. Commits to other projects are unaffected. %br - = link_to 'Remove user and contributions', admin_user_path(@user, hard_delete: true), data: { confirm: "USER #{@user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-remove" + %button.delete-user-button.btn.text-danger{ data: { toggle: 'modal', + target: '#delete-user-modal', + delete_user_url: admin_user_path(@user, hard_delete: true), + block_user_url: block_admin_user_path(@user), + username: @user.name, + delete_contributions: 'true' }, type: 'button' } + = s_('AdminUsers|Delete user and contributions') - else %p You don't have access to delete this user. + + #delete-user-modal diff --git a/app/views/ci/lints/show.html.haml b/app/views/ci/lints/show.html.haml index e6408f35201..3c0881caa06 100644 --- a/app/views/ci/lints/show.html.haml +++ b/app/views/ci/lints/show.html.haml @@ -18,6 +18,8 @@ .col-sm-12 .pull-left.prepend-top-10 = submit_tag('Validate', class: 'btn btn-success submit-yml') + .pull-right.prepend-top-10 + = button_tag('Clear', type: 'button', class: 'btn btn-default clear-yml') .row.prepend-top-20 .col-sm-12 diff --git a/app/views/ci/variables/_content.html.haml b/app/views/ci/variables/_content.html.haml index fbfe3e56588..d355e7799df 100644 --- a/app/views/ci/variables/_content.html.haml +++ b/app/views/ci/variables/_content.html.haml @@ -1,3 +1 @@ -%p.append-bottom-default - Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. - You can use variables for passwords, secret keys, or whatever you want. += _('Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want.') diff --git a/app/views/ci/variables/_form.html.haml b/app/views/ci/variables/_form.html.haml deleted file mode 100644 index eebd0955c80..00000000000 --- a/app/views/ci/variables/_form.html.haml +++ /dev/null @@ -1,19 +0,0 @@ -= form_for @variable, as: :variable, url: @variable.form_path do |f| - = form_errors(@variable) - - .form-group - = f.label :key, "Key", class: "label-light" - = f.text_field :key, class: "form-control", placeholder: @variable.placeholder, required: true - .form-group - = f.label :value, "Value", class: "label-light" - = f.text_area :value, class: "form-control", placeholder: @variable.placeholder - .form-group - .checkbox - = f.label :protected do - = f.check_box :protected - %strong Protected - .help-block - This variable will be passed only to pipelines running on protected branches and tags - = link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'protected-secret-variables'), target: '_blank' - - = f.submit btn_text, class: "btn btn-save" diff --git a/app/views/ci/variables/_index.html.haml b/app/views/ci/variables/_index.html.haml index 6e399fc7392..e402801a776 100644 --- a/app/views/ci/variables/_index.html.haml +++ b/app/views/ci/variables/_index.html.haml @@ -1,16 +1,20 @@ -.row.prepend-top-default.append-bottom-default - .col-lg-12 - %h5.prepend-top-0 - Add a variable - = render "ci/variables/form", btn_text: "Add new variable" - %hr - %h5.prepend-top-0 - Your variables (#{@variables.size}) - - if @variables.empty? - %p.settings-message.text-center.append-bottom-0 - No variables found, add one with the form above. - - else - .js-secret-variable-table - = render "ci/variables/table" - %button.btn.btn-info.js-secret-value-reveal-button{ data: { secret_reveal_status: 'false' } } +- save_endpoint = local_assigns.fetch(:save_endpoint, nil) + +.row + .col-lg-12.js-ci-variable-list-section{ data: { save_endpoint: save_endpoint } } + .hide.alert.alert-danger.js-ci-variable-error-box + + %ul.ci-variable-list + - @variables.each.each do |variable| + = render 'ci/variables/variable_row', form_field: 'variables', variable: variable + = render 'ci/variables/variable_row', form_field: 'variables' + .prepend-top-20 + %button.btn.btn-success.js-secret-variables-save-button{ type: 'button' } + %span.hide.js-secret-variables-save-loading-icon + = icon('spinner spin') + = _('Save variables') + %button.btn.btn-info.btn-inverted.prepend-left-10.js-secret-value-reveal-button{ type: 'button', data: { secret_reveal_status: "#{@variables.size == 0}" } } + - if @variables.size == 0 + = n_('Hide value', 'Hide values', @variables.size) + - else = n_('Reveal value', 'Reveal values', @variables.size) diff --git a/app/views/ci/variables/_show.html.haml b/app/views/ci/variables/_show.html.haml deleted file mode 100644 index 6d75ae96124..00000000000 --- a/app/views/ci/variables/_show.html.haml +++ /dev/null @@ -1,9 +0,0 @@ -- page_title "Variables" - -.row.prepend-top-default.append-bottom-default - .col-lg-3 - = render "ci/variables/content" - .col-lg-9 - %h4.prepend-top-0 - Update variable - = render "ci/variables/form", btn_text: "Save variable" diff --git a/app/views/ci/variables/_table.html.haml b/app/views/ci/variables/_table.html.haml deleted file mode 100644 index 2298930d0c7..00000000000 --- a/app/views/ci/variables/_table.html.haml +++ /dev/null @@ -1,32 +0,0 @@ -.table-responsive.variables-table - %table.table - %colgroup - %col - %col - %col - %col{ width: 100 } - %thead - %th Key - %th Value - %th Protected - %th - %tbody - - @variables.each do |variable| - - if variable.id? - %tr - %td.variable-key= variable.key - %td.variable-value - %span.js-secret-value-placeholder - = '*' * 6 - %span.hide.js-secret-value - = variable.value - %td.variable-protected= Gitlab::Utils.boolean_to_yes_no(variable.protected) - %td.variable-menu - = link_to variable.edit_path, class: "btn btn-transparent btn-variable-edit" do - %span.sr-only - Update - = icon("pencil") - = link_to variable.delete_path, class: "btn btn-transparent btn-variable-delete", method: :delete, data: { confirm: "Are you sure?" } do - %span.sr-only - Remove - = icon("trash") diff --git a/app/views/ci/variables/_variable_row.html.haml b/app/views/ci/variables/_variable_row.html.haml index 495a55660cb..15201780451 100644 --- a/app/views/ci/variables/_variable_row.html.haml +++ b/app/views/ci/variables/_variable_row.html.haml @@ -5,7 +5,7 @@ - id = variable&.id - key = variable&.key - value = variable&.value -- is_protected = variable && !only_key_value ? variable.protected : true +- is_protected = variable && !only_key_value ? variable.protected : false - id_input_name = "#{form_field}[variables_attributes][][id]" - destroy_input_name = "#{form_field}[variables_attributes][][_destroy]" diff --git a/app/views/discussions/_discussion.html.haml b/app/views/discussions/_discussion.html.haml index 205320ed87c..8b9fa3d6b05 100644 --- a/app/views/discussions/_discussion.html.haml +++ b/app/views/discussions/_discussion.html.haml @@ -3,7 +3,7 @@ .timeline-entry-inner .timeline-icon = link_to user_path(discussion.author) do - = image_tag avatar_icon(discussion.author), class: "avatar s40" + = image_tag avatar_icon_for_user(discussion.author), class: "avatar s40" .timeline-content .discussion.js-toggle-container{ data: { discussion_id: discussion.id } } .discussion-header diff --git a/app/views/events/_event.atom.builder b/app/views/events/_event.atom.builder index 38741fe6662..d56234e6c1a 100644 --- a/app/views/events/_event.atom.builder +++ b/app/views/events/_event.atom.builder @@ -10,7 +10,7 @@ xml.entry do # eager-loaded. This allows us to re-use the user object's Email address, # instead of having to run additional queries to figure out what Email to use # for the avatar. - xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(event.author)) + xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon_for_user(event.author)) xml.author do xml.username event.author_username diff --git a/app/views/groups/labels/new.html.haml b/app/views/groups/labels/new.html.haml index ae240490bbd..538c353cf2d 100644 --- a/app/views/groups/labels/new.html.haml +++ b/app/views/groups/labels/new.html.haml @@ -1,6 +1,5 @@ - breadcrumb_title "Labels" - page_title 'New Label' -- header_title group_title(@group, 'Labels', group_labels_path(@group)) %h3.page-title New Label diff --git a/app/views/groups/settings/ci_cd/show.html.haml b/app/views/groups/settings/ci_cd/show.html.haml index 472da2a6a72..dd82922ec55 100644 --- a/app/views/groups/settings/ci_cd/show.html.haml +++ b/app/views/groups/settings/ci_cd/show.html.haml @@ -1,4 +1,11 @@ - breadcrumb_title "CI / CD Settings" - page_title "CI / CD" -= render 'ci/variables/index' +%h4 + = _('Secret variables') + = link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'secret-variables'), target: '_blank', rel: 'noopener noreferrer' + +%p + = render "ci/variables/content" + += render 'ci/variables/index', save_endpoint: group_variables_path diff --git a/app/views/groups/variables/show.html.haml b/app/views/groups/variables/show.html.haml deleted file mode 100644 index df533952b76..00000000000 --- a/app/views/groups/variables/show.html.haml +++ /dev/null @@ -1 +0,0 @@ -= render 'ci/variables/show' diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml index 445f0dffbcc..1c4d67a8d2c 100644 --- a/app/views/help/ui.html.haml +++ b/app/views/help/ui.html.haml @@ -68,7 +68,7 @@ .example .cover-block .avatar-holder - = image_tag avatar_icon('admin@example.com', 90), class: "avatar s90", alt: '' + = image_tag avatar_icon_for_email('admin@example.com', 90), class: "avatar s90", alt: '' .cover-title John Smith diff --git a/app/views/import/base/create.js.haml b/app/views/import/base/create.js.haml deleted file mode 100644 index 4dc3a4a0acf..00000000000 --- a/app/views/import/base/create.js.haml +++ /dev/null @@ -1,13 +0,0 @@ -- if @project.persisted? - :plain - job = $("tr#repo_#{@repo_id}") - job.attr("id", "project_#{@project.id}") - target_field = job.find(".import-target") - target_field.empty() - target_field.append('#{link_to @project.full_path, project_path(@project)}') - $("table.import-jobs tbody").prepend(job) - job.addClass("active").find(".import-actions").html("<i class='fa fa-spinner fa-spin'></i> started") -- else - :plain - job = $("tr#repo_#{@repo_id}") - job.find(".import-actions").html("<i class='fa fa-exclamation-circle'></i> Error saving project: #{escape_javascript(h(@project.errors.full_messages.join(',')))}") diff --git a/app/views/import/base/unauthorized.js.haml b/app/views/import/base/unauthorized.js.haml deleted file mode 100644 index ada5f99f4e2..00000000000 --- a/app/views/import/base/unauthorized.js.haml +++ /dev/null @@ -1,14 +0,0 @@ -:plain - tr = $("tr#repo_#{@repo_id}") - target_field = tr.find(".import-target") - import_button = tr.find(".btn-import") - origin_target = target_field.text() - project_name = "#{@project_name}" - origin_namespace = "#{@target_namespace.full_path}" - target_field.empty() - target_field.append("<p class='alert alert-danger'>This namespace has already been taken! Please choose another one.</p>") - target_field.append("<input type='text' name='target_namespace' />") - target_field.append("/" + project_name) - target_field.data("project_name", project_name) - target_field.find('input').prop("value", origin_namespace) - import_button.enable().removeClass('is-loading') diff --git a/app/views/issues/_issue.atom.builder b/app/views/issues/_issue.atom.builder index 0c113c08526..21cf6d0dd65 100644 --- a/app/views/issues/_issue.atom.builder +++ b/app/views/issues/_issue.atom.builder @@ -3,7 +3,7 @@ xml.entry do xml.link href: project_issue_url(issue.project, issue) xml.title truncate(issue.title, length: 80) xml.updated issue.updated_at.xmlschema - xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(issue.author_email)) + xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon_for_user(issue.author)) xml.author do xml.name issue.author_name diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index eba9cd253bb..f0963cf9da8 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -1,7 +1,7 @@ .layout-page{ class: page_with_sidebar_class } - if defined?(nav) && nav = render "layouts/nav/sidebar/#{nav}" - .content-wrapper + .content-wrapper{ class: "#{@content_wrapper_class}" } = render 'shared/outdated_browser' .mobile-overlay .alert-wrapper diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index e7fc83a8d04..1d00ae928f6 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -45,7 +45,7 @@ = todos_count_format(todos_pending_count) %li.header-user.dropdown = link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do - = image_tag avatar_icon(current_user, 23), width: 23, height: 23, class: "header-user-avatar qa-user-avatar" + = image_tag avatar_icon_for_user(current_user, 23), width: 23, height: 23, class: "header-user-avatar qa-user-avatar" = sprite_icon('angle-down', css_class: 'caret-down') .dropdown-menu-nav.dropdown-menu-align-right %ul diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml index 2b98cb9de99..059571f795f 100644 --- a/app/views/layouts/nav/sidebar/_project.html.haml +++ b/app/views/layouts/nav/sidebar/_project.html.haml @@ -127,6 +127,19 @@ = link_to project_milestones_path(@project), title: 'Milestones' do %span Milestones + - if project_nav_tab? :external_issue_tracker + = nav_link do + - issue_tracker = @project.external_issue_tracker + = link_to issue_tracker.issue_tracker_path, class: 'shortcuts-external_tracker' do + .nav-icon-container + = sprite_icon('issue-external') + %span.nav-item-name + = issue_tracker.title + %ul.sidebar-sub-level-items.is-fly-out-only + = nav_link(html_options: { class: "fly-out-top-item" } ) do + = link_to issue_tracker.issue_tracker_path do + %strong.fly-out-top-item-name + = issue_tracker.title - if project_nav_tab? :merge_requests = nav_link(controller: @project.issues_enabled? ? :merge_requests : [:merge_requests, :labels, :milestones]) do diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml index 8eb3f2d5192..38dab104eb5 100644 --- a/app/views/notify/pipeline_failed_email.html.haml +++ b/app/views/notify/pipeline_failed_email.html.haml @@ -60,7 +60,7 @@ %tbody %tr %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } - %img.avatar{ height: "24", src: avatar_icon(commit.author || commit.author_email, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "" }/ + %img.avatar{ height: "24", src: avatar_icon_for(commit.author, commit.author_email, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "" }/ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } - if commit.author %a.muted{ href: user_url(commit.author), style: "color:#333333;text-decoration:none;" } @@ -76,7 +76,7 @@ %tbody %tr %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } - %img.avatar{ height: "24", src: avatar_icon(commit.committer || commit.committer_email, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "" }/ + %img.avatar{ height: "24", src: avatar_icon_for(commit.committer, commit.committer_email, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "" }/ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } - if commit.committer %a.muted{ href: user_url(commit.committer), style: "color:#333333;text-decoration:none;" } @@ -100,7 +100,7 @@ triggered by - if @pipeline.user %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;padding-left:5px", width: "24" } - %img.avatar{ height: "24", src: avatar_icon(@pipeline.user, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "" }/ + %img.avatar{ height: "24", src: avatar_icon_for_user(@pipeline.user, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "" }/ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;font-weight:500;line-height:1.4;vertical-align:baseline;" } %a.muted{ href: user_url(@pipeline.user), style: "color:#333333;text-decoration:none;" } = @pipeline.user.name diff --git a/app/views/notify/pipeline_success_email.html.haml b/app/views/notify/pipeline_success_email.html.haml index bae37292d62..7b06e8afa0b 100644 --- a/app/views/notify/pipeline_success_email.html.haml +++ b/app/views/notify/pipeline_success_email.html.haml @@ -60,7 +60,7 @@ %tbody %tr %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } - %img.avatar{ height: "24", src: avatar_icon(commit.author || commit.author_email, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "" }/ + %img.avatar{ height: "24", src: avatar_icon_for(commit.author, commit.author_email, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "" }/ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } - if commit.author %a.muted{ href: user_url(commit.author), style: "color:#333333;text-decoration:none;" } @@ -76,7 +76,7 @@ %tbody %tr %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } - %img.avatar{ height: "24", src: avatar_icon(commit.committer || commit.committer_email, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "" }/ + %img.avatar{ height: "24", src: avatar_icon_for(commit.committer, commit.committer_email, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "" }/ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } - if commit.committer %a.muted{ href: user_url(commit.committer), style: "color:#333333;text-decoration:none;" } @@ -100,7 +100,7 @@ triggered by - if @pipeline.user %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;padding-left:5px", width: "24" } - %img.avatar{ height: "24", src: avatar_icon(@pipeline.user, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "" }/ + %img.avatar{ height: "24", src: avatar_icon_for_user(@pipeline.user, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "" }/ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;font-weight:500;line-height:1.4;vertical-align:baseline;" } %a.muted{ href: user_url(@pipeline.user), style: "color:#333333;text-decoration:none;" } = @pipeline.user.name diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index 5c76d2d8f51..110736dc557 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -20,8 +20,8 @@ or change it at #{link_to Gitlab.config.gravatar.host, 'https://' + Gitlab.config.gravatar.host} .col-lg-8 .clearfix.avatar-image.append-bottom-default - = link_to avatar_icon(@user, 400), target: '_blank', rel: 'noopener noreferrer' do - = image_tag avatar_icon(@user, 160), alt: '', class: 'avatar s160' + = link_to avatar_icon_for_user(@user, 400), target: '_blank', rel: 'noopener noreferrer' do + = image_tag avatar_icon_for_user(@user, 160), alt: '', class: 'avatar s160' %h5.prepend-top-0= _("Upload new avatar") .prepend-top-5.append-bottom-10 %button.btn.js-choose-user-avatar-button{ type: 'button' }= _("Choose file...") diff --git a/app/views/projects/clusters/show.html.haml b/app/views/projects/clusters/show.html.haml index a60afde06d2..2b1b23ba198 100644 --- a/app/views/projects/clusters/show.html.haml +++ b/app/views/projects/clusters/show.html.haml @@ -14,7 +14,8 @@ cluster_status: @cluster.status_name, cluster_status_reason: @cluster.status_reason, help_path: help_page_path('user/project/clusters/index.md', anchor: 'installing-applications'), - ingress_help_path: help_page_path('user/project/clusters/index.md', anchor: 'getting-the-external-ip-address') } } + ingress_help_path: help_page_path('user/project/clusters/index.md', anchor: 'getting-the-external-ip-address'), + manage_prometheus_path: edit_project_service_path(@cluster.project, 'prometheus') } } .js-cluster-application-notice .flash-container diff --git a/app/views/projects/commits/_commit.atom.builder b/app/views/projects/commits/_commit.atom.builder index 04914888763..50f7e7a3a33 100644 --- a/app/views/projects/commits/_commit.atom.builder +++ b/app/views/projects/commits/_commit.atom.builder @@ -3,7 +3,7 @@ xml.entry do xml.link href: project_commit_url(@project, id: commit.id) xml.title truncate(commit.title, length: 80, escape: false) xml.updated commit.committed_date.xmlschema - xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(commit.author_email)) + xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon_for_email(commit.author_email)) xml.author do |author| xml.name commit.author_name diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index 64259669c19..6ff7bcae54f 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -51,7 +51,7 @@ - if commit.status(ref) = render_commit_status(commit, ref: ref) - #commit-pipeline-status{ data: { endpoint: pipelines_project_commit_path(project, commit.id) } } + .js-commit-pipeline-status{ data: { endpoint: pipelines_project_commit_path(project, commit.id) } } = link_to commit.short_id, link, class: "commit-sha btn btn-transparent btn-link" = clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard")) = link_to_browse_code(project, commit) diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index e16d132f869..0931ceb1512 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -58,7 +58,7 @@ - if @project.avatar? %hr = link_to _('Remove avatar'), project_avatar_path(@project), data: { confirm: _("Avatar will be removed. Are you sure?") }, method: :delete, class: "btn btn-danger btn-inverted" - = f.submit 'Save changes', class: "btn btn-success" + = f.submit 'Save changes', class: "btn btn-success js-btn-save-general-project-settings" %section.settings.sharing-permissions.no-animate{ class: ('expanded' if expanded) } .settings-header diff --git a/app/views/projects/environments/metrics.html.haml b/app/views/projects/environments/metrics.html.haml index 5257b42548e..10812f67cbe 100644 --- a/app/views/projects/environments/metrics.html.haml +++ b/app/views/projects/environments/metrics.html.haml @@ -13,6 +13,7 @@ = link_to @environment.name, environment_path(@environment) #prometheus-graphs{ data: { "settings-path": edit_project_service_path(@project, 'prometheus'), + "clusters-path": project_clusters_path(@project), "documentation-path": help_page_path('administration/monitoring/prometheus/index.md'), "empty-getting-started-svg-path": image_path('illustrations/monitoring/getting_started.svg'), "empty-loading-svg-path": image_path('illustrations/monitoring/loading.svg'), diff --git a/app/views/projects/graphs/charts.html.haml b/app/views/projects/graphs/charts.html.haml index b46a1207889..efdb494e1ae 100644 --- a/app/views/projects/graphs/charts.html.haml +++ b/app/views/projects/graphs/charts.html.haml @@ -5,7 +5,7 @@ .repo-charts{ class: container_class } %h4.sub-header - Programming languages used in this repository + = _("Programming languages used in this repository") .row .col-md-4 @@ -28,9 +28,11 @@ .row.tree-ref-header .col-md-6 %h4 - Commit statistics for - %strong= @ref - #{@commits_graph.start_date.strftime('%b %d')} - #{@commits_graph.end_date.strftime('%b %d')} + - start_time = capture do + #{@commits_graph.start_date.strftime('%b %d')} + - end_time = capture do + #{@commits_graph.end_date.strftime('%b %d')} + = (_("Commit statistics for %{ref} %{start_time} - %{end_time}") % { ref: "<strong>#{@ref}</strong>", start_time: start_time, end_time: end_time }).html_safe .col-md-6 .tree-ref-container @@ -43,32 +45,35 @@ .col-md-6 %ul.commit-stats %li - Total: - %strong #{@commits_graph.commits.size} commits + - total = capture do + #{@commits_graph.commits.size} + = (_("Total: %{total}") % { total: "<strong>#{total} commits</strong>" }).html_safe %li - Average per day: - %strong #{@commits_graph.commit_per_day} commits + - average = capture do + #{@commits_graph.commit_per_day} + = (_("Average per day: %{average}") % { average: "<strong>#{average} commits</strong>" }).html_safe %li - Authors: - %strong= @commits_graph.authors + - authors = capture do + #{@commits_graph.authors} + = (_("Authors: %{authors}") % { authors: "<strong>#{authors}</strong>" }).html_safe .col-md-6 %div %p.slead - Commits per day of month + = _("Commits per day of month") %canvas#month-chart .row .col-md-6 .col-md-6 %div %p.slead - Commits per weekday + = _("Commits per weekday") %canvas#weekday-chart .row .col-md-6 .col-md-6 %div %p.slead - Commits per day hour (UTC) + = _("Commits per day hour (UTC)") %canvas#hour-chart %script#projectChartData{ type: "application/json" } diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml index 9779c1985d5..11b5e02f1e0 100644 --- a/app/views/projects/issues/_discussion.html.haml +++ b/app/views/projects/issues/_discussion.html.haml @@ -12,6 +12,8 @@ markdown_docs_path: help_page_path('user/markdown'), quick_actions_docs_path: help_page_path('user/project/quick_actions'), notes_path: notes_url, + close_issue_path: issue_path(@issue, issue: { state_event: :close }, format: 'json'), + reopen_issue_path: issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), last_fetched_at: Time.now.to_i, noteable_data: serialize_issuable(@issue), current_user_data: UserSerializer.new.represent(current_user, only_path: true).to_json } } diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml index 37b00a14fc6..36e24037214 100644 --- a/app/views/projects/issues/_new_branch.html.haml +++ b/app/views/projects/issues/_new_branch.html.haml @@ -21,30 +21,33 @@ %button.btn.create-merge-request-dropdown-toggle.dropdown-toggle.btn-success.btn-inverted.js-dropdown-toggle{ type: 'button', data: { dropdown: { trigger: '#create-merge-request-dropdown' } } } = icon('caret-down') - %ul#create-merge-request-dropdown.create-merge-request-dropdown-menu.dropdown-menu.dropdown-menu-align-right.gl-show-field-errors{ data: { dropdown: true } } - - if can_create_merge_request - %li.create-item.droplab-item-selected.droplab-item-ignore-hiding{ role: 'button', data: { value: 'create-mr', text: 'Create merge request' } } - .menu-item.droplab-item-ignore-hiding - .icon-container.droplab-item-ignore-hiding= icon('check') - .description.droplab-item-ignore-hiding Create merge request and branch - - %li.create-item.droplab-item-ignore-hiding{ class: [!can_create_merge_request && 'droplab-item-selected'], role: 'button', data: { value: 'create-branch', text: 'Create branch' } } - .menu-item.droplab-item-ignore-hiding - .icon-container.droplab-item-ignore-hiding= icon('check') - .description.droplab-item-ignore-hiding Create branch - %li.divider - - %li.droplab-item-ignore - Branch name - %input.js-branch-name.form-control.droplab-item-ignore{ type: 'text', placeholder: "#{@issue.to_branch_name}", value: "#{@issue.to_branch_name}" } - %span.js-branch-message.branch-message.droplab-item-ignore - - %li.droplab-item-ignore - Source (branch or tag) - %input.js-ref.ref.form-control.droplab-item-ignore{ type: 'text', placeholder: "#{@project.default_branch}", value: "#{@project.default_branch}", data: { value: "#{@project.default_branch}" } } - %span.js-ref-message.ref-message.droplab-item-ignore - - %li.droplab-item-ignore - %button.btn.btn-success.js-create-target.droplab-item-ignore{ type: 'button', data: { action: 'create-mr' } } - Create merge request - + .droplab-dropdown + %ul#create-merge-request-dropdown.create-merge-request-dropdown-menu.dropdown-menu.dropdown-menu-align-right.gl-show-field-errors{ data: { dropdown: true } } + - if can_create_merge_request + %li.droplab-item-selected{ role: 'button', data: { value: 'create-mr', text: _('Create merge request') } } + .menu-item + = icon('check', class: 'icon') + = _('Create merge request and branch') + + %li{ class: [!can_create_merge_request && 'droplab-item-selected'], role: 'button', data: { value: 'create-branch', text: _('Create branch') } } + .menu-item + = icon('check', class: 'icon') + = _('Create branch') + %li.divider.droplab-item-ignore + + %li.droplab-item-ignore.prepend-left-8.append-right-8.prepend-top-16 + .form-group + %label{ for: 'new-branch-name' } + = _('Branch name') + %input#new-branch-name.js-branch-name.form-control{ type: 'text', placeholder: "#{@issue.to_branch_name}", value: "#{@issue.to_branch_name}" } + %span.js-branch-message.help-block + + .form-group + %label{ for: 'source-name' } + = _('Source (branch or tag)') + %input#source-name.js-ref.ref.form-control{ type: 'text', placeholder: "#{@project.default_branch}", value: "#{@project.default_branch}", data: { value: "#{@project.default_branch}" } } + %span.js-ref-message.help-block + + .form-group + %button.btn.btn-success.js-create-target{ type: 'button', data: { action: 'create-mr' } } + = _('Create merge request') diff --git a/app/views/projects/jobs/_user.html.haml b/app/views/projects/jobs/_user.html.haml index 83f299da651..461d503f95d 100644 --- a/app/views/projects/jobs/_user.html.haml +++ b/app/views/projects/jobs/_user.html.haml @@ -1,7 +1,7 @@ by %a{ href: user_path(@build.user) } %span.hidden-xs - = image_tag avatar_icon(@build.user, 24), class: "avatar s24" + = image_tag avatar_icon_for_user(@build.user, 24), class: "avatar s24" %strong{ data: { toggle: 'tooltip', placement: 'top', title: @build.user.to_reference } } = @build.user.name %strong.visible-xs-inline= @build.user.to_reference diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index 623c42ba88e..de381d489c6 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -27,7 +27,7 @@ Edit - if @project.group - = link_to promote_project_milestone_path(@milestone.project, @milestone), title: "Promote to Group Milestone", class: 'btn btn-grouped', data: { confirm: "You are about to promote #{@milestone.title} to a group level. This will make this milestone available to all projects inside #{@project.group.name}. The existing project milestone will be merged into the group level. This action cannot be reversed.", toggle: "tooltip" }, method: :post do + = link_to promote_project_milestone_path(@milestone.project, @milestone), title: "Promote to Group Milestone", class: 'btn btn-grouped', data: { confirm: "Promoting #{@milestone.title} will make it available for all projects inside #{@project.group.name}. Existing project milestones with the same name will be merged. This action cannot be reversed.", toggle: "tooltip" }, method: :post do Promote - if @milestone.active? diff --git a/app/views/projects/network/_head.html.haml b/app/views/projects/network/_head.html.haml index 8f6805268d5..f08526f485e 100644 --- a/app/views/projects/network/_head.html.haml +++ b/app/views/projects/network/_head.html.haml @@ -6,4 +6,4 @@ = render partial: 'shared/ref_switcher', locals: {destination: 'graph'} .oneline - You can move around the graph by using the arrow keys. + = _("You can move around the graph by using the arrow keys.") diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml index 9f68a91759f..97be8950db0 100644 --- a/app/views/projects/network/show.html.haml +++ b/app/views/projects/network/show.html.haml @@ -7,14 +7,14 @@ .project-network .controls = form_tag project_network_path(@project, @id), method: :get, class: 'form-inline network-form' do |f| - = text_field_tag :extended_sha1, @options[:extended_sha1], placeholder: "Git revision", class: 'search-input form-control input-mx-250 search-sha' + = text_field_tag :extended_sha1, @options[:extended_sha1], placeholder: _("Git revision"), class: 'search-input form-control input-mx-250 search-sha' = button_tag class: 'btn btn-success' do = icon('search') .inline.prepend-left-20 .checkbox.light = label_tag :filter_ref do = check_box_tag :filter_ref, 1, @options[:filter_ref] - %span Begin with the selected commit + %span= _("Begin with the selected commit") - if @commit .network-graph{ data: { url: @url, commit_url: @commit_url, ref: @ref, commit_id: @commit.id } } diff --git a/app/views/projects/network/show.json.erb b/app/views/projects/network/show.json.erb index 122e84b41b2..a0e82e891ff 100644 --- a/app/views/projects/network/show.json.erb +++ b/app/views/projects/network/show.json.erb @@ -9,11 +9,11 @@ author: { name: c.author_name, email: c.author_email, - icon: image_path(avatar_icon(c.author_email, 20)) + icon: image_path(avatar_icon_for_email(c.author_email, 20)) }, time: c.time, space: c.spaces.first, - refs: get_refs(@graph.repo, c), + refs: refs(@graph.repo, c), id: c.sha, date: c.date, message: c.message, diff --git a/app/views/projects/pipeline_schedules/_form.html.haml b/app/views/projects/pipeline_schedules/_form.html.haml index ff440e99042..160e325996a 100644 --- a/app/views/projects/pipeline_schedules/_form.html.haml +++ b/app/views/projects/pipeline_schedules/_form.html.haml @@ -1,7 +1,3 @@ -- content_for :page_specific_javascripts do - = webpack_bundle_tag 'common_vue' - = webpack_bundle_tag 'schedule_form' - = form_for [@project.namespace.becomes(Namespace), @project, @schedule], as: :schedule, html: { id: "new-pipeline-schedule-form", class: "form-horizontal js-pipeline-schedule-form" } do |f| = form_errors(@schedule) .form-group diff --git a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml index a8692b83b07..55d0e8bb7f9 100644 --- a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml +++ b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml @@ -21,7 +21,7 @@ = s_("PipelineSchedules|Inactive") %td - if pipeline_schedule.owner - = image_tag avatar_icon(pipeline_schedule.owner, 20), class: "avatar s20" + = image_tag avatar_icon_for_user(pipeline_schedule.owner, 20), class: "avatar s20" = link_to user_path(pipeline_schedule.owner) do = pipeline_schedule.owner&.name %td diff --git a/app/views/projects/pipeline_schedules/index.html.haml b/app/views/projects/pipeline_schedules/index.html.haml index 4fbdd1dd5e4..bcb6dddba1a 100644 --- a/app/views/projects/pipeline_schedules/index.html.haml +++ b/app/views/projects/pipeline_schedules/index.html.haml @@ -1,9 +1,5 @@ - breadcrumb_title _("Schedules") -- content_for :page_specific_javascripts do - = webpack_bundle_tag 'common_vue' - = webpack_bundle_tag 'schedules_index' - - @no_container = true - page_title _("Pipeline Schedules") diff --git a/app/views/projects/pipelines/charts.html.haml b/app/views/projects/pipelines/charts.html.haml index 8512ac2a89b..a86cb14960a 100644 --- a/app/views/projects/pipelines/charts.html.haml +++ b/app/views/projects/pipelines/charts.html.haml @@ -1,8 +1,6 @@ - @no_container = true - breadcrumb_title "CI / CD Charts" - page_title _("Charts"), _("Pipelines") -- content_for :page_specific_javascripts do - = webpack_bundle_tag('common_d3') %div{ class: container_class } .sub-header-block diff --git a/app/views/projects/pipelines/charts/_pipeline_times.haml b/app/views/projects/pipelines/charts/_pipeline_times.haml index a5dbd1b1532..510697c2ae9 100644 --- a/app/views/projects/pipelines/charts/_pipeline_times.haml +++ b/app/views/projects/pipelines/charts/_pipeline_times.haml @@ -1,6 +1,3 @@ -- content_for :page_specific_javascripts do - = webpack_bundle_tag('pipelines_times') - %div %p.light = _("Commit duration in minutes for last 30 commits") diff --git a/app/views/projects/pipelines/charts/_pipelines.haml b/app/views/projects/pipelines/charts/_pipelines.haml index 41dc2f6cf9d..2f4b6def155 100644 --- a/app/views/projects/pipelines/charts/_pipelines.haml +++ b/app/views/projects/pipelines/charts/_pipelines.haml @@ -1,6 +1,3 @@ -- content_for :page_specific_javascripts do - = webpack_bundle_tag('pipelines_charts') - %h4= _("Pipelines charts") %p diff --git a/app/views/projects/repositories/_feed.html.haml b/app/views/projects/repositories/_feed.html.haml index 170f9e259df..87895a15239 100644 --- a/app/views/projects/repositories/_feed.html.haml +++ b/app/views/projects/repositories/_feed.html.haml @@ -11,7 +11,7 @@ %div = link_to project_commits_path(@project, commit.id) do %code= commit.short_id - = image_tag avatar_icon(commit.author_email), class: "", width: 16, alt: '' + = image_tag avatar_icon_for_email(commit.author_email), class: "", width: 16, alt: '' = markdown(truncate(commit.title, length: 40), pipeline: :single_line, author: commit.author) %td %span.pull-right.cgray diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml index 21acd857ce7..17e804d682b 100644 --- a/app/views/projects/services/_form.html.haml +++ b/app/views/projects/services/_form.html.haml @@ -1,6 +1,3 @@ -- content_for :page_specific_javascripts do - = webpack_bundle_tag('integrations') - .row.prepend-top-default.append-bottom-default .col-lg-3 %h4.prepend-top-0 @@ -9,7 +6,7 @@ %p= @service.description .col-lg-9 - = form_for(@service, as: :service, url: project_service_path(@project, @service.to_param), method: :put, html: { class: 'gl-show-field-errors form-horizontal js-integration-settings-form', data: { 'can-test' => @service.can_test?, 'test-url' => test_project_service_path(@project, @service) } }) do |form| + = form_for(@service, as: :service, url: project_service_path(@project, @service.to_param), method: :put, html: { class: 'gl-show-field-errors form-horizontal integration-settings-form js-integration-settings-form', data: { 'can-test' => @service.can_test?, 'test-url' => test_project_service_path(@project, @service) } }) do |form| = render 'shared/service_settings', form: form, subject: @service - if @service.editable? .footer-block.row-content-block diff --git a/app/views/projects/services/prometheus/_help.html.haml b/app/views/projects/services/prometheus/_help.html.haml new file mode 100644 index 00000000000..5e320a252d8 --- /dev/null +++ b/app/views/projects/services/prometheus/_help.html.haml @@ -0,0 +1,33 @@ +%h4 + = s_('PrometheusService|Auto configuration') + +- if @service.manual_configuration? + .well + = s_('PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below') +- else + .container-fluid + .row + - if @service.prometheus_installed? + .col-sm-2 + .svg-container + = image_tag 'illustrations/monitoring/getting_started.svg' + .col-sm-10 + %p.text-success.prepend-top-default + = s_('PrometheusService|Prometheus is being automatically managed on your clusters') + = link_to s_('PrometheusService|Manage clusters'), project_clusters_path(@project), class: 'btn' + - else + .col-sm-2 + = image_tag 'illustrations/monitoring/loading.svg' + .col-sm-10 + %p.prepend-top-default + = s_('PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments') + = link_to s_('PrometheusService|Install Prometheus on clusters'), project_clusters_path(@project), class: 'btn btn-success' + +%hr + +%h4.append-bottom-default + = s_('PrometheusService|Manual configuration') + +- unless @service.editable? + .well + = s_('PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters') diff --git a/app/views/projects/services/prometheus/_show.html.haml b/app/views/projects/services/prometheus/_show.html.haml index b0cb5ce5e8f..5f38ecd6820 100644 --- a/app/views/projects/services/prometheus/_show.html.haml +++ b/app/views/projects/services/prometheus/_show.html.haml @@ -1,6 +1,3 @@ -- content_for :page_specific_javascripts do - = webpack_bundle_tag('prometheus_metrics') - .row.prepend-top-default.append-bottom-default.prometheus-metrics-monitoring.js-prometheus-metrics-monitoring .col-lg-3 %h4.prepend-top-0 diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml index 664a4554692..756f31f91d9 100644 --- a/app/views/projects/settings/ci_cd/show.html.haml +++ b/app/views/projects/settings/ci_cd/show.html.haml @@ -29,14 +29,14 @@ %section.settings.no-animate{ class: ('expanded' if expanded) } .settings-header %h4 - Secret variables - = link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'secret-variables'), target: '_blank' + = _('Secret variables') + = link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'secret-variables'), target: '_blank', rel: 'noopener noreferrer' %button.btn.js-settings-toggle = expanded ? 'Collapse' : 'Expand' - %p + %p.append-bottom-0 = render "ci/variables/content" .settings-content - = render 'ci/variables/index' + = render 'ci/variables/index', save_endpoint: project_variables_path(@project) %section.settings.no-animate{ class: ('expanded' if expanded) } .settings-header diff --git a/app/views/projects/update.js.haml b/app/views/projects/update.js.haml index 2c05ebe52ae..1a353953838 100644 --- a/app/views/projects/update.js.haml +++ b/app/views/projects/update.js.haml @@ -6,4 +6,4 @@ $(".project-edit-errors").html("#{escape_javascript(render('errors'))}"); $('.save-project-loader').hide(); $('.project-edit-container').show(); - $('.edit-project .btn-save').enable(); + $('.edit-project .js-btn-save-general-project-settings').enable(); diff --git a/app/views/projects/variables/show.html.haml b/app/views/projects/variables/show.html.haml deleted file mode 100644 index df533952b76..00000000000 --- a/app/views/projects/variables/show.html.haml +++ /dev/null @@ -1 +0,0 @@ -= render 'ci/variables/show' diff --git a/app/views/search/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml index c4a5131c1a7..57a0b64bfd5 100644 --- a/app/views/search/results/_snippet_blob.html.haml +++ b/app/views/search/results/_snippet_blob.html.haml @@ -7,7 +7,7 @@ = snippet.title by = link_to user_snippets_path(snippet.author) do - = image_tag avatar_icon(snippet.author), class: "avatar avatar-inline s16", alt: '' + = image_tag avatar_icon_for_user(snippet.author), class: "avatar avatar-inline s16", alt: '' = snippet.author_name %span.light= time_ago_with_tooltip(snippet.created_at) %h4.snippet-title diff --git a/app/views/search/results/_snippet_title.html.haml b/app/views/search/results/_snippet_title.html.haml index aef825691e0..65710c09a89 100644 --- a/app/views/search/results/_snippet_title.html.haml +++ b/app/views/search/results/_snippet_title.html.haml @@ -18,6 +18,6 @@ %span by = link_to user_snippets_path(snippet_title.author) do - = image_tag avatar_icon(snippet_title.author), class: "avatar avatar-inline s16", alt: '' + = image_tag avatar_icon_for_user(snippet_title.author), class: "avatar avatar-inline s16", alt: '' = snippet_title.author_name %span.light= time_ago_with_tooltip(snippet_title.created_at) diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index c0eebdfaddd..8847d11f623 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -48,7 +48,7 @@ .pull-right.hidden-xs.hidden-sm - if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_label, label.project.group) - = link_to promote_project_label_path(label.project, label), title: "Promote to Group Label", class: 'btn btn-transparent btn-action', data: {confirm: "You are about to promote #{label.title} to a group level. This will make this milestone available to all projects inside #{label.project.group.name}. The existing project label will be merged into the group level. This action cannot be reversed.", toggle: "tooltip"}, method: :post do + = link_to promote_project_label_path(label.project, label), title: "Promote to Group Label", class: 'btn btn-transparent btn-action', data: {confirm: "Promoting #{label.title} will make it available for all projects inside #{label.project.group.name}. Existing project labels with the same name will be merged. This action cannot be reversed.", toggle: "tooltip"}, method: :post do %span.sr-only Promote to Group = sprite_icon('level-up') - if can?(current_user, :admin_label, label) diff --git a/app/views/shared/groups/_dropdown.html.haml b/app/views/shared/groups/_dropdown.html.haml index 1a259b679c7..8607be9cd06 100644 --- a/app/views/shared/groups/_dropdown.html.haml +++ b/app/views/shared/groups/_dropdown.html.haml @@ -23,11 +23,11 @@ - if show_archive_options %li.divider %li.js-filter-archived-projects - = link_to group_children_path(@group, archived: nil), class: ("is-active" unless params[:archived].present?) do + = link_to filter_groups_path(archived: nil), class: ("is-active" unless params[:archived].present?) do Hide archived projects %li.js-filter-archived-projects - = link_to group_children_path(@group, archived: true), class: ("is-active" if Gitlab::Utils.to_boolean(params[:archived])) do + = link_to filter_groups_path(archived: true), class: ("is-active" if Gitlab::Utils.to_boolean(params[:archived])) do Show archived projects %li.js-filter-archived-projects - = link_to group_children_path(@group, archived: 'only'), class: ("is-active" if params[:archived] == 'only') do + = link_to filter_groups_path(archived: 'only'), class: ("is-active" if params[:archived] == 'only') do Show archived projects only diff --git a/app/views/shared/issuable/_label_page_create.html.haml b/app/views/shared/issuable/_label_page_create.html.haml index 0a692d9653f..d5e7d3b87b7 100644 --- a/app/views/shared/issuable/_label_page_create.html.haml +++ b/app/views/shared/issuable/_label_page_create.html.haml @@ -2,16 +2,16 @@ = dropdown_title("Create new label", options: { back: true }) = dropdown_content do .dropdown-labels-error.js-label-error - %input#new_label_name.default-dropdown-input{ type: "text", placeholder: "Name new label" } + %input#new_label_name.default-dropdown-input{ type: "text", placeholder: _('Name new label') } .suggest-colors.suggest-colors-dropdown - suggested_colors.each do |color| = link_to '#', style: "background-color: #{color}", data: { color: color } do   .dropdown-label-color-input .dropdown-label-color-preview.js-dropdown-label-color-preview - %input#new_label_color.default-dropdown-input{ type: "text", placeholder: "Assign custom color like #FF0000" } + %input#new_label_color.default-dropdown-input{ type: "text", placeholder: _('Assign custom color like #FF0000') } .clearfix %button.btn.btn-primary.pull-left.js-new-label-btn{ type: "button" } - Create + = _('Create') %button.btn.btn-default.pull-right.js-cancel-label-btn{ type: "button" } - Cancel + = _('Cancel') diff --git a/app/views/shared/issuable/_label_page_default.html.haml b/app/views/shared/issuable/_label_page_default.html.haml index ad031e6af80..6a83321abcb 100644 --- a/app/views/shared/issuable/_label_page_default.html.haml +++ b/app/views/shared/issuable/_label_page_default.html.haml @@ -1,4 +1,4 @@ -- title = local_assigns.fetch(:title, 'Assign labels') +- title = local_assigns.fetch(:title, _('Assign labels')) - show_create = local_assigns.fetch(:show_create, true) - show_footer = local_assigns.fetch(:show_footer, true) - filter_placeholder = local_assigns.fetch(:filter_placeholder, 'Search') @@ -8,7 +8,7 @@ - if show_boards_content .issue-board-dropdown-content %p - Create lists from labels. Issues with that label appear in that list. + = _('Create lists from labels. Issues with that label appear in that list.') = dropdown_filter(filter_placeholder) = dropdown_content - if current_board_parent && show_footer @@ -17,11 +17,11 @@ - if can?(current_user, :admin_label, current_board_parent) %li %a.dropdown-toggle-page{ href: "#" } - Create new label + = _('Create new label') %li = link_to labels_path, :"data-is-link" => true do - if show_create && can?(current_user, :admin_label, current_board_parent) - Manage labels + = _('Manage labels') - else - View labels + = _('View labels') = dropdown_loading diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index f384f8da11a..dc583d3eb3b 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -9,7 +9,7 @@ .block.issuable-sidebar-header - if current_user %span.issuable-header-text.hide-collapsed.pull-left - Todo + = _('Todo') %a.gutter-toggle.pull-right.js-sidebar-toggle{ role: "button", href: "#", "aria-label" => "Toggle sidebar" } = sidebar_gutter_toggle_icon - if current_user @@ -29,9 +29,9 @@ %span.has-tooltip{ title: "#{issuable.milestone.title}<br>#{milestone_tooltip_title(issuable.milestone)}", data: { container: 'body', html: 1, placement: 'left' } } = issuable.milestone.title - else - None + = _('None') .title.hide-collapsed - Milestone + = _('Milestone') = icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true') - if can_edit_issuable = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link pull-right' @@ -39,16 +39,17 @@ - if issuable.milestone = link_to issuable.milestone.title, milestone_path(issuable.milestone), class: "bold has-tooltip", title: milestone_tooltip_title(issuable.milestone), data: { container: "body", html: 1 } - else - %span.no-value None + %span.no-value + = _('None') .selectbox.hide-collapsed = f.hidden_field 'milestone_id', value: issuable.milestone_id, id: nil - = dropdown_tag('Milestone', options: { title: 'Assign milestone', toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: 'Search milestones', data: { show_no: true, field_name: "#{issuable.to_ability_name}[milestone_id]", project_id: @project.id, issuable_id: issuable.id, milestones: project_milestones_path(@project, :json), ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable), use_id: true, default_no: true, selected: (issuable.milestone.name if issuable.milestone), null_default: true }}) + = dropdown_tag('Milestone', options: { title: _('Assign milestone'), toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: _('Search milestones'), data: { show_no: true, field_name: "#{issuable.to_ability_name}[milestone_id]", project_id: @project.id, issuable_id: issuable.id, milestones: project_milestones_path(@project, :json), ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable), use_id: true, default_no: true, selected: (issuable.milestone.name if issuable.milestone), null_default: true }}) - if issuable.has_attribute?(:time_estimate) #issuable-time-tracker.block // Fallback while content is loading .title.hide-collapsed - Time tracking + = _('Time tracking') = icon('spinner spin', 'aria-hidden': 'true') - if issuable.has_attribute?(:due_date) .block.due_date @@ -57,7 +58,7 @@ %span.js-due-date-sidebar-value = issuable.due_date.try(:to_s, :medium) || 'None' .title.hide-collapsed - Due date + = _('Due date') = icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true') - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link pull-right' @@ -66,21 +67,23 @@ - if issuable.due_date %span.bold= issuable.due_date.to_s(:medium) - else - %span.no-value No due date + %span.no-value + = _('No due date') - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) %span.no-value.js-remove-due-date-holder{ class: ("hidden" if issuable.due_date.nil?) } \- %a.js-remove-due-date{ href: "#", role: "button" } - remove due date + = _('remove due date') - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) .selectbox.hide-collapsed = f.hidden_field :due_date, value: issuable.due_date.try(:strftime, 'yy-mm-dd') .dropdown %button.dropdown-menu-toggle.js-due-date-select{ type: 'button', data: { toggle: 'dropdown', field_name: "#{issuable.to_ability_name}[due_date]", ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable) } } - %span.dropdown-toggle-text Due date + %span.dropdown-toggle-text + = _('Due date') = icon('chevron-down', 'aria-hidden': 'true') .dropdown-menu.dropdown-menu-due-date - = dropdown_title('Due date') + = dropdown_title(_('Due date')) = dropdown_content do .js-due-date-calendar @@ -92,7 +95,7 @@ %span = selected_labels.size .title.hide-collapsed - Labels + = _('Labels') = icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true') - if can_edit_issuable = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link pull-right' @@ -101,7 +104,8 @@ - selected_labels.each do |label| = link_to_label(label, subject: issuable.project, type: issuable.to_ability_name) - else - %span.no-value None + %span.no-value + = _('None') .selectbox.hide-collapsed - selected_labels.each do |label| = hidden_field_tag "#{issuable.to_ability_name}[label_names][]", label.id, id: nil @@ -131,29 +135,29 @@ - project_ref = cross_project_reference(@project, issuable) .block.project-reference .sidebar-collapsed-icon.dont-change-state - = clipboard_button(text: project_ref, title: "Copy reference to clipboard", placement: "left") + = clipboard_button(text: project_ref, title: _('Copy reference to clipboard'), placement: "left") .cross-project-reference.hide-collapsed %span - Reference: + = _('Reference:') %cite{ title: project_ref } = project_ref - = clipboard_button(text: project_ref, title: "Copy reference to clipboard", placement: "left") + = clipboard_button(text: project_ref, title: _('Copy reference to clipboard'), placement: "left") - if current_user && issuable.can_move?(current_user) .block.js-sidebar-move-issue-block - .sidebar-collapsed-icon{ data: { toggle: 'tooltip', placement: 'left', container: 'body' }, title: 'Move issue' } + .sidebar-collapsed-icon{ data: { toggle: 'tooltip', placement: 'left', container: 'body' }, title: _('Move issue') } = custom_icon('icon_arrow_right') .dropdown.sidebar-move-issue-dropdown.hide-collapsed %button.btn.btn-default.btn-block.js-sidebar-dropdown-toggle.js-move-issue{ type: 'button', data: { toggle: 'dropdown' } } - Move issue + = _('Move issue') .dropdown-menu.dropdown-menu-selectable - = dropdown_title('Move issue') - = dropdown_filter('Search project', search_id: 'sidebar-move-issue-dropdown-search') + = dropdown_title(_('Move issue')) + = dropdown_filter(_('Search project'), search_id: 'sidebar-move-issue-dropdown-search') = dropdown_content = dropdown_loading = dropdown_footer add_content_class: true do %button.btn.btn-new.sidebar-move-issue-confirmation-button.js-move-issue-confirmation-button{ disabled: true } - Move + = _('Move') = icon('spinner spin', class: 'sidebar-move-issue-confirmation-loading-icon') %script.js-sidebar-options{ type: "application/json" }= issuable_sidebar_options(issuable, can_edit_issuable).to_json.html_safe diff --git a/app/views/shared/issuable/_sidebar_assignees.html.haml b/app/views/shared/issuable/_sidebar_assignees.html.haml index 0fca4162ec9..304df38a096 100644 --- a/app/views/shared/issuable/_sidebar_assignees.html.haml +++ b/app/views/shared/issuable/_sidebar_assignees.html.haml @@ -1,7 +1,7 @@ - if issuable.is_a?(Issue) #js-vue-sidebar-assignees{ data: { field: "#{issuable.to_ability_name}[assignee_ids]", signed_in: signed_in } } .title.hide-collapsed - Assignee + = _('Assignee') = icon('spinner spin') - else .sidebar-collapsed-icon.sidebar-collapsed-user{ data: { toggle: "tooltip", placement: "left", container: "body" }, title: (issuable.assignee.name if issuable.assignee) } @@ -10,35 +10,35 @@ - else = icon('user', 'aria-hidden': 'true') .title.hide-collapsed - Assignee + = _('Assignee') = icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true') - if can_edit_issuable = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link pull-right' - if !signed_in - %a.gutter-toggle.pull-right.js-sidebar-toggle{ role: "button", href: "#", "aria-label" => "Toggle sidebar" } + %a.gutter-toggle.pull-right.js-sidebar-toggle{ role: "button", href: "#", "aria-label" => _('Toggle sidebar') } = sidebar_gutter_toggle_icon .value.hide-collapsed - if issuable.assignee = link_to_member(@project, issuable.assignee, size: 32, extra_class: 'bold') do - if !issuable.can_be_merged_by?(issuable.assignee) - %span.pull-right.cannot-be-merged{ data: { toggle: 'tooltip', placement: 'left' }, title: 'Not allowed to merge' } + %span.pull-right.cannot-be-merged{ data: { toggle: 'tooltip', placement: 'left' }, title: _('Not allowed to merge') } = icon('exclamation-triangle', 'aria-hidden': 'true') %span.username = issuable.assignee.to_reference - else %span.assign-yourself.no-value - No assignee + = _('No assignee') - if can_edit_issuable \- %a.js-assign-yourself{ href: '#' } - assign yourself + = _('assign yourself') .selectbox.hide-collapsed - issuable.assignees.each do |assignee| = hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", assignee.id, id: nil, data: { avatar_url: assignee.avatar_url, name: assignee.name, username: assignee.username } - - options = { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: current_user&.username, current_user: true, project_id: @project&.id, author_id: issuable.author_id, field_name: "#{issuable.to_ability_name}[assignee_ids][]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } } - - title = 'Select assignee' + - options = { toggle_class: 'js-user-search js-author-search', title: _('Assign to'), filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: _('Search users'), data: { first_user: current_user&.username, current_user: true, project_id: @project&.id, author_id: issuable.author_id, field_name: "#{issuable.to_ability_name}[assignee_ids][]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } } + - title = _('Select assignee') - if issuable.is_a?(Issue) - unless issuable.assignees.any? diff --git a/app/views/shared/issuable/_sidebar_todo.html.haml b/app/views/shared/issuable/_sidebar_todo.html.haml index 574e2958ae8..b77e104c072 100644 --- a/app/views/shared/issuable/_sidebar_todo.html.haml +++ b/app/views/shared/issuable/_sidebar_todo.html.haml @@ -1,11 +1,11 @@ - is_collapsed = local_assigns.fetch(:is_collapsed, false) -- mark_content = is_collapsed ? icon('check-square', class: 'todo-undone') : 'Mark done' -- todo_content = is_collapsed ? icon('plus-square') : 'Add todo' +- mark_content = is_collapsed ? icon('check-square', class: 'todo-undone') : _('Mark done') +- todo_content = is_collapsed ? icon('plus-square') : _('Add todo') %button.issuable-todo-btn.js-issuable-todo{ type: 'button', class: (is_collapsed ? 'btn-blank sidebar-collapsed-icon dont-change-state has-tooltip' : 'btn btn-default issuable-header-btn pull-right'), - title: (todo.nil? ? 'Add todo' : 'Mark done'), - 'aria-label' => (todo.nil? ? 'Add todo' : 'Mark done'), + title: (todo.nil? ? _('Add todo') : _('Mark done')), + 'aria-label' => (todo.nil? ? _('Add todo') : _('Mark done')), data: issuable_todo_button_data(issuable, todo, is_collapsed) } %span.issuable-todo-inner.js-issuable-todo-inner< - if todo diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index 71878e93255..ba57d922c6d 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -8,7 +8,7 @@ %li.member{ class: dom_class(member), id: dom_id(member) } %span.list-item-name - if user - = image_tag avatar_icon(user, 40), class: "avatar s40", alt: '' + = image_tag avatar_icon_for_user(user, 40), class: "avatar s40", alt: '' .user-info = link_to user.name, user_path(user), class: 'member' %span.cgray= user.to_reference @@ -36,7 +36,7 @@ Expires in #{distance_of_time_in_words_to_now(member.expires_at)} - else - = image_tag avatar_icon(member.invite_email, 40), class: "avatar s40", alt: '' + = image_tag avatar_icon_for_email(member.invite_email, 40), class: "avatar s40", alt: '' .user-info .member= member.invite_email .cgray diff --git a/app/views/shared/milestones/_issuable.html.haml b/app/views/shared/milestones/_issuable.html.haml index 14395bcc661..479b7270b28 100644 --- a/app/views/shared/milestones/_issuable.html.haml +++ b/app/views/shared/milestones/_issuable.html.haml @@ -28,4 +28,4 @@ - assignees.each do |assignee| = link_to polymorphic_path(issuable_type_args, { milestone_title: @milestone.title, assignee_id: assignee.id, state: 'all' }), class: 'has-tooltip', title: "Assigned to #{assignee.name}", data: { container: 'body' } do - - image_tag(avatar_icon(assignee, 16), class: "avatar s16", alt: '') + - image_tag(avatar_icon_for_user(assignee, 16), class: "avatar s16", alt: '') diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml index e08a49b4e59..e3b2b53833e 100644 --- a/app/views/shared/milestones/_milestone.html.haml +++ b/app/views/shared/milestones/_milestone.html.haml @@ -51,7 +51,7 @@ \ - if @project.group - = link_to promote_project_milestone_path(milestone.project, milestone), title: "Promote to Group Milestone", class: 'btn btn-xs btn-grouped', data: { confirm: "You are about to promote #{milestone.title} to a group level. This will make this milestone available to all projects inside #{@project.group.name}. The existing project milestone will be merged into the group level. This action cannot be reversed.", toggle: "tooltip" }, method: :post do + = link_to promote_project_milestone_path(milestone.project, milestone), title: "Promote to Group Milestone", class: 'btn btn-xs btn-grouped', data: { confirm: "Promoting #{milestone.title} will make it available for all projects inside #{@project.group.name}. Existing project milestones with the same name will be merged. This action cannot be reversed.", toggle: "tooltip" }, method: :post do Promote = link_to 'Close Milestone', project_milestone_path(@project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close btn-grouped" diff --git a/app/views/shared/milestones/_participants_tab.html.haml b/app/views/shared/milestones/_participants_tab.html.haml index 1615871385e..fe83040c168 100644 --- a/app/views/shared/milestones/_participants_tab.html.haml +++ b/app/views/shared/milestones/_participants_tab.html.haml @@ -2,7 +2,7 @@ - users.each do |user| %li = link_to user, title: user.name, class: "darken" do - = image_tag avatar_icon(user, 32), class: "avatar s32" + = image_tag avatar_icon_for_user(user, 32), class: "avatar s32" %strong= truncate(user.name, length: 40) %div %small.cgray= user.username diff --git a/app/views/shared/notes/_note.html.haml b/app/views/shared/notes/_note.html.haml index 98e0161f7d1..bf359774ead 100644 --- a/app/views/shared/notes/_note.html.haml +++ b/app/views/shared/notes/_note.html.haml @@ -16,7 +16,7 @@ = icon_for_system_note(note) - else %a.image-diff-avatar-link{ href: user_path(note.author) } - = image_tag avatar_icon(note.author), alt: '', class: 'avatar s40' + = image_tag avatar_icon_for_user(note.author), alt: '', class: 'avatar s40' - if note.is_a?(DiffNote) && note.on_image? - if show_image_comment_badge && note_counter == 0 -# Only show this for the first comment in the discussion diff --git a/app/views/shared/notes/_notes_with_form.html.haml b/app/views/shared/notes/_notes_with_form.html.haml index e11f778adf5..b3f865c5b47 100644 --- a/app/views/shared/notes/_notes_with_form.html.haml +++ b/app/views/shared/notes/_notes_with_form.html.haml @@ -14,7 +14,7 @@ .timeline-icon.hidden-xs.hidden-sm %a.author_link{ href: user_path(current_user) } - = image_tag avatar_icon(current_user), alt: current_user.to_reference, class: 'avatar s40' + = image_tag avatar_icon_for_user(current_user), alt: current_user.to_reference, class: 'avatar s40' .timeline-content.timeline-content-form = render "shared/notes/form", view: diff_view, supports_autocomplete: autocomplete - elsif !current_user diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml index 2a75b46d376..33435216c14 100644 --- a/app/views/shared/projects/_project.html.haml +++ b/app/views/shared/projects/_project.html.haml @@ -17,7 +17,7 @@ .avatar-container.s40 = link_to project_path(project), class: dom_class(project) do - if project.creator && use_creator_avatar - = image_tag avatar_icon(project.creator.email, 40), class: "avatar s40", alt:'' + = image_tag avatar_icon_for_user(project.creator, 40), class: "avatar s40", alt:'' - else = project_icon(project, alt: '', class: 'avatar project-avatar s40') .project-details diff --git a/app/views/shared/snippets/_snippet.html.haml b/app/views/shared/snippets/_snippet.html.haml index 7388f20a9fd..491a8a41090 100644 --- a/app/views/shared/snippets/_snippet.html.haml +++ b/app/views/shared/snippets/_snippet.html.haml @@ -1,7 +1,7 @@ - link_project = local_assigns.fetch(:link_project, false) %li.snippet-row - = image_tag avatar_icon(snippet.author_email), class: "avatar s40 hidden-xs", alt: '' + = image_tag avatar_icon_for_user(snippet.author), class: "avatar s40 hidden-xs", alt: '' .title = link_to reliable_snippet_path(snippet) do diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 90aa1be30ac..a396d1007a7 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -4,9 +4,6 @@ - page_description @user.bio - header_title @user.name, user_path(@user) - @no_container = true -- content_for :page_specific_javascripts do - = webpack_bundle_tag 'common_d3' - = webpack_bundle_tag 'users' = content_for :meta_tags do = auto_discovery_link_tag(:atom, user_url(@user, format: :atom), title: "#{@user.name} activity") @@ -35,8 +32,8 @@ .profile-header .avatar-holder - = link_to avatar_icon(@user, 400), target: '_blank', rel: 'noopener noreferrer' do - = image_tag avatar_icon(@user, 90), class: "avatar s90", alt: '' + = link_to avatar_icon_for_user(@user, 400), target: '_blank', rel: 'noopener noreferrer' do + = image_tag avatar_icon_for_user(@user, 90), class: "avatar s90", alt: '' .user-info .cover-title diff --git a/app/workers/check_gcp_project_billing_worker.rb b/app/workers/check_gcp_project_billing_worker.rb index 5466ccdda59..363f81590ab 100644 --- a/app/workers/check_gcp_project_billing_worker.rb +++ b/app/workers/check_gcp_project_billing_worker.rb @@ -7,6 +7,7 @@ class CheckGcpProjectBillingWorker LEASE_TIMEOUT = 3.seconds.to_i SESSION_KEY_TIMEOUT = 5.minutes BILLING_TIMEOUT = 1.hour + BILLING_CHANGED_LABELS = { state_transition: nil }.freeze def self.get_session_token(token_key) Gitlab::Redis::SharedState.with do |redis| @@ -22,8 +23,11 @@ class CheckGcpProjectBillingWorker end end - def self.redis_shared_state_key_for(token) - "gitlab:gcp:#{Digest::SHA1.hexdigest(token)}:billing_enabled" + def self.get_billing_state(token) + Gitlab::Redis::SharedState.with do |redis| + value = redis.get(redis_shared_state_key_for(token)) + ActiveRecord::Type::Boolean.new.type_cast_from_user(value) + end end def perform(token_key) @@ -33,12 +37,9 @@ class CheckGcpProjectBillingWorker return unless token return unless try_obtain_lease_for(token) - billing_enabled_projects = CheckGcpProjectBillingService.new.execute(token) - Gitlab::Redis::SharedState.with do |redis| - redis.set(self.class.redis_shared_state_key_for(token), - !billing_enabled_projects.empty?, - ex: BILLING_TIMEOUT) - end + billing_enabled_state = !CheckGcpProjectBillingService.new.execute(token).empty? + update_billing_change_counter(self.class.get_billing_state(token), billing_enabled_state) + self.class.set_billing_state(token, billing_enabled_state) end private @@ -51,9 +52,41 @@ class CheckGcpProjectBillingWorker "gitlab:gcp:session:#{token_key}" end + def self.redis_shared_state_key_for(token) + "gitlab:gcp:#{Digest::SHA1.hexdigest(token)}:billing_enabled" + end + + def self.set_billing_state(token, value) + Gitlab::Redis::SharedState.with do |redis| + redis.set(redis_shared_state_key_for(token), value, ex: BILLING_TIMEOUT) + end + end + def try_obtain_lease_for(token) Gitlab::ExclusiveLease .new("check_gcp_project_billing_worker:#{token.hash}", timeout: LEASE_TIMEOUT) .try_obtain end + + def billing_changed_counter + @billing_changed_counter ||= Gitlab::Metrics.counter( + :gcp_billing_change_count, + "Counts the number of times a GCP project changed billing_enabled state from false to true", + BILLING_CHANGED_LABELS + ) + end + + def state_transition(previous_state, current_state) + if previous_state.nil? && !current_state + 'no_billing' + elsif previous_state.nil? && current_state + 'with_billing' + elsif !previous_state && current_state + 'billing_configured' + end + end + + def update_billing_change_counter(previous_state, current_state) + billing_changed_counter.increment(state_transition: state_transition(previous_state, current_state)) + end end diff --git a/changelogs/unreleased-ee/39118-dynamic-pipeline-variables-fe.yml b/changelogs/unreleased-ee/39118-dynamic-pipeline-variables-fe.yml new file mode 100644 index 00000000000..a38b447e345 --- /dev/null +++ b/changelogs/unreleased-ee/39118-dynamic-pipeline-variables-fe.yml @@ -0,0 +1,6 @@ +--- +title: Update CI/CD secret variables list to be dynamic and save without reloading + the page +merge_request: 4110 +author: +type: added diff --git a/changelogs/unreleased/26466-natural-sort-mrs.yml b/changelogs/unreleased/26466-natural-sort-mrs.yml new file mode 100644 index 00000000000..e3bf9834f24 --- /dev/null +++ b/changelogs/unreleased/26466-natural-sort-mrs.yml @@ -0,0 +1,4 @@ +--- +title: Group MRs on issue page by project and namespace. +merge_request: 8494 +author: Jeff Stubler diff --git a/changelogs/unreleased/34130-null-pipes.yml b/changelogs/unreleased/34130-null-pipes.yml new file mode 100644 index 00000000000..a56e5cf8db2 --- /dev/null +++ b/changelogs/unreleased/34130-null-pipes.yml @@ -0,0 +1,5 @@ +--- +title: Prevent MR Widget error when no CI configured +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/34416-issue-i18n.yml b/changelogs/unreleased/34416-issue-i18n.yml new file mode 100644 index 00000000000..523073ee43b --- /dev/null +++ b/changelogs/unreleased/34416-issue-i18n.yml @@ -0,0 +1,5 @@ +--- +title: Translate issuable sidebar +merge_request: +author: +type: other diff --git a/changelogs/unreleased/35530-teleporting-emoji.yml b/changelogs/unreleased/35530-teleporting-emoji.yml new file mode 100644 index 00000000000..a60a42b9e48 --- /dev/null +++ b/changelogs/unreleased/35530-teleporting-emoji.yml @@ -0,0 +1,5 @@ +--- +title: Fix Teleporting Emoji +merge_request: 16963 +author: Jared Deckard <jared.deckard@gmail.com> +type: fixed diff --git a/changelogs/unreleased/35856-implement-file-locking-api.yml b/changelogs/unreleased/35856-implement-file-locking-api.yml new file mode 100644 index 00000000000..fa848ad9ed8 --- /dev/null +++ b/changelogs/unreleased/35856-implement-file-locking-api.yml @@ -0,0 +1,5 @@ +--- +title: Backport of LFS File Locking API +merge_request: 16935 +author: +type: added diff --git a/changelogs/unreleased/37050-ext-issue-tracker.yml b/changelogs/unreleased/37050-ext-issue-tracker.yml new file mode 100644 index 00000000000..29bccdded02 --- /dev/null +++ b/changelogs/unreleased/37050-ext-issue-tracker.yml @@ -0,0 +1,5 @@ +--- +title: Display a link to external issue tracker when enabled +merge_request: +author: +type: changed diff --git a/changelogs/unreleased/38175-add-domain-field-to-auto-devops-application-setting.yml b/changelogs/unreleased/38175-add-domain-field-to-auto-devops-application-setting.yml new file mode 100644 index 00000000000..475e1dc12b5 --- /dev/null +++ b/changelogs/unreleased/38175-add-domain-field-to-auto-devops-application-setting.yml @@ -0,0 +1,5 @@ +--- +title: Add Auto DevOps Domain application setting +merge_request: 16604 +author: +type: changed diff --git a/changelogs/unreleased/38265-stuckcijobsworker-wrongly-detects-cancels-stuck-builds-when-per-job-timeout-is-more-than-an-hour.yml b/changelogs/unreleased/38265-stuckcijobsworker-wrongly-detects-cancels-stuck-builds-when-per-job-timeout-is-more-than-an-hour.yml new file mode 100644 index 00000000000..4d8e6acfcb7 --- /dev/null +++ b/changelogs/unreleased/38265-stuckcijobsworker-wrongly-detects-cancels-stuck-builds-when-per-job-timeout-is-more-than-an-hour.yml @@ -0,0 +1,5 @@ +--- +title: Update runner info on all authenticated requests +merge_request: 16756 +author: +type: changed diff --git a/changelogs/unreleased/39607-fix-avatar--vertical-align.yml b/changelogs/unreleased/39607-fix-avatar--vertical-align.yml new file mode 100644 index 00000000000..4d9fee12f04 --- /dev/null +++ b/changelogs/unreleased/39607-fix-avatar--vertical-align.yml @@ -0,0 +1,5 @@ +--- +title: "Fix user avatar's vertical align on the issues and merge requests pages" +merge_request: 17072 +author: Laszlo Karpati +type: fixed diff --git a/changelogs/unreleased/40552-sanitize-extra-blank-spaces-used-when-uploading-a-ssh-key.yml b/changelogs/unreleased/40552-sanitize-extra-blank-spaces-used-when-uploading-a-ssh-key.yml deleted file mode 100644 index 9e4811ca308..00000000000 --- a/changelogs/unreleased/40552-sanitize-extra-blank-spaces-used-when-uploading-a-ssh-key.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Sanitize extra blank spaces used when uploading a SSH key -merge_request: 40552 -author: -type: fixed diff --git a/changelogs/unreleased/40623-fix-404-when-listing-archived-projects-in-a-group-where-all-projects-have-been-archived.yml b/changelogs/unreleased/40623-fix-404-when-listing-archived-projects-in-a-group-where-all-projects-have-been-archived.yml new file mode 100644 index 00000000000..543fd7c5e8d --- /dev/null +++ b/changelogs/unreleased/40623-fix-404-when-listing-archived-projects-in-a-group-where-all-projects-have-been-archived.yml @@ -0,0 +1,4 @@ +title: Fix 404 when listing archived projects in a group where all projects have been archived +merge_request: 17077 +author: Ashley Dumaine +type: fixed diff --git a/changelogs/unreleased/40755-snippets-author-n-1.yml b/changelogs/unreleased/40755-snippets-author-n-1.yml new file mode 100644 index 00000000000..6e09c8a54ec --- /dev/null +++ b/changelogs/unreleased/40755-snippets-author-n-1.yml @@ -0,0 +1,5 @@ +--- +title: Fix N+1 query problem for snippets dashboard. +merge_request: 16944 +author: +type: performance diff --git a/changelogs/unreleased/40994-expose-features-as-ci-cd-variable.yml b/changelogs/unreleased/40994-expose-features-as-ci-cd-variable.yml new file mode 100644 index 00000000000..1e377094791 --- /dev/null +++ b/changelogs/unreleased/40994-expose-features-as-ci-cd-variable.yml @@ -0,0 +1,5 @@ +--- +title: 'Expose GITLAB_FEATURES as CI/CD variable (fixes #40994)' +merge_request: +author: +type: added diff --git a/changelogs/unreleased/41209-ci-linter-fails-on-gitlab-ci-blob-viewer.yml b/changelogs/unreleased/41209-ci-linter-fails-on-gitlab-ci-blob-viewer.yml new file mode 100644 index 00000000000..61d6bf8fd36 --- /dev/null +++ b/changelogs/unreleased/41209-ci-linter-fails-on-gitlab-ci-blob-viewer.yml @@ -0,0 +1,5 @@ +--- +title: 'Handle all Psych YAML parser exceptions (fixes #41209)' +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/41763-search-api.yml b/changelogs/unreleased/41763-search-api.yml new file mode 100644 index 00000000000..0a760a66510 --- /dev/null +++ b/changelogs/unreleased/41763-search-api.yml @@ -0,0 +1,5 @@ +--- +title: Add search support into the API +merge_request: 16878 +author: +type: added diff --git a/changelogs/unreleased/41899-api-endpoint-for-importing-a-project-export.yml b/changelogs/unreleased/41899-api-endpoint-for-importing-a-project-export.yml new file mode 100644 index 00000000000..29ab7cc7cab --- /dev/null +++ b/changelogs/unreleased/41899-api-endpoint-for-importing-a-project-export.yml @@ -0,0 +1,5 @@ +--- +title: API endpoint for importing a project export +merge_request: 17025 +author: +type: added diff --git a/changelogs/unreleased/42160-error-500-loading-merge-request-undefined-method-index-for-nil-nilclass.yml b/changelogs/unreleased/42160-error-500-loading-merge-request-undefined-method-index-for-nil-nilclass.yml deleted file mode 100644 index 64340ab08cd..00000000000 --- a/changelogs/unreleased/42160-error-500-loading-merge-request-undefined-method-index-for-nil-nilclass.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix 500 error when loading a merge request with an invalid comment -merge_request: 16795 -author: -type: fixed diff --git a/changelogs/unreleased/42314-diff-file.yml b/changelogs/unreleased/42314-diff-file.yml new file mode 100644 index 00000000000..1eed5ef1a34 --- /dev/null +++ b/changelogs/unreleased/42314-diff-file.yml @@ -0,0 +1,5 @@ +--- +title: Render modified icon for moved file in changes dropdown +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/42462-edit-note.yml b/changelogs/unreleased/42462-edit-note.yml new file mode 100644 index 00000000000..8df98f3ecef --- /dev/null +++ b/changelogs/unreleased/42462-edit-note.yml @@ -0,0 +1,5 @@ +--- +title: Fix cnacel edit note button reverting changes +merge_request: 42462 +author: +type: fixed diff --git a/changelogs/unreleased/42481-remove-notification-settings-left-projects.yml b/changelogs/unreleased/42481-remove-notification-settings-left-projects.yml new file mode 100644 index 00000000000..ea99649131b --- /dev/null +++ b/changelogs/unreleased/42481-remove-notification-settings-left-projects.yml @@ -0,0 +1,5 @@ +--- +title: Remove user notification settings for groups and projects when user leaves +merge_request: 16906 +author: Jacopo Beschi @jacopo-beschi +type: fixed diff --git a/changelogs/unreleased/42591-update-nokogiri.yml b/changelogs/unreleased/42591-update-nokogiri.yml deleted file mode 100644 index 5f9587d2d92..00000000000 --- a/changelogs/unreleased/42591-update-nokogiri.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update nokogiri to 1.8.2 -merge_request: 16807 -author: -type: security diff --git a/changelogs/unreleased/42641-monaco-service-workers-do-not-work-with-cdn-enabled.yml b/changelogs/unreleased/42641-monaco-service-workers-do-not-work-with-cdn-enabled.yml new file mode 100644 index 00000000000..955a5a27e21 --- /dev/null +++ b/changelogs/unreleased/42641-monaco-service-workers-do-not-work-with-cdn-enabled.yml @@ -0,0 +1,5 @@ +--- +title: Fix monaco editor features which were incompatable with GitLab CDN settings +merge_request: 17021 +author: +type: fixed diff --git a/changelogs/unreleased/42696-gitlab-import-leaves-group_id-on-projectlabel.yml b/changelogs/unreleased/42696-gitlab-import-leaves-group_id-on-projectlabel.yml deleted file mode 100644 index c46cc86c99b..00000000000 --- a/changelogs/unreleased/42696-gitlab-import-leaves-group_id-on-projectlabel.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix GitLab import leaving group_id on ProjectLabel -merge_request: 16877 -author: -type: fixed diff --git a/changelogs/unreleased/42800-change-usage-of-avatar_icon.yml b/changelogs/unreleased/42800-change-usage-of-avatar_icon.yml new file mode 100644 index 00000000000..00f4b7436a7 --- /dev/null +++ b/changelogs/unreleased/42800-change-usage-of-avatar_icon.yml @@ -0,0 +1,6 @@ +--- +title: Use a user object in ApplicationHelper#avatar_icon where possible to avoid + N+1 queries. +merge_request: 42800 +author: +type: performance diff --git a/changelogs/unreleased/42922-environment-name.yml b/changelogs/unreleased/42922-environment-name.yml new file mode 100644 index 00000000000..0e9544245f6 --- /dev/null +++ b/changelogs/unreleased/42922-environment-name.yml @@ -0,0 +1,5 @@ +--- +title: Adds tooltip in environment names to increase readability +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/42923-close-issue.yml b/changelogs/unreleased/42923-close-issue.yml new file mode 100644 index 00000000000..e332bbf5dec --- /dev/null +++ b/changelogs/unreleased/42923-close-issue.yml @@ -0,0 +1,5 @@ +--- +title: Fix close button on issues not working on mobile +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/42929-hide-new-variable-values.yml b/changelogs/unreleased/42929-hide-new-variable-values.yml new file mode 100644 index 00000000000..68decd25b5a --- /dev/null +++ b/changelogs/unreleased/42929-hide-new-variable-values.yml @@ -0,0 +1,5 @@ +--- +title: Hide CI secret variable values after saving +merge_request: 17044 +author: +type: changed diff --git a/changelogs/unreleased/43198-fix-settings-panel-expanding-when-fragment-hash-linked.yml b/changelogs/unreleased/43198-fix-settings-panel-expanding-when-fragment-hash-linked.yml new file mode 100644 index 00000000000..49ba48a0fef --- /dev/null +++ b/changelogs/unreleased/43198-fix-settings-panel-expanding-when-fragment-hash-linked.yml @@ -0,0 +1,5 @@ +--- +title: Fix settings panels not expanding when fragment hash linked +merge_request: 17074 +author: +type: fixed diff --git a/changelogs/unreleased/43201-rename-repository-submit-button-disabled.yml b/changelogs/unreleased/43201-rename-repository-submit-button-disabled.yml new file mode 100644 index 00000000000..b527000332e --- /dev/null +++ b/changelogs/unreleased/43201-rename-repository-submit-button-disabled.yml @@ -0,0 +1,5 @@ +--- +title: Allows project rename after validation error +merge_request: 17150 +author: +type: fixed diff --git a/changelogs/unreleased/4826-geo-wikisyncservice-attempts-to-sync-projects.yml b/changelogs/unreleased/4826-geo-wikisyncservice-attempts-to-sync-projects.yml new file mode 100644 index 00000000000..7f1ccbfcc7e --- /dev/null +++ b/changelogs/unreleased/4826-geo-wikisyncservice-attempts-to-sync-projects.yml @@ -0,0 +1,5 @@ +--- +title: Create empty wiki when import from GitLab and wiki is not there +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/api-refs-for-commit.yml b/changelogs/unreleased/api-refs-for-commit.yml new file mode 100644 index 00000000000..df8a2b0eccc --- /dev/null +++ b/changelogs/unreleased/api-refs-for-commit.yml @@ -0,0 +1,5 @@ +--- +title: 'API: Get references a commit is pushed to' +merge_request: 15026 +author: Robert Schilling +type: added diff --git a/changelogs/unreleased/asciidoc_inter_document_cross_references.yml b/changelogs/unreleased/asciidoc_inter_document_cross_references.yml new file mode 100644 index 00000000000..34b26753312 --- /dev/null +++ b/changelogs/unreleased/asciidoc_inter_document_cross_references.yml @@ -0,0 +1,5 @@ +--- +title: Asciidoc now support inter-document cross references between files in repository +merge_request: 17125 +author: Turo Soisenniemi +type: changed diff --git a/changelogs/unreleased/bvl-fix-500-on-fork-without-restricted-visibility-levels.yml b/changelogs/unreleased/bvl-fix-500-on-fork-without-restricted-visibility-levels.yml deleted file mode 100644 index 378f0ef7ce9..00000000000 --- a/changelogs/unreleased/bvl-fix-500-on-fork-without-restricted-visibility-levels.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix forking projects when no restricted visibility levels are defined applicationwide -merge_request: 16881 -author: -type: fixed diff --git a/changelogs/unreleased/bvl-fix-concurrent-fork-network-migrations.yml b/changelogs/unreleased/bvl-fix-concurrent-fork-network-migrations.yml new file mode 100644 index 00000000000..b2a77f75e55 --- /dev/null +++ b/changelogs/unreleased/bvl-fix-concurrent-fork-network-migrations.yml @@ -0,0 +1,5 @@ +--- +title: Avoid running `PopulateForkNetworksRange`-migration multiple times +merge_request: 16988 +author: +type: fixed diff --git a/changelogs/unreleased/change-strip-whitespace-from-username-input-42637.yml b/changelogs/unreleased/change-strip-whitespace-from-username-input-42637.yml new file mode 100644 index 00000000000..a51781396ee --- /dev/null +++ b/changelogs/unreleased/change-strip-whitespace-from-username-input-42637.yml @@ -0,0 +1,5 @@ +--- +title: Remove whitespace from the username/email sign in form field +merge_request: 17020 +author: Peter lauck +type: changed diff --git a/changelogs/unreleased/dm-escape-commit-message.yml b/changelogs/unreleased/dm-escape-commit-message.yml new file mode 100644 index 00000000000..89af2da3484 --- /dev/null +++ b/changelogs/unreleased/dm-escape-commit-message.yml @@ -0,0 +1,5 @@ +--- +title: Escape HTML entities in commit messages +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/dm-user-namespace-route-path-validation.yml b/changelogs/unreleased/dm-user-namespace-route-path-validation.yml deleted file mode 100644 index 36615e5b976..00000000000 --- a/changelogs/unreleased/dm-user-namespace-route-path-validation.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Validate user namespace before saving so that errors persist on model -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/docs-update-vue-naming-guidelines.yml b/changelogs/unreleased/docs-update-vue-naming-guidelines.yml new file mode 100644 index 00000000000..95bfd212370 --- /dev/null +++ b/changelogs/unreleased/docs-update-vue-naming-guidelines.yml @@ -0,0 +1,5 @@ +--- +title: Update vue component naming guidelines +merge_request: 17018 +author: George Tsiolis +type: other diff --git a/changelogs/unreleased/expired-ci-artifacts.yml b/changelogs/unreleased/expired-ci-artifacts.yml new file mode 100644 index 00000000000..2fcbdb02f84 --- /dev/null +++ b/changelogs/unreleased/expired-ci-artifacts.yml @@ -0,0 +1,5 @@ +--- +title: Change SQL for expired artifacts to use new ci_job_artifacts.expire_at +merge_request: 16578 +author: +type: performance diff --git a/changelogs/unreleased/feature-26598-clear-button-ci-lint.yml b/changelogs/unreleased/feature-26598-clear-button-ci-lint.yml new file mode 100644 index 00000000000..fcf237f20f0 --- /dev/null +++ b/changelogs/unreleased/feature-26598-clear-button-ci-lint.yml @@ -0,0 +1,4 @@ +--- +title: Added clear button to ci lint editor +merge_request: +author: Michael Robinson diff --git a/changelogs/unreleased/feature-include-custom-attributes-in-api.yml b/changelogs/unreleased/feature-include-custom-attributes-in-api.yml new file mode 100644 index 00000000000..f1087d7f7cc --- /dev/null +++ b/changelogs/unreleased/feature-include-custom-attributes-in-api.yml @@ -0,0 +1,5 @@ +--- +title: Allow including custom attributes in API responses +merge_request: 16526 +author: Markus Koller +type: changed diff --git a/changelogs/unreleased/feature-oidc-groups-claim.yml b/changelogs/unreleased/feature-oidc-groups-claim.yml new file mode 100644 index 00000000000..bde19130114 --- /dev/null +++ b/changelogs/unreleased/feature-oidc-groups-claim.yml @@ -0,0 +1,4 @@ +--- +title: Add groups to OpenID Connect claims +merge_request: 16929 +author: Hassan Zamani diff --git a/changelogs/unreleased/fix-template-project-visibility.yml b/changelogs/unreleased/fix-template-project-visibility.yml new file mode 100644 index 00000000000..6576097822b --- /dev/null +++ b/changelogs/unreleased/fix-template-project-visibility.yml @@ -0,0 +1,5 @@ +--- +title: Respect description and visibility when creating project from template +merge_request: 16820 +author: George Tsiolis +type: fixed diff --git a/changelogs/unreleased/fix-validation-of-environment-scope-for-variables.yml b/changelogs/unreleased/fix-validation-of-environment-scope-for-variables.yml new file mode 100644 index 00000000000..5424c15a8ae --- /dev/null +++ b/changelogs/unreleased/fix-validation-of-environment-scope-for-variables.yml @@ -0,0 +1,5 @@ +--- +title: Fix validation of environment scope of variables +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/fj-42910-unauthenticated-limit-via-ssh.yml b/changelogs/unreleased/fj-42910-unauthenticated-limit-via-ssh.yml new file mode 100644 index 00000000000..cef339ef787 --- /dev/null +++ b/changelogs/unreleased/fj-42910-unauthenticated-limit-via-ssh.yml @@ -0,0 +1,5 @@ +--- +title: Fixed bug with unauthenticated requests through git ssh +merge_request: 17149 +author: +type: fixed diff --git a/changelogs/unreleased/fl-refresh-btn.yml b/changelogs/unreleased/fl-refresh-btn.yml new file mode 100644 index 00000000000..640fdda9ce7 --- /dev/null +++ b/changelogs/unreleased/fl-refresh-btn.yml @@ -0,0 +1,5 @@ +--- +title: Show loading button inline in refresh button in MR widget +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/group-label-page-breadcrumb.yml b/changelogs/unreleased/group-label-page-breadcrumb.yml new file mode 100644 index 00000000000..c6cc4618c52 --- /dev/null +++ b/changelogs/unreleased/group-label-page-breadcrumb.yml @@ -0,0 +1,5 @@ +--- +title: Fix breadcrumb on labels page for groups +merge_request: 17045 +author: Onuwa Nnachi Isaac +type: fixed diff --git a/changelogs/unreleased/internationalize-charts-page.yml b/changelogs/unreleased/internationalize-charts-page.yml new file mode 100644 index 00000000000..481b83fb059 --- /dev/null +++ b/changelogs/unreleased/internationalize-charts-page.yml @@ -0,0 +1,5 @@ +--- +title: Internationalize charts page +merge_request: 16687 +author: selrahman +type: changed diff --git a/changelogs/unreleased/internationalize-graph-page.yml b/changelogs/unreleased/internationalize-graph-page.yml new file mode 100644 index 00000000000..904dbd606d7 --- /dev/null +++ b/changelogs/unreleased/internationalize-graph-page.yml @@ -0,0 +1,5 @@ +--- +title: Internationalize graph page selrahman +merge_request: 16688 +author: Shah El-Rahman +type: changed diff --git a/changelogs/unreleased/issue-39885.yml b/changelogs/unreleased/issue-39885.yml new file mode 100644 index 00000000000..75bf9434152 --- /dev/null +++ b/changelogs/unreleased/issue-39885.yml @@ -0,0 +1,5 @@ +--- +title: 'Ensure users cannot create environments with leading or trailing slashes (Fixes #39885)' +merge_request: 15273 +author: +type: fixed diff --git a/changelogs/unreleased/jej-fix-slow-lfs-object-check.yml b/changelogs/unreleased/jej-fix-slow-lfs-object-check.yml new file mode 100644 index 00000000000..09112fba85e --- /dev/null +++ b/changelogs/unreleased/jej-fix-slow-lfs-object-check.yml @@ -0,0 +1,5 @@ +--- +title: Only check LFS integrity for first ref in a push to avoid timeout +merge_request: 17098 +author: +type: performance diff --git a/changelogs/unreleased/jivl-update-katex.yml b/changelogs/unreleased/jivl-update-katex.yml new file mode 100644 index 00000000000..99b5fe49620 --- /dev/null +++ b/changelogs/unreleased/jivl-update-katex.yml @@ -0,0 +1,5 @@ +--- +title: Updated the katex library +merge_request: 15864 +author: +type: other diff --git a/changelogs/unreleased/move-board-list-vue-component.yml b/changelogs/unreleased/move-board-list-vue-component.yml new file mode 100644 index 00000000000..9c566b43cc2 --- /dev/null +++ b/changelogs/unreleased/move-board-list-vue-component.yml @@ -0,0 +1,5 @@ +--- +title: Move BoardList vue component to vue file +merge_request: 16888 +author: George Tsiolis +type: performance diff --git a/changelogs/unreleased/osw-remove-duplicate-can-be-reverted-calls.yml b/changelogs/unreleased/osw-remove-duplicate-can-be-reverted-calls.yml new file mode 100644 index 00000000000..03940555162 --- /dev/null +++ b/changelogs/unreleased/osw-remove-duplicate-can-be-reverted-calls.yml @@ -0,0 +1,5 @@ +--- +title: Remove duplicate calls of MergeRequest#can_be_reverted? +merge_request: +author: +type: performance diff --git a/changelogs/unreleased/pawel-connect_to_prometheus_through_proxy-30480.yml b/changelogs/unreleased/pawel-connect_to_prometheus_through_proxy-30480.yml new file mode 100644 index 00000000000..b2bb173912a --- /dev/null +++ b/changelogs/unreleased/pawel-connect_to_prometheus_through_proxy-30480.yml @@ -0,0 +1,6 @@ +--- +title: Implement multi server support and use kube proxy to connect to Prometheus + servers inside K8S cluster +merge_request: 16182 +author: +type: added diff --git a/changelogs/unreleased/refactor-move-issuable-time-tracker-vue-component.yml b/changelogs/unreleased/refactor-move-issuable-time-tracker-vue-component.yml new file mode 100644 index 00000000000..5ed06c61817 --- /dev/null +++ b/changelogs/unreleased/refactor-move-issuable-time-tracker-vue-component.yml @@ -0,0 +1,5 @@ +--- +title: Move IssuableTimeTracker vue component +merge_request: 16948 +author: George Tsiolis +type: performance diff --git a/changelogs/unreleased/sh-fix-jira-trailing-slash.yml b/changelogs/unreleased/sh-fix-jira-trailing-slash.yml deleted file mode 100644 index 786f6cd3727..00000000000 --- a/changelogs/unreleased/sh-fix-jira-trailing-slash.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix JIRA not working when a trailing slash is included -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/winh-new-modal-component.yml b/changelogs/unreleased/winh-new-modal-component.yml new file mode 100644 index 00000000000..bcc0d489c88 --- /dev/null +++ b/changelogs/unreleased/winh-new-modal-component.yml @@ -0,0 +1,5 @@ +--- +title: Add new modal Vue component +merge_request: 17108 +author: +type: changed diff --git a/config/application.rb b/config/application.rb index 751307de975..918bd4d57cf 100644 --- a/config/application.rb +++ b/config/application.rb @@ -11,6 +11,7 @@ module Gitlab require_dependency Rails.root.join('lib/gitlab/redis/queues') require_dependency Rails.root.join('lib/gitlab/redis/shared_state') require_dependency Rails.root.join('lib/gitlab/request_context') + require_dependency Rails.root.join('lib/gitlab/current_settings') # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers @@ -68,6 +69,7 @@ module Gitlab # - Webhook URLs (:hook) # - Sentry DSN (:sentry_dsn) # - Deploy keys (:key) + # - Secret variable values (:value) config.filter_parameters += [/token$/, /password/, /secret/] config.filter_parameters += %i( certificate @@ -79,6 +81,7 @@ module Gitlab sentry_dsn trace variables + value ) # Enable escaping HTML in JSON. @@ -107,8 +110,6 @@ module Gitlab config.assets.precompile << "print.css" config.assets.precompile << "notify.css" config.assets.precompile << "mailers/*.css" - config.assets.precompile << "katex.css" - config.assets.precompile << "katex.js" config.assets.precompile << "xterm/xterm.css" config.assets.precompile << "performance_bar.css" config.assets.precompile << "lib/ace.js" diff --git a/config/initializers/doorkeeper_openid_connect.rb b/config/initializers/doorkeeper_openid_connect.rb index af174def047..98e1f6e830f 100644 --- a/config/initializers/doorkeeper_openid_connect.rb +++ b/config/initializers/doorkeeper_openid_connect.rb @@ -31,6 +31,7 @@ Doorkeeper::OpenidConnect.configure do o.claim(:website) { |user| user.full_website_url if user.website_url? } o.claim(:profile) { |user| Gitlab::Routing.url_helpers.user_url user } o.claim(:picture) { |user| user.avatar_url(only_path: false) } + o.claim(:groups) { |user| user.membership_groups.map(&:full_path) } end end end diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb index 5e3e4c966cb..e9326653cbe 100644 --- a/config/initializers/mime_types.rb +++ b/config/initializers/mime_types.rb @@ -14,4 +14,4 @@ Mime::Type.register "video/webm", :webm Mime::Type.register "video/ogg", :ogv Mime::Type.unregister :json -Mime::Type.register 'application/json', :json, %w(application/vnd.git-lfs+json application/json) +Mime::Type.register 'application/json', :json, [LfsRequest::CONTENT_TYPE, 'application/json'] diff --git a/config/initializers/rack_attack_global.rb b/config/initializers/rack_attack_global.rb index 9453df2ec5a..a90516eee7d 100644 --- a/config/initializers/rack_attack_global.rb +++ b/config/initializers/rack_attack_global.rb @@ -26,6 +26,7 @@ class Rack::Attack throttle('throttle_unauthenticated', Gitlab::Throttle.unauthenticated_options) do |req| Gitlab::Throttle.settings.throttle_unauthenticated_enabled && req.unauthenticated? && + !req.api_internal_request? && req.ip end @@ -54,6 +55,10 @@ class Rack::Attack path.start_with?('/api') end + def api_internal_request? + path =~ %r{^/api/v\d+/internal/} + end + def web_request? !api_request? end diff --git a/config/locales/doorkeeper.en.yml b/config/locales/doorkeeper.en.yml index b1c71095d4f..889111282ef 100644 --- a/config/locales/doorkeeper.en.yml +++ b/config/locales/doorkeeper.en.yml @@ -68,7 +68,7 @@ en: read_user: Read-only access to the user's profile information, like username, public email and full name openid: - The ability to authenticate using GitLab, and read-only access to the user's profile information + The ability to authenticate using GitLab, and read-only access to the user's profile information and group memberships sudo: Access to the Sudo feature, to perform API actions as any user in the system (only available for admins) flash: diff --git a/config/routes/git_http.rb b/config/routes/git_http.rb index a53c94326d4..ff51823897d 100644 --- a/config/routes/git_http.rb +++ b/config/routes/git_http.rb @@ -16,6 +16,13 @@ scope(path: '*namespace_id/:project_id', get '/*oid', action: :deprecated end + scope(path: 'info/lfs') do + resources :lfs_locks, controller: :lfs_locks_api, path: 'locks' do + post :unlock, on: :member + post :verify, on: :collection + end + end + # GitLab LFS object storage scope(path: 'gitlab-lfs/objects/*oid', controller: :lfs_storage, constraints: { oid: /[a-f0-9]{64}/ }) do get '/', action: :download diff --git a/config/routes/group.rb b/config/routes/group.rb index b17611d8623..7a4740a4df7 100644 --- a/config/routes/group.rb +++ b/config/routes/group.rb @@ -28,7 +28,7 @@ constraints(GroupUrlConstrainer.new) do resource :ci_cd, only: [:show], controller: 'ci_cd' end - resources :variables, only: [:index, :show, :update, :create, :destroy] + resource :variables, only: [:show, :update] resources :children, only: [:index] diff --git a/config/routes/project.rb b/config/routes/project.rb index bcaa68c8ce5..1912808f9c0 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -156,7 +156,8 @@ constraints(ProjectUrlConstrainer.new) do end end - resources :variables, only: [:index, :show, :update, :create, :destroy] + resource :variables, only: [:show, :update] + resources :triggers, only: [:index, :create, :edit, :update, :destroy] do member do post :take_ownership diff --git a/config/webpack.config.js b/config/webpack.config.js index 69b2d6dfa72..df48afe6d6b 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -66,7 +66,6 @@ var config = { help: './help/help.js', how_to_merge: './how_to_merge.js', issue_show: './issue_show/index.js', - integrations: './integrations', job_details: './jobs/job_details_bundle.js', locale: './locale/index.js', main: './main.js', @@ -77,19 +76,14 @@ var config = { notes: './notes/index.js', pdf_viewer: './blob/pdf_viewer.js', pipelines: './pipelines/pipelines_bundle.js', - pipelines_charts: './pipelines/pipelines_charts.js', pipelines_details: './pipelines/pipeline_details_bundle.js', - pipelines_times: './pipelines/pipelines_times.js', profile: './profile/profile_bundle.js', project_import_gl: './projects/project_import_gitlab_project.js', - prometheus_metrics: './prometheus_metrics', protected_branches: './protected_branches', protected_tags: './protected_tags', registry_list: './registry/index.js', ide: './ide/index.js', sidebar: './sidebar/sidebar_bundle.js', - schedule_form: './pipeline_schedules/pipeline_schedule_form_bundle.js', - schedules_index: './pipeline_schedules/pipeline_schedules_index_bundle.js', snippet: './snippet/snippet_bundle.js', sketch_viewer: './blob/sketch_viewer.js', stl_viewer: './blob/stl_viewer.js', @@ -100,7 +94,6 @@ var config = { vue_merge_request_widget: './vue_merge_request_widget/index.js', test: './test.js', two_factor_auth: './two_factor_auth.js', - users: './users/index.js', webpack_runtime: './webpack.js', }, @@ -152,6 +145,27 @@ var config = { } }, { + test: /katex.css$/, + include: /node_modules\/katex\/dist/, + use: [ + { loader: 'style-loader' }, + { + loader: 'css-loader', + options: { + name: '[name].[hash].[ext]' + } + }, + ], + }, + { + test: /\.(eot|ttf|woff|woff2)$/, + include: /node_modules\/katex\/dist\/fonts/, + loader: 'file-loader', + options: { + name: '[name].[hash].[ext]', + } + }, + { test: /monaco-editor\/\w+\/vs\/loader\.js$/, use: [ { loader: 'exports-loader', options: 'l.global' }, diff --git a/db/fixtures/development/01_admin.rb b/db/fixtures/development/01_admin.rb index 6f241f6fa4a..dfb50c195c1 100644 --- a/db/fixtures/development/01_admin.rb +++ b/db/fixtures/development/01_admin.rb @@ -9,7 +9,6 @@ Gitlab::Seeder.quiet do s.username = 'root' s.password = '5iveL!fe' s.admin = true - s.projects_limit = 100 s.confirmed_at = DateTime.now end end diff --git a/db/migrate/20170929131201_populate_fork_networks.rb b/db/migrate/20170929131201_populate_fork_networks.rb index 1214962770f..ddbf27e1852 100644 --- a/db/migrate/20170929131201_populate_fork_networks.rb +++ b/db/migrate/20170929131201_populate_fork_networks.rb @@ -6,22 +6,8 @@ class PopulateForkNetworks < ActiveRecord::Migration DOWNTIME = false - MIGRATION = 'PopulateForkNetworksRange'.freeze - BATCH_SIZE = 100 - DELAY_INTERVAL = 15.seconds - - disable_ddl_transaction! - - class ForkedProjectLink < ActiveRecord::Base - include EachBatch - - self.table_name = 'forked_project_links' - end - def up - say 'Populating the `fork_networks` based on existing `forked_project_links`' - - queue_background_migration_jobs_by_range_at_intervals(ForkedProjectLink, MIGRATION, DELAY_INTERVAL, batch_size: BATCH_SIZE) + say 'Fork networks will be populated in 20171205190711 - RescheduleForkNetworkCreationCaller' end def down diff --git a/db/migrate/20180116193854_create_lfs_file_locks.rb b/db/migrate/20180116193854_create_lfs_file_locks.rb new file mode 100644 index 00000000000..23b0c90484b --- /dev/null +++ b/db/migrate/20180116193854_create_lfs_file_locks.rb @@ -0,0 +1,30 @@ +class CreateLfsFileLocks < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + create_table :lfs_file_locks do |t| + t.references :project, null: false, foreign_key: { on_delete: :cascade } + t.references :user, null: false, index: true, foreign_key: { on_delete: :cascade } + t.datetime :created_at, null: false + t.string :path, limit: 511 + end + + add_index :lfs_file_locks, [:project_id, :path], unique: true + end + + def down + if foreign_keys_for(:lfs_file_locks, :project_id).any? + remove_foreign_key :lfs_file_locks, column: :project_id + end + + if index_exists?(:lfs_file_locks, [:project_id, :path]) + remove_concurrent_index :lfs_file_locks, [:project_id, :path] + end + + drop_table :lfs_file_locks + end +end diff --git a/db/migrate/20180119160751_optimize_ci_job_artifacts.rb b/db/migrate/20180119160751_optimize_ci_job_artifacts.rb new file mode 100644 index 00000000000..9b4340ed7b7 --- /dev/null +++ b/db/migrate/20180119160751_optimize_ci_job_artifacts.rb @@ -0,0 +1,23 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class OptimizeCiJobArtifacts < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + disable_ddl_transaction! + + def up + # job_id is just here to be a covering index for index only scans + # since we'll almost always be joining against ci_builds on job_id + add_concurrent_index(:ci_job_artifacts, [:expire_at, :job_id]) + add_concurrent_index(:ci_builds, [:artifacts_expire_at], where: "artifacts_file <> ''") + end + + def down + remove_concurrent_index(:ci_job_artifacts, [:expire_at, :job_id]) + remove_concurrent_index(:ci_builds, [:artifacts_expire_at], where: "artifacts_file <> ''") + end +end diff --git a/db/migrate/20180122162010_add_auto_devops_domain_to_application_settings.rb b/db/migrate/20180122162010_add_auto_devops_domain_to_application_settings.rb new file mode 100644 index 00000000000..7e16cb83087 --- /dev/null +++ b/db/migrate/20180122162010_add_auto_devops_domain_to_application_settings.rb @@ -0,0 +1,13 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddAutoDevopsDomainToApplicationSettings < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + def change + add_column :application_settings, :auto_devops_domain, :string + end +end diff --git a/db/migrate/20180206200543_reset_events_primary_key_sequence.rb b/db/migrate/20180206200543_reset_events_primary_key_sequence.rb new file mode 100644 index 00000000000..eb5c4a6a1e7 --- /dev/null +++ b/db/migrate/20180206200543_reset_events_primary_key_sequence.rb @@ -0,0 +1,35 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class ResetEventsPrimaryKeySequence < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + class Event < ActiveRecord::Base + self.table_name = 'events' + end + + def up + if Gitlab::Database.postgresql? + reset_primary_key_for_postgresql + else + reset_primary_key_for_mysql + end + end + + def down + # No-op + end + + def reset_primary_key_for_postgresql + reset_pk_sequence!(Event.table_name) + end + + def reset_primary_key_for_mysql + amount = Event.pluck('COALESCE(MAX(id), 1)').first + + execute "ALTER TABLE #{Event.table_name} AUTO_INCREMENT = #{amount}" + end +end diff --git a/db/migrate/20180208183958_schedule_populate_untracked_uploads_if_needed.rb b/db/migrate/20180208183958_schedule_populate_untracked_uploads_if_needed.rb new file mode 100644 index 00000000000..e46e793d9d2 --- /dev/null +++ b/db/migrate/20180208183958_schedule_populate_untracked_uploads_if_needed.rb @@ -0,0 +1,47 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class SchedulePopulateUntrackedUploadsIfNeeded < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + FOLLOW_UP_MIGRATION = 'PopulateUntrackedUploads'.freeze + + class UntrackedFile < ActiveRecord::Base + include EachBatch + + self.table_name = 'untracked_files_for_uploads' + end + + def up + if table_exists?(:untracked_files_for_uploads) + process_or_remove_table + end + end + + def down + # nothing + end + + private + + def process_or_remove_table + if UntrackedFile.all.empty? + drop_temp_table + else + schedule_populate_untracked_uploads_jobs + end + end + + def drop_temp_table + drop_table(:untracked_files_for_uploads, if_exists: true) + end + + def schedule_populate_untracked_uploads_jobs + say "Scheduling #{FOLLOW_UP_MIGRATION} background migration jobs since there are rows in untracked_files_for_uploads." + + bulk_queue_background_migration_jobs_by_range( + UntrackedFile, FOLLOW_UP_MIGRATION) + end +end diff --git a/db/post_migrate/20170717111152_cleanup_move_system_upload_folder_symlink.rb b/db/post_migrate/20170717111152_cleanup_move_system_upload_folder_symlink.rb index 26b99b61424..c48f1c938d0 100644 --- a/db/post_migrate/20170717111152_cleanup_move_system_upload_folder_symlink.rb +++ b/db/post_migrate/20170717111152_cleanup_move_system_upload_folder_symlink.rb @@ -20,7 +20,7 @@ class CleanupMoveSystemUploadFolderSymlink < ActiveRecord::Migration def down if File.directory?(new_directory) say "Symlinking #{old_directory} -> #{new_directory}" - FileUtils.ln_s(new_directory, old_directory) + FileUtils.ln_s(new_directory, old_directory) unless File.exist?(old_directory) else say "#{new_directory} doesn't exist, skipping." end diff --git a/db/post_migrate/20171124150326_reschedule_fork_network_creation.rb b/db/post_migrate/20171124150326_reschedule_fork_network_creation.rb index 05430efe1f6..26f917d5a1e 100644 --- a/db/post_migrate/20171124150326_reschedule_fork_network_creation.rb +++ b/db/post_migrate/20171124150326_reschedule_fork_network_creation.rb @@ -3,22 +3,8 @@ class RescheduleForkNetworkCreation < ActiveRecord::Migration DOWNTIME = false - MIGRATION = 'PopulateForkNetworksRange'.freeze - BATCH_SIZE = 100 - DELAY_INTERVAL = 15.seconds - - disable_ddl_transaction! - - class ForkedProjectLink < ActiveRecord::Base - include EachBatch - - self.table_name = 'forked_project_links' - end - def up - say 'Populating the `fork_networks` based on existing `forked_project_links`' - - queue_background_migration_jobs_by_range_at_intervals(ForkedProjectLink, MIGRATION, DELAY_INTERVAL, batch_size: BATCH_SIZE) + say 'Fork networks will be populated in 20171205190711 - RescheduleForkNetworkCreationCaller' end def down diff --git a/db/post_migrate/20180119121225_remove_redundant_pipeline_stages.rb b/db/post_migrate/20180119121225_remove_redundant_pipeline_stages.rb new file mode 100644 index 00000000000..61ea85eb2a7 --- /dev/null +++ b/db/post_migrate/20180119121225_remove_redundant_pipeline_stages.rb @@ -0,0 +1,66 @@ +class RemoveRedundantPipelineStages < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up(attempts: 100) + remove_redundant_pipeline_stages! + remove_outdated_index! + add_unique_index! + rescue ActiveRecord::RecordNotUnique + retry if (attempts -= 1) > 0 + + raise StandardError, <<~EOS + Failed to add an unique index to ci_stages, despite retrying the + migration 100 times. + + See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/16580. + EOS + end + + def down + remove_concurrent_index :ci_stages, [:pipeline_id, :name], unique: true + add_concurrent_index :ci_stages, [:pipeline_id, :name] + end + + private + + def remove_outdated_index! + return unless index_exists?(:ci_stages, [:pipeline_id, :name]) + + remove_concurrent_index :ci_stages, [:pipeline_id, :name] + end + + def add_unique_index! + add_concurrent_index :ci_stages, [:pipeline_id, :name], unique: true + end + + def remove_redundant_pipeline_stages! + disable_statement_timeout + + redundant_stages_ids = <<~SQL + SELECT id FROM ci_stages WHERE (pipeline_id, name) IN ( + SELECT pipeline_id, name FROM ci_stages + GROUP BY pipeline_id, name HAVING COUNT(*) > 1 + ) + SQL + + execute <<~SQL + UPDATE ci_builds SET stage_id = NULL WHERE stage_id IN (#{redundant_stages_ids}) + SQL + + if Gitlab::Database.postgresql? + execute <<~SQL + DELETE FROM ci_stages WHERE id IN (#{redundant_stages_ids}) + SQL + else # We can't modify a table we are selecting from on MySQL + execute <<~SQL + DELETE a FROM ci_stages AS a, ci_stages AS b + WHERE a.pipeline_id = b.pipeline_id AND a.name = b.name + AND a.id <> b.id + SQL + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 432eb095746..6b43fc8403c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180204200836) do +ActiveRecord::Schema.define(version: 20180208183958) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -155,6 +155,7 @@ ActiveRecord::Schema.define(version: 20180204200836) do t.integer "gitaly_timeout_medium", default: 30, null: false t.integer "gitaly_timeout_fast", default: 10, null: false t.boolean "authorized_keys_enabled", default: true, null: false + t.string "auto_devops_domain" end create_table "audit_events", force: :cascade do |t| @@ -292,6 +293,7 @@ ActiveRecord::Schema.define(version: 20180204200836) do t.integer "failure_reason" end + add_index "ci_builds", ["artifacts_expire_at"], name: "index_ci_builds_on_artifacts_expire_at", where: "(artifacts_file <> ''::text)", using: :btree add_index "ci_builds", ["auto_canceled_by_id"], name: "index_ci_builds_on_auto_canceled_by_id", using: :btree add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree add_index "ci_builds", ["commit_id", "status", "type"], name: "index_ci_builds_on_commit_id_and_status_and_type", using: :btree @@ -332,6 +334,7 @@ ActiveRecord::Schema.define(version: 20180204200836) do t.string "file" end + add_index "ci_job_artifacts", ["expire_at", "job_id"], name: "index_ci_job_artifacts_on_expire_at_and_job_id", using: :btree add_index "ci_job_artifacts", ["job_id", "file_type"], name: "index_ci_job_artifacts_on_job_id_and_file_type", unique: true, using: :btree add_index "ci_job_artifacts", ["project_id"], name: "index_ci_job_artifacts_on_project_id", using: :btree @@ -450,7 +453,7 @@ ActiveRecord::Schema.define(version: 20180204200836) do t.integer "lock_version" end - add_index "ci_stages", ["pipeline_id", "name"], name: "index_ci_stages_on_pipeline_id_and_name", using: :btree + add_index "ci_stages", ["pipeline_id", "name"], name: "index_ci_stages_on_pipeline_id_and_name", unique: true, using: :btree add_index "ci_stages", ["pipeline_id"], name: "index_ci_stages_on_pipeline_id", using: :btree add_index "ci_stages", ["project_id"], name: "index_ci_stages_on_project_id", using: :btree @@ -946,6 +949,16 @@ ActiveRecord::Schema.define(version: 20180204200836) do add_index "labels", ["title"], name: "index_labels_on_title", using: :btree add_index "labels", ["type", "project_id"], name: "index_labels_on_type_and_project_id", using: :btree + create_table "lfs_file_locks", force: :cascade do |t| + t.integer "project_id", null: false + t.integer "user_id", null: false + t.datetime "created_at", null: false + t.string "path", limit: 511 + end + + add_index "lfs_file_locks", ["project_id", "path"], name: "index_lfs_file_locks_on_project_id_and_path", unique: true, using: :btree + add_index "lfs_file_locks", ["user_id"], name: "index_lfs_file_locks_on_user_id", using: :btree + create_table "lfs_objects", force: :cascade do |t| t.string "oid", null: false t.integer "size", limit: 8, null: false @@ -1997,6 +2010,8 @@ ActiveRecord::Schema.define(version: 20180204200836) do add_foreign_key "label_priorities", "projects", on_delete: :cascade add_foreign_key "labels", "namespaces", column: "group_id", on_delete: :cascade add_foreign_key "labels", "projects", name: "fk_7de4989a69", on_delete: :cascade + add_foreign_key "lfs_file_locks", "projects", on_delete: :cascade + add_foreign_key "lfs_file_locks", "users", on_delete: :cascade add_foreign_key "lists", "boards", name: "fk_0d3f677137", on_delete: :cascade add_foreign_key "lists", "labels", name: "fk_7a5553d60f", on_delete: :cascade add_foreign_key "members", "users", name: "fk_2e88fb7ce9", on_delete: :cascade diff --git a/doc/administration/high_availability/gitlab.md b/doc/administration/high_availability/gitlab.md index b85a166089d..e201848791c 100644 --- a/doc/administration/high_availability/gitlab.md +++ b/doc/administration/high_availability/gitlab.md @@ -25,11 +25,11 @@ for each GitLab application server in your environment. options. Here is an example snippet to add to `/etc/fstab`: ``` - 10.1.0.1:/var/opt/gitlab/.ssh /var/opt/gitlab/.ssh nfs defaults,soft,rsize=1048576,wsize=1048576,noatime,nobootwait,lookupcache=positive 0 2 - 10.1.0.1:/var/opt/gitlab/gitlab-rails/uploads /var/opt/gitlab/gitlab-rails/uploads nfs defaults,soft,rsize=1048576,wsize=1048576,noatime,nobootwait,lookupcache=positive 0 2 - 10.1.0.1:/var/opt/gitlab/gitlab-rails/shared /var/opt/gitlab/gitlab-rails/shared nfs defaults,soft,rsize=1048576,wsize=1048576,noatime,nobootwait,lookupcache=positive 0 2 - 10.1.0.1:/var/opt/gitlab/gitlab-ci/builds /var/opt/gitlab/gitlab-ci/builds nfs defaults,soft,rsize=1048576,wsize=1048576,noatime,nobootwait,lookupcache=positive 0 2 - 10.1.1.1:/var/opt/gitlab/git-data /var/opt/gitlab/git-data nfs defaults,soft,rsize=1048576,wsize=1048576,noatime,nobootwait,lookupcache=positive 0 2 + 10.1.0.1:/var/opt/gitlab/.ssh /var/opt/gitlab/.ssh nfs defaults,soft,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2 + 10.1.0.1:/var/opt/gitlab/gitlab-rails/uploads /var/opt/gitlab/gitlab-rails/uploads nfs defaults,soft,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2 + 10.1.0.1:/var/opt/gitlab/gitlab-rails/shared /var/opt/gitlab/gitlab-rails/shared nfs defaults,soft,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2 + 10.1.0.1:/var/opt/gitlab/gitlab-ci/builds /var/opt/gitlab/gitlab-ci/builds nfs defaults,soft,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2 + 10.1.0.1:/var/opt/gitlab/git-data /var/opt/gitlab/git-data nfs defaults,soft,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2 ``` 1. Create the shared directories. These may be different depending on your NFS diff --git a/doc/administration/index.md b/doc/administration/index.md index 0b199eecefd..e53268e5f3e 100644 --- a/doc/administration/index.md +++ b/doc/administration/index.md @@ -1,7 +1,7 @@ # Administrator documentation Learn how to administer your GitLab instance (Community Edition and -[Enterprise Editions](https://about.gitlab.com/gitlab-ee/)). +[Enterprise Editions](https://about.gitlab.com/products/)). Regular users don't have access to GitLab administration tools and settings. GitLab.com is administered by GitLab, Inc., therefore, only GitLab team members have diff --git a/doc/administration/integration/plantuml.md b/doc/administration/integration/plantuml.md index 65f59b72690..d978d1dca53 100644 --- a/doc/administration/integration/plantuml.md +++ b/doc/administration/integration/plantuml.md @@ -9,7 +9,19 @@ created in snippets, wikis, and repos. ## PlantUML Server Before you can enable PlantUML in GitLab; you need to set up your own PlantUML -server that will generate the diagrams. Installing and configuring your +server that will generate the diagrams. + +### Docker + +With Docker, you can just run a container like this: + +`docker run -d --name plantuml -p 8080:8080 plantuml/plantuml-server:tomcat` + +The **PlantUML URL** will be the hostname of the server running the container. + +### Debian/Ubuntu + +Installing and configuring your own PlantUML server is easy in Debian/Ubuntu distributions using Tomcat. First you need to create a `plantuml.war` file from the source code: diff --git a/doc/administration/job_artifacts.md b/doc/administration/job_artifacts.md index 33f8a69c249..d86a54daadd 100644 --- a/doc/administration/job_artifacts.md +++ b/doc/administration/job_artifacts.md @@ -87,10 +87,10 @@ _The artifacts are stored by default in ### Using object storage -In [GitLab Enterprise Edition Premium][eep] you can use an object storage like -AWS S3 to store the artifacts. +> Available in [GitLab Premium](https://about.gitlab.com/products/) and +[GitLab.com Silver](https://about.gitlab.com/gitlab-com/). -[Learn how to use the object storage option.][ee-os] +Use an [Object storage option][ee-os] like AWS S3 to store job artifacts. ## Expiring artifacts @@ -198,4 +198,3 @@ memory and disk I/O. [restart gitlab]: restart_gitlab.md "How to restart GitLab" [gitlab workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse "GitLab Workhorse repository" [ee-os]: https://docs.gitlab.com/ee/administration/job_artifacts.html#using-object-storage -[eep]: https://about.gitlab.com/gitlab-ee/ "GitLab Enterprise Edition Premium" diff --git a/doc/administration/operations/fast_ssh_key_lookup.md b/doc/administration/operations/fast_ssh_key_lookup.md index 9d1589d84aa..a795d5116ea 100644 --- a/doc/administration/operations/fast_ssh_key_lookup.md +++ b/doc/administration/operations/fast_ssh_key_lookup.md @@ -56,7 +56,7 @@ new one, and attempting to pull a repo. > **Warning:** Do not disable writes until SSH is confirmed to be working perfectly, because the file will quickly become out-of-date. -In the case of lookup failures (which are not uncommon), the `authorized_keys` +In the case of lookup failures (which are common), the `authorized_keys` file will still be scanned. So git SSH performance will still be slow for many users as long as a large file exists. diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md index 7d47aaac299..edb3e4c961e 100644 --- a/doc/administration/pages/index.md +++ b/doc/administration/pages/index.md @@ -61,6 +61,21 @@ Before proceeding with the Pages configuration, you will need to: NOTE: **Note:** If your GitLab instance and the Pages daemon are deployed in a private network or behind a firewall, your GitLab Pages websites will only be accessible to devices/users that have access to the private network. +### Add the domain to the Public Suffix List + +The [Public Suffix List](https://publicsuffix.org) is used by browsers to +decide how to treat subdomains. If your GitLab instance allows members of the +public to create GitLab Pages sites, it also allows those users to create +subdomains on the pages domain (`example.io`). Adding the domain to the Public +Suffix List prevents browsers from accepting +[supercookies](https://en.wikipedia.org/wiki/HTTP_cookie#Supercookie), +among other things. + +Follow [these instructions](https://publicsuffix.org/submit/) to submit your +GitLab Pages subdomain. For instance, if your domain is `example.io`, you should +request that `*.example.io` is added to the Public Suffix List. GitLab.com +added `*.gitlab.io` [in 2016](https://gitlab.com/gitlab-com/infrastructure/issues/230). + ### DNS configuration GitLab Pages expect to run on their own virtual host. In your DNS server/provider diff --git a/doc/administration/repository_storage_types.md b/doc/administration/repository_storage_types.md index 21184fed6e9..39bd19ac851 100644 --- a/doc/administration/repository_storage_types.md +++ b/doc/administration/repository_storage_types.md @@ -4,50 +4,63 @@ ## Legacy Storage -Legacy Storage is the storage behavior prior to version 10.0. For historical reasons, GitLab replicated the same -mapping structure from the projects URLs: +Legacy Storage is the storage behavior prior to version 10.0. For historical +reasons, GitLab replicated the same mapping structure from the projects URLs: - * Project's repository: `#{namespace}/#{project_name}.git` - * Project's wiki: `#{namespace}/#{project_name}.wiki.git` +* Project's repository: `#{namespace}/#{project_name}.git` +* Project's wiki: `#{namespace}/#{project_name}.wiki.git` -This structure made simple to migrate from existing solutions to GitLab and easy for Administrators to find where the -repository is stored. +This structure made it simple to migrate from existing solutions to GitLab and +easy for Administrators to find where the repository is stored. On the other hand this has some drawbacks: -Storage location will concentrate huge amount of top-level namespaces. The impact can be reduced by the introduction of [multiple storage paths][storage-paths]. +Storage location will concentrate huge amount of top-level namespaces. The +impact can be reduced by the introduction of [multiple storage +paths][storage-paths]. -Because Backups are a snapshot of the same URL mapping, if you try to recover a very old backup, you need to verify -if any project has taken the place of an old removed project sharing the same URL. This means that `mygroup/myproject` -from your backup may not be the same original project that is today in the same URL. +Because backups are a snapshot of the same URL mapping, if you try to recover a +very old backup, you need to verify whether any project has taken the place of +an old removed or renamed project sharing the same URL. This means that +`mygroup/myproject` from your backup may not be the same original project that +is at that same URL today. -Any change in the URL will need to be reflected on disk (when groups / users or projects are renamed). This can add a lot -of load in big installations, and can be even worst if they are using any type of network based filesystem. +Any change in the URL will need to be reflected on disk (when groups / users or +projects are renamed). This can add a lot of load in big installations, +especially if using any type of network based filesystem. -Last, for GitLab Geo, this storage type means we have to synchronize the disk state, replicate renames in the correct -order or we may end-up with wrong repository or missing data temporarily. +For GitLab Geo in particular: Geo does work with legacy storage, but in some +edge cases due to race conditions it can lead to errors when a project is +renamed multiple times in short succession, or a project is deleted and +recreated under the same name very quickly. We expect these race events to be +rare, and we have not observed a race condition side-effect happening yet. -This pattern also exists in other objects stored in GitLab, like issue Attachments, GitLab Pages artifacts, -Docker Containers for the integrated Registry, etc. +This pattern also exists in other objects stored in GitLab, like issue +Attachments, GitLab Pages artifacts, Docker Containers for the integrated +Registry, etc. ## Hashed Storage -Hashed Storage is the new storage behavior we are rolling out with 10.0. It's not enabled by default yet, but we -encourage everyone to try-it and take the time to fix any script you may have that depends on the old behavior. +> **Warning:** Hashed storage is in **Beta**. For the latest updates, check the +> associated [issue](https://gitlab.com/gitlab-com/infrastructure/issues/2821) +> and please report any problems you encounter. -Instead of coupling project URL and the folder structure where the repository will be stored on disk, we are coupling -a hash, based on the project's ID. +Hashed Storage is the new storage behavior we are rolling out with 10.0. Instead +of coupling project URL and the folder structure where the repository will be +stored on disk, we are coupling a hash, based on the project's ID. This makes +the folder structure immutable, and therefore eliminates any requirement to +synchronize state from URLs to disk structure. This means that renaming a group, +user, or project will cost only the database transaction, and will take effect +immediately. -This makes the folder structure immutable, and therefore eliminates any requirement to synchronize state from URLs to -disk structure. This means that renaming a group, user or project will cost only the database transaction, and will take -effect immediately. +The hash also helps to spread the repositories more evenly on the disk, so the +top-level directory will contain less folders than the total amount of top-level +namespaces. -The hash also helps to spread the repositories more evenly on the disk, so the top-level directory will contain less -folders than the total amount of top-level namespaces. - -Hash format is based on hexadecimal representation of SHA256: `SHA256(project.id)`. -Top-level folder uses first 2 characters, followed by another folder with the next 2 characters. They are both stored in -a special folder `@hashed`, to co-exist with existing Legacy projects: +The hash format is based on the hexadecimal representation of SHA256: +`SHA256(project.id)`. The top-level folder uses the first 2 characters, followed +by another folder with the next 2 characters. They are both stored in a special +`@hashed` folder, to be able to co-exist with existing Legacy Storage projects: ```ruby # Project's repository: @@ -57,15 +70,13 @@ a special folder `@hashed`, to co-exist with existing Legacy projects: "@hashed/#{hash[0..1]}/#{hash[2..3]}/#{hash}.wiki.git" ``` -This new format also makes possible to restore backups with confidence, as when restoring a repository from the backup, -you will never mistakenly restore a repository in the wrong project (considering the backup is made after the migration). - ### How to migrate to Hashed Storage -In GitLab, go to **Admin > Settings**, find the **Repository Storage** section and select -"_Create new projects using hashed storage paths_". +In GitLab, go to **Admin > Settings**, find the **Repository Storage** section +and select "_Create new projects using hashed storage paths_". -To migrate your existing projects to the new storage type, check the specific [rake tasks]. +To migrate your existing projects to the new storage type, check the specific +[rake tasks]. [ce-28283]: https://gitlab.com/gitlab-org/gitlab-ce/issues/28283 [rake tasks]: raketasks/storage.md#migrate-existing-projects-to-hashed-storage @@ -73,11 +84,13 @@ To migrate your existing projects to the new storage type, check the specific [r ### Hashed Storage coverage -We are incrementally moving every storable object in GitLab to the Hashed Storage pattern. You can check the current -coverage status below. +We are incrementally moving every storable object in GitLab to the Hashed +Storage pattern. You can check the current coverage status below (and also see +the [issue](https://gitlab.com/gitlab-com/infrastructure/issues/2821)). -Note that things stored in an S3 compatible endpoint will not have the downsides mentioned earlier, if they are not -prefixed with `#{namespace}/#{project_name}`, which is true for CI Cache and LFS Objects. +Note that things stored in an S3 compatible endpoint will not have the downsides +mentioned earlier, if they are not prefixed with `#{namespace}/#{project_name}`, +which is true for CI Cache and LFS Objects. | Storable Object | Legacy Storage | Hashed Storage | S3 Compatible | GitLab Version | | --------------- | -------------- | -------------- | ------------- | -------------- | @@ -87,6 +100,6 @@ prefixed with `#{namespace}/#{project_name}`, which is true for CI Cache and LFS | Pages | Yes | No | - | - | | Docker Registry | Yes | No | - | - | | CI Build Logs | No | No | - | - | -| CI Artifacts | No | No | Yes (EEP) | - | +| CI Artifacts | No | No | Yes (Premium) | - | | CI Cache | No | No | Yes | - | -| LFS Objects | Yes | No | Yes (EEP) | - | +| LFS Objects | Yes | No | Yes (Premium) | - | diff --git a/doc/api/README.md b/doc/api/README.md index f226716c3b5..b193ef4ab7f 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -43,12 +43,14 @@ following locations: - [Pipeline Schedules](pipeline_schedules.md) - [Projects](projects.md) including setting Webhooks - [Project Access Requests](access_requests.md) +- [Project import/export](project_import_export.md) - [Project Members](members.md) - [Project Snippets](project_snippets.md) - [Protected Branches](protected_branches.md) - [Repositories](repositories.md) - [Repository Files](repository_files.md) - [Runners](runners.md) +- [Search](search.md) - [Services](services.md) - [Settings](settings.md) - [Sidekiq metrics](sidekiq_metrics.md) diff --git a/doc/api/commits.md b/doc/api/commits.md index 63554c63057..2c745d00887 100644 --- a/doc/api/commits.md +++ b/doc/api/commits.md @@ -198,6 +198,41 @@ Example response: } ``` +## Get references a commit is pushed to + +> [Introduced][ce-15026] in GitLab 10.6 + +Get all references (from branches or tags) a commit is pushed to. +The pagination parameters `page` and `per_page` can be used to restrict the list of references. + +``` +GET /projects/:id/repository/commits/:sha/refs +``` + +Parameters: + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user +| `sha` | string | yes | The commit hash | +| `type` | string | no | The scope of commits. Possible values `branch`, `tag`, `all`. Default is `all`. | + +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/repository/commits/5937ac0a7beb003549fc5fd26fc247adbce4a52e/refs?type=all" +``` + +Example response: + +```json +[ + {"type": "branch", "name": "'test'"}, + {"type": "branch", "name": "add-balsamiq-file"}, + {"type": "branch", "name": "wip"}, + {"type": "tag", "name": "v1.1.0"} + ] + +``` + ## Cherry pick a commit > [Introduced][ce-8047] in GitLab 8.15. @@ -500,3 +535,4 @@ Example response: [ce-6096]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6096 "Multi-file commit" [ce-8047]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8047 +[ce-15026]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15026 diff --git a/doc/api/groups.md b/doc/api/groups.md index de730cdd869..f50558b58a6 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -15,6 +15,7 @@ Parameters: | `order_by` | string | no | Order groups by `name` or `path`. Default is `name` | | `sort` | string | no | Order groups in `asc` or `desc` order. Default is `asc` | | `statistics` | boolean | no | Include group statistics (admins only) | +| `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (admins only) | | `owned` | boolean | no | Limit to groups owned by the current user | ``` @@ -98,6 +99,7 @@ Parameters: | `order_by` | string | no | Order groups by `name` or `path`. Default is `name` | | `sort` | string | no | Order groups in `asc` or `desc` order. Default is `asc` | | `statistics` | boolean | no | Include group statistics (admins only) | +| `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (admins only) | | `owned` | boolean | no | Limit to groups owned by the current user | ``` @@ -145,6 +147,7 @@ Parameters: | `simple` | boolean | no | Return only the ID, URL, name, and path of each project | | `owned` | boolean | no | Limit by projects owned by the current user | | `starred` | boolean | no | Limit by projects starred by the current user | +| `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (admins only) | Example response: @@ -204,6 +207,7 @@ Parameters: | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user | +| `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (admins only) | ```bash curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/4 diff --git a/doc/api/namespaces.md b/doc/api/namespaces.md index 25cae5ce1f9..1f0dc700640 100644 --- a/doc/api/namespaces.md +++ b/doc/api/namespaces.md @@ -39,7 +39,7 @@ Example response: "path": "group1", "kind": "group", "full_path": "group1", - "parent_id": "null", + "parent_id": null, "members_count_with_descendants": 2 }, { @@ -48,7 +48,7 @@ Example response: "path": "bar", "kind": "group", "full_path": "foo/bar", - "parent_id": "9", + "parent_id": 9, "members_count_with_descendants": 5 } ] @@ -84,7 +84,7 @@ Example response: "path": "twitter", "kind": "group", "full_path": "twitter", - "parent_id": "null", + "parent_id": null, "members_count_with_descendants": 2 } ] @@ -117,7 +117,7 @@ Example response: "path": "group1", "kind": "group", "full_path": "group1", - "parent_id": "null", + "parent_id": null, "members_count_with_descendants": 2 } ``` @@ -137,7 +137,7 @@ Example response: "path": "group1", "kind": "group", "full_path": "group1", - "parent_id": "null", + "parent_id": null, "members_count_with_descendants": 2 } ``` diff --git a/doc/api/project_import_export.md b/doc/api/project_import_export.md new file mode 100644 index 00000000000..e442442c750 --- /dev/null +++ b/doc/api/project_import_export.md @@ -0,0 +1,74 @@ +# Project import API + +[Introduced][ce-41899] in GitLab 10.6 + +[See also the project import/export documentation](../user/project/settings/import_export.md) + +## Import a file + +```http +POST /projects/import +``` + +| Attribute | Type | Required | Description | +| --------- | -------------- | -------- | ---------------------------------------- | +| `namespace` | integer/string | no | The ID or path of the namespace that the project will be imported to. Defaults to the current user's namespace | +| `file` | string | yes | The file to be uploaded | +| `path` | string | yes | Name and path for new project | + +To upload a file from your filesystem, use the `--form` argument. This causes +cURL to post data using the header `Content-Type: multipart/form-data`. +The `file=` parameter must point to a file on your filesystem and be preceded +by `@`. For example: + +```console +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "path=api-project" --form "file=@/path/to/file" https://gitlab.example.com/api/v4/projects/import +``` + +```json +{ + "id": 1, + "description": null, + "name": "api-project", + "name_with_namespace": "Administrator / api-project", + "path": "api-project", + "path_with_namespace": "root/api-project", + "created_at": "2018-02-13T09:05:58.023Z", + "import_status": "scheduled" +} +``` + +## Import status + +Get the status of an import. + +```http +GET /projects/:id/import +``` + +| Attribute | Type | Required | Description | +| --------- | -------------- | -------- | ---------------------------------------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | + +```console +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/import +``` + +Status can be one of `none`, `scheduled`, `failed`, `started`, or `finished`. + +If the status is `failed`, it will include the import error message under `import_error`. + +```json +{ + "id": 1, + "description": "Itaque perspiciatis minima aspernatur corporis consequatur.", + "name": "Gitlab Test", + "name_with_namespace": "Gitlab Org / Gitlab Test", + "path": "gitlab-test", + "path_with_namespace": "gitlab-org/gitlab-test", + "created_at": "2017-08-29T04:36:44.383Z", + "import_status": "started" +} +``` + +[ce-41899]: https://gitlab.com/gitlab-org/gitlab-ce/issues/41899 diff --git a/doc/api/projects.md b/doc/api/projects.md index 46f5de5aa0e..9e649efea9c 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -37,6 +37,7 @@ GET /projects | `membership` | boolean | no | Limit by projects that the current user is a member of | | `starred` | boolean | no | Limit by projects starred by the current user | | `statistics` | boolean | no | Include project statistics | +| `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (admins only) | | `with_issues_enabled` | boolean | no | Limit by enabled issues feature | | `with_merge_requests_enabled` | boolean | no | Limit by enabled merge requests feature | @@ -220,6 +221,7 @@ GET /users/:user_id/projects | `membership` | boolean | no | Limit by projects that the current user is a member of | | `starred` | boolean | no | Limit by projects starred by the current user | | `statistics` | boolean | no | Include project statistics | +| `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (admins only) | | `with_issues_enabled` | boolean | no | Limit by enabled issues feature | | `with_merge_requests_enabled` | boolean | no | Limit by enabled merge requests feature | @@ -388,6 +390,7 @@ GET /projects/:id | --------- | ---- | -------- | ----------- | | `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) | | `statistics` | boolean | no | Include project statistics | +| `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (admins only) | ```json { @@ -664,6 +667,7 @@ GET /projects/:id/forks | `membership` | boolean | no | Limit by projects that the current user is a member of | | `starred` | boolean | no | Limit by projects starred by the current user | | `statistics` | boolean | no | Include project statistics | +| `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (admins only) | | `with_issues_enabled` | boolean | no | Limit by enabled issues feature | | `with_merge_requests_enabled` | boolean | no | Limit by enabled merge requests feature | @@ -1326,6 +1330,10 @@ POST /projects/:id/housekeeping Read more in the [Branches](branches.md) documentation. +## Project Import/Export + +Read more in the [Project import/export](project_import_export.md) documentation. + ## Project members Read more in the [Project members](members.md) documentation. diff --git a/doc/api/search.md b/doc/api/search.md new file mode 100644 index 00000000000..d441b556186 --- /dev/null +++ b/doc/api/search.md @@ -0,0 +1,800 @@ +# Search API + +[Introduced][ce-41763] in GitLab 10.5 + +Every API call to search must be authenticated. + +## Global Search API + +Search globally across the GitLab instance. + +``` +GET /search +``` + +| Attribute | Type | Required | Description | +| ------------------- | ---------------- | ---------- | ---------------------------------------------------------------------------------------| +| `scope` | string | yes | The scope to search in | +| `search` | string | yes | The search query | + +Search the expression within the specified scope. Currently these scopes are supported: projects, issues, merge_requests, milestones, snippet_titles, snippet_blobs. + +The response depends on the requested scope. + +### Scope: projects + +```bash +curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/search?scope=projects&search=flight +``` + +Example response: + +```json +[ + { + "id": 6, + "description": "Nobis sed ipsam vero quod cupiditate veritatis hic.", + "name": "Flight", + "name_with_namespace": "Twitter / Flight", + "path": "flight", + "path_with_namespace": "twitter/flight", + "created_at": "2017-09-05T07:58:01.621Z", + "default_branch": "master", + "tag_list":[], + "ssh_url_to_repo": "ssh://jarka@localhost:2222/twitter/flight.git", + "http_url_to_repo": "http://localhost:3000/twitter/flight.git", + "web_url": "http://localhost:3000/twitter/flight", + "avatar_url": null, + "star_count": 0, + "forks_count": 0, + "last_activity_at": "2018-01-31T09:56:30.902Z" + } +] +``` + +### Scope: issues + +```bash +curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/search?scope=issues&search=file +``` + +Example response: + +```json +[ + { + "id": 83, + "iid": 1, + "project_id": 12, + "title": "Add file", + "description": "Add first file", + "state": "opened", + "created_at": "2018-01-24T06:02:15.514Z", + "updated_at": "2018-02-06T12:36:23.263Z", + "closed_at": null, + "labels":[], + "milestone": null, + "assignees": [{ + "id": 20, + "name": "Ceola Deckow", + "username": "sammy.collier", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/c23d85a4f50e0ea76ab739156c639231?s=80&d=identicon", + "web_url": "http://localhost:3000/sammy.collier" + }], + "author": { + "id": 1, + "name": "Administrator", + "username": "root", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", + "web_url": "http://localhost:3000/root" + }, + "assignee": { + "id": 20, + "name": "Ceola Deckow", + "username": "sammy.collier", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/c23d85a4f50e0ea76ab739156c639231?s=80&d=identicon", + "web_url": "http://localhost:3000/sammy.collier" + }, + "user_notes_count": 0, + "upvotes": 0, + "downvotes": 0, + "due_date": null, + "confidential": false, + "discussion_locked": null, + "web_url": "http://localhost:3000/h5bp/7bp/subgroup-prj/issues/1", + "time_stats": { + "time_estimate": 0, + "total_time_spent": 0, + "human_time_estimate": null, + "human_total_time_spent": null + } + } +] +``` + +**Note**: `assignee` column is deprecated, now we show it as a single-sized array `assignees` to conform to the GitLab EE API. + +### Scope: merge_requests + +```bash +curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/search?scope=merge_requests&search=file +``` + +Example response: + +```json +[ + { + "id": 56, + "iid": 8, + "project_id": 6, + "title": "Add first file", + "description": "This is a test MR to add file", + "state": "opened", + "created_at": "2018-01-22T14:21:50.830Z", + "updated_at": "2018-02-06T12:40:33.295Z", + "target_branch": "master", + "source_branch": "jaja-test", + "upvotes": 0, + "downvotes": 0, + "author": { + "id": 1, + "name": "Administrator", + "username": "root", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", + "web_url": "http://localhost:3000/root" + }, + "assignee": { + "id": 5, + "name": "Jacquelyn Kutch", + "username": "abigail", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/3138c66095ee4bd11a508c2f7f7772da?s=80&d=identicon", + "web_url": "http://localhost:3000/abigail" + }, + "source_project_id": 6, + "target_project_id": 6, + "labels": [ + "ruby", + "tests" + ], + "work_in_progress": false, + "milestone": { + "id": 13, + "iid": 3, + "project_id": 6, + "title": "v2.0", + "description": "Qui aut qui eos dolor beatae itaque tempore molestiae.", + "state": "active", + "created_at": "2017-09-05T07:58:29.099Z", + "updated_at": "2017-09-05T07:58:29.099Z", + "due_date": null, + "start_date": null + }, + "merge_when_pipeline_succeeds": false, + "merge_status": "can_be_merged", + "sha": "78765a2d5e0a43585945c58e61ba2f822e4d090b", + "merge_commit_sha": null, + "user_notes_count": 0, + "discussion_locked": null, + "should_remove_source_branch": null, + "force_remove_source_branch": true, + "web_url": "http://localhost:3000/twitter/flight/merge_requests/8", + "time_stats": { + "time_estimate": 0, + "total_time_spent": 0, + "human_time_estimate": null, + "human_total_time_spent": null + } + } +] +``` + +### Scope: milestones + +```bash +curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/search?scope=milestones&search=release +``` + +Example response: + +```json +[ + { + "id": 44, + "iid": 1, + "project_id": 12, + "title": "next release", + "description": "Next release milestone", + "state": "active", + "created_at": "2018-02-06T12:43:39.271Z", + "updated_at": "2018-02-06T12:44:01.298Z", + "due_date": "2018-04-18", + "start_date": "2018-02-04" + } +] +``` + +### Scope: snippet_titles + +```bash +curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/search?scope=snippet_titles&search=sample +``` + +Example response: + +```json +[ + { + "id": 50, + "title": "Sample file", + "file_name": "file.rb", + "description": "Simple ruby file", + "author": { + "id": 1, + "name": "Administrator", + "username": "root", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", + "web_url": "http://localhost:3000/root" + }, + "updated_at": "2018-02-06T12:49:29.104Z", + "created_at": "2017-11-28T08:20:18.071Z", + "project_id": 9, + "web_url": "http://localhost:3000/root/jira-test/snippets/50" + } +] +``` + +### Scope: snippet_blobs + +```bash +curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/search?scope=snippet_blos&search=test +``` + +Example response: + +```json +[ + { + "id": 50, + "title": "Sample file", + "file_name": "file.rb", + "description": "Simple ruby file", + "author": { + "id": 1, + "name": "Administrator", + "username": "root", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", + "web_url": "http://localhost:3000/root" + }, + "updated_at": "2018-02-06T12:49:29.104Z", + "created_at": "2017-11-28T08:20:18.071Z", + "project_id": 9, + "web_url": "http://localhost:3000/root/jira-test/snippets/50" + } +] +``` + + +## Group Search API + +Search within the specified group. + +If a user is not a member of a group and the group is private, a `GET` request on that group will result to a `404` status code. + +``` +GET /groups/:id/-/search +``` + +| Attribute | Type | Required | Description | +| ------------------- | ---------------- | ---------- | ---------------------------------------------------------------------------------------| +| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user | +| `scope` | string | yes | The scope to search in | +| `search` | string | yes | The search query | + +Search the expression within the specified scope. Currently these scopes are supported: projects, issues, merge_requests, milestones. + +The response depends on the requested scope. + +### Scope: projects + +```bash +curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/3/-/search?scope=projects&search=flight +``` + +Example response: + +```json +[ + { + "id": 6, + "description": "Nobis sed ipsam vero quod cupiditate veritatis hic.", + "name": "Flight", + "name_with_namespace": "Twitter / Flight", + "path": "flight", + "path_with_namespace": "twitter/flight", + "created_at": "2017-09-05T07:58:01.621Z", + "default_branch": "master", + "tag_list":[], + "ssh_url_to_repo": "ssh://jarka@localhost:2222/twitter/flight.git", + "http_url_to_repo": "http://localhost:3000/twitter/flight.git", + "web_url": "http://localhost:3000/twitter/flight", + "avatar_url": null, + "star_count": 0, + "forks_count": 0, + "last_activity_at": "2018-01-31T09:56:30.902Z" + } +] +``` + +### Scope: issues + +```bash +curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/3/-/search?scope=issues&search=file +``` + +Example response: + +```json +[ + { + "id": 83, + "iid": 1, + "project_id": 12, + "title": "Add file", + "description": "Add first file", + "state": "opened", + "created_at": "2018-01-24T06:02:15.514Z", + "updated_at": "2018-02-06T12:36:23.263Z", + "closed_at": null, + "labels":[], + "milestone": null, + "assignees": [{ + "id": 20, + "name": "Ceola Deckow", + "username": "sammy.collier", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/c23d85a4f50e0ea76ab739156c639231?s=80&d=identicon", + "web_url": "http://localhost:3000/sammy.collier" + }], + "author": { + "id": 1, + "name": "Administrator", + "username": "root", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", + "web_url": "http://localhost:3000/root" + }, + "assignee": { + "id": 20, + "name": "Ceola Deckow", + "username": "sammy.collier", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/c23d85a4f50e0ea76ab739156c639231?s=80&d=identicon", + "web_url": "http://localhost:3000/sammy.collier" + }, + "user_notes_count": 0, + "upvotes": 0, + "downvotes": 0, + "due_date": null, + "confidential": false, + "discussion_locked": null, + "web_url": "http://localhost:3000/h5bp/7bp/subgroup-prj/issues/1", + "time_stats": { + "time_estimate": 0, + "total_time_spent": 0, + "human_time_estimate": null, + "human_total_time_spent": null + } + } +] +``` + +**Note**: `assignee` column is deprecated, now we show it as a single-sized array `assignees` to conform to the GitLab EE API. + +### Scope: merge_requests + +```bash +curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/3/-/search?scope=merge_requests&search=file +``` + +Example response: + +```json +[ + { + "id": 56, + "iid": 8, + "project_id": 6, + "title": "Add first file", + "description": "This is a test MR to add file", + "state": "opened", + "created_at": "2018-01-22T14:21:50.830Z", + "updated_at": "2018-02-06T12:40:33.295Z", + "target_branch": "master", + "source_branch": "jaja-test", + "upvotes": 0, + "downvotes": 0, + "author": { + "id": 1, + "name": "Administrator", + "username": "root", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", + "web_url": "http://localhost:3000/root" + }, + "assignee": { + "id": 5, + "name": "Jacquelyn Kutch", + "username": "abigail", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/3138c66095ee4bd11a508c2f7f7772da?s=80&d=identicon", + "web_url": "http://localhost:3000/abigail" + }, + "source_project_id": 6, + "target_project_id": 6, + "labels": [ + "ruby", + "tests" + ], + "work_in_progress": false, + "milestone": { + "id": 13, + "iid": 3, + "project_id": 6, + "title": "v2.0", + "description": "Qui aut qui eos dolor beatae itaque tempore molestiae.", + "state": "active", + "created_at": "2017-09-05T07:58:29.099Z", + "updated_at": "2017-09-05T07:58:29.099Z", + "due_date": null, + "start_date": null + }, + "merge_when_pipeline_succeeds": false, + "merge_status": "can_be_merged", + "sha": "78765a2d5e0a43585945c58e61ba2f822e4d090b", + "merge_commit_sha": null, + "user_notes_count": 0, + "discussion_locked": null, + "should_remove_source_branch": null, + "force_remove_source_branch": true, + "web_url": "http://localhost:3000/twitter/flight/merge_requests/8", + "time_stats": { + "time_estimate": 0, + "total_time_spent": 0, + "human_time_estimate": null, + "human_total_time_spent": null + } + } +] +``` + +### Scope: milestones + +```bash +curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/3/-/search?scope=milestones&search=release +``` + +Example response: + +```json +[ + { + "id": 44, + "iid": 1, + "project_id": 12, + "title": "next release", + "description": "Next release milestone", + "state": "active", + "created_at": "2018-02-06T12:43:39.271Z", + "updated_at": "2018-02-06T12:44:01.298Z", + "due_date": "2018-04-18", + "start_date": "2018-02-04" + } +] +``` + +## Project Search API + +Search within the specified project. + +If a user is not a member of a project and the project is private, a `GET` request on that project will result to a `404` status code. + +``` +GET /projects/:id/-/search +``` + +| Attribute | Type | Required | Description | +| ------------------- | ---------------- | ---------- | ---------------------------------------------------------------------------------------| +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | +| `scope` | string | yes | The scope to search in | +| `search` | string | yes | The search query | + +Search the expression within the specified scope. Currently these scopes are supported: issues, merge_requests, milestones, notes, wiki_blobs, commits, blobs. + +The response depends on the requested scope. + + +### Scope: issues + +```bash +curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/12/-/search?scope=issues&search=file +``` + +Example response: + +```json +[ + { + "id": 83, + "iid": 1, + "project_id": 12, + "title": "Add file", + "description": "Add first file", + "state": "opened", + "created_at": "2018-01-24T06:02:15.514Z", + "updated_at": "2018-02-06T12:36:23.263Z", + "closed_at": null, + "labels":[], + "milestone": null, + "assignees": [{ + "id": 20, + "name": "Ceola Deckow", + "username": "sammy.collier", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/c23d85a4f50e0ea76ab739156c639231?s=80&d=identicon", + "web_url": "http://localhost:3000/sammy.collier" + }], + "author": { + "id": 1, + "name": "Administrator", + "username": "root", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", + "web_url": "http://localhost:3000/root" + }, + "assignee": { + "id": 20, + "name": "Ceola Deckow", + "username": "sammy.collier", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/c23d85a4f50e0ea76ab739156c639231?s=80&d=identicon", + "web_url": "http://localhost:3000/sammy.collier" + }, + "user_notes_count": 0, + "upvotes": 0, + "downvotes": 0, + "due_date": null, + "confidential": false, + "discussion_locked": null, + "web_url": "http://localhost:3000/h5bp/7bp/subgroup-prj/issues/1", + "time_stats": { + "time_estimate": 0, + "total_time_spent": 0, + "human_time_estimate": null, + "human_total_time_spent": null + } + } +] +``` + +**Note**: `assignee` column is deprecated, now we show it as a single-sized array `assignees` to conform to the GitLab EE API. + +### Scope: merge_requests + +```bash +curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/6/-/search?scope=merge_requests&search=file +``` + +Example response: + +```json +[ + { + "id": 56, + "iid": 8, + "project_id": 6, + "title": "Add first file", + "description": "This is a test MR to add file", + "state": "opened", + "created_at": "2018-01-22T14:21:50.830Z", + "updated_at": "2018-02-06T12:40:33.295Z", + "target_branch": "master", + "source_branch": "jaja-test", + "upvotes": 0, + "downvotes": 0, + "author": { + "id": 1, + "name": "Administrator", + "username": "root", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", + "web_url": "http://localhost:3000/root" + }, + "assignee": { + "id": 5, + "name": "Jacquelyn Kutch", + "username": "abigail", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/3138c66095ee4bd11a508c2f7f7772da?s=80&d=identicon", + "web_url": "http://localhost:3000/abigail" + }, + "source_project_id": 6, + "target_project_id": 6, + "labels": [ + "ruby", + "tests" + ], + "work_in_progress": false, + "milestone": { + "id": 13, + "iid": 3, + "project_id": 6, + "title": "v2.0", + "description": "Qui aut qui eos dolor beatae itaque tempore molestiae.", + "state": "active", + "created_at": "2017-09-05T07:58:29.099Z", + "updated_at": "2017-09-05T07:58:29.099Z", + "due_date": null, + "start_date": null + }, + "merge_when_pipeline_succeeds": false, + "merge_status": "can_be_merged", + "sha": "78765a2d5e0a43585945c58e61ba2f822e4d090b", + "merge_commit_sha": null, + "user_notes_count": 0, + "discussion_locked": null, + "should_remove_source_branch": null, + "force_remove_source_branch": true, + "web_url": "http://localhost:3000/twitter/flight/merge_requests/8", + "time_stats": { + "time_estimate": 0, + "total_time_spent": 0, + "human_time_estimate": null, + "human_total_time_spent": null + } + } +] +``` + +### Scope: milestones + +```bash +curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/12/-/search?scope=milestones&search=release +``` + +Example response: + +```json +[ + { + "id": 44, + "iid": 1, + "project_id": 12, + "title": "next release", + "description": "Next release milestone", + "state": "active", + "created_at": "2018-02-06T12:43:39.271Z", + "updated_at": "2018-02-06T12:44:01.298Z", + "due_date": "2018-04-18", + "start_date": "2018-02-04" + } +] +``` + +### Scope: notes + +```bash +curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/6/-/search?scope=notes&search=maxime +``` + +Example response: + +```json +[ + { + "id": 191, + "body": "Harum maxime consequuntur et et deleniti assumenda facilis.", + "attachment": null, + "author": { + "id": 23, + "name": "User 1", + "username": "user1", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/111d68d06e2d317b5a59c2c6c5bad808?s=80&d=identicon", + "web_url": "http://localhost:3000/user1" + }, + "created_at": "2017-09-05T08:01:32.068Z", + "updated_at": "2017-09-05T08:01:32.068Z", + "system": false, + "noteable_id": 22, + "noteable_type": "Issue", + "noteable_iid": 2 + } +] +``` + +### Scope: wiki_blobs + +```bash +curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/6/-/search?scope=wiki_blobs&search=bye +``` + +Example response: + +```json + +[ + { + "basename": "home", + "data": "hello\n\nand bye\n\nend", + "filename": "home.md", + "id": null, + "ref": "master", + "startline": 5, + "project_id": 6 + } +] +``` + +### Scope: commits + +```bash +curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/6/-/search?scope=commits&search=bye +``` + +Example response: + +```json + +[ + { + "id": "4109c2d872d5fdb1ed057400d103766aaea97f98", + "short_id": "4109c2d8", + "title": "goodbye $.browser", + "created_at": "2013-02-18T22:02:54.000Z", + "parent_ids": [ + "59d05353ab575bcc2aa958fe1782e93297de64c9" + ], + "message": "goodbye $.browser\n", + "author_name": "angus croll", + "author_email": "anguscroll@gmail.com", + "authored_date": "2013-02-18T22:02:54.000Z", + "committer_name": "angus croll", + "committer_email": "anguscroll@gmail.com", + "committed_date": "2013-02-18T22:02:54.000Z", + "project_id": 6 + } +] +``` + +### Scope: blobs + +```bash +curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/6/-/search?scope=blobs&search=installation +``` + +Example response: + +```json + +[ + { + "basename": "README", + "data": "```\n\n## Installation\n\nQuick start using the [pre-built", + "filename": "README.md", + "id": null, + "ref": "master", + "startline": 46, + "project_id": 6 + } +] +``` + +[ce-41763]: https://gitlab.com/gitlab-org/gitlab-ce/issues/41763 diff --git a/doc/api/users.md b/doc/api/users.md index 2082e45756a..a4447e32908 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -165,6 +165,12 @@ You can filter by [custom attributes](custom_attributes.md) with: GET /users?custom_attributes[key]=value&custom_attributes[other_key]=other_value ``` +You can include the users' [custom attributes](custom_attributes.md) in the response with: + +``` +GET /users?with_custom_attributes=true +``` + ## Single user Get a single user. @@ -245,6 +251,12 @@ Parameters: } ``` +You can include the user's [custom attributes](custom_attributes.md) in the response with: + +``` +GET /users/:id?with_custom_attributes=true +``` + ## User creation Creates a new user. Note only administrators can create new users. Either `password` or `reset_password` should be specified (`reset_password` takes priority). diff --git a/doc/ci/README.md b/doc/ci/README.md index eabeb4510db..532ae52a184 100644 --- a/doc/ci/README.md +++ b/doc/ci/README.md @@ -70,6 +70,8 @@ learn how to leverage its potential even more. - [Use SSH keys in your build environment](ssh_keys/README.md) - [Trigger pipelines through the GitLab API](triggers/README.md) - [Trigger pipelines on a schedule](../user/project/pipelines/schedules.md) +- [Kubernetes clusters](../user/project/clusters/index.md) - Integrate one or + more Kubernetes clusters to your project ## GitLab CI/CD for Docker diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md index 0109e77935a..9f6b0c54990 100644 --- a/doc/ci/examples/README.md +++ b/doc/ci/examples/README.md @@ -43,7 +43,7 @@ There's also a collection of repositories with [example projects](https://gitlab ### Static Application Security Testing (SAST) -- **(EEU)** [Scan your code for vulnerabilities](https://docs.gitlab.com/ee/ci/examples/sast.html) +- **(Ultimate)** [Scan your code for vulnerabilities](https://docs.gitlab.com/ee/ci/examples/sast.html) - [Scan your Docker images for vulnerabilities](sast_docker.md) ### Dynamic Application Security Testing (DAST) diff --git a/doc/ci/examples/browser_performance.md b/doc/ci/examples/browser_performance.md index a7945d05cd0..7bd0514d406 100644 --- a/doc/ci/examples/browser_performance.md +++ b/doc/ci/examples/browser_performance.md @@ -22,7 +22,7 @@ Once you set up the Runner, add a new job to `.gitlab-ci.yml`, called `performan This will create a `performance` job in your CI/CD pipeline and will run Sitespeed.io against the webpage you define. The full HTML Sitespeed.io report will be saved as an artifact, and if you have Pages enabled it can be viewed directly in your browser. For further customization options of Sitespeed.io, including the ability to provide a list of URLs to test, please consult their [documentation](https://www.sitespeed.io/documentation/sitespeed.io/configuration/). -For GitLab [Enterprise Edition Premium](https://about.gitlab.com/gitlab-ee/) users, a performance score can be automatically +For [GitLab Premium](https://about.gitlab.com/products/) users, a performance score can be automatically extracted and shown right in the merge request widget. Learn more about [Browser Performance Testing](https://docs.gitlab.com/ee/user/project/merge_requests/browser_performance_testing.html). ## Performance testing on Review Apps diff --git a/doc/ci/examples/code_climate.md b/doc/ci/examples/code_climate.md index f919ed3c797..d7df53494ed 100644 --- a/doc/ci/examples/code_climate.md +++ b/doc/ci/examples/code_climate.md @@ -25,10 +25,10 @@ codequality: This will create a `codequality` job in your CI pipeline and will allow you to download and analyze the report artifact in JSON format. -For GitLab [Enterprise Edition Starter][ee] users, this information can be automatically +For [GitLab Starter][ee] users, this information can be automatically extracted and shown right in the merge request widget. [Learn more on code quality diffs in merge requests](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html). [cli]: https://github.com/codeclimate/codeclimate [dind]: ../docker/using_docker_build.md#use-docker-in-docker-executor -[ee]: https://about.gitlab.com/gitlab-ee/ +[ee]: https://about.gitlab.com/products/ diff --git a/doc/ci/examples/dast.md b/doc/ci/examples/dast.md index 7bf647bbb8b..96de0f5ff5c 100644 --- a/doc/ci/examples/dast.md +++ b/doc/ci/examples/dast.md @@ -31,10 +31,10 @@ own) and finally write the results in the `gl-dast-report.json` file. You can then download and analyze the report artifact in JSON format. TIP: **Tip:** -Starting with [GitLab Enterprise Edition Ultimate][ee] 10.4, this information will +Starting with [GitLab Ultimate][ee] 10.4, this information will be automatically extracted and shown right in the merge request widget. To do so, the CI job must be named `dast` and the artifact path must be `gl-dast-report.json`. [Learn more about DAST results shown in merge requests](https://docs.gitlab.com/ee/user/project/merge_requests/dast.html). -[ee]: https://about.gitlab.com/gitlab-ee/ +[ee]: https://about.gitlab.com/products/ diff --git a/doc/ci/examples/sast_docker.md b/doc/ci/examples/sast_docker.md index d99cfe93afa..57a9c4bcfc1 100644 --- a/doc/ci/examples/sast_docker.md +++ b/doc/ci/examples/sast_docker.md @@ -46,10 +46,10 @@ them in a [YAML file](https://github.com/arminc/clair-scanner/blob/master/README in our case its named `clair-whitelist.yml`. TIP: **Tip:** -Starting with [GitLab Enterprise Edition Ultimate][ee] 10.4, this information will +Starting with [GitLab Ultimate][ee] 10.4, this information will be automatically extracted and shown right in the merge request widget. To do so, the CI/CD job must be named `sast:container` and the artifact path must be `gl-sast-container-report.json`. [Learn more on application security testing results shown in merge requests](https://docs.gitlab.com/ee/user/project/merge_requests/sast_docker.html). -[ee]: https://about.gitlab.com/gitlab-ee/ +[ee]: https://about.gitlab.com/products/ diff --git a/doc/ci/triggers/README.md b/doc/ci/triggers/README.md index 56a16f77e7f..47a576fdf5f 100644 --- a/doc/ci/triggers/README.md +++ b/doc/ci/triggers/README.md @@ -219,7 +219,7 @@ removed with one of the future versions of GitLab. You are advised to [ee-2017]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2017 [ci-229]: https://gitlab.com/gitlab-org/gitlab-ci/merge_requests/229 -[ee]: https://about.gitlab.com/gitlab-ee/ +[ee]: https://about.gitlab.com/products/ [variables]: ../variables/README.md [predef]: ../variables/README.md#predefined-variables-environment-variables [registry]: ../../user/project/container_registry.md diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 598a7515b01..f30a85b114e 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -447,7 +447,7 @@ export CI_REGISTRY_PASSWORD="longalfanumstring" ``` [ce-13784]: https://gitlab.com/gitlab-org/gitlab-ce/issues/13784 "Simple protection of CI secret variables" -[eep]: https://about.gitlab.com/gitlab-ee/ "Available only in GitLab Enterprise Edition Premium" +[eep]: https://about.gitlab.com/products/ "Available only in GitLab Premium" [envs]: ../environments.md [protected branches]: ../../user/project/protected_branches.md [protected tags]: ../../user/project/protected_tags.md diff --git a/doc/ci/variables/img/secret_variables.png b/doc/ci/variables/img/secret_variables.png Binary files differindex f70935069d9..3c1aa361dc2 100644 --- a/doc/ci/variables/img/secret_variables.png +++ b/doc/ci/variables/img/secret_variables.png diff --git a/doc/development/background_migrations.md b/doc/development/background_migrations.md index af2026c483e..fc1b202b5eb 100644 --- a/doc/development/background_migrations.md +++ b/doc/development/background_migrations.md @@ -94,6 +94,18 @@ jobs = [['BackgroundMigrationClassName', [1]], BackgroundMigrationWorker.bulk_perform_in(5.minutes, jobs) ``` +### Rescheduling background migrations + +If one of the background migrations contains a bug that is fixed in a patch +release, the background migration needs to be rescheduled so the migration would +be repeated on systems that already performed the initial migration. + +When you reschedule the background migration, make sure to turn the original +scheduling into a no-op by clearing up the `#up` and `#down` methods of the +migration performing the scheduling. Otherwise the background migration would be +scheduled multiple times on systems that are upgrading multiple patch releases at +once. + ## Cleaning Up >**Note:** diff --git a/doc/development/ee_features.md b/doc/development/ee_features.md index f8cee89e650..f6a14de96b2 100644 --- a/doc/development/ee_features.md +++ b/doc/development/ee_features.md @@ -28,9 +28,8 @@ we still need to merge changes from GitLab CE to EE. To help us get there, we should make sure that we no longer edit CE files in place in order to implement EE features. -Instead, all EE codes should be put inside the `ee/` top-level directory, and -tests should be put inside `spec/ee/`. We don't use `ee/spec` for now due to -technical limitation. The rest of codes should be as close as to the CE files. +Instead, all EE code should be put inside the `ee/` top-level directory. The +rest of the code should be as close to the CE files as possible. [single code base]: https://gitlab.com/gitlab-org/gitlab-ee/issues/2952#note_41016454 @@ -318,7 +317,7 @@ When you're testing EE-only features, avoid adding examples to the existing CE specs. Also do no change existing CE examples, since they should remain working as-is when EE is running without a license. -Instead place EE specs in the `spec/ee/spec` folder. +Instead place EE specs in the `ee/spec` folder. ## JavaScript code in `assets/javascripts/` diff --git a/doc/development/fe_guide/components.md b/doc/development/fe_guide/components.md new file mode 100644 index 00000000000..66a8abe42f7 --- /dev/null +++ b/doc/development/fe_guide/components.md @@ -0,0 +1,61 @@ +# Components + +## Contents +* [Dropdowns](#dropdowns) +* [Modals](#modals) + +## Dropdowns + +See also the [corresponding UX guide](../ux_guide/components.md#dropdowns). + +### How to style a bootstrap dropdown +1. Use the HTML structure provided by the [docs][bootstrap-dropdowns] +1. Add a specific class to the top level `.dropdown` element + + + ```Haml + .dropdown.my-dropdown + %button{ type: 'button', data: { toggle: 'dropdown' }, 'aria-haspopup': true, 'aria-expanded': false } + %span.dropdown-toggle-text + Toggle Dropdown + = icon('chevron-down') + + %ul.dropdown-menu + %li + %a + item! + ``` + + Or use the helpers + ```Haml + .dropdown.my-dropdown + = dropdown_toggle('Toogle!', { toggle: 'dropdown' }) + = dropdown_content + %li + %a + item! + ``` + +[bootstrap-dropdowns]: https://getbootstrap.com/docs/3.3/javascript/#dropdowns + +## Modals + +See also the [corresponding UX guide](../ux_guide/components.md#modals). + +We have a reusable Vue component for modals: [vue_shared/components/gl-modal.vue](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/javascripts/vue_shared/components/gl-modal.vue) + +Here is an example of how to use it: + +```html + <gl-modal + id="dogs-out-modal" + :header-title-text="s__('ModalExample|Let the dogs out?')" + footer-primary-button-variant="danger" + :footer-primary-button-text="s__('ModalExample|Let them out')" + @submit="letOut(theDogs)" + > + {{ s__('ModalExample|You’re about to let the dogs out.') }} + </gl-modal> +``` + +![example modal](img/gl-modal.png) diff --git a/doc/development/fe_guide/dropdowns.md b/doc/development/fe_guide/dropdowns.md index 6314f8f38d2..e9d6244355c 100644 --- a/doc/development/fe_guide/dropdowns.md +++ b/doc/development/fe_guide/dropdowns.md @@ -1,32 +1 @@ -# Dropdowns - - -## How to style a bootstrap dropdown -1. Use the HTML structure provided by the [docs][bootstrap-dropdowns] -1. Add a specific class to the top level `.dropdown` element - - - ```Haml - .dropdown.my-dropdown - %button{ type: 'button', data: { toggle: 'dropdown' }, 'aria-haspopup': true, 'aria-expanded': false } - %span.dropdown-toggle-text - Toggle Dropdown - = icon('chevron-down') - - %ul.dropdown-menu - %li - %a - item! - ``` - - Or use the helpers - ```Haml - .dropdown.my-dropdown - = dropdown_toggle('Toogle!', { toggle: 'dropdown' }) - = dropdown_content - %li - %a - item! - ``` - -[bootstrap-dropdowns]: https://getbootstrap.com/docs/3.3/javascript/#dropdowns +This page has moved [here](components.md#dropdowns). diff --git a/doc/development/fe_guide/img/gl-modal.png b/doc/development/fe_guide/img/gl-modal.png Binary files differnew file mode 100644 index 00000000000..47302e857bc --- /dev/null +++ b/doc/development/fe_guide/img/gl-modal.png diff --git a/doc/development/fe_guide/index.md b/doc/development/fe_guide/index.md index 72cb557d054..12dfc10812b 100644 --- a/doc/development/fe_guide/index.md +++ b/doc/development/fe_guide/index.md @@ -21,6 +21,8 @@ Working with our frontend assets requires Node (v4.3 or greater) and Yarn [jQuery][jquery] is used throughout the application's JavaScript, with [Vue.js][vue] for particularly advanced, dynamic elements. +We also use [Axios][axios] to handle all of our network requests. + ### Browser Support For our currently-supported browsers, see our [requirements][requirements]. @@ -77,8 +79,10 @@ Axios specific practices and gotchas. ## [Icons](icons.md) How we use SVG for our Icons. -## [Dropdowns](dropdowns.md) -How we use dropdowns. +## [Components](components.md) + +How we use UI components. + --- ## Style Guides @@ -122,6 +126,7 @@ The [externalization part of the guide](../i18n/externalization.md) explains the [webpack]: https://webpack.js.org/ [jquery]: https://jquery.com/ [vue]: http://vuejs.org/ +[axios]: https://github.com/axios/axios [airbnb-js-style-guide]: https://github.com/airbnb/javascript [scss-lint]: https://github.com/brigade/scss-lint [install]: ../../install/installation.md#4-node diff --git a/doc/development/fe_guide/style_guide_js.md b/doc/development/fe_guide/style_guide_js.md index 02773162801..917d28b48ee 100644 --- a/doc/development/fe_guide/style_guide_js.md +++ b/doc/development/fe_guide/style_guide_js.md @@ -207,10 +207,39 @@ Do not use them anymore and feel free to remove them when refactoring legacy cod var c = pureFunction(values.foo); ``` -1. Avoid constructors with side-effects +1. Avoid constructors with side-effects. +Although we aim for code without side-effects we need some side-effects for our code to run. + +If the class won't do anything if we only instantiate it, it's ok to add side effects into the constructor (_Note:_ The following is just an example. If the only purpose of the class is to add an event listener and handle the callback a function will be more suitable.) + +```javascript +// Bad +export class Foo { + constructor() { + this.init(); + } + init() { + document.addEventListener('click', this.handleCallback) + }, + handleCallback() { + + } +} + +// Good +export class Foo { + constructor() { + document.addEventListener() + } + handleCallback() { + } +} +``` + +On the other hand, if a class only needs to extend a third party/add event listeners in some specific cases, they should be initialized oustside of the constructor. 1. Prefer `.map`, `.reduce` or `.filter` over `.forEach` -A forEach will cause side effects, it will be mutating the array being iterated. Prefer using `.map`, +A forEach will most likely cause side effects, it will be mutating the array being iterated. Prefer using `.map`, `.reduce` or `.filter` ```javascript const users = [ { name: 'Foo' }, { name: 'Bar' } ]; @@ -302,20 +331,20 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation. #### Naming 1. **Extensions**: Use `.vue` extension for Vue components. -1. **Reference Naming**: Use camelCase for their instances: +1. **Reference Naming**: Use PascalCase for their instances: ```javascript // bad - import CardBoard from 'cardBoard' + import cardBoard from 'cardBoard.vue' components: { - CardBoard: + cardBoard, }; // good - import cardBoard from 'cardBoard' + import CardBoard from 'cardBoard.vue' components: { - cardBoard: + CardBoard, }; ``` diff --git a/doc/development/fe_guide/style_guide_scss.md b/doc/development/fe_guide/style_guide_scss.md index 86a8b4135af..655d94793dd 100644 --- a/doc/development/fe_guide/style_guide_scss.md +++ b/doc/development/fe_guide/style_guide_scss.md @@ -7,6 +7,8 @@ easy to maintain, and performant for the end-user. ### Naming +Filenames should use `snake_case`. + CSS classes should use the `lowercase-hyphenated` format rather than `snake_case` or `camelCase`. diff --git a/doc/development/feature_flags.md b/doc/development/feature_flags.md index 59e8a087e02..5d1f657015c 100644 --- a/doc/development/feature_flags.md +++ b/doc/development/feature_flags.md @@ -1,6 +1,6 @@ # Manage feature flags -Starting from GitLab 9.3 we support feature flags via +Starting from GitLab 9.3 we support feature flags for features in GitLab via [Flipper](https://github.com/jnunemaker/flipper/). You should use the `Feature` class (defined in `lib/feature.rb`) in your code to get, set and list feature flags. @@ -19,3 +19,8 @@ dynamic (querying the DB etc.). Once defined in `lib/feature.rb`, you will be able to activate a feature for a given feature group via the [`feature_group` param of the features API](../api/features.md#set-or-create-a-feature) + +## Feature flags for user applications + +GitLab does not yet support the use of feature flags in deployed user applications. +You can follow the progress on that [in the issue on our issue tracker](https://gitlab.com/gitlab-org/gitlab-ee/issues/779).
\ No newline at end of file diff --git a/doc/development/i18n/externalization.md b/doc/development/i18n/externalization.md index f493ad4ae66..c0a325a83e9 100644 --- a/doc/development/i18n/externalization.md +++ b/doc/development/i18n/externalization.md @@ -110,6 +110,8 @@ You can mark that content for translation with: In JavaScript we added the `__()` (double underscore parenthesis) function for translations. +In order to test JavaScript translations you have to change the GitLab localization to other language than English and you have to generate JSON files using `bundle exec rake gettext:po_to_json` or `bundle exec rake gettext:compile`. + ## Updating the PO files with the new content Now that the new content is marked for translation, we need to update the PO @@ -124,6 +126,9 @@ strings and remove any strings that aren't used anymore. You should check this file in. Once the changes are on master, they will be picked up by [Crowdin](http://translate.gitlab.com) and be presented for translation. +If there are merge conflicts in the `gitlab.pot` file, you can delete the file +and regenerate it using the same command. Confirm that you are not deleting any strings accidentally by looking over the diff. + The command also updates the translation files for each language: `locale/*/gitlab.po` These changes can be discarded, the languange files will be updated by Crowdin automatically. diff --git a/doc/development/i18n/index.md b/doc/development/i18n/index.md index 8aa0462d213..7290a175501 100644 --- a/doc/development/i18n/index.md +++ b/doc/development/i18n/index.md @@ -40,37 +40,12 @@ See [Translation guidelines](translation.md). ### Proof reading -Proof reading helps ensure the accuracy and consistency of translations. -All translations are proof read before being accepted. -If a translations requires changes, you will be notified with a comment explaining why. - -Community assistance proof reading translations is encouraged and appreciated. -Requests to become a proof reader will be considered on the merits of previous translations. - -- Bulgarian -- Chinese Simplified - - [Huang Tao](https://crowdin.com/profile/htve) -- Chinese Traditional - - [Huang Tao](https://crowdin.com/profile/htve) -- Chinese Traditional, Hong Kong - - [Huang Tao](https://crowdin.com/profile/htve) -- Dutch -- Esperanto -- French -- German -- Italian - - [Paolo Falomo](https://crowdin.com/profile/paolo.falomo) -- Japanese -- Korean - - [Huang Tao](https://crowdin.com/profile/htve) -- Portuguese, Brazilian -- Russian - - [Alexy Lustin](https://crowdin.com/profile/lustin) - - [Nikita Grylov](https://crowdin.com/profile/nixel2007) -- Spanish -- Ukrainian - -If you would like to be added as a proof reader, please [open an issue](https://gitlab.com/gitlab-org/gitlab-ce/issues). +Proof reading helps ensure the accuracy and consistency of translations. All +translations are proof read before being accepted. If a translations requires +changes, you will be notified with a comment explaining why. + +See [Proofreading Translations](proofreader.md) for more information on who's +able to proofread and instructions on becoming a proofreader yourself. ## Release diff --git a/doc/development/i18n/proofreader.md b/doc/development/i18n/proofreader.md new file mode 100644 index 00000000000..795e1e83105 --- /dev/null +++ b/doc/development/i18n/proofreader.md @@ -0,0 +1,48 @@ +# Proofread Translations + +Most translations are contributed, reviewed, and accepted by the community. We +are very appreciative of the work done by translators and proofreaders! + +## Proofreaders + +- Bulgarian +- Chinese Simplified + - Huang Tao - [GitLab](https://gitlab.com/htve), [Crowdin](https://crowdin.com/profile/htve) +- Chinese Traditional + - Huang Tao - [GitLab](https://gitlab.com/htve), [Crowdin](https://crowdin.com/profile/htve) +- Chinese Traditional, Hong Kong + - Huang Tao - [GitLab](https://gitlab.com/htve), [Crowdin](https://crowdin.com/profile/htve) +- Dutch +- Esperanto +- French +- German +- Italian + - Paolo Falomo - [GitLab](https://gitlab.com/paolofalomo), [Crowdin](https://crowdin.com/profile/paolo.falomo) +- Japanese +- Korean + - Huang Tao - [GitLab](https://gitlab.com/htve), [Crowdin](https://crowdin.com/profile/htve) +- Portuguese, Brazilian + - Paulo George Gomes Bezerra - [GitLab](https://gitlab.com/paulobezerra), [Crowdin](https://crowdin.com/profile/paulogomes.rep) +- Russian + - Nikita Grylov - [GitLab](https://gitlab.com/nixel2007), [Crowdin](https://crowdin.com/profile/nixel2007) + - Alexy Lustin - [GitLab](https://gitlab.com/allustin), [Crowdin](https://crowdin.com/profile/lustin) +- Spanish +- Ukrainian + - Volodymyr Sobotovych - [GitLab](https://gitlab.com/wheleph), [Crowdin](https://crowdin.com/profile/wheleph) + - Andrew Vityuk - [GitLab](https://gitlab.com/3_1_3_u), [Crowdin](https://crowdin.com/profile/andruwa13) + +## Become a proofreader + +> **Note:** Before requesting Proofreader permissions in Crowdin please make +> sure that you have a history of contributing translations to the GitLab +> project. + +1. Once your translations have been accepted, + [open a merge request](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/new) + to request Proofreader permissions and add yourself to the list above. + + In the merge request description, please include links to any projects you + have previously translated. + +1. Your request to become a proofreader will be considered on the merits of + your previous translations. diff --git a/doc/development/profiling.md b/doc/development/profiling.md index 97c997e0568..11878b4009b 100644 --- a/doc/development/profiling.md +++ b/doc/development/profiling.md @@ -27,6 +27,17 @@ Gitlab::Profiler.profile('/my-user') # Returns a RubyProf::Profile where 100 seconds is spent in UsersController#show ``` +For routes that require authorization you will need to provide a user to +`Gitlab::Profiler`. You can do this like so: + +```ruby +Gitlab::Profiler.profile('/gitlab-org/gitlab-test', user: User.first) +``` + +The user you provide will need to have a [personal access +token](https://docs.gitlab.com/ce/user/profile/personal_access_tokens.html) in +the GitLab instance. + Passing a `logger:` keyword argument to `Gitlab::Profiler.profile` will send ActiveRecord and ActionController log output to that logger. Further options are documented with the method source. diff --git a/doc/development/query_count_limits.md b/doc/development/query_count_limits.md index ebb6e0c2dac..310e3faf61b 100644 --- a/doc/development/query_count_limits.md +++ b/doc/development/query_count_limits.md @@ -1,8 +1,7 @@ # Query Count Limits -Each controller or API endpoint is allowed to execute up to 100 SQL queries. In -a production environment we'll only log an error in case this threshold is -exceeded, but in a test environment we'll raise an error instead. +Each controller or API endpoint is allowed to execute up to 100 SQL queries and +in test environments we'll raise an error when this threshold is exceeded. ## Solving Failing Tests diff --git a/doc/development/sidekiq_style_guide.md b/doc/development/sidekiq_style_guide.md index 59ebf41e09f..76ff51446ba 100644 --- a/doc/development/sidekiq_style_guide.md +++ b/doc/development/sidekiq_style_guide.md @@ -17,6 +17,9 @@ would be `process_something`. If you're not sure what queue a worker uses, you can find it using `SomeWorker.queue`. There is almost never a reason to manually override the queue name using `sidekiq_options queue: :some_queue`. +You must always add any new queues to `app/workers/all_queues.yml` otherwise +your worker will not run. + ## Queue Namespaces While different workers cannot share a queue, they can share a queue namespace. diff --git a/doc/development/testing_guide/testing_levels.md b/doc/development/testing_guide/testing_levels.md index 4adf0dc7c7a..e86c1f5232a 100644 --- a/doc/development/testing_guide/testing_levels.md +++ b/doc/development/testing_guide/testing_levels.md @@ -134,6 +134,10 @@ learn more. [GitLab QA]: https://gitlab.com/gitlab-org/gitlab-qa [part of GitLab Rails]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/qa +## EE-specific tests + +EE-specific tests follows the same organization, but under the `ee/spec` folder. + ## How to test at the correct level? As many things in life, deciding what to test at each level of testing is a diff --git a/doc/install/database_mysql.md b/doc/install/database_mysql.md index f9ba1508705..5c7557ed2b3 100644 --- a/doc/install/database_mysql.md +++ b/doc/install/database_mysql.md @@ -6,7 +6,6 @@ and [problems](https://bugs.mysql.com/bug.php?id=65830) that [suggested](https://bugs.mysql.com/bug.php?id=50909) [fixes](https://bugs.mysql.com/bug.php?id=65830) [have](https://bugs.mysql.com/bug.php?id=63164). -- We recommend using MySQL version 5.6 or later. Please see the following [issue][ce-38152]. ## Initial database setup diff --git a/doc/install/google_cloud_platform/img/boot_disk.png b/doc/install/google_cloud_platform/img/boot_disk.png Binary files differnew file mode 100644 index 00000000000..37b2d9eaae7 --- /dev/null +++ b/doc/install/google_cloud_platform/img/boot_disk.png diff --git a/doc/install/google_cloud_platform/img/change_admin_passwd_email.png b/doc/install/google_cloud_platform/img/change_admin_passwd_email.png Binary files differdeleted file mode 100644 index 1ffe14f60ff..00000000000 --- a/doc/install/google_cloud_platform/img/change_admin_passwd_email.png +++ /dev/null diff --git a/doc/install/google_cloud_platform/img/chrome_not_secure_page.png b/doc/install/google_cloud_platform/img/chrome_not_secure_page.png Binary files differdeleted file mode 100644 index e732066908f..00000000000 --- a/doc/install/google_cloud_platform/img/chrome_not_secure_page.png +++ /dev/null diff --git a/doc/install/google_cloud_platform/img/first_signin.png b/doc/install/google_cloud_platform/img/first_signin.png Binary files differnew file mode 100644 index 00000000000..6eb3392d674 --- /dev/null +++ b/doc/install/google_cloud_platform/img/first_signin.png diff --git a/doc/install/google_cloud_platform/img/gcp_gitlab_being_deployed.png b/doc/install/google_cloud_platform/img/gcp_gitlab_being_deployed.png Binary files differdeleted file mode 100644 index 2a1859da6e3..00000000000 --- a/doc/install/google_cloud_platform/img/gcp_gitlab_being_deployed.png +++ /dev/null diff --git a/doc/install/google_cloud_platform/img/gcp_gitlab_overview.png b/doc/install/google_cloud_platform/img/gcp_gitlab_overview.png Binary files differdeleted file mode 100644 index 1c4c870dbc9..00000000000 --- a/doc/install/google_cloud_platform/img/gcp_gitlab_overview.png +++ /dev/null diff --git a/doc/install/google_cloud_platform/img/gcp_landing.png b/doc/install/google_cloud_platform/img/gcp_landing.png Binary files differindex 6398d247ba0..d6390c4dd4f 100644 --- a/doc/install/google_cloud_platform/img/gcp_landing.png +++ b/doc/install/google_cloud_platform/img/gcp_landing.png diff --git a/doc/install/google_cloud_platform/img/gcp_launcher_console_home_page.png b/doc/install/google_cloud_platform/img/gcp_launcher_console_home_page.png Binary files differdeleted file mode 100644 index f492888ea4a..00000000000 --- a/doc/install/google_cloud_platform/img/gcp_launcher_console_home_page.png +++ /dev/null diff --git a/doc/install/google_cloud_platform/img/gcp_search_for_gitlab.png b/doc/install/google_cloud_platform/img/gcp_search_for_gitlab.png Binary files differdeleted file mode 100644 index b38af3966e2..00000000000 --- a/doc/install/google_cloud_platform/img/gcp_search_for_gitlab.png +++ /dev/null diff --git a/doc/install/google_cloud_platform/img/gitlab_deployed_page.png b/doc/install/google_cloud_platform/img/gitlab_deployed_page.png Binary files differdeleted file mode 100644 index fef9ae45f32..00000000000 --- a/doc/install/google_cloud_platform/img/gitlab_deployed_page.png +++ /dev/null diff --git a/doc/install/google_cloud_platform/img/gitlab_first_sign_in.png b/doc/install/google_cloud_platform/img/gitlab_first_sign_in.png Binary files differdeleted file mode 100644 index 381c0fe48a5..00000000000 --- a/doc/install/google_cloud_platform/img/gitlab_first_sign_in.png +++ /dev/null diff --git a/doc/install/google_cloud_platform/img/gitlab_launch_button.png b/doc/install/google_cloud_platform/img/gitlab_launch_button.png Binary files differdeleted file mode 100644 index 50f66f66118..00000000000 --- a/doc/install/google_cloud_platform/img/gitlab_launch_button.png +++ /dev/null diff --git a/doc/install/google_cloud_platform/img/launch_vm.png b/doc/install/google_cloud_platform/img/launch_vm.png Binary files differnew file mode 100644 index 00000000000..3fd13f232bb --- /dev/null +++ b/doc/install/google_cloud_platform/img/launch_vm.png diff --git a/doc/install/google_cloud_platform/img/new_gitlab_deployment_settings.png b/doc/install/google_cloud_platform/img/new_gitlab_deployment_settings.png Binary files differdeleted file mode 100644 index 00060841619..00000000000 --- a/doc/install/google_cloud_platform/img/new_gitlab_deployment_settings.png +++ /dev/null diff --git a/doc/install/google_cloud_platform/img/ssh_terminal.png b/doc/install/google_cloud_platform/img/ssh_terminal.png Binary files differnew file mode 100644 index 00000000000..6a1a418d8e9 --- /dev/null +++ b/doc/install/google_cloud_platform/img/ssh_terminal.png diff --git a/doc/install/google_cloud_platform/img/ssh_via_button.png b/doc/install/google_cloud_platform/img/ssh_via_button.png Binary files differdeleted file mode 100644 index 26106f159ad..00000000000 --- a/doc/install/google_cloud_platform/img/ssh_via_button.png +++ /dev/null diff --git a/doc/install/google_cloud_platform/img/vm_created.png b/doc/install/google_cloud_platform/img/vm_created.png Binary files differnew file mode 100644 index 00000000000..fb467f40838 --- /dev/null +++ b/doc/install/google_cloud_platform/img/vm_created.png diff --git a/doc/install/google_cloud_platform/img/vm_details.png b/doc/install/google_cloud_platform/img/vm_details.png Binary files differnew file mode 100644 index 00000000000..2d230416a4b --- /dev/null +++ b/doc/install/google_cloud_platform/img/vm_details.png diff --git a/doc/install/google_cloud_platform/index.md b/doc/install/google_cloud_platform/index.md index c6b767fff02..3389f0260f9 100644 --- a/doc/install/google_cloud_platform/index.md +++ b/doc/install/google_cloud_platform/index.md @@ -2,12 +2,7 @@ ![GCP landing page](img/gcp_landing.png) -The fastest way to get started on [Google Cloud Platform (GCP)][gcp] is through -the [Google Cloud Launcher][launcher] program. - -GitLab's official Google Launcher apps: -1. [GitLab Community Edition](https://console.cloud.google.com/launcher/details/gitlab-public/gitlab-community-edition?project=gitlab-public) -2. [GitLab Enterprise Edition](https://console.cloud.google.com/launcher/details/gitlab-public/gitlab-enterprise-edition?project=gitlab-public) +Gettung started with GitLab on a [Google Cloud Platform (GCP)][gcp] instance is quick and easy. ## Prerequisites @@ -17,84 +12,52 @@ There are only two prerequisites in order to install GitLab on GCP: 1. You need to sign up for the GCP program. If this is your first time, Google gives you [$300 credit for free][freetrial] to consume over a 60-day period. -Once you have performed those two steps, you can visit the -[GCP launcher console][console] which has a list of all the things you can -deploy on GCP. - -![GCP launcher console](img/gcp_launcher_console_home_page.png) - -The next step is to find and install GitLab. +Once you have performed those two steps, you can [create a VM](#creating-the-vm). -## Configuring and deploying the VM +## Creating the VM To deploy GitLab on GCP you need to follow five simple steps: -1. Go to https://cloud.google.com/launcher and login with your Google credentials -1. Search for GitLab from GitLab Inc. (not the same as Bitnami) and click on - the tile. +1. Go to https://console.cloud.google.com/compute/instances and login with your Google credentials. - ![Search for GitLab](img/gcp_search_for_gitlab.png) +1. Click on **Create** -1. In the next page, you can see an overview of the GitLab VM as well as some - estimated costs. Click the **Launch on Compute Engine** button to choose the - hardware and network settings. + ![Search for GitLab](img/launch_vm.png) - ![Launch on Compute Engine](img/gcp_gitlab_overview.png) +1. On the next page, you can select the type of VM as well as the + estimated costs. Provide the name of the instance, desired datacenter, and machine type. Note that GitLab recommends at least 2 vCPU's and 4GB of RAM. -1. In the settings page you can choose things like the datacenter where your GitLab - server will be hosted, the number of CPUs and amount of RAM, the disk size - and type, etc. Read GitLab's [requirements documentation][req] for more - details on what to choose depending on your needs. + ![Launch on Compute Engine](img/vm_details.png) - ![Deploy settings](img/new_gitlab_deployment_settings.png) +1. Click **Change** under Boot disk to select the size, type, and desired operating system. GitLab supports a [variety of linux operating systems][req], including Ubuntu and Debian. Click **Select** when finished. -1. As a last step, hit **Deploy** when ready. The process will finish in a few - seconds. + ![Deploy in progress](img/boot_disk.png) - ![Deploy in progress](img/gcp_gitlab_being_deployed.png) +1. As a last step allow HTTP and HTTPS traffic, then click **Create**. The process will finish in a few seconds. +## Installing GitLab -## Visiting GitLab for the first time +After a few seconds, the instance will be created and available to log in. The next step is to install GitLab onto the instance. -After a few seconds, GitLab will be successfully deployed and you should be -able to see the IP address that Google assigned to the VM, as well as the -credentials to the GitLab admin account. +![Deploy settings](img/vm_created.png) -![Deploy settings](img/gitlab_deployed_page.png) +1. Make a note of the IP address of the instance, as you will need that in a later step. +1. Click on the SSH button to connect to the instance. +1. A new window will appear, with you logged into the instance. -1. Click on the IP under **Site address** to visit GitLab. -1. Accept the self-signed certificate that Google automatically deployed in - order to securely reach GitLab's login page. -1. Use the username and password that are present in the Google console page - to login into GitLab and click **Sign in**. + ![GitLab first sign in](img/ssh_terminal.png) - ![GitLab first sign in](img/gitlab_first_sign_in.png) +1. Next, follow the instructions for installing GitLab for the operating system you choose, at https://about.gitlab.com/installation/. You can use the IP address from the step above, as the hostname. -Congratulations! GitLab is now installed and you can access it via your browser, -but we're not done yet. There are some steps you need to take in order to have -a fully functional GitLab installation. +1. Congratulations! GitLab is now installed and you can access it via your browser. To finish installation, open the URL in your browser and provide the initial administrator password. The username for this account is `root`. + + ![GitLab first sign in](img/first_signin.png) ## Next steps These are the most important next steps to take after you installed GitLab for the first time. -### Changing the admin password and email - -Google assigned a random password for the GitLab admin account and you should -change it ASAP: - -1. Visit the GitLab admin page through the link in the Google console under - **Admin URL**. -1. Find the Administrator user under the **Users** page and hit **Edit**. -1. Change the email address to a real one and enter a new password. - - ![Change GitLab admin password](img/change_admin_passwd_email.png) - -1. Hit **Save changes** for the changes to take effect. -1. After changing the password, you will be signed out from GitLab. Use the - new credentials to login again. - ### Assigning a static IP By default, Google assigns an ephemeral IP to your instance. It is strongly @@ -112,7 +75,7 @@ here's how you configure GitLab to be aware of the change: 1. SSH into the VM. You can easily use the **SSH** button in the Google console and a new window will pop up. - ![SSH button](img/ssh_via_button.png) + ![SSH button](img/vm_created.png) In the future you might want to set up [connecting with an SSH key][ssh] instead. @@ -161,7 +124,6 @@ Kerberos, etc. Here are some documents you might be interested in reading: - [GitLab Pages configuration](https://docs.gitlab.com/ce/administration/pages/index.html) - [GitLab Container Registry configuration](https://docs.gitlab.com/ce/administration/container_registry.html) -[console]: https://console.cloud.google.com/launcher "GCP launcher console" [freetrial]: https://console.cloud.google.com/freetrial "GCP free trial" [ip]: https://cloud.google.com/compute/docs/configure-instance-ip-addresses#promote_ephemeral_ip "Configuring an Instance's IP Addresses" [gcp]: https://cloud.google.com/ "Google Cloud Platform" diff --git a/doc/install/installation.md b/doc/install/installation.md index 6eb8890cc4f..4dfc03d0fe0 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -299,9 +299,9 @@ sudo usermod -aG redis git ### Clone the Source # Clone GitLab repository - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 10-4-stable gitlab + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 10-5-stable gitlab -**Note:** You can change `10-4-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! +**Note:** You can change `10-5-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! ### Configure It diff --git a/doc/integration/openid_connect_provider.md b/doc/integration/openid_connect_provider.md index 56f367d841e..ad41be52045 100644 --- a/doc/integration/openid_connect_provider.md +++ b/doc/integration/openid_connect_provider.md @@ -39,6 +39,7 @@ Currently the following user information is shared with clients: | `website` | `string` | URL for the user's website | `profile` | `string` | URL for the user's GitLab profile | `picture` | `string` | URL for the user's GitLab avatar +| `groups` | `array` | Names of the groups the user is a member of [OpenID Connect]: http://openid.net/connect/ "OpenID Connect website" [doorkeeper-openid_connect]: https://github.com/doorkeeper-gem/doorkeeper-openid_connect "Doorkeeper::OpenidConnect website" diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md index 0fab752afad..5f5ba2b69bc 100644 --- a/doc/topics/autodevops/index.md +++ b/doc/topics/autodevops/index.md @@ -95,7 +95,9 @@ Auto Deploy, and Auto Monitoring will be silently skipped. The Auto DevOps base domain is required if you want to make use of [Auto Review Apps](#auto-review-apps) and [Auto Deploy](#auto-deploy). It is defined -under the project's CI/CD settings while [enabling Auto DevOps](#enabling-auto-devops). +either under the project's CI/CD settings while +[enabling Auto DevOps](#enabling-auto-devops) or in instance-wide settings in +the CI/CD section. It can also be set at the project or group level as a variable, `AUTO_DEVOPS_DOMAIN`. A wildcard DNS A record matching the base domain is required, for example, @@ -198,13 +200,13 @@ static analysis and other code checks on the current code. The report is created, and is uploaded as an artifact which you can later download and check out. -In GitLab Enterprise Edition Starter, differences between the source and +In GitLab Starter, differences between the source and target branches are also [shown in the merge request widget](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html). ### Auto SAST -> Introduced in [GitLab Enterprise Edition Ultimate][ee] 10.3. +> Introduced in [GitLab Ultimate][ee] 10.3. Static Application Security Testing (SAST) uses the [gl-sast Docker image](https://gitlab.com/gitlab-org/gl-sast) to run static @@ -212,7 +214,7 @@ analysis on the current code and checks for potential security issues. Once the report is created, it's uploaded as an artifact which you can later download and check out. -In GitLab Enterprise Edition Ultimate, any security warnings are also +In GitLab Ultimate, any security warnings are also [shown in the merge request widget](https://docs.gitlab.com/ee/user/project/merge_requests/sast.html). ### Auto SAST for Docker images @@ -225,7 +227,7 @@ Docker image and checks for potential security issues. Once the report is created, it's uploaded as an artifact which you can later download and check out. -In GitLab Enterprise Edition Ultimate, any security warnings are also +In GitLab Ultimate, any security warnings are also [shown in the merge request widget](https://docs.gitlab.com/ee/user/project/merge_requests/sast_docker.html). ### Auto Review Apps @@ -256,7 +258,7 @@ be deleted. ### Auto DAST -> Introduced in [GitLab Enterprise Edition Ultimate][ee] 10.4. +> Introduced in [GitLab Ultimate][ee] 10.4. Dynamic Application Security Testing (DAST) uses the popular open source tool [OWASP ZAProxy](https://github.com/zaproxy/zaproxy) @@ -264,12 +266,12 @@ to perform an analysis on the current code and checks for potential security issues. Once the report is created, it's uploaded as an artifact which you can later download and check out. -In GitLab Enterprise Edition Ultimate, any security warnings are also +In GitLab Ultimate, any security warnings are also [shown in the merge request widget](https://docs.gitlab.com/ee/user/project/merge_requests/dast.html). ### Auto Browser Performance Testing -> Introduced in [GitLab Enterprise Edition Premium][ee] 10.4. +> Introduced in [GitLab Premium][ee] 10.4. Auto Browser Performance Testing utilizes the [Sitespeed.io container](https://hub.docker.com/r/sitespeedio/sitespeed.io/) to measure the performance of a web page. A JSON report is created and uploaded as an artifact, which includes the overall performance score for each page. By default, the root page of Review and Production environments will be tested. If you would like to add additional URL's to test, simply add the paths to a file named `.gitlab-urls.txt` in the root directory, one per line. For example: @@ -279,7 +281,7 @@ Auto Browser Performance Testing utilizes the [Sitespeed.io container](https://h /direction ``` -In GitLab Enterprise Edition Premium, performance differences between the source and target branches are [shown in the merge request widget](https://docs.gitlab.com/ee/user/project/merge_requests/browser_performance_testing.html). +In GitLab Premium, performance differences between the source and target branches are [shown in the merge request widget](https://docs.gitlab.com/ee/user/project/merge_requests/browser_performance_testing.html). ### Auto Deploy @@ -593,4 +595,4 @@ curl --data "value=true" --header "PRIVATE-TOKEN: personal_access_token" https:/ [postgresql]: https://www.postgresql.org/ [Auto DevOps template]: https://gitlab.com/gitlab-org/gitlab-ci-yml/blob/master/Auto-DevOps.gitlab-ci.yml [GitLab Omnibus Helm Chart]: ../../install/kubernetes/gitlab_omnibus.md -[ee]: https://about.gitlab.com/gitlab-ee/ +[ee]: https://about.gitlab.com/products/ diff --git a/doc/topics/autodevops/quick_start_guide.md b/doc/topics/autodevops/quick_start_guide.md index 4858735ee86..15567715c98 100644 --- a/doc/topics/autodevops/quick_start_guide.md +++ b/doc/topics/autodevops/quick_start_guide.md @@ -102,6 +102,11 @@ running: kubectl get svc ruby-app-nginx-ingress-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip}' ``` +NOTE: **Note:** +If your ingress controller has been installed in a different way, you can find +how to get the external IP address in the +[Cluster documentation](../../user/project/clusters/index.md#getting-the-external-ip-address). + Use this IP address to configure your DNS. This part heavily depends on your preferences and domain provider. But in case you are not sure, just create an A record with a wildcard host like `*.<your-domain>`. diff --git a/doc/update/10.4-to-10.5.md b/doc/update/10.4-to-10.5.md new file mode 100644 index 00000000000..313419ed13d --- /dev/null +++ b/doc/update/10.4-to-10.5.md @@ -0,0 +1,361 @@ +--- +comments: false +--- + +# From 10.4 to 10.5 + +Make sure you view this update guide from the tag (version) of GitLab you would +like to install. In most cases this should be the highest numbered production +tag (without rc in it). You can select the tag in the version dropdown at the +top left corner of GitLab (below the menu bar). + +If the highest number stable branch is unclear please check the +[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation +guide links by version. + +### 1. Stop server + +```bash +sudo service gitlab stop +``` + +### 2. Backup + +NOTE: If you installed GitLab from source, make sure `rsync` is installed. + +```bash +cd /home/git/gitlab + +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 3. Update Ruby + +NOTE: GitLab 9.0 and higher only support Ruby 2.3.x and dropped support for Ruby 2.1.x. Be +sure to upgrade your interpreter if necessary. + +You can check which version you are running with `ruby -v`. + +Download and compile Ruby: + +```bash +mkdir /tmp/ruby && cd /tmp/ruby +curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.6.tar.gz +echo '4e6a0f828819e15d274ae58485585fc8b7caace0 ruby-2.3.6.tar.gz' | shasum -c - && tar xzf ruby-2.3.6.tar.gz +cd ruby-2.3.6 +./configure --disable-install-rdoc +make +sudo make install +``` + +Install Bundler: + +```bash +sudo gem install bundler --no-ri --no-rdoc +``` + +### 4. Update Node + +GitLab now runs [webpack](http://webpack.js.org) to compile frontend assets. +We require a minimum version of node v6.0.0. + +You can check which version you are running with `node -v`. If you are running +a version older than `v6.0.0` you will need to update to a newer version. You +can find instructions to install from community maintained packages or compile +from source at the nodejs.org website. + +<https://nodejs.org/en/download/> + +Since 8.17, GitLab requires the use of yarn `>= v0.17.0` to manage +JavaScript dependencies. + +```bash +curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - +echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list +sudo apt-get update +sudo apt-get install yarn +``` + +More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install). + +### 5. Update Go + +NOTE: GitLab 9.2 and higher only supports Go 1.8.3 and dropped support for Go +1.5.x through 1.7.x. Be sure to upgrade your installation if necessary. + +You can check which version you are running with `go version`. + +Download and install Go: + +```bash +# Remove former Go installation folder +sudo rm -rf /usr/local/go + +curl --remote-name --progress https://storage.googleapis.com/golang/go1.8.3.linux-amd64.tar.gz +echo '1862f4c3d3907e59b04a757cfda0ea7aa9ef39274af99a784f5be843c80c6772 go1.8.3.linux-amd64.tar.gz' | shasum -a256 -c - && \ + sudo tar -C /usr/local -xzf go1.8.3.linux-amd64.tar.gz +sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/ +rm go1.8.3.linux-amd64.tar.gz +``` + +### 6. Get latest code + +```bash +cd /home/git/gitlab + +sudo -u git -H git fetch --all +sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically +sudo -u git -H git checkout -- locale +``` + +For GitLab Community Edition: + +```bash +cd /home/git/gitlab + +sudo -u git -H git checkout 10-5-stable +``` + +OR + +For GitLab Enterprise Edition: + +```bash +cd /home/git/gitlab + +sudo -u git -H git checkout 10-5-stable-ee +``` + +### 7. Update gitlab-shell + +```bash +cd /home/git/gitlab-shell + +sudo -u git -H git fetch --all --tags +sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_SHELL_VERSION) +sudo -u git -H bin/compile +``` + +### 8. Update gitlab-workhorse + +Install and compile gitlab-workhorse. GitLab-Workhorse uses +[GNU Make](https://www.gnu.org/software/make/). +If you are not using Linux you may have to run `gmake` instead of +`make` below. + +```bash +cd /home/git/gitlab-workhorse + +sudo -u git -H git fetch --all --tags +sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_WORKHORSE_VERSION) +sudo -u git -H make +``` + +### 9. Update Gitaly + +#### New Gitaly configuration options required + +In order to function Gitaly needs some additional configuration information. Below we assume you installed Gitaly in `/home/git/gitaly` and GitLab Shell in `/home/git/gitlab-shell`. + +```shell +echo ' +[gitaly-ruby] +dir = "/home/git/gitaly/ruby" + +[gitlab-shell] +dir = "/home/git/gitlab-shell" +' | sudo -u git tee -a /home/git/gitaly/config.toml +``` + +#### Check Gitaly configuration + +Due to a bug in the `rake gitlab:gitaly:install` script your Gitaly +configuration file may contain syntax errors. The block name +`[[storages]]`, which may occur more than once in your `config.toml` +file, should be `[[storage]]` instead. + +```shell +sudo -u git -H sed -i.pre-10.1 's/\[\[storages\]\]/[[storage]]/' /home/git/gitaly/config.toml +``` + +#### Compile Gitaly + +```shell +cd /home/git/gitaly +sudo -u git -H git fetch --all --tags +sudo -u git -H git checkout v$(</home/git/gitlab/GITALY_SERVER_VERSION) +sudo -u git -H make +``` + +### 10. Update MySQL permissions + +If you are using MySQL you need to grant the GitLab user the necessary +permissions on the database: + +```bash +mysql -u root -p -e "GRANT TRIGGER ON \`gitlabhq_production\`.* TO 'git'@'localhost';" +``` + +If you use MySQL with replication, or just have MySQL configured with binary logging, +you will need to also run the following on all of your MySQL servers: + +```bash +mysql -u root -p -e "SET GLOBAL log_bin_trust_function_creators = 1;" +``` + +You can make this setting permanent by adding it to your `my.cnf`: + +``` +log_bin_trust_function_creators=1 +``` + +### 11. Update configuration files + +#### New configuration options for `gitlab.yml` + +There might be configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them manually to your current `gitlab.yml`: + +```sh +cd /home/git/gitlab + +git diff origin/10-4-stable:config/gitlab.yml.example origin/10-5-stable:config/gitlab.yml.example +``` + +#### Nginx configuration + +Ensure you're still up-to-date with the latest NGINX configuration changes: + +```sh +cd /home/git/gitlab + +# For HTTPS configurations +git diff origin/10-4-stable:lib/support/nginx/gitlab-ssl origin/10-5-stable:lib/support/nginx/gitlab-ssl + +# For HTTP configurations +git diff origin/10-4-stable:lib/support/nginx/gitlab origin/10-5-stable:lib/support/nginx/gitlab +``` + +If you are using Strict-Transport-Security in your installation to continue using it you must enable it in your Nginx +configuration as GitLab application no longer handles setting it. + +If you are using Apache instead of NGINX please see the updated [Apache templates]. +Also note that because Apache does not support upstreams behind Unix sockets you +will need to let gitlab-workhorse listen on a TCP port. You can do this +via [/etc/default/gitlab]. + +[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache +[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/10-5-stable/lib/support/init.d/gitlab.default.example#L38 + +#### SMTP configuration + +If you're installing from source and use SMTP to deliver mail, you will need to add the following line +to config/initializers/smtp_settings.rb: + +```ruby +ActionMailer::Base.delivery_method = :smtp +``` + +See [smtp_settings.rb.sample] as an example. + +[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/10-5-stable/config/initializers/smtp_settings.rb.sample#L13 + +#### Init script + +There might be new configuration options available for [`gitlab.default.example`][gl-example]. View them with the command below and apply them manually to your current `/etc/default/gitlab`: + +```sh +cd /home/git/gitlab + +git diff origin/10-4-stable:lib/support/init.d/gitlab.default.example origin/10-5-stable:lib/support/init.d/gitlab.default.example +``` + +Ensure you're still up-to-date with the latest init script changes: + +```bash +cd /home/git/gitlab + +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab +``` + +For Ubuntu 16.04.1 LTS: + +```bash +sudo systemctl daemon-reload +``` + +### 12. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL installations (note: the line below states '--without postgres') +sudo -u git -H bundle install --without postgres development test --deployment + +# PostgreSQL installations (note: the line below states '--without mysql') +sudo -u git -H bundle install --without mysql development test --deployment + +# Optional: clean up old gems +sudo -u git -H bundle clean + +# Run database migrations +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production + +# Compile GetText PO files + +sudo -u git -H bundle exec rake gettext:compile RAILS_ENV=production + +# Update node dependencies and recompile assets +sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile RAILS_ENV=production NODE_ENV=production + +# Clean up cache +sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production +``` + +**MySQL installations**: Run through the `MySQL strings limits` and `Tables and data conversion to utf8mb4` [tasks](../install/database_mysql.md). + +### 13. Start application + +```bash +sudo service gitlab start +sudo service nginx restart +``` + +### 14. Check application status + +Check if GitLab and its environment are configured correctly: + +```bash +cd /home/git/gitlab + +sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production +``` + +To make sure you didn't miss anything run a more thorough check: + +```bash +cd /home/git/gitlab + +sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production +``` + +If all items are green, then congratulations, the upgrade is complete! + +## Things went south? Revert to previous version (10.4) + +### 1. Revert the code to the previous version + +Follow the [upgrade guide from 10.3 to 10.4](10.3-to-10.4.md), except for the +database migration (the backup is already migrated to the previous version). + +### 2. Restore from the backup + +```bash +cd /home/git/gitlab + +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` + +If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above. + +[yaml]: https://gitlab.com/gitlab-org/gitlab-ce/blob/10-5-stable/config/gitlab.yml.example +[gl-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/10-5-stable/lib/support/init.d/gitlab.default.example diff --git a/doc/update/mysql_to_postgresql.md b/doc/update/mysql_to_postgresql.md index fff47180099..44e9f6c5516 100644 --- a/doc/update/mysql_to_postgresql.md +++ b/doc/update/mysql_to_postgresql.md @@ -1,13 +1,15 @@ --- -last_updated: 2017-10-05 +last_updated: 2018-02-07 --- # Migrating from MySQL to PostgreSQL -> **Note:** This guide assumes you have a working Omnibus GitLab instance with +> **Note:** This guide assumes you have a working GitLab instance with > MySQL and want to migrate to bundled PostgreSQL database. -## Prerequisites +## Omnibus installation + +### Prerequisites First, we'll need to enable the bundled PostgreSQL database with up-to-date schema. Next, we'll use [pgloader](http://pgloader.io) to migrate the data @@ -19,7 +21,7 @@ Here's what you'll need to have installed: - Omnibus GitLab - MySQL -## Enable bundled PostgreSQL database +### Enable bundled PostgreSQL database 1. Stop GitLab: @@ -65,7 +67,7 @@ Here's what you'll need to have installed: After these steps, you'll have a fresh PostgreSQL database with up-to-date schema. -## Migrate data from MySQL to PostgreSQL +### Migrate data from MySQL to PostgreSQL Now, you can use pgloader to migrate the data from MySQL to PostgreSQL: @@ -104,122 +106,9 @@ the following: ----------------------------------------------- --------- --------- --------- -------------- public.abuse_reports 0 0 0 0.490s public.appearances 0 0 0 0.488s - public.approvals 0 0 0 0.273s - public.application_settings 1 1 0 0.266s - public.approvers 0 0 0 0.339s - public.approver_groups 0 0 0 0.357s - public.audit_events 1 1 0 0.410s - public.award_emoji 0 0 0 0.441s - public.boards 0 0 0 0.505s - public.broadcast_messages 0 0 0 0.498s - public.chat_names 0 0 0 0.576s - public.chat_teams 0 0 0 0.617s - public.ci_builds 0 0 0 0.611s - public.ci_group_variables 0 0 0 0.620s - public.ci_pipelines 0 0 0 0.599s - public.ci_pipeline_schedules 0 0 0 0.622s - public.ci_pipeline_schedule_variables 0 0 0 0.573s - public.ci_pipeline_variables 0 0 0 0.594s - public.ci_runners 0 0 0 0.533s - public.ci_runner_projects 0 0 0 0.584s - public.ci_sources_pipelines 0 0 0 0.564s - public.ci_stages 0 0 0 0.595s - public.ci_triggers 0 0 0 0.569s - public.ci_trigger_requests 0 0 0 0.596s - public.ci_variables 0 0 0 0.565s - public.container_repositories 0 0 0 0.605s - public.conversational_development_index_metrics 0 0 0 0.571s - public.deployments 0 0 0 0.607s - public.emails 0 0 0 0.602s - public.deploy_keys_projects 0 0 0 0.557s - public.events 160 160 0 0.677s - public.environments 0 0 0 0.567s - public.features 0 0 0 0.639s - public.events_for_migration 160 160 0 0.582s - public.feature_gates 0 0 0 0.579s - public.forked_project_links 0 0 0 0.660s - public.geo_nodes 0 0 0 0.686s - public.geo_event_log 0 0 0 0.626s - public.geo_repositories_changed_events 0 0 0 0.677s - public.geo_node_namespace_links 0 0 0 0.618s - public.geo_repository_renamed_events 0 0 0 0.696s - public.gpg_keys 0 0 0 0.704s - public.geo_repository_deleted_events 0 0 0 0.638s - public.historical_data 0 0 0 0.729s - public.geo_repository_updated_events 0 0 0 0.634s - public.index_statuses 0 0 0 0.746s - public.gpg_signatures 0 0 0 0.667s - public.issue_assignees 80 80 0 0.769s - public.identities 0 0 0 0.655s - public.issue_metrics 80 80 0 0.781s - public.issues 80 80 0 0.720s - public.labels 0 0 0 0.795s - public.issue_links 0 0 0 0.707s - public.label_priorities 0 0 0 0.793s - public.keys 0 0 0 0.734s - public.lfs_objects 0 0 0 0.812s - public.label_links 0 0 0 0.725s - public.licenses 0 0 0 0.813s - public.ldap_group_links 0 0 0 0.751s - public.members 52 52 0 0.830s - public.lfs_objects_projects 0 0 0 0.738s - public.merge_requests_closing_issues 0 0 0 0.825s - public.lists 0 0 0 0.769s - public.merge_request_diff_commits 0 0 0 0.840s - public.merge_request_metrics 0 0 0 0.837s - public.merge_requests 0 0 0 0.753s - public.merge_request_diffs 0 0 0 0.771s - public.namespaces 30 30 0 0.874s - public.merge_request_diff_files 0 0 0 0.775s - public.notes 0 0 0 0.849s - public.milestones 40 40 0 0.799s - public.oauth_access_grants 0 0 0 0.979s - public.namespace_statistics 0 0 0 0.797s - public.oauth_applications 0 0 0 0.899s - public.notification_settings 72 72 0 0.818s - public.oauth_access_tokens 0 0 0 0.807s - public.pages_domains 0 0 0 0.958s - public.oauth_openid_requests 0 0 0 0.832s - public.personal_access_tokens 0 0 0 0.965s - public.projects 8 8 0 0.987s - public.path_locks 0 0 0 0.925s - public.plans 0 0 0 0.923s - public.project_features 8 8 0 0.985s - public.project_authorizations 66 66 0 0.969s - public.project_import_data 8 8 0 1.002s - public.project_statistics 8 8 0 1.001s - public.project_group_links 0 0 0 0.949s - public.project_mirror_data 0 0 0 0.972s - public.protected_branch_merge_access_levels 0 0 0 1.017s - public.protected_branches 0 0 0 0.969s - public.protected_branch_push_access_levels 0 0 0 0.991s - public.protected_tags 0 0 0 1.009s - public.protected_tag_create_access_levels 0 0 0 0.985s - public.push_event_payloads 0 0 0 1.041s - public.push_rules 0 0 0 0.999s - public.redirect_routes 0 0 0 1.020s - public.remote_mirrors 0 0 0 1.034s - public.releases 0 0 0 0.993s - public.schema_migrations 896 896 0 1.057s - public.routes 38 38 0 1.021s - public.services 0 0 0 1.055s - public.sent_notifications 0 0 0 1.003s - public.slack_integrations 0 0 0 1.022s - public.spam_logs 0 0 0 1.024s - public.snippets 0 0 0 1.058s - public.subscriptions 0 0 0 1.069s - public.taggings 0 0 0 1.099s - public.timelogs 0 0 0 1.104s - public.system_note_metadata 0 0 0 1.038s - public.tags 0 0 0 1.034s - public.trending_projects 0 0 0 1.140s - public.uploads 0 0 0 1.129s - public.todos 80 80 0 1.085s - public.users_star_projects 0 0 0 1.153s - public.u2f_registrations 0 0 0 1.061s - public.web_hooks 0 0 0 1.179s - public.users 26 26 0 1.163s - public.user_agent_details 0 0 0 1.068s + . + . + . public.web_hook_logs 0 0 0 1.080s ----------------------------------------------- --------- --------- --------- -------------- COPY Threads Completion 4 4 0 2.008s @@ -240,9 +129,9 @@ the following: Now, you can verify that everything worked by visiting GitLab. -## Troubleshooting +### Troubleshooting -### Permissions +#### Permissions Note that the PostgreSQL user that you use for the above MUST have **superuser** privileges. Otherwise, you may see a similar message to the following: @@ -256,7 +145,7 @@ debugger invoked on a CL-POSTGRES-ERROR:INSUFFICIENT-PRIVILEGE in thread QUERY: ALTER TABLE approver_groups DISABLE TRIGGER ALL; ``` -### Experiencing 500 errors after the migration +#### Experiencing 500 errors after the migration If you experience 500 errors after the migration, try to clear the cache: @@ -265,3 +154,130 @@ sudo gitlab-rake cache:clear ``` [reconfigure GitLab]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure + +## Source installation + +### Prerequisites + +#### Install PostgreSQL and create database + +See [installation guide](../install/installation.md#6-database). + +#### Install [pgloader](http://pgloader.io) 3.4.1+ + +Install directly from your distro: +``` bash +sudo apt-get install pgloader +``` + +If this version is too old, use PostgreSQL's repository: +``` bash +# add repository +sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' + +# add key +sudo apt-get install wget ca-certificates +wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - + +# install package +sudo apt-get update +sudo apt-get install pgloader +``` + +### Enable bundled PostgreSQL database + +1. Stop GitLab: + + ``` bash + sudo service gitlab stop + ``` + +1. Switch database from MySQL to PostgreSQL + + ``` bash + cd /home/git/gitlab + sudo -u git mv config/database.yml config/database.yml.bak + sudo -u git cp config/database.yml.postgresql config/database.yml + sudo -u git -H chmod o-rwx config/database.yml + ``` + +1. Run the following commands to prepare the schema: + + ``` bash + sudo -u git -H bundle exec rake db:create db:migrate RAILS_ENV=production + ``` + +After these steps, you'll have a fresh PostgreSQL database with up-to-date schema. + +### Migrate data from MySQL to PostgreSQL + +Now, you can use pgloader to migrate the data from MySQL to PostgreSQL: + +1. Save the following snippet in a `commands.load` file, and edit with your + MySQL `username`, `password` and `host`: + + ``` + LOAD DATABASE + FROM mysql://username:password@host/gitlabhq_production + INTO postgresql://postgres@unix://var/run/postgresql:/gitlabhq_production + + WITH include no drop, truncate, disable triggers, create no tables, + create no indexes, preserve index names, no foreign keys, + data only + + ALTER SCHEMA 'gitlabhq_production' RENAME TO 'public' + + ; + ``` + +1. Start the migration: + + ``` bash + sudo -u postgres pgloader commands.load + ``` + +1. Once the migration finishes, you should see a summary table that looks like +the following: + + + ``` + table name read imported errors total time + ----------------------------------------------- --------- --------- --------- -------------- + fetch meta data 119 119 0 0.388s + Truncate 119 119 0 1.134s + ----------------------------------------------- --------- --------- --------- -------------- + public.abuse_reports 0 0 0 0.490s + public.appearances 0 0 0 0.488s + . + . + . + public.web_hook_logs 0 0 0 1.080s + ----------------------------------------------- --------- --------- --------- -------------- + COPY Threads Completion 4 4 0 2.008s + Reset Sequences 113 113 0 0.304s + Install Comments 0 0 0 0.000s + ----------------------------------------------- --------- --------- --------- -------------- + Total import time 1894 1894 0 12.497s + ``` + + If there is no output for more than 30 minutes, it's possible pgloader encountered an error. See + the [troubleshooting guide](#Troubleshooting) for more details. + +1. Start GitLab: + + ``` bash + sudo service gitlab start + ``` + +Now, you can verify that everything worked by visiting GitLab. + +### Troubleshooting + +#### Experiencing 500 errors after the migration + +If you experience 500 errors after the migration, try to clear the cache: + +``` bash +sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production +``` + diff --git a/doc/user/group/index.md b/doc/user/group/index.md index f78e5089886..88efddbfba8 100644 --- a/doc/user/group/index.md +++ b/doc/user/group/index.md @@ -245,20 +245,22 @@ To enable this feature, navigate to the group settings page. Select ![Checkbox for share with group lock](img/share_with_group_lock.png) -#### Member Lock (EES/EEP) +#### Member Lock -Available in [GitLab Enterprise Edition Starter](https://about.gitlab.com/gitlab-ee/), -with **Member Lock** it is possible to lock membership in project to the +> Available in [GitLab Starter](https://about.gitlab.com/products/) and +[GitLab.com Bronze](https://about.gitlab.com/gitlab-com/). + +With **Member Lock** it is possible to lock membership in project to the level of members in group. -Learn more about [Member Lock](https://docs.gitlab.com/ee/user/group/index.html#member-lock-ees-eep). +Learn more about [Member Lock](https://docs.gitlab.com/ee/user/group/index.html#member-lock). ### Advanced settings - **Projects**: view all projects within that group, add members to each project, access each project's settings, and remove any project from the same screen. - **Webhooks**: configure [webhooks](../project/integrations/webhooks.md) -and [push rules](https://docs.gitlab.com/ee/push_rules/push_rules.html#push-rules) to your group (Push Rules is available in [GitLab Enteprise Edition Starter](https://about.gitlab.com/products/).) +and [push rules](https://docs.gitlab.com/ee/push_rules/push_rules.html#push-rules) to your group (Push Rules is available in [GitLab Starter](https://about.gitlab.com/products/).) - **Audit Events**: view [Audit Events](https://docs.gitlab.com/ee/administration/audit_events.html#audit-events) -for the group (GitLab admins only, available in [GitLab Enterprise Edition Starter][ee]). +for the group (GitLab admins only, available in [GitLab Starter][ee]). - **Pipelines quota**: keep track of the [pipeline quota](../admin_area/settings/continuous_integration.md) for the group diff --git a/doc/user/permissions.md b/doc/user/permissions.md index 708d07fcec9..914a80bcd6a 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -117,14 +117,16 @@ and drag issues around. Read though the [documentation on Issue Boards permissions](project/issue_board.md#permissions) to learn more. -### File Locking permissions (EEP) +### File Locking permissions + +> Available in [GitLab Premium](https://about.gitlab.com/products/). The user that locks a file or directory is the only one that can edit and push their changes back to the repository where the locked objects are located. Read through the documentation on [permissions for File Locking](https://docs.gitlab.com/ee/user/project/file_lock.html#permissions-on-file-locking) to learn more. File Locking is available in -[GitLab Enterprise Edition Premium](https://about.gitlab.com/gitlab-ee/) only. +[GitLab Premium](https://about.gitlab.com/products/) only. ### Confidential Issues permissions @@ -251,12 +253,14 @@ for details about the pipelines security model. Since GitLab 8.15, LDAP user permissions can now be manually overridden by an admin user. Read through the documentation on [LDAP users permissions](https://docs.gitlab.com/ee/articles/how_to_configure_ldap_gitlab_ee/index.html#updating-user-permissions-new-feature) to learn more. -## Auditor users permissions (EEP) +## Auditor users permissions + +> Available in [GitLab Premium](https://about.gitlab.com/products/). An Auditor user should be able to access all projects and groups of a GitLab instance with the permissions described on the documentation on [auditor users permissions](https://docs.gitlab.com/ee/administration/auditor_users.html#permissions-and-restrictions-of-an-auditor-user). -Auditor users are available in [GitLab Enterprise Edition Premium](https://about.gitlab.com/gitlab-ee/) +Auditor users are available in [GitLab Premium](https://about.gitlab.com/products/) only. [^1]: On public and internal projects, all users are able to perform this action diff --git a/doc/user/profile/account/delete_account.md b/doc/user/profile/account/delete_account.md index e7596f5c577..910bd20f882 100644 --- a/doc/user/profile/account/delete_account.md +++ b/doc/user/profile/account/delete_account.md @@ -1,7 +1,7 @@ # Deleting a User Account - As a user, you can delete your own account by navigating to **Settings** > **Account** and selecting **Delete account** -- As an admin, you can delete a user account by navigating to the **Admin Area**, selecting the **Users** tab, selecting a user, and clicking on **Remove user** +- As an admin, you can delete a user account by navigating to the **Admin Area**, selecting the **Users** tab, selecting a user, and clicking on **Delete user** ## Associated Records diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md index e87b4403854..bbe25c2d911 100644 --- a/doc/user/project/clusters/index.md +++ b/doc/user/project/clusters/index.md @@ -5,20 +5,23 @@ Connect your project to Google Kubernetes Engine (GKE) or an existing Kubernetes cluster in a few steps. -With a cluster associated to your project, you can use Review Apps, deploy your -applications, run your pipelines, and much more, in an easy way. +## Overview + +With a Kubernetes cluster associated to your project, you can use +[Review Apps](../../../ci/review_apps/index.md), deploy your applications, run +your pipelines, and much more, in an easy way. There are two options when adding a new cluster to your project; either associate your account with Google Kubernetes Engine (GKE) so that you can [create new clusters](#adding-and-creating-a-new-gke-cluster-via-gitlab) from within GitLab, or provide the credentials to an [existing Kubernetes cluster](#adding-an-existing-kubernetes-cluster). -## Prerequisites +## Adding and creating a new GKE cluster via GitLab -In order to be able to manage your Kubernetes cluster through GitLab, the -following prerequisites must be met. +NOTE: **Note:** +You need Master [permissions] and above to access the Kubernetes page. -**For a cluster hosted on GKE:** +Before proceeding, make sure the following requirements are met: - The [Google authentication integration](../../../integration/google.md) must be enabled in GitLab at the instance level. If that's not the case, ask your @@ -28,30 +31,16 @@ following prerequisites must be met. account](https://cloud.google.com/billing/docs/how-to/manage-billing-account) must be set up and that you have to have permissions to access it. - You must have Master [permissions] in order to be able to access the - **Cluster** page. + **Kubernetes** page. - You must have [Cloud Billing API](https://cloud.google.com/billing/) enabled - You must have [Resource Manager API](https://cloud.google.com/resource-manager/) -**For an existing Kubernetes cluster:** - -- Since the cluster is already created, there are no prerequisites. - ---- - -If all of the above requirements are met, you can proceed to add a new Kubernetes -cluster. - -## Adding and creating a new GKE cluster via GitLab - -NOTE: **Note:** -You need Master [permissions] and above to access the Clusters page. - -Before proceeding, make sure all [prerequisites](#prerequisites) are met. -To add a new cluster hosted on GKE to your project: +If all of the above requirements are met, you can proceed to create and add a +new Kubernetes cluster that will be hosted on GKE to your project: -1. Navigate to your project's **CI/CD > Clusters** page. -1. Click on **Add cluster**. +1. Navigate to your project's **CI/CD > Kubernetes** page. +1. Click on **Add Kubernetes cluster**. 1. Click on **Create with GKE**. 1. Connect your Google account if you haven't done already by clicking the **Sign in with Google** button. @@ -66,7 +55,7 @@ To add a new cluster hosted on GKE to your project: - **Machine type** - The [machine type](https://cloud.google.com/compute/docs/machine-types) of the Virtual Machine instance that the cluster will be based on. - **Environment scope** - The [associated environment](#setting-the-environment-scope) to this cluster. -1. Finally, click the **Create cluster** button. +1. Finally, click the **Create Kubernetes cluster** button. After a few moments, your cluster should be created. If something goes wrong, you will be notified. @@ -77,14 +66,14 @@ enable the Cluster integration. ## Adding an existing Kubernetes cluster NOTE: **Note:** -You need Master [permissions] and above to access the Clusters page. +You need Master [permissions] and above to access the Kubernetes page. To add an existing Kubernetes cluster to your project: -1. Navigate to your project's **CI/CD > Clusters** page. -1. Click on **Add cluster**. -1. Click on **Add an existing cluster** and fill in the details: - - **Cluster name** (required) - The name you wish to give the cluster. +1. Navigate to your project's **CI/CD > Kubernetes** page. +1. Click on **Add Kuberntes cluster**. +1. Click on **Add an existing Kubernetes cluster** and fill in the details: + - **Kubernetes cluster name** (required) - The name you wish to give the cluster. - **Environment scope** (required)- The [associated environment](#setting-the-environment-scope) to this cluster. - **API URL** (required) - @@ -112,15 +101,13 @@ To add an existing Kubernetes cluster to your project: - If you or someone created a secret specifically for the project, usually with limited permissions, the secret's namespace and project namespace may be the same. -1. Finally, click the **Create cluster** button. - -The Kubernetes service takes the following parameters: +1. Finally, click the **Create Kuberntes cluster** button. After a few moments, your cluster should be created. If something goes wrong, you will be notified. You can now proceed to install some pre-defined applications and then -enable the Cluster integration. +enable the Kubernetes cluster integration. ## Installing applications @@ -134,11 +121,52 @@ added directly to your configured cluster. Those applications are needed for | [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) | 10.2+ | Ingress can provide load balancing, SSL termination, and name-based virtual hosting. It acts as a web proxy for your applications and is useful if you want to use [Auto DevOps](../../../topics/autodevops/index.md) or deploy your own web apps. | | [Prometheus](https://prometheus.io/docs/introduction/overview/) | 10.4+ | Prometheus is an open-source monitoring and alerting system useful to supervise your deployed applications | +## Getting the external IP address + +NOTE: **Note:** +You need a load balancer installed in your cluster in order to obtain the +external IP address with the following procedure. It can be deployed using the +[**Ingress** application](#installing-appplications). + +In order to publish your web application, you first need to find the external IP +address associated to your load balancer. + +If the cluster is on GKE, click on the **Google Kubernetes Engine** link in the +**Advanced settings**, or go directly to the +[Google Kubernetes Engine dashboard](https://console.cloud.google.com/kubernetes/) +and select the proper project and cluster. Then click on **Connect** and execute +the `gcloud` command in a local terminal or using the **Cloud Shell**. + +If the cluster is not on GKE, follow the specific instructions for your +Kubernetes provider to configure `kubectl` with the right credentials. + +If you installed the Ingress [via the **Applications**](#installing-applications), +run the following command: + +```bash +kubectl get svc --namespace=gitlab-managed-apps ingress-nginx-ingress-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip} ' +``` + +Otherwise, you can list the IP addresses of all load balancers: + +```bash +kubectl get svc --all-namespaces -o jsonpath='{range.items[?(@.status.loadBalancer.ingress)]}{.status.loadBalancer.ingress[*].ip} ' +``` + +The output is the external IP address of your cluster. This information can then +be used to set up DNS entries and forwarding rules that allow external access to +your deployed applications. + ## Setting the environment scope -When adding more than one clusters, you need to differentiate them with an -environment scope. The environment scope associates clusters and -[environments](../../../ci/environments.md) in an 1:1 relationship similar to how the +NOTE: **Note:** +This is only available for [GitLab Premium][ee] where you can add more than +one Kubernetes cluster. + +When adding more than one Kubernetes clusters to your project, you need to +differentiate them with an environment scope. The environment scope associates +clusters and [environments](../../../ci/environments.md) in an 1:1 relationship +similar to how the [environment-specific variables](../../../ci/variables/README.md#limiting-environment-scopes-of-secret-variables) work. @@ -148,7 +176,7 @@ cluster in a project, and a validation error will occur if otherwise. --- -For example, let's say the following clusters exist in a project: +For example, let's say the following Kubernetes clusters exist in a project: | Cluster | Environment scope | | ---------- | ------------------- | @@ -190,14 +218,13 @@ The result will then be: ## Multiple Kubernetes clusters -> Introduced in [GitLab Enterprise Edition Premium][ee] 10.3. +> Introduced in [GitLab Premium][ee] 10.3. -With GitLab EEP, you can associate more than one Kubernetes clusters to your +With GitLab Premium, you can associate more than one Kubernetes clusters to your project. That way you can have different clusters for different environments, like dev, staging, production, etc. -To add another cluster, follow the same steps as described in [adding a -Kubernetes cluster](#adding-a-kubernetes-cluster) and make sure to +Simply add another cluster, like you did the first time, and make sure to [set an environment scope](#setting-the-environment-scope) that will differentiate the new cluster with the rest. @@ -205,53 +232,50 @@ differentiate the new cluster with the rest. The Kubernetes cluster integration exposes the following [deployment variables](../../../ci/variables/README.md#deployment-variables) in the -GitLab CI/CD build environment: - -- `KUBE_URL` - Equal to the API URL. -- `KUBE_TOKEN` - The Kubernetes token. -- `KUBE_NAMESPACE` - The Kubernetes namespace is auto-generated if not specified. - The default value is `<project_name>-<project_id>`. You can overwrite it to - use different one if needed, otherwise the `KUBE_NAMESPACE` variable will - receive the default value. -- `KUBE_CA_PEM_FILE` - Only present if a custom CA bundle was specified. Path - to a file containing PEM data. -- `KUBE_CA_PEM` (deprecated) - Only if a custom CA bundle was specified. Raw PEM data. -- `KUBECONFIG` - Path to a file containing `kubeconfig` for this deployment. - CA bundle would be embedded if specified. - -## Enabling or disabling the Cluster integration +GitLab CI/CD build environment. + +| Variable | Description | +| -------- | ----------- | +| `KUBE_URL` | Equal to the API URL. | +| `KUBE_TOKEN` | The Kubernetes token. | +| `KUBE_NAMESPACE` | The Kubernetes namespace is auto-generated if not specified. The default value is `<project_name>-<project_id>`. You can overwrite it to use different one if needed, otherwise the `KUBE_NAMESPACE` variable will receive the default value. | +| `KUBE_CA_PEM_FILE` | Only present if a custom CA bundle was specified. Path to a file containing PEM data. | +| `KUBE_CA_PEM` | (**deprecated**) Only if a custom CA bundle was specified. Raw PEM data. | +| `KUBECONFIG` | Path to a file containing `kubeconfig` for this deployment. CA bundle would be embedded if specified. | + +## Enabling or disabling the Kubernetes cluster integration After you have successfully added your cluster information, you can enable the -Cluster integration: +Kubernetes cluster integration: 1. Click the "Enabled/Disabled" switch 1. Hit **Save** for the changes to take effect You can now start using your Kubernetes cluster for your deployments. -To disable the Cluster integration, follow the same procedure. +To disable the Kubernetes cluster integration, follow the same procedure. -## Removing the Cluster integration +## Removing the Kubernetes cluster integration NOTE: **Note:** -You need Master [permissions] and above to remove a cluster integration. +You need Master [permissions] and above to remove a Kubernetes cluster integration. NOTE: **Note:** When you remove a cluster, you only remove its relation to GitLab, not the cluster itself. To remove the cluster, you can do so by visiting the GKE dashboard or using `kubectl`. -To remove the Cluster integration from your project, simply click on the +To remove the Kubernetes cluster integration from your project, simply click on the **Remove integration** button. You will then be able to follow the procedure -and [add a cluster](#adding-a-cluster) again. +and add a Kubernetes cluster again. ## What you can get with the Kubernetes integration Here's what you can do with GitLab if you enable the Kubernetes integration. -### Deploy Boards (EEP) +### Deploy Boards -> Available in [GitLab Enterprise Edition Premium][ee]. +> Available in [GitLab Premium][ee]. GitLab's Deploy Boards offer a consolidated view of the current health and status of each CI [environment](../../../ci/environments.md) running on Kubernetes, @@ -261,9 +285,9 @@ workflow they already use without any need to access Kubernetes. [> Read more about Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html) -### Canary Deployments (EEP) +### Canary Deployments -> Available in [GitLab Enterprise Edition Premium][ee]. +> Available in [GitLab Premium][ee]. Leverage [Kubernetes' Canary deployments](https://kubernetes.io/docs/concepts/cluster-administration/manage-deployment/#canary-deployments) and visualize your canary deployments right inside the Deploy Board, without @@ -303,4 +327,4 @@ the deployment variables above, ensuring any pods you create are labelled with `app=$CI_ENVIRONMENT_SLUG`. GitLab will do the rest! [permissions]: ../../permissions.md -[ee]: https://about.gitlab.com/gitlab-ee/ +[ee]: https://about.gitlab.com/products/ diff --git a/doc/user/project/img/label_priority_sort_order.png b/doc/user/project/img/label_priority_sort_order.png Binary files differdeleted file mode 100644 index 21c7a76a322..00000000000 --- a/doc/user/project/img/label_priority_sort_order.png +++ /dev/null diff --git a/doc/user/project/img/labels_assign_label_sidebar.png b/doc/user/project/img/labels_assign_label_sidebar.png Binary files differdeleted file mode 100644 index d74796fdb4d..00000000000 --- a/doc/user/project/img/labels_assign_label_sidebar.png +++ /dev/null diff --git a/doc/user/project/img/labels_assign_label_sidebar_saved.png b/doc/user/project/img/labels_assign_label_sidebar_saved.png Binary files differdeleted file mode 100644 index dabffe956dc..00000000000 --- a/doc/user/project/img/labels_assign_label_sidebar_saved.png +++ /dev/null diff --git a/doc/user/project/img/labels_default.png b/doc/user/project/img/labels_default.png Binary files differindex 7934e3bfb5e..7a7fab611a4 100644 --- a/doc/user/project/img/labels_default.png +++ b/doc/user/project/img/labels_default.png diff --git a/doc/user/project/img/labels_description_tooltip.png b/doc/user/project/img/labels_description_tooltip.png Binary files differdeleted file mode 100644 index eea4f8cf0f4..00000000000 --- a/doc/user/project/img/labels_description_tooltip.png +++ /dev/null diff --git a/doc/user/project/img/labels_filter.png b/doc/user/project/img/labels_filter.png Binary files differdeleted file mode 100644 index 6a1ebfc2ecb..00000000000 --- a/doc/user/project/img/labels_filter.png +++ /dev/null diff --git a/doc/user/project/img/labels_generate.png b/doc/user/project/img/labels_generate.png Binary files differdeleted file mode 100644 index 987f4b5be71..00000000000 --- a/doc/user/project/img/labels_generate.png +++ /dev/null diff --git a/doc/user/project/img/labels_generate_default.png b/doc/user/project/img/labels_generate_default.png Binary files differnew file mode 100644 index 00000000000..fca2a06e04f --- /dev/null +++ b/doc/user/project/img/labels_generate_default.png diff --git a/doc/user/project/img/labels_group_issues.png b/doc/user/project/img/labels_group_issues.png Binary files differnew file mode 100644 index 00000000000..29dcf7ff45e --- /dev/null +++ b/doc/user/project/img/labels_group_issues.png diff --git a/doc/user/project/img/labels_list.png b/doc/user/project/img/labels_list.png Binary files differnew file mode 100644 index 00000000000..12c47ea9766 --- /dev/null +++ b/doc/user/project/img/labels_list.png diff --git a/doc/user/project/img/labels_new_label.png b/doc/user/project/img/labels_new_label.png Binary files differdeleted file mode 100644 index e26425d0188..00000000000 --- a/doc/user/project/img/labels_new_label.png +++ /dev/null diff --git a/doc/user/project/img/labels_new_label_on_the_fly.png b/doc/user/project/img/labels_new_label_on_the_fly.png Binary files differdeleted file mode 100644 index 2ac9805b1ab..00000000000 --- a/doc/user/project/img/labels_new_label_on_the_fly.png +++ /dev/null diff --git a/doc/user/project/img/labels_new_label_on_the_fly_create.png b/doc/user/project/img/labels_new_label_on_the_fly_create.png Binary files differdeleted file mode 100644 index 02ccf68553b..00000000000 --- a/doc/user/project/img/labels_new_label_on_the_fly_create.png +++ /dev/null diff --git a/doc/user/project/img/labels_prioritize.png b/doc/user/project/img/labels_prioritize.png Binary files differdeleted file mode 100644 index d602a3c90ec..00000000000 --- a/doc/user/project/img/labels_prioritize.png +++ /dev/null diff --git a/doc/user/project/img/labels_prioritized.png b/doc/user/project/img/labels_prioritized.png Binary files differnew file mode 100644 index 00000000000..57dcfe89b3d --- /dev/null +++ b/doc/user/project/img/labels_prioritized.png diff --git a/doc/user/project/img/labels_promotion.png b/doc/user/project/img/labels_promotion.png Binary files differnew file mode 100644 index 00000000000..8a5efd210a2 --- /dev/null +++ b/doc/user/project/img/labels_promotion.png diff --git a/doc/user/project/img/labels_sidebar.png b/doc/user/project/img/labels_sidebar.png Binary files differnew file mode 100644 index 00000000000..7349c6d4f0c --- /dev/null +++ b/doc/user/project/img/labels_sidebar.png diff --git a/doc/user/project/img/labels_sidebar_assign.png b/doc/user/project/img/labels_sidebar_assign.png Binary files differnew file mode 100644 index 00000000000..61e8d04fc85 --- /dev/null +++ b/doc/user/project/img/labels_sidebar_assign.png diff --git a/doc/user/project/img/labels_sidebar_inline.png b/doc/user/project/img/labels_sidebar_inline.png Binary files differnew file mode 100644 index 00000000000..31fa397761d --- /dev/null +++ b/doc/user/project/img/labels_sidebar_inline.png diff --git a/doc/user/project/img/labels_sort_label_priority.png b/doc/user/project/img/labels_sort_label_priority.png Binary files differnew file mode 100644 index 00000000000..c8b97639121 --- /dev/null +++ b/doc/user/project/img/labels_sort_label_priority.png diff --git a/doc/user/project/img/labels_sort_priority.png b/doc/user/project/img/labels_sort_priority.png Binary files differnew file mode 100644 index 00000000000..a95198e7f72 --- /dev/null +++ b/doc/user/project/img/labels_sort_priority.png diff --git a/doc/user/project/img/labels_subscribe.png b/doc/user/project/img/labels_subscribe.png Binary files differdeleted file mode 100644 index 56f24ae7bc8..00000000000 --- a/doc/user/project/img/labels_subscribe.png +++ /dev/null diff --git a/doc/user/project/img/labels_subscriptions.png b/doc/user/project/img/labels_subscriptions.png Binary files differnew file mode 100644 index 00000000000..8bcb3b57f6c --- /dev/null +++ b/doc/user/project/img/labels_subscriptions.png diff --git a/doc/user/project/img/new_label_from_sidebar.gif b/doc/user/project/img/new_label_from_sidebar.gif Binary files differnew file mode 100644 index 00000000000..572b29a86e1 --- /dev/null +++ b/doc/user/project/img/new_label_from_sidebar.gif diff --git a/doc/user/project/index.md b/doc/user/project/index.md index 77eba8eda7c..175a8975ae1 100644 --- a/doc/user/project/index.md +++ b/doc/user/project/index.md @@ -17,7 +17,7 @@ When you create a project in GitLab, you'll have access to a large number of - [Issue tracker](issues/index.md): Discuss implementations with your team within issues - [Issue Boards](issue_board.md): Organize and prioritize your workflow - - [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards) (**EES/EEP**): Allow your teams to create their own workflows (Issue Boards) for the same project + - [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards) (**Starter/Premium**): Allow your teams to create their own workflows (Issue Boards) for the same project - [Repositories](repository/index.md): Host your code in a fully integrated platform - [Branches](repository/branches/index.md): use Git branching strategies to @@ -29,7 +29,7 @@ integrated platform - [Signing commits](gpg_signed_commits/index.md): use GPG to sign your commits - [Merge Requests](merge_requests/index.md): Apply your branching strategy and get reviewed by your team - - [Merge Request Approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) (**EES/EEP**): Ask for approval before + - [Merge Request Approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) (**Starter/Premium**): Ask for approval before implementing a change - [Fix merge conflicts from the UI](merge_requests/resolve_conflicts.md): Your Git diff tool right from GitLab's UI diff --git a/doc/user/project/integrations/bugzilla.md b/doc/user/project/integrations/bugzilla.md index ba2adc1afda..671804035cc 100644 --- a/doc/user/project/integrations/bugzilla.md +++ b/doc/user/project/integrations/bugzilla.md @@ -11,11 +11,7 @@ in the table below. | `issues_url` | The URL to the issue in Bugzilla project that is linked to this GitLab project. Note that the `issues_url` requires `:id` in the URL. This ID is used by GitLab as a placeholder to replace the issue number. | | `new_issue_url` | This is the URL to create a new issue in Bugzilla for the project linked to this GitLab project. Note that the `new_issue_url` requires PRODUCT_NAME to be updated with the product/project name in Bugzilla. | -Once you have configured and enabled Bugzilla: - -- the **Issues** link on the GitLab project pages takes you to the appropriate - Bugzilla product page -- clicking **New issue** on the project dashboard takes you to Bugzilla for entering a new issue +Once you have configured and enabled Bugzilla you'll see the Bugzilla link on the GitLab project pages that takes you to the appropriate Bugzilla project. ## Referencing issues in Bugzilla diff --git a/doc/user/project/integrations/jira.md b/doc/user/project/integrations/jira.md index f77569e4886..fc527663db0 100644 --- a/doc/user/project/integrations/jira.md +++ b/doc/user/project/integrations/jira.md @@ -116,7 +116,7 @@ in the table below. | `Transition ID` | This is the ID of a transition that moves issues to a closed state. You can find this number under JIRA workflow administration ([see screenshot](img/jira_workflow_screenshot.png)). **Closing JIRA issues via commits or Merge Requests won't work if you don't set the ID correctly.** | After saving the configuration, your GitLab project will be able to interact -with all JIRA projects in your JIRA instance. +with all JIRA projects in your JIRA instance and you'll see the JIRA link on the GitLab project pages that takes you to the appropriate JIRA project. ![JIRA service page](img/jira_service_page.png) diff --git a/doc/user/project/integrations/kubernetes.md b/doc/user/project/integrations/kubernetes.md index 543baaa81e1..f502d1c9821 100644 --- a/doc/user/project/integrations/kubernetes.md +++ b/doc/user/project/integrations/kubernetes.md @@ -81,9 +81,9 @@ GitLab CI/CD build environment: Here's what you can do with GitLab if you enable the Kubernetes integration. -### Deploy Boards (EEP) +### Deploy Boards -> Available in [GitLab Enterprise Edition Premium][ee]. +> Available in [GitLab Premium][ee]. GitLab's Deploy Boards offer a consolidated view of the current health and status of each CI [environment](../../../ci/environments.md) running on Kubernetes, @@ -93,9 +93,9 @@ workflow they already use without any need to access Kubernetes. [> Read more about Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html) -### Canary Deployments (EEP) +### Canary Deployments -> Available in [GitLab Enterprise Edition Premium][ee]. +> Available in [GitLab Premium][ee]. Leverage [Kubernetes' Canary deployments](https://kubernetes.io/docs/concepts/cluster-administration/manage-deployment/#canary-deployments) and visualize your canary deployments right inside the Deploy Board, without @@ -134,4 +134,4 @@ containers. To use this integration, you should deploy to Kubernetes using the deployment variables above, ensuring any pods you create are labelled with `app=$CI_ENVIRONMENT_SLUG`. GitLab will do the rest! -[ee]: https://about.gitlab.com/gitlab-ee/ +[ee]: https://about.gitlab.com/products/ diff --git a/doc/user/project/integrations/redmine.md b/doc/user/project/integrations/redmine.md index cc3218fbfd1..de2cf6d4647 100644 --- a/doc/user/project/integrations/redmine.md +++ b/doc/user/project/integrations/redmine.md @@ -12,6 +12,8 @@ in the table below. | `issues_url` | The URL to the issue in Redmine project that is linked to this GitLab project. Note that the `issues_url` requires `:id` in the URL. This ID is used by GitLab as a placeholder to replace the issue number. | | `new_issue_url` | This is the URL to create a new issue in Redmine for the project linked to this GitLab project. **This is currently not being used and will be removed in a future release.** | + Once you have configured and enabled Redmine you'll see the Redmine link on the GitLab project pages that takes you to the appropriate Redmine project. + As an example, below is a configuration for a project named gitlab-ci. ![Redmine configuration](img/redmine_configuration.png) diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md index 8c2690ec3b2..bc6306927e1 100644 --- a/doc/user/project/issue_board.md +++ b/doc/user/project/issue_board.md @@ -34,7 +34,7 @@ and deploy from one single platform. Issue Boards help you to visualize and manage the entire process _in_ GitLab. With [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards), available -only in [GitLab Enterprise Edition](https://about.gitlab.com/gitlab-ee/), +only in [GitLab Ultimate](https://about.gitlab.com/products/), you go even further, as you can not only keep yourself and your project organized from a broader perspective with one Issue Board per project, but also allow your team members to organize their own workflow by creating diff --git a/doc/user/project/issues/index.md b/doc/user/project/issues/index.md index 3e81dcb78c6..be4436749f9 100644 --- a/doc/user/project/issues/index.md +++ b/doc/user/project/issues/index.md @@ -35,7 +35,7 @@ your project public, open to collaboration. ### Streamline collaboration With [Multiple Assignees for Issues](https://docs.gitlab.com/ee/user/project/issues/multiple_assignees_for_issues.html), -available in [GitLab Enterprise Edition Starter](https://about.gitlab.com/gitlab-ee/) +available in [GitLab Starter](https://about.gitlab.com/products/) you can streamline collaboration and allow shared responsibilities to be clearly displayed. All assignees are shown across your workflows and receive notifications (as they would as single assignees), simplifying communication and ownership. @@ -64,9 +64,7 @@ You can also [search and filter](../../search/index.md#issues-and-merge-requests ### Issues per group -View all the issues in a group (that is, all the issues across all projects in that -group) by navigating to **Group > Issues**. This view also has the open and closed -issue tabs. +View issues in all projects in the group, including all projects of all descendant subgroups of the group. Navigate to **Group > Issues** to view these issues. This view also has the open and closed issues tabs. ![Group Issues list view](img/group_issues_list_view.png) @@ -141,7 +139,7 @@ Find GitLab Issue Boards by navigating to your **Project's Dashboard** > **Issue Read through the documentation for [Issue Boards](../issue_board.md) to find out more about this feature. -With [GitLab Enterprise Edition Starter](https://about.gitlab.com/gitlab-ee/), you can also +With [GitLab Starter](https://about.gitlab.com/products/), you can also create various boards per project with [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards). ### External Issue Tracker diff --git a/doc/user/project/issues/issues_functionalities.md b/doc/user/project/issues/issues_functionalities.md index 66140f389af..0bef83d18e8 100644 --- a/doc/user/project/issues/issues_functionalities.md +++ b/doc/user/project/issues/issues_functionalities.md @@ -41,9 +41,10 @@ it's reassigned to someone else to take it from there. if a user is not member of that project, it can only be assigned to them if they created the issue themselves. -##### 3.1. Multiple Assignees (EES/EEP) +##### 3.1. Multiple Assignees -Multiple Assignees are only available in [GitLab Enterprise Edition](https://about.gitlab.com/gitlab-ee/). +> Available in [GitLab Starter](https://about.gitlab.com/products/) and +[GitLab.com Bronze](https://about.gitlab.com/gitlab-com/). Often multiple people likely work on the same issue together, which can especially be difficult to track in large teams @@ -88,9 +89,10 @@ but they are immediately available to all projects in the group. > **Tip:** if the label doesn't exist yet, when you click **Edit**, it opens a dropdown menu from which you can select **Create new label**. -#### 8. Weight (EES/EEP) +#### 8. Weight -Issue Weights are only available in [GitLab Enterprise Edition](https://about.gitlab.com/gitlab-ee/). +> Available in [GitLab Starter](https://about.gitlab.com/products/) and +[GitLab.com Bronze](https://about.gitlab.com/gitlab-com/). - Attribute a weight (in a 0 to 9 range) to that issue. Easy to complete should weight 1 and very hard to complete should weight 9. diff --git a/doc/user/project/labels.md b/doc/user/project/labels.md index d7eb4bca89c..49f7baf9652 100644 --- a/doc/user/project/labels.md +++ b/doc/user/project/labels.md @@ -1,174 +1,125 @@ # Labels -Labels provide an easy way to categorize the issues or merge requests based on -descriptive titles like `bug`, `documentation` or any other text you feel like. -They can have different colors, a description, and are visible throughout -the issue tracker or inside each issue individually. +## Overview -With labels, you can navigate the issue tracker and filter any bloated -information to visualize only the issues you are interested in. Let's see how -that works. +Labels allow you to categorize issues or merge requests using descriptive titles like `bug`, `feature request`, or `docs`. Each label also has a customizable color. They allow you to quickly and dynamically filter and manage issues or merge requests you care about, and are visible throughout GitLab in most places where issues and merge requests are located. -## Create new labels +## Project labels and group labels + +In GitLab, you can create project and group labels: + +- **Project labels** can be assigned to issues or merge requests in that project only. +- **Group labels** can be assigned to any issue or merge request of any project in that group. +- In the [future](https://gitlab.com/gitlab-org/gitlab-ce/issues/40915), you will be able to assign group labels to issues and merge reqeusts of projects in [subgroups](../group/subgroups/index.md). + +## Creating labels >**Note:** -A permission level of `Developer` or higher is required in order to manage -labels. +A permission level of `Developer` or higher is required in order to create labels. -Head over a single project and navigate to **Issues > Labels**. +### New project label -The first time you visit this page, you'll notice that there are no labels -created yet. +To create a **project label**, navigate to **Issues > Labels** in the project. -Creating a new label from scratch is as easy as pressing the **New label** -button. From there on you can choose the name, give it an optional description, -a color and you are set. +Click the **New label** button. Enter the title, an optional description, and the background color. Click **Create label** to create the label. -When you are ready press the **Create label** button to create the new label. +If a project has no labels, you can generate a default set of project labels from its empty label list page: -![New label](img/labels_new_label.png) +![Labels generate default](img/labels_generate_default.png) ---- +GitLab will add the following default labels to the project: -## Default labels +![Labels default](img/labels_default.png) -The very first time you visit the labels area, it's gonna be empty. In that -case, it's possible to populate the labels for your project from a set of -predefined labels. +### New group label -Click the link to 'Generate a default set of labels' and GitLab will -generate them for you. There are 8 default generated labels in total: +To create a **group label**, follow similar steps from above to project labels. Navigate to **Issues > Labels** in the group and create it from there. -- bug -- confirmed -- critical -- discussion -- documentation -- enhancement -- suggestion -- support +Group labels appear in every label list page of the group's child projects. -## Labels Overview +![Labels list](img/labels_list.png) -![Default generated labels](img/labels_default.png) +### New project label from sidebar -You can see that from the labels page you can have an overview of the number of -issues and merge requests assigned to each label. +From the sidebar of an issue or a merge request, you can create a create a new **project label** inline immediately, instead of navigating to the project label list page. -## Prioritize labels +![Labels inline](img/new_label_from_sidebar.gif) ->**Notes:** -> -> - Introduced in GitLab 8.9. -> - Priority sorting is based on the highest priority label only. This might -> change in the future, follow the discussion in -> https://gitlab.com/gitlab-org/gitlab-ce/issues/18554. +## Editing labels -Prioritized labels are like any other label, but sorted by priority. This allows -you to sort issues and merge requests by label priority. +NOTE: **Note:** +A permission level of `Developer` or higher is required in order to edit labels. -To prioritize labels, navigate to your project's **Issues > Labels** and click -on the star icon next to them to put them in the priority list. Click on the -star icon again to remove them from the list. +You can update a label by navigating to **Issues > Labels** in the project ot group and clicking the pencil icon. -From there, you can drag them around to set the desired priority. Priority is -set from high to low with an ascending order. Labels with no priority, count as -having their priority set to null. +You can delete a label by clicking the trash icon. -![Prioritize labels](img/labels_prioritize.png) +### Promoting project labels to group labels -Now that you have labels prioritized, you can use the 'Label priority' and 'Priority' -sort orders in the issues or merge requests tracker. +If you are expanding from a few projects to a larger number of projects within the same group, you may want to share the same label among multiple projects in the same group. If you previously created a project label and now want to make it available for other projects, you can promote it to a group label. -In the following, everything applies to both issues and merge requests, but we'll -refer to just issues for brevity. +From the project label list page, you can promote a project label to a group label. This will merge all project labels across all projects in this group with the same name into a single group label. All issues and merge requests that previously were assigned one of these project labels will now be assigned the new group label. This action cannot be reversed and the changes are permanent. -The 'Label priority' sort order positions issues with higher priority labels -toward the top, and issues with lower priority labels toward the bottom. A non-prioritized -label is considered to have the lowest priority. For a given issue, we _only_ consider the -highest priority label assigned to it in the comparison. ([We are discussing](https://gitlab.com/gitlab-org/gitlab-ce/issues/18554) -including all the labels in a given issue for this comparison.) Given two issues -are equal according to this sort comparison, their relative order is equal, and -therefore it's not guaranteed that one will be always above the other. +![Labels promotion](img/labels_promotion.png) -![Label priority sort order](img/label_priority_sort_order.png) +## Assigning labels from the sidebar -The 'Priority' sort order comparison first considers an issue's milestone's due date, -(if the issue is assigned a milestone and the milestone's due date exists), and then -secondarily considers the label priority comparison above. Sooner due dates results -a higher sort order. If an issue doesn't have a milestone due date, it is equivalent to -being assigned to a milestone that has a due date in the infinite future. Given two issues -are equal according to this two-stage sort comparison, their relative order is equal, and -therefore it's not guaranteed that one will be always above the other. +Every issue and merge request can be assigned any number of labels. The labels are visible on every issue and merge request page, in the sidebar. They are also visible in the issue board. From the sidebar, you can assign or unassign a label to the object (i.e. label or unlabel it). You can also perform this as a [quick action](quick_actions.md) in a comment. -![Priority sort order](img/priority_sort_order.png) +| View labels in sidebar | Assign labels from sidebar | +|:---:|:---:| +| ![Labels sidebar](img/labels_sidebar.png) | ![Labels sidebar assign](img/labels_sidebar_assign.png) | +## Filtering issues and merge requests by label -## Subscribe to labels +### Filtering in list pages -If you don’t want to miss issues or merge requests that are important to you, -simply subscribe to a label. You’ll get notified whenever the label gets added -to an issue or merge request, making sure you don’t miss a thing. +From the project issue list page and the project merge request list page, you can [filter](../search/index.md#issues-and-merge-requests) by both group labels and project labels. -Go to your project's **Issues > Labels** area, find the label(s) you want to -subscribe to and click on the eye icon. Click again to unsubscribe. +From the group issue list page and the group merge request list page, you can [filter](../search/index.md#issues-and-merge-requests) by both group labels and project labels. -![Subscribe to labels](img/labels_subscribe.png) +![Labels group issues](img/labels_group_issues.png) -If you work on a large or popular project, try subscribing only to the labels -that are relevant to you. You’ll notice it’ll be much easier to focus on what’s -important. +### Filtering in issue boards -## Create a new label when inside an issue +- From [project boards](issue_board.md), you can filter by both group labels and project labels in the [search and filter bar](../search/index.md#issue-boards). -There are times when you are already inside an issue searching to assign a -label, only to realize it doesn't exist. Instead of going to the **Labels** -page and being distracted from your original purpose, you can create new -labels on the fly. +## Subscribing to labels -Expand the issue sidebar and select **Create new label** from the labels dropdown -list. Provide a name, pick a color and hit **Create**. The new label will be -ready to used right away! +From the project label list page and the group label list page, you can subscribe to [notifications](../../workflow/notifications.md) of a given label, to alert you that that label has been assigned to an issue or merge request. -![New label on the fly](img/labels_new_label_on_the_fly.png) +![Labels subscriptions](img/labels_subscriptions.png) -## Assigning labels to issues and merge requests +## Label priority -There are generally two ways to assign a label to an issue or merge request. +>**Notes:** +> +> - Introduced in GitLab 8.9. +> - Priority sorting is based on the highest priority label only. [This discussion](https://gitlab.com/gitlab-org/gitlab-ce/issues/18554) considers changing this. -The first one is to assign a label when you first create or edit an issue or -merge request. +Labels can have relative priorities, which are used in the "Label priority" and "Priority" sort orders of the issue and merge request list pages. -The second way is by using the right sidebar when inside an issue or merge -request. Expand it and hit **Edit** in the labels area. Start typing the name -of the label you are looking for to narrow down the list, and select it. You -can add more than one labels at once. When done, click outside the sidebar area -for the changes to take effect. +From the project label list page, star a label to indicate that it has a priority. Drag starred labels up and down to change their priority. Higher means higher priority. Prioritization happens at the project level, only on the project label list page, and not on the group label list page. However, both project and group labels can be prioritized on the project label list page since both types are displayed on the project label list page. -![Assign label in sidebar](img/labels_assign_label_sidebar.png) -![Save labels in sidebar](img/labels_assign_label_sidebar_saved.png) +![Labels prioritized](img/labels_prioritized.png) ---- +On the project and group issue and merge request list pages, you can sort by `Label priority` and `Priority`, which account for objects (issues and merge requests) that have prioritized labels assigned to them. -To remove labels, expand the left sidebar and unmark them from the labels list. -Simple as that. +If you sort by `Label priority`, GitLab considers this sort comparison order: -## Use labels to filter issues +- Object with a higher priority prioritized label. +- Object without a prioritized label. -Once you start adding labels to your issues, you'll see the benefit of it. -Labels can have several uses, one of them being the quick filtering of issues -or merge requests. +Ties are broken arbitrarily. (Note that we _only_ consider the highest prioritized label in an object, and not any of the lower prioritized labels. [This discussion](https://gitlab.com/gitlab-org/gitlab-ce/issues/18554) considers changing this.) -Pick an existing label from the dropdown _Label_ menu or click on an existing -label from the issue tracker. In the latter case, you also get to see the -label description like shown below. +![Labels sort label priority](img/labels_sort_label_priority.png) -![Filter labels](img/labels_filter.png) +If you sort by `Priority`, GitLab considers this sort comparison order: ---- +- Object's assigned [milestone](milestones/index.md)'s due date is sooner, provided the object has a milestone and the milestone has a due date. If this isn't the case, consider the object having a due date in the infinite future. +- Object with a higher priority prioritized label. +- Object without a prioritized label. -And if you added a description to your label, you can see it by hovering your -mouse over the label in the issue tracker or wherever else the label is -rendered. +Ties are broken arbitrarily. -![Label tooltips](img/labels_description_tooltip.png) +![Labels sort priority](img/labels_sort_priority.png) diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md index 7037d7f5989..0de89f90e21 100644 --- a/doc/user/project/merge_requests/index.md +++ b/doc/user/project/merge_requests/index.md @@ -31,10 +31,10 @@ With GitLab merge requests, you can: With **[GitLab Enterprise Edition][ee]**, you can also: -- View the deployment process across projects with [Multi-Project Pipeline Graphs](https://docs.gitlab.com/ee/ci/multi_project_pipeline_graphs.html#multi-project-pipeline-graphs) (available only in GitLab Enterprise Edition Premium) -- Request [approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) from your managers (available in GitLab Enterprise Edition Starter) -- [Squash and merge](https://docs.gitlab.com/ee/user/project/merge_requests/squash_and_merge.html) for a cleaner commit history (available in GitLab Enterprise Edition Starter) -- Analise the impact of your changes with [Code Quality reports](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html) (available in GitLab Enterprise Edition Starter) +- View the deployment process across projects with [Multi-Project Pipeline Graphs](https://docs.gitlab.com/ee/ci/multi_project_pipeline_graphs.html#multi-project-pipeline-graphs) (available only in GitLab Premium) +- Request [approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) from your managers (available in GitLab Starter) +- [Squash and merge](https://docs.gitlab.com/ee/user/project/merge_requests/squash_and_merge.html) for a cleaner commit history (available in GitLab Starter) +- Analise the impact of your changes with [Code Quality reports](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html) (available in GitLab Starter) ## Use cases @@ -42,10 +42,10 @@ A. Consider you are a software developer working in a team: 1. You checkout a new branch, and submit your changes through a merge request 1. You gather feedback from your team -1. You work on the implementation optimizing code with [Code Quality reports](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html) (available in GitLab Enterprise Edition Starter) +1. You work on the implementation optimizing code with [Code Quality reports](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html) (available in GitLab Starter) 1. You build and test your changes with GitLab CI/CD 1. You request the approval from your manager -1. Your manager pushes a commit with his final review, [approves the merge request](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html), and set it to [merge when pipeline succeeds](#merge-when-pipeline-succeeds) (Merge Request Approvals are available in GitLab Enterprise Edition Starter) +1. Your manager pushes a commit with his final review, [approves the merge request](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html), and set it to [merge when pipeline succeeds](#merge-when-pipeline-succeeds) (Merge Request Approvals are available in GitLab Starter) 1. Your changes get deployed to production with [manual actions](../../../ci/yaml/README.md#manual-actions) for GitLab CI/CD 1. Your implementations were successfully shipped to your customer @@ -55,8 +55,8 @@ B. Consider you're a web developer writing a webpage for your company's: 1. You gather feedback from your reviewers 1. Your changes are previewed with [Review Apps](../../../ci/review_apps/index.md) 1. You request your web designers for their implementation -1. You request the [approval](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) from your manager (available in GitLab Enterprise Edition Starter) -1. Once approved, your merge request is [squashed and merged](https://docs.gitlab.com/ee/user/project/merge_requests/squash_and_merge.html), and [deployed to staging with GitLab Pages](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/) (Squash and Merge is available in GitLab Enterprise Edition Starter) +1. You request the [approval](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) from your manager (available in GitLab Starter) +1. Once approved, your merge request is [squashed and merged](https://docs.gitlab.com/ee/user/project/merge_requests/squash_and_merge.html), and [deployed to staging with GitLab Pages](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/) (Squash and Merge is available in GitLab Starter) 1. Your production team [cherry picks](#cherry-pick-changes) the merge commit into production ## Merge requests per project @@ -70,9 +70,9 @@ and you can use the tabs available to quickly filter by open and closed. You can ## Merge requests per group -View all the merge requests in a group (that is, all the merge requests across all projects in that -group) by navigating to **Group > Merge Requests**. This view also has the open, merged, and closed -merge request tabs, from which you can [search and filter the results](../../search/index.md#issues-and-merge-requests-per-group). +View merge requests in all projects in the group, including all projects of all descendant subgroups of the group. Navigate to **Group > Merge Requests** to view these merge requests. This view also has the open and closed merge requests tabs. + +You can [search and filter the results](../../search/index.md#issues-and-merge-requests-per-group) from here. ![Group Issues list view](img/group_merge_requests_list_view.png) @@ -146,6 +146,19 @@ administrator to do so. ![Create new merge requests by email](img/create_from_email.png) +## Find the merge request that introduced a change + +> **Note**: this feature was [implemented in GitLab 10.5](https://gitlab.com/gitlab-org/gitlab-ce/issues/2383). + +When viewing the commit details page, GitLab will link to the merge request (or +merge requests, if it's in more than one) containing that commit. + +This only applies to commits that are in the most recent version of a merge +request - if a commit was in a merge request, then rebased out of that merge +request, they will not be linked. + +[Read more about merge request versions](versions.md) + ## Revert changes GitLab implements Git's powerful feature to revert any commit with introducing @@ -160,7 +173,7 @@ of merge request diff is created. When you visit a merge request that contains more than one pushes, you can select and compare the versions of those merge request diffs. -[Read more about the merge requests versions.](versions.md) +[Read more about merge request versions](versions.md) ## Work In Progress merge requests @@ -287,4 +300,4 @@ git checkout origin/merge-requests/1 ``` [protected branches]: ../protected_branches.md -[ee]: https://about.gitlab.com/gitlab-ee/ "GitLab Enterprise Edition" +[ee]: https://about.gitlab.com/products/ "GitLab Enterprise Edition" diff --git a/doc/user/project/merge_requests/work_in_progress_merge_requests.md b/doc/user/project/merge_requests/work_in_progress_merge_requests.md index 546c8bdc5e5..f01da06fa6e 100644 --- a/doc/user/project/merge_requests/work_in_progress_merge_requests.md +++ b/doc/user/project/merge_requests/work_in_progress_merge_requests.md @@ -7,7 +7,8 @@ have been marked a **Work In Progress**. ![Blocked Accept Button](img/wip_blocked_accept_button.png) To mark a merge request a Work In Progress, simply start its title with `[WIP]` -or `WIP:`. +or `WIP:`. As an alternative, you're also able to do it by sending a commit +with its title starting with `wip` or `WIP` to the merge request's source branch. ![Mark as WIP](img/wip_mark_as_wip.png) diff --git a/doc/user/project/pages/getting_started_part_three.md b/doc/user/project/pages/getting_started_part_three.md index 0096f8507d2..a153610c712 100644 --- a/doc/user/project/pages/getting_started_part_three.md +++ b/doc/user/project/pages/getting_started_part_three.md @@ -155,15 +155,40 @@ Certificates are NOT required to add to your custom (sub)domain on your GitLab Pages project, though they are highly recommendable. -The importance of having any website securely served under HTTPS -is explained on the introductory section of the blog post -[Secure GitLab Pages with StartSSL](https://about.gitlab.com/2016/06/24/secure-gitlab-pages-with-startssl/#https-a-quick-overview). +Let's start with an introduction to the importance of HTTPS. +Alternatively, jump ahead to [adding certificates to your project](#adding-certificates-to-your-project). -The reason why certificates are so important is that they encrypt +#### Why should I care about HTTPS? + +This might be your first question. If our sites are hosted by GitLab Pages, +they are static, hence we are not dealing with server-side scripts +nor credit card transactions, then why do we need secure connections? + +Back in the 1990s, where HTTPS came out, [SSL](https://en.wikipedia.org/wiki/Transport_Layer_Security#SSL_1.0.2C_2.0_and_3.0) was considered a "special" +security measure, necessary just for big companies, like banks and shoppings sites +with financial transactions. +Now we have a different picture. [According to Josh Aas](https://letsencrypt.org/2015/10/29/phishing-and-malware.html), Executive Director at [ISRG](https://en.wikipedia.org/wiki/Internet_Security_Research_Group): + +> _We’ve since come to realize that HTTPS is important for almost all websites. It’s important for any website that allows people to log in with a password, any website that [tracks its users](https://www.washingtonpost.com/news/the-switch/wp/2013/12/10/nsa-uses-google-cookies-to-pinpoint-targets-for-hacking/) in any way, any website that [doesn’t want its content altered](http://arstechnica.com/tech-policy/2014/09/why-comcasts-javascript-ad-injections-threaten-security-net-neutrality/), and for any site that offers content people might not want others to know they are consuming. We’ve also learned that any site not secured by HTTPS [can be used to attack other sites](http://krebsonsecurity.com/2015/04/dont-be-fodder-for-chinas-great-cannon/)._ + +Therefore, the reason why certificates are so important is that they encrypt the connection between the **client** (you, me, your visitors) and the **server** (where you site lives), through a keychain of authentications and validations. +How about taking Josh's advice and protecting our sites too? We will be +well supported, and we'll contribute to a safer internet. + +#### Organizations supporting HTTPS + +There is a huge movement in favor of securing all the web. W3C fully +[supports the cause](https://w3ctag.github.io/web-https/) and explains very well +the reasons for that. Richard Barnes, a writer for Mozilla Security Blog, +suggested that [Firefox would deprecate HTTP](https://blog.mozilla.org/security/2015/04/30/deprecating-non-secure-http/), +and would no longer accept unsecured connections. Recently, Mozilla published a +[communication](https://blog.mozilla.org/security/2016/03/29/march-2016-ca-communication/) +reiterating the importance of HTTPS. + ### Issuing Certificates GitLab Pages accepts [PEM](https://support.quovadisglobal.com/kb/a37/what-is-pem-format.aspx) certificates issued by diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md index 8404d789de6..df245710940 100644 --- a/doc/user/project/pages/index.md +++ b/doc/user/project/pages/index.md @@ -54,7 +54,6 @@ _Blog posts for securing GitLab Pages custom domains with SSL/TLS certificates:_ - [CloudFlare](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/) - [Let's Encrypt](https://about.gitlab.com/2016/04/11/tutorial-securing-your-gitlab-pages-with-tls-and-letsencrypt/) (outdated) -- [StartSSL](https://about.gitlab.com/2016/06/24/secure-gitlab-pages-with-startssl/) (deprecated) ## Advanced use diff --git a/doc/user/project/pages/introduction.md b/doc/user/project/pages/introduction.md index f52f66f518a..0b5076b8c5d 100644 --- a/doc/user/project/pages/introduction.md +++ b/doc/user/project/pages/introduction.md @@ -316,6 +316,47 @@ or various static site generators. Contributions are very welcome. Visit the GitLab Pages group for a full list of example projects: <https://gitlab.com/groups/pages>. +### Serving compressed assets + +Most modern browsers support downloading files in a compressed format. This +speeds up downloads by reducing the size of files. + +Before serving an uncompressed file, Pages will check whether the same file +exists with a `.gz` extension. If it does, and the browser supports receiving +compressed files, it will serve that version instead of the uncompressed one. + +To take advantage of this feature, the artifact you upload to the Pages should +have this structure: + +``` +public/ +├─┬ index.html +│ â”” index.html.gz +│ +├── css/ +│  └─┬ main.css +│ â”” main.css.gz +│ +└── js/ + └─┬ main.js + â”” main.js.gz +``` + +This can be achieved by including a `script:` command like this in your +`.gitlab-ci.yml` pages job: + +```yaml +pages: + # Other directives + script: + - # build the public/ directory first + - find public -type f -iregex '.*\.\(htm\|html\|txt\|text\|js\|css\)$' -execdir gzip -f --keep {} \; +``` + +By pre-compressing the files and including both versions in the artifact, Pages +can serve requests for both compressed and uncompressed content without +needing to compress files on-demand. + ### Add a custom domain to your Pages website For a complete guide on Pages domains, read through the article diff --git a/doc/user/project/repository/index.md b/doc/user/project/repository/index.md index 9501db88f57..da3c30a8eaf 100644 --- a/doc/user/project/repository/index.md +++ b/doc/user/project/repository/index.md @@ -18,7 +18,7 @@ documentation. > **Important:** For security reasons, when using the command line, we strongly recommend -you to [connect with GitLab via SSH](../../../ssh/README.md). +that you [connect with GitLab via SSH](../../../ssh/README.md). ## Files @@ -66,9 +66,9 @@ your implementation with your team. You can live preview changes submitted to a new branch with [Review Apps](../../../ci/review_apps/index.md). -With [GitLab Enterprise Edition](https://about.gitlab.com/gitlab-ee/) +With [GitLab Enterprise Edition](https://about.gitlab.com/products/) subscriptions, you can also request -[approval](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html#merge-request-approvals) from your managers. +[approval](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) from your managers. To create, delete, and [branches](branches/index.md) via GitLab's UI: @@ -147,12 +147,14 @@ Select branches to compare and view the changes inline: Find it under your project's **Repository > Compare**. -## Locked files (EEP) +## Locked files + +> Available in [GitLab Premium](https://about.gitlab.com/products/). Lock your files to prevent any conflicting changes. [File Locking](https://docs.gitlab.com/ee/user/project/file_lock.html) is available only in -[GitLab Enterprise Edition Premium](https://about.gitlab.com/gitlab-ee/). +[GitLab Premium](https://about.gitlab.com/products/). ## Repository's API diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md index b8f865679a2..dedf102fc37 100644 --- a/doc/user/project/settings/import_export.md +++ b/doc/user/project/settings/import_export.md @@ -22,6 +22,7 @@ > in the import side is required to map the users, based on email or username. > Otherwise, a supplementary comment is left to mention the original author and > the MRs, notes or issues will be owned by the importer. +> - Control project Import/Export with the [API](../../../api/project_import_export.md). Existing projects running on any GitLab instance or GitLab.com can be exported with all their related data and be moved into a new GitLab instance. diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md index f01fa5b1860..888dd0e143a 100644 --- a/doc/user/project/settings/index.md +++ b/doc/user/project/settings/index.md @@ -34,7 +34,7 @@ Set up your project's merge request settings: - Set up the merge request method (merge commit, [fast-forward merge](../merge_requests/fast_forward_merge.html)). - Merge request [description templates](../description_templates.md#description-templates). -- Enable [merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html#merge-request-approvals), _available in [GitLab Enterprise Edition Starter](https://about.gitlab.com/gitlab-ee/)_. +- Enable [merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html#merge-request-approvals), _available in [GitLab Starter](https://about.gitlab.com/products/)_. - Enable [merge only of pipeline succeeds](../merge_requests/merge_when_pipeline_succeeds.md). - Enable [merge only when all discussions are resolved](../../discussions/index.md#only-allow-merge-requests-to-be-merged-if-all-discussions-are-resolved). @@ -42,7 +42,7 @@ Set up your project's merge request settings: ### Service Desk -Enable [Service Desk](https://docs.gitlab.com/ee/user/project/service_desk.html) for your project to offer customer support. Service Desk is available in [GitLab Enterprise Edition Premium](https://about.gitlab.com/gitlab-ee/). +Enable [Service Desk](https://docs.gitlab.com/ee/user/project/service_desk.html) for your project to offer customer support. Service Desk is available in [GitLab Premium](https://about.gitlab.com/products/). ### Export project diff --git a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md index ce7895780c3..8fff3d591fe 100644 --- a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md +++ b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md @@ -83,6 +83,72 @@ that are on the remote repository, eg. from branch `master`: git lfs fetch master ``` +## File Locking + +The first thing to do before using File Locking is to tell Git LFS which +kind of files are lockable. The following command will store PNG files +in LFS and flag them as lockable: + +```bash +git lfs track "*.png" --lockable +``` + +After executing the above command a file named `.gitattributes` will be +created or updated with the following content: + +```bash +*.png filter=lfs diff=lfs merge=lfs -text lockable +``` + +You can also register a file type as lockable without using LFS +(In order to be able to lock/unlock a file you need a remote server that implements the LFS File Locking API), +in order to do that you can edit the `.gitattributes` file manually: + +```bash +*.pdf lockable +``` + +After a file type has been registered as lockable, Git LFS will make +them readonly on the file system automatically. This means you will +need to lock the file before editing it. + +### Managing Locked Files + +Once you're ready to edit your file you need to lock it first: + +```bash +git lfs lock images/banner.png +Locked images/banner.png +``` + +This will register the file as locked in your name on the server: + +```bash +git lfs locks +images/banner.png joe ID:123 +``` + +Once you have pushed your changes, you can unlock the file so others can +also edit it: + +```bash +git lfs unlock images/banner.png +``` + +You can also unlock by id: + +```bash +git lfs unlock --id=123 +``` + +If for some reason you need to unlock a file that was not locked by you, +you can use the `--force` flag as long as you have a `master` access on +the project: + +```bash +git lfs unlock --id=123 --force +``` + ## Troubleshooting ### error: Repository or object not found diff --git a/features/group/members.feature b/features/group/members.feature deleted file mode 100644 index 49a44f57cbb..00000000000 --- a/features/group/members.feature +++ /dev/null @@ -1,12 +0,0 @@ -Feature: Group Members - Background: - Given I sign in as "John Doe" - And "John Doe" is owner of group "Owned" - And "John Doe" is guest of group "Guest" - - Scenario: Search member by name - Given "Mary Jane" is guest of group "Guest" - And I visit group "Guest" members page - When I search for 'Mary' member - Then I should see user "Mary Jane" in team list - Then I should not see user "John Doe" in team list diff --git a/features/group/milestones.feature b/features/group/milestones.feature deleted file mode 100644 index 2211acfee20..00000000000 --- a/features/group/milestones.feature +++ /dev/null @@ -1,48 +0,0 @@ -Feature: Group Milestones - Background: - Given I sign in as "John Doe" - And "John Doe" is owner of group "Owned" - - Scenario: I should see group "Owned" milestone index page with no milestones - When I visit group "Owned" page - And I click on group milestones - Then I should see group milestones index page has no milestones - - Scenario: I should see group "Owned" milestone index page with milestones - Given Group has projects with milestones - When I visit group "Owned" page - And I click on group milestones - Then I should see group milestones index page with milestones - - Scenario: I should see group "Owned" milestone show page - Given Group has projects with milestones - When I visit group "Owned" page - And I click on group milestones - And I click on one group milestone - Then I should see group milestone with descriptions and expiry date - And I should see group milestone with all issues and MRs assigned to that milestone - - Scenario: Create group milestones - Given I visit group "Owned" milestones page - And I click new milestone button - And I fill milestone name - When I press create mileston button - Then group milestone should be created - - Scenario: I should see Issues listed with labels - Given Group has projects with milestones - When I visit group "Owned" page - And I click on group milestones - And I click on one group milestone - Then I should see the "bug" label - And I should see the "feature" label - And I should see the project name in the Issue row - - @javascript - Scenario: I should see the Labels tab - Given Group has projects with milestones - When I visit group "Owned" page - And I click on group milestones - And I click on one group milestone - And I click on the "Labels" tab - Then I should see the list of labels diff --git a/features/profile/profile.feature b/features/profile/profile.feature deleted file mode 100644 index 3263d3e212b..00000000000 --- a/features/profile/profile.feature +++ /dev/null @@ -1,85 +0,0 @@ -@profile -Feature: Profile - Background: - Given I sign in as a user - - Scenario: I look at my profile - Given I visit profile page - Then I should see my profile info - - @javascript - Scenario: I can see groups I belong to - Given I have group with projects - When I visit profile page - And I click on my profile picture - Then I should see my user page - And I should see groups I belong to - - Scenario: I edit profile - Given I visit profile page - Then I change my profile info - And I should see new profile info - - Scenario: I change my password without old one - Given I visit profile password page - When I try change my password w/o old one - Then I should see a missing password error message - And I should be redirected to password page - - Scenario: I change my password - Given I visit profile password page - Then I change my password - And I should be redirected to sign in page - - Scenario: I edit my avatar - Given I visit profile page - Then I change my avatar - And I should see new avatar - And I should see the "Remove avatar" button - And I should see the gravatar host link - - Scenario: I remove my avatar - Given I visit profile page - And I have an avatar - When I remove my avatar - Then I should see my gravatar - And I should not see the "Remove avatar" button - And I should see the gravatar host link - - Scenario: My password is expired - Given my password is expired - And I am not an ldap user - Given I visit profile password page - Then I redirected to expired password page - And I submit new password - And I redirected to sign in page - - Scenario: I unsuccessfully change my password - Given I visit profile password page - When I unsuccessfully change my password - Then I should see a password error message - - Scenario: I visit history tab - Given I logout - And I sign in via the UI - And I have activity - When I visit Authentication log page - Then I should see my activity - - Scenario: I visit my user page - When I visit profile page - And I click on my profile picture - Then I should see my user page - - Scenario: I can manage application - Given I visit profile applications page - Then I should see application form - Then I fill application form out and submit - And I see application - Then I click edit - And I see edit application form - Then I change name of application and submit - And I see that application was changed - Then I visit profile applications page - And I click to remove application - Then I see that application is removed diff --git a/features/steps/group/members.rb b/features/steps/group/members.rb index 0ab1012660c..97bcca7730b 100644 --- a/features/steps/group/members.rb +++ b/features/steps/group/members.rb @@ -9,14 +9,6 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps expect(group_members_list).to have_content("John Doe") end - step 'I should not see user "John Doe" in team list' do - expect(group_members_list).not_to have_content("John Doe") - end - - step 'I should see user "Mary Jane" in team list' do - expect(group_members_list).to have_content("Mary Jane") - end - step 'I should not see user "Mary Jane" in team list' do expect(group_members_list).not_to have_content("Mary Jane") end @@ -41,13 +33,6 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps # poltergeist always confirms popups. end - step 'I search for \'Mary\' member' do - page.within '.member-search-form' do - fill_in 'search', with: 'Mary' - find('.member-search-btn').click - end - end - step 'I change the "Mary Jane" role to "Developer"' do member = mary_jane_member diff --git a/features/steps/group/milestones.rb b/features/steps/group/milestones.rb deleted file mode 100644 index 818bbb50d0e..00000000000 --- a/features/steps/group/milestones.rb +++ /dev/null @@ -1,135 +0,0 @@ -class Spinach::Features::GroupMilestones < Spinach::FeatureSteps - include WaitForRequests - include SharedAuthentication - include SharedPaths - include SharedGroup - include SharedUser - - step 'I click on group milestones' do - visit group_milestones_path('owned') - end - - step 'I should see group milestones index page has no milestones' do - expect(page).to have_content('No milestones to show') - end - - step 'Group has projects with milestones' do - group_milestone - end - - step 'I should see group milestones index page with milestones' do - expect(page).to have_content('Version 7.2') - expect(page).to have_content('GL-113') - expect(page).to have_link('3 Issues', href: issues_group_path("owned", milestone_title: "Version 7.2")) - expect(page).to have_link('0 Merge Requests', href: merge_requests_group_path("owned", milestone_title: "GL-113")) - end - - step 'I click on one group milestone' do - milestones = Milestone.where(title: 'GL-113') - @global_milestone = GlobalMilestone.new('GL-113', milestones) - - click_link 'GL-113' - end - - step 'I should see group milestone with descriptions and expiry date' do - expect(page).to have_content('expires on Aug 20, 2114') - end - - step 'I should see group milestone with all issues and MRs assigned to that milestone' do - expect(page).to have_content('Milestone GL-113') - expect(page).to have_content('Issues 3 Open: 3 Closed: 0') - issue = Milestone.find_by(name: 'GL-113').issues.first - expect(page).to have_link(issue.title, href: project_issue_path(issue.project, issue)) - end - - step 'I fill milestone name' do - fill_in 'milestone_title', with: 'v2.9.0' - end - - step 'I click new milestone button' do - page.within('.nav-controls') do - click_link "New milestone" - end - end - - step 'I press create mileston button' do - click_button "Create milestone" - end - - step 'group milestone should be created' do - group = Group.find_by(name: 'Owned') - expect(page).to have_content group.milestones.find_by_title('v2.9.0').title - end - - step 'I should see the "bug" label' do - page.within('#tab-issues') do - expect(page).to have_content 'bug' - end - end - - step 'I should see the "feature" label' do - page.within('#tab-issues') do - expect(page).to have_content 'bug' - end - end - - step 'I should see the project name in the Issue row' do - page.within('#tab-issues') do - @global_milestone.projects.each do |project| - expect(page).to have_content project.name - end - end - end - - step 'I click on the "Labels" tab' do - page.within('.content .nav-links') do - page.find(:xpath, "//a[@href='#tab-labels']").click - end - end - - step 'I should see the list of labels' do - wait_for_requests - - page.within('#tab-labels') do - expect(page).to have_content 'bug' - expect(page).to have_content 'feature' - end - end - - private - - def group_milestone - group = owned_group - - %w(gitlabhq gitlab-ci cookbook-gitlab).each do |path| - project = create(:project, path: path, group: group) - milestone = create :milestone, title: "Version 7.2", project: project - - create(:label, project: project, title: 'bug') - create(:label, project: project, title: 'feature') - - create :issue, - project: project, - assignees: [current_user], - author: current_user, - milestone: milestone - - milestone = create :milestone, - title: "GL-113", - project: project, - due_date: '2114-08-20', - description: 'Lorem Ipsum is simply dummy text' - - issue = create :issue, - project: project, - assignees: [current_user], - author: current_user, - milestone: milestone - - issue.labels << project.labels.find_by(title: 'bug') - issue.labels << project.labels.find_by(title: 'feature') - end - - current_user.refresh_authorized_projects - end -end diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb deleted file mode 100644 index d3b88ae8d2a..00000000000 --- a/features/steps/profile/profile.rb +++ /dev/null @@ -1,226 +0,0 @@ -class Spinach::Features::Profile < Spinach::FeatureSteps - include SharedAuthentication - include SharedPaths - - step 'I should see my profile info' do - expect(page).to have_content "This information will appear on your profile" - end - - step 'I change my profile info' do - fill_in 'user_skype', with: 'testskype' - fill_in 'user_linkedin', with: 'testlinkedin' - fill_in 'user_twitter', with: 'testtwitter' - fill_in 'user_website_url', with: 'testurl' - fill_in 'user_location', with: 'Ukraine' - fill_in 'user_bio', with: 'I <3 GitLab' - fill_in 'user_organization', with: 'GitLab' - click_button 'Update profile settings' - @user.reload - end - - step 'I should see new profile info' do - expect(@user.skype).to eq 'testskype' - expect(@user.linkedin).to eq 'testlinkedin' - expect(@user.twitter).to eq 'testtwitter' - expect(@user.website_url).to eq 'testurl' - expect(@user.bio).to eq 'I <3 GitLab' - expect(@user.organization).to eq 'GitLab' - expect(find('#user_location').value).to eq 'Ukraine' - end - - step 'I change my avatar' do - attach_file(:user_avatar, File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif')) - click_button "Update profile settings" - @user.reload - end - - step 'I should see new avatar' do - expect(@user.avatar).to be_instance_of AvatarUploader - expect(@user.avatar.url).to eq "/uploads/-/system/user/avatar/#{@user.id}/banana_sample.gif" - end - - step 'I should see the "Remove avatar" button' do - expect(page).to have_link("Remove avatar") - end - - step 'I have an avatar' do - attach_file(:user_avatar, File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif')) - click_button "Update profile settings" - @user.reload - end - - step 'I remove my avatar' do - click_link "Remove avatar" - @user.reload - end - - step 'I should see my gravatar' do - expect(@user.avatar?).to eq false - end - - step 'I should not see the "Remove avatar" button' do - expect(page).not_to have_link("Remove avatar") - end - - step 'I should see the gravatar host link' do - expect(page).to have_link("gravatar.com") - end - - step 'I try change my password w/o old one' do - page.within '.update-password' do - fill_in "user_password", with: "22233344" - fill_in "user_password_confirmation", with: "22233344" - click_button "Save password" - end - end - - step 'I change my password' do - page.within '.update-password' do - fill_in "user_current_password", with: "12345678" - fill_in "user_password", with: "22233344" - fill_in "user_password_confirmation", with: "22233344" - click_button "Save password" - end - end - - step 'I unsuccessfully change my password' do - page.within '.update-password' do - fill_in "user_current_password", with: "12345678" - fill_in "user_password", with: "password" - fill_in "user_password_confirmation", with: "confirmation" - click_button "Save password" - end - end - - step "I should see a missing password error message" do - page.within ".flash-container" do - expect(page).to have_content "You must provide a valid current password" - end - end - - step "I should see a password error message" do - page.within '.alert-danger' do - expect(page).to have_content "Password confirmation doesn't match" - end - end - - step 'I have activity' do - create(:closed_issue_event, author: current_user) - end - - step 'I should see my activity' do - expect(page).to have_content "Signed in with standard authentication" - end - - step 'my password is expired' do - current_user.update_attributes(password_expires_at: Time.now - 1.hour) - end - - step "I am not an ldap user" do - current_user.identities.delete - expect(current_user.ldap_user?).to eq false - end - - step 'I redirected to expired password page' do - expect(current_path).to eq new_profile_password_path - end - - step 'I submit new password' do - fill_in :user_current_password, with: '12345678' - fill_in :user_password, with: '12345678' - fill_in :user_password_confirmation, with: '12345678' - click_button "Set new password" - end - - step 'I redirected to sign in page' do - expect(current_path).to eq new_user_session_path - end - - step 'I should be redirected to password page' do - expect(current_path).to eq edit_profile_password_path - end - - step 'I should be redirected to account page' do - expect(current_path).to eq profile_account_path - end - - step 'I click on my profile picture' do - find(:css, '.header-user-dropdown-toggle').click - - page.within ".header-user" do - click_link "Profile" - end - end - - step 'I should see my user page' do - page.within ".cover-block" do - expect(page).to have_content current_user.name - expect(page).to have_content current_user.username - end - end - - step 'I have group with projects' do - @group = create(:group) - @group.add_owner(current_user) - @project = create(:project, :repository, namespace: @group) - @event = create(:closed_issue_event, project: @project) - - @project.add_master(current_user) - end - - step 'I should see groups I belong to' do - page.within ".content" do - click_link "Groups" - end - - page.within "#groups" do - expect(page).to have_content @group.name - end - end - - step 'I should see application form' do - expect(page).to have_content "Add new application" - end - - step 'I fill application form out and submit' do - fill_in :doorkeeper_application_name, with: 'test' - fill_in :doorkeeper_application_redirect_uri, with: 'https://test.com' - click_on "Save application" - end - - step 'I see application' do - expect(page).to have_content "Application: test" - expect(page).to have_content "Application Id" - expect(page).to have_content "Secret" - end - - step 'I click edit' do - click_on "Edit" - end - - step 'I see edit application form' do - expect(page).to have_content "Edit application" - end - - step 'I change name of application and submit' do - expect(page).to have_content "Edit application" - fill_in :doorkeeper_application_name, with: 'test_changed' - click_on "Save application" - end - - step 'I see that application was changed' do - expect(page).to have_content "test_changed" - expect(page).to have_content "Application Id" - expect(page).to have_content "Secret" - end - - step 'I click to remove application' do - page.within '.oauth-applications' do - click_on "Destroy" - end - end - - step "I see that application is removed" do - expect(page.find(".oauth-applications")).not_to have_content "test_changed" - end -end diff --git a/features/support/env.rb b/features/support/env.rb index 7f5b4c1c11b..15211995918 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -20,15 +20,16 @@ Dir["#{Rails.root}/features/steps/shared/*.rb"].each { |file| require file } Spinach.hooks.before_run do include RSpec::Mocks::ExampleMethods + include ActiveJob::TestHelper + include FactoryBot::Syntax::Methods + include GitlabRoutingHelper + RSpec::Mocks.setup TestEnv.init(mailer: false) # skip pre-receive hook check so we can use # web editor and merge TestEnv.disable_pre_receive - - include FactoryBot::Syntax::Methods - include GitlabRoutingHelper end Spinach.hooks.after_scenario do |scenario_data, step_definitions| diff --git a/lib/api/api.rb b/lib/api/api.rb index f3f64244589..754549f72f0 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -138,6 +138,7 @@ module API mount ::API::PagesDomains mount ::API::Pipelines mount ::API::PipelineSchedules + mount ::API::ProjectImport mount ::API::ProjectHooks mount ::API::Projects mount ::API::ProjectMilestones @@ -146,6 +147,7 @@ module API mount ::API::Repositories mount ::API::Runner mount ::API::Runners + mount ::API::Search mount ::API::Services mount ::API::Settings mount ::API::SidekiqMetrics diff --git a/lib/api/commits.rb b/lib/api/commits.rb index d8fd6a6eb06..d83c43ee49b 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -156,6 +156,27 @@ module API end end + desc 'Get all references a commit is pushed to' do + detail 'This feature was introduced in GitLab 10.6' + success Entities::BasicRef + end + params do + requires :sha, type: String, desc: 'A commit sha' + optional :type, type: String, values: %w[branch tag all], default: 'all', desc: 'Scope' + use :pagination + end + get ':id/repository/commits/:sha/refs', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do + commit = user_project.commit(params[:sha]) + not_found!('Commit') unless commit + + refs = [] + refs.concat(user_project.repository.branch_names_contains(commit.id).map {|name| { type: 'branch', name: name }}) unless params[:type] == 'tag' + refs.concat(user_project.repository.tag_names_contains(commit.id).map {|name| { type: 'tag', name: name }}) unless params[:type] == 'branch' + refs = Kaminari.paginate_array(refs) + + present paginate(refs), with: Entities::BasicRef + end + desc 'Post comment to commit' do success Entities::CommitNote end @@ -165,7 +186,7 @@ module API optional :path, type: String, desc: 'The file path' given :path do requires :line, type: Integer, desc: 'The line number' - requires :line_type, type: String, values: %w(new old), default: 'new', desc: 'The type of the line' + requires :line_type, type: String, values: %w[new old], default: 'new', desc: 'The type of the line' end end post ':id/repository/commits/:sha/comments', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do diff --git a/lib/api/entities.rb b/lib/api/entities.rb index e13463ec66b..45c737c6c29 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -22,6 +22,7 @@ module API end expose :avatar_path, if: ->(user, options) { options.fetch(:only_path, false) && user.avatar_path } + expose :custom_attributes, using: 'API::Entities::CustomAttribute', if: :with_custom_attributes expose :web_url do |user, options| Gitlab::Routing.url_helpers.user_url(user) @@ -90,6 +91,13 @@ module API expose :created_at end + class ProjectImportStatus < ProjectIdentity + expose :import_status + + # TODO: Use `expose_nil` once we upgrade the grape-entity gem + expose :import_error, if: lambda { |status, _ops| status.import_error } + end + class BasicProjectDetails < ProjectIdentity include ::API::ProjectsRelationBuilder @@ -109,6 +117,8 @@ module API expose :star_count, :forks_count expose :last_activity_at + expose :custom_attributes, using: 'API::Entities::CustomAttribute', if: :with_custom_attributes + def self.preload_relation(projects_relation, options = {}) projects_relation.preload(:project_feature, :route) .preload(namespace: [:route, :owner], @@ -230,6 +240,8 @@ module API expose :parent_id end + expose :custom_attributes, using: 'API::Entities::CustomAttribute', if: :with_custom_attributes + expose :statistics, if: :statistics do with_options format_with: -> (value) { value.to_i } do expose :storage_size @@ -274,6 +286,11 @@ module API expose :stats, using: Entities::CommitStats, if: :stats expose :status expose :last_pipeline, using: 'API::Entities::PipelineBasic' + expose :project_id + end + + class BasicRef < Grape::Entity + expose :type, :name end class Branch < Grape::Entity @@ -314,24 +331,20 @@ module API end end - class ProjectSnippet < Grape::Entity + class Snippet < Grape::Entity expose :id, :title, :file_name, :description expose :author, using: Entities::UserBasic expose :updated_at, :created_at - - expose :web_url do |snippet, options| + expose :project_id + expose :web_url do |snippet| Gitlab::UrlBuilder.build(snippet) end end - class PersonalSnippet < Grape::Entity - expose :id, :title, :file_name, :description - expose :author, using: Entities::UserBasic - expose :updated_at, :created_at + class ProjectSnippet < Snippet + end - expose :web_url do |snippet| - Gitlab::UrlBuilder.build(snippet) - end + class PersonalSnippet < Snippet expose :raw_url do |snippet| Gitlab::UrlBuilder.build(snippet) + "/raw" end @@ -1168,5 +1181,15 @@ module API class ApplicationWithSecret < Application expose :secret end + + class Blob < Grape::Entity + expose :basename + expose :data + expose :filename + expose :id + expose :ref + expose :startline + expose :project_id + end end end diff --git a/lib/api/groups.rb b/lib/api/groups.rb index b81f07a1770..4a4df1b8b9e 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -1,6 +1,7 @@ module API class Groups < Grape::API include PaginationParams + include Helpers::CustomAttributes before { authenticate_non_get! } @@ -67,6 +68,8 @@ module API } groups = groups.with_statistics if options[:statistics] + groups, options = with_custom_attributes(groups, options) + present paginate(groups), options end end @@ -79,6 +82,7 @@ module API end params do use :group_list_params + use :with_custom_attributes end get do groups = find_groups(params) @@ -142,9 +146,20 @@ module API desc 'Get a single group, with containing projects.' do success Entities::GroupDetail end + params do + use :with_custom_attributes + end get ":id" do group = find_group!(params[:id]) - present group, with: Entities::GroupDetail, current_user: current_user + + options = { + with: Entities::GroupDetail, + current_user: current_user + } + + group, options = with_custom_attributes(group, options) + + present group, options end desc 'Remove a group.' @@ -175,12 +190,19 @@ module API optional :starred, type: Boolean, default: false, desc: 'Limit by starred status' use :pagination + use :with_custom_attributes end get ":id/projects" do projects = find_group_projects(params) - entity = params[:simple] ? Entities::BasicProjectDetails : Entities::Project - present entity.prepare_relation(projects), with: entity, current_user: current_user + options = { + with: params[:simple] ? Entities::BasicProjectDetails : Entities::Project, + current_user: current_user + } + + projects, options = with_custom_attributes(projects, options) + + present options[:with].prepare_relation(projects), options end desc 'Get a list of subgroups in this group.' do @@ -188,6 +210,7 @@ module API end params do use :group_list_params + use :with_custom_attributes end get ":id/subgroups" do groups = find_groups(params) diff --git a/lib/api/helpers/custom_attributes.rb b/lib/api/helpers/custom_attributes.rb new file mode 100644 index 00000000000..70e4eda95f8 --- /dev/null +++ b/lib/api/helpers/custom_attributes.rb @@ -0,0 +1,28 @@ +module API + module Helpers + module CustomAttributes + extend ActiveSupport::Concern + + included do + helpers do + params :with_custom_attributes do + optional :with_custom_attributes, type: Boolean, default: false, desc: 'Include custom attributes in the response' + end + + def with_custom_attributes(collection_or_resource, options = {}) + options = options.merge( + with_custom_attributes: params[:with_custom_attributes] && + can?(current_user, :read_custom_attribute) + ) + + if options[:with_custom_attributes] && collection_or_resource.is_a?(ActiveRecord::Relation) + collection_or_resource = collection_or_resource.includes(:custom_attributes) + end + + [collection_or_resource, options] + end + end + end + end + end +end diff --git a/lib/api/helpers/pagination.rb b/lib/api/helpers/pagination.rb index bb70370ba77..09805049169 100644 --- a/lib/api/helpers/pagination.rb +++ b/lib/api/helpers/pagination.rb @@ -12,13 +12,16 @@ module API private def add_pagination_headers(paginated_data) - header 'X-Total', paginated_data.total_count.to_s - header 'X-Total-Pages', total_pages(paginated_data).to_s header 'X-Per-Page', paginated_data.limit_value.to_s header 'X-Page', paginated_data.current_page.to_s header 'X-Next-Page', paginated_data.next_page.to_s header 'X-Prev-Page', paginated_data.prev_page.to_s header 'Link', pagination_links(paginated_data) + + return if data_without_counts?(paginated_data) + + header 'X-Total', paginated_data.total_count.to_s + header 'X-Total-Pages', total_pages(paginated_data).to_s end def pagination_links(paginated_data) @@ -37,8 +40,10 @@ module API request_params[:page] = 1 links << %(<#{request_url}?#{request_params.to_query}>; rel="first") - request_params[:page] = total_pages(paginated_data) - links << %(<#{request_url}?#{request_params.to_query}>; rel="last") + unless data_without_counts?(paginated_data) + request_params[:page] = total_pages(paginated_data) + links << %(<#{request_url}?#{request_params.to_query}>; rel="last") + end links.join(', ') end @@ -55,6 +60,10 @@ module API relation end + + def data_without_counts?(paginated_data) + paginated_data.is_a?(Kaminari::PaginatableWithoutCount) + end end end end diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb index 3d0d1287407..fbe30192a16 100644 --- a/lib/api/helpers/runner.rb +++ b/lib/api/helpers/runner.rb @@ -3,7 +3,6 @@ module API module Runner JOB_TOKEN_HEADER = 'HTTP_JOB_TOKEN'.freeze JOB_TOKEN_PARAM = :token - UPDATE_RUNNER_EVERY = 10 * 60 def runner_registration_token_valid? ActiveSupport::SecurityUtils.variable_size_secure_compare(params[:token], @@ -18,30 +17,14 @@ module API def authenticate_runner! forbidden! unless current_runner + + current_runner.update_cached_info(get_runner_version_from_params) end def current_runner @runner ||= ::Ci::Runner.find_by_token(params[:token].to_s) end - def update_runner_info - return unless update_runner? - - current_runner.contacted_at = Time.now - current_runner.assign_attributes(get_runner_version_from_params) - current_runner.save if current_runner.changed? - end - - def update_runner? - # Use a random threshold to prevent beating DB updates. - # It generates a distribution between [40m, 80m]. - # - contacted_at_max_age = UPDATE_RUNNER_EVERY + Random.rand(UPDATE_RUNNER_EVERY) - - current_runner.contacted_at.nil? || - (Time.now - current_runner.contacted_at) >= contacted_at_max_age - end - def validate_job!(job) not_found! unless job diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 9285fb90cdc..b3660e4a1d0 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -13,7 +13,7 @@ module API # key_id - ssh key id for Git over SSH # user_id - user id for Git over HTTP # protocol - Git access protocol being used, e.g. HTTP or SSH - # project - project path with namespace + # project - project full_path (not path on disk) # action - git action (git-upload-pack or git-receive-pack) # changes - changes as "oldrev newrev ref", see Gitlab::ChangesList post "/allowed" do diff --git a/lib/api/project_import.rb b/lib/api/project_import.rb new file mode 100644 index 00000000000..a509c1f32c1 --- /dev/null +++ b/lib/api/project_import.rb @@ -0,0 +1,69 @@ +module API + class ProjectImport < Grape::API + include PaginationParams + + helpers do + def import_params + declared_params(include_missing: false) + end + + def file_is_valid? + import_params[:file] && import_params[:file]['tempfile'].respond_to?(:read) + end + + def validate_file! + render_api_error!('The file is invalid', 400) unless file_is_valid? + end + end + + before do + forbidden! unless Gitlab::CurrentSettings.import_sources.include?('gitlab_project') + end + + resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do + params do + requires :path, type: String, desc: 'The new project path and name' + requires :file, type: File, desc: 'The project export file to be imported' + optional :namespace, type: String, desc: "The ID or name of the namespace that the project will be imported into. Defaults to the current user's namespace." + end + desc 'Create a new project import' do + detail 'This feature was introduced in GitLab 10.6.' + success Entities::ProjectImportStatus + end + post 'import' do + validate_file! + + Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42437') + + namespace = if import_params[:namespace] + find_namespace!(import_params[:namespace]) + else + current_user.namespace + end + + project_params = { + path: import_params[:path], + namespace_id: namespace.id, + file: import_params[:file]['tempfile'] + } + + project = ::Projects::GitlabProjectsImportService.new(current_user, project_params).execute + + render_api_error!(project.errors.full_messages&.first, 400) unless project.saved? + + present project, with: Entities::ProjectImportStatus + end + + params do + requires :id, type: String, desc: 'The ID of a project' + end + desc 'Get a project export status' do + detail 'This feature was introduced in GitLab 10.6.' + success Entities::ProjectImportStatus + end + get ':id/import' do + present user_project, with: Entities::ProjectImportStatus + end + end + end +end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 5b481121a10..e90892a90f7 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -3,6 +3,7 @@ require_dependency 'declarative_policy' module API class Projects < Grape::API include PaginationParams + include Helpers::CustomAttributes before { authenticate_non_get! } @@ -80,6 +81,7 @@ module API projects = projects.with_merge_requests_enabled if params[:with_merge_requests_enabled] projects = projects.with_statistics if params[:statistics] projects = paginate(projects) + projects, options = with_custom_attributes(projects, options) if current_user project_members = current_user.project_members.preload(:source, user: [notification_settings: :source]) @@ -107,6 +109,7 @@ module API requires :user_id, type: String, desc: 'The ID or username of the user' use :collection_params use :statistics_params + use :with_custom_attributes end get ":user_id/projects" do user = find_user(params[:user_id]) @@ -127,6 +130,7 @@ module API params do use :collection_params use :statistics_params + use :with_custom_attributes end get do present_projects load_projects @@ -196,11 +200,19 @@ module API end params do use :statistics_params + use :with_custom_attributes end get ":id" do - entity = current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails - present user_project, with: entity, current_user: current_user, - user_can_admin_project: can?(current_user, :admin_project, user_project), statistics: params[:statistics] + options = { + with: current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails, + current_user: current_user, + user_can_admin_project: can?(current_user, :admin_project, user_project), + statistics: params[:statistics] + } + + project, options = with_custom_attributes(user_project, options) + + present project, options end desc 'Fork new project for the current user or provided namespace.' do @@ -242,6 +254,7 @@ module API end params do use :collection_params + use :with_custom_attributes end get ':id/forks' do forks = ForkProjectsFinder.new(user_project, params: project_finder_params, current_user: current_user).execute diff --git a/lib/api/runner.rb b/lib/api/runner.rb index 1f80646a2ea..5469cba69a6 100644 --- a/lib/api/runner.rb +++ b/lib/api/runner.rb @@ -78,7 +78,6 @@ module API post '/request' do authenticate_runner! no_content! unless current_runner.active? - update_runner_info if current_runner.runner_queue_value_latest?(params[:last_update]) header 'X-GitLab-Last-Update', params[:last_update] diff --git a/lib/api/search.rb b/lib/api/search.rb new file mode 100644 index 00000000000..3556ad98c52 --- /dev/null +++ b/lib/api/search.rb @@ -0,0 +1,111 @@ +module API + class Search < Grape::API + include PaginationParams + + before { authenticate! } + + helpers do + SCOPE_ENTITY = { + merge_requests: Entities::MergeRequestBasic, + issues: Entities::IssueBasic, + projects: Entities::BasicProjectDetails, + milestones: Entities::Milestone, + notes: Entities::Note, + commits: Entities::CommitDetail, + blobs: Entities::Blob, + wiki_blobs: Entities::Blob, + snippet_titles: Entities::Snippet, + snippet_blobs: Entities::Snippet + }.freeze + + def search(additional_params = {}) + search_params = { + scope: params[:scope], + search: params[:search], + snippets: snippets?, + page: params[:page], + per_page: params[:per_page] + }.merge(additional_params) + + results = SearchService.new(current_user, search_params).search_objects + + process_results(results) + end + + def process_results(results) + case params[:scope] + when 'wiki_blobs' + paginate(results).map { |blob| Gitlab::ProjectSearchResults.parse_search_result(blob, user_project) } + when 'blobs' + paginate(results).map { |blob| blob[1] } + else + paginate(results) + end + end + + def snippets? + %w(snippet_blobs snippet_titles).include?(params[:scope]).to_s + end + + def entity + SCOPE_ENTITY[params[:scope].to_sym] + end + end + + resource :search do + desc 'Search on GitLab' do + detail 'This feature was introduced in GitLab 10.5.' + end + params do + requires :search, type: String, desc: 'The expression it should be searched for' + requires :scope, + type: String, + desc: 'The scope of search, available scopes: + projects, issues, merge_requests, milestones, snippet_titles, snippet_blobs', + values: %w(projects issues merge_requests milestones snippet_titles snippet_blobs) + use :pagination + end + get do + present search, with: entity + end + end + + resource :groups, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do + desc 'Search on GitLab' do + detail 'This feature was introduced in GitLab 10.5.' + end + params do + requires :id, type: String, desc: 'The ID of a group' + requires :search, type: String, desc: 'The expression it should be searched for' + requires :scope, + type: String, + desc: 'The scope of search, available scopes: + projects, issues, merge_requests, milestones', + values: %w(projects issues merge_requests milestones) + use :pagination + end + get ':id/-/search' do + present search(group_id: user_group.id), with: entity + end + end + + resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do + desc 'Search on GitLab' do + detail 'This feature was introduced in GitLab 10.5.' + end + params do + requires :id, type: String, desc: 'The ID of a project' + requires :search, type: String, desc: 'The expression it should be searched for' + requires :scope, + type: String, + desc: 'The scope of search, available scopes: + issues, merge_requests, milestones, notes, wiki_blobs, commits, blobs', + values: %w(issues merge_requests milestones notes wiki_blobs commits blobs) + use :pagination + end + get ':id/-/search' do + present search(project_id: user_project.id), with: entity + end + end + end +end diff --git a/lib/api/todos.rb b/lib/api/todos.rb index ffccfebe752..c6dbcf84e3a 100644 --- a/lib/api/todos.rb +++ b/lib/api/todos.rb @@ -60,7 +60,7 @@ module API end post ':id/mark_as_done' do TodoService.new.mark_todos_as_done_by_ids(params[:id], current_user) - todo = Todo.find(params[:id]) + todo = current_user.todos.find(params[:id]) present todo, with: Entities::Todo, current_user: current_user end diff --git a/lib/api/users.rb b/lib/api/users.rb index 3cc12724b8a..3920171205f 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -2,6 +2,7 @@ module API class Users < Grape::API include PaginationParams include APIGuard + include Helpers::CustomAttributes allow_access_with_scope :read_user, if: -> (request) { request.get? } @@ -70,6 +71,7 @@ module API use :sort_params use :pagination + use :with_custom_attributes end get do authenticated_as_admin! if params[:external].present? || (params[:extern_uid].present? && params[:provider].present?) @@ -94,8 +96,9 @@ module API entity = current_user&.admin? ? Entities::UserWithAdmin : Entities::UserBasic users = users.preload(:identities, :u2f_registrations) if entity == Entities::UserWithAdmin + users, options = with_custom_attributes(users, with: entity) - present paginate(users), with: entity + present paginate(users), options end desc 'Get a single user' do @@ -103,12 +106,16 @@ module API end params do requires :id, type: Integer, desc: 'The ID of the user' + + use :with_custom_attributes end get ":id" do user = User.find_by(id: params[:id]) not_found!('User') unless user && can?(current_user, :read_user, user) opts = current_user&.admin? ? { with: Entities::UserWithAdmin } : { with: Entities::User } + user, opts = with_custom_attributes(user, opts) + present user, opts end diff --git a/lib/api/v3/projects.rb b/lib/api/v3/projects.rb index c856ba99f09..7d8b1f369fe 100644 --- a/lib/api/v3/projects.rb +++ b/lib/api/v3/projects.rb @@ -174,7 +174,7 @@ module API use :pagination end get "/search/:query", requirements: { query: %r{[^/]+} } do - search_service = Search::GlobalService.new(current_user, search: params[:query]).execute + search_service = ::Search::GlobalService.new(current_user, search: params[:query]).execute projects = search_service.objects('projects', params[:page], false) projects = projects.reorder(params[:order_by] => params[:sort]) diff --git a/lib/api/v3/todos.rb b/lib/api/v3/todos.rb index 2f2cf259987..3e2c61f6dbd 100644 --- a/lib/api/v3/todos.rb +++ b/lib/api/v3/todos.rb @@ -12,7 +12,7 @@ module API end delete ':id' do TodoService.new.mark_todos_as_done_by_ids(params[:id], current_user) - todo = Todo.find(params[:id]) + todo = current_user.todos.find(params[:id]) present todo, with: ::API::Entities::Todo, current_user: current_user end diff --git a/lib/banzai/filter/html_entity_filter.rb b/lib/banzai/filter/html_entity_filter.rb index f3bd587c28b..e008fd428b0 100644 --- a/lib/banzai/filter/html_entity_filter.rb +++ b/lib/banzai/filter/html_entity_filter.rb @@ -5,7 +5,7 @@ module Banzai # Text filter that escapes these HTML entities: & " < > class HtmlEntityFilter < HTML::Pipeline::TextFilter def call - ERB::Util.html_escape_once(text) + ERB::Util.html_escape(text) end end end diff --git a/lib/banzai/filter/syntax_highlight_filter.rb b/lib/banzai/filter/syntax_highlight_filter.rb index a79a0154846..0ac7e231b5b 100644 --- a/lib/banzai/filter/syntax_highlight_filter.rb +++ b/lib/banzai/filter/syntax_highlight_filter.rb @@ -14,23 +14,33 @@ module Banzai end def highlight_node(node) - code = node.text css_classes = 'code highlight js-syntax-highlight' - language = node.attr('lang') + lang = node.attr('lang') + retried = false - if use_rouge?(language) - lexer = lexer_for(language) + if use_rouge?(lang) + lexer = lexer_for(lang) language = lexer.tag + else + lexer = Rouge::Lexers::PlainText.new + language = lang + end + + begin + code = Rouge::Formatters::HTMLGitlab.format(lex(lexer, node.text), tag: language) + css_classes << " #{language}" if language + rescue + # Gracefully handle syntax highlighter bugs/errors to ensure users can + # still access an issue/comment/etc. First, retry with the plain text + # filter. If that fails, then just skip this entirely, but that would + # be a pretty bad upstream bug. + return if retried - begin - code = Rouge::Formatters::HTMLGitlab.format(lex(lexer, code), tag: language) - css_classes << " #{language}" - rescue - # Gracefully handle syntax highlighter bugs/errors to ensure - # users can still access an issue/comment/etc. + language = nil + lexer = Rouge::Lexers::PlainText.new + retried = true - language = nil - end + retry end highlighted = %(<pre class="#{css_classes}" lang="#{language}" v-pre="true"><code>#{code}</code></pre>) diff --git a/lib/gitlab/asciidoc.rb b/lib/gitlab/asciidoc.rb index ee7f4be6b9f..62c41801d75 100644 --- a/lib/gitlab/asciidoc.rb +++ b/lib/gitlab/asciidoc.rb @@ -8,7 +8,8 @@ module Gitlab module Asciidoc DEFAULT_ADOC_ATTRS = [ 'showtitle', 'idprefix=user-content-', 'idseparator=-', 'env=gitlab', - 'env-gitlab', 'source-highlighter=html-pipeline', 'icons=font' + 'env-gitlab', 'source-highlighter=html-pipeline', 'icons=font', + 'outfilesuffix=.adoc' ].freeze # Public: Converts the provided Asciidoc markup into HTML. diff --git a/lib/gitlab/background_migration/create_fork_network_memberships_range.rb b/lib/gitlab/background_migration/create_fork_network_memberships_range.rb index 03b17b319fa..1b4a9e8a194 100644 --- a/lib/gitlab/background_migration/create_fork_network_memberships_range.rb +++ b/lib/gitlab/background_migration/create_fork_network_memberships_range.rb @@ -14,6 +14,14 @@ module Gitlab def perform(start_id, end_id) log("Creating memberships for forks: #{start_id} - #{end_id}") + insert_members(start_id, end_id) + + if missing_members?(start_id, end_id) + BackgroundMigrationWorker.perform_in(RESCHEDULE_DELAY, "CreateForkNetworkMembershipsRange", [start_id, end_id]) + end + end + + def insert_members(start_id, end_id) ActiveRecord::Base.connection.execute <<~INSERT_MEMBERS INSERT INTO fork_network_members (fork_network_id, project_id, forked_from_project_id) @@ -33,10 +41,9 @@ module Gitlab WHERE existing_members.project_id = forked_project_links.forked_to_project_id ) INSERT_MEMBERS - - if missing_members?(start_id, end_id) - BackgroundMigrationWorker.perform_in(RESCHEDULE_DELAY, "CreateForkNetworkMembershipsRange", [start_id, end_id]) - end + rescue ActiveRecord::RecordNotUnique => e + # `fork_network_member` was created concurrently in another migration + log(e.message) end def missing_members?(start_id, end_id) diff --git a/lib/gitlab/background_migration/populate_untracked_uploads.rb b/lib/gitlab/background_migration/populate_untracked_uploads.rb index 8a8e770940e..ee55fabd6f0 100644 --- a/lib/gitlab/background_migration/populate_untracked_uploads.rb +++ b/lib/gitlab/background_migration/populate_untracked_uploads.rb @@ -249,7 +249,7 @@ module Gitlab end def drop_temp_table_if_finished - if UntrackedFile.all.empty? + if UntrackedFile.all.empty? && !Rails.env.test? # Dropping a table intermittently breaks test cleanup UntrackedFile.connection.drop_table(:untracked_files_for_uploads, if_exists: true) end diff --git a/lib/gitlab/background_migration/prepare_untracked_uploads.rb b/lib/gitlab/background_migration/prepare_untracked_uploads.rb index a7a1bbe1752..914a9e48a2f 100644 --- a/lib/gitlab/background_migration/prepare_untracked_uploads.rb +++ b/lib/gitlab/background_migration/prepare_untracked_uploads.rb @@ -43,7 +43,11 @@ module Gitlab store_untracked_file_paths - schedule_populate_untracked_uploads_jobs + if UntrackedFile.all.empty? + drop_temp_table + else + schedule_populate_untracked_uploads_jobs + end end private @@ -92,7 +96,7 @@ module Gitlab end end - yield(paths) + yield(paths) if paths.any? end def build_find_command(search_dir) @@ -165,6 +169,13 @@ module Gitlab bulk_queue_background_migration_jobs_by_range( UntrackedFile, FOLLOW_UP_MIGRATION) end + + def drop_temp_table + unless Rails.env.test? # Dropping a table intermittently breaks test cleanup + UntrackedFile.connection.drop_table(:untracked_files_for_uploads, + if_exists: true) + end + end end end end diff --git a/lib/gitlab/checks/change_access.rb b/lib/gitlab/checks/change_access.rb index 945d70e7a24..521680b8708 100644 --- a/lib/gitlab/checks/change_access.rb +++ b/lib/gitlab/checks/change_access.rb @@ -16,11 +16,11 @@ module Gitlab lfs_objects_missing: 'LFS objects are missing. Ensure LFS is properly set up or try a manual "git lfs push --all".' }.freeze - attr_reader :user_access, :project, :skip_authorization, :protocol, :oldrev, :newrev, :ref, :branch_name, :tag_name + attr_reader :user_access, :project, :skip_authorization, :skip_lfs_integrity_check, :protocol, :oldrev, :newrev, :ref, :branch_name, :tag_name def initialize( change, user_access:, project:, skip_authorization: false, - protocol: + skip_lfs_integrity_check: false, protocol: ) @oldrev, @newrev, @ref = change.values_at(:oldrev, :newrev, :ref) @branch_name = Gitlab::Git.branch_name(@ref) @@ -28,16 +28,18 @@ module Gitlab @user_access = user_access @project = project @skip_authorization = skip_authorization + @skip_lfs_integrity_check = skip_lfs_integrity_check @protocol = protocol end - def exec + def exec(skip_commits_check: false) return true if skip_authorization push_checks branch_checks tag_checks - lfs_objects_exist_check + lfs_objects_exist_check unless skip_lfs_integrity_check + commits_check unless skip_commits_check true end @@ -117,6 +119,24 @@ module Gitlab end end + def commits_check + return if deletion? || newrev.nil? + + # n+1: https://gitlab.com/gitlab-org/gitlab-ee/issues/3593 + ::Gitlab::GitalyClient.allow_n_plus_1_calls do + commits.each do |commit| + commit_check.validate(commit, validations_for_commit(commit)) + end + end + + commit_check.validate_file_paths + end + + # Method overwritten in EE to inject custom validations + def validations_for_commit(_) + [] + end + private def updated_from_web? @@ -150,6 +170,14 @@ module Gitlab raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:lfs_objects_missing] end end + + def commit_check + @commit_check ||= Gitlab::Checks::CommitCheck.new(project, user_access.user, newrev, oldrev) + end + + def commits + project.repository.new_commits(newrev) + end end end end diff --git a/lib/gitlab/checks/commit_check.rb b/lib/gitlab/checks/commit_check.rb new file mode 100644 index 00000000000..ae0cd142378 --- /dev/null +++ b/lib/gitlab/checks/commit_check.rb @@ -0,0 +1,61 @@ +module Gitlab + module Checks + class CommitCheck + include Gitlab::Utils::StrongMemoize + + attr_reader :project, :user, :newrev, :oldrev + + def initialize(project, user, newrev, oldrev) + @project = project + @user = user + @newrev = user + @oldrev = user + @file_paths = [] + end + + def validate(commit, validations) + return if validations.empty? && path_validations.empty? + + commit.raw_deltas.each do |diff| + @file_paths << (diff.new_path || diff.old_path) + + validations.each do |validation| + if error = validation.call(diff) + raise ::Gitlab::GitAccess::UnauthorizedError, error + end + end + end + end + + def validate_file_paths + path_validations.each do |validation| + if error = validation.call(@file_paths) + raise ::Gitlab::GitAccess::UnauthorizedError, error + end + end + end + + private + + def validate_lfs_file_locks? + strong_memoize(:validate_lfs_file_locks) do + project.lfs_enabled? && project.lfs_file_locks.any? && newrev && oldrev + end + end + + def lfs_file_locks_validation + lambda do |paths| + lfs_lock = project.lfs_file_locks.where(path: paths).where.not(user_id: user.id).first + + if lfs_lock + return "The path '#{lfs_lock.path}' is locked in Git LFS by #{lfs_lock.user.name}" + end + end + end + + def path_validations + validate_lfs_file_locks? ? [lfs_file_locks_validation] : [] + end + end + end +end diff --git a/lib/gitlab/ci/config/loader.rb b/lib/gitlab/ci/config/loader.rb index e7d9f6a7761..141d2714cb6 100644 --- a/lib/gitlab/ci/config/loader.rb +++ b/lib/gitlab/ci/config/loader.rb @@ -6,6 +6,8 @@ module Gitlab def initialize(config) @config = YAML.safe_load(config, [Symbol], [], true) + rescue Psych::Exception => e + raise FormatError, e.message end def valid? diff --git a/lib/gitlab/ci/yaml_processor.rb b/lib/gitlab/ci/yaml_processor.rb index 0bd78b03448..a7285ac8f9d 100644 --- a/lib/gitlab/ci/yaml_processor.rb +++ b/lib/gitlab/ci/yaml_processor.rb @@ -85,7 +85,7 @@ module Gitlab begin Gitlab::Ci::YamlProcessor.new(content) nil - rescue ValidationError, Psych::SyntaxError => e + rescue ValidationError => e e.message end end diff --git a/lib/gitlab/encoding_helper.rb b/lib/gitlab/encoding_helper.rb index c0edcabc6fd..6659efa0961 100644 --- a/lib/gitlab/encoding_helper.rb +++ b/lib/gitlab/encoding_helper.rb @@ -28,9 +28,9 @@ module Gitlab # encode and clean the bad chars message.replace clean(message) - rescue ArgumentError - return nil - rescue + rescue ArgumentError => e + return unless e.message.include?('unknown encoding name') + encoding = detect ? detect[:encoding] : "unknown" "--broken encoding: #{encoding}" end diff --git a/lib/gitlab/file_finder.rb b/lib/gitlab/file_finder.rb index 10ffc345bd5..8c082c0c336 100644 --- a/lib/gitlab/file_finder.rb +++ b/lib/gitlab/file_finder.rb @@ -28,7 +28,7 @@ module Gitlab def find_by_content(query) results = repository.search_files_by_content(query, ref).first(BATCH_SIZE) - results.map { |result| Gitlab::ProjectSearchResults.parse_search_result(result) } + results.map { |result| Gitlab::ProjectSearchResults.parse_search_result(result, project) } end def find_by_filename(query, except: []) @@ -45,7 +45,8 @@ module Gitlab basename: File.basename(blob.path), ref: ref, startline: 1, - data: blob.data + data: blob.data, + project: project ) end end diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb index 4828301dbb9..b2fca2c16de 100644 --- a/lib/gitlab/git/blob.rb +++ b/lib/gitlab/git/blob.rb @@ -53,11 +53,7 @@ module Gitlab def batch(repository, blob_references, blob_size_limit: MAX_DATA_DISPLAY_SIZE) Gitlab::GitalyClient.migrate(:list_blobs_by_sha_path) do |is_enabled| if is_enabled - Gitlab::GitalyClient.allow_n_plus_1_calls do - blob_references.map do |sha, path| - find_by_gitaly(repository, sha, path, limit: blob_size_limit) - end - end + repository.gitaly_blob_client.get_blobs(blob_references, blob_size_limit).to_a else blob_references.map do |sha, path| find_by_rugged(repository, sha, path, limit: blob_size_limit) diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb index 768617e2cae..d95561fe1b2 100644 --- a/lib/gitlab/git/commit.rb +++ b/lib/gitlab/git/commit.rb @@ -402,15 +402,6 @@ module Gitlab end end - # Get a collection of Rugged::Reference objects for this commit. - # - # Ex. - # commit.ref(repo) - # - def refs(repo) - repo.refs_hash[id] - end - # Get ref names collection # # Ex. @@ -418,7 +409,7 @@ module Gitlab # def ref_names(repo) refs(repo).map do |ref| - ref.name.sub(%r{^refs/(heads|remotes|tags)/}, "") + ref.sub(%r{^refs/(heads|remotes|tags)/}, "") end end @@ -553,6 +544,15 @@ module Gitlab date: Google::Protobuf::Timestamp.new(seconds: author_or_committer[:time].to_i) ) end + + # Get a collection of Gitlab::Git::Ref objects for this commit. + # + # Ex. + # commit.ref(repo) + # + def refs(repo) + repo.refs_hash[id] + end end end end diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index d7510061def..5f014e43c6f 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -631,21 +631,18 @@ module Gitlab end end - # Get refs hash which key is SHA1 - # and value is a Rugged::Reference + # Get refs hash which key is is the commit id + # and value is a Gitlab::Git::Tag or Gitlab::Git::Branch + # Note that both inherit from Gitlab::Git::Ref def refs_hash - # Initialize only when first call - if @refs_hash.nil? - @refs_hash = Hash.new { |h, k| h[k] = [] } - - rugged.references.each do |r| - # Symbolic/remote references may not have an OID; skip over them - target_oid = r.target.try(:oid) - if target_oid - sha = rev_parse_target(target_oid).oid - @refs_hash[sha] << r - end - end + return @refs_hash if @refs_hash + + @refs_hash = Hash.new { |h, k| h[k] = [] } + + (tags + branches).each do |ref| + next unless ref.target && ref.name + + @refs_hash[ref.dereferenced_target.id] << ref.name end @refs_hash @@ -1617,17 +1614,14 @@ module Gitlab # Gitaly note: JV: Trying to get rid of the 'filter' option so we can implement this with 'git'. def branches_filter(filter: nil, sort_by: nil) - # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37464 - branches = Gitlab::GitalyClient.allow_n_plus_1_calls do - rugged.branches.each(filter).map do |rugged_ref| - begin - target_commit = Gitlab::Git::Commit.find(self, rugged_ref.target) - Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target, target_commit) - rescue Rugged::ReferenceError - # Omit invalid branch - end - end.compact - end + branches = rugged.branches.each(filter).map do |rugged_ref| + begin + target_commit = Gitlab::Git::Commit.find(self, rugged_ref.target) + Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target, target_commit) + rescue Rugged::ReferenceError + # Omit invalid branch + end + end.compact sort_branches(branches, sort_by) end diff --git a/lib/gitlab/git/wiki.rb b/lib/gitlab/git/wiki.rb index 39040d56971..ac12271a87e 100644 --- a/lib/gitlab/git/wiki.rb +++ b/lib/gitlab/git/wiki.rb @@ -25,9 +25,8 @@ module Gitlab @repository.exists? end - # Disabled because of https://gitlab.com/gitlab-org/gitaly/merge_requests/539 def write_page(name, format, content, commit_details) - @repository.gitaly_migrate(:wiki_write_page, status: Gitlab::GitalyClient::MigrationStatus::DISABLED) do |is_enabled| + @repository.gitaly_migrate(:wiki_write_page) do |is_enabled| if is_enabled gitaly_write_page(name, format, content, commit_details) gollum_wiki.clear_cache @@ -48,9 +47,8 @@ module Gitlab end end - # Disable because of https://gitlab.com/gitlab-org/gitlab-ce/issues/42094 def update_page(page_path, title, format, content, commit_details) - @repository.gitaly_migrate(:wiki_update_page, status: Gitlab::GitalyClient::MigrationStatus::DISABLED) do |is_enabled| + @repository.gitaly_migrate(:wiki_update_page) do |is_enabled| if is_enabled gitaly_update_page(page_path, title, format, content, commit_details) gollum_wiki.clear_cache diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 8ec3386184a..9ec3858b493 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -238,19 +238,22 @@ module Gitlab changes_list = Gitlab::ChangesList.new(changes) # Iterate over all changes to find if user allowed all of them to be applied - changes_list.each do |change| + changes_list.each.with_index do |change, index| + first_change = index == 0 + # If user does not have access to make at least one change, cancel all # push by allowing the exception to bubble up - check_single_change_access(change) + check_single_change_access(change, skip_lfs_integrity_check: !first_change) end end - def check_single_change_access(change) + def check_single_change_access(change, skip_lfs_integrity_check: false) Checks::ChangeAccess.new( change, user_access: user_access, project: project, skip_authorization: deploy_key?, + skip_lfs_integrity_check: skip_lfs_integrity_check, protocol: protocol ).exec end diff --git a/lib/gitlab/git_access_wiki.rb b/lib/gitlab/git_access_wiki.rb index 1c9477e84b2..84d6e1490c3 100644 --- a/lib/gitlab/git_access_wiki.rb +++ b/lib/gitlab/git_access_wiki.rb @@ -13,7 +13,7 @@ module Gitlab authentication_abilities.include?(:download_code) && user_access.can_do_action?(:download_wiki_code) end - def check_single_change_access(change) + def check_single_change_access(change, _options = {}) unless user_access.can_do_action?(:create_wiki) raise UnauthorizedError, ERROR_MESSAGES[:write_to_wiki] end diff --git a/lib/gitlab/gitaly_client/blob_service.rb b/lib/gitlab/gitaly_client/blob_service.rb index d70a1a7665e..dfa0fa43b0f 100644 --- a/lib/gitlab/gitaly_client/blob_service.rb +++ b/lib/gitlab/gitaly_client/blob_service.rb @@ -1,6 +1,8 @@ module Gitlab module GitalyClient class BlobService + include Gitlab::EncodingHelper + def initialize(repository) @gitaly_repo = repository.gitaly_repository end @@ -54,6 +56,30 @@ module Gitlab end end end + + def get_blobs(revision_paths, limit = -1) + return [] if revision_paths.empty? + + revision_paths.map! do |rev, path| + Gitaly::GetBlobsRequest::RevisionPath.new(revision: rev, path: encode_binary(path)) + end + + request = Gitaly::GetBlobsRequest.new( + repository: @gitaly_repo, + revision_paths: revision_paths, + limit: limit + ) + + response = GitalyClient.call( + @gitaly_repo.storage_name, + :blob_service, + :get_blobs, + request, + timeout: GitalyClient.default_timeout + ) + + GitalyClient::BlobsStitcher.new(response) + end end end end diff --git a/lib/gitlab/gitaly_client/blobs_stitcher.rb b/lib/gitlab/gitaly_client/blobs_stitcher.rb new file mode 100644 index 00000000000..5ca592ff812 --- /dev/null +++ b/lib/gitlab/gitaly_client/blobs_stitcher.rb @@ -0,0 +1,47 @@ +module Gitlab + module GitalyClient + class BlobsStitcher + include Enumerable + + def initialize(rpc_response) + @rpc_response = rpc_response + end + + def each + current_blob_data = nil + + @rpc_response.each do |msg| + begin + if msg.oid.blank? && msg.data.blank? + next + elsif msg.oid.present? + yield new_blob(current_blob_data) if current_blob_data + + current_blob_data = msg.to_h.slice(:oid, :path, :size, :revision, :mode) + current_blob_data[:data] = msg.data.dup + else + current_blob_data[:data] << msg.data + end + end + end + + yield new_blob(current_blob_data) if current_blob_data + end + + private + + def new_blob(blob_data) + Gitlab::Git::Blob.new( + id: blob_data[:oid], + mode: blob_data[:mode].to_s(8), + name: File.basename(blob_data[:path]), + path: blob_data[:path], + size: blob_data[:size], + commit_id: blob_data[:revision], + data: blob_data[:data], + binary: Gitlab::Git::Blob.binary?(blob_data[:data]) + ) + end + end + end +end diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb index 5767f06b0ce..269a048cf5d 100644 --- a/lib/gitlab/gitaly_client/commit_service.rb +++ b/lib/gitlab/gitaly_client/commit_service.rb @@ -222,14 +222,25 @@ module Gitlab end def find_commit(revision) - request = Gitaly::FindCommitRequest.new( - repository: @gitaly_repo, - revision: encode_binary(revision) - ) - - response = GitalyClient.call(@repository.storage, :commit_service, :find_commit, request, timeout: GitalyClient.medium_timeout) - - response.commit + if RequestStore.active? + # We don't use RequeStstore.fetch(key) { ... } directly because `revision` + # can be a branch name, so we can't use it as a key as it could point + # to another commit later on (happens a lot in tests). + key = { + storage: @gitaly_repo.storage_name, + relative_path: @gitaly_repo.relative_path, + commit_id: revision + } + return RequestStore[key] if RequestStore.exist?(key) + + commit = call_find_commit(revision) + return unless commit + + key[:commit_id] = commit.id + RequestStore[key] = commit + else + call_find_commit(revision) + end end def patch(revision) @@ -346,6 +357,17 @@ module Gitlab def encode_repeated(a) Google::Protobuf::RepeatedField.new(:bytes, a.map { |s| encode_binary(s) } ) end + + def call_find_commit(revision) + request = Gitaly::FindCommitRequest.new( + repository: @gitaly_repo, + revision: encode_binary(revision) + ) + + response = GitalyClient.call(@repository.storage, :commit_service, :find_commit, request, timeout: GitalyClient.medium_timeout) + + response.commit + end end end end diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb index 86a90d57d9c..ba04387022d 100644 --- a/lib/gitlab/gon_helper.rb +++ b/lib/gitlab/gon_helper.rb @@ -13,8 +13,6 @@ module Gitlab gon.relative_url_root = Gitlab.config.gitlab.relative_url_root gon.shortcuts_path = help_page_path('shortcuts') gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class - gon.katex_css_url = ActionController::Base.helpers.asset_path('katex.css') - gon.katex_js_url = ActionController::Base.helpers.asset_path('katex.js') gon.sentry_dsn = Gitlab::CurrentSettings.clientside_sentry_dsn if Gitlab::CurrentSettings.clientside_sentry_enabled gon.gitlab_url = Gitlab.config.gitlab.url gon.revision = Gitlab::REVISION diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index 2daed10f678..9f404003125 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -27,6 +27,8 @@ project_tree: - :releases - project_members: - :user + - lfs_file_locks: + - :user - merge_requests: - notes: - :author diff --git a/lib/gitlab/import_export/importer.rb b/lib/gitlab/import_export/importer.rb index c14646b0611..a00795f553e 100644 --- a/lib/gitlab/import_export/importer.rb +++ b/lib/gitlab/import_export/importer.rb @@ -50,9 +50,10 @@ module Gitlab end def wiki_restorer - Gitlab::ImportExport::RepoRestorer.new(path_to_bundle: wiki_repo_path, + Gitlab::ImportExport::WikiRestorer.new(path_to_bundle: wiki_repo_path, shared: @shared, - project: ProjectWiki.new(project_tree.restored_project)) + project: ProjectWiki.new(project_tree.restored_project), + wiki_enabled: @project.wiki_enabled?) end def uploads_restorer diff --git a/lib/gitlab/import_export/project_creator.rb b/lib/gitlab/import_export/project_creator.rb deleted file mode 100644 index 77bb3ca6581..00000000000 --- a/lib/gitlab/import_export/project_creator.rb +++ /dev/null @@ -1,23 +0,0 @@ -module Gitlab - module ImportExport - class ProjectCreator - def initialize(namespace_id, current_user, file, project_path) - @namespace_id = namespace_id - @current_user = current_user - @file = file - @project_path = project_path - end - - def execute - ::Projects::CreateService.new( - @current_user, - name: @project_path, - path: @project_path, - namespace_id: @namespace_id, - import_type: "gitlab_project", - import_source: @file - ).execute - end - end - end -end diff --git a/lib/gitlab/import_export/wiki_restorer.rb b/lib/gitlab/import_export/wiki_restorer.rb new file mode 100644 index 00000000000..f33bfb332ab --- /dev/null +++ b/lib/gitlab/import_export/wiki_restorer.rb @@ -0,0 +1,23 @@ +module Gitlab + module ImportExport + class WikiRestorer < RepoRestorer + def initialize(project:, shared:, path_to_bundle:, wiki_enabled:) + super(project: project, shared: shared, path_to_bundle: path_to_bundle) + + @wiki_enabled = wiki_enabled + end + + def restore + @project.wiki if create_empty_wiki? + + super + end + + private + + def create_empty_wiki? + !File.exist?(@path_to_bundle) && @wiki_enabled + end + end + end +end diff --git a/lib/gitlab/ldap/config.rb b/lib/gitlab/ldap/config.rb index 47b3fce3e7a..a6bea98d631 100644 --- a/lib/gitlab/ldap/config.rb +++ b/lib/gitlab/ldap/config.rb @@ -15,7 +15,7 @@ module Gitlab end def self.servers - Gitlab.config.ldap.servers.values + Gitlab.config.ldap['servers']&.values || [] end def self.available_servers diff --git a/lib/gitlab/ldap/person.rb b/lib/gitlab/ldap/person.rb index b91757c2a4b..c59df556247 100644 --- a/lib/gitlab/ldap/person.rb +++ b/lib/gitlab/ldap/person.rb @@ -63,8 +63,6 @@ module Gitlab Rails.logger.debug { "Instantiating #{self.class.name} with LDIF:\n#{entry.to_ldif}" } @entry = entry @provider = provider - - validate_entry end def name @@ -117,19 +115,6 @@ module Gitlab entry.public_send(selected_attr) # rubocop:disable GitlabSecurity/PublicSend end - - def validate_entry - allowed_attrs = self.class.ldap_attributes(config).map(&:downcase) - - # Net::LDAP::Entry transforms keys to symbols. Change to strings to compare. - entry_attrs = entry.attribute_names.map { |n| n.to_s.downcase } - invalid_attrs = entry_attrs - allowed_attrs - - if invalid_attrs.any? - raise InvalidEntryError, - "#{self.class.name} initialized with Net::LDAP::Entry containing invalid attributes(s): #{invalid_attrs}" - end - end end end end diff --git a/lib/gitlab/middleware/multipart.rb b/lib/gitlab/middleware/multipart.rb index cc1e92480be..d4c54049b74 100644 --- a/lib/gitlab/middleware/multipart.rb +++ b/lib/gitlab/middleware/multipart.rb @@ -42,7 +42,7 @@ module Gitlab key, value = parsed_field.first if value.nil? - value = open_file(tmp_path) + value = open_file(tmp_path, @request.params["#{key}.name"]) @open_files << value else value = decorate_params_value(value, @request.params[key], tmp_path) @@ -70,7 +70,7 @@ module Gitlab case path_value when nil - value_hash[path_key] = open_file(tmp_path) + value_hash[path_key] = open_file(tmp_path, value_hash.dig(path_key, '.name')) @open_files << value_hash[path_key] value_hash when Hash @@ -81,8 +81,8 @@ module Gitlab end end - def open_file(path) - ::UploadedFile.new(path, File.basename(path), 'application/octet-stream') + def open_file(path, name) + ::UploadedFile.new(path, name || File.basename(path), 'application/octet-stream') end end diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb index a3e1c66c19f..28ebac1776e 100644 --- a/lib/gitlab/o_auth/user.rb +++ b/lib/gitlab/o_auth/user.rb @@ -198,9 +198,11 @@ module Gitlab end def update_profile + clear_user_synced_attributes_metadata + return unless sync_profile_from_provider? || creating_linked_ldap_user? - metadata = gl_user.user_synced_attributes_metadata || gl_user.build_user_synced_attributes_metadata + metadata = gl_user.build_user_synced_attributes_metadata if sync_profile_from_provider? UserSyncedAttributesMetadata::SYNCABLE_ATTRIBUTES.each do |key| @@ -221,6 +223,10 @@ module Gitlab end end + def clear_user_synced_attributes_metadata + gl_user&.user_synced_attributes_metadata&.destroy + end + def log Gitlab::AppLogger end diff --git a/lib/gitlab/profiler.rb b/lib/gitlab/profiler.rb index 95d94b3cc68..98a168b43bb 100644 --- a/lib/gitlab/profiler.rb +++ b/lib/gitlab/profiler.rb @@ -45,6 +45,7 @@ module Gitlab if user private_token ||= user.personal_access_tokens.active.pluck(:token).first + raise 'Your user must have a personal_access_token' unless private_token end headers['Private-Token'] = private_token if private_token diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb index 4823f703ba4..cf0935dbd9a 100644 --- a/lib/gitlab/project_search_results.rb +++ b/lib/gitlab/project_search_results.rb @@ -2,11 +2,12 @@ module Gitlab class ProjectSearchResults < SearchResults attr_reader :project, :repository_ref - def initialize(current_user, project, query, repository_ref = nil) + def initialize(current_user, project, query, repository_ref = nil, per_page: 20) @current_user = current_user @project = project @repository_ref = repository_ref.presence || project.default_branch @query = query + @per_page = per_page end def objects(scope, page = nil) @@ -40,7 +41,7 @@ module Gitlab @commits_count ||= commits.count end - def self.parse_search_result(result) + def self.parse_search_result(result, project = nil) ref = nil filename = nil basename = nil @@ -65,7 +66,8 @@ module Gitlab basename: basename, ref: ref, startline: startline, - data: data + data: data, + project_id: project ? project.id : nil ) end diff --git a/lib/gitlab/prometheus/queries/additional_metrics_deployment_query.rb b/lib/gitlab/prometheus/queries/additional_metrics_deployment_query.rb index 69d055c901c..294a6ae34ca 100644 --- a/lib/gitlab/prometheus/queries/additional_metrics_deployment_query.rb +++ b/lib/gitlab/prometheus/queries/additional_metrics_deployment_query.rb @@ -4,7 +4,7 @@ module Gitlab class AdditionalMetricsDeploymentQuery < BaseQuery include QueryAdditionalMetrics - def query(deployment_id) + def query(environment_id, deployment_id) Deployment.find_by(id: deployment_id).try do |deployment| query_metrics( common_query_context( diff --git a/lib/gitlab/prometheus/queries/deployment_query.rb b/lib/gitlab/prometheus/queries/deployment_query.rb index 170f483540e..6e6da593178 100644 --- a/lib/gitlab/prometheus/queries/deployment_query.rb +++ b/lib/gitlab/prometheus/queries/deployment_query.rb @@ -2,7 +2,7 @@ module Gitlab module Prometheus module Queries class DeploymentQuery < BaseQuery - def query(deployment_id) + def query(environment_id, deployment_id) Deployment.find_by(id: deployment_id).try do |deployment| environment_slug = deployment.environment.slug diff --git a/lib/gitlab/prometheus_client.rb b/lib/gitlab/prometheus_client.rb index aa94614bf18..10527972663 100644 --- a/lib/gitlab/prometheus_client.rb +++ b/lib/gitlab/prometheus_client.rb @@ -3,10 +3,10 @@ module Gitlab # Helper methods to interact with Prometheus network services & resources class PrometheusClient - attr_reader :api_url + attr_reader :rest_client, :headers - def initialize(api_url:) - @api_url = api_url + def initialize(rest_client) + @rest_client = rest_client end def ping @@ -40,37 +40,40 @@ module Gitlab private def json_api_get(type, args = {}) - get(join_api_url(type, args)) + path = ['api', 'v1', type].join('/') + get(path, args) + rescue JSON::ParserError + raise PrometheusError, 'Parsing response failed' rescue Errno::ECONNREFUSED raise PrometheusError, 'Connection refused' end - def join_api_url(type, args = {}) - url = URI.parse(api_url) - rescue URI::Error - raise PrometheusError, "Invalid API URL: #{api_url}" - else - url.path = [url.path.sub(%r{/+\z}, ''), 'api', 'v1', type].join('/') - url.query = args.to_query - - url.to_s - end - - def get(url) - handle_response(HTTParty.get(url)) + def get(path, args) + response = rest_client[path].get(params: args) + handle_response(response) rescue SocketError - raise PrometheusError, "Can't connect to #{url}" + raise PrometheusError, "Can't connect to #{rest_client.url}" rescue OpenSSL::SSL::SSLError - raise PrometheusError, "#{url} contains invalid SSL data" - rescue HTTParty::Error + raise PrometheusError, "#{rest_client.url} contains invalid SSL data" + rescue RestClient::ExceptionWithResponse => ex + handle_exception_response(ex.response) + rescue RestClient::Exception raise PrometheusError, "Network connection error" end def handle_response(response) - if response.code == 200 && response['status'] == 'success' - response['data'] || {} - elsif response.code == 400 - raise PrometheusError, response['error'] || 'Bad data received' + json_data = JSON.parse(response.body) + if response.code == 200 && json_data['status'] == 'success' + json_data['data'] || {} + else + raise PrometheusError, "#{response.code} - #{response.body}" + end + end + + def handle_exception_response(response) + if response.code == 400 + json_data = JSON.parse(response.body) + raise PrometheusError, json_data['error'] || 'Bad data received' else raise PrometheusError, "#{response.code} - #{response.body}" end diff --git a/lib/gitlab/query_limiting.rb b/lib/gitlab/query_limiting.rb index f64f1757144..9f69a9e4a39 100644 --- a/lib/gitlab/query_limiting.rb +++ b/lib/gitlab/query_limiting.rb @@ -6,7 +6,7 @@ module Gitlab # This ensures we don't produce any errors that users can't do anything # about themselves. def self.enable? - Gitlab.com? || Rails.env.development? || Rails.env.test? + Rails.env.development? || Rails.env.test? end # Allows the current request to execute any number of SQL queries. diff --git a/lib/gitlab/query_limiting/transaction.rb b/lib/gitlab/query_limiting/transaction.rb index 3cbafadb0d0..66d7d9275cf 100644 --- a/lib/gitlab/query_limiting/transaction.rb +++ b/lib/gitlab/query_limiting/transaction.rb @@ -51,13 +51,7 @@ module Gitlab error = ThresholdExceededError.new(error_message) - if raise_error? - raise(error) - else - # Raven automatically logs to the Rails log if disabled, thus we don't - # need to manually log anything in case Sentry support is not enabled. - Raven.capture_exception(error) - end + raise(error) if raise_error? end def increment diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index 7ab85e1c35c..ac3de2a8f71 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -40,12 +40,16 @@ module Gitlab 'a-zA-Z0-9_/\\$\\{\\}\\. \\-' end + def environment_name_regex_chars_without_slash + 'a-zA-Z0-9_\\$\\{\\}\\. -' + end + def environment_name_regex - @environment_name_regex ||= /\A[#{environment_name_regex_chars}]+\z/.freeze + @environment_name_regex ||= /\A[#{environment_name_regex_chars_without_slash}]([#{environment_name_regex_chars}]*[#{environment_name_regex_chars_without_slash}])?\z/.freeze end def environment_name_regex_message - "can contain only letters, digits, '-', '_', '/', '$', '{', '}', '.', and spaces" + "can contain only letters, digits, '-', '_', '/', '$', '{', '}', '.', and spaces, but it cannot start or end with '/'" end def kubernetes_namespace_regex diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb index 7362514167f..5a5ae7f19d4 100644 --- a/lib/gitlab/search_results.rb +++ b/lib/gitlab/search_results.rb @@ -1,7 +1,7 @@ module Gitlab class SearchResults class FoundBlob - attr_reader :id, :filename, :basename, :ref, :startline, :data + attr_reader :id, :filename, :basename, :ref, :startline, :data, :project_id def initialize(opts = {}) @id = opts.fetch(:id, nil) @@ -10,6 +10,8 @@ module Gitlab @ref = opts.fetch(:ref, nil) @startline = opts.fetch(:startline, nil) @data = opts.fetch(:data, nil) + @per_page = opts.fetch(:per_page, 20) + @project_id = opts.fetch(:project_id, nil) end def path @@ -21,7 +23,7 @@ module Gitlab end end - attr_reader :current_user, :query + attr_reader :current_user, :query, :per_page # Limit search results by passed projects # It allows us to search only for projects user has access to @@ -33,11 +35,12 @@ module Gitlab # query attr_reader :default_project_filter - def initialize(current_user, limit_projects, query, default_project_filter: false) + def initialize(current_user, limit_projects, query, default_project_filter: false, per_page: 20) @current_user = current_user @limit_projects = limit_projects || Project.all @query = query @default_project_filter = default_project_filter + @per_page = per_page end def objects(scope, page = nil, without_count = true) @@ -153,10 +156,6 @@ module Gitlab 'projects' end - def per_page - 20 - end - def project_ids_relation limit_projects.select(:id).reorder(nil) end diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb index f4a41dc3eda..4ba44e0feef 100644 --- a/lib/gitlab/shell.rb +++ b/lib/gitlab/shell.rb @@ -294,7 +294,8 @@ module Gitlab # add_namespace("/path/to/storage", "gitlab") # def add_namespace(storage, name) - Gitlab::GitalyClient.migrate(:add_namespace) do |enabled| + Gitlab::GitalyClient.migrate(:add_namespace, + status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |enabled| if enabled gitaly_namespace_client(storage).add(name) else @@ -315,7 +316,8 @@ module Gitlab # rm_namespace("/path/to/storage", "gitlab") # def rm_namespace(storage, name) - Gitlab::GitalyClient.migrate(:remove_namespace) do |enabled| + Gitlab::GitalyClient.migrate(:remove_namespace, + status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |enabled| if enabled gitaly_namespace_client(storage).remove(name) else @@ -333,7 +335,8 @@ module Gitlab # mv_namespace("/path/to/storage", "gitlab", "gitlabhq") # def mv_namespace(storage, old_name, new_name) - Gitlab::GitalyClient.migrate(:rename_namespace) do |enabled| + Gitlab::GitalyClient.migrate(:rename_namespace, + status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |enabled| if enabled gitaly_namespace_client(storage).rename(old_name, new_name) else @@ -368,7 +371,8 @@ module Gitlab # # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/385 def exists?(storage, dir_name) - Gitlab::GitalyClient.migrate(:namespace_exists) do |enabled| + Gitlab::GitalyClient.migrate(:namespace_exists, + status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |enabled| if enabled gitaly_namespace_client(storage).exists?(dir_name) else diff --git a/lib/gitlab/ssh_public_key.rb b/lib/gitlab/ssh_public_key.rb index 545e7c74f7e..89ca1298120 100644 --- a/lib/gitlab/ssh_public_key.rb +++ b/lib/gitlab/ssh_public_key.rb @@ -21,22 +21,6 @@ module Gitlab technology(name)&.supported_sizes end - def self.sanitize(key_content) - ssh_type, *parts = key_content.strip.split - - return key_content if parts.empty? - - parts.each_with_object("#{ssh_type} ").with_index do |(part, content), index| - content << part - - if Gitlab::SSHPublicKey.new(content).valid? - break [content, parts[index + 1]].compact.join(' ') # Add the comment part if present - elsif parts.size == index + 1 # return original content if we've reached the last element - break key_content - end - end - end - attr_reader :key_text, :key # Unqualified MD5 fingerprint for compatibility @@ -53,23 +37,23 @@ module Gitlab end def valid? - key.present? && bits && technology.supported_sizes.include?(bits) + key.present? end def type - technology.name if key.present? + technology.name if valid? end def bits - return if key.blank? + return unless valid? case type when :rsa - key.n&.num_bits + key.n.num_bits when :dsa - key.p&.num_bits + key.p.num_bits when :ecdsa - key.group.order&.num_bits + key.group.order.num_bits when :ed25519 256 else diff --git a/lib/tasks/gemojione.rake b/lib/tasks/gemojione.rake index c2d3a6b6950..c6942d22926 100644 --- a/lib/tasks/gemojione.rake +++ b/lib/tasks/gemojione.rake @@ -115,7 +115,7 @@ namespace :gemojione do end end - style_path = Rails.root.join(*%w(app assets stylesheets framework emoji-sprites.scss)) + style_path = Rails.root.join(*%w(app assets stylesheets framework emoji_sprites.scss)) # Combine the resized assets into a packed sprite and re-generate the SCSS SpriteFactory.cssurl = "image-url('$IMAGE')" diff --git a/lib/tasks/lint.rake b/lib/tasks/lint.rake index 3ab406eff2c..fe5032cae18 100644 --- a/lib/tasks/lint.rake +++ b/lib/tasks/lint.rake @@ -16,5 +16,54 @@ unless Rails.env.production? task :javascript do Rake::Task['eslint'].invoke end + + desc "GitLab | lint | Run several lint checks" + task :all do + status = 0 + + %w[ + config_lint + haml_lint + scss_lint + flay + gettext:lint + lint:static_verification + ].each do |task| + pid = Process.fork do + rd, wr = IO.pipe + stdout = $stdout.dup + stderr = $stderr.dup + $stdout.reopen(wr) + $stderr.reopen(wr) + + begin + begin + Rake::Task[task].invoke + rescue RuntimeError # The haml_lint tasks raise a RuntimeError + exit(1) + end + rescue SystemExit => ex + msg = "*** Rake task #{task} failed with the following error(s):" + raise ex + ensure + $stdout.reopen(stdout) + $stderr.reopen(stderr) + wr.close + + if msg + warn "\n#{msg}\n\n" + IO.copy_stream(rd, $stderr) + else + IO.copy_stream(rd, $stdout) + end + end + end + + Process.waitpid(pid) + status += $?.exitstatus + end + + exit(status) + end end end diff --git a/locale/bg/gitlab.po b/locale/bg/gitlab.po index 8432914d6a7..badd665c08c 100644 --- a/locale/bg/gitlab.po +++ b/locale/bg/gitlab.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab-ee\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-12-12 18:31+0000\n" -"PO-Revision-Date: 2018-01-05 04:42-0500\n" +"POT-Creation-Date: 2018-02-07 11:38-0600\n" +"PO-Revision-Date: 2018-02-12 03:58-0500\n" "Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n" "Language-Team: Bulgarian\n" "Language: bg_BG\n" @@ -16,23 +16,41 @@ msgstr "" "X-Crowdin-Language: bg\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" +msgid " and" +msgstr "" + msgid "%d commit" msgid_plural "%d commits" msgstr[0] "%d подаване" msgstr[1] "%d подаваниÑ" +msgid "%d commit behind" +msgid_plural "%d commits behind" +msgstr[0] "" +msgstr[1] "" + +msgid "%d issue" +msgid_plural "%d issues" +msgstr[0] "" +msgstr[1] "" + msgid "%d layer" msgid_plural "%d layers" msgstr[0] "" msgstr[1] "" +msgid "%d merge request" +msgid_plural "%d merge requests" +msgstr[0] "" +msgstr[1] "" + msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "%s additional commits have been omitted to prevent performance issues." msgstr[0] "%s подаване беше пропуÑнато, за да не Ñе натоварва ÑиÑтемата." msgstr[1] "%s Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ð¸Ñ Ð±Ñха пропуÑнати, за да не Ñе натоварва ÑиÑтемата." -msgid "%{commit_author_link} committed %{commit_timeago}" -msgstr "%{commit_author_link} подаде %{commit_timeago}" +msgid "%{commit_author_link} authored %{commit_timeago}" +msgstr "" msgid "%{count} participant" msgid_plural "%{count} participants" @@ -45,9 +63,6 @@ msgstr "" msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will allow access on the next attempt." msgstr "" -msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will block access for %{number_of_seconds} seconds." -msgstr "" - msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved." msgstr "" @@ -121,24 +136,81 @@ msgstr "ДобавÑне на лиценз" msgid "Add new directory" msgstr "ДобавÑне на нова папка" +msgid "Add todo" +msgstr "" + +msgid "AdminArea|Stop all jobs" +msgstr "" + +msgid "AdminArea|Stop all jobs?" +msgstr "" + +msgid "AdminArea|Stop jobs" +msgstr "" + +msgid "AdminArea|Stopping jobs failed" +msgstr "" + +msgid "AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running." +msgstr "" + msgid "AdminHealthPageLink|health page" msgstr "" +msgid "Advanced" +msgstr "" + msgid "Advanced settings" msgstr "" msgid "All" msgstr "" +msgid "All changes are committed" +msgstr "" + +msgid "Allows you to add and manage Kubernetes clusters." +msgstr "" + +msgid "An error occurred previewing the blob" +msgstr "" + msgid "An error occurred when toggling the notification subscription" msgstr "" msgid "An error occurred when updating the issue weight" msgstr "" +msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." +msgstr "" + +msgid "An error occurred while fetching markdown preview" +msgstr "" + msgid "An error occurred while fetching sidebar data" msgstr "" +msgid "An error occurred while getting projects" +msgstr "" + +msgid "An error occurred while loading filenames" +msgstr "" + +msgid "An error occurred while rendering KaTeX" +msgstr "" + +msgid "An error occurred while rendering preview broadcast message" +msgstr "" + +msgid "An error occurred while retrieving calendar activity" +msgstr "" + +msgid "An error occurred while retrieving diff" +msgstr "" + +msgid "An error occurred while validating username" +msgstr "" + msgid "An error occurred. Please try again." msgstr "" @@ -163,9 +235,6 @@ msgstr "ÐаиÑтина ли иÑкате да изтриете този пла msgid "Are you sure you want to discard your changes?" msgstr "" -msgid "Are you sure you want to leave this group?" -msgstr "" - msgid "Are you sure you want to reset registration token?" msgstr "" @@ -178,6 +247,21 @@ msgstr "" msgid "Artifacts" msgstr "" +msgid "Assign custom color like #FF0000" +msgstr "" + +msgid "Assign labels" +msgstr "" + +msgid "Assign milestone" +msgstr "" + +msgid "Assign to" +msgstr "" + +msgid "Assignee" +msgstr "" + msgid "Attach a file by drag & drop or %{upload_link}" msgstr "Прикачете файл чрез влачене и пуÑкане или %{upload_link}" @@ -193,13 +277,16 @@ msgstr "" msgid "Author" msgstr "" -msgid "Auto Review Apps and Auto Deploy need a domain name and the %{kubernetes} to work correctly." +msgid "Authors: %{authors}" msgstr "" -msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly." +msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." msgstr "" -msgid "Auto Review Apps and Auto Deploy need the %{kubernetes} to work correctly." +msgid "Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly." +msgstr "" + +msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly." msgstr "" msgid "AutoDevOps|Auto DevOps (Beta)" @@ -223,6 +310,12 @@ msgstr "" msgid "Available" msgstr "" +msgid "Avatar will be removed. Are you sure?" +msgstr "" + +msgid "Average per day: %{average}" +msgstr "" + msgid "Billing" msgstr "" @@ -277,6 +370,9 @@ msgstr "" msgid "BillingPlans|per user" msgstr "" +msgid "Begin with the selected commit" +msgstr "" + msgid "Branch" msgid_plural "Branches" msgstr[0] "Клон" @@ -405,8 +501,8 @@ msgstr "от" msgid "CI / CD" msgstr "" -msgid "CI configuration" -msgstr "ÐšÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñ Ð½Ð° непрекъÑната интеграциÑ" +msgid "CI/CD configuration" +msgstr "" msgid "CICD|Jobs" msgstr "" @@ -417,6 +513,9 @@ msgstr "Отказ" msgid "Cancel edit" msgstr "" +msgid "Cannot modify managed Kubernetes cluster" +msgstr "" + msgid "Change Weight" msgstr "" @@ -432,15 +531,24 @@ msgstr "Подбиране" msgid "ChangeTypeAction|Revert" msgstr "ОтмÑна" +msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes." +msgstr "" + msgid "Changelog" msgstr "СпиÑък Ñ Ð¿Ñ€Ð¾Ð¼ÐµÐ½Ð¸" +msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision." +msgstr "" + msgid "Charts" msgstr "Графики" msgid "Chat" msgstr "" +msgid "Check interval" +msgstr "" + msgid "Checking %{text} availability…" msgstr "" @@ -453,7 +561,19 @@ msgstr "Подбиране на това подаване" msgid "Cherry-pick this merge request" msgstr "Подбиране на тази заÑвка за Ñливане" -msgid "Choose which groups you wish to replicate to this secondary node. Leave blank to replicate all." +msgid "Choose File ..." +msgstr "" + +msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request." +msgstr "" + +msgid "Choose file..." +msgstr "" + +msgid "Choose which groups you wish to synchronize to this secondary node." +msgstr "" + +msgid "Choose which shards you wish to synchronize to this secondary node." msgstr "" msgid "CiStatusLabel|canceled" @@ -510,79 +630,91 @@ msgstr "пропуÑнато" msgid "CiStatus|running" msgstr "протича в момента" -msgid "CircuitBreakerApiLink|circuitbreaker api" +msgid "CiVariables|Input variable key" msgstr "" -msgid "Clone repository" +msgid "CiVariables|Input variable value" msgstr "" -msgid "Close" +msgid "CiVariables|Remove variable row" msgstr "" -msgid "Cluster" +msgid "CiVariable|* (All environments)" msgstr "" -msgid "ClusterIntegration|%{appList} was successfully installed on your cluster" +msgid "CiVariable|All environments" msgstr "" -msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which incur additional costs. See %{pricingLink}" +msgid "CiVariable|Create wildcard" msgstr "" -msgid "ClusterIntegration|API URL" +msgid "CiVariable|Error occured while saving variables" msgstr "" -msgid "ClusterIntegration|Active" +msgid "CiVariable|New environment" msgstr "" -msgid "ClusterIntegration|Add an existing cluster" +msgid "CiVariable|Protected" msgstr "" -msgid "ClusterIntegration|Add cluster" +msgid "CiVariable|Search environments" msgstr "" -msgid "ClusterIntegration|All" +msgid "CiVariable|Toggle protected" msgstr "" -msgid "ClusterIntegration|Applications" +msgid "CiVariable|Validation failed" msgstr "" -msgid "ClusterIntegration|CA Certificate" +msgid "CircuitBreakerApiLink|circuitbreaker api" msgstr "" -msgid "ClusterIntegration|Certificate Authority bundle (PEM format)" +msgid "Click to expand text" msgstr "" -msgid "ClusterIntegration|Choose how to set up cluster integration" +msgid "Clone repository" msgstr "" -msgid "ClusterIntegration|Cluster" +msgid "Close" msgstr "" -msgid "ClusterIntegration|Cluster details" +msgid "Closed" msgstr "" -msgid "ClusterIntegration|Cluster integration" +msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Cluster integration is disabled for this project." +msgid "ClusterIntegration|API URL" msgstr "" -msgid "ClusterIntegration|Cluster integration is enabled for this project." +msgid "ClusterIntegration|Add Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab's connection to it." +msgid "ClusterIntegration|Add an existing Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Cluster is being created on Google Kubernetes Engine..." +msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration" +msgstr "" + +msgid "ClusterIntegration|Applications" +msgstr "" + +msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster." +msgstr "" + +msgid "ClusterIntegration|CA Certificate" +msgstr "" + +msgid "ClusterIntegration|Certificate Authority bundle (PEM format)" msgstr "" -msgid "ClusterIntegration|Cluster name" +msgid "ClusterIntegration|Choose how to set up Kubernetes cluster integration" msgstr "" -msgid "ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine. Refresh the page to see cluster's details" +msgid "ClusterIntegration|Choose which of your project's environments will use this Kubernetes cluster." msgstr "" -msgid "ClusterIntegration|Clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" +msgid "ClusterIntegration|Control how your Kubernetes cluster integrates with GitLab" msgstr "" msgid "ClusterIntegration|Copy API URL" @@ -591,37 +723,34 @@ msgstr "" msgid "ClusterIntegration|Copy CA Certificate" msgstr "" -msgid "ClusterIntegration|Copy Token" +msgid "ClusterIntegration|Copy Kubernetes cluster name" msgstr "" -msgid "ClusterIntegration|Copy cluster name" +msgid "ClusterIntegration|Copy Token" msgstr "" -msgid "ClusterIntegration|Create a new cluster on Google Engine right from GitLab" +msgid "ClusterIntegration|Create Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Create cluster" +msgid "ClusterIntegration|Create Kubernetes cluster on Google Kubernetes Engine" msgstr "" -msgid "ClusterIntegration|Create cluster on Google Kubernetes Engine" +msgid "ClusterIntegration|Create a new Kubernetes cluster on Google Kubernetes Engine right from GitLab" msgstr "" msgid "ClusterIntegration|Create on GKE" msgstr "" -msgid "ClusterIntegration|Enable cluster integration" -msgstr "" - msgid "ClusterIntegration|Enter the details for an existing Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Enter the details for your cluster" +msgid "ClusterIntegration|Enter the details for your Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Environment pattern" +msgid "ClusterIntegration|Environment scope" msgstr "" -msgid "ClusterIntegration|GKE pricing" +msgid "ClusterIntegration|GitLab Integration" msgstr "" msgid "ClusterIntegration|GitLab Runner" @@ -639,64 +768,94 @@ msgstr "" msgid "ClusterIntegration|Helm Tiller" msgstr "" -msgid "ClusterIntegration|Inactive" -msgstr "" - msgid "ClusterIntegration|Ingress" msgstr "" msgid "ClusterIntegration|Install" msgstr "" -msgid "ClusterIntegration|Install applications on your cluster. Read more about %{helpLink}" -msgstr "" - msgid "ClusterIntegration|Installed" msgstr "" msgid "ClusterIntegration|Installing" msgstr "" -msgid "ClusterIntegration|Integrate cluster automation" +msgid "ClusterIntegration|Integrate Kubernetes cluster automation" +msgstr "" + +msgid "ClusterIntegration|Integration status" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster details" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is disabled for this project." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project. Disabling this integration will not affect your Kubernetes cluster, it will only temporarily turn off GitLab's connection to it." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster name" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project" msgstr "" msgid "ClusterIntegration|Learn more about %{link_to_documentation}" msgstr "" -msgid "ClusterIntegration|Learn more about Clusters" +msgid "ClusterIntegration|Learn more about Kubernetes" msgstr "" -msgid "ClusterIntegration|Machine type" +msgid "ClusterIntegration|Learn more about environments" msgstr "" -msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create clusters" +msgid "ClusterIntegration|Machine type" msgstr "" -msgid "ClusterIntegration|Manage cluster integration on your GitLab project" +msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters" msgstr "" -msgid "ClusterIntegration|Manage your cluster by visiting %{link_gke}" +msgid "ClusterIntegration|Manage" msgstr "" -msgid "ClusterIntegration|Multiple clusters are available in GitLab Entreprise Edition Premium and Ultimate" +msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}" msgstr "" -msgid "ClusterIntegration|Note:" +msgid "ClusterIntegration|More information" msgstr "" -msgid "ClusterIntegration|Number of nodes" +msgid "ClusterIntegration|Multiple Kubernetes clusters are available in GitLab Enterprise Edition Premium and Ultimate" msgstr "" -msgid "ClusterIntegration|Please enter access information for your cluster. If you need help, you can read our %{link_to_help_page} on clusters" +msgid "ClusterIntegration|Note:" msgstr "" -msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:" +msgid "ClusterIntegration|Number of nodes" msgstr "" -msgid "ClusterIntegration|Problem setting up the cluster" +msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes" msgstr "" -msgid "ClusterIntegration|Problem setting up the clusters list" +msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:" msgstr "" msgid "ClusterIntegration|Project ID" @@ -708,16 +867,19 @@ msgstr "" msgid "ClusterIntegration|Project namespace (optional, unique)" msgstr "" -msgid "ClusterIntegration|Read our %{link_to_help_page} on cluster integration." +msgid "ClusterIntegration|Prometheus" +msgstr "" + +msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration." msgstr "" -msgid "ClusterIntegration|Remove cluster integration" +msgid "ClusterIntegration|Remove Kubernetes cluster integration" msgstr "" msgid "ClusterIntegration|Remove integration" msgstr "" -msgid "ClusterIntegration|Removing cluster integration will remove the cluster configuration you have added to this project. It will not delete your cluster on Google Kubernetes Engine." +msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster." msgstr "" msgid "ClusterIntegration|Request to begin installing failed" @@ -726,7 +888,7 @@ msgstr "" msgid "ClusterIntegration|Save changes" msgstr "" -msgid "ClusterIntegration|See and edit the details for your cluster" +msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster" msgstr "" msgid "ClusterIntegration|See machine types" @@ -747,25 +909,25 @@ msgstr "" msgid "ClusterIntegration|Something went wrong on our end." msgstr "" -msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine" +msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine" msgstr "" msgid "ClusterIntegration|Something went wrong while installing %{title}" msgstr "" -msgid "ClusterIntegration|There are no clusters to show" +msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below" msgstr "" -msgid "ClusterIntegration|This account must have permissions to create a cluster in the %{link_to_container_project} specified below" +msgid "ClusterIntegration|Toggle Kubernetes Cluster" msgstr "" -msgid "ClusterIntegration|Toggle Cluster" +msgid "ClusterIntegration|Toggle Kubernetes cluster" msgstr "" msgid "ClusterIntegration|Token" msgstr "" -msgid "ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." +msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." msgstr "" msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}" @@ -777,7 +939,7 @@ msgstr "" msgid "ClusterIntegration|access to Google Kubernetes Engine" msgstr "" -msgid "ClusterIntegration|cluster" +msgid "ClusterIntegration|check the pricing here" msgstr "" msgid "ClusterIntegration|documentation" @@ -795,6 +957,9 @@ msgstr "" msgid "ClusterIntegration|properly configured" msgstr "" +msgid "Collapse" +msgstr "" + msgid "Comments" msgstr "" @@ -812,6 +977,9 @@ msgstr "Времетраене на подаваниÑта в минути за msgid "Commit message" msgstr "Съобщение за подаването" +msgid "Commit statistics for %{ref} %{start_time} - %{end_time}" +msgstr "" + msgid "CommitBoxTitle|Commit" msgstr "Подаване" @@ -824,15 +992,57 @@ msgstr "ПодаваниÑ" msgid "Commits feed" msgstr "Поток от подаваниÑ" +msgid "Commits per day hour (UTC)" +msgstr "" + +msgid "Commits per day of month" +msgstr "" + +msgid "Commits per weekday" +msgstr "" + +msgid "Commits|An error occurred while fetching merge requests data." +msgstr "" + +msgid "Commits|Commit: %{commitText}" +msgstr "" + msgid "Commits|History" msgstr "ИÑториÑ" +msgid "Commits|No related merge requests found" +msgstr "" + msgid "Committed by" msgstr "Подадено от" msgid "Compare" msgstr "Сравнение" +msgid "Compare Git revisions" +msgstr "" + +msgid "Compare Revisions" +msgstr "" + +msgid "CompareBranches|%{source_branch} and %{target_branch} are the same." +msgstr "" + +msgid "CompareBranches|Compare" +msgstr "" + +msgid "CompareBranches|Source" +msgstr "" + +msgid "CompareBranches|Target" +msgstr "" + +msgid "CompareBranches|There isn't anything to compare." +msgstr "" + +msgid "Confidentiality" +msgstr "" + msgid "Container Registry" msgstr "" @@ -884,6 +1094,9 @@ msgstr "РъководÑтво за ÑътрудничеÑтво" msgid "Contributors" msgstr "Сътрудници" +msgid "ContributorsPage|%{startDate} – %{endDate}" +msgstr "" + msgid "ContributorsPage|Building repository graph." msgstr "" @@ -905,9 +1118,18 @@ msgstr "" msgid "Copy URL to clipboard" msgstr "Копиране на адреÑа в буфера за обмен" +msgid "Copy branch name to clipboard" +msgstr "" + msgid "Copy commit SHA to clipboard" msgstr "Копиране на идентификатора на подаването в буфера за обмен" +msgid "Copy reference to clipboard" +msgstr "" + +msgid "Create" +msgstr "" + msgid "Create New Directory" msgstr "Създаване на нова папка" @@ -926,6 +1148,9 @@ msgstr "" msgid "Create file" msgstr "" +msgid "Create lists from labels. Issues with that label appear in that list." +msgstr "" + msgid "Create merge request" msgstr "Създаване на заÑвка за Ñливане" @@ -938,6 +1163,9 @@ msgstr "" msgid "Create new file" msgstr "" +msgid "Create new label" +msgstr "" + msgid "Create new..." msgstr "Създаване на нов…" @@ -959,6 +1187,9 @@ msgstr "ЧаÑова зона за „Cron“" msgid "Cron syntax" msgstr "СинтакÑÐ¸Ñ Ð½Ð° „Cron“" +msgid "Current node" +msgstr "" + msgid "Custom notification events" msgstr "ПерÑонализирани ÑÑŠÐ±Ð¸Ñ‚Ð¸Ñ Ð·Ð° извеÑÑ‚Ñване" @@ -968,9 +1199,6 @@ msgstr "ПерÑонализираните нива на извеÑÑ‚Ñване msgid "Cycle Analytics" msgstr "Ðнализ на циклите" -msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project." -msgstr "Ðнализът на циклите дава общ поглед върху това колко време е нужно на една Ð¸Ð´ÐµÑ Ð´Ð° Ñе превърне в завършена функционалноÑÑ‚ в проекта." - msgid "CycleAnalyticsStage|Code" msgstr "Програмиране" @@ -1027,12 +1255,21 @@ msgstr "" msgid "Details" msgstr "" +msgid "Diffs|No file name available" +msgstr "" + msgid "Directory name" msgstr "Име на папката" +msgid "Disable" +msgstr "" + msgid "Discard changes" msgstr "" +msgid "Discover GitLab Geo." +msgstr "" + msgid "Dismiss Cycle Analytics introduction box" msgstr "" @@ -1058,7 +1295,7 @@ msgid "Download zip" msgstr "СвалÑне във формат „zip“" msgid "DownloadArtifacts|Download" -msgstr "СвалÑне" +msgstr "СвалÑне на" msgid "DownloadCommit|Email Patches" msgstr "Изпращане на кръпките по е-поща" @@ -1069,15 +1306,24 @@ msgstr "Обикновен файл Ñ Ñ€Ð°Ð·Ð»Ð¸ÐºÐ¸" msgid "DownloadSource|Download" msgstr "СвалÑне" +msgid "Due date" +msgstr "" + msgid "Edit" msgstr "Редактиране" msgid "Edit Pipeline Schedule %{id}" msgstr "Редактиране на плана %{id} за Ñхема" +msgid "Edit files in the editor and commit changes here" +msgstr "" + msgid "Emails" msgstr "" +msgid "Enable" +msgstr "" + msgid "Environments|An error occurred while fetching the environments." msgstr "" @@ -1096,9 +1342,6 @@ msgstr "" msgid "Environments|Environments" msgstr "" -msgid "Environments|Environments are places where code gets deployed, such as staging or production." -msgstr "" - msgid "Environments|Job" msgstr "" @@ -1141,9 +1384,33 @@ msgstr "" msgid "Error creating epic" msgstr "" +msgid "Error fetching contributors data." +msgstr "" + +msgid "Error fetching labels." +msgstr "" + +msgid "Error fetching network graph." +msgstr "" + +msgid "Error fetching refs" +msgstr "" + +msgid "Error fetching usage ping data." +msgstr "" + msgid "Error occurred when toggling the notification subscription" msgstr "" +msgid "Error saving label update." +msgstr "" + +msgid "Error updating status for all todos." +msgstr "" + +msgid "Error updating todo status." +msgstr "" + msgid "EventFilterBy|Filter by all" msgstr "" @@ -1171,6 +1438,9 @@ msgstr "Ð’Ñеки меÑец (на 1-во чиÑло, в 4 ч. Ñутринта msgid "Every week (Sundays at 4:00am)" msgstr "Ð’ÑÑка Ñедмица (в неделÑ, в 4 ч. Ñутринта)" +msgid "Expand" +msgstr "" + msgid "Explore projects" msgstr "" @@ -1189,6 +1459,9 @@ msgstr "" msgid "February" msgstr "" +msgid "Fields on this page are now uneditable, you can configure" +msgstr "" + msgid "File name" msgstr "" @@ -1233,10 +1506,10 @@ msgstr "От прилагането на заÑвката за Ñливане д msgid "GPG Keys" msgstr "" -msgid "Geo Nodes" +msgid "Generate a default set of labels" msgstr "" -msgid "GeoNodeSyncStatus|Failed" +msgid "Geo Nodes" msgstr "" msgid "GeoNodeSyncStatus|Node is failing or broken." @@ -1245,16 +1518,100 @@ msgstr "" msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage." msgstr "" -msgid "GeoNodeSyncStatus|Out of sync" +msgid "GeoNodes|Database replication lag:" +msgstr "" + +msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?" +msgstr "" + +msgid "GeoNodes|Does not match the primary storage configuration" +msgstr "" + +msgid "GeoNodes|Failed" +msgstr "" + +msgid "GeoNodes|Full" +msgstr "" + +msgid "GeoNodes|GitLab version does not match the primary node version" +msgstr "" + +msgid "GeoNodes|GitLab version:" +msgstr "" + +msgid "GeoNodes|Health status:" +msgstr "" + +msgid "GeoNodes|Last event ID processed by cursor:" +msgstr "" + +msgid "GeoNodes|Last event ID seen from primary:" +msgstr "" + +msgid "GeoNodes|Loading nodes" +msgstr "" + +msgid "GeoNodes|Local Attachments:" +msgstr "" + +msgid "GeoNodes|Local LFS objects:" +msgstr "" + +msgid "GeoNodes|Local job artifacts:" +msgstr "" + +msgid "GeoNodes|New node" +msgstr "" + +msgid "GeoNodes|Out of sync" +msgstr "" + +msgid "GeoNodes|Replication slot WAL:" +msgstr "" + +msgid "GeoNodes|Replication slots:" +msgstr "" + +msgid "GeoNodes|Repositories:" +msgstr "" + +msgid "GeoNodes|Selective" +msgstr "" + +msgid "GeoNodes|Storage config:" +msgstr "" + +msgid "GeoNodes|Sync settings:" +msgstr "" + +msgid "GeoNodes|Synced" +msgstr "" + +msgid "GeoNodes|Unused slots" +msgstr "" + +msgid "GeoNodes|Used slots" +msgstr "" + +msgid "GeoNodes|Wikis:" +msgstr "" + +msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS." msgstr "" -msgid "GeoNodeSyncStatus|Synced" +msgid "Geo|All projects" msgstr "" msgid "Geo|File sync capacity" msgstr "" -msgid "Geo|Groups to replicate" +msgid "Geo|Groups to synchronize" +msgstr "" + +msgid "Geo|Projects in certain groups" +msgstr "" + +msgid "Geo|Projects in certain storage shards" msgstr "" msgid "Geo|Repository sync capacity" @@ -1263,12 +1620,24 @@ msgstr "" msgid "Geo|Select groups to replicate." msgstr "" +msgid "Geo|Shards to synchronize" +msgstr "" + +msgid "Git revision" +msgstr "" + msgid "Git storage health information has been reset" msgstr "" +msgid "Git version" +msgstr "" + msgid "GitLab Runner section" msgstr "" +msgid "Gitaly Servers" +msgstr "" + msgid "Go to your fork" msgstr "Към Вашето разклонение" @@ -1278,6 +1647,9 @@ msgstr "Разклонение" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it!" +msgstr "" + msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" msgstr "" @@ -1314,7 +1686,7 @@ msgstr "" msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group." msgstr "" -msgid "GroupsTree|Are you sure you want to leave the \"${this.group.fullName}\" group?" +msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?" msgstr "" msgid "GroupsTree|Create a project in this group." @@ -1365,6 +1737,11 @@ msgstr "" msgid "HealthCheck|Unhealthy" msgstr "" +msgid "Hide value" +msgid_plural "Hide values" +msgstr[0] "" +msgstr[1] "" + msgid "History" msgstr "" @@ -1391,6 +1768,12 @@ msgid_plural "Instances" msgstr[0] "" msgstr[1] "" +msgid "Instance does not support multiple Kubernetes clusters" +msgstr "" + +msgid "Interested parties can even contribute by pushing commits if they want to." +msgstr "" + msgid "Internal - The group and any internal projects can be viewed by any logged in user." msgstr "" @@ -1418,6 +1801,9 @@ msgstr "" msgid "Issues" msgstr "" +msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable." +msgstr "" + msgid "Jan" msgstr "" @@ -1436,6 +1822,27 @@ msgstr "" msgid "June" msgstr "" +msgid "Kubernetes" +msgstr "" + +msgid "Kubernetes Cluster" +msgstr "" + +msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}" +msgstr "" + +msgid "Kubernetes cluster integration was not removed." +msgstr "" + +msgid "Kubernetes cluster integration was successfully removed." +msgstr "" + +msgid "Kubernetes cluster was successfully updated." +msgstr "" + +msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page" +msgstr "" + msgid "LFSStatus|Disabled" msgstr "Изключено" @@ -1445,6 +1852,9 @@ msgstr "Включено" msgid "Labels" msgstr "" +msgid "Labels can be applied to issues and merge requests to categorize them." +msgstr "" + msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "ПоÑÐ»ÐµÐ´Ð½Ð¸Ñ %d ден" @@ -1474,6 +1884,9 @@ msgstr "" msgid "LastPushEvent|at" msgstr "" +msgid "Learn more" +msgstr "" + msgid "Learn more in the" msgstr "Ðаучете повече в" @@ -1492,14 +1905,18 @@ msgstr "ÐапуÑкане на проекта" msgid "License" msgstr "" -msgid "Limited to showing %d event at most" -msgid_plural "Limited to showing %d events at most" -msgstr[0] "Ограничено до показване на най-много %d Ñъбитие" -msgstr[1] "Ограничено до показване на най-много %d ÑъбитиÑ" +msgid "Loading the GitLab IDE..." +msgstr "" msgid "Lock" msgstr "" +msgid "Lock %{issuableDisplayName}" +msgstr "" + +msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment." +msgstr "" + msgid "Locked" msgstr "" @@ -1509,12 +1926,21 @@ msgstr "" msgid "Login" msgstr "" +msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos." +msgstr "" + +msgid "Manage labels" +msgstr "" + msgid "Mar" msgstr "" msgid "March" msgstr "" +msgid "Mark done" +msgstr "" + msgid "Maximum git storage failures" msgstr "" @@ -1527,6 +1953,9 @@ msgstr "Медиана" msgid "Members" msgstr "" +msgid "Merge Request" +msgstr "" + msgid "Merge Requests" msgstr "" @@ -1536,9 +1965,30 @@ msgstr "" msgid "Merge request" msgstr "" +msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others" +msgstr "" + +msgid "Merged" +msgstr "" + msgid "Messages" msgstr "" +msgid "Milestone" +msgstr "" + +msgid "Milestones|Delete milestone" +msgstr "" + +msgid "Milestones|Delete milestone %{milestoneTitle}?" +msgstr "" + +msgid "Milestones|Failed to delete milestone %{milestoneTitle}" +msgstr "" + +msgid "Milestones|Milestone %{milestoneTitle} was not found" +msgstr "" + msgid "MissingSSHKeyWarningLink|add an SSH key" msgstr "добавите SSH ключ" @@ -1548,10 +1998,16 @@ msgstr "" msgid "More information is available|here" msgstr "" +msgid "Move" +msgstr "" + +msgid "Move issue" +msgstr "" + msgid "Multiple issue boards" msgstr "" -msgid "New Cluster" +msgid "Name new label" msgstr "" msgid "New Issue" @@ -1559,6 +2015,12 @@ msgid_plural "New Issues" msgstr[0] "Ðов проблем" msgstr[1] "Ðови проблема" +msgid "New Kubernetes Cluster" +msgstr "" + +msgid "New Kubernetes cluster" +msgstr "" + msgid "New Pipeline Schedule" msgstr "Ðов план за Ñхема" @@ -1583,6 +2045,9 @@ msgstr "" msgid "New issue" msgstr "Ðов проблем" +msgid "New label" +msgstr "" + msgid "New merge request" msgstr "Ðова заÑвка за Ñливане" @@ -1601,7 +2066,22 @@ msgstr "" msgid "New tag" msgstr "Ðов етикет" -msgid "No container images stored for this project. Add one by following the instructions above." +msgid "No assignee" +msgstr "" + +msgid "No changes" +msgstr "" + +msgid "No connection could be made to a Gitaly Server, please check your logs!" +msgstr "" + +msgid "No due date" +msgstr "" + +msgid "No estimate or time spent" +msgstr "" + +msgid "No file chosen" msgstr "" msgid "No repository" @@ -1616,9 +2096,15 @@ msgstr "" msgid "None" msgstr "" +msgid "Not allowed to merge" +msgstr "" + msgid "Not available" msgstr "Ðе е налично" +msgid "Not confidential" +msgstr "" + msgid "Not enough data" msgstr "ÐÑма доÑтатъчно данни" @@ -1679,6 +2165,12 @@ msgstr "Ðаблюдение" msgid "Notifications" msgstr "" +msgid "Notifications off" +msgstr "" + +msgid "Notifications on" +msgstr "" + msgid "Nov" msgstr "" @@ -1688,7 +2180,7 @@ msgstr "" msgid "Number of access attempts" msgstr "" -msgid "Number of failures before backing off" +msgid "OK" msgstr "" msgid "Oct" @@ -1703,6 +2195,9 @@ msgstr "Филтър" msgid "Only project members can comment." msgstr "" +msgid "Open" +msgstr "" + msgid "Opened" msgstr "" @@ -1736,9 +2231,6 @@ msgstr "" msgid "Password" msgstr "" -msgid "People without permission will never get a notification and won\\'t be able to comment." -msgstr "" - msgid "Pipeline" msgstr "Схема" @@ -1781,12 +2273,6 @@ msgstr "Ð’Ñички" msgid "PipelineSchedules|Inactive" msgstr "Ðеактивно" -msgid "PipelineSchedules|Input variable key" -msgstr "Въведете ключ за променливата" - -msgid "PipelineSchedules|Input variable value" -msgstr "Въведете ÑтойноÑтта на променливата" - msgid "PipelineSchedules|Next Run" msgstr "Следващо изпълнение" @@ -1796,9 +2282,6 @@ msgstr "Ðищо" msgid "PipelineSchedules|Provide a short description for this pipeline" msgstr "Въведете кратко опиÑание за тази Ñхема" -msgid "PipelineSchedules|Remove variable row" -msgstr "Премахване на реда за променлива" - msgid "PipelineSchedules|Take ownership" msgstr "Поемане на ÑобÑтвеноÑтта" @@ -1826,6 +2309,12 @@ msgstr "" msgid "Pipelines for last year" msgstr "" +msgid "Pipelines|Build with confidence" +msgstr "" + +msgid "Pipelines|Get started with Pipelines" +msgstr "" + msgid "Pipeline|all" msgstr "вÑички" @@ -1838,12 +2327,21 @@ msgstr "Ñ ÐµÑ‚Ð°Ð¿" msgid "Pipeline|with stages" msgstr "Ñ ÐµÑ‚Ð°Ð¿Ð¸" +msgid "Play" +msgstr "" + +msgid "Please <a href=%{link_to_billing} target=\"_blank\" rel=\"noopener noreferrer\">enable billing for one of your projects to be able to create a Kubernetes cluster</a>, then try again." +msgstr "" + msgid "Please solve the reCAPTCHA" msgstr "" msgid "Preferences" msgstr "" +msgid "Primary" +msgstr "" + msgid "Private - Project access must be granted explicitly to each user." msgstr "" @@ -1889,6 +2387,9 @@ msgstr "" msgid "Profiles|your account" msgstr "" +msgid "Programming languages used in this repository" +msgstr "" + msgid "Project '%{project_name}' is in the process of being deleted." msgstr "" @@ -1904,6 +2405,15 @@ msgstr "Проектът „%{project_name}“ беше обновен уÑÐ¿ÐµÑ msgid "Project access must be granted explicitly to each user." msgstr "ДоÑтъпът до проекта Ñ‚Ñ€Ñбва да бъде даван поотделно на вÑеки потребител." +msgid "Project avatar" +msgstr "" + +msgid "Project avatar in repository: %{link}" +msgstr "" + +msgid "Project cache successfully reset." +msgstr "" + msgid "Project details" msgstr "" @@ -1922,6 +2432,21 @@ msgstr "ИзнаÑÑнето на проекта започна. Ще получ msgid "ProjectActivityRSS|Subscribe" msgstr "" +msgid "ProjectCreationLevel|Allowed to create projects" +msgstr "" + +msgid "ProjectCreationLevel|Default project creation protection" +msgstr "" + +msgid "ProjectCreationLevel|Developers + Masters" +msgstr "" + +msgid "ProjectCreationLevel|Masters" +msgstr "" + +msgid "ProjectCreationLevel|No one" +msgstr "" + msgid "ProjectFeature|Disabled" msgstr "Изключено" @@ -1946,15 +2471,9 @@ msgstr "Графика" msgid "ProjectSettings|Contact an admin to change this setting." msgstr "" -msgid "ProjectSettings|Immediately run a pipeline on the default branch" -msgstr "" - msgid "ProjectSettings|Only signed commits can be pushed to this repository." msgstr "" -msgid "ProjectSettings|Problem setting up the CI/CD settings JavaScript" -msgstr "" - msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin." msgstr "" @@ -2018,12 +2537,15 @@ msgstr "" msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/" msgstr "" -msgid "PrometheusService|Prometheus monitoring" +msgid "PrometheusService|Time-series monitoring service" msgstr "" msgid "PrometheusService|View environments" msgstr "" +msgid "Protip:" +msgstr "" + msgid "Public - The group and any public projects can be viewed without any authentication." msgstr "" @@ -2039,6 +2561,9 @@ msgstr "" msgid "PushRule|Committer restriction" msgstr "" +msgid "Quick actions can be used in the issues description and comment boxes." +msgstr "" + msgid "Read more" msgstr "Прочетете повече" @@ -2051,6 +2576,12 @@ msgstr "Клони" msgid "RefSwitcher|Tags" msgstr "Етикети" +msgid "Reference:" +msgstr "" + +msgid "Register / Sign In" +msgstr "" + msgid "Registry" msgstr "" @@ -2075,9 +2606,18 @@ msgstr "Свързани приложени заÑвки за Ñливане" msgid "Remind later" msgstr "ÐапомнÑне по-къÑно" +msgid "Remove" +msgstr "" + +msgid "Remove avatar" +msgstr "" + msgid "Remove project" msgstr "Премахване на проекта" +msgid "Repair authentication" +msgstr "" + msgid "Repository" msgstr "" @@ -2093,6 +2633,11 @@ msgstr "" msgid "Reset runners registration token" msgstr "" +msgid "Reveal value" +msgid_plural "Reveal values" +msgstr[0] "" +msgstr[1] "" + msgid "Revert this commit" msgstr "ОтмÑна на това подаване" @@ -2102,15 +2647,15 @@ msgstr "ОтмÑна на тази заÑвка за Ñливане" msgid "SSH Keys" msgstr "" -msgid "Save" -msgstr "" - msgid "Save changes" msgstr "" msgid "Save pipeline schedule" msgstr "Запазване на плана за Ñхема" +msgid "Save variables" +msgstr "" + msgid "Schedule a new pipeline" msgstr "Създаване на нов план за Ñхема" @@ -2126,38 +2671,59 @@ msgstr "" msgid "Search branches and tags" msgstr "ТърÑете в клоните и етикетите" -msgid "Seconds before reseting failure information" +msgid "Search milestones" +msgstr "" + +msgid "Search project" msgstr "" -msgid "Seconds to wait after a storage failure" +msgid "Search users" +msgstr "" + +msgid "Seconds before reseting failure information" msgstr "" msgid "Seconds to wait for a storage access attempt" msgstr "" +msgid "Secret variables" +msgstr "" + msgid "Select Archive Format" msgstr "Изберете формата на архива" msgid "Select a timezone" msgstr "Изберете чаÑова зона" +msgid "Select assignee" +msgstr "" + +msgid "Select branch/tag" +msgstr "" + msgid "Select target branch" msgstr "Изберете целеви клон" +msgid "Selective synchronization" +msgstr "" + msgid "Sep" msgstr "" msgid "September" msgstr "" +msgid "Server version" +msgstr "" + msgid "Service Templates" msgstr "" msgid "Set a password on your account to pull or push via %{protocol}." msgstr "Задайте парола на акаунта Ñи, за да можете да изтеглÑте и изпращате промени чрез %{protocol}." -msgid "Set up CI" -msgstr "ÐаÑтройка на ÐИ" +msgid "Set up CI/CD" +msgstr "" msgid "Set up Koding" msgstr "ÐаÑтройка на „Koding“" @@ -2171,6 +2737,15 @@ msgstr "зададете парола" msgid "Settings" msgstr "" +msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero." +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset pipeline minutes" +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes" +msgstr "" + msgid "Show parent pages" msgstr "" @@ -2185,9 +2760,6 @@ msgstr[1] "Показване на %d ÑъбитиÑ" msgid "Sidebar|Change weight" msgstr "" -msgid "Sidebar|Edit" -msgstr "" - msgid "Sidebar|No" msgstr "" @@ -2200,18 +2772,30 @@ msgstr "" msgid "Snippets" msgstr "" +msgid "Something went wrong on our end" +msgstr "" + msgid "Something went wrong on our end." msgstr "" +msgid "Something went wrong trying to change the confidentiality of this issue" +msgstr "" + msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}" msgstr "" +msgid "Something went wrong when toggling the button" +msgstr "" + msgid "Something went wrong while fetching the projects." msgstr "" msgid "Something went wrong while fetching the registry list." msgstr "" +msgid "Something went wrong. Please try again." +msgstr "" + msgid "Sort by" msgstr "" @@ -2341,10 +2925,10 @@ msgstr "" msgid "Stopped" msgstr "" -msgid "Subgroups" +msgid "Storage" msgstr "" -msgid "Subscribe" +msgid "Subgroups" msgstr "" msgid "Switch branch/tag" @@ -2442,7 +3026,10 @@ msgstr "" msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project." msgstr "" -msgid "The circuitbreaker backoff threshold should be lower than the failure count threshold" +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project" +msgstr "" + +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project." msgstr "" msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request." @@ -2457,10 +3044,10 @@ msgstr "Връзката на разклонение беше премахнат msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage." msgstr "Етапът на проблемите показва колко е времето от Ñъздаването на проблем до определÑнето на целеви етап на проекта за него, или до добавÑнето му в ÑпиÑък на дъÑката за проблеми. Започнете да добавÑте проблеми, за да видите данните за този етап." -msgid "The number of attempts GitLab will make to access a storage." +msgid "The maximum file size allowed is 200KB." msgstr "" -msgid "The number of failures after which GitLab will start temporarily disabling access to a storage shard on a host" +msgid "The number of attempts GitLab will make to access a storage." msgstr "" msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}." @@ -2469,9 +3056,6 @@ msgstr "" msgid "The phase of the development lifecycle." msgstr "Етапът от цикъла на разработка" -msgid "The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user." -msgstr "Планът за Ñхемата ще изпълнÑва Ñхемите в бъдеще, периодично, за определени клони или етикети. Тези планирани Ñхеми ще наÑледÑÑ‚ ограничениÑта на доÑтъпа до проекта на ÑÐ²ÑŠÑ€Ð·Ð°Ð½Ð¸Ñ Ñ Ñ‚ÑÑ… потребител." - msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit." msgstr "Етапът на планиране показва колко е времето от преходната Ñтъпка до изпращането на първото подаване. Това време ще бъде добавено автоматично Ñлед като изпратите първото Ñи подаване." @@ -2502,19 +3086,46 @@ msgstr "" msgid "The time in seconds GitLab will try to access storage. After this time a timeout error will be raised." msgstr "" +msgid "The time in seconds between storage checks. When a previous check did complete yet, GitLab will skip a check." +msgstr "" + msgid "The time taken by each data entry gathered by that stage." msgstr "Времето, което отнема вÑеки Ð·Ð°Ð¿Ð¸Ñ Ð¾Ñ‚ данни за ÑÑŠÐ¾Ñ‚Ð²ÐµÑ‚Ð½Ð¸Ñ ÐµÑ‚Ð°Ð¿." msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6." msgstr "СтойноÑтта, коÑто Ñе намира в Ñредата на поÑледователноÑтта от наблюдавани данни. Ðапример: медианата на 3, 5 и 9 е 5, а медианата на 3, 5, 7 и 8 е (5+7)/2 = 6." +msgid "There are no issues to show" +msgstr "" + +msgid "There are no merge requests to show" +msgstr "" + msgid "There are problems accessing Git storage: " msgstr "" +msgid "There was an error loading users activity calendar." +msgstr "" + +msgid "There was an error saving your notification settings." +msgstr "" + +msgid "There was an error subscribing to this label." +msgstr "" + +msgid "There was an error when reseting email token." +msgstr "" + +msgid "There was an error when subscribing to this label." +msgstr "" + +msgid "There was an error when unsubscribing from this label." +msgstr "" + msgid "This board\\'s scope is reduced" msgstr "" -msgid "This branch has changed since you started editing. Would you like to create a new branch?" +msgid "This directory" msgstr "" msgid "This is a confidential issue." @@ -2523,18 +3134,45 @@ msgstr "" msgid "This is the author's first Merge Request to this project." msgstr "" +msgid "This issue is confidential" +msgstr "" + msgid "This issue is confidential and locked." msgstr "" msgid "This issue is locked." msgstr "" +msgid "This job depends on a user to trigger its process. Often they are used to deploy code to production environments" +msgstr "" + +msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered" +msgstr "" + +msgid "This job has not been triggered yet" +msgstr "" + +msgid "This job has not started yet" +msgstr "" + +msgid "This job is in pending state and is waiting to be picked by a runner" +msgstr "" + +msgid "This job requires a manual action" +msgstr "" + msgid "This means you can not push code until you create an empty repository or import existing one." msgstr "Това означава, че нÑма да можете да изпращате код, докато не Ñъздадете празно хранилище или не внеÑете ÑъщеÑтвуващо такова." msgid "This merge request is locked." msgstr "" +msgid "This project" +msgstr "" + +msgid "This repository" +msgstr "" + msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here." msgstr "" @@ -2547,9 +3185,21 @@ msgstr "Време преди работата по проблем да запо msgid "Time between merge request creation and merge/close" msgstr "Време между Ñъздаване на заÑвка за Ñливане и прилагането/отхвърлÑнето Ñ" +msgid "Time tracking" +msgstr "" + msgid "Time until first merge request" msgstr "Време преди първата заÑвка за Ñливане" +msgid "TimeTrackingEstimated|Est" +msgstr "" + +msgid "TimeTracking|Estimated:" +msgstr "" + +msgid "TimeTracking|Spent" +msgstr "" + msgid "Timeago|%s days ago" msgstr "преди %s дни" @@ -2689,6 +3339,18 @@ msgstr "Ñек" msgid "Title" msgstr "" +msgid "Todo" +msgstr "" + +msgid "Toggle sidebar" +msgstr "" + +msgid "ToggleButton|Toggle Status: OFF" +msgstr "" + +msgid "ToggleButton|Toggle Status: ON" +msgstr "" + msgid "Total Time" msgstr "Общо време" @@ -2704,19 +3366,40 @@ msgstr "" msgid "Track groups of issues that share a theme, across projects and milestones" msgstr "" +msgid "Total: %{total}" +msgstr "" + +msgid "Track time with quick actions" +msgstr "" + +msgid "Trigger this manual action" +msgstr "" + msgid "Turn on Service Desk" msgstr "" +msgid "Type %{value} to confirm:" +msgstr "" + +msgid "Unable to reset project cache." +msgstr "" + +msgid "Unknown" +msgstr "" + msgid "Unlock" msgstr "" +msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment." +msgstr "" + msgid "Unlocked" msgstr "" msgid "Unstar" msgstr "Без звезда" -msgid "Unsubscribe" +msgid "Up to date" msgstr "" msgid "Upgrade your plan to activate Advanced Global Search." @@ -2740,6 +3423,9 @@ msgstr "Качване на нов файл" msgid "Upload file" msgstr "Качване на файл" +msgid "Upload new avatar" +msgstr "" + msgid "UploadLink|click to upload" msgstr "щракнете за качване" @@ -2752,9 +3438,15 @@ msgstr "" msgid "Use your global notification setting" msgstr "Използване на глобалната Ви наÑтройка за извеÑтиÑта" +msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." +msgstr "" + msgid "View file @ " msgstr "" +msgid "View labels" +msgstr "" + msgid "View open merge request" msgstr "Преглед на отворената заÑвка за Ñливане" @@ -2776,6 +3468,9 @@ msgstr "ÐеизвеÑтно" msgid "Want to see the data? Please ask an administrator for access." msgstr "ИÑкате ли да видите данните? Помолете админиÑтратор за доÑтъп." +msgid "We could not verify that one of your projects on GCP has billing enabled. Please try again." +msgstr "" + msgid "We don't have enough data to show this stage." msgstr "ÐÑма доÑтатъчно данни за този етап." @@ -2788,9 +3483,6 @@ msgstr "" msgid "Weight" msgstr "" -msgid "When access to a storage fails. GitLab will prevent access to the storage for the time specified here. This allows the filesystem to recover. Repositories on failing shards are temporarly unavailable" -msgstr "" - msgid "Wiki" msgstr "" @@ -2809,6 +3501,12 @@ msgstr "" msgid "WikiClone|Start Gollum and edit locally" msgstr "" +msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title." +msgstr "" + +msgid "WikiEdit|There is already a page with the same title in that path." +msgstr "" + msgid "WikiEmptyPageError|You are not allowed to create wiki pages" msgstr "" @@ -2911,9 +3609,21 @@ msgstr "Ðа път Ñте да премахнете връзката на раРmsgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" msgstr "Ðа път Ñте да прехвърлите „%{project_name_with_namespace}“ към друг ÑобÑтвеник. ÐÐИСТИÐРли иÑкате това?" +msgid "You can also star a label to make it a priority label." +msgstr "" + +msgid "You can move around the graph by using the arrow keys." +msgstr "" + +msgid "You can move around the graph by using the arrow keys." +msgstr "" + msgid "You can only add files when you are on a branch" msgstr "Можете да добавÑте файлове Ñамо когато Ñе намирате в клон" +msgid "You can only edit files when you are on a branch" +msgstr "" + msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead." msgstr "" @@ -2953,6 +3663,12 @@ msgstr "ÐÑма да можете да изтеглÑте или изпраща msgid "You won't be able to pull or push project code via SSH until you add an SSH key to your profile" msgstr "" +msgid "You'll need to use different branch names to get a valid comparison." +msgstr "" + +msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure" +msgstr "" + msgid "Your comment will not be visible to the public." msgstr "" @@ -2965,26 +3681,220 @@ msgstr "Вашето име" msgid "Your projects" msgstr "" +msgid "assign yourself" +msgstr "" + msgid "branch name" msgstr "" msgid "by" msgstr "" +msgid "ciReport|Code quality" +msgstr "" + +msgid "ciReport|DAST detected no alerts by analyzing the review app" +msgstr "" + +msgid "ciReport|Failed to load ${type} report" +msgstr "" + +msgid "ciReport|Fixed:" +msgstr "" + +msgid "ciReport|Instances" +msgstr "" + +msgid "ciReport|Learn more about whitelisting" +msgstr "" + +msgid "ciReport|Loading ${type} report" +msgstr "" + +msgid "ciReport|No changes to code quality" +msgstr "" + +msgid "ciReport|No changes to performance metrics" +msgstr "" + +msgid "ciReport|Performance metrics" +msgstr "" + +msgid "ciReport|SAST" +msgstr "" + +msgid "ciReport|SAST detected no security vulnerabilities" +msgstr "" + +msgid "ciReport|SAST:container no vulnerabilities were found" +msgstr "" + +msgid "ciReport|Show complete code vulnerabilities report" +msgstr "" + +msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}" +msgstr "" + msgid "commit" msgstr "" +msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue." +msgstr "" + +msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue." +msgstr "" + msgid "day" msgid_plural "days" msgstr[0] "ден" msgstr[1] "дни" +msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command." +msgstr "" + +msgid "merge request" +msgid_plural "merge requests" +msgstr[0] "" +msgstr[1] "" + +msgid "mrWidget|Cancel automatic merge" +msgstr "" + +msgid "mrWidget|Check out branch" +msgstr "" + +msgid "mrWidget|Checking ability to merge automatically" +msgstr "" + +msgid "mrWidget|Cherry-pick" +msgstr "" + +msgid "mrWidget|Cherry-pick this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Closed" +msgstr "" + +msgid "mrWidget|Closed by" +msgstr "" + +msgid "mrWidget|Closes" +msgstr "" + +msgid "mrWidget|Did not close" +msgstr "" + +msgid "mrWidget|Email patches" +msgstr "" + +msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|Mentions" +msgstr "" + +msgid "mrWidget|Merge" +msgstr "" + +msgid "mrWidget|Merge failed." +msgstr "" + +msgid "mrWidget|Merge locally" +msgstr "" + +msgid "mrWidget|Merged by" +msgstr "" + +msgid "mrWidget|Plain diff" +msgstr "" + +msgid "mrWidget|Refresh" +msgstr "" + +msgid "mrWidget|Refresh now" +msgstr "" + +msgid "mrWidget|Refreshing now" +msgstr "" + +msgid "mrWidget|Remove Source Branch" +msgstr "" + +msgid "mrWidget|Remove source branch" +msgstr "" + +msgid "mrWidget|Request to merge" +msgstr "" + +msgid "mrWidget|Resolve conflicts" +msgstr "" + +msgid "mrWidget|Revert" +msgstr "" + +msgid "mrWidget|Revert this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Set by" +msgstr "" + +msgid "mrWidget|The changes were merged into" +msgstr "" + +msgid "mrWidget|The changes were not merged into" +msgstr "" + +msgid "mrWidget|The changes will be merged into" +msgstr "" + +msgid "mrWidget|The source branch has been removed" +msgstr "" + +msgid "mrWidget|The source branch is being removed" +msgstr "" + +msgid "mrWidget|The source branch will be removed" +msgstr "" + +msgid "mrWidget|The source branch will not be removed" +msgstr "" + +msgid "mrWidget|There are merge conflicts" +msgstr "" + +msgid "mrWidget|This merge request failed to be merged automatically" +msgstr "" + +msgid "mrWidget|This merge request is in the process of being merged" +msgstr "" + +msgid "mrWidget|This project is archived, write access has been disabled" +msgstr "" + +msgid "mrWidget|You can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|You can remove source branch now" +msgstr "" + +msgid "mrWidget|command line" +msgstr "" + +msgid "mrWidget|into" +msgstr "" + +msgid "mrWidget|to be merged automatically when the pipeline succeeds" +msgstr "" + msgid "new merge request" msgstr "нова заÑвка за Ñливане" msgid "notification emails" msgstr "извеÑÑ‚Ð¸Ñ Ð¿Ð¾ е-поща" +msgid "or" +msgstr "" + msgid "parent" msgid_plural "parents" msgstr[0] "родител" @@ -2996,12 +3906,21 @@ msgstr "" msgid "personal access token" msgstr "" +msgid "remove due date" +msgstr "" + msgid "source" msgstr "" +msgid "spendCommand|%{slash_command} will update the sum of the time spent." +msgstr "" + msgid "to help your contributors communicate effectively!" msgstr "" msgid "username" msgstr "" +msgid "uses Kubernetes clusters to deploy your code!" +msgstr "" + diff --git a/locale/de/gitlab.po b/locale/de/gitlab.po index db7f41c5476..4a0ca1e7efb 100644 --- a/locale/de/gitlab.po +++ b/locale/de/gitlab.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab-ee\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-12-12 18:31+0000\n" -"PO-Revision-Date: 2018-01-05 04:41-0500\n" +"POT-Creation-Date: 2018-02-07 11:38-0600\n" +"PO-Revision-Date: 2018-02-12 04:00-0500\n" "Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n" "Language-Team: German\n" "Language: de_DE\n" @@ -16,23 +16,41 @@ msgstr "" "X-Crowdin-Language: de\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" +msgid " and" +msgstr "" + msgid "%d commit" msgid_plural "%d commits" msgstr[0] "%d Commit" msgstr[1] "%d Commits" +msgid "%d commit behind" +msgid_plural "%d commits behind" +msgstr[0] "" +msgstr[1] "" + +msgid "%d issue" +msgid_plural "%d issues" +msgstr[0] "" +msgstr[1] "" + msgid "%d layer" msgid_plural "%d layers" msgstr[0] "" msgstr[1] "" +msgid "%d merge request" +msgid_plural "%d merge requests" +msgstr[0] "" +msgstr[1] "" + msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "%s additional commits have been omitted to prevent performance issues." msgstr[0] "%s zusätzlicher Commit wurde ausgelassen um Leistungsprobleme zu verhindern." msgstr[1] "%s zusätzliche Commits wurden ausgelassen um Leistungsprobleme zu verhindern." -msgid "%{commit_author_link} committed %{commit_timeago}" -msgstr "%{commit_author_link} hat %{commit_timeago} committet" +msgid "%{commit_author_link} authored %{commit_timeago}" +msgstr "" msgid "%{count} participant" msgid_plural "%{count} participants" @@ -45,9 +63,6 @@ msgstr "" msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will allow access on the next attempt." msgstr "%{number_of_failures} von %{maximum_failures} Fehlschlägen. GitLab wird den Zugriff beim nächsten Versuch zulassen." -msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will block access for %{number_of_seconds} seconds." -msgstr "%{number_of_failures} von %{maximum_failures} Fehlschlägen. GitLab wird den Zugriff für %{number_of_seconds} Sekunden blockieren." - msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved." msgstr "%{number_of_failures} von %{maximum_failures} Fehlschlägen. GitLab wird es nicht weiter versuchen. Setze die Speicherinformation nach Behebung des Problems zurück." @@ -121,24 +136,81 @@ msgstr "Lizenz hinzufügen" msgid "Add new directory" msgstr "Erstelle eine neues Verzeichnis" +msgid "Add todo" +msgstr "" + +msgid "AdminArea|Stop all jobs" +msgstr "" + +msgid "AdminArea|Stop all jobs?" +msgstr "" + +msgid "AdminArea|Stop jobs" +msgstr "" + +msgid "AdminArea|Stopping jobs failed" +msgstr "" + +msgid "AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running." +msgstr "" + msgid "AdminHealthPageLink|health page" msgstr "" +msgid "Advanced" +msgstr "" + msgid "Advanced settings" msgstr "" msgid "All" msgstr "Alle" +msgid "All changes are committed" +msgstr "" + +msgid "Allows you to add and manage Kubernetes clusters." +msgstr "" + +msgid "An error occurred previewing the blob" +msgstr "" + msgid "An error occurred when toggling the notification subscription" msgstr "" msgid "An error occurred when updating the issue weight" msgstr "" +msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." +msgstr "" + +msgid "An error occurred while fetching markdown preview" +msgstr "" + msgid "An error occurred while fetching sidebar data" msgstr "" +msgid "An error occurred while getting projects" +msgstr "" + +msgid "An error occurred while loading filenames" +msgstr "" + +msgid "An error occurred while rendering KaTeX" +msgstr "" + +msgid "An error occurred while rendering preview broadcast message" +msgstr "" + +msgid "An error occurred while retrieving calendar activity" +msgstr "" + +msgid "An error occurred while retrieving diff" +msgstr "" + +msgid "An error occurred while validating username" +msgstr "" + msgid "An error occurred. Please try again." msgstr "" @@ -163,9 +235,6 @@ msgstr "Bist Du sicher, dass Du diesen Pipeline-Zeitplan löschen möchtest?" msgid "Are you sure you want to discard your changes?" msgstr "Bist Du sicher, dass Du alle Änderungen zurücksetzen willst?" -msgid "Are you sure you want to leave this group?" -msgstr "" - msgid "Are you sure you want to reset registration token?" msgstr "Bist Du sicher, dass Du den Registrierungstoken zurücksetzen willst?" @@ -178,6 +247,21 @@ msgstr "Bist Du sicher?" msgid "Artifacts" msgstr "" +msgid "Assign custom color like #FF0000" +msgstr "" + +msgid "Assign labels" +msgstr "" + +msgid "Assign milestone" +msgstr "" + +msgid "Assign to" +msgstr "" + +msgid "Assignee" +msgstr "" + msgid "Attach a file by drag & drop or %{upload_link}" msgstr "Datei mittels Drag & Drop oder %{upload_link} hinzufügen" @@ -193,13 +277,16 @@ msgstr "" msgid "Author" msgstr "" -msgid "Auto Review Apps and Auto Deploy need a domain name and the %{kubernetes} to work correctly." +msgid "Authors: %{authors}" msgstr "" -msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly." +msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." msgstr "" -msgid "Auto Review Apps and Auto Deploy need the %{kubernetes} to work correctly." +msgid "Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly." +msgstr "" + +msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly." msgstr "" msgid "AutoDevOps|Auto DevOps (Beta)" @@ -223,6 +310,12 @@ msgstr "" msgid "Available" msgstr "" +msgid "Avatar will be removed. Are you sure?" +msgstr "" + +msgid "Average per day: %{average}" +msgstr "" + msgid "Billing" msgstr "" @@ -277,6 +370,9 @@ msgstr "" msgid "BillingPlans|per user" msgstr "" +msgid "Begin with the selected commit" +msgstr "" + msgid "Branch" msgid_plural "Branches" msgstr[0] "Zweig" @@ -405,8 +501,8 @@ msgstr "von" msgid "CI / CD" msgstr "CI / CD" -msgid "CI configuration" -msgstr "CI-Konfiguration" +msgid "CI/CD configuration" +msgstr "" msgid "CICD|Jobs" msgstr "" @@ -417,6 +513,9 @@ msgstr "Abbrechen" msgid "Cancel edit" msgstr "Bearbeitung abbrechen" +msgid "Cannot modify managed Kubernetes cluster" +msgstr "" + msgid "Change Weight" msgstr "" @@ -432,15 +531,24 @@ msgstr "Herauspicken" msgid "ChangeTypeAction|Revert" msgstr "Wiederherstellen " +msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes." +msgstr "" + msgid "Changelog" msgstr "Änderungsliste " +msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision." +msgstr "" + msgid "Charts" msgstr "Diagramme" msgid "Chat" msgstr "Chat" +msgid "Check interval" +msgstr "" + msgid "Checking %{text} availability…" msgstr "" @@ -453,7 +561,19 @@ msgstr "Diesen Commit herauspicken " msgid "Cherry-pick this merge request" msgstr "Diesen Merge Request herauspicken" -msgid "Choose which groups you wish to replicate to this secondary node. Leave blank to replicate all." +msgid "Choose File ..." +msgstr "" + +msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request." +msgstr "" + +msgid "Choose file..." +msgstr "" + +msgid "Choose which groups you wish to synchronize to this secondary node." +msgstr "" + +msgid "Choose which shards you wish to synchronize to this secondary node." msgstr "" msgid "CiStatusLabel|canceled" @@ -510,79 +630,91 @@ msgstr "übersprungen" msgid "CiStatus|running" msgstr "laufend" -msgid "CircuitBreakerApiLink|circuitbreaker api" +msgid "CiVariables|Input variable key" msgstr "" -msgid "Clone repository" +msgid "CiVariables|Input variable value" msgstr "" -msgid "Close" +msgid "CiVariables|Remove variable row" msgstr "" -msgid "Cluster" +msgid "CiVariable|* (All environments)" msgstr "" -msgid "ClusterIntegration|%{appList} was successfully installed on your cluster" +msgid "CiVariable|All environments" msgstr "" -msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which incur additional costs. See %{pricingLink}" +msgid "CiVariable|Create wildcard" msgstr "" -msgid "ClusterIntegration|API URL" +msgid "CiVariable|Error occured while saving variables" msgstr "" -msgid "ClusterIntegration|Active" +msgid "CiVariable|New environment" msgstr "" -msgid "ClusterIntegration|Add an existing cluster" +msgid "CiVariable|Protected" msgstr "" -msgid "ClusterIntegration|Add cluster" +msgid "CiVariable|Search environments" msgstr "" -msgid "ClusterIntegration|All" +msgid "CiVariable|Toggle protected" msgstr "" -msgid "ClusterIntegration|Applications" +msgid "CiVariable|Validation failed" msgstr "" -msgid "ClusterIntegration|CA Certificate" +msgid "CircuitBreakerApiLink|circuitbreaker api" msgstr "" -msgid "ClusterIntegration|Certificate Authority bundle (PEM format)" +msgid "Click to expand text" msgstr "" -msgid "ClusterIntegration|Choose how to set up cluster integration" +msgid "Clone repository" msgstr "" -msgid "ClusterIntegration|Cluster" +msgid "Close" msgstr "" -msgid "ClusterIntegration|Cluster details" +msgid "Closed" msgstr "" -msgid "ClusterIntegration|Cluster integration" +msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Cluster integration is disabled for this project." +msgid "ClusterIntegration|API URL" msgstr "" -msgid "ClusterIntegration|Cluster integration is enabled for this project." +msgid "ClusterIntegration|Add Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab's connection to it." +msgid "ClusterIntegration|Add an existing Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Cluster is being created on Google Kubernetes Engine..." +msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration" +msgstr "" + +msgid "ClusterIntegration|Applications" +msgstr "" + +msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster." +msgstr "" + +msgid "ClusterIntegration|CA Certificate" +msgstr "" + +msgid "ClusterIntegration|Certificate Authority bundle (PEM format)" msgstr "" -msgid "ClusterIntegration|Cluster name" +msgid "ClusterIntegration|Choose how to set up Kubernetes cluster integration" msgstr "" -msgid "ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine. Refresh the page to see cluster's details" +msgid "ClusterIntegration|Choose which of your project's environments will use this Kubernetes cluster." msgstr "" -msgid "ClusterIntegration|Clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" +msgid "ClusterIntegration|Control how your Kubernetes cluster integrates with GitLab" msgstr "" msgid "ClusterIntegration|Copy API URL" @@ -591,37 +723,34 @@ msgstr "" msgid "ClusterIntegration|Copy CA Certificate" msgstr "" -msgid "ClusterIntegration|Copy Token" +msgid "ClusterIntegration|Copy Kubernetes cluster name" msgstr "" -msgid "ClusterIntegration|Copy cluster name" +msgid "ClusterIntegration|Copy Token" msgstr "" -msgid "ClusterIntegration|Create a new cluster on Google Engine right from GitLab" +msgid "ClusterIntegration|Create Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Create cluster" +msgid "ClusterIntegration|Create Kubernetes cluster on Google Kubernetes Engine" msgstr "" -msgid "ClusterIntegration|Create cluster on Google Kubernetes Engine" +msgid "ClusterIntegration|Create a new Kubernetes cluster on Google Kubernetes Engine right from GitLab" msgstr "" msgid "ClusterIntegration|Create on GKE" msgstr "" -msgid "ClusterIntegration|Enable cluster integration" -msgstr "" - msgid "ClusterIntegration|Enter the details for an existing Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Enter the details for your cluster" +msgid "ClusterIntegration|Enter the details for your Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Environment pattern" +msgid "ClusterIntegration|Environment scope" msgstr "" -msgid "ClusterIntegration|GKE pricing" +msgid "ClusterIntegration|GitLab Integration" msgstr "" msgid "ClusterIntegration|GitLab Runner" @@ -639,64 +768,94 @@ msgstr "" msgid "ClusterIntegration|Helm Tiller" msgstr "" -msgid "ClusterIntegration|Inactive" -msgstr "" - msgid "ClusterIntegration|Ingress" msgstr "" msgid "ClusterIntegration|Install" msgstr "" -msgid "ClusterIntegration|Install applications on your cluster. Read more about %{helpLink}" -msgstr "" - msgid "ClusterIntegration|Installed" msgstr "" msgid "ClusterIntegration|Installing" msgstr "" -msgid "ClusterIntegration|Integrate cluster automation" +msgid "ClusterIntegration|Integrate Kubernetes cluster automation" +msgstr "" + +msgid "ClusterIntegration|Integration status" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster details" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is disabled for this project." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project. Disabling this integration will not affect your Kubernetes cluster, it will only temporarily turn off GitLab's connection to it." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster name" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project" msgstr "" msgid "ClusterIntegration|Learn more about %{link_to_documentation}" msgstr "" -msgid "ClusterIntegration|Learn more about Clusters" +msgid "ClusterIntegration|Learn more about Kubernetes" msgstr "" -msgid "ClusterIntegration|Machine type" +msgid "ClusterIntegration|Learn more about environments" msgstr "" -msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create clusters" +msgid "ClusterIntegration|Machine type" msgstr "" -msgid "ClusterIntegration|Manage cluster integration on your GitLab project" +msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters" msgstr "" -msgid "ClusterIntegration|Manage your cluster by visiting %{link_gke}" +msgid "ClusterIntegration|Manage" msgstr "" -msgid "ClusterIntegration|Multiple clusters are available in GitLab Entreprise Edition Premium and Ultimate" +msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}" msgstr "" -msgid "ClusterIntegration|Note:" +msgid "ClusterIntegration|More information" msgstr "" -msgid "ClusterIntegration|Number of nodes" +msgid "ClusterIntegration|Multiple Kubernetes clusters are available in GitLab Enterprise Edition Premium and Ultimate" msgstr "" -msgid "ClusterIntegration|Please enter access information for your cluster. If you need help, you can read our %{link_to_help_page} on clusters" +msgid "ClusterIntegration|Note:" msgstr "" -msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:" +msgid "ClusterIntegration|Number of nodes" msgstr "" -msgid "ClusterIntegration|Problem setting up the cluster" +msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes" msgstr "" -msgid "ClusterIntegration|Problem setting up the clusters list" +msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:" msgstr "" msgid "ClusterIntegration|Project ID" @@ -708,16 +867,19 @@ msgstr "" msgid "ClusterIntegration|Project namespace (optional, unique)" msgstr "" -msgid "ClusterIntegration|Read our %{link_to_help_page} on cluster integration." +msgid "ClusterIntegration|Prometheus" +msgstr "" + +msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration." msgstr "" -msgid "ClusterIntegration|Remove cluster integration" +msgid "ClusterIntegration|Remove Kubernetes cluster integration" msgstr "" msgid "ClusterIntegration|Remove integration" msgstr "" -msgid "ClusterIntegration|Removing cluster integration will remove the cluster configuration you have added to this project. It will not delete your cluster on Google Kubernetes Engine." +msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster." msgstr "" msgid "ClusterIntegration|Request to begin installing failed" @@ -726,7 +888,7 @@ msgstr "" msgid "ClusterIntegration|Save changes" msgstr "" -msgid "ClusterIntegration|See and edit the details for your cluster" +msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster" msgstr "" msgid "ClusterIntegration|See machine types" @@ -747,25 +909,25 @@ msgstr "" msgid "ClusterIntegration|Something went wrong on our end." msgstr "" -msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine" +msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine" msgstr "" msgid "ClusterIntegration|Something went wrong while installing %{title}" msgstr "" -msgid "ClusterIntegration|There are no clusters to show" +msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below" msgstr "" -msgid "ClusterIntegration|This account must have permissions to create a cluster in the %{link_to_container_project} specified below" +msgid "ClusterIntegration|Toggle Kubernetes Cluster" msgstr "" -msgid "ClusterIntegration|Toggle Cluster" +msgid "ClusterIntegration|Toggle Kubernetes cluster" msgstr "" msgid "ClusterIntegration|Token" msgstr "" -msgid "ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." +msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." msgstr "" msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}" @@ -777,7 +939,7 @@ msgstr "" msgid "ClusterIntegration|access to Google Kubernetes Engine" msgstr "" -msgid "ClusterIntegration|cluster" +msgid "ClusterIntegration|check the pricing here" msgstr "" msgid "ClusterIntegration|documentation" @@ -795,6 +957,9 @@ msgstr "" msgid "ClusterIntegration|properly configured" msgstr "" +msgid "Collapse" +msgstr "" + msgid "Comments" msgstr "Kommentare" @@ -812,6 +977,9 @@ msgstr "Dauer der Commits in Minuten für die letzten 30 Commits" msgid "Commit message" msgstr "Commit Nachricht" +msgid "Commit statistics for %{ref} %{start_time} - %{end_time}" +msgstr "" + msgid "CommitBoxTitle|Commit" msgstr "Commit" @@ -824,15 +992,57 @@ msgstr "Commits" msgid "Commits feed" msgstr "Liste der Commits" +msgid "Commits per day hour (UTC)" +msgstr "" + +msgid "Commits per day of month" +msgstr "" + +msgid "Commits per weekday" +msgstr "" + +msgid "Commits|An error occurred while fetching merge requests data." +msgstr "" + +msgid "Commits|Commit: %{commitText}" +msgstr "" + msgid "Commits|History" msgstr "Verlauf" +msgid "Commits|No related merge requests found" +msgstr "" + msgid "Committed by" msgstr "Committed von" msgid "Compare" msgstr "Vergleichen" +msgid "Compare Git revisions" +msgstr "" + +msgid "Compare Revisions" +msgstr "" + +msgid "CompareBranches|%{source_branch} and %{target_branch} are the same." +msgstr "" + +msgid "CompareBranches|Compare" +msgstr "" + +msgid "CompareBranches|Source" +msgstr "" + +msgid "CompareBranches|Target" +msgstr "" + +msgid "CompareBranches|There isn't anything to compare." +msgstr "" + +msgid "Confidentiality" +msgstr "" + msgid "Container Registry" msgstr "" @@ -884,6 +1094,9 @@ msgstr "Mitarbeitsanleitung" msgid "Contributors" msgstr "Mitarbeiter" +msgid "ContributorsPage|%{startDate} – %{endDate}" +msgstr "" + msgid "ContributorsPage|Building repository graph." msgstr "" @@ -905,9 +1118,18 @@ msgstr "" msgid "Copy URL to clipboard" msgstr "Kopiere URL in die Zwischenablage" +msgid "Copy branch name to clipboard" +msgstr "" + msgid "Copy commit SHA to clipboard" msgstr "Kopiere Commit SHA in die Zwischenablage" +msgid "Copy reference to clipboard" +msgstr "" + +msgid "Create" +msgstr "" + msgid "Create New Directory" msgstr "Erstelle neues Verzeichnis" @@ -926,6 +1148,9 @@ msgstr "" msgid "Create file" msgstr "" +msgid "Create lists from labels. Issues with that label appear in that list." +msgstr "" + msgid "Create merge request" msgstr "Erstelle Merge Request" @@ -938,6 +1163,9 @@ msgstr "" msgid "Create new file" msgstr "" +msgid "Create new label" +msgstr "" + msgid "Create new..." msgstr "Erstelle neues..." @@ -959,6 +1187,9 @@ msgstr "Cron Zeitzone" msgid "Cron syntax" msgstr "Cron Syntax" +msgid "Current node" +msgstr "" + msgid "Custom notification events" msgstr "Individuelle Benachrichtigungsereignisse" @@ -968,9 +1199,6 @@ msgstr "Individuelle Benachrichtigungsstufen sind identisch mit den Beteiligungs msgid "Cycle Analytics" msgstr "Arbeitsablaufsanalysen" -msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project." -msgstr "Arbeitsablaufsanalysen verschaffen einen Ãœberblick, welche Zeit Dein Projekt von der Idee zur Realisierung benötigt." - msgid "CycleAnalyticsStage|Code" msgstr "Entwicklung" @@ -1027,12 +1255,21 @@ msgstr "" msgid "Details" msgstr "Details" +msgid "Diffs|No file name available" +msgstr "" + msgid "Directory name" msgstr "Verzeichnisname" +msgid "Disable" +msgstr "" + msgid "Discard changes" msgstr "Änderungen verwerfen" +msgid "Discover GitLab Geo." +msgstr "" + msgid "Dismiss Cycle Analytics introduction box" msgstr "" @@ -1069,15 +1306,24 @@ msgstr "Unterschiede" msgid "DownloadSource|Download" msgstr "Herunterladen" +msgid "Due date" +msgstr "" + msgid "Edit" msgstr "Bearbeiten" msgid "Edit Pipeline Schedule %{id}" msgstr "Pipeline Zeitplan bearbeiten %{id}" +msgid "Edit files in the editor and commit changes here" +msgstr "" + msgid "Emails" msgstr "E-Mails" +msgid "Enable" +msgstr "" + msgid "Environments|An error occurred while fetching the environments." msgstr "" @@ -1096,9 +1342,6 @@ msgstr "" msgid "Environments|Environments" msgstr "" -msgid "Environments|Environments are places where code gets deployed, such as staging or production." -msgstr "" - msgid "Environments|Job" msgstr "" @@ -1141,9 +1384,33 @@ msgstr "" msgid "Error creating epic" msgstr "" +msgid "Error fetching contributors data." +msgstr "" + +msgid "Error fetching labels." +msgstr "" + +msgid "Error fetching network graph." +msgstr "" + +msgid "Error fetching refs" +msgstr "" + +msgid "Error fetching usage ping data." +msgstr "" + msgid "Error occurred when toggling the notification subscription" msgstr "" +msgid "Error saving label update." +msgstr "" + +msgid "Error updating status for all todos." +msgstr "" + +msgid "Error updating todo status." +msgstr "" + msgid "EventFilterBy|Filter by all" msgstr "Filtere alle" @@ -1171,6 +1438,9 @@ msgstr "Monatlich (am Ersten um 4:00 Uhr)" msgid "Every week (Sundays at 4:00am)" msgstr "Wöchentlich (Sonntags um 4:00 Uhr)" +msgid "Expand" +msgstr "" + msgid "Explore projects" msgstr "" @@ -1189,6 +1459,9 @@ msgstr "" msgid "February" msgstr "" +msgid "Fields on this page are now uneditable, you can configure" +msgstr "" + msgid "File name" msgstr "" @@ -1233,10 +1506,10 @@ msgstr "Vom Umsetzen des Merge Request bis zur Bereitstellung auf dem Produktivs msgid "GPG Keys" msgstr "" -msgid "Geo Nodes" +msgid "Generate a default set of labels" msgstr "" -msgid "GeoNodeSyncStatus|Failed" +msgid "Geo Nodes" msgstr "" msgid "GeoNodeSyncStatus|Node is failing or broken." @@ -1245,16 +1518,100 @@ msgstr "" msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage." msgstr "" -msgid "GeoNodeSyncStatus|Out of sync" +msgid "GeoNodes|Database replication lag:" +msgstr "" + +msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?" +msgstr "" + +msgid "GeoNodes|Does not match the primary storage configuration" +msgstr "" + +msgid "GeoNodes|Failed" +msgstr "" + +msgid "GeoNodes|Full" +msgstr "" + +msgid "GeoNodes|GitLab version does not match the primary node version" +msgstr "" + +msgid "GeoNodes|GitLab version:" +msgstr "" + +msgid "GeoNodes|Health status:" +msgstr "" + +msgid "GeoNodes|Last event ID processed by cursor:" +msgstr "" + +msgid "GeoNodes|Last event ID seen from primary:" +msgstr "" + +msgid "GeoNodes|Loading nodes" +msgstr "" + +msgid "GeoNodes|Local Attachments:" +msgstr "" + +msgid "GeoNodes|Local LFS objects:" +msgstr "" + +msgid "GeoNodes|Local job artifacts:" +msgstr "" + +msgid "GeoNodes|New node" +msgstr "" + +msgid "GeoNodes|Out of sync" msgstr "" -msgid "GeoNodeSyncStatus|Synced" +msgid "GeoNodes|Replication slot WAL:" +msgstr "" + +msgid "GeoNodes|Replication slots:" +msgstr "" + +msgid "GeoNodes|Repositories:" +msgstr "" + +msgid "GeoNodes|Selective" +msgstr "" + +msgid "GeoNodes|Storage config:" +msgstr "" + +msgid "GeoNodes|Sync settings:" +msgstr "" + +msgid "GeoNodes|Synced" +msgstr "" + +msgid "GeoNodes|Unused slots" +msgstr "" + +msgid "GeoNodes|Used slots" +msgstr "" + +msgid "GeoNodes|Wikis:" +msgstr "" + +msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS." +msgstr "" + +msgid "Geo|All projects" msgstr "" msgid "Geo|File sync capacity" msgstr "" -msgid "Geo|Groups to replicate" +msgid "Geo|Groups to synchronize" +msgstr "" + +msgid "Geo|Projects in certain groups" +msgstr "" + +msgid "Geo|Projects in certain storage shards" msgstr "" msgid "Geo|Repository sync capacity" @@ -1263,12 +1620,24 @@ msgstr "" msgid "Geo|Select groups to replicate." msgstr "" +msgid "Geo|Shards to synchronize" +msgstr "" + +msgid "Git revision" +msgstr "" + msgid "Git storage health information has been reset" msgstr "Informationen über den Speicherzustand von Gitlab wurden zurückgesetzt." +msgid "Git version" +msgstr "" + msgid "GitLab Runner section" msgstr "GitLab Runner Bereich" +msgid "Gitaly Servers" +msgstr "" + msgid "Go to your fork" msgstr "Gehe zu Deinem Ableger" @@ -1278,6 +1647,9 @@ msgstr "Ableger" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it!" +msgstr "" + msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" msgstr "" @@ -1314,7 +1686,7 @@ msgstr "" msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group." msgstr "" -msgid "GroupsTree|Are you sure you want to leave the \"${this.group.fullName}\" group?" +msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?" msgstr "" msgid "GroupsTree|Create a project in this group." @@ -1365,6 +1737,11 @@ msgstr "Keine Probleme erkannt" msgid "HealthCheck|Unhealthy" msgstr "Problematisch" +msgid "Hide value" +msgid_plural "Hide values" +msgstr[0] "" +msgstr[1] "" + msgid "History" msgstr "" @@ -1391,6 +1768,12 @@ msgid_plural "Instances" msgstr[0] "" msgstr[1] "" +msgid "Instance does not support multiple Kubernetes clusters" +msgstr "" + +msgid "Interested parties can even contribute by pushing commits if they want to." +msgstr "" + msgid "Internal - The group and any internal projects can be viewed by any logged in user." msgstr "" @@ -1418,6 +1801,9 @@ msgstr "" msgid "Issues" msgstr "" +msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable." +msgstr "" + msgid "Jan" msgstr "" @@ -1436,6 +1822,27 @@ msgstr "" msgid "June" msgstr "" +msgid "Kubernetes" +msgstr "" + +msgid "Kubernetes Cluster" +msgstr "" + +msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}" +msgstr "" + +msgid "Kubernetes cluster integration was not removed." +msgstr "" + +msgid "Kubernetes cluster integration was successfully removed." +msgstr "" + +msgid "Kubernetes cluster was successfully updated." +msgstr "" + +msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page" +msgstr "" + msgid "LFSStatus|Disabled" msgstr "Deaktiviert" @@ -1445,6 +1852,9 @@ msgstr "Aktiviert" msgid "Labels" msgstr "" +msgid "Labels can be applied to issues and merge requests to categorize them." +msgstr "" + msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "Letzten %d Tag" @@ -1474,6 +1884,9 @@ msgstr "Du übertrugst an" msgid "LastPushEvent|at" msgstr "am" +msgid "Learn more" +msgstr "" + msgid "Learn more in the" msgstr "Erfahre mehr in den" @@ -1492,14 +1905,18 @@ msgstr "Verlasse das Projekt" msgid "License" msgstr "" -msgid "Limited to showing %d event at most" -msgid_plural "Limited to showing %d events at most" -msgstr[0] "Limitiere die Anzeige auf höchstens %d Ereignis" -msgstr[1] "Limitiere die Anzeige auf höchstens %d Ereignisse" +msgid "Loading the GitLab IDE..." +msgstr "" msgid "Lock" msgstr "" +msgid "Lock %{issuableDisplayName}" +msgstr "" + +msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment." +msgstr "" + msgid "Locked" msgstr "" @@ -1509,12 +1926,21 @@ msgstr "" msgid "Login" msgstr "" +msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos." +msgstr "" + +msgid "Manage labels" +msgstr "" + msgid "Mar" msgstr "" msgid "March" msgstr "" +msgid "Mark done" +msgstr "" + msgid "Maximum git storage failures" msgstr "" @@ -1527,6 +1953,9 @@ msgstr "Median" msgid "Members" msgstr "Mitglieder" +msgid "Merge Request" +msgstr "" + msgid "Merge Requests" msgstr "" @@ -1536,9 +1965,30 @@ msgstr "Ereignisse zusammenführen" msgid "Merge request" msgstr "" +msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others" +msgstr "" + +msgid "Merged" +msgstr "" + msgid "Messages" msgstr "Nachrichten" +msgid "Milestone" +msgstr "" + +msgid "Milestones|Delete milestone" +msgstr "" + +msgid "Milestones|Delete milestone %{milestoneTitle}?" +msgstr "" + +msgid "Milestones|Failed to delete milestone %{milestoneTitle}" +msgstr "" + +msgid "Milestones|Milestone %{milestoneTitle} was not found" +msgstr "" + msgid "MissingSSHKeyWarningLink|add an SSH key" msgstr "einen SSH Schlüssel hinzufügst" @@ -1548,10 +1998,16 @@ msgstr "Ãœberwachung" msgid "More information is available|here" msgstr "hier" +msgid "Move" +msgstr "" + +msgid "Move issue" +msgstr "" + msgid "Multiple issue boards" msgstr "" -msgid "New Cluster" +msgid "Name new label" msgstr "" msgid "New Issue" @@ -1559,6 +2015,12 @@ msgid_plural "New Issues" msgstr[0] "Neues Ticket" msgstr[1] "Neue Tickets" +msgid "New Kubernetes Cluster" +msgstr "" + +msgid "New Kubernetes cluster" +msgstr "" + msgid "New Pipeline Schedule" msgstr "Neuer Pipeline Zeitplan" @@ -1583,6 +2045,9 @@ msgstr "" msgid "New issue" msgstr "Neues Ticket" +msgid "New label" +msgstr "" + msgid "New merge request" msgstr "Neuer Merge Request" @@ -1601,7 +2066,22 @@ msgstr "" msgid "New tag" msgstr "Neuer Tag" -msgid "No container images stored for this project. Add one by following the instructions above." +msgid "No assignee" +msgstr "" + +msgid "No changes" +msgstr "" + +msgid "No connection could be made to a Gitaly Server, please check your logs!" +msgstr "" + +msgid "No due date" +msgstr "" + +msgid "No estimate or time spent" +msgstr "" + +msgid "No file chosen" msgstr "" msgid "No repository" @@ -1616,9 +2096,15 @@ msgstr "" msgid "None" msgstr "" +msgid "Not allowed to merge" +msgstr "" + msgid "Not available" msgstr "Nicht verfügbar" +msgid "Not confidential" +msgstr "" + msgid "Not enough data" msgstr "Nicht genügend Daten" @@ -1679,6 +2165,12 @@ msgstr "Beobachten" msgid "Notifications" msgstr "Benachrichtigungen" +msgid "Notifications off" +msgstr "" + +msgid "Notifications on" +msgstr "" + msgid "Nov" msgstr "" @@ -1688,7 +2180,7 @@ msgstr "" msgid "Number of access attempts" msgstr "" -msgid "Number of failures before backing off" +msgid "OK" msgstr "" msgid "Oct" @@ -1703,6 +2195,9 @@ msgstr "Filter" msgid "Only project members can comment." msgstr "" +msgid "Open" +msgstr "" + msgid "Opened" msgstr "" @@ -1736,9 +2231,6 @@ msgstr "" msgid "Password" msgstr "Passwort" -msgid "People without permission will never get a notification and won\\'t be able to comment." -msgstr "" - msgid "Pipeline" msgstr "" @@ -1781,12 +2273,6 @@ msgstr "Alle" msgid "PipelineSchedules|Inactive" msgstr "Inaktiv" -msgid "PipelineSchedules|Input variable key" -msgstr "Schlüssel der Eingangsvariable" - -msgid "PipelineSchedules|Input variable value" -msgstr "Wert der Eingangsvariable" - msgid "PipelineSchedules|Next Run" msgstr "Nächste Durchführung" @@ -1796,9 +2282,6 @@ msgstr "Nichts" msgid "PipelineSchedules|Provide a short description for this pipeline" msgstr "Beschreibe diese Pipeline" -msgid "PipelineSchedules|Remove variable row" -msgstr "Entferne Variablenreihe" - msgid "PipelineSchedules|Take ownership" msgstr "Eigentümer werden" @@ -1826,6 +2309,12 @@ msgstr "" msgid "Pipelines for last year" msgstr "Pipelines des letzten Jahres" +msgid "Pipelines|Build with confidence" +msgstr "" + +msgid "Pipelines|Get started with Pipelines" +msgstr "" + msgid "Pipeline|all" msgstr "Alle" @@ -1838,12 +2327,21 @@ msgstr "mit Stage" msgid "Pipeline|with stages" msgstr "mit Stages" +msgid "Play" +msgstr "" + +msgid "Please <a href=%{link_to_billing} target=\"_blank\" rel=\"noopener noreferrer\">enable billing for one of your projects to be able to create a Kubernetes cluster</a>, then try again." +msgstr "" + msgid "Please solve the reCAPTCHA" msgstr "" msgid "Preferences" msgstr "" +msgid "Primary" +msgstr "" + msgid "Private - Project access must be granted explicitly to each user." msgstr "" @@ -1889,6 +2387,9 @@ msgstr "" msgid "Profiles|your account" msgstr "" +msgid "Programming languages used in this repository" +msgstr "" + msgid "Project '%{project_name}' is in the process of being deleted." msgstr "" @@ -1904,6 +2405,15 @@ msgstr "Das Projekt '%{project_name}' wurde erfolgreich aktualisiert." msgid "Project access must be granted explicitly to each user." msgstr "Jedem Nutzer muss explizit der Zugriff auf das Projekt gewährt werden." +msgid "Project avatar" +msgstr "" + +msgid "Project avatar in repository: %{link}" +msgstr "" + +msgid "Project cache successfully reset." +msgstr "" + msgid "Project details" msgstr "Projektdetails" @@ -1922,6 +2432,21 @@ msgstr "Export des Projektes gestartet. Ein Link zum herunterladen wir Dir per E msgid "ProjectActivityRSS|Subscribe" msgstr "Abonnieren" +msgid "ProjectCreationLevel|Allowed to create projects" +msgstr "" + +msgid "ProjectCreationLevel|Default project creation protection" +msgstr "" + +msgid "ProjectCreationLevel|Developers + Masters" +msgstr "" + +msgid "ProjectCreationLevel|Masters" +msgstr "" + +msgid "ProjectCreationLevel|No one" +msgstr "" + msgid "ProjectFeature|Disabled" msgstr "Dekativiert" @@ -1946,15 +2471,9 @@ msgstr "Diagramm" msgid "ProjectSettings|Contact an admin to change this setting." msgstr "" -msgid "ProjectSettings|Immediately run a pipeline on the default branch" -msgstr "" - msgid "ProjectSettings|Only signed commits can be pushed to this repository." msgstr "" -msgid "ProjectSettings|Problem setting up the CI/CD settings JavaScript" -msgstr "" - msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin." msgstr "" @@ -1977,7 +2496,7 @@ msgid "ProjectsDropdown|Loading projects" msgstr "" msgid "ProjectsDropdown|Projects you visit often will appear here" -msgstr "ProjectsDropdown | Projekte, die Sie häufig besuchen, werden hier angezeigt" +msgstr "Projekte, die du häufig besuchst, werden hier angezeigt" msgid "ProjectsDropdown|Search your projects" msgstr "" @@ -2018,12 +2537,15 @@ msgstr "" msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/" msgstr "" -msgid "PrometheusService|Prometheus monitoring" +msgid "PrometheusService|Time-series monitoring service" msgstr "" msgid "PrometheusService|View environments" msgstr "" +msgid "Protip:" +msgstr "" + msgid "Public - The group and any public projects can be viewed without any authentication." msgstr "" @@ -2039,6 +2561,9 @@ msgstr "Ãœbertragungsereignisse" msgid "PushRule|Committer restriction" msgstr "" +msgid "Quick actions can be used in the issues description and comment boxes." +msgstr "" + msgid "Read more" msgstr "Mehr lesen" @@ -2051,6 +2576,12 @@ msgstr "Branches" msgid "RefSwitcher|Tags" msgstr "Tags" +msgid "Reference:" +msgstr "" + +msgid "Register / Sign In" +msgstr "" + msgid "Registry" msgstr "" @@ -2075,9 +2606,18 @@ msgstr "Zugehörige umgesetzte Merge Requests" msgid "Remind later" msgstr "Später erinnern" +msgid "Remove" +msgstr "" + +msgid "Remove avatar" +msgstr "" + msgid "Remove project" msgstr "Projekt entfernen" +msgid "Repair authentication" +msgstr "" + msgid "Repository" msgstr "" @@ -2093,6 +2633,11 @@ msgstr "Zugriffstoken für Systemzustand zurücksetzen" msgid "Reset runners registration token" msgstr "Registrierungstoken für Runner zurücksetzen" +msgid "Reveal value" +msgid_plural "Reveal values" +msgstr[0] "" +msgstr[1] "" + msgid "Revert this commit" msgstr "Commit zurücksetzen" @@ -2102,15 +2647,15 @@ msgstr "Merge Request zurücksetzen" msgid "SSH Keys" msgstr "SSH-Schlüssel" -msgid "Save" -msgstr "" - msgid "Save changes" msgstr "" msgid "Save pipeline schedule" msgstr "Zeitplan der Pipeline speichern" +msgid "Save variables" +msgstr "" + msgid "Schedule a new pipeline" msgstr "Plane eine neue Pipeline" @@ -2126,38 +2671,59 @@ msgstr "" msgid "Search branches and tags" msgstr "Suche nach Branches und Tags" -msgid "Seconds before reseting failure information" +msgid "Search milestones" +msgstr "" + +msgid "Search project" msgstr "" -msgid "Seconds to wait after a storage failure" +msgid "Search users" +msgstr "" + +msgid "Seconds before reseting failure information" msgstr "" msgid "Seconds to wait for a storage access attempt" msgstr "" +msgid "Secret variables" +msgstr "" + msgid "Select Archive Format" msgstr "Archivierungsformat auswählen" msgid "Select a timezone" msgstr "Zeitzone auswählen" +msgid "Select assignee" +msgstr "" + +msgid "Select branch/tag" +msgstr "" + msgid "Select target branch" msgstr "Zielbranch auswählen" +msgid "Selective synchronization" +msgstr "" + msgid "Sep" msgstr "" msgid "September" msgstr "" +msgid "Server version" +msgstr "" + msgid "Service Templates" msgstr "" msgid "Set a password on your account to pull or push via %{protocol}." msgstr "Lege ein Passwort für dein Konto fest, um mittels %{protocol} zu übertragen (push) oder abzurufen (pull)." -msgid "Set up CI" -msgstr "CI einrichten" +msgid "Set up CI/CD" +msgstr "" msgid "Set up Koding" msgstr "Koding einrichten" @@ -2171,6 +2737,15 @@ msgstr "ein Passwort festlegst" msgid "Settings" msgstr "Einstellungen" +msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero." +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset pipeline minutes" +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes" +msgstr "" + msgid "Show parent pages" msgstr "" @@ -2185,9 +2760,6 @@ msgstr[1] "Zeige %d Ereignisse" msgid "Sidebar|Change weight" msgstr "" -msgid "Sidebar|Edit" -msgstr "" - msgid "Sidebar|No" msgstr "" @@ -2200,18 +2772,30 @@ msgstr "" msgid "Snippets" msgstr "" +msgid "Something went wrong on our end" +msgstr "" + msgid "Something went wrong on our end." msgstr "" +msgid "Something went wrong trying to change the confidentiality of this issue" +msgstr "" + msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}" msgstr "" +msgid "Something went wrong when toggling the button" +msgstr "" + msgid "Something went wrong while fetching the projects." msgstr "" msgid "Something went wrong while fetching the registry list." msgstr "" +msgid "Something went wrong. Please try again." +msgstr "" + msgid "Sort by" msgstr "" @@ -2341,10 +2925,10 @@ msgstr "Starte den Runner!" msgid "Stopped" msgstr "" -msgid "Subgroups" +msgid "Storage" msgstr "" -msgid "Subscribe" +msgid "Subgroups" msgstr "" msgid "Switch branch/tag" @@ -2442,7 +3026,10 @@ msgstr "" msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project." msgstr "" -msgid "The circuitbreaker backoff threshold should be lower than the failure count threshold" +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project" +msgstr "" + +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project." msgstr "" msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request." @@ -2457,10 +3044,10 @@ msgstr "Die Beziehung des Ablegers wurde entfernt." msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage." msgstr "Die Ticketphase stellt die Zeit vom Anlegen eines Tickets bis zum Zuweisen eines Meilensteins oder Hinzufügen zur Aufgabentafel dar. Erstelle einen Ticket, damit dessen Daten hier erscheinen." -msgid "The number of attempts GitLab will make to access a storage." +msgid "The maximum file size allowed is 200KB." msgstr "" -msgid "The number of failures after which GitLab will start temporarily disabling access to a storage shard on a host" +msgid "The number of attempts GitLab will make to access a storage." msgstr "" msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}." @@ -2469,9 +3056,6 @@ msgstr "" msgid "The phase of the development lifecycle." msgstr "Die Phase des Entwicklungslebenszyklus." -msgid "The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user." -msgstr "Die Pipelinezeitpläne starten Pipelines in der Zukunft, wiederholend, für bestimmte Branches oder Tags. Diese geplanten Pipelines haben denselben begrenzten Zugriff auf das Projekt, wie der zugeordnete Nutzer." - msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit." msgstr "Die Planungsphase stellt die Zeit von der vorherigen Phase bis zum Ãœbertragen des ersten Commits dar. Sobald Du den ersten Commit überträgst, werden dessen Daten hier erscheinen." @@ -2502,19 +3086,46 @@ msgstr "" msgid "The time in seconds GitLab will try to access storage. After this time a timeout error will be raised." msgstr "" +msgid "The time in seconds between storage checks. When a previous check did complete yet, GitLab will skip a check." +msgstr "" + msgid "The time taken by each data entry gathered by that stage." msgstr "Zeit, die für das jeweilige Ereignis in der Phase ermittelt wurde." msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6." msgstr "Der mittlere aller erfassten Werte. Zum Beispiel ist für 3, 5, 9 der Median 5. Bei 3, 5, 7, 8 ist der Median (5+7)/2 = 6." +msgid "There are no issues to show" +msgstr "" + +msgid "There are no merge requests to show" +msgstr "" + msgid "There are problems accessing Git storage: " msgstr "Es gibt ein Problem beim Zugriff auf den Gitspeicher:" +msgid "There was an error loading users activity calendar." +msgstr "" + +msgid "There was an error saving your notification settings." +msgstr "" + +msgid "There was an error subscribing to this label." +msgstr "" + +msgid "There was an error when reseting email token." +msgstr "" + +msgid "There was an error when subscribing to this label." +msgstr "" + +msgid "There was an error when unsubscribing from this label." +msgstr "" + msgid "This board\\'s scope is reduced" msgstr "" -msgid "This branch has changed since you started editing. Would you like to create a new branch?" +msgid "This directory" msgstr "" msgid "This is a confidential issue." @@ -2523,18 +3134,45 @@ msgstr "" msgid "This is the author's first Merge Request to this project." msgstr "" +msgid "This issue is confidential" +msgstr "" + msgid "This issue is confidential and locked." msgstr "" msgid "This issue is locked." msgstr "" +msgid "This job depends on a user to trigger its process. Often they are used to deploy code to production environments" +msgstr "" + +msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered" +msgstr "" + +msgid "This job has not been triggered yet" +msgstr "" + +msgid "This job has not started yet" +msgstr "" + +msgid "This job is in pending state and is waiting to be picked by a runner" +msgstr "" + +msgid "This job requires a manual action" +msgstr "" + msgid "This means you can not push code until you create an empty repository or import existing one." msgstr "Dies bedeutet, dass Du keinen Code übertragen kannst, bevor Du kein leeres Repositorium erstellt oder ein Existierendes importiert hast." msgid "This merge request is locked." msgstr "" +msgid "This project" +msgstr "" + +msgid "This repository" +msgstr "" + msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here." msgstr "" @@ -2547,9 +3185,21 @@ msgstr "Zeit bis die Implementierung für ein Ticket beginnt" msgid "Time between merge request creation and merge/close" msgstr "Zeit zwischen einem Merge Request und dessen Umsetzung / Schließung" +msgid "Time tracking" +msgstr "" + msgid "Time until first merge request" msgstr "Zeit bis zum ersten Merge Request" +msgid "TimeTrackingEstimated|Est" +msgstr "" + +msgid "TimeTracking|Estimated:" +msgstr "" + +msgid "TimeTracking|Spent" +msgstr "" + msgid "Timeago|%s days ago" msgstr "vor %s Tagen" @@ -2689,6 +3339,18 @@ msgstr "Sek." msgid "Title" msgstr "" +msgid "Todo" +msgstr "" + +msgid "Toggle sidebar" +msgstr "" + +msgid "ToggleButton|Toggle Status: OFF" +msgstr "" + +msgid "ToggleButton|Toggle Status: ON" +msgstr "" + msgid "Total Time" msgstr "Gesamtzeit" @@ -2704,19 +3366,40 @@ msgstr "" msgid "Track groups of issues that share a theme, across projects and milestones" msgstr "" +msgid "Total: %{total}" +msgstr "" + +msgid "Track time with quick actions" +msgstr "" + +msgid "Trigger this manual action" +msgstr "" + msgid "Turn on Service Desk" msgstr "" +msgid "Type %{value} to confirm:" +msgstr "" + +msgid "Unable to reset project cache." +msgstr "" + +msgid "Unknown" +msgstr "" + msgid "Unlock" msgstr "" +msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment." +msgstr "" + msgid "Unlocked" msgstr "" msgid "Unstar" msgstr "Entfavorisieren" -msgid "Unsubscribe" +msgid "Up to date" msgstr "" msgid "Upgrade your plan to activate Advanced Global Search." @@ -2740,6 +3423,9 @@ msgstr "Eine Neue Datei hochladen" msgid "Upload file" msgstr "Eine Datei hochladen" +msgid "Upload new avatar" +msgstr "" + msgid "UploadLink|click to upload" msgstr "Zum Upload klicken" @@ -2752,9 +3438,15 @@ msgstr "Benutze den folgenden Registrierungstoken während des Setups:" msgid "Use your global notification setting" msgstr "Benutze Deine globalen Benachrichtigungseinstellungen" +msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." +msgstr "" + msgid "View file @ " msgstr "" +msgid "View labels" +msgstr "" + msgid "View open merge request" msgstr "Zeige offene Merge Requests." @@ -2776,6 +3468,9 @@ msgstr "Unbekannt" msgid "Want to see the data? Please ask an administrator for access." msgstr "Du möchtest diese Daten sehen? Bitte frage einen Administrator nach dem Zugang." +msgid "We could not verify that one of your projects on GCP has billing enabled. Please try again." +msgstr "" + msgid "We don't have enough data to show this stage." msgstr "Es liegen nicht genügend Daten vor, um diese Phase anzuzeigen." @@ -2788,9 +3483,6 @@ msgstr "" msgid "Weight" msgstr "" -msgid "When access to a storage fails. GitLab will prevent access to the storage for the time specified here. This allows the filesystem to recover. Repositories on failing shards are temporarly unavailable" -msgstr "" - msgid "Wiki" msgstr "Wiki" @@ -2809,6 +3501,12 @@ msgstr "" msgid "WikiClone|Start Gollum and edit locally" msgstr "" +msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title." +msgstr "" + +msgid "WikiEdit|There is already a page with the same title in that path." +msgstr "" + msgid "WikiEmptyPageError|You are not allowed to create wiki pages" msgstr "" @@ -2911,9 +3609,21 @@ msgstr "Du bist dabei, die Beziehung des Ablegers zum Ursprungsprojekt %{forked_ msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" msgstr "Du bist dabei %{project_name_with_namespace} einem andere Besitzer zu übergeben. Bist Du dir WIRKLICH sicher?" +msgid "You can also star a label to make it a priority label." +msgstr "" + +msgid "You can move around the graph by using the arrow keys." +msgstr "" + +msgid "You can move around the graph by using the arrow keys." +msgstr "" + msgid "You can only add files when you are on a branch" msgstr "Du kannst Dateien nur hinzufügen, wenn Du dich auf einem Branch befindest." +msgid "You can only edit files when you are on a branch" +msgstr "" + msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead." msgstr "" @@ -2953,6 +3663,12 @@ msgstr "Du kannst erst mittels SSH übertragen (push) oder abrufen (pull), nachd msgid "You won't be able to pull or push project code via SSH until you add an SSH key to your profile" msgstr "" +msgid "You'll need to use different branch names to get a valid comparison." +msgstr "" + +msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure" +msgstr "" + msgid "Your comment will not be visible to the public." msgstr "" @@ -2965,26 +3681,220 @@ msgstr "Dein Name" msgid "Your projects" msgstr "Deine Projekte" +msgid "assign yourself" +msgstr "" + msgid "branch name" msgstr "" msgid "by" msgstr "" +msgid "ciReport|Code quality" +msgstr "" + +msgid "ciReport|DAST detected no alerts by analyzing the review app" +msgstr "" + +msgid "ciReport|Failed to load ${type} report" +msgstr "" + +msgid "ciReport|Fixed:" +msgstr "" + +msgid "ciReport|Instances" +msgstr "" + +msgid "ciReport|Learn more about whitelisting" +msgstr "" + +msgid "ciReport|Loading ${type} report" +msgstr "" + +msgid "ciReport|No changes to code quality" +msgstr "" + +msgid "ciReport|No changes to performance metrics" +msgstr "" + +msgid "ciReport|Performance metrics" +msgstr "" + +msgid "ciReport|SAST" +msgstr "" + +msgid "ciReport|SAST detected no security vulnerabilities" +msgstr "" + +msgid "ciReport|SAST:container no vulnerabilities were found" +msgstr "" + +msgid "ciReport|Show complete code vulnerabilities report" +msgstr "" + +msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}" +msgstr "" + msgid "commit" msgstr "" +msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue." +msgstr "" + +msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue." +msgstr "" + msgid "day" msgid_plural "days" msgstr[0] "Tag" msgstr[1] "Tage" +msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command." +msgstr "" + +msgid "merge request" +msgid_plural "merge requests" +msgstr[0] "" +msgstr[1] "" + +msgid "mrWidget|Cancel automatic merge" +msgstr "" + +msgid "mrWidget|Check out branch" +msgstr "" + +msgid "mrWidget|Checking ability to merge automatically" +msgstr "" + +msgid "mrWidget|Cherry-pick" +msgstr "" + +msgid "mrWidget|Cherry-pick this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Closed" +msgstr "" + +msgid "mrWidget|Closed by" +msgstr "" + +msgid "mrWidget|Closes" +msgstr "" + +msgid "mrWidget|Did not close" +msgstr "" + +msgid "mrWidget|Email patches" +msgstr "" + +msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|Mentions" +msgstr "" + +msgid "mrWidget|Merge" +msgstr "" + +msgid "mrWidget|Merge failed." +msgstr "" + +msgid "mrWidget|Merge locally" +msgstr "" + +msgid "mrWidget|Merged by" +msgstr "" + +msgid "mrWidget|Plain diff" +msgstr "" + +msgid "mrWidget|Refresh" +msgstr "" + +msgid "mrWidget|Refresh now" +msgstr "" + +msgid "mrWidget|Refreshing now" +msgstr "" + +msgid "mrWidget|Remove Source Branch" +msgstr "" + +msgid "mrWidget|Remove source branch" +msgstr "" + +msgid "mrWidget|Request to merge" +msgstr "" + +msgid "mrWidget|Resolve conflicts" +msgstr "" + +msgid "mrWidget|Revert" +msgstr "" + +msgid "mrWidget|Revert this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Set by" +msgstr "" + +msgid "mrWidget|The changes were merged into" +msgstr "" + +msgid "mrWidget|The changes were not merged into" +msgstr "" + +msgid "mrWidget|The changes will be merged into" +msgstr "" + +msgid "mrWidget|The source branch has been removed" +msgstr "" + +msgid "mrWidget|The source branch is being removed" +msgstr "" + +msgid "mrWidget|The source branch will be removed" +msgstr "" + +msgid "mrWidget|The source branch will not be removed" +msgstr "" + +msgid "mrWidget|There are merge conflicts" +msgstr "" + +msgid "mrWidget|This merge request failed to be merged automatically" +msgstr "" + +msgid "mrWidget|This merge request is in the process of being merged" +msgstr "" + +msgid "mrWidget|This project is archived, write access has been disabled" +msgstr "" + +msgid "mrWidget|You can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|You can remove source branch now" +msgstr "" + +msgid "mrWidget|command line" +msgstr "" + +msgid "mrWidget|into" +msgstr "" + +msgid "mrWidget|to be merged automatically when the pipeline succeeds" +msgstr "" + msgid "new merge request" msgstr "Neuer Merge Request" msgid "notification emails" msgstr "Benachrichtungsemail" +msgid "or" +msgstr "" + msgid "parent" msgid_plural "parents" msgstr[0] "Vorgänger" @@ -2996,12 +3906,21 @@ msgstr "" msgid "personal access token" msgstr "" +msgid "remove due date" +msgstr "" + msgid "source" msgstr "" +msgid "spendCommand|%{slash_command} will update the sum of the time spent." +msgstr "" + msgid "to help your contributors communicate effectively!" msgstr "" msgid "username" msgstr "" +msgid "uses Kubernetes clusters to deploy your code!" +msgstr "" + diff --git a/locale/eo/gitlab.po b/locale/eo/gitlab.po index be7cfa6e4b5..7d4648c55f1 100644 --- a/locale/eo/gitlab.po +++ b/locale/eo/gitlab.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab-ee\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-12-12 18:31+0000\n" -"PO-Revision-Date: 2018-01-05 04:42-0500\n" +"POT-Creation-Date: 2018-02-07 11:38-0600\n" +"PO-Revision-Date: 2018-02-12 03:59-0500\n" "Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n" "Language-Team: Esperanto\n" "Language: eo_UY\n" @@ -16,23 +16,41 @@ msgstr "" "X-Crowdin-Language: eo\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" +msgid " and" +msgstr "" + msgid "%d commit" msgid_plural "%d commits" msgstr[0] "%d enmetado" msgstr[1] "%d enmetadoj" +msgid "%d commit behind" +msgid_plural "%d commits behind" +msgstr[0] "" +msgstr[1] "" + +msgid "%d issue" +msgid_plural "%d issues" +msgstr[0] "" +msgstr[1] "" + msgid "%d layer" msgid_plural "%d layers" msgstr[0] "" msgstr[1] "" +msgid "%d merge request" +msgid_plural "%d merge requests" +msgstr[0] "" +msgstr[1] "" + msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "%s additional commits have been omitted to prevent performance issues." msgstr[0] "%s enmetado estis transsaltita, por ne troÅarÄi la sistemon." msgstr[1] "%s enmetadoj estis transsaltitaj, por ne troÅarÄi la sistemon." -msgid "%{commit_author_link} committed %{commit_timeago}" -msgstr "%{commit_author_link} enmetis %{commit_timeago}" +msgid "%{commit_author_link} authored %{commit_timeago}" +msgstr "" msgid "%{count} participant" msgid_plural "%{count} participants" @@ -45,9 +63,6 @@ msgstr "" msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will allow access on the next attempt." msgstr "" -msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will block access for %{number_of_seconds} seconds." -msgstr "" - msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved." msgstr "" @@ -121,24 +136,81 @@ msgstr "Aldoni rajtigilon" msgid "Add new directory" msgstr "Aldoni novan dosierujon" +msgid "Add todo" +msgstr "" + +msgid "AdminArea|Stop all jobs" +msgstr "" + +msgid "AdminArea|Stop all jobs?" +msgstr "" + +msgid "AdminArea|Stop jobs" +msgstr "" + +msgid "AdminArea|Stopping jobs failed" +msgstr "" + +msgid "AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running." +msgstr "" + msgid "AdminHealthPageLink|health page" msgstr "" +msgid "Advanced" +msgstr "" + msgid "Advanced settings" msgstr "" msgid "All" msgstr "" +msgid "All changes are committed" +msgstr "" + +msgid "Allows you to add and manage Kubernetes clusters." +msgstr "" + +msgid "An error occurred previewing the blob" +msgstr "" + msgid "An error occurred when toggling the notification subscription" msgstr "" msgid "An error occurred when updating the issue weight" msgstr "" +msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." +msgstr "" + +msgid "An error occurred while fetching markdown preview" +msgstr "" + msgid "An error occurred while fetching sidebar data" msgstr "" +msgid "An error occurred while getting projects" +msgstr "" + +msgid "An error occurred while loading filenames" +msgstr "" + +msgid "An error occurred while rendering KaTeX" +msgstr "" + +msgid "An error occurred while rendering preview broadcast message" +msgstr "" + +msgid "An error occurred while retrieving calendar activity" +msgstr "" + +msgid "An error occurred while retrieving diff" +msgstr "" + +msgid "An error occurred while validating username" +msgstr "" + msgid "An error occurred. Please try again." msgstr "" @@ -163,9 +235,6 @@ msgstr "Ĉu vi certe volas forigi ĉi tiun ĉenstablan planon?" msgid "Are you sure you want to discard your changes?" msgstr "" -msgid "Are you sure you want to leave this group?" -msgstr "" - msgid "Are you sure you want to reset registration token?" msgstr "" @@ -178,6 +247,21 @@ msgstr "" msgid "Artifacts" msgstr "" +msgid "Assign custom color like #FF0000" +msgstr "" + +msgid "Assign labels" +msgstr "" + +msgid "Assign milestone" +msgstr "" + +msgid "Assign to" +msgstr "" + +msgid "Assignee" +msgstr "" + msgid "Attach a file by drag & drop or %{upload_link}" msgstr "Alkroĉu dosieron per Åovmetado aÅ %{upload_link}" @@ -193,13 +277,16 @@ msgstr "" msgid "Author" msgstr "" -msgid "Auto Review Apps and Auto Deploy need a domain name and the %{kubernetes} to work correctly." +msgid "Authors: %{authors}" msgstr "" -msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly." +msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." msgstr "" -msgid "Auto Review Apps and Auto Deploy need the %{kubernetes} to work correctly." +msgid "Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly." +msgstr "" + +msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly." msgstr "" msgid "AutoDevOps|Auto DevOps (Beta)" @@ -223,6 +310,12 @@ msgstr "" msgid "Available" msgstr "" +msgid "Avatar will be removed. Are you sure?" +msgstr "" + +msgid "Average per day: %{average}" +msgstr "" + msgid "Billing" msgstr "" @@ -277,6 +370,9 @@ msgstr "" msgid "BillingPlans|per user" msgstr "" +msgid "Begin with the selected commit" +msgstr "" + msgid "Branch" msgid_plural "Branches" msgstr[0] "Branĉo" @@ -405,8 +501,8 @@ msgstr "de" msgid "CI / CD" msgstr "" -msgid "CI configuration" -msgstr "Agordoj de seninterrompa integrado" +msgid "CI/CD configuration" +msgstr "" msgid "CICD|Jobs" msgstr "" @@ -417,6 +513,9 @@ msgstr "Nuligi" msgid "Cancel edit" msgstr "" +msgid "Cannot modify managed Kubernetes cluster" +msgstr "" + msgid "Change Weight" msgstr "" @@ -432,15 +531,24 @@ msgstr "Precize elekti" msgid "ChangeTypeAction|Revert" msgstr "Malfari" +msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes." +msgstr "" + msgid "Changelog" msgstr "Listo de ÅanÄoj" +msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision." +msgstr "" + msgid "Charts" msgstr "Diagramoj" msgid "Chat" msgstr "" +msgid "Check interval" +msgstr "" + msgid "Checking %{text} availability…" msgstr "" @@ -453,7 +561,19 @@ msgstr "Precize elekti ĉi tiun kunmetadon" msgid "Cherry-pick this merge request" msgstr "Precize elekti ĉi tiun peton pri kunfando" -msgid "Choose which groups you wish to replicate to this secondary node. Leave blank to replicate all." +msgid "Choose File ..." +msgstr "" + +msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request." +msgstr "" + +msgid "Choose file..." +msgstr "" + +msgid "Choose which groups you wish to synchronize to this secondary node." +msgstr "" + +msgid "Choose which shards you wish to synchronize to this secondary node." msgstr "" msgid "CiStatusLabel|canceled" @@ -510,79 +630,91 @@ msgstr "transsaltita" msgid "CiStatus|running" msgstr "plenumiÄanta" -msgid "CircuitBreakerApiLink|circuitbreaker api" +msgid "CiVariables|Input variable key" msgstr "" -msgid "Clone repository" +msgid "CiVariables|Input variable value" msgstr "" -msgid "Close" +msgid "CiVariables|Remove variable row" msgstr "" -msgid "Cluster" +msgid "CiVariable|* (All environments)" msgstr "" -msgid "ClusterIntegration|%{appList} was successfully installed on your cluster" +msgid "CiVariable|All environments" msgstr "" -msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which incur additional costs. See %{pricingLink}" +msgid "CiVariable|Create wildcard" msgstr "" -msgid "ClusterIntegration|API URL" +msgid "CiVariable|Error occured while saving variables" msgstr "" -msgid "ClusterIntegration|Active" +msgid "CiVariable|New environment" msgstr "" -msgid "ClusterIntegration|Add an existing cluster" +msgid "CiVariable|Protected" msgstr "" -msgid "ClusterIntegration|Add cluster" +msgid "CiVariable|Search environments" msgstr "" -msgid "ClusterIntegration|All" +msgid "CiVariable|Toggle protected" msgstr "" -msgid "ClusterIntegration|Applications" +msgid "CiVariable|Validation failed" msgstr "" -msgid "ClusterIntegration|CA Certificate" +msgid "CircuitBreakerApiLink|circuitbreaker api" msgstr "" -msgid "ClusterIntegration|Certificate Authority bundle (PEM format)" +msgid "Click to expand text" msgstr "" -msgid "ClusterIntegration|Choose how to set up cluster integration" +msgid "Clone repository" msgstr "" -msgid "ClusterIntegration|Cluster" +msgid "Close" msgstr "" -msgid "ClusterIntegration|Cluster details" +msgid "Closed" msgstr "" -msgid "ClusterIntegration|Cluster integration" +msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Cluster integration is disabled for this project." +msgid "ClusterIntegration|API URL" msgstr "" -msgid "ClusterIntegration|Cluster integration is enabled for this project." +msgid "ClusterIntegration|Add Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab's connection to it." +msgid "ClusterIntegration|Add an existing Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Cluster is being created on Google Kubernetes Engine..." +msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration" +msgstr "" + +msgid "ClusterIntegration|Applications" +msgstr "" + +msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster." +msgstr "" + +msgid "ClusterIntegration|CA Certificate" +msgstr "" + +msgid "ClusterIntegration|Certificate Authority bundle (PEM format)" msgstr "" -msgid "ClusterIntegration|Cluster name" +msgid "ClusterIntegration|Choose how to set up Kubernetes cluster integration" msgstr "" -msgid "ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine. Refresh the page to see cluster's details" +msgid "ClusterIntegration|Choose which of your project's environments will use this Kubernetes cluster." msgstr "" -msgid "ClusterIntegration|Clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" +msgid "ClusterIntegration|Control how your Kubernetes cluster integrates with GitLab" msgstr "" msgid "ClusterIntegration|Copy API URL" @@ -591,37 +723,34 @@ msgstr "" msgid "ClusterIntegration|Copy CA Certificate" msgstr "" -msgid "ClusterIntegration|Copy Token" +msgid "ClusterIntegration|Copy Kubernetes cluster name" msgstr "" -msgid "ClusterIntegration|Copy cluster name" +msgid "ClusterIntegration|Copy Token" msgstr "" -msgid "ClusterIntegration|Create a new cluster on Google Engine right from GitLab" +msgid "ClusterIntegration|Create Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Create cluster" +msgid "ClusterIntegration|Create Kubernetes cluster on Google Kubernetes Engine" msgstr "" -msgid "ClusterIntegration|Create cluster on Google Kubernetes Engine" +msgid "ClusterIntegration|Create a new Kubernetes cluster on Google Kubernetes Engine right from GitLab" msgstr "" msgid "ClusterIntegration|Create on GKE" msgstr "" -msgid "ClusterIntegration|Enable cluster integration" -msgstr "" - msgid "ClusterIntegration|Enter the details for an existing Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Enter the details for your cluster" +msgid "ClusterIntegration|Enter the details for your Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Environment pattern" +msgid "ClusterIntegration|Environment scope" msgstr "" -msgid "ClusterIntegration|GKE pricing" +msgid "ClusterIntegration|GitLab Integration" msgstr "" msgid "ClusterIntegration|GitLab Runner" @@ -639,64 +768,94 @@ msgstr "" msgid "ClusterIntegration|Helm Tiller" msgstr "" -msgid "ClusterIntegration|Inactive" -msgstr "" - msgid "ClusterIntegration|Ingress" msgstr "" msgid "ClusterIntegration|Install" msgstr "" -msgid "ClusterIntegration|Install applications on your cluster. Read more about %{helpLink}" -msgstr "" - msgid "ClusterIntegration|Installed" msgstr "" msgid "ClusterIntegration|Installing" msgstr "" -msgid "ClusterIntegration|Integrate cluster automation" +msgid "ClusterIntegration|Integrate Kubernetes cluster automation" +msgstr "" + +msgid "ClusterIntegration|Integration status" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster details" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is disabled for this project." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project. Disabling this integration will not affect your Kubernetes cluster, it will only temporarily turn off GitLab's connection to it." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster name" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project" msgstr "" msgid "ClusterIntegration|Learn more about %{link_to_documentation}" msgstr "" -msgid "ClusterIntegration|Learn more about Clusters" +msgid "ClusterIntegration|Learn more about Kubernetes" msgstr "" -msgid "ClusterIntegration|Machine type" +msgid "ClusterIntegration|Learn more about environments" msgstr "" -msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create clusters" +msgid "ClusterIntegration|Machine type" msgstr "" -msgid "ClusterIntegration|Manage cluster integration on your GitLab project" +msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters" msgstr "" -msgid "ClusterIntegration|Manage your cluster by visiting %{link_gke}" +msgid "ClusterIntegration|Manage" msgstr "" -msgid "ClusterIntegration|Multiple clusters are available in GitLab Entreprise Edition Premium and Ultimate" +msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}" msgstr "" -msgid "ClusterIntegration|Note:" +msgid "ClusterIntegration|More information" msgstr "" -msgid "ClusterIntegration|Number of nodes" +msgid "ClusterIntegration|Multiple Kubernetes clusters are available in GitLab Enterprise Edition Premium and Ultimate" msgstr "" -msgid "ClusterIntegration|Please enter access information for your cluster. If you need help, you can read our %{link_to_help_page} on clusters" +msgid "ClusterIntegration|Note:" msgstr "" -msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:" +msgid "ClusterIntegration|Number of nodes" msgstr "" -msgid "ClusterIntegration|Problem setting up the cluster" +msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes" msgstr "" -msgid "ClusterIntegration|Problem setting up the clusters list" +msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:" msgstr "" msgid "ClusterIntegration|Project ID" @@ -708,16 +867,19 @@ msgstr "" msgid "ClusterIntegration|Project namespace (optional, unique)" msgstr "" -msgid "ClusterIntegration|Read our %{link_to_help_page} on cluster integration." +msgid "ClusterIntegration|Prometheus" +msgstr "" + +msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration." msgstr "" -msgid "ClusterIntegration|Remove cluster integration" +msgid "ClusterIntegration|Remove Kubernetes cluster integration" msgstr "" msgid "ClusterIntegration|Remove integration" msgstr "" -msgid "ClusterIntegration|Removing cluster integration will remove the cluster configuration you have added to this project. It will not delete your cluster on Google Kubernetes Engine." +msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster." msgstr "" msgid "ClusterIntegration|Request to begin installing failed" @@ -726,7 +888,7 @@ msgstr "" msgid "ClusterIntegration|Save changes" msgstr "" -msgid "ClusterIntegration|See and edit the details for your cluster" +msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster" msgstr "" msgid "ClusterIntegration|See machine types" @@ -747,25 +909,25 @@ msgstr "" msgid "ClusterIntegration|Something went wrong on our end." msgstr "" -msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine" +msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine" msgstr "" msgid "ClusterIntegration|Something went wrong while installing %{title}" msgstr "" -msgid "ClusterIntegration|There are no clusters to show" +msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below" msgstr "" -msgid "ClusterIntegration|This account must have permissions to create a cluster in the %{link_to_container_project} specified below" +msgid "ClusterIntegration|Toggle Kubernetes Cluster" msgstr "" -msgid "ClusterIntegration|Toggle Cluster" +msgid "ClusterIntegration|Toggle Kubernetes cluster" msgstr "" msgid "ClusterIntegration|Token" msgstr "" -msgid "ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." +msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." msgstr "" msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}" @@ -777,7 +939,7 @@ msgstr "" msgid "ClusterIntegration|access to Google Kubernetes Engine" msgstr "" -msgid "ClusterIntegration|cluster" +msgid "ClusterIntegration|check the pricing here" msgstr "" msgid "ClusterIntegration|documentation" @@ -795,6 +957,9 @@ msgstr "" msgid "ClusterIntegration|properly configured" msgstr "" +msgid "Collapse" +msgstr "" + msgid "Comments" msgstr "" @@ -812,6 +977,9 @@ msgstr "DaÅro de la enmetadoj por la lastaj 30 enmetadoj" msgid "Commit message" msgstr "MesaÄo pri la enmetado" +msgid "Commit statistics for %{ref} %{start_time} - %{end_time}" +msgstr "" + msgid "CommitBoxTitle|Commit" msgstr "Enmeti" @@ -824,15 +992,57 @@ msgstr "Enmetadoj" msgid "Commits feed" msgstr "Fluo de enmetadoj" +msgid "Commits per day hour (UTC)" +msgstr "" + +msgid "Commits per day of month" +msgstr "" + +msgid "Commits per weekday" +msgstr "" + +msgid "Commits|An error occurred while fetching merge requests data." +msgstr "" + +msgid "Commits|Commit: %{commitText}" +msgstr "" + msgid "Commits|History" msgstr "Historio" +msgid "Commits|No related merge requests found" +msgstr "" + msgid "Committed by" msgstr "Enmetita de" msgid "Compare" msgstr "Kompari" +msgid "Compare Git revisions" +msgstr "" + +msgid "Compare Revisions" +msgstr "" + +msgid "CompareBranches|%{source_branch} and %{target_branch} are the same." +msgstr "" + +msgid "CompareBranches|Compare" +msgstr "" + +msgid "CompareBranches|Source" +msgstr "" + +msgid "CompareBranches|Target" +msgstr "" + +msgid "CompareBranches|There isn't anything to compare." +msgstr "" + +msgid "Confidentiality" +msgstr "" + msgid "Container Registry" msgstr "" @@ -884,6 +1094,9 @@ msgstr "Gvidlinioj por kontribuado" msgid "Contributors" msgstr "Kontribuantoj" +msgid "ContributorsPage|%{startDate} – %{endDate}" +msgstr "" + msgid "ContributorsPage|Building repository graph." msgstr "" @@ -905,9 +1118,18 @@ msgstr "" msgid "Copy URL to clipboard" msgstr "Kopii la adreson en la kopibufron" +msgid "Copy branch name to clipboard" +msgstr "" + msgid "Copy commit SHA to clipboard" msgstr "Kopii la identigilon de la enmetado" +msgid "Copy reference to clipboard" +msgstr "" + +msgid "Create" +msgstr "" + msgid "Create New Directory" msgstr "Krei novan dosierujon" @@ -926,6 +1148,9 @@ msgstr "" msgid "Create file" msgstr "" +msgid "Create lists from labels. Issues with that label appear in that list." +msgstr "" + msgid "Create merge request" msgstr "Krei peton pri kunfando" @@ -938,6 +1163,9 @@ msgstr "" msgid "Create new file" msgstr "" +msgid "Create new label" +msgstr "" + msgid "Create new..." msgstr "Krei novan…" @@ -959,6 +1187,9 @@ msgstr "Horzono por Cron" msgid "Cron syntax" msgstr "La sintakso de Cron" +msgid "Current node" +msgstr "" + msgid "Custom notification events" msgstr "Propraj sciigaj eventoj" @@ -968,9 +1199,6 @@ msgstr "La propraj sciigaj niveloj estas la samaj kiel la niveloj de partoprenad msgid "Cycle Analytics" msgstr "Cikla analizo" -msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project." -msgstr "La cikla analizo esploras kiom da tempo necesas por disvolvi ideon Äis Äi fariÄos realaĵo." - msgid "CycleAnalyticsStage|Code" msgstr "Programado" @@ -1027,12 +1255,21 @@ msgstr "" msgid "Details" msgstr "" +msgid "Diffs|No file name available" +msgstr "" + msgid "Directory name" msgstr "Nomo de dosierujo" +msgid "Disable" +msgstr "" + msgid "Discard changes" msgstr "" +msgid "Discover GitLab Geo." +msgstr "" + msgid "Dismiss Cycle Analytics introduction box" msgstr "" @@ -1069,15 +1306,24 @@ msgstr "Normala dosiero kun diferencoj" msgid "DownloadSource|Download" msgstr "ElÅuti" +msgid "Due date" +msgstr "" + msgid "Edit" msgstr "Redakti" msgid "Edit Pipeline Schedule %{id}" msgstr "Redakti ĉenstablan planon %{id}" +msgid "Edit files in the editor and commit changes here" +msgstr "" + msgid "Emails" msgstr "" +msgid "Enable" +msgstr "" + msgid "Environments|An error occurred while fetching the environments." msgstr "" @@ -1096,9 +1342,6 @@ msgstr "" msgid "Environments|Environments" msgstr "" -msgid "Environments|Environments are places where code gets deployed, such as staging or production." -msgstr "" - msgid "Environments|Job" msgstr "" @@ -1141,9 +1384,33 @@ msgstr "" msgid "Error creating epic" msgstr "" +msgid "Error fetching contributors data." +msgstr "" + +msgid "Error fetching labels." +msgstr "" + +msgid "Error fetching network graph." +msgstr "" + +msgid "Error fetching refs" +msgstr "" + +msgid "Error fetching usage ping data." +msgstr "" + msgid "Error occurred when toggling the notification subscription" msgstr "" +msgid "Error saving label update." +msgstr "" + +msgid "Error updating status for all todos." +msgstr "" + +msgid "Error updating todo status." +msgstr "" + msgid "EventFilterBy|Filter by all" msgstr "" @@ -1171,6 +1438,9 @@ msgstr "Ĉiumonate (en la 1a de la monato, je 4:00)" msgid "Every week (Sundays at 4:00am)" msgstr "Ĉiusemajne (en dimanĉo, je 4:00)" +msgid "Expand" +msgstr "" + msgid "Explore projects" msgstr "" @@ -1189,6 +1459,9 @@ msgstr "" msgid "February" msgstr "" +msgid "Fields on this page are now uneditable, you can configure" +msgstr "" + msgid "File name" msgstr "" @@ -1233,10 +1506,10 @@ msgstr "De la kunfandado de la peto pri kunfando Äis la disponigado en la publi msgid "GPG Keys" msgstr "" -msgid "Geo Nodes" +msgid "Generate a default set of labels" msgstr "" -msgid "GeoNodeSyncStatus|Failed" +msgid "Geo Nodes" msgstr "" msgid "GeoNodeSyncStatus|Node is failing or broken." @@ -1245,16 +1518,100 @@ msgstr "" msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage." msgstr "" -msgid "GeoNodeSyncStatus|Out of sync" +msgid "GeoNodes|Database replication lag:" +msgstr "" + +msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?" +msgstr "" + +msgid "GeoNodes|Does not match the primary storage configuration" +msgstr "" + +msgid "GeoNodes|Failed" +msgstr "" + +msgid "GeoNodes|Full" +msgstr "" + +msgid "GeoNodes|GitLab version does not match the primary node version" +msgstr "" + +msgid "GeoNodes|GitLab version:" +msgstr "" + +msgid "GeoNodes|Health status:" +msgstr "" + +msgid "GeoNodes|Last event ID processed by cursor:" +msgstr "" + +msgid "GeoNodes|Last event ID seen from primary:" +msgstr "" + +msgid "GeoNodes|Loading nodes" +msgstr "" + +msgid "GeoNodes|Local Attachments:" +msgstr "" + +msgid "GeoNodes|Local LFS objects:" +msgstr "" + +msgid "GeoNodes|Local job artifacts:" +msgstr "" + +msgid "GeoNodes|New node" +msgstr "" + +msgid "GeoNodes|Out of sync" msgstr "" -msgid "GeoNodeSyncStatus|Synced" +msgid "GeoNodes|Replication slot WAL:" +msgstr "" + +msgid "GeoNodes|Replication slots:" +msgstr "" + +msgid "GeoNodes|Repositories:" +msgstr "" + +msgid "GeoNodes|Selective" +msgstr "" + +msgid "GeoNodes|Storage config:" +msgstr "" + +msgid "GeoNodes|Sync settings:" +msgstr "" + +msgid "GeoNodes|Synced" +msgstr "" + +msgid "GeoNodes|Unused slots" +msgstr "" + +msgid "GeoNodes|Used slots" +msgstr "" + +msgid "GeoNodes|Wikis:" +msgstr "" + +msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS." +msgstr "" + +msgid "Geo|All projects" msgstr "" msgid "Geo|File sync capacity" msgstr "" -msgid "Geo|Groups to replicate" +msgid "Geo|Groups to synchronize" +msgstr "" + +msgid "Geo|Projects in certain groups" +msgstr "" + +msgid "Geo|Projects in certain storage shards" msgstr "" msgid "Geo|Repository sync capacity" @@ -1263,12 +1620,24 @@ msgstr "" msgid "Geo|Select groups to replicate." msgstr "" +msgid "Geo|Shards to synchronize" +msgstr "" + +msgid "Git revision" +msgstr "" + msgid "Git storage health information has been reset" msgstr "" +msgid "Git version" +msgstr "" + msgid "GitLab Runner section" msgstr "" +msgid "Gitaly Servers" +msgstr "" + msgid "Go to your fork" msgstr "Al via disbranĉigo" @@ -1278,6 +1647,9 @@ msgstr "Disbranĉigo" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it!" +msgstr "" + msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" msgstr "" @@ -1314,7 +1686,7 @@ msgstr "" msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group." msgstr "" -msgid "GroupsTree|Are you sure you want to leave the \"${this.group.fullName}\" group?" +msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?" msgstr "" msgid "GroupsTree|Create a project in this group." @@ -1365,6 +1737,11 @@ msgstr "" msgid "HealthCheck|Unhealthy" msgstr "" +msgid "Hide value" +msgid_plural "Hide values" +msgstr[0] "" +msgstr[1] "" + msgid "History" msgstr "" @@ -1391,6 +1768,12 @@ msgid_plural "Instances" msgstr[0] "" msgstr[1] "" +msgid "Instance does not support multiple Kubernetes clusters" +msgstr "" + +msgid "Interested parties can even contribute by pushing commits if they want to." +msgstr "" + msgid "Internal - The group and any internal projects can be viewed by any logged in user." msgstr "" @@ -1418,6 +1801,9 @@ msgstr "" msgid "Issues" msgstr "" +msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable." +msgstr "" + msgid "Jan" msgstr "" @@ -1436,6 +1822,27 @@ msgstr "" msgid "June" msgstr "" +msgid "Kubernetes" +msgstr "" + +msgid "Kubernetes Cluster" +msgstr "" + +msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}" +msgstr "" + +msgid "Kubernetes cluster integration was not removed." +msgstr "" + +msgid "Kubernetes cluster integration was successfully removed." +msgstr "" + +msgid "Kubernetes cluster was successfully updated." +msgstr "" + +msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page" +msgstr "" + msgid "LFSStatus|Disabled" msgstr "MalÅaltita" @@ -1445,6 +1852,9 @@ msgstr "Åœaltita" msgid "Labels" msgstr "" +msgid "Labels can be applied to issues and merge requests to categorize them." +msgstr "" + msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "La lasta %d tago" @@ -1474,6 +1884,9 @@ msgstr "" msgid "LastPushEvent|at" msgstr "" +msgid "Learn more" +msgstr "" + msgid "Learn more in the" msgstr "Lernu pli en la" @@ -1492,14 +1905,18 @@ msgstr "Forlasi la projekton" msgid "License" msgstr "" -msgid "Limited to showing %d event at most" -msgid_plural "Limited to showing %d events at most" -msgstr[0] "Limigita al montrado de ne pli ol %d evento" -msgstr[1] "Limigita al montrado de ne pli ol %d eventoj" +msgid "Loading the GitLab IDE..." +msgstr "" msgid "Lock" msgstr "" +msgid "Lock %{issuableDisplayName}" +msgstr "" + +msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment." +msgstr "" + msgid "Locked" msgstr "" @@ -1509,12 +1926,21 @@ msgstr "" msgid "Login" msgstr "" +msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos." +msgstr "" + +msgid "Manage labels" +msgstr "" + msgid "Mar" msgstr "" msgid "March" msgstr "" +msgid "Mark done" +msgstr "" + msgid "Maximum git storage failures" msgstr "" @@ -1527,6 +1953,9 @@ msgstr "Mediano" msgid "Members" msgstr "" +msgid "Merge Request" +msgstr "" + msgid "Merge Requests" msgstr "" @@ -1536,9 +1965,30 @@ msgstr "" msgid "Merge request" msgstr "" +msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others" +msgstr "" + +msgid "Merged" +msgstr "" + msgid "Messages" msgstr "" +msgid "Milestone" +msgstr "" + +msgid "Milestones|Delete milestone" +msgstr "" + +msgid "Milestones|Delete milestone %{milestoneTitle}?" +msgstr "" + +msgid "Milestones|Failed to delete milestone %{milestoneTitle}" +msgstr "" + +msgid "Milestones|Milestone %{milestoneTitle} was not found" +msgstr "" + msgid "MissingSSHKeyWarningLink|add an SSH key" msgstr "aldonos SSH-Ålosilon" @@ -1548,10 +1998,16 @@ msgstr "" msgid "More information is available|here" msgstr "" +msgid "Move" +msgstr "" + +msgid "Move issue" +msgstr "" + msgid "Multiple issue boards" msgstr "" -msgid "New Cluster" +msgid "Name new label" msgstr "" msgid "New Issue" @@ -1559,6 +2015,12 @@ msgid_plural "New Issues" msgstr[0] "Nova problemo" msgstr[1] "Novaj problemoj" +msgid "New Kubernetes Cluster" +msgstr "" + +msgid "New Kubernetes cluster" +msgstr "" + msgid "New Pipeline Schedule" msgstr "Nova ĉenstabla plano" @@ -1583,6 +2045,9 @@ msgstr "" msgid "New issue" msgstr "Nova problemo" +msgid "New label" +msgstr "" + msgid "New merge request" msgstr "Nova peto pri kunfando" @@ -1601,7 +2066,22 @@ msgstr "" msgid "New tag" msgstr "Nova etikedo" -msgid "No container images stored for this project. Add one by following the instructions above." +msgid "No assignee" +msgstr "" + +msgid "No changes" +msgstr "" + +msgid "No connection could be made to a Gitaly Server, please check your logs!" +msgstr "" + +msgid "No due date" +msgstr "" + +msgid "No estimate or time spent" +msgstr "" + +msgid "No file chosen" msgstr "" msgid "No repository" @@ -1616,9 +2096,15 @@ msgstr "" msgid "None" msgstr "" +msgid "Not allowed to merge" +msgstr "" + msgid "Not available" msgstr "Ne disponebla" +msgid "Not confidential" +msgstr "" + msgid "Not enough data" msgstr "Ne estas sufiĉe da datenoj" @@ -1679,6 +2165,12 @@ msgstr "Rigardado" msgid "Notifications" msgstr "" +msgid "Notifications off" +msgstr "" + +msgid "Notifications on" +msgstr "" + msgid "Nov" msgstr "" @@ -1688,7 +2180,7 @@ msgstr "" msgid "Number of access attempts" msgstr "" -msgid "Number of failures before backing off" +msgid "OK" msgstr "" msgid "Oct" @@ -1703,6 +2195,9 @@ msgstr "Filtrilo" msgid "Only project members can comment." msgstr "" +msgid "Open" +msgstr "" + msgid "Opened" msgstr "" @@ -1736,9 +2231,6 @@ msgstr "" msgid "Password" msgstr "" -msgid "People without permission will never get a notification and won\\'t be able to comment." -msgstr "" - msgid "Pipeline" msgstr "Ĉenstablo" @@ -1781,12 +2273,6 @@ msgstr "Ĉiuj" msgid "PipelineSchedules|Inactive" msgstr "MalÅaltitaj" -msgid "PipelineSchedules|Input variable key" -msgstr "Entajpu Ålosilon por la variablo" - -msgid "PipelineSchedules|Input variable value" -msgstr "Entajpu la valoron de la variablo" - msgid "PipelineSchedules|Next Run" msgstr "Sekvanta plenumo" @@ -1796,9 +2282,6 @@ msgstr "Nenio" msgid "PipelineSchedules|Provide a short description for this pipeline" msgstr "Entajpu mallongan priskribon pri ĉi tiu ĉenstablo" -msgid "PipelineSchedules|Remove variable row" -msgstr "Forigi la variablan linion" - msgid "PipelineSchedules|Take ownership" msgstr "Akiri posedon" @@ -1826,6 +2309,12 @@ msgstr "" msgid "Pipelines for last year" msgstr "" +msgid "Pipelines|Build with confidence" +msgstr "" + +msgid "Pipelines|Get started with Pipelines" +msgstr "" + msgid "Pipeline|all" msgstr "ĉiuj" @@ -1838,12 +2327,21 @@ msgstr "kun etapo" msgid "Pipeline|with stages" msgstr "kun etapoj" +msgid "Play" +msgstr "" + +msgid "Please <a href=%{link_to_billing} target=\"_blank\" rel=\"noopener noreferrer\">enable billing for one of your projects to be able to create a Kubernetes cluster</a>, then try again." +msgstr "" + msgid "Please solve the reCAPTCHA" msgstr "" msgid "Preferences" msgstr "" +msgid "Primary" +msgstr "" + msgid "Private - Project access must be granted explicitly to each user." msgstr "" @@ -1889,6 +2387,9 @@ msgstr "" msgid "Profiles|your account" msgstr "" +msgid "Programming languages used in this repository" +msgstr "" + msgid "Project '%{project_name}' is in the process of being deleted." msgstr "" @@ -1904,6 +2405,15 @@ msgstr "La projekto „%{project_name}“ estis sukcese Äisdatigita." msgid "Project access must be granted explicitly to each user." msgstr "Ĉiu uzanto devas akiri propran atingon al la projekto." +msgid "Project avatar" +msgstr "" + +msgid "Project avatar in repository: %{link}" +msgstr "" + +msgid "Project cache successfully reset." +msgstr "" + msgid "Project details" msgstr "" @@ -1922,6 +2432,21 @@ msgstr "La elporto de la projekto komenciÄis. Vi ricevos ligilon per retpoÅto msgid "ProjectActivityRSS|Subscribe" msgstr "" +msgid "ProjectCreationLevel|Allowed to create projects" +msgstr "" + +msgid "ProjectCreationLevel|Default project creation protection" +msgstr "" + +msgid "ProjectCreationLevel|Developers + Masters" +msgstr "" + +msgid "ProjectCreationLevel|Masters" +msgstr "" + +msgid "ProjectCreationLevel|No one" +msgstr "" + msgid "ProjectFeature|Disabled" msgstr "MalÅaltita" @@ -1946,15 +2471,9 @@ msgstr "Grafeo" msgid "ProjectSettings|Contact an admin to change this setting." msgstr "" -msgid "ProjectSettings|Immediately run a pipeline on the default branch" -msgstr "" - msgid "ProjectSettings|Only signed commits can be pushed to this repository." msgstr "" -msgid "ProjectSettings|Problem setting up the CI/CD settings JavaScript" -msgstr "" - msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin." msgstr "" @@ -2018,12 +2537,15 @@ msgstr "" msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/" msgstr "" -msgid "PrometheusService|Prometheus monitoring" +msgid "PrometheusService|Time-series monitoring service" msgstr "" msgid "PrometheusService|View environments" msgstr "" +msgid "Protip:" +msgstr "" + msgid "Public - The group and any public projects can be viewed without any authentication." msgstr "" @@ -2039,6 +2561,9 @@ msgstr "" msgid "PushRule|Committer restriction" msgstr "" +msgid "Quick actions can be used in the issues description and comment boxes." +msgstr "" + msgid "Read more" msgstr "Legu pli" @@ -2051,6 +2576,12 @@ msgstr "Branĉoj" msgid "RefSwitcher|Tags" msgstr "Etikedoj" +msgid "Reference:" +msgstr "" + +msgid "Register / Sign In" +msgstr "" + msgid "Registry" msgstr "" @@ -2075,9 +2606,18 @@ msgstr "Rilataj aplikitaj petoj pri kunfando" msgid "Remind later" msgstr "Rememorigu denove" +msgid "Remove" +msgstr "" + +msgid "Remove avatar" +msgstr "" + msgid "Remove project" msgstr "Forigi la projekton" +msgid "Repair authentication" +msgstr "" + msgid "Repository" msgstr "" @@ -2093,6 +2633,11 @@ msgstr "" msgid "Reset runners registration token" msgstr "" +msgid "Reveal value" +msgid_plural "Reveal values" +msgstr[0] "" +msgstr[1] "" + msgid "Revert this commit" msgstr "Malfari ĉi tiun enmetadon" @@ -2102,15 +2647,15 @@ msgstr "Malfari ĉi tiun peton pri kunfando" msgid "SSH Keys" msgstr "" -msgid "Save" -msgstr "" - msgid "Save changes" msgstr "" msgid "Save pipeline schedule" msgstr "Konservi ĉenstablan planon" +msgid "Save variables" +msgstr "" + msgid "Schedule a new pipeline" msgstr "Plani novan ĉenstablon" @@ -2126,38 +2671,59 @@ msgstr "" msgid "Search branches and tags" msgstr "Serĉu branĉon aÅ etikedon" -msgid "Seconds before reseting failure information" +msgid "Search milestones" +msgstr "" + +msgid "Search project" msgstr "" -msgid "Seconds to wait after a storage failure" +msgid "Search users" +msgstr "" + +msgid "Seconds before reseting failure information" msgstr "" msgid "Seconds to wait for a storage access attempt" msgstr "" +msgid "Secret variables" +msgstr "" + msgid "Select Archive Format" msgstr "Elektu formaton de arkivo" msgid "Select a timezone" msgstr "Elektu horzonon" +msgid "Select assignee" +msgstr "" + +msgid "Select branch/tag" +msgstr "" + msgid "Select target branch" msgstr "Elektu celan branĉon" +msgid "Selective synchronization" +msgstr "" + msgid "Sep" msgstr "" msgid "September" msgstr "" +msgid "Server version" +msgstr "" + msgid "Service Templates" msgstr "" msgid "Set a password on your account to pull or push via %{protocol}." msgstr "Kreu pasvorton por via konto por ebligi al vi eltiri kaj alpuÅi per %{protocol}." -msgid "Set up CI" -msgstr "Agordi SI" +msgid "Set up CI/CD" +msgstr "" msgid "Set up Koding" msgstr "Agordi „Koding“" @@ -2171,6 +2737,15 @@ msgstr "kreos pasvorton" msgid "Settings" msgstr "" +msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero." +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset pipeline minutes" +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes" +msgstr "" + msgid "Show parent pages" msgstr "" @@ -2185,9 +2760,6 @@ msgstr[1] "Estas montrataj %d eventoj" msgid "Sidebar|Change weight" msgstr "" -msgid "Sidebar|Edit" -msgstr "" - msgid "Sidebar|No" msgstr "" @@ -2200,18 +2772,30 @@ msgstr "" msgid "Snippets" msgstr "" +msgid "Something went wrong on our end" +msgstr "" + msgid "Something went wrong on our end." msgstr "" +msgid "Something went wrong trying to change the confidentiality of this issue" +msgstr "" + msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}" msgstr "" +msgid "Something went wrong when toggling the button" +msgstr "" + msgid "Something went wrong while fetching the projects." msgstr "" msgid "Something went wrong while fetching the registry list." msgstr "" +msgid "Something went wrong. Please try again." +msgstr "" + msgid "Sort by" msgstr "" @@ -2341,10 +2925,10 @@ msgstr "" msgid "Stopped" msgstr "" -msgid "Subgroups" +msgid "Storage" msgstr "" -msgid "Subscribe" +msgid "Subgroups" msgstr "" msgid "Switch branch/tag" @@ -2442,7 +3026,10 @@ msgstr "" msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project." msgstr "" -msgid "The circuitbreaker backoff threshold should be lower than the failure count threshold" +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project" +msgstr "" + +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project." msgstr "" msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request." @@ -2457,10 +3044,10 @@ msgstr "La rilato de disbranĉigo estis forigita." msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage." msgstr "La etapo de la problemo montras kiom la tempo pasas de la kreado de problemo Äis la atribuado de la problemo al cela etapo de la projekto, aÅ al listo sur la problemtabulo. Komencu krei problemojn por vidi la datenojn por ĉi tiu etapo." -msgid "The number of attempts GitLab will make to access a storage." +msgid "The maximum file size allowed is 200KB." msgstr "" -msgid "The number of failures after which GitLab will start temporarily disabling access to a storage shard on a host" +msgid "The number of attempts GitLab will make to access a storage." msgstr "" msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}." @@ -2469,9 +3056,6 @@ msgstr "" msgid "The phase of the development lifecycle." msgstr "La etapo de la disvolva ciklo." -msgid "The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user." -msgstr "La ĉenstabla plano plenumas ĉenstablojn en la estonteco, ripete, por difinitaj branĉoj aÅ etikedoj. Tiuj planitaj ĉenstabloj heredos la limigitan atingon al la projekto de la rilata uzanto." - msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit." msgstr "La etapo de la plano montras la tempon de la antaÅa Åtupo Äis la alpuÅado de via unua enmetado. Ĉi tiu tempo aldoniÄos aÅtomate post kiam vi alpuÅas la unuan enmetadon." @@ -2502,19 +3086,46 @@ msgstr "" msgid "The time in seconds GitLab will try to access storage. After this time a timeout error will be raised." msgstr "" +msgid "The time in seconds between storage checks. When a previous check did complete yet, GitLab will skip a check." +msgstr "" + msgid "The time taken by each data entry gathered by that stage." msgstr "La tempo, kiu estas necesa por ĉiu dateno kolektita de la etapo." msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6." msgstr "La valoro, kiu troviÄas en la mezo de aro da rigardataj valoroj. Ekzemple: inter 3, 5 kaj 9, la mediano estas 5. Inter 3, 5, 7 kaj 8, la mediano estas (5+7)/2 = 6." +msgid "There are no issues to show" +msgstr "" + +msgid "There are no merge requests to show" +msgstr "" + msgid "There are problems accessing Git storage: " msgstr "" +msgid "There was an error loading users activity calendar." +msgstr "" + +msgid "There was an error saving your notification settings." +msgstr "" + +msgid "There was an error subscribing to this label." +msgstr "" + +msgid "There was an error when reseting email token." +msgstr "" + +msgid "There was an error when subscribing to this label." +msgstr "" + +msgid "There was an error when unsubscribing from this label." +msgstr "" + msgid "This board\\'s scope is reduced" msgstr "" -msgid "This branch has changed since you started editing. Would you like to create a new branch?" +msgid "This directory" msgstr "" msgid "This is a confidential issue." @@ -2523,18 +3134,45 @@ msgstr "" msgid "This is the author's first Merge Request to this project." msgstr "" +msgid "This issue is confidential" +msgstr "" + msgid "This issue is confidential and locked." msgstr "" msgid "This issue is locked." msgstr "" +msgid "This job depends on a user to trigger its process. Often they are used to deploy code to production environments" +msgstr "" + +msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered" +msgstr "" + +msgid "This job has not been triggered yet" +msgstr "" + +msgid "This job has not started yet" +msgstr "" + +msgid "This job is in pending state and is waiting to be picked by a runner" +msgstr "" + +msgid "This job requires a manual action" +msgstr "" + msgid "This means you can not push code until you create an empty repository or import existing one." msgstr "Ĉi tiu signifas, ke vi ne povos alpuÅi kodon, antaÅ ol vi kreos malplenan deponejon aÅ enportos jam ekzistantan." msgid "This merge request is locked." msgstr "" +msgid "This project" +msgstr "" + +msgid "This repository" +msgstr "" + msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here." msgstr "" @@ -2547,9 +3185,21 @@ msgstr "Tempo antaÅ la komenco de laboro super problemo" msgid "Time between merge request creation and merge/close" msgstr "Tempo inter la kreado de poeto pri kunfando kaj Äia aplikado/fermado" +msgid "Time tracking" +msgstr "" + msgid "Time until first merge request" msgstr "Tempo Äis la unua peto pri kunfando" +msgid "TimeTrackingEstimated|Est" +msgstr "" + +msgid "TimeTracking|Estimated:" +msgstr "" + +msgid "TimeTracking|Spent" +msgstr "" + msgid "Timeago|%s days ago" msgstr "antaÅ %s tagoj" @@ -2689,6 +3339,18 @@ msgstr "s" msgid "Title" msgstr "" +msgid "Todo" +msgstr "" + +msgid "Toggle sidebar" +msgstr "" + +msgid "ToggleButton|Toggle Status: OFF" +msgstr "" + +msgid "ToggleButton|Toggle Status: ON" +msgstr "" + msgid "Total Time" msgstr "Totala tempo" @@ -2704,19 +3366,40 @@ msgstr "" msgid "Track groups of issues that share a theme, across projects and milestones" msgstr "" +msgid "Total: %{total}" +msgstr "" + +msgid "Track time with quick actions" +msgstr "" + +msgid "Trigger this manual action" +msgstr "" + msgid "Turn on Service Desk" msgstr "" +msgid "Type %{value} to confirm:" +msgstr "" + +msgid "Unable to reset project cache." +msgstr "" + +msgid "Unknown" +msgstr "" + msgid "Unlock" msgstr "" +msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment." +msgstr "" + msgid "Unlocked" msgstr "" msgid "Unstar" msgstr "Malsteligi" -msgid "Unsubscribe" +msgid "Up to date" msgstr "" msgid "Upgrade your plan to activate Advanced Global Search." @@ -2740,6 +3423,9 @@ msgstr "AlÅuti novan dosieron" msgid "Upload file" msgstr "AlÅuti dosieron" +msgid "Upload new avatar" +msgstr "" + msgid "UploadLink|click to upload" msgstr "alklaku por alÅuti" @@ -2752,9 +3438,15 @@ msgstr "" msgid "Use your global notification setting" msgstr "Uzi vian Äeneralan agordon pri la sciigoj" +msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." +msgstr "" + msgid "View file @ " msgstr "" +msgid "View labels" +msgstr "" + msgid "View open merge request" msgstr "Vidi la malfermitan peton pri kunfando" @@ -2776,6 +3468,9 @@ msgstr "Nekonata" msgid "Want to see the data? Please ask an administrator for access." msgstr "Ĉu vi volas vidi la datenojn? Bonvolu peti atingeblon de administranto." +msgid "We could not verify that one of your projects on GCP has billing enabled. Please try again." +msgstr "" + msgid "We don't have enough data to show this stage." msgstr "Ne estas sufiĉe da datenoj por montri ĉi tiun etapon." @@ -2788,9 +3483,6 @@ msgstr "" msgid "Weight" msgstr "" -msgid "When access to a storage fails. GitLab will prevent access to the storage for the time specified here. This allows the filesystem to recover. Repositories on failing shards are temporarly unavailable" -msgstr "" - msgid "Wiki" msgstr "" @@ -2809,6 +3501,12 @@ msgstr "" msgid "WikiClone|Start Gollum and edit locally" msgstr "" +msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title." +msgstr "" + +msgid "WikiEdit|There is already a page with the same title in that path." +msgstr "" + msgid "WikiEmptyPageError|You are not allowed to create wiki pages" msgstr "" @@ -2911,9 +3609,21 @@ msgstr "Vi forigos la rilaton de la disbranĉigo al la originala projekto, „%{ msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" msgstr "Vi estas transigonta „%{project_name_with_namespace}“ al alia posedanto. Ĉu vi estas ABSOLUTE certa?" +msgid "You can also star a label to make it a priority label." +msgstr "" + +msgid "You can move around the graph by using the arrow keys." +msgstr "" + +msgid "You can move around the graph by using the arrow keys." +msgstr "" + msgid "You can only add files when you are on a branch" msgstr "Oni povas aldoni dosierojn nur kiam oni estas en branĉo" +msgid "You can only edit files when you are on a branch" +msgstr "" + msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead." msgstr "" @@ -2953,6 +3663,12 @@ msgstr "Vi ne povos eltiri aÅ alpuÅi kodon per SSH antaÅ ol vi %{add_ssh_key_ msgid "You won't be able to pull or push project code via SSH until you add an SSH key to your profile" msgstr "" +msgid "You'll need to use different branch names to get a valid comparison." +msgstr "" + +msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure" +msgstr "" + msgid "Your comment will not be visible to the public." msgstr "" @@ -2965,26 +3681,220 @@ msgstr "Via nomo" msgid "Your projects" msgstr "" +msgid "assign yourself" +msgstr "" + msgid "branch name" msgstr "" msgid "by" msgstr "" +msgid "ciReport|Code quality" +msgstr "" + +msgid "ciReport|DAST detected no alerts by analyzing the review app" +msgstr "" + +msgid "ciReport|Failed to load ${type} report" +msgstr "" + +msgid "ciReport|Fixed:" +msgstr "" + +msgid "ciReport|Instances" +msgstr "" + +msgid "ciReport|Learn more about whitelisting" +msgstr "" + +msgid "ciReport|Loading ${type} report" +msgstr "" + +msgid "ciReport|No changes to code quality" +msgstr "" + +msgid "ciReport|No changes to performance metrics" +msgstr "" + +msgid "ciReport|Performance metrics" +msgstr "" + +msgid "ciReport|SAST" +msgstr "" + +msgid "ciReport|SAST detected no security vulnerabilities" +msgstr "" + +msgid "ciReport|SAST:container no vulnerabilities were found" +msgstr "" + +msgid "ciReport|Show complete code vulnerabilities report" +msgstr "" + +msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}" +msgstr "" + msgid "commit" msgstr "" +msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue." +msgstr "" + +msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue." +msgstr "" + msgid "day" msgid_plural "days" msgstr[0] "tago" msgstr[1] "tagoj" +msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command." +msgstr "" + +msgid "merge request" +msgid_plural "merge requests" +msgstr[0] "" +msgstr[1] "" + +msgid "mrWidget|Cancel automatic merge" +msgstr "" + +msgid "mrWidget|Check out branch" +msgstr "" + +msgid "mrWidget|Checking ability to merge automatically" +msgstr "" + +msgid "mrWidget|Cherry-pick" +msgstr "" + +msgid "mrWidget|Cherry-pick this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Closed" +msgstr "" + +msgid "mrWidget|Closed by" +msgstr "" + +msgid "mrWidget|Closes" +msgstr "" + +msgid "mrWidget|Did not close" +msgstr "" + +msgid "mrWidget|Email patches" +msgstr "" + +msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|Mentions" +msgstr "" + +msgid "mrWidget|Merge" +msgstr "" + +msgid "mrWidget|Merge failed." +msgstr "" + +msgid "mrWidget|Merge locally" +msgstr "" + +msgid "mrWidget|Merged by" +msgstr "" + +msgid "mrWidget|Plain diff" +msgstr "" + +msgid "mrWidget|Refresh" +msgstr "" + +msgid "mrWidget|Refresh now" +msgstr "" + +msgid "mrWidget|Refreshing now" +msgstr "" + +msgid "mrWidget|Remove Source Branch" +msgstr "" + +msgid "mrWidget|Remove source branch" +msgstr "" + +msgid "mrWidget|Request to merge" +msgstr "" + +msgid "mrWidget|Resolve conflicts" +msgstr "" + +msgid "mrWidget|Revert" +msgstr "" + +msgid "mrWidget|Revert this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Set by" +msgstr "" + +msgid "mrWidget|The changes were merged into" +msgstr "" + +msgid "mrWidget|The changes were not merged into" +msgstr "" + +msgid "mrWidget|The changes will be merged into" +msgstr "" + +msgid "mrWidget|The source branch has been removed" +msgstr "" + +msgid "mrWidget|The source branch is being removed" +msgstr "" + +msgid "mrWidget|The source branch will be removed" +msgstr "" + +msgid "mrWidget|The source branch will not be removed" +msgstr "" + +msgid "mrWidget|There are merge conflicts" +msgstr "" + +msgid "mrWidget|This merge request failed to be merged automatically" +msgstr "" + +msgid "mrWidget|This merge request is in the process of being merged" +msgstr "" + +msgid "mrWidget|This project is archived, write access has been disabled" +msgstr "" + +msgid "mrWidget|You can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|You can remove source branch now" +msgstr "" + +msgid "mrWidget|command line" +msgstr "" + +msgid "mrWidget|into" +msgstr "" + +msgid "mrWidget|to be merged automatically when the pipeline succeeds" +msgstr "" + msgid "new merge request" msgstr "novan peton pri kunfando" msgid "notification emails" msgstr "sciigoj per retpoÅto" +msgid "or" +msgstr "" + msgid "parent" msgid_plural "parents" msgstr[0] "patro" @@ -2996,12 +3906,21 @@ msgstr "" msgid "personal access token" msgstr "" +msgid "remove due date" +msgstr "" + msgid "source" msgstr "" +msgid "spendCommand|%{slash_command} will update the sum of the time spent." +msgstr "" + msgid "to help your contributors communicate effectively!" msgstr "" msgid "username" msgstr "" +msgid "uses Kubernetes clusters to deploy your code!" +msgstr "" + diff --git a/locale/es/gitlab.po b/locale/es/gitlab.po index 44ad3d4633a..7d28a7064d3 100644 --- a/locale/es/gitlab.po +++ b/locale/es/gitlab.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab-ee\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-12-12 18:31+0000\n" -"PO-Revision-Date: 2018-01-05 04:41-0500\n" +"POT-Creation-Date: 2018-02-07 11:38-0600\n" +"PO-Revision-Date: 2018-02-12 04:01-0500\n" "Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n" "Language-Team: Spanish\n" "Language: es_ES\n" @@ -16,13 +16,31 @@ msgstr "" "X-Crowdin-Language: es-ES\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" +msgid " and" +msgstr "" + msgid "%d commit" msgid_plural "%d commits" msgstr[0] "%d cambio" msgstr[1] "%d cambios" +msgid "%d commit behind" +msgid_plural "%d commits behind" +msgstr[0] "" +msgstr[1] "" + +msgid "%d issue" +msgid_plural "%d issues" +msgstr[0] "" +msgstr[1] "" + msgid "%d layer" msgid_plural "%d layers" +msgstr[0] "%d capa" +msgstr[1] "%d capas" + +msgid "%d merge request" +msgid_plural "%d merge requests" msgstr[0] "" msgstr[1] "" @@ -31,50 +49,47 @@ msgid_plural "%s additional commits have been omitted to prevent performance iss msgstr[0] "%s cambio adicional ha sido omitido para evitar problemas de rendimiento." msgstr[1] "%s cambios adicionales han sido omitidos para evitar problemas de rendimiento." -msgid "%{commit_author_link} committed %{commit_timeago}" -msgstr "%{commit_author_link} cambió %{commit_timeago}" +msgid "%{commit_author_link} authored %{commit_timeago}" +msgstr "" msgid "%{count} participant" msgid_plural "%{count} participants" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%{count} participante" +msgstr[1] "%{count} participantes" msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead" -msgstr "" +msgstr "%{number_commits_behind} commits detrás de %{default_branch}, %{number_commits_ahead} commits por delante" msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will allow access on the next attempt." -msgstr "" - -msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will block access for %{number_of_seconds} seconds." -msgstr "" +msgstr "%{number_of_failures} de %{maximum_failures} intentos fallidos. GitLab permitirá el acceso en el siguiente intento." msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved." -msgstr "" +msgstr "%{number_of_failures} de %{maximum_failures} intentos fallidos. GitLab no reintentará automáticamente. Debe reinicializar la información de almacenamiento cuando el problema sea solventado." msgid "%{storage_name}: failed storage access attempt on host:" msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%{storage_name}: intento de acceso fallido al almacenamiento en host:" +msgstr[1] "%{storage_name}: %{failed_attempts} intentos de acceso fallido al almacenamiento:" msgid "%{text} is available" -msgstr "" +msgstr "%{text} esta disponible" msgid "(checkout the %{link} for information on how to install it)." -msgstr "" +msgstr "(para obtener información sobre cómo instalarlo visite %{link})." msgid "+ %{moreCount} more" -msgstr "" +msgstr "+ %{moreCount} más" msgid "- show less" -msgstr "" +msgstr "- mostrar menos" msgid "1 pipeline" msgid_plural "%d pipelines" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "1 pipeline" +msgstr[1] "%d pipelines" msgid "1st contribution!" -msgstr "" +msgstr "¡1ra contribución!" msgid "2FA enabled" msgstr "" @@ -86,16 +101,16 @@ msgid "About auto deploy" msgstr "Acerca del auto despliegue" msgid "Abuse Reports" -msgstr "" +msgstr "Reportes de abuso" msgid "Access Tokens" -msgstr "" +msgstr "Tokens de acceso" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" msgid "Account" -msgstr "" +msgstr "Cuenta" msgid "Active" msgstr "Activo" @@ -104,7 +119,7 @@ msgid "Activity" msgstr "Actividad" msgid "Add" -msgstr "" +msgstr "Añadir" msgid "Add Changelog" msgstr "Agregar Changelog" @@ -113,7 +128,7 @@ msgid "Add Contribution guide" msgstr "Agregar guÃa de contribución" msgid "Add Group Webhooks and GitLab Enterprise Edition." -msgstr "" +msgstr "Añadir Webhooks Grupales y Gitlab Enterprise Edition." msgid "Add License" msgstr "Agregar Licencia" @@ -121,38 +136,95 @@ msgstr "Agregar Licencia" msgid "Add new directory" msgstr "Agregar nuevo directorio" +msgid "Add todo" +msgstr "" + +msgid "AdminArea|Stop all jobs" +msgstr "" + +msgid "AdminArea|Stop all jobs?" +msgstr "" + +msgid "AdminArea|Stop jobs" +msgstr "" + +msgid "AdminArea|Stopping jobs failed" +msgstr "" + +msgid "AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running." +msgstr "" + msgid "AdminHealthPageLink|health page" +msgstr "Página de estado" + +msgid "Advanced" msgstr "" msgid "Advanced settings" -msgstr "" +msgstr "Configuración avanzada" msgid "All" msgstr "" -msgid "An error occurred when toggling the notification subscription" +msgid "All changes are committed" msgstr "" +msgid "Allows you to add and manage Kubernetes clusters." +msgstr "" + +msgid "An error occurred previewing the blob" +msgstr "" + +msgid "An error occurred when toggling the notification subscription" +msgstr "Se produjo un error al activar/desactivar la suscripción de notificación" + msgid "An error occurred when updating the issue weight" +msgstr "Se produjo un error al actualizar el peso de la incidencia" + +msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." +msgstr "" + +msgid "An error occurred while fetching markdown preview" msgstr "" msgid "An error occurred while fetching sidebar data" +msgstr "Se produjo un error al obtener datos de la barra lateral" + +msgid "An error occurred while getting projects" msgstr "" -msgid "An error occurred. Please try again." +msgid "An error occurred while loading filenames" msgstr "" -msgid "Appearance" +msgid "An error occurred while rendering KaTeX" msgstr "" -msgid "Applications" +msgid "An error occurred while rendering preview broadcast message" +msgstr "" + +msgid "An error occurred while retrieving calendar activity" msgstr "" +msgid "An error occurred while retrieving diff" +msgstr "" + +msgid "An error occurred while validating username" +msgstr "" + +msgid "An error occurred. Please try again." +msgstr "Se produjo un error. Por favor inténtelo de nuevo." + +msgid "Appearance" +msgstr "Apariencia" + +msgid "Applications" +msgstr "Aplicaciones" + msgid "Apr" msgstr "" msgid "April" -msgstr "" +msgstr "Abril" msgid "Archived project! Repository is read-only" msgstr "¡Proyecto archivado! El repositorio es de solo lectura" @@ -161,45 +233,60 @@ msgid "Are you sure you want to delete this pipeline schedule?" msgstr "¿Estás seguro que deseas eliminar esta programación del pipeline?" msgid "Are you sure you want to discard your changes?" -msgstr "" +msgstr "¿Está seguro que desea descartar sus cambios?" + +msgid "Are you sure you want to reset registration token?" +msgstr "¿Está seguro que desea reinicializar el token de registro?" + +msgid "Are you sure you want to reset the health check token?" +msgstr "¿Está seguro que desea reinicializar el token de Verificación de Estado?" + +msgid "Are you sure?" +msgstr "¿Estás seguro?" + +msgid "Artifacts" +msgstr "Artefactos" -msgid "Are you sure you want to leave this group?" +msgid "Assign custom color like #FF0000" msgstr "" -msgid "Are you sure you want to reset registration token?" +msgid "Assign labels" msgstr "" -msgid "Are you sure you want to reset the health check token?" +msgid "Assign milestone" msgstr "" -msgid "Are you sure?" +msgid "Assign to" msgstr "" -msgid "Artifacts" +msgid "Assignee" msgstr "" msgid "Attach a file by drag & drop or %{upload_link}" msgstr "Adjunte un archivo arrastrando & soltando o %{upload_link}" msgid "Aug" -msgstr "" +msgstr "Ago" msgid "August" -msgstr "" +msgstr "Agosto" msgid "Authentication Log" -msgstr "" +msgstr "Registro de Autenticación" msgid "Author" +msgstr "Autor" + +msgid "Authors: %{authors}" msgstr "" -msgid "Auto Review Apps and Auto Deploy need a domain name and the %{kubernetes} to work correctly." +msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." msgstr "" -msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly." +msgid "Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly." msgstr "" -msgid "Auto Review Apps and Auto Deploy need the %{kubernetes} to work correctly." +msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly." msgstr "" msgid "AutoDevOps|Auto DevOps (Beta)" @@ -221,11 +308,17 @@ msgid "AutoDevOps|You can activate %{link_to_settings} for this project." msgstr "" msgid "Available" +msgstr "Disponible" + +msgid "Avatar will be removed. Are you sure?" msgstr "" -msgid "Billing" +msgid "Average per day: %{average}" msgstr "" +msgid "Billing" +msgstr "Facturación" + msgid "BillingPlans|%{group_name} is currently on the %{plan_link} plan." msgstr "" @@ -277,6 +370,9 @@ msgstr "" msgid "BillingPlans|per user" msgstr "" +msgid "Begin with the selected commit" +msgstr "" + msgid "Branch" msgid_plural "Branches" msgstr[0] "Rama" @@ -405,8 +501,8 @@ msgstr "por" msgid "CI / CD" msgstr "" -msgid "CI configuration" -msgstr "Configuración de CI" +msgid "CI/CD configuration" +msgstr "" msgid "CICD|Jobs" msgstr "" @@ -417,6 +513,9 @@ msgstr "Cancelar" msgid "Cancel edit" msgstr "" +msgid "Cannot modify managed Kubernetes cluster" +msgstr "" + msgid "Change Weight" msgstr "" @@ -432,15 +531,24 @@ msgstr "Cherry-pick" msgid "ChangeTypeAction|Revert" msgstr "Revertir" +msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes." +msgstr "" + msgid "Changelog" msgstr "" +msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision." +msgstr "" + msgid "Charts" msgstr "Gráficos" msgid "Chat" msgstr "" +msgid "Check interval" +msgstr "" + msgid "Checking %{text} availability…" msgstr "" @@ -453,7 +561,19 @@ msgstr "Escoger este cambio" msgid "Cherry-pick this merge request" msgstr "Escoger esta solicitud de fusión" -msgid "Choose which groups you wish to replicate to this secondary node. Leave blank to replicate all." +msgid "Choose File ..." +msgstr "" + +msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request." +msgstr "" + +msgid "Choose file..." +msgstr "" + +msgid "Choose which groups you wish to synchronize to this secondary node." +msgstr "" + +msgid "Choose which shards you wish to synchronize to this secondary node." msgstr "" msgid "CiStatusLabel|canceled" @@ -510,79 +630,91 @@ msgstr "omitido" msgid "CiStatus|running" msgstr "en ejecución" -msgid "CircuitBreakerApiLink|circuitbreaker api" +msgid "CiVariables|Input variable key" msgstr "" -msgid "Clone repository" +msgid "CiVariables|Input variable value" msgstr "" -msgid "Close" +msgid "CiVariables|Remove variable row" msgstr "" -msgid "Cluster" +msgid "CiVariable|* (All environments)" msgstr "" -msgid "ClusterIntegration|%{appList} was successfully installed on your cluster" +msgid "CiVariable|All environments" msgstr "" -msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which incur additional costs. See %{pricingLink}" +msgid "CiVariable|Create wildcard" msgstr "" -msgid "ClusterIntegration|API URL" +msgid "CiVariable|Error occured while saving variables" msgstr "" -msgid "ClusterIntegration|Active" +msgid "CiVariable|New environment" msgstr "" -msgid "ClusterIntegration|Add an existing cluster" +msgid "CiVariable|Protected" msgstr "" -msgid "ClusterIntegration|Add cluster" +msgid "CiVariable|Search environments" msgstr "" -msgid "ClusterIntegration|All" +msgid "CiVariable|Toggle protected" msgstr "" -msgid "ClusterIntegration|Applications" +msgid "CiVariable|Validation failed" msgstr "" -msgid "ClusterIntegration|CA Certificate" +msgid "CircuitBreakerApiLink|circuitbreaker api" msgstr "" -msgid "ClusterIntegration|Certificate Authority bundle (PEM format)" +msgid "Click to expand text" msgstr "" -msgid "ClusterIntegration|Choose how to set up cluster integration" +msgid "Clone repository" msgstr "" -msgid "ClusterIntegration|Cluster" +msgid "Close" msgstr "" -msgid "ClusterIntegration|Cluster details" +msgid "Closed" msgstr "" -msgid "ClusterIntegration|Cluster integration" +msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Cluster integration is disabled for this project." +msgid "ClusterIntegration|API URL" msgstr "" -msgid "ClusterIntegration|Cluster integration is enabled for this project." +msgid "ClusterIntegration|Add Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab's connection to it." +msgid "ClusterIntegration|Add an existing Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Cluster is being created on Google Kubernetes Engine..." +msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration" msgstr "" -msgid "ClusterIntegration|Cluster name" +msgid "ClusterIntegration|Applications" msgstr "" -msgid "ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine. Refresh the page to see cluster's details" +msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster." msgstr "" -msgid "ClusterIntegration|Clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" +msgid "ClusterIntegration|CA Certificate" +msgstr "" + +msgid "ClusterIntegration|Certificate Authority bundle (PEM format)" +msgstr "" + +msgid "ClusterIntegration|Choose how to set up Kubernetes cluster integration" +msgstr "" + +msgid "ClusterIntegration|Choose which of your project's environments will use this Kubernetes cluster." +msgstr "" + +msgid "ClusterIntegration|Control how your Kubernetes cluster integrates with GitLab" msgstr "" msgid "ClusterIntegration|Copy API URL" @@ -591,37 +723,34 @@ msgstr "" msgid "ClusterIntegration|Copy CA Certificate" msgstr "" -msgid "ClusterIntegration|Copy Token" +msgid "ClusterIntegration|Copy Kubernetes cluster name" msgstr "" -msgid "ClusterIntegration|Copy cluster name" +msgid "ClusterIntegration|Copy Token" msgstr "" -msgid "ClusterIntegration|Create a new cluster on Google Engine right from GitLab" +msgid "ClusterIntegration|Create Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Create cluster" +msgid "ClusterIntegration|Create Kubernetes cluster on Google Kubernetes Engine" msgstr "" -msgid "ClusterIntegration|Create cluster on Google Kubernetes Engine" +msgid "ClusterIntegration|Create a new Kubernetes cluster on Google Kubernetes Engine right from GitLab" msgstr "" msgid "ClusterIntegration|Create on GKE" msgstr "" -msgid "ClusterIntegration|Enable cluster integration" -msgstr "" - msgid "ClusterIntegration|Enter the details for an existing Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Enter the details for your cluster" +msgid "ClusterIntegration|Enter the details for your Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Environment pattern" +msgid "ClusterIntegration|Environment scope" msgstr "" -msgid "ClusterIntegration|GKE pricing" +msgid "ClusterIntegration|GitLab Integration" msgstr "" msgid "ClusterIntegration|GitLab Runner" @@ -639,64 +768,94 @@ msgstr "" msgid "ClusterIntegration|Helm Tiller" msgstr "" -msgid "ClusterIntegration|Inactive" -msgstr "" - msgid "ClusterIntegration|Ingress" msgstr "" msgid "ClusterIntegration|Install" msgstr "" -msgid "ClusterIntegration|Install applications on your cluster. Read more about %{helpLink}" -msgstr "" - msgid "ClusterIntegration|Installed" msgstr "" msgid "ClusterIntegration|Installing" msgstr "" -msgid "ClusterIntegration|Integrate cluster automation" +msgid "ClusterIntegration|Integrate Kubernetes cluster automation" +msgstr "" + +msgid "ClusterIntegration|Integration status" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster details" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is disabled for this project." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project. Disabling this integration will not affect your Kubernetes cluster, it will only temporarily turn off GitLab's connection to it." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster name" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project" msgstr "" msgid "ClusterIntegration|Learn more about %{link_to_documentation}" msgstr "" -msgid "ClusterIntegration|Learn more about Clusters" +msgid "ClusterIntegration|Learn more about Kubernetes" msgstr "" -msgid "ClusterIntegration|Machine type" +msgid "ClusterIntegration|Learn more about environments" msgstr "" -msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create clusters" +msgid "ClusterIntegration|Machine type" msgstr "" -msgid "ClusterIntegration|Manage cluster integration on your GitLab project" +msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters" msgstr "" -msgid "ClusterIntegration|Manage your cluster by visiting %{link_gke}" +msgid "ClusterIntegration|Manage" msgstr "" -msgid "ClusterIntegration|Multiple clusters are available in GitLab Entreprise Edition Premium and Ultimate" +msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}" msgstr "" -msgid "ClusterIntegration|Note:" +msgid "ClusterIntegration|More information" msgstr "" -msgid "ClusterIntegration|Number of nodes" +msgid "ClusterIntegration|Multiple Kubernetes clusters are available in GitLab Enterprise Edition Premium and Ultimate" msgstr "" -msgid "ClusterIntegration|Please enter access information for your cluster. If you need help, you can read our %{link_to_help_page} on clusters" +msgid "ClusterIntegration|Note:" msgstr "" -msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:" +msgid "ClusterIntegration|Number of nodes" msgstr "" -msgid "ClusterIntegration|Problem setting up the cluster" +msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes" msgstr "" -msgid "ClusterIntegration|Problem setting up the clusters list" +msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:" msgstr "" msgid "ClusterIntegration|Project ID" @@ -708,16 +867,19 @@ msgstr "" msgid "ClusterIntegration|Project namespace (optional, unique)" msgstr "" -msgid "ClusterIntegration|Read our %{link_to_help_page} on cluster integration." +msgid "ClusterIntegration|Prometheus" msgstr "" -msgid "ClusterIntegration|Remove cluster integration" +msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration." +msgstr "" + +msgid "ClusterIntegration|Remove Kubernetes cluster integration" msgstr "" msgid "ClusterIntegration|Remove integration" msgstr "" -msgid "ClusterIntegration|Removing cluster integration will remove the cluster configuration you have added to this project. It will not delete your cluster on Google Kubernetes Engine." +msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster." msgstr "" msgid "ClusterIntegration|Request to begin installing failed" @@ -726,7 +888,7 @@ msgstr "" msgid "ClusterIntegration|Save changes" msgstr "" -msgid "ClusterIntegration|See and edit the details for your cluster" +msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster" msgstr "" msgid "ClusterIntegration|See machine types" @@ -747,25 +909,25 @@ msgstr "" msgid "ClusterIntegration|Something went wrong on our end." msgstr "" -msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine" +msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine" msgstr "" msgid "ClusterIntegration|Something went wrong while installing %{title}" msgstr "" -msgid "ClusterIntegration|There are no clusters to show" +msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below" msgstr "" -msgid "ClusterIntegration|This account must have permissions to create a cluster in the %{link_to_container_project} specified below" +msgid "ClusterIntegration|Toggle Kubernetes Cluster" msgstr "" -msgid "ClusterIntegration|Toggle Cluster" +msgid "ClusterIntegration|Toggle Kubernetes cluster" msgstr "" msgid "ClusterIntegration|Token" msgstr "" -msgid "ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." +msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." msgstr "" msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}" @@ -777,7 +939,7 @@ msgstr "" msgid "ClusterIntegration|access to Google Kubernetes Engine" msgstr "" -msgid "ClusterIntegration|cluster" +msgid "ClusterIntegration|check the pricing here" msgstr "" msgid "ClusterIntegration|documentation" @@ -795,6 +957,9 @@ msgstr "" msgid "ClusterIntegration|properly configured" msgstr "" +msgid "Collapse" +msgstr "" + msgid "Comments" msgstr "" @@ -812,6 +977,9 @@ msgstr "Duración de los cambios en minutos para los últimos 30" msgid "Commit message" msgstr "Mensaje del cambio" +msgid "Commit statistics for %{ref} %{start_time} - %{end_time}" +msgstr "" + msgid "CommitBoxTitle|Commit" msgstr "Cambio" @@ -824,15 +992,57 @@ msgstr "Cambios" msgid "Commits feed" msgstr "Feed de cambios" +msgid "Commits per day hour (UTC)" +msgstr "" + +msgid "Commits per day of month" +msgstr "" + +msgid "Commits per weekday" +msgstr "" + +msgid "Commits|An error occurred while fetching merge requests data." +msgstr "" + +msgid "Commits|Commit: %{commitText}" +msgstr "" + msgid "Commits|History" msgstr "Historial" +msgid "Commits|No related merge requests found" +msgstr "" + msgid "Committed by" msgstr "Enviado por" msgid "Compare" msgstr "Comparar" +msgid "Compare Git revisions" +msgstr "" + +msgid "Compare Revisions" +msgstr "" + +msgid "CompareBranches|%{source_branch} and %{target_branch} are the same." +msgstr "" + +msgid "CompareBranches|Compare" +msgstr "" + +msgid "CompareBranches|Source" +msgstr "" + +msgid "CompareBranches|Target" +msgstr "" + +msgid "CompareBranches|There isn't anything to compare." +msgstr "" + +msgid "Confidentiality" +msgstr "" + msgid "Container Registry" msgstr "" @@ -884,6 +1094,9 @@ msgstr "GuÃa de contribución" msgid "Contributors" msgstr "Contribuidores" +msgid "ContributorsPage|%{startDate} – %{endDate}" +msgstr "" + msgid "ContributorsPage|Building repository graph." msgstr "" @@ -905,9 +1118,18 @@ msgstr "" msgid "Copy URL to clipboard" msgstr "Copiar URL al portapapeles" +msgid "Copy branch name to clipboard" +msgstr "" + msgid "Copy commit SHA to clipboard" msgstr "Copiar SHA del cambio al portapapeles" +msgid "Copy reference to clipboard" +msgstr "" + +msgid "Create" +msgstr "" + msgid "Create New Directory" msgstr "Crear Nuevo Directorio" @@ -926,6 +1148,9 @@ msgstr "" msgid "Create file" msgstr "" +msgid "Create lists from labels. Issues with that label appear in that list." +msgstr "" + msgid "Create merge request" msgstr "Crear solicitud de fusión" @@ -938,6 +1163,9 @@ msgstr "" msgid "Create new file" msgstr "" +msgid "Create new label" +msgstr "" + msgid "Create new..." msgstr "Crear nuevo..." @@ -959,6 +1187,9 @@ msgstr "Zona horaria del Cron" msgid "Cron syntax" msgstr "Sintaxis de Cron" +msgid "Current node" +msgstr "" + msgid "Custom notification events" msgstr "Eventos de notificaciones personalizadas" @@ -968,9 +1199,6 @@ msgstr "Los niveles de notificación personalizados son los mismos que los nivel msgid "Cycle Analytics" msgstr "" -msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project." -msgstr "Cycle Analytics ofrece una visión general de cuánto tiempo tarda en pasar de idea a producción en su proyecto." - msgid "CycleAnalyticsStage|Code" msgstr "Código" @@ -1027,12 +1255,21 @@ msgstr "" msgid "Details" msgstr "" +msgid "Diffs|No file name available" +msgstr "" + msgid "Directory name" msgstr "Nombre del directorio" +msgid "Disable" +msgstr "" + msgid "Discard changes" msgstr "" +msgid "Discover GitLab Geo." +msgstr "" + msgid "Dismiss Cycle Analytics introduction box" msgstr "" @@ -1069,15 +1306,24 @@ msgstr "Diferencias en texto plano" msgid "DownloadSource|Download" msgstr "Descargar" +msgid "Due date" +msgstr "" + msgid "Edit" msgstr "Editar" msgid "Edit Pipeline Schedule %{id}" msgstr "Editar Programación del Pipeline %{id}" +msgid "Edit files in the editor and commit changes here" +msgstr "" + msgid "Emails" msgstr "" +msgid "Enable" +msgstr "" + msgid "Environments|An error occurred while fetching the environments." msgstr "" @@ -1096,9 +1342,6 @@ msgstr "" msgid "Environments|Environments" msgstr "" -msgid "Environments|Environments are places where code gets deployed, such as staging or production." -msgstr "" - msgid "Environments|Job" msgstr "" @@ -1141,9 +1384,33 @@ msgstr "" msgid "Error creating epic" msgstr "" +msgid "Error fetching contributors data." +msgstr "" + +msgid "Error fetching labels." +msgstr "" + +msgid "Error fetching network graph." +msgstr "" + +msgid "Error fetching refs" +msgstr "" + +msgid "Error fetching usage ping data." +msgstr "" + msgid "Error occurred when toggling the notification subscription" msgstr "" +msgid "Error saving label update." +msgstr "" + +msgid "Error updating status for all todos." +msgstr "" + +msgid "Error updating todo status." +msgstr "" + msgid "EventFilterBy|Filter by all" msgstr "" @@ -1171,6 +1438,9 @@ msgstr "Todos los meses (el dÃa 1 a las 4:00 am)" msgid "Every week (Sundays at 4:00am)" msgstr "Todas las semanas (domingos a las 4:00 am)" +msgid "Expand" +msgstr "" + msgid "Explore projects" msgstr "" @@ -1189,6 +1459,9 @@ msgstr "" msgid "February" msgstr "" +msgid "Fields on this page are now uneditable, you can configure" +msgstr "" + msgid "File name" msgstr "" @@ -1233,10 +1506,10 @@ msgstr "Desde la integración de la solicitud de fusión hasta el despliegue a p msgid "GPG Keys" msgstr "" -msgid "Geo Nodes" +msgid "Generate a default set of labels" msgstr "" -msgid "GeoNodeSyncStatus|Failed" +msgid "Geo Nodes" msgstr "" msgid "GeoNodeSyncStatus|Node is failing or broken." @@ -1245,16 +1518,100 @@ msgstr "" msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage." msgstr "" -msgid "GeoNodeSyncStatus|Out of sync" +msgid "GeoNodes|Database replication lag:" +msgstr "" + +msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?" +msgstr "" + +msgid "GeoNodes|Does not match the primary storage configuration" +msgstr "" + +msgid "GeoNodes|Failed" +msgstr "" + +msgid "GeoNodes|Full" +msgstr "" + +msgid "GeoNodes|GitLab version does not match the primary node version" +msgstr "" + +msgid "GeoNodes|GitLab version:" +msgstr "" + +msgid "GeoNodes|Health status:" +msgstr "" + +msgid "GeoNodes|Last event ID processed by cursor:" +msgstr "" + +msgid "GeoNodes|Last event ID seen from primary:" +msgstr "" + +msgid "GeoNodes|Loading nodes" +msgstr "" + +msgid "GeoNodes|Local Attachments:" +msgstr "" + +msgid "GeoNodes|Local LFS objects:" +msgstr "" + +msgid "GeoNodes|Local job artifacts:" +msgstr "" + +msgid "GeoNodes|New node" +msgstr "" + +msgid "GeoNodes|Out of sync" +msgstr "" + +msgid "GeoNodes|Replication slot WAL:" +msgstr "" + +msgid "GeoNodes|Replication slots:" +msgstr "" + +msgid "GeoNodes|Repositories:" +msgstr "" + +msgid "GeoNodes|Selective" msgstr "" -msgid "GeoNodeSyncStatus|Synced" +msgid "GeoNodes|Storage config:" +msgstr "" + +msgid "GeoNodes|Sync settings:" +msgstr "" + +msgid "GeoNodes|Synced" +msgstr "" + +msgid "GeoNodes|Unused slots" +msgstr "" + +msgid "GeoNodes|Used slots" +msgstr "" + +msgid "GeoNodes|Wikis:" +msgstr "" + +msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS." +msgstr "" + +msgid "Geo|All projects" msgstr "" msgid "Geo|File sync capacity" msgstr "" -msgid "Geo|Groups to replicate" +msgid "Geo|Groups to synchronize" +msgstr "" + +msgid "Geo|Projects in certain groups" +msgstr "" + +msgid "Geo|Projects in certain storage shards" msgstr "" msgid "Geo|Repository sync capacity" @@ -1263,12 +1620,24 @@ msgstr "" msgid "Geo|Select groups to replicate." msgstr "" +msgid "Geo|Shards to synchronize" +msgstr "" + +msgid "Git revision" +msgstr "" + msgid "Git storage health information has been reset" msgstr "" +msgid "Git version" +msgstr "" + msgid "GitLab Runner section" msgstr "" +msgid "Gitaly Servers" +msgstr "" + msgid "Go to your fork" msgstr "Ir a tu bifurcación" @@ -1278,6 +1647,9 @@ msgstr "Bifurcación" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it!" +msgstr "" + msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" msgstr "" @@ -1314,7 +1686,7 @@ msgstr "" msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group." msgstr "" -msgid "GroupsTree|Are you sure you want to leave the \"${this.group.fullName}\" group?" +msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?" msgstr "" msgid "GroupsTree|Create a project in this group." @@ -1365,6 +1737,11 @@ msgstr "" msgid "HealthCheck|Unhealthy" msgstr "" +msgid "Hide value" +msgid_plural "Hide values" +msgstr[0] "" +msgstr[1] "" + msgid "History" msgstr "" @@ -1391,6 +1768,12 @@ msgid_plural "Instances" msgstr[0] "" msgstr[1] "" +msgid "Instance does not support multiple Kubernetes clusters" +msgstr "" + +msgid "Interested parties can even contribute by pushing commits if they want to." +msgstr "" + msgid "Internal - The group and any internal projects can be viewed by any logged in user." msgstr "" @@ -1418,6 +1801,9 @@ msgstr "" msgid "Issues" msgstr "" +msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable." +msgstr "" + msgid "Jan" msgstr "" @@ -1436,6 +1822,27 @@ msgstr "" msgid "June" msgstr "" +msgid "Kubernetes" +msgstr "" + +msgid "Kubernetes Cluster" +msgstr "" + +msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}" +msgstr "" + +msgid "Kubernetes cluster integration was not removed." +msgstr "" + +msgid "Kubernetes cluster integration was successfully removed." +msgstr "" + +msgid "Kubernetes cluster was successfully updated." +msgstr "" + +msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page" +msgstr "" + msgid "LFSStatus|Disabled" msgstr "Deshabilitado" @@ -1445,6 +1852,9 @@ msgstr "Habilitado" msgid "Labels" msgstr "" +msgid "Labels can be applied to issues and merge requests to categorize them." +msgstr "" + msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "Último %d dÃa" @@ -1474,6 +1884,9 @@ msgstr "" msgid "LastPushEvent|at" msgstr "" +msgid "Learn more" +msgstr "" + msgid "Learn more in the" msgstr "Más información en la" @@ -1492,14 +1905,18 @@ msgstr "Abandonar proyecto" msgid "License" msgstr "" -msgid "Limited to showing %d event at most" -msgid_plural "Limited to showing %d events at most" -msgstr[0] "Limitado a mostrar máximo %d evento" -msgstr[1] "Limitado a mostrar máximo %d eventos" +msgid "Loading the GitLab IDE..." +msgstr "" msgid "Lock" msgstr "" +msgid "Lock %{issuableDisplayName}" +msgstr "" + +msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment." +msgstr "" + msgid "Locked" msgstr "" @@ -1509,12 +1926,21 @@ msgstr "" msgid "Login" msgstr "" +msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos." +msgstr "" + +msgid "Manage labels" +msgstr "" + msgid "Mar" msgstr "" msgid "March" msgstr "" +msgid "Mark done" +msgstr "" + msgid "Maximum git storage failures" msgstr "" @@ -1527,6 +1953,9 @@ msgstr "Mediana" msgid "Members" msgstr "" +msgid "Merge Request" +msgstr "" + msgid "Merge Requests" msgstr "" @@ -1536,9 +1965,30 @@ msgstr "" msgid "Merge request" msgstr "" +msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others" +msgstr "" + +msgid "Merged" +msgstr "" + msgid "Messages" msgstr "" +msgid "Milestone" +msgstr "" + +msgid "Milestones|Delete milestone" +msgstr "" + +msgid "Milestones|Delete milestone %{milestoneTitle}?" +msgstr "" + +msgid "Milestones|Failed to delete milestone %{milestoneTitle}" +msgstr "" + +msgid "Milestones|Milestone %{milestoneTitle} was not found" +msgstr "" + msgid "MissingSSHKeyWarningLink|add an SSH key" msgstr "agregar una clave SSH" @@ -1548,10 +1998,16 @@ msgstr "" msgid "More information is available|here" msgstr "" +msgid "Move" +msgstr "" + +msgid "Move issue" +msgstr "" + msgid "Multiple issue boards" msgstr "" -msgid "New Cluster" +msgid "Name new label" msgstr "" msgid "New Issue" @@ -1559,6 +2015,12 @@ msgid_plural "New Issues" msgstr[0] "Nueva incidencia" msgstr[1] "Nuevas incidencias" +msgid "New Kubernetes Cluster" +msgstr "" + +msgid "New Kubernetes cluster" +msgstr "" + msgid "New Pipeline Schedule" msgstr "Nueva Programación del Pipeline" @@ -1583,6 +2045,9 @@ msgstr "" msgid "New issue" msgstr "Nueva incidencia" +msgid "New label" +msgstr "" + msgid "New merge request" msgstr "Nueva solicitud de fusión" @@ -1601,7 +2066,22 @@ msgstr "" msgid "New tag" msgstr "Nueva etiqueta" -msgid "No container images stored for this project. Add one by following the instructions above." +msgid "No assignee" +msgstr "" + +msgid "No changes" +msgstr "" + +msgid "No connection could be made to a Gitaly Server, please check your logs!" +msgstr "" + +msgid "No due date" +msgstr "" + +msgid "No estimate or time spent" +msgstr "" + +msgid "No file chosen" msgstr "" msgid "No repository" @@ -1616,9 +2096,15 @@ msgstr "" msgid "None" msgstr "" +msgid "Not allowed to merge" +msgstr "" + msgid "Not available" msgstr "No disponible" +msgid "Not confidential" +msgstr "" + msgid "Not enough data" msgstr "No hay suficientes datos" @@ -1679,6 +2165,12 @@ msgstr "Vigilancia" msgid "Notifications" msgstr "" +msgid "Notifications off" +msgstr "" + +msgid "Notifications on" +msgstr "" + msgid "Nov" msgstr "" @@ -1688,7 +2180,7 @@ msgstr "" msgid "Number of access attempts" msgstr "" -msgid "Number of failures before backing off" +msgid "OK" msgstr "" msgid "Oct" @@ -1703,6 +2195,9 @@ msgstr "Filtrar" msgid "Only project members can comment." msgstr "" +msgid "Open" +msgstr "" + msgid "Opened" msgstr "" @@ -1736,9 +2231,6 @@ msgstr "" msgid "Password" msgstr "" -msgid "People without permission will never get a notification and won\\'t be able to comment." -msgstr "" - msgid "Pipeline" msgstr "" @@ -1781,12 +2273,6 @@ msgstr "Todos" msgid "PipelineSchedules|Inactive" msgstr "Inactivos" -msgid "PipelineSchedules|Input variable key" -msgstr "Ingrese nombre de clave" - -msgid "PipelineSchedules|Input variable value" -msgstr "Ingrese el valor de la variable" - msgid "PipelineSchedules|Next Run" msgstr "Próxima Ejecución" @@ -1796,9 +2282,6 @@ msgstr "Ninguno" msgid "PipelineSchedules|Provide a short description for this pipeline" msgstr "Proporcione una descripción breve para este pipeline" -msgid "PipelineSchedules|Remove variable row" -msgstr "Eliminar fila de variable" - msgid "PipelineSchedules|Take ownership" msgstr "Tomar posesión" @@ -1826,6 +2309,12 @@ msgstr "" msgid "Pipelines for last year" msgstr "" +msgid "Pipelines|Build with confidence" +msgstr "" + +msgid "Pipelines|Get started with Pipelines" +msgstr "" + msgid "Pipeline|all" msgstr "todos" @@ -1838,12 +2327,21 @@ msgstr "con etapa" msgid "Pipeline|with stages" msgstr "con etapas" +msgid "Play" +msgstr "" + +msgid "Please <a href=%{link_to_billing} target=\"_blank\" rel=\"noopener noreferrer\">enable billing for one of your projects to be able to create a Kubernetes cluster</a>, then try again." +msgstr "" + msgid "Please solve the reCAPTCHA" msgstr "" msgid "Preferences" msgstr "" +msgid "Primary" +msgstr "" + msgid "Private - Project access must be granted explicitly to each user." msgstr "" @@ -1889,6 +2387,9 @@ msgstr "" msgid "Profiles|your account" msgstr "" +msgid "Programming languages used in this repository" +msgstr "" + msgid "Project '%{project_name}' is in the process of being deleted." msgstr "" @@ -1904,6 +2405,15 @@ msgstr "Proyecto ‘%{project_name}’ fue actualizado satisfactoriamente." msgid "Project access must be granted explicitly to each user." msgstr "El acceso al proyecto debe concederse explÃcitamente a cada usuario." +msgid "Project avatar" +msgstr "" + +msgid "Project avatar in repository: %{link}" +msgstr "" + +msgid "Project cache successfully reset." +msgstr "" + msgid "Project details" msgstr "" @@ -1922,6 +2432,21 @@ msgstr "Se inició la exportación del proyecto. Se enviará un enlace de descar msgid "ProjectActivityRSS|Subscribe" msgstr "" +msgid "ProjectCreationLevel|Allowed to create projects" +msgstr "" + +msgid "ProjectCreationLevel|Default project creation protection" +msgstr "" + +msgid "ProjectCreationLevel|Developers + Masters" +msgstr "" + +msgid "ProjectCreationLevel|Masters" +msgstr "" + +msgid "ProjectCreationLevel|No one" +msgstr "" + msgid "ProjectFeature|Disabled" msgstr "Deshabilitada" @@ -1946,15 +2471,9 @@ msgstr "Historial gráfico" msgid "ProjectSettings|Contact an admin to change this setting." msgstr "" -msgid "ProjectSettings|Immediately run a pipeline on the default branch" -msgstr "" - msgid "ProjectSettings|Only signed commits can be pushed to this repository." msgstr "" -msgid "ProjectSettings|Problem setting up the CI/CD settings JavaScript" -msgstr "" - msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin." msgstr "" @@ -2018,12 +2537,15 @@ msgstr "" msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/" msgstr "" -msgid "PrometheusService|Prometheus monitoring" +msgid "PrometheusService|Time-series monitoring service" msgstr "" msgid "PrometheusService|View environments" msgstr "" +msgid "Protip:" +msgstr "" + msgid "Public - The group and any public projects can be viewed without any authentication." msgstr "" @@ -2039,6 +2561,9 @@ msgstr "" msgid "PushRule|Committer restriction" msgstr "" +msgid "Quick actions can be used in the issues description and comment boxes." +msgstr "" + msgid "Read more" msgstr "Leer más" @@ -2051,6 +2576,12 @@ msgstr "Ramas" msgid "RefSwitcher|Tags" msgstr "Etiquetas" +msgid "Reference:" +msgstr "" + +msgid "Register / Sign In" +msgstr "" + msgid "Registry" msgstr "" @@ -2075,9 +2606,18 @@ msgstr "Solicitudes de fusión Relacionadas" msgid "Remind later" msgstr "Recordar después" +msgid "Remove" +msgstr "" + +msgid "Remove avatar" +msgstr "" + msgid "Remove project" msgstr "Eliminar proyecto" +msgid "Repair authentication" +msgstr "" + msgid "Repository" msgstr "" @@ -2093,6 +2633,11 @@ msgstr "" msgid "Reset runners registration token" msgstr "" +msgid "Reveal value" +msgid_plural "Reveal values" +msgstr[0] "" +msgstr[1] "" + msgid "Revert this commit" msgstr "Revertir este cambio" @@ -2102,15 +2647,15 @@ msgstr "Revertir esta solicitud de fusión" msgid "SSH Keys" msgstr "" -msgid "Save" -msgstr "" - msgid "Save changes" msgstr "" msgid "Save pipeline schedule" msgstr "Guardar programación del pipeline" +msgid "Save variables" +msgstr "" + msgid "Schedule a new pipeline" msgstr "Programar un nuevo pipeline" @@ -2126,38 +2671,59 @@ msgstr "" msgid "Search branches and tags" msgstr "Buscar ramas y etiquetas" -msgid "Seconds before reseting failure information" +msgid "Search milestones" +msgstr "" + +msgid "Search project" msgstr "" -msgid "Seconds to wait after a storage failure" +msgid "Search users" +msgstr "" + +msgid "Seconds before reseting failure information" msgstr "" msgid "Seconds to wait for a storage access attempt" msgstr "" +msgid "Secret variables" +msgstr "" + msgid "Select Archive Format" msgstr "Seleccionar formato de archivo" msgid "Select a timezone" msgstr "Selecciona una zona horaria" +msgid "Select assignee" +msgstr "" + +msgid "Select branch/tag" +msgstr "" + msgid "Select target branch" msgstr "Selecciona una rama de destino" +msgid "Selective synchronization" +msgstr "" + msgid "Sep" msgstr "" msgid "September" msgstr "" +msgid "Server version" +msgstr "" + msgid "Service Templates" msgstr "" msgid "Set a password on your account to pull or push via %{protocol}." msgstr "Establezca una contraseña en su cuenta para actualizar o enviar a través de %{protocol}." -msgid "Set up CI" -msgstr "Configurar CI" +msgid "Set up CI/CD" +msgstr "" msgid "Set up Koding" msgstr "Configurar Koding" @@ -2171,6 +2737,15 @@ msgstr "establecer una contraseña" msgid "Settings" msgstr "" +msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero." +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset pipeline minutes" +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes" +msgstr "" + msgid "Show parent pages" msgstr "" @@ -2185,9 +2760,6 @@ msgstr[1] "Mostrando %d eventos" msgid "Sidebar|Change weight" msgstr "" -msgid "Sidebar|Edit" -msgstr "" - msgid "Sidebar|No" msgstr "" @@ -2200,18 +2772,30 @@ msgstr "" msgid "Snippets" msgstr "" +msgid "Something went wrong on our end" +msgstr "" + msgid "Something went wrong on our end." msgstr "" +msgid "Something went wrong trying to change the confidentiality of this issue" +msgstr "" + msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}" msgstr "" +msgid "Something went wrong when toggling the button" +msgstr "" + msgid "Something went wrong while fetching the projects." msgstr "" msgid "Something went wrong while fetching the registry list." msgstr "" +msgid "Something went wrong. Please try again." +msgstr "" + msgid "Sort by" msgstr "" @@ -2341,10 +2925,10 @@ msgstr "" msgid "Stopped" msgstr "" -msgid "Subgroups" +msgid "Storage" msgstr "" -msgid "Subscribe" +msgid "Subgroups" msgstr "" msgid "Switch branch/tag" @@ -2442,7 +3026,10 @@ msgstr "" msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project." msgstr "" -msgid "The circuitbreaker backoff threshold should be lower than the failure count threshold" +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project" +msgstr "" + +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project." msgstr "" msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request." @@ -2457,10 +3044,10 @@ msgstr "La relación con la bifurcación se ha eliminado." msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage." msgstr "La etapa de incidencia muestra el tiempo que toma desde la creación de un tema hasta asignar el tema a un hito, o añadir el tema a una lista en el panel de temas. Empieza a crear temas para ver los datos de esta etapa." -msgid "The number of attempts GitLab will make to access a storage." +msgid "The maximum file size allowed is 200KB." msgstr "" -msgid "The number of failures after which GitLab will start temporarily disabling access to a storage shard on a host" +msgid "The number of attempts GitLab will make to access a storage." msgstr "" msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}." @@ -2469,9 +3056,6 @@ msgstr "" msgid "The phase of the development lifecycle." msgstr "La etapa del ciclo de vida de desarrollo." -msgid "The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user." -msgstr "La programación de pipelines ejecuta pipelines en el futuro, repetidamente, para ramas o etiquetas especÃficas. Los pipelines programados heredarán acceso limitado al proyecto basado en su usuario asociado." - msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit." msgstr "La etapa de planificación muestra el tiempo desde el paso anterior hasta el envÃo de tu primera confirmación. Este tiempo se añadirá automáticamente una vez que usted envÃe el primer cambio." @@ -2502,19 +3086,46 @@ msgstr "" msgid "The time in seconds GitLab will try to access storage. After this time a timeout error will be raised." msgstr "" +msgid "The time in seconds between storage checks. When a previous check did complete yet, GitLab will skip a check." +msgstr "" + msgid "The time taken by each data entry gathered by that stage." msgstr "El tiempo utilizado por cada entrada de datos obtenido por esa etapa." msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6." msgstr "El valor en el punto medio de una serie de valores observados. Por ejemplo, entre 3, 5, 9, la mediana es 5. Entre 3, 5, 7, 8, la mediana es (5 + 7) / 2 = 6." +msgid "There are no issues to show" +msgstr "" + +msgid "There are no merge requests to show" +msgstr "" + msgid "There are problems accessing Git storage: " msgstr "" +msgid "There was an error loading users activity calendar." +msgstr "" + +msgid "There was an error saving your notification settings." +msgstr "" + +msgid "There was an error subscribing to this label." +msgstr "" + +msgid "There was an error when reseting email token." +msgstr "" + +msgid "There was an error when subscribing to this label." +msgstr "" + +msgid "There was an error when unsubscribing from this label." +msgstr "" + msgid "This board\\'s scope is reduced" msgstr "" -msgid "This branch has changed since you started editing. Would you like to create a new branch?" +msgid "This directory" msgstr "" msgid "This is a confidential issue." @@ -2523,18 +3134,45 @@ msgstr "" msgid "This is the author's first Merge Request to this project." msgstr "" +msgid "This issue is confidential" +msgstr "" + msgid "This issue is confidential and locked." msgstr "" msgid "This issue is locked." msgstr "" +msgid "This job depends on a user to trigger its process. Often they are used to deploy code to production environments" +msgstr "" + +msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered" +msgstr "" + +msgid "This job has not been triggered yet" +msgstr "" + +msgid "This job has not started yet" +msgstr "" + +msgid "This job is in pending state and is waiting to be picked by a runner" +msgstr "" + +msgid "This job requires a manual action" +msgstr "" + msgid "This means you can not push code until you create an empty repository or import existing one." msgstr "Esto significa que no puede enviar código hasta que cree un repositorio vacÃo o importe uno existente." msgid "This merge request is locked." msgstr "" +msgid "This project" +msgstr "" + +msgid "This repository" +msgstr "" + msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here." msgstr "" @@ -2547,9 +3185,21 @@ msgstr "Tiempo antes de que empieze la implementación de una incidencia" msgid "Time between merge request creation and merge/close" msgstr "Tiempo entre la creación de la solicitud de fusión y la integración o cierre de ésta" +msgid "Time tracking" +msgstr "" + msgid "Time until first merge request" msgstr "Tiempo hasta la primera solicitud de fusión" +msgid "TimeTrackingEstimated|Est" +msgstr "" + +msgid "TimeTracking|Estimated:" +msgstr "" + +msgid "TimeTracking|Spent" +msgstr "" + msgid "Timeago|%s days ago" msgstr "hace %s dÃas" @@ -2689,6 +3339,18 @@ msgstr "s" msgid "Title" msgstr "" +msgid "Todo" +msgstr "" + +msgid "Toggle sidebar" +msgstr "" + +msgid "ToggleButton|Toggle Status: OFF" +msgstr "" + +msgid "ToggleButton|Toggle Status: ON" +msgstr "" + msgid "Total Time" msgstr "Tiempo Total" @@ -2704,19 +3366,40 @@ msgstr "" msgid "Track groups of issues that share a theme, across projects and milestones" msgstr "" +msgid "Total: %{total}" +msgstr "" + +msgid "Track time with quick actions" +msgstr "" + +msgid "Trigger this manual action" +msgstr "" + msgid "Turn on Service Desk" msgstr "" +msgid "Type %{value} to confirm:" +msgstr "" + +msgid "Unable to reset project cache." +msgstr "" + +msgid "Unknown" +msgstr "" + msgid "Unlock" msgstr "" +msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment." +msgstr "" + msgid "Unlocked" msgstr "" msgid "Unstar" msgstr "No Destacar" -msgid "Unsubscribe" +msgid "Up to date" msgstr "" msgid "Upgrade your plan to activate Advanced Global Search." @@ -2740,6 +3423,9 @@ msgstr "Subir nuevo archivo" msgid "Upload file" msgstr "Subir archivo" +msgid "Upload new avatar" +msgstr "" + msgid "UploadLink|click to upload" msgstr "Hacer clic para subir" @@ -2752,9 +3438,15 @@ msgstr "" msgid "Use your global notification setting" msgstr "Utiliza tu configuración de notificación global" +msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." +msgstr "" + msgid "View file @ " msgstr "" +msgid "View labels" +msgstr "" + msgid "View open merge request" msgstr "Ver solicitud de fusión abierta" @@ -2776,6 +3468,9 @@ msgstr "Desconocido" msgid "Want to see the data? Please ask an administrator for access." msgstr "¿Quieres ver los datos? Por favor pide acceso al administrador." +msgid "We could not verify that one of your projects on GCP has billing enabled. Please try again." +msgstr "" + msgid "We don't have enough data to show this stage." msgstr "No hay suficientes datos para mostrar en esta etapa." @@ -2788,9 +3483,6 @@ msgstr "" msgid "Weight" msgstr "" -msgid "When access to a storage fails. GitLab will prevent access to the storage for the time specified here. This allows the filesystem to recover. Repositories on failing shards are temporarly unavailable" -msgstr "" - msgid "Wiki" msgstr "" @@ -2809,6 +3501,12 @@ msgstr "" msgid "WikiClone|Start Gollum and edit locally" msgstr "" +msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title." +msgstr "" + +msgid "WikiEdit|There is already a page with the same title in that path." +msgstr "" + msgid "WikiEmptyPageError|You are not allowed to create wiki pages" msgstr "" @@ -2911,9 +3609,21 @@ msgstr "Vas a eliminar el enlace de la bifurcación con el proyecto original %{f msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" msgstr "Vas a transferir %{project_name_with_namespace} a otro propietario. ¿Estás TOTALMENTE seguro?" +msgid "You can also star a label to make it a priority label." +msgstr "" + +msgid "You can move around the graph by using the arrow keys." +msgstr "" + +msgid "You can move around the graph by using the arrow keys." +msgstr "" + msgid "You can only add files when you are on a branch" msgstr "Solo puedes agregar archivos cuando estás en una rama" +msgid "You can only edit files when you are on a branch" +msgstr "" + msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead." msgstr "" @@ -2953,6 +3663,12 @@ msgstr "No podrás actualizar o enviar código al proyecto a través de SSH hast msgid "You won't be able to pull or push project code via SSH until you add an SSH key to your profile" msgstr "" +msgid "You'll need to use different branch names to get a valid comparison." +msgstr "" + +msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure" +msgstr "" + msgid "Your comment will not be visible to the public." msgstr "" @@ -2965,26 +3681,220 @@ msgstr "Tu nombre" msgid "Your projects" msgstr "" +msgid "assign yourself" +msgstr "" + msgid "branch name" msgstr "" msgid "by" msgstr "" +msgid "ciReport|Code quality" +msgstr "" + +msgid "ciReport|DAST detected no alerts by analyzing the review app" +msgstr "" + +msgid "ciReport|Failed to load ${type} report" +msgstr "" + +msgid "ciReport|Fixed:" +msgstr "" + +msgid "ciReport|Instances" +msgstr "" + +msgid "ciReport|Learn more about whitelisting" +msgstr "" + +msgid "ciReport|Loading ${type} report" +msgstr "" + +msgid "ciReport|No changes to code quality" +msgstr "" + +msgid "ciReport|No changes to performance metrics" +msgstr "" + +msgid "ciReport|Performance metrics" +msgstr "" + +msgid "ciReport|SAST" +msgstr "" + +msgid "ciReport|SAST detected no security vulnerabilities" +msgstr "" + +msgid "ciReport|SAST:container no vulnerabilities were found" +msgstr "" + +msgid "ciReport|Show complete code vulnerabilities report" +msgstr "" + +msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}" +msgstr "" + msgid "commit" msgstr "" +msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue." +msgstr "" + +msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue." +msgstr "" + msgid "day" msgid_plural "days" msgstr[0] "dÃa" msgstr[1] "dÃas" +msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command." +msgstr "" + +msgid "merge request" +msgid_plural "merge requests" +msgstr[0] "" +msgstr[1] "" + +msgid "mrWidget|Cancel automatic merge" +msgstr "" + +msgid "mrWidget|Check out branch" +msgstr "" + +msgid "mrWidget|Checking ability to merge automatically" +msgstr "" + +msgid "mrWidget|Cherry-pick" +msgstr "" + +msgid "mrWidget|Cherry-pick this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Closed" +msgstr "" + +msgid "mrWidget|Closed by" +msgstr "" + +msgid "mrWidget|Closes" +msgstr "" + +msgid "mrWidget|Did not close" +msgstr "" + +msgid "mrWidget|Email patches" +msgstr "" + +msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|Mentions" +msgstr "" + +msgid "mrWidget|Merge" +msgstr "" + +msgid "mrWidget|Merge failed." +msgstr "" + +msgid "mrWidget|Merge locally" +msgstr "" + +msgid "mrWidget|Merged by" +msgstr "" + +msgid "mrWidget|Plain diff" +msgstr "" + +msgid "mrWidget|Refresh" +msgstr "" + +msgid "mrWidget|Refresh now" +msgstr "" + +msgid "mrWidget|Refreshing now" +msgstr "" + +msgid "mrWidget|Remove Source Branch" +msgstr "" + +msgid "mrWidget|Remove source branch" +msgstr "" + +msgid "mrWidget|Request to merge" +msgstr "" + +msgid "mrWidget|Resolve conflicts" +msgstr "" + +msgid "mrWidget|Revert" +msgstr "" + +msgid "mrWidget|Revert this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Set by" +msgstr "" + +msgid "mrWidget|The changes were merged into" +msgstr "" + +msgid "mrWidget|The changes were not merged into" +msgstr "" + +msgid "mrWidget|The changes will be merged into" +msgstr "" + +msgid "mrWidget|The source branch has been removed" +msgstr "" + +msgid "mrWidget|The source branch is being removed" +msgstr "" + +msgid "mrWidget|The source branch will be removed" +msgstr "" + +msgid "mrWidget|The source branch will not be removed" +msgstr "" + +msgid "mrWidget|There are merge conflicts" +msgstr "" + +msgid "mrWidget|This merge request failed to be merged automatically" +msgstr "" + +msgid "mrWidget|This merge request is in the process of being merged" +msgstr "" + +msgid "mrWidget|This project is archived, write access has been disabled" +msgstr "" + +msgid "mrWidget|You can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|You can remove source branch now" +msgstr "" + +msgid "mrWidget|command line" +msgstr "" + +msgid "mrWidget|into" +msgstr "" + +msgid "mrWidget|to be merged automatically when the pipeline succeeds" +msgstr "" + msgid "new merge request" msgstr "nueva solicitud de fusión" msgid "notification emails" msgstr "correos electrónicos de notificación" +msgid "or" +msgstr "" + msgid "parent" msgid_plural "parents" msgstr[0] "padre" @@ -2996,12 +3906,21 @@ msgstr "" msgid "personal access token" msgstr "" +msgid "remove due date" +msgstr "" + msgid "source" msgstr "" +msgid "spendCommand|%{slash_command} will update the sum of the time spent." +msgstr "" + msgid "to help your contributors communicate effectively!" msgstr "" msgid "username" msgstr "" +msgid "uses Kubernetes clusters to deploy your code!" +msgstr "" + diff --git a/locale/fr/gitlab.po b/locale/fr/gitlab.po index ace6a5d2f66..d176e67937f 100644 --- a/locale/fr/gitlab.po +++ b/locale/fr/gitlab.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab-ee\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-12-12 18:31+0000\n" -"PO-Revision-Date: 2018-01-05 04:40-0500\n" +"POT-Creation-Date: 2018-02-07 11:38-0600\n" +"PO-Revision-Date: 2018-02-12 03:59-0500\n" "Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n" "Language-Team: French\n" "Language: fr_FR\n" @@ -16,23 +16,41 @@ msgstr "" "X-Crowdin-Language: fr\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" +msgid " and" +msgstr "" + msgid "%d commit" msgid_plural "%d commits" -msgstr[0] "%d validation" -msgstr[1] "%d validations" +msgstr[0] "%d commit" +msgstr[1] "%d commits" + +msgid "%d commit behind" +msgid_plural "%d commits behind" +msgstr[0] "" +msgstr[1] "" + +msgid "%d issue" +msgid_plural "%d issues" +msgstr[0] "" +msgstr[1] "" msgid "%d layer" msgid_plural "%d layers" msgstr[0] "%d couche" msgstr[1] "%d couches" +msgid "%d merge request" +msgid_plural "%d merge requests" +msgstr[0] "" +msgstr[1] "" + msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "%s additional commits have been omitted to prevent performance issues." msgstr[0] "%s validation supplémentaire a été masquée afin d'éviter de créer de problèmes de performances." msgstr[1] "%s validations supplémentaires ont été masquées afin d'éviter de créer de problèmes de performances." -msgid "%{commit_author_link} committed %{commit_timeago}" -msgstr "%{commit_author_link} a validé %{commit_timeago}" +msgid "%{commit_author_link} authored %{commit_timeago}" +msgstr "" msgid "%{count} participant" msgid_plural "%{count} participants" @@ -45,9 +63,6 @@ msgstr "%{number_commits_behind} validations de retard sur %{default_branch}, %{ msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will allow access on the next attempt." msgstr "%{number_of_failures} sur %{maximum_failures} tentative(s). GitLab va vous permettre d'accéder à la prochaine tentative." -msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will block access for %{number_of_seconds} seconds." -msgstr "%{number_of_failures} échecs sur %{maximum_failures}. GitLab va bloquer l’accès pendant %{number_of_seconds} secondes." - msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved." msgstr "%{number_of_failures} échecs sur %{maximum_failures}. GitLab ne va plus réessayer automatiquement. Réinitialisez les informations de stockage lorsque le problème est résolu." @@ -121,24 +136,81 @@ msgstr "Ajouter une licence" msgid "Add new directory" msgstr "Ajouter un nouveau dossier" +msgid "Add todo" +msgstr "" + +msgid "AdminArea|Stop all jobs" +msgstr "" + +msgid "AdminArea|Stop all jobs?" +msgstr "" + +msgid "AdminArea|Stop jobs" +msgstr "" + +msgid "AdminArea|Stopping jobs failed" +msgstr "" + +msgid "AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running." +msgstr "" + msgid "AdminHealthPageLink|health page" msgstr "État des services" +msgid "Advanced" +msgstr "" + msgid "Advanced settings" msgstr "Paramètres avancés" msgid "All" msgstr "Tous" +msgid "All changes are committed" +msgstr "" + +msgid "Allows you to add and manage Kubernetes clusters." +msgstr "" + +msgid "An error occurred previewing the blob" +msgstr "" + msgid "An error occurred when toggling the notification subscription" msgstr "Une erreur s’est produite lors de l’activation/désactivation de l’abonnement aux notifications" msgid "An error occurred when updating the issue weight" msgstr "Une erreur s'est produite lors de la mise à jour du poids du ticket" +msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." +msgstr "" + +msgid "An error occurred while fetching markdown preview" +msgstr "" + msgid "An error occurred while fetching sidebar data" msgstr "Une erreur s'est produite lors de la récupération des données de la barre latérale" +msgid "An error occurred while getting projects" +msgstr "" + +msgid "An error occurred while loading filenames" +msgstr "" + +msgid "An error occurred while rendering KaTeX" +msgstr "" + +msgid "An error occurred while rendering preview broadcast message" +msgstr "" + +msgid "An error occurred while retrieving calendar activity" +msgstr "" + +msgid "An error occurred while retrieving diff" +msgstr "" + +msgid "An error occurred while validating username" +msgstr "" + msgid "An error occurred. Please try again." msgstr "Une erreur est survenue. Merci de réessayer." @@ -163,9 +235,6 @@ msgstr "Êtes-vous sûr·e de vouloir supprimer ce pipeline programmé ?" msgid "Are you sure you want to discard your changes?" msgstr "Êtes-vous sûr·e de vouloir annuler vos modifications ?" -msgid "Are you sure you want to leave this group?" -msgstr "Êtes-vous sûr·e de vouloir quitter ce groupe ?" - msgid "Are you sure you want to reset registration token?" msgstr "Êtes-vous sûr·e de vouloir réinitialiser le jeton d’inscription ?" @@ -178,6 +247,21 @@ msgstr "Êtes-vous certain ?" msgid "Artifacts" msgstr "Artéfacts" +msgid "Assign custom color like #FF0000" +msgstr "" + +msgid "Assign labels" +msgstr "" + +msgid "Assign milestone" +msgstr "" + +msgid "Assign to" +msgstr "" + +msgid "Assignee" +msgstr "" + msgid "Attach a file by drag & drop or %{upload_link}" msgstr "Attachez un fichier par glisser & déposer ou %{upload_link}" @@ -193,15 +277,18 @@ msgstr "Journal d'authentification" msgid "Author" msgstr "Auteur" -msgid "Auto Review Apps and Auto Deploy need a domain name and the %{kubernetes} to work correctly." -msgstr "Les applications de révision automatique et le déploiement automatique requièrent un nom de domaine et un %{kubernetes} pour fonctionner correctement." +msgid "Authors: %{authors}" +msgstr "" + +msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." +msgstr "" + +msgid "Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly." +msgstr "" msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly." msgstr "Les applications de révision automatique et de déploiement automatique requièrent un nom de domaine pour fonctionner correctement." -msgid "Auto Review Apps and Auto Deploy need the %{kubernetes} to work correctly." -msgstr "Les applications de révision automatique et de déploiement automatique requièrent un %{kubernetes} pour fonctionner correctement." - msgid "AutoDevOps|Auto DevOps (Beta)" msgstr "Auto DevOps (Béta)" @@ -223,6 +310,12 @@ msgstr "Vous pouvez activer %{link_to_settings} pour ce projet." msgid "Available" msgstr "Disponible" +msgid "Avatar will be removed. Are you sure?" +msgstr "" + +msgid "Average per day: %{average}" +msgstr "" + msgid "Billing" msgstr "Facturation" @@ -277,6 +370,9 @@ msgstr "payé annuellement pour %{price_per_year}" msgid "BillingPlans|per user" msgstr "par utilisateur" +msgid "Begin with the selected commit" +msgstr "" + msgid "Branch" msgid_plural "Branches" msgstr[0] "Branche" @@ -405,8 +501,8 @@ msgstr "par" msgid "CI / CD" msgstr "Intégration continu / Déploiement continu" -msgid "CI configuration" -msgstr "Configuration de l'intégration continue (CI)" +msgid "CI/CD configuration" +msgstr "" msgid "CICD|Jobs" msgstr "Tâches" @@ -417,6 +513,9 @@ msgstr "Annuler" msgid "Cancel edit" msgstr "Annuler modification" +msgid "Cannot modify managed Kubernetes cluster" +msgstr "" + msgid "Change Weight" msgstr "Changer le poids" @@ -432,15 +531,24 @@ msgstr "Picorer" msgid "ChangeTypeAction|Revert" msgstr "Défaire" +msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes." +msgstr "" + msgid "Changelog" msgstr "Journal des modifications" +msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision." +msgstr "" + msgid "Charts" msgstr "Statistiques" msgid "Chat" msgstr "Chat" +msgid "Check interval" +msgstr "" + msgid "Checking %{text} availability…" msgstr "Vérification de la disponibilité de %{text}…" @@ -453,8 +561,20 @@ msgstr "Picorer cette validation" msgid "Cherry-pick this merge request" msgstr "Picorer cette demande de fusion" -msgid "Choose which groups you wish to replicate to this secondary node. Leave blank to replicate all." -msgstr "Choisissez quels groupes vous souhaitez répliquer sur le nÅ“ud secondaire. Laissez vide pour tous les répliquer." +msgid "Choose File ..." +msgstr "" + +msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request." +msgstr "" + +msgid "Choose file..." +msgstr "" + +msgid "Choose which groups you wish to synchronize to this secondary node." +msgstr "" + +msgid "Choose which shards you wish to synchronize to this secondary node." +msgstr "" msgid "CiStatusLabel|canceled" msgstr "annulé" @@ -510,224 +630,266 @@ msgstr "ignoré" msgid "CiStatus|running" msgstr "en cours" +msgid "CiVariables|Input variable key" +msgstr "" + +msgid "CiVariables|Input variable value" +msgstr "" + +msgid "CiVariables|Remove variable row" +msgstr "" + +msgid "CiVariable|* (All environments)" +msgstr "" + +msgid "CiVariable|All environments" +msgstr "" + +msgid "CiVariable|Create wildcard" +msgstr "" + +msgid "CiVariable|Error occured while saving variables" +msgstr "" + +msgid "CiVariable|New environment" +msgstr "" + +msgid "CiVariable|Protected" +msgstr "" + +msgid "CiVariable|Search environments" +msgstr "" + +msgid "CiVariable|Toggle protected" +msgstr "" + +msgid "CiVariable|Validation failed" +msgstr "" + msgid "CircuitBreakerApiLink|circuitbreaker api" msgstr "CircuitBreaker API" +msgid "Click to expand text" +msgstr "" + msgid "Clone repository" msgstr "Cloner le dépôt" msgid "Close" msgstr "Fermer" -msgid "Cluster" -msgstr "Cluster" - -msgid "ClusterIntegration|%{appList} was successfully installed on your cluster" -msgstr "%{appList} a été installé avec succès sur votre cluster" +msgid "Closed" +msgstr "" -msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which incur additional costs. See %{pricingLink}" -msgstr "%{boldNotice} Cela va ajouter des ressources supplémentaires comme un répartiteur de charge, qui engendrent des coûts supplémentaires. Voir %{pricingLink}" +msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster" +msgstr "" msgid "ClusterIntegration|API URL" msgstr "URL de l'API" -msgid "ClusterIntegration|Active" -msgstr "Actif" - -msgid "ClusterIntegration|Add an existing cluster" -msgstr "Ajouter un cluster existant" +msgid "ClusterIntegration|Add Kubernetes cluster" +msgstr "" -msgid "ClusterIntegration|Add cluster" -msgstr "Ajoutez le cluster" +msgid "ClusterIntegration|Add an existing Kubernetes cluster" +msgstr "" -msgid "ClusterIntegration|All" -msgstr "Tous" +msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration" +msgstr "" msgid "ClusterIntegration|Applications" msgstr "Applications" +msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster." +msgstr "" + msgid "ClusterIntegration|CA Certificate" msgstr "Certificat d‘autorité de certification" msgid "ClusterIntegration|Certificate Authority bundle (PEM format)" msgstr "Paquet de l‘Autorité de certification (format PEM)" -msgid "ClusterIntegration|Choose how to set up cluster integration" -msgstr "Choisissez comment configurer l‘intégration de cluster" - -msgid "ClusterIntegration|Cluster" -msgstr "" - -msgid "ClusterIntegration|Cluster details" -msgstr "Détails du cluster" - -msgid "ClusterIntegration|Cluster integration" -msgstr "Intégration du cluster" - -msgid "ClusterIntegration|Cluster integration is disabled for this project." -msgstr "L'intégration du cluster est désactivée pour ce projet." - -msgid "ClusterIntegration|Cluster integration is enabled for this project." -msgstr "L'intégration du cluster est activée pour ce projet." - -msgid "ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab's connection to it." -msgstr "L'intégration de cluster est activée pour ce projet. La désactivation de cette intégration n’affectera pas votre cluster, il coupera temporairement la connexion de GitLab à celui-ci." - -msgid "ClusterIntegration|Cluster is being created on Google Kubernetes Engine..." +msgid "ClusterIntegration|Choose how to set up Kubernetes cluster integration" msgstr "" -msgid "ClusterIntegration|Cluster name" -msgstr "Nom du cluster" - -msgid "ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine. Refresh the page to see cluster's details" +msgid "ClusterIntegration|Choose which of your project's environments will use this Kubernetes cluster." msgstr "" -msgid "ClusterIntegration|Clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" +msgid "ClusterIntegration|Control how your Kubernetes cluster integrates with GitLab" msgstr "" msgid "ClusterIntegration|Copy API URL" -msgstr "" +msgstr "Copier l’URL de l’API" msgid "ClusterIntegration|Copy CA Certificate" -msgstr "" +msgstr "Copier le certificat CA" -msgid "ClusterIntegration|Copy Token" +msgid "ClusterIntegration|Copy Kubernetes cluster name" msgstr "" -msgid "ClusterIntegration|Copy cluster name" -msgstr "Copier le nom du cluster" +msgid "ClusterIntegration|Copy Token" +msgstr "Copier le jeton" -msgid "ClusterIntegration|Create a new cluster on Google Engine right from GitLab" +msgid "ClusterIntegration|Create Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Create cluster" -msgstr "Créer le cluster" - -msgid "ClusterIntegration|Create cluster on Google Kubernetes Engine" +msgid "ClusterIntegration|Create Kubernetes cluster on Google Kubernetes Engine" msgstr "" -msgid "ClusterIntegration|Create on GKE" +msgid "ClusterIntegration|Create a new Kubernetes cluster on Google Kubernetes Engine right from GitLab" msgstr "" -msgid "ClusterIntegration|Enable cluster integration" -msgstr "Activer l’intégration du cluster" +msgid "ClusterIntegration|Create on GKE" +msgstr "Créer sur GKE" msgid "ClusterIntegration|Enter the details for an existing Kubernetes cluster" -msgstr "" +msgstr "Entrer les détails pour le cluster Kubernetes existant" -msgid "ClusterIntegration|Enter the details for your cluster" +msgid "ClusterIntegration|Enter the details for your Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Environment pattern" +msgid "ClusterIntegration|Environment scope" msgstr "" -msgid "ClusterIntegration|GKE pricing" +msgid "ClusterIntegration|GitLab Integration" msgstr "" msgid "ClusterIntegration|GitLab Runner" -msgstr "" +msgstr "Éxécuteur GitLab" msgid "ClusterIntegration|Google Cloud Platform project ID" msgstr "ID de projet Google Cloud Platform" msgid "ClusterIntegration|Google Kubernetes Engine" -msgstr "" +msgstr "Google Kubernetes Engine" msgid "ClusterIntegration|Google Kubernetes Engine project" -msgstr "" +msgstr "Projet Google Kubernetes Engine" msgid "ClusterIntegration|Helm Tiller" +msgstr "Helm Tiller" + +msgid "ClusterIntegration|Ingress" +msgstr "Ingress" + +msgid "ClusterIntegration|Install" +msgstr "Installer" + +msgid "ClusterIntegration|Installed" +msgstr "Installé" + +msgid "ClusterIntegration|Installing" +msgstr "En cours d’installation" + +msgid "ClusterIntegration|Integrate Kubernetes cluster automation" msgstr "" -msgid "ClusterIntegration|Inactive" +msgid "ClusterIntegration|Integration status" msgstr "" -msgid "ClusterIntegration|Ingress" +msgid "ClusterIntegration|Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Install" +msgid "ClusterIntegration|Kubernetes cluster details" msgstr "" -msgid "ClusterIntegration|Install applications on your cluster. Read more about %{helpLink}" +msgid "ClusterIntegration|Kubernetes cluster integration" msgstr "" -msgid "ClusterIntegration|Installed" +msgid "ClusterIntegration|Kubernetes cluster integration is disabled for this project." msgstr "" -msgid "ClusterIntegration|Installing" +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project. Disabling this integration will not affect your Kubernetes cluster, it will only temporarily turn off GitLab's connection to it." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..." msgstr "" -msgid "ClusterIntegration|Integrate cluster automation" +msgid "ClusterIntegration|Kubernetes cluster name" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project" msgstr "" msgid "ClusterIntegration|Learn more about %{link_to_documentation}" msgstr "En savoir plus sur %{link_to_documentation}" -msgid "ClusterIntegration|Learn more about Clusters" +msgid "ClusterIntegration|Learn more about Kubernetes" +msgstr "" + +msgid "ClusterIntegration|Learn more about environments" msgstr "" msgid "ClusterIntegration|Machine type" msgstr "Type de machine" -msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create clusters" -msgstr "Assurez-vous que votre compte %{link_to_requirements} pour créer des clusters" +msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters" +msgstr "" -msgid "ClusterIntegration|Manage cluster integration on your GitLab project" +msgid "ClusterIntegration|Manage" msgstr "" -msgid "ClusterIntegration|Manage your cluster by visiting %{link_gke}" -msgstr "Gérer votre cluster en visitant le lien %{link_gke}" +msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}" +msgstr "" -msgid "ClusterIntegration|Multiple clusters are available in GitLab Entreprise Edition Premium and Ultimate" +msgid "ClusterIntegration|More information" msgstr "" -msgid "ClusterIntegration|Note:" +msgid "ClusterIntegration|Multiple Kubernetes clusters are available in GitLab Enterprise Edition Premium and Ultimate" msgstr "" +msgid "ClusterIntegration|Note:" +msgstr "Remarque :" + msgid "ClusterIntegration|Number of nodes" msgstr "Nombre de nÅ“uds" -msgid "ClusterIntegration|Please enter access information for your cluster. If you need help, you can read our %{link_to_help_page} on clusters" +msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes" msgstr "" msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:" msgstr "Veuillez vous assurer que votre compte Google répond aux exigences suivantes : " -msgid "ClusterIntegration|Problem setting up the cluster" -msgstr "" - -msgid "ClusterIntegration|Problem setting up the clusters list" -msgstr "" - msgid "ClusterIntegration|Project ID" -msgstr "" +msgstr "ID du projet" msgid "ClusterIntegration|Project namespace" -msgstr "" +msgstr "Espace de noms du projet" msgid "ClusterIntegration|Project namespace (optional, unique)" msgstr "Espace de noms du projet (facultatif, unique)" -msgid "ClusterIntegration|Read our %{link_to_help_page} on cluster integration." -msgstr "Lire notre %{link_to_help_page} sur l’intégration d’un cluster." +msgid "ClusterIntegration|Prometheus" +msgstr "" + +msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration." +msgstr "" -msgid "ClusterIntegration|Remove cluster integration" -msgstr "Retirer l’intégration du cluster" +msgid "ClusterIntegration|Remove Kubernetes cluster integration" +msgstr "" msgid "ClusterIntegration|Remove integration" msgstr "Retirer l’intégration" -msgid "ClusterIntegration|Removing cluster integration will remove the cluster configuration you have added to this project. It will not delete your cluster on Google Kubernetes Engine." +msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster." msgstr "" msgid "ClusterIntegration|Request to begin installing failed" -msgstr "" +msgstr "La demande de lancement d'installation a échoué" msgid "ClusterIntegration|Save changes" -msgstr "" +msgstr "Enregistrer les modifications" -msgid "ClusterIntegration|See and edit the details for your cluster" -msgstr "Voir et modifier les détails de votre cluster" +msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster" +msgstr "" msgid "ClusterIntegration|See machine types" msgstr "Voir les types de machine" @@ -739,55 +901,55 @@ msgid "ClusterIntegration|See zones" msgstr "Voir les zones" msgid "ClusterIntegration|Service token" -msgstr "" +msgstr "Jeton de service" msgid "ClusterIntegration|Show" -msgstr "" +msgstr "Afficher" msgid "ClusterIntegration|Something went wrong on our end." msgstr "Un problème est survenu de notre côté." -msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine" +msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine" msgstr "" msgid "ClusterIntegration|Something went wrong while installing %{title}" -msgstr "" +msgstr "Une erreur s’est produite lors de l'installation de %{title}" -msgid "ClusterIntegration|There are no clusters to show" +msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below" msgstr "" -msgid "ClusterIntegration|This account must have permissions to create a cluster in the %{link_to_container_project} specified below" +msgid "ClusterIntegration|Toggle Kubernetes Cluster" msgstr "" -msgid "ClusterIntegration|Toggle Cluster" -msgstr "Activer/désactiver le cluster" +msgid "ClusterIntegration|Toggle Kubernetes cluster" +msgstr "" msgid "ClusterIntegration|Token" -msgstr "" +msgstr "Jeton" -msgid "ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." -msgstr "Avec un cluster associé à ce projet, vous pouvez utiliser des applications de revue, déployer vos applications, exécuter vos pipelines et bien plus encore, de manière très simple." +msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." +msgstr "" msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}" -msgstr "" +msgstr "Votre compte doit disposer de %{link_to_kubernetes_engine}" msgid "ClusterIntegration|Zone" msgstr "Zone" msgid "ClusterIntegration|access to Google Kubernetes Engine" -msgstr "" +msgstr "Accèder à Google Kubernetes Engine" -msgid "ClusterIntegration|cluster" -msgstr "cluster" +msgid "ClusterIntegration|check the pricing here" +msgstr "" msgid "ClusterIntegration|documentation" -msgstr "" +msgstr "documentation" msgid "ClusterIntegration|help page" msgstr "page d’aide" msgid "ClusterIntegration|installing applications" -msgstr "" +msgstr "Installation des applications" msgid "ClusterIntegration|meets the requirements" msgstr "répond aux exigences" @@ -795,6 +957,9 @@ msgstr "répond aux exigences" msgid "ClusterIntegration|properly configured" msgstr "correctement configuré" +msgid "Collapse" +msgstr "" + msgid "Comments" msgstr "Commentaires" @@ -812,6 +977,9 @@ msgstr "Durée des 30 derniers pipelines en minutes" msgid "Commit message" msgstr "Message de validation" +msgid "Commit statistics for %{ref} %{start_time} - %{end_time}" +msgstr "" + msgid "CommitBoxTitle|Commit" msgstr "Validation" @@ -824,15 +992,57 @@ msgstr "Validations" msgid "Commits feed" msgstr "Flux de validations" +msgid "Commits per day hour (UTC)" +msgstr "" + +msgid "Commits per day of month" +msgstr "" + +msgid "Commits per weekday" +msgstr "" + +msgid "Commits|An error occurred while fetching merge requests data." +msgstr "" + +msgid "Commits|Commit: %{commitText}" +msgstr "" + msgid "Commits|History" msgstr "Historique" +msgid "Commits|No related merge requests found" +msgstr "" + msgid "Committed by" msgstr "Validé par" msgid "Compare" msgstr "Comparer" +msgid "Compare Git revisions" +msgstr "" + +msgid "Compare Revisions" +msgstr "" + +msgid "CompareBranches|%{source_branch} and %{target_branch} are the same." +msgstr "" + +msgid "CompareBranches|Compare" +msgstr "" + +msgid "CompareBranches|Source" +msgstr "" + +msgid "CompareBranches|Target" +msgstr "" + +msgid "CompareBranches|There isn't anything to compare." +msgstr "" + +msgid "Confidentiality" +msgstr "" + msgid "Container Registry" msgstr "Registre de conteneur" @@ -884,20 +1094,23 @@ msgstr "Guide de contribution" msgid "Contributors" msgstr "Contributeurs" -msgid "ContributorsPage|Building repository graph." +msgid "ContributorsPage|%{startDate} – %{endDate}" msgstr "" +msgid "ContributorsPage|Building repository graph." +msgstr "Construction du graphique du dépôt." + msgid "ContributorsPage|Commits to %{branch_name}, excluding merge commits. Limited to 6,000 commits." -msgstr "" +msgstr "Validations sur %{branch_name}, à l’exclusion des validations de fusion. Limité à 6 000 validations." msgid "ContributorsPage|Please wait a moment, this page will automatically refresh when ready." -msgstr "" +msgstr "Veuillez patienter, cette page va être automatiquement actualisée." msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node" -msgstr "" +msgstr "Contrôler la concurrence maximale des remplacements de fichier-joint LFS pour ce nÅ“ud secondaire" msgid "Control the maximum concurrency of repository backfill for this secondary node" -msgstr "" +msgstr "Contrôler la concurrence maximale des remplacements de dépôt pour ce nÅ“ud secondaire" msgid "Copy SSH public key to clipboard" msgstr "Copier la clé publique SSH dans le presse-papier" @@ -905,9 +1118,18 @@ msgstr "Copier la clé publique SSH dans le presse-papier" msgid "Copy URL to clipboard" msgstr "Copier l'URL dans le presse-papier" +msgid "Copy branch name to clipboard" +msgstr "" + msgid "Copy commit SHA to clipboard" msgstr "Copier le SHA de la validation" +msgid "Copy reference to clipboard" +msgstr "" + +msgid "Create" +msgstr "" + msgid "Create New Directory" msgstr "Créer un nouveau dossier" @@ -921,11 +1143,14 @@ msgid "Create empty bare repository" msgstr "Créer un dépôt vide" msgid "Create epic" -msgstr "" +msgstr "Créer l'épopée" msgid "Create file" msgstr "Créer un fichier" +msgid "Create lists from labels. Issues with that label appear in that list." +msgstr "" + msgid "Create merge request" msgstr "Créer une demande de fusion" @@ -938,6 +1163,9 @@ msgstr "Créer un nouveau dossier" msgid "Create new file" msgstr "Créer un nouveau fichier" +msgid "Create new label" +msgstr "" + msgid "Create new..." msgstr "Créer nouveau..." @@ -951,7 +1179,7 @@ msgid "CreateTokenToCloneLink|create a personal access token" msgstr "Créer un jeton d'accès personnel" msgid "Creating epic" -msgstr "" +msgstr "Création de l'épopée en cours" msgid "Cron Timezone" msgstr "Fuseau horaire de Cron" @@ -959,6 +1187,9 @@ msgstr "Fuseau horaire de Cron" msgid "Cron syntax" msgstr "Syntaxe Cron" +msgid "Current node" +msgstr "" + msgid "Custom notification events" msgstr "Événements de notification personnalisés" @@ -968,9 +1199,6 @@ msgstr "Le niveau de notification Personnalisé est similaire au niveau Particip msgid "Cycle Analytics" msgstr "Analyseur de cycle" -msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project." -msgstr "L’analyseur de cycle permet d’avoir une vue d’ensemble du temps nécessaire pour aller d’une idée à sa mise en production pour votre projet." - msgid "CycleAnalyticsStage|Code" msgstr "Code" @@ -1027,12 +1255,21 @@ msgstr "Les modèles de description permettent de définir des modèles spécifi msgid "Details" msgstr "Détails" +msgid "Diffs|No file name available" +msgstr "" + msgid "Directory name" msgstr "Nom du dossier" +msgid "Disable" +msgstr "" + msgid "Discard changes" msgstr "Supprimer les modifications" +msgid "Discover GitLab Geo." +msgstr "" + msgid "Dismiss Cycle Analytics introduction box" msgstr "Passer l’introduction Cycle Analytics" @@ -1069,15 +1306,24 @@ msgstr "Diff simple" msgid "DownloadSource|Download" msgstr "Télécharger" +msgid "Due date" +msgstr "" + msgid "Edit" msgstr "Éditer" msgid "Edit Pipeline Schedule %{id}" msgstr "Éditer le pipeline programmé %{id}" +msgid "Edit files in the editor and commit changes here" +msgstr "" + msgid "Emails" msgstr "Courriels" +msgid "Enable" +msgstr "" + msgid "Environments|An error occurred while fetching the environments." msgstr "Une erreur s‘est produite lors de la récupération des environnements." @@ -1096,9 +1342,6 @@ msgstr "Environnement" msgid "Environments|Environments" msgstr "Environnements" -msgid "Environments|Environments are places where code gets deployed, such as staging or production." -msgstr "Les environnements sont des lieux où le code est déployé, comme staging ou production." - msgid "Environments|Job" msgstr "Tâche" @@ -1130,20 +1373,44 @@ msgid "Environments|You don't have any environments right now." msgstr "Vous n’avez aucun environnement pour le moment." msgid "Epic will be removed! Are you sure?" -msgstr "" +msgstr "L’épopée sera supprimée ! Êtes-vous sûr•e ?" msgid "Epics" -msgstr "" +msgstr "Épopées" msgid "Epics let you manage your portfolio of projects more efficiently and with less effort" -msgstr "" +msgstr "Les épopées vous permettent de gérer votre portefeuille de projets plus efficacement et avec moins d'effort" msgid "Error creating epic" +msgstr "Erreur lors de la création de l’épopée" + +msgid "Error fetching contributors data." +msgstr "" + +msgid "Error fetching labels." +msgstr "" + +msgid "Error fetching network graph." +msgstr "" + +msgid "Error fetching refs" +msgstr "" + +msgid "Error fetching usage ping data." msgstr "" msgid "Error occurred when toggling the notification subscription" msgstr "Une erreur s’est produite lors de l’activation/désactivation de l’abonnement aux notifications" +msgid "Error saving label update." +msgstr "" + +msgid "Error updating status for all todos." +msgstr "" + +msgid "Error updating todo status." +msgstr "" + msgid "EventFilterBy|Filter by all" msgstr "Aucun filtre" @@ -1171,6 +1438,9 @@ msgstr "Chaque mois (le 1er à 4:00 du matin)" msgid "Every week (Sundays at 4:00am)" msgstr "Chaque semaine (dimanche à 4h00 du matin)" +msgid "Expand" +msgstr "" + msgid "Explore projects" msgstr "Explorer les projets" @@ -1189,6 +1459,9 @@ msgstr "Févr." msgid "February" msgstr "Février" +msgid "Fields on this page are now uneditable, you can configure" +msgstr "" + msgid "File name" msgstr "Nom du fichier" @@ -1233,29 +1506,113 @@ msgstr "Depuis la fusion de la demande de fusion jusqu'au déploiement en produc msgid "GPG Keys" msgstr "Clés GPG" +msgid "Generate a default set of labels" +msgstr "" + msgid "Geo Nodes" msgstr "NÅ“uds Geo" -msgid "GeoNodeSyncStatus|Failed" -msgstr "Échec" - msgid "GeoNodeSyncStatus|Node is failing or broken." msgstr "Le nÅ“ud est défaillant ou cassé." msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage." msgstr "Le nÅ“ud est lent, surchargé, ou il vient juste de récupérer après un problème." -msgid "GeoNodeSyncStatus|Out of sync" -msgstr "Désynchronisé" +msgid "GeoNodes|Database replication lag:" +msgstr "" + +msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?" +msgstr "" + +msgid "GeoNodes|Does not match the primary storage configuration" +msgstr "" + +msgid "GeoNodes|Failed" +msgstr "" + +msgid "GeoNodes|Full" +msgstr "" + +msgid "GeoNodes|GitLab version does not match the primary node version" +msgstr "" + +msgid "GeoNodes|GitLab version:" +msgstr "" + +msgid "GeoNodes|Health status:" +msgstr "" + +msgid "GeoNodes|Last event ID processed by cursor:" +msgstr "" + +msgid "GeoNodes|Last event ID seen from primary:" +msgstr "" + +msgid "GeoNodes|Loading nodes" +msgstr "" + +msgid "GeoNodes|Local Attachments:" +msgstr "" + +msgid "GeoNodes|Local LFS objects:" +msgstr "" + +msgid "GeoNodes|Local job artifacts:" +msgstr "" + +msgid "GeoNodes|New node" +msgstr "" + +msgid "GeoNodes|Out of sync" +msgstr "" + +msgid "GeoNodes|Replication slot WAL:" +msgstr "" + +msgid "GeoNodes|Replication slots:" +msgstr "" + +msgid "GeoNodes|Repositories:" +msgstr "" + +msgid "GeoNodes|Selective" +msgstr "" + +msgid "GeoNodes|Storage config:" +msgstr "" + +msgid "GeoNodes|Sync settings:" +msgstr "" + +msgid "GeoNodes|Synced" +msgstr "" + +msgid "GeoNodes|Unused slots" +msgstr "" + +msgid "GeoNodes|Used slots" +msgstr "" + +msgid "GeoNodes|Wikis:" +msgstr "" + +msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS." +msgstr "" -msgid "GeoNodeSyncStatus|Synced" -msgstr "Synchronisé" +msgid "Geo|All projects" +msgstr "" msgid "Geo|File sync capacity" msgstr "Capacité de synchronisation de fichier" -msgid "Geo|Groups to replicate" -msgstr "Groupes à répliquer" +msgid "Geo|Groups to synchronize" +msgstr "" + +msgid "Geo|Projects in certain groups" +msgstr "" + +msgid "Geo|Projects in certain storage shards" +msgstr "" msgid "Geo|Repository sync capacity" msgstr "Capacité de synchronisation du dépôt" @@ -1263,12 +1620,24 @@ msgstr "Capacité de synchronisation du dépôt" msgid "Geo|Select groups to replicate." msgstr "Sélectionner les groupes à répliquer." +msgid "Geo|Shards to synchronize" +msgstr "" + +msgid "Git revision" +msgstr "" + msgid "Git storage health information has been reset" msgstr "Les informations de santé du stockage Git ont été réinitialisées" +msgid "Git version" +msgstr "" + msgid "GitLab Runner section" msgstr "Section de l'Exécuteur GitLab" +msgid "Gitaly Servers" +msgstr "" + msgid "Go to your fork" msgstr "Aller à votre fourche" @@ -1278,6 +1647,9 @@ msgstr "Fourche" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "L’authentification Google n’est pas %{link_to_documentation}. Demandez à votre administrateur GitLab si vous souhaitez utiliser ce service." +msgid "Got it!" +msgstr "" + msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" msgstr "Empêcher le partage d'un projet de %{group} avec d'autres groupes" @@ -1314,8 +1686,8 @@ msgstr "Aucun groupe trouvé" msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group." msgstr "Vous pouvez gérer les autorisations des membres de votre groupe et accéder à chacun de ses projets." -msgid "GroupsTree|Are you sure you want to leave the \"${this.group.fullName}\" group?" -msgstr "Êtes-vous sûr·e de vouloir quitter le groupe « ${this.group.fullName} » ?" +msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?" +msgstr "" msgid "GroupsTree|Create a project in this group." msgstr "Créez un projet dans ce groupe." @@ -1345,7 +1717,7 @@ msgid "GroupsTree|Sorry, no groups or projects matched your search" msgstr "Désolé, aucun groupe ni projet ne correspond à vos critères de recherche" msgid "Have your users email" -msgstr "" +msgstr "Lister les emails utilisateurs" msgid "Health Check" msgstr "État des services" @@ -1365,6 +1737,11 @@ msgstr "Aucun problème détecté" msgid "HealthCheck|Unhealthy" msgstr "En mauvaise santé" +msgid "Hide value" +msgid_plural "Hide values" +msgstr[0] "" +msgstr[1] "" + msgid "History" msgstr "Historique" @@ -1391,6 +1768,12 @@ msgid_plural "Instances" msgstr[0] "Instance" msgstr[1] "Instances" +msgid "Instance does not support multiple Kubernetes clusters" +msgstr "" + +msgid "Interested parties can even contribute by pushing commits if they want to." +msgstr "" + msgid "Internal - The group and any internal projects can be viewed by any logged in user." msgstr "Interne - Le groupe ainsi que tous les projets internes sont accessibles pour n’importe quel·le utilisa·teur·trice connecté·e." @@ -1418,6 +1801,9 @@ msgstr "Tableaux" msgid "Issues" msgstr "Tickets" +msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable." +msgstr "" + msgid "Jan" msgstr "Janv." @@ -1436,6 +1822,27 @@ msgstr "Juin" msgid "June" msgstr "Juin" +msgid "Kubernetes" +msgstr "" + +msgid "Kubernetes Cluster" +msgstr "" + +msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}" +msgstr "" + +msgid "Kubernetes cluster integration was not removed." +msgstr "" + +msgid "Kubernetes cluster integration was successfully removed." +msgstr "" + +msgid "Kubernetes cluster was successfully updated." +msgstr "" + +msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page" +msgstr "" + msgid "LFSStatus|Disabled" msgstr "Désactivé" @@ -1445,6 +1852,9 @@ msgstr "Activé" msgid "Labels" msgstr "Labels" +msgid "Labels can be applied to issues and merge requests to categorize them." +msgstr "" + msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "Le dernier %d jour" @@ -1474,6 +1884,9 @@ msgstr "Vous avez poussé sur" msgid "LastPushEvent|at" msgstr "à " +msgid "Learn more" +msgstr "" + msgid "Learn more in the" msgstr "En apprendre plus dans le" @@ -1492,14 +1905,18 @@ msgstr "Quitter le projet" msgid "License" msgstr "Licence" -msgid "Limited to showing %d event at most" -msgid_plural "Limited to showing %d events at most" -msgstr[0] "Limiter l'affichage au plus à %d évènement" -msgstr[1] "Limiter l'affichage au plus à %d évènements" +msgid "Loading the GitLab IDE..." +msgstr "" msgid "Lock" msgstr "Verrouiller" +msgid "Lock %{issuableDisplayName}" +msgstr "" + +msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment." +msgstr "" + msgid "Locked" msgstr "Verrouillé" @@ -1509,12 +1926,21 @@ msgstr "Fichiers verrouillés" msgid "Login" msgstr "Se connecter" +msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos." +msgstr "" + +msgid "Manage labels" +msgstr "" + msgid "Mar" msgstr "Mars" msgid "March" msgstr "Mars" +msgid "Mark done" +msgstr "" + msgid "Maximum git storage failures" msgstr "Nombre maximum d’échecs du stockage git" @@ -1527,6 +1953,9 @@ msgstr "Médian" msgid "Members" msgstr "Membres" +msgid "Merge Request" +msgstr "" + msgid "Merge Requests" msgstr "Demandes de fusion" @@ -1536,9 +1965,30 @@ msgstr "Événements de fusion" msgid "Merge request" msgstr "Demande de fusion" +msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others" +msgstr "" + +msgid "Merged" +msgstr "" + msgid "Messages" msgstr "Messages" +msgid "Milestone" +msgstr "" + +msgid "Milestones|Delete milestone" +msgstr "" + +msgid "Milestones|Delete milestone %{milestoneTitle}?" +msgstr "" + +msgid "Milestones|Failed to delete milestone %{milestoneTitle}" +msgstr "" + +msgid "Milestones|Milestone %{milestoneTitle} was not found" +msgstr "" + msgid "MissingSSHKeyWarningLink|add an SSH key" msgstr "ajouter une clé SSH" @@ -1548,17 +1998,29 @@ msgstr "Surveillance" msgid "More information is available|here" msgstr "ici" +msgid "Move" +msgstr "" + +msgid "Move issue" +msgstr "" + msgid "Multiple issue boards" msgstr "Multiple tableaux de tickets" -msgid "New Cluster" -msgstr "Nouveau cluster" +msgid "Name new label" +msgstr "" msgid "New Issue" msgid_plural "New Issues" msgstr[0] "Nouveau ticket" msgstr[1] "Nouveaux tickets" +msgid "New Kubernetes Cluster" +msgstr "" + +msgid "New Kubernetes cluster" +msgstr "" + msgid "New Pipeline Schedule" msgstr "Nouveau pipeline programmé" @@ -1572,7 +2034,7 @@ msgid "New directory" msgstr "Nouveau dossier" msgid "New epic" -msgstr "" +msgstr "Nouvelle épopée" msgid "New file" msgstr "Nouveau fichier" @@ -1583,6 +2045,9 @@ msgstr "Nouveau groupe" msgid "New issue" msgstr "Nouveau ticket" +msgid "New label" +msgstr "" + msgid "New merge request" msgstr "Nouvelle demande de fusion" @@ -1601,8 +2066,23 @@ msgstr "Nouveau sous-groupe" msgid "New tag" msgstr "Nouveau tag" -msgid "No container images stored for this project. Add one by following the instructions above." -msgstr "Aucune image de conteneur stockée pour ce projet. Ajoutez en une en suivant les instructions ci-dessous." +msgid "No assignee" +msgstr "" + +msgid "No changes" +msgstr "" + +msgid "No connection could be made to a Gitaly Server, please check your logs!" +msgstr "" + +msgid "No due date" +msgstr "" + +msgid "No estimate or time spent" +msgstr "" + +msgid "No file chosen" +msgstr "" msgid "No repository" msgstr "Pas de dépôt" @@ -1616,9 +2096,15 @@ msgstr "Pas de temps passé" msgid "None" msgstr "Aucun·e" +msgid "Not allowed to merge" +msgstr "" + msgid "Not available" msgstr "Indisponible" +msgid "Not confidential" +msgstr "" + msgid "Not enough data" msgstr "Données insuffisantes" @@ -1679,6 +2165,12 @@ msgstr "Surveillé" msgid "Notifications" msgstr "Notifications" +msgid "Notifications off" +msgstr "" + +msgid "Notifications on" +msgstr "" + msgid "Nov" msgstr "Nov." @@ -1688,8 +2180,8 @@ msgstr "Novembre" msgid "Number of access attempts" msgstr "Nombre de tentatives d'accès" -msgid "Number of failures before backing off" -msgstr "Nombre d'échecs avant annulation" +msgid "OK" +msgstr "" msgid "Oct" msgstr "Oct." @@ -1703,6 +2195,9 @@ msgstr "Filtre" msgid "Only project members can comment." msgstr "Seuls les membres du projet peuvent commenter." +msgid "Open" +msgstr "" + msgid "Opened" msgstr "Ouvert" @@ -1736,9 +2231,6 @@ msgstr "« Première" msgid "Password" msgstr "Mot de Passe" -msgid "People without permission will never get a notification and won\\'t be able to comment." -msgstr "Les personnes sans autorisation ne recevront jamais de notifications et ne pourront pas commenter." - msgid "Pipeline" msgstr "Pipeline" @@ -1781,12 +2273,6 @@ msgstr "Tous" msgid "PipelineSchedules|Inactive" msgstr "Inactif" -msgid "PipelineSchedules|Input variable key" -msgstr "Nom de la variable" - -msgid "PipelineSchedules|Input variable value" -msgstr "Valeur de la variable" - msgid "PipelineSchedules|Next Run" msgstr "Prochaine exécution" @@ -1796,9 +2282,6 @@ msgstr "Aucune" msgid "PipelineSchedules|Provide a short description for this pipeline" msgstr "Indiquez une courte description" -msgid "PipelineSchedules|Remove variable row" -msgstr "Supprimer la variable" - msgid "PipelineSchedules|Take ownership" msgstr "S’approprier" @@ -1826,6 +2309,12 @@ msgstr "Pipelines de la semaine dernière" msgid "Pipelines for last year" msgstr "Pipelines de l’année dernière" +msgid "Pipelines|Build with confidence" +msgstr "" + +msgid "Pipelines|Get started with Pipelines" +msgstr "" + msgid "Pipeline|all" msgstr "Tous" @@ -1838,12 +2327,21 @@ msgstr "avec l'étape" msgid "Pipeline|with stages" msgstr "avec les étapes" +msgid "Play" +msgstr "" + +msgid "Please <a href=%{link_to_billing} target=\"_blank\" rel=\"noopener noreferrer\">enable billing for one of your projects to be able to create a Kubernetes cluster</a>, then try again." +msgstr "" + msgid "Please solve the reCAPTCHA" msgstr "Veuillez résoudre le reCAPTCHA" msgid "Preferences" msgstr "Préférences" +msgid "Primary" +msgstr "" + msgid "Private - Project access must be granted explicitly to each user." msgstr "Privé - L’accès au projet doit être autorisé explicitement pour chaque utilisa·teur·trice." @@ -1889,6 +2387,9 @@ msgstr "Votre compte est actuellement propriétaire des groupes suivants :" msgid "Profiles|your account" msgstr "votre compte" +msgid "Programming languages used in this repository" +msgstr "" + msgid "Project '%{project_name}' is in the process of being deleted." msgstr "Le projet “%{project_name}†est en train d’être supprimé." @@ -1904,6 +2405,15 @@ msgstr "Projet '%{project_name}' mis à jour avec succès." msgid "Project access must be granted explicitly to each user." msgstr "L’accès au projet doit être explicitement accordé à chaque utilisateur." +msgid "Project avatar" +msgstr "" + +msgid "Project avatar in repository: %{link}" +msgstr "" + +msgid "Project cache successfully reset." +msgstr "" + msgid "Project details" msgstr "Détails du projet" @@ -1922,6 +2432,21 @@ msgstr "L'export du projet a débuté. Un lien de téléchargement sera envoyé msgid "ProjectActivityRSS|Subscribe" msgstr "S’abonner" +msgid "ProjectCreationLevel|Allowed to create projects" +msgstr "" + +msgid "ProjectCreationLevel|Default project creation protection" +msgstr "" + +msgid "ProjectCreationLevel|Developers + Masters" +msgstr "" + +msgid "ProjectCreationLevel|Masters" +msgstr "" + +msgid "ProjectCreationLevel|No one" +msgstr "" + msgid "ProjectFeature|Disabled" msgstr "Désactivé" @@ -1946,15 +2471,9 @@ msgstr "Graphes" msgid "ProjectSettings|Contact an admin to change this setting." msgstr "Contactez un administrateur pour modifier ce paramètre." -msgid "ProjectSettings|Immediately run a pipeline on the default branch" -msgstr "Exécuter immédiatement un pipeline sur la branche par défaut" - msgid "ProjectSettings|Only signed commits can be pushed to this repository." msgstr "Seules les validations signées peuvent être poussées sur ce dépôt." -msgid "ProjectSettings|Problem setting up the CI/CD settings JavaScript" -msgstr "Problème lors de la configuration des paramètres CI/CD JavaScript" - msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin." msgstr "Ce paramètre est appliqué au niveau du serveur et peut être modifié par un administrateur." @@ -1992,36 +2511,39 @@ msgid "ProjectsDropdown|This feature requires browser localStorage support" msgstr "Cette fonctionnalité requiert le support du localStorage par votre navigateur" msgid "PrometheusService|By default, Prometheus listens on ‘http://localhost:9090’. It’s not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server." -msgstr "" +msgstr "Par défaut, Prometheus écoute sur 'http://localhost:9090'. Il n’est pas recommandé de changer l’adresse et le port par défaut car cela pourrait affecter ou entrer en conflit avec d'autres services fonctionnant sur le serveur GitLab." msgid "PrometheusService|Finding and configuring metrics..." -msgstr "" +msgstr "Recherche et configuration des métriques en cours…" msgid "PrometheusService|Metrics" -msgstr "" +msgstr "Métriques" msgid "PrometheusService|Metrics are automatically configured and monitored based on a library of metrics from popular exporters." -msgstr "" +msgstr "Les métriques sont automatiquement configurées et surveillées en fonction d’une bibliothèque de métriques provenant d’exportateurs populaires." msgid "PrometheusService|Missing environment variable" -msgstr "" +msgstr "Variable d’environnement manquante" msgid "PrometheusService|Monitored" -msgstr "" +msgstr "Surveillé" msgid "PrometheusService|More information" -msgstr "" +msgstr "Plus d’informations" msgid "PrometheusService|No metrics are being monitored. To start monitoring, deploy to an environment." -msgstr "" +msgstr "Aucune métrique n’est surveillée. Pour démarrer la surveillance, déployez sur un environnement." msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/" -msgstr "" +msgstr "URL de base de l’API Prometheus, comme http://prometheus.example.com/" -msgid "PrometheusService|Prometheus monitoring" +msgid "PrometheusService|Time-series monitoring service" msgstr "" msgid "PrometheusService|View environments" +msgstr "Afficher les environnements" + +msgid "Protip:" msgstr "" msgid "Public - The group and any public projects can be viewed without any authentication." @@ -2039,6 +2561,9 @@ msgstr "Évènements de poussée" msgid "PushRule|Committer restriction" msgstr "Restriction du validateur" +msgid "Quick actions can be used in the issues description and comment boxes." +msgstr "" + msgid "Read more" msgstr "Lire plus" @@ -2051,6 +2576,12 @@ msgstr "Branches" msgid "RefSwitcher|Tags" msgstr "Tags" +msgid "Reference:" +msgstr "" + +msgid "Register / Sign In" +msgstr "" + msgid "Registry" msgstr "Registre" @@ -2075,9 +2606,18 @@ msgstr "Demandes fusionnées liées" msgid "Remind later" msgstr "Me le rappeler ultérieurement" +msgid "Remove" +msgstr "" + +msgid "Remove avatar" +msgstr "" + msgid "Remove project" msgstr "Supprimer le projet" +msgid "Repair authentication" +msgstr "" + msgid "Repository" msgstr "Dépôt" @@ -2093,6 +2633,11 @@ msgstr "Réinitialiser le jeton d’accès au bilan de santé" msgid "Reset runners registration token" msgstr "Réinitialiser le jeton d’inscription des exécuteurs" +msgid "Reveal value" +msgid_plural "Reveal values" +msgstr[0] "" +msgstr[1] "" + msgid "Revert this commit" msgstr "Défaire cette validation" @@ -2102,15 +2647,15 @@ msgstr "Défaire cette demande de fusion" msgid "SSH Keys" msgstr "Clés SSH" -msgid "Save" -msgstr "Enregistrer" - msgid "Save changes" msgstr "Enregistrer les modifications" msgid "Save pipeline schedule" msgstr "Sauvegarder le pipeline programmé" +msgid "Save variables" +msgstr "" + msgid "Schedule a new pipeline" msgstr "Programmer un nouveau pipeline" @@ -2121,43 +2666,64 @@ msgid "Scheduling Pipelines" msgstr "Programmer des pipelines" msgid "Scoped issue boards" -msgstr "" +msgstr "Tableaux de tickets avec portée limitée" msgid "Search branches and tags" msgstr "Rechercher dans les branches et les étiquettes" +msgid "Search milestones" +msgstr "" + +msgid "Search project" +msgstr "" + +msgid "Search users" +msgstr "" + msgid "Seconds before reseting failure information" msgstr "Nombre de secondes avant de réinitialiser les informations d’échec" -msgid "Seconds to wait after a storage failure" -msgstr "Nombre de secondes d'attente après un échec de stockage" - msgid "Seconds to wait for a storage access attempt" msgstr "Nombre de secondes d’attente pour un essai d'accès au stockage" +msgid "Secret variables" +msgstr "" + msgid "Select Archive Format" msgstr "Sélectionnez le format de l'archive" msgid "Select a timezone" msgstr "Sélectionnez un fuseau horaire" +msgid "Select assignee" +msgstr "" + +msgid "Select branch/tag" +msgstr "" + msgid "Select target branch" msgstr "Sélectionnez une branche cible" +msgid "Selective synchronization" +msgstr "" + msgid "Sep" msgstr "Sept." msgid "September" msgstr "Septembre" +msgid "Server version" +msgstr "" + msgid "Service Templates" msgstr "Modèles de service" msgid "Set a password on your account to pull or push via %{protocol}." msgstr "Définissez un mot de passe pour votre compte pour pouvoir tirer ou pousser par %{protocol}." -msgid "Set up CI" -msgstr "Mettre en place l'intégration continue (CI)" +msgid "Set up CI/CD" +msgstr "" msgid "Set up Koding" msgstr "Mettre en place Koding" @@ -2171,6 +2737,15 @@ msgstr "définir un mot de passe" msgid "Settings" msgstr "Paramètres" +msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero." +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset pipeline minutes" +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes" +msgstr "" + msgid "Show parent pages" msgstr "Afficher les pages parentes" @@ -2185,9 +2760,6 @@ msgstr[1] "Affichage de %d évènements" msgid "Sidebar|Change weight" msgstr "Changer le poids" -msgid "Sidebar|Edit" -msgstr "Modifier" - msgid "Sidebar|No" msgstr "Non" @@ -2200,18 +2772,30 @@ msgstr "Poids" msgid "Snippets" msgstr "Extraits de code" +msgid "Something went wrong on our end" +msgstr "" + msgid "Something went wrong on our end." msgstr "Une erreur est survenue de notre côté." +msgid "Something went wrong trying to change the confidentiality of this issue" +msgstr "" + msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}" msgstr "Quelque chose ne s‘est pas bien passé en essayant de changer l’état de verrouillage de cette ${this.issuableDisplayName}" +msgid "Something went wrong when toggling the button" +msgstr "" + msgid "Something went wrong while fetching the projects." msgstr "Une erreur s'est produite lors de la récupération des projets." msgid "Something went wrong while fetching the registry list." msgstr "Une erreur s'est produite lors de la récupération de la liste du registre." +msgid "Something went wrong. Please try again." +msgstr "" + msgid "Sort by" msgstr "Trier par" @@ -2318,7 +2902,7 @@ msgid "Source code" msgstr "Code source" msgid "Source is not available" -msgstr "" +msgstr "La source n’est pas disponible" msgid "Spam Logs" msgstr "Journaux des messages indésirables" @@ -2341,12 +2925,12 @@ msgstr "Démarrer l'Exécuteur !" msgid "Stopped" msgstr "Arrêté" +msgid "Storage" +msgstr "" + msgid "Subgroups" msgstr "Sous-groupes" -msgid "Subscribe" -msgstr "S’abonner" - msgid "Switch branch/tag" msgstr "Changer de branche / tag" @@ -2442,8 +3026,11 @@ msgstr "Merci ! Ne plus afficher ce message" msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project." msgstr "La Recherche Globale Avancée de Gitlab est un outils puissant qui vous fait gagner du temps. Au lieu de créer du code similaire et perdre du temps, vous pouvez maintenant chercher dans le code d'autres équipes pour vous aider sur votre projet." -msgid "The circuitbreaker backoff threshold should be lower than the failure count threshold" -msgstr "Le seuil d’interruption du disjoncteur devrait être inférieur au seuil de nombre de défaillance" +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project" +msgstr "" + +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project." +msgstr "" msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request." msgstr "L’étape de développement montre le temps entre la première validation et la création de la demande de fusion. Les données seront automatiquement ajoutées ici une fois que vous aurez créé votre première demande de fusion." @@ -2457,21 +3044,18 @@ msgstr "La relation de fourche a été supprimée." msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage." msgstr "L'étape des tickets montre le temps nécessaire entre la création d'un ticket et son assignation à un jalon, ou son ajout à une liste d'un tableau de tickets. Commencez par créer des tickets pour voir des données pour cette étape." +msgid "The maximum file size allowed is 200KB." +msgstr "" + msgid "The number of attempts GitLab will make to access a storage." msgstr "Le nombre de tentatives que GitLab va effectuer pour accéder au stockage." -msgid "The number of failures after which GitLab will start temporarily disabling access to a storage shard on a host" -msgstr "Le nombre d'échecs avant que GitLab ne commence à désactiver l'accès à la partition de stockage sur l'hôte" - msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}." msgstr "Nombre d’échecs avant que GitLab n’empêche tout accès au stockage. Ce nombre d’échecs peut être réinitialisé dans l’interface d’administration : %{link_to_health_page} ou en suivant le %{api_documentation_link}." msgid "The phase of the development lifecycle." msgstr "Les étapes du cycle de développement." -msgid "The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user." -msgstr "Les pipelines programmés exécutent des pipelines dans le futur, de façon répétée, pour les branches et tags spécifiées. Ces pipelines programmés héritent d’un accès partiel au projet basé sur l’utilisa·teur·trice qui leurs est associé·e." - msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit." msgstr "L’étape de planification montre le temps entre l’étape précédente et l’envoi de votre première validation. Ce temps sera automatiquement ajouté quand vous pousserez votre première validation." @@ -2502,20 +3086,47 @@ msgstr "Délai en secondes pendant lequel GitLab gardera les informations d’é msgid "The time in seconds GitLab will try to access storage. After this time a timeout error will be raised." msgstr "Temps en secondes pendant lequel GitLab essaiera d’accéder au stockage. Après ce délai, une erreur d’expiration d’attente sera déclenchée." +msgid "The time in seconds between storage checks. When a previous check did complete yet, GitLab will skip a check." +msgstr "" + msgid "The time taken by each data entry gathered by that stage." msgstr "Le temps pris par chaque entrée récoltée durant cette étape." msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6." msgstr "La valeur située au point médian d’une série de valeur observée. C.à .d., entre 3, 5, 9, le médian est 5. Entre 3, 5, 7, 8, le médian est (5+7)/2 = 6." +msgid "There are no issues to show" +msgstr "" + +msgid "There are no merge requests to show" +msgstr "" + msgid "There are problems accessing Git storage: " msgstr "Il y a des difficultés à accéder aux données Git : " -msgid "This board\\'s scope is reduced" +msgid "There was an error loading users activity calendar." msgstr "" -msgid "This branch has changed since you started editing. Would you like to create a new branch?" -msgstr "Cette branche a changé depuis le début de l’édition. Souhaitez-vous créer une nouvelle branche ?" +msgid "There was an error saving your notification settings." +msgstr "" + +msgid "There was an error subscribing to this label." +msgstr "" + +msgid "There was an error when reseting email token." +msgstr "" + +msgid "There was an error when subscribing to this label." +msgstr "" + +msgid "There was an error when unsubscribing from this label." +msgstr "" + +msgid "This board\\'s scope is reduced" +msgstr "La portée de ce tableau est limitée" + +msgid "This directory" +msgstr "" msgid "This is a confidential issue." msgstr "Ce ticket est confidentiel." @@ -2523,21 +3134,48 @@ msgstr "Ce ticket est confidentiel." msgid "This is the author's first Merge Request to this project." msgstr "C’est la première demande de fusion de cet auteur pour ce projet." +msgid "This issue is confidential" +msgstr "" + msgid "This issue is confidential and locked." msgstr "Ce ticket est confidentiel et verrouillé." msgid "This issue is locked." msgstr "Ce ticket est verrouillé." +msgid "This job depends on a user to trigger its process. Often they are used to deploy code to production environments" +msgstr "" + +msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered" +msgstr "" + +msgid "This job has not been triggered yet" +msgstr "" + +msgid "This job has not started yet" +msgstr "" + +msgid "This job is in pending state and is waiting to be picked by a runner" +msgstr "" + +msgid "This job requires a manual action" +msgstr "" + msgid "This means you can not push code until you create an empty repository or import existing one." msgstr "Cela signifie que vous ne pouvez pas pousser du code tant que vous n’avez pas créé un dépôt vide, ou que vous n’avez pas importé un dépôt existant." msgid "This merge request is locked." msgstr "Cette demande de fusion est verrouillée." -msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here." +msgid "This project" +msgstr "" + +msgid "This repository" msgstr "" +msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here." +msgstr "Ces emails deviennent automatiquement des tickets (les commentaires étant extrait de la conversation par email) répertoriés ici." + msgid "Time before an issue gets scheduled" msgstr "Temps avant qu’un ticket ne soit planifié" @@ -2547,9 +3185,21 @@ msgstr "Temps avant que la résolution du ticket ne débute" msgid "Time between merge request creation and merge/close" msgstr "Temps entre la création d'une demande de fusion et sa fusion/clôture" +msgid "Time tracking" +msgstr "" + msgid "Time until first merge request" msgstr "Temps jusqu’à la première demande de fusion" +msgid "TimeTrackingEstimated|Est" +msgstr "" + +msgid "TimeTracking|Estimated:" +msgstr "" + +msgid "TimeTracking|Spent" +msgstr "" + msgid "Timeago|%s days ago" msgstr "il y a %s jours" @@ -2689,6 +3339,18 @@ msgstr "s" msgid "Title" msgstr "Titre" +msgid "Todo" +msgstr "" + +msgid "Toggle sidebar" +msgstr "" + +msgid "ToggleButton|Toggle Status: OFF" +msgstr "" + +msgid "ToggleButton|Toggle Status: ON" +msgstr "" + msgid "Total Time" msgstr "Temps total" @@ -2702,22 +3364,43 @@ msgid "Track activity with Contribution Analytics." msgstr "Suivre l’activité avec Contribution Analytics." msgid "Track groups of issues that share a theme, across projects and milestones" +msgstr "Suivez les groupes de tickets qui partagent un thème, entre projets et jalons" + +msgid "Total: %{total}" +msgstr "" + +msgid "Track time with quick actions" +msgstr "" + +msgid "Trigger this manual action" msgstr "" msgid "Turn on Service Desk" +msgstr "Activer le Service Desk" + +msgid "Type %{value} to confirm:" +msgstr "" + +msgid "Unable to reset project cache." +msgstr "" + +msgid "Unknown" msgstr "" msgid "Unlock" msgstr "Déverrouiller" +msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment." +msgstr "" + msgid "Unlocked" msgstr "Déverrouillé" msgid "Unstar" msgstr "Supprimer des favoris" -msgid "Unsubscribe" -msgstr "Se désabonner" +msgid "Up to date" +msgstr "" msgid "Upgrade your plan to activate Advanced Global Search." msgstr "Mettez à jour votre abonnement pour activer la Recherche Globale Avancée." @@ -2740,11 +3423,14 @@ msgstr "Téléverser un nouveau fichier" msgid "Upload file" msgstr "Téléverser un fichier" +msgid "Upload new avatar" +msgstr "" + msgid "UploadLink|click to upload" msgstr "Cliquez pour envoyer" msgid "Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab" -msgstr "" +msgstr "Utilisez Service Desk pour intéragir avec vos utilisateurs (par exemple pour offrir un support client) par email directement dans GitLab" msgid "Use the following registration token during setup:" msgstr "Utiliser le jeton d’inscription suivant pendant l’installation :" @@ -2752,9 +3438,15 @@ msgstr "Utiliser le jeton d’inscription suivant pendant l’installation :" msgid "Use your global notification setting" msgstr "Utiliser vos paramètres de notification globaux" +msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." +msgstr "" + msgid "View file @ " msgstr "Voir le fichier @ " +msgid "View labels" +msgstr "" + msgid "View open merge request" msgstr "Afficher la demande de fusion" @@ -2776,11 +3468,14 @@ msgstr "Inconnu" msgid "Want to see the data? Please ask an administrator for access." msgstr "Vous voulez voir les données ? Merci de contacter un administrateur pour en obtenir l’accès." +msgid "We could not verify that one of your projects on GCP has billing enabled. Please try again." +msgstr "" + msgid "We don't have enough data to show this stage." msgstr "Nous n'avons pas suffisamment de données pour afficher cette étape." msgid "We want to be sure it is you, please confirm you are not a robot." -msgstr "" +msgstr "Nous voulons être sûrs que c'est bien vous, merci de confirmer que vous n’êtes pas un robot." msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group." msgstr "Les webhooks vous permettent d’appeler une URL si, par exemple, du nouveau code est poussé ou un nouveau ticket est créé. Vous pouvez configurer les webhooks pour écouter les événements spécifiques comme des poussées de code, des tickets ou des demandes de fusion. Les webhooks de groupes s’appliqueront à tous les projets dans un groupe, ce qui vous permet de normaliser la fonctionnalité du webhook dans votre groupe entier." @@ -2788,9 +3483,6 @@ msgstr "Les webhooks vous permettent d’appeler une URL si, par exemple, du nou msgid "Weight" msgstr "Poids" -msgid "When access to a storage fails. GitLab will prevent access to the storage for the time specified here. This allows the filesystem to recover. Repositories on failing shards are temporarly unavailable" -msgstr "Si l’accès à un stockage échoue, GitLab empêchera l’accès au stockage pendant la durée spécifiée ici. Cela permettra au système de fichiers de récupérer. Les dépôts présents sur les secteurs en erreur seront temporairement indisponibles." - msgid "Wiki" msgstr "Wiki" @@ -2809,6 +3501,12 @@ msgstr "Il est recommandé d’installer %{markdown} pour que les spécificités msgid "WikiClone|Start Gollum and edit locally" msgstr "Démarrer Gollum et modifier localement" +msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title." +msgstr "" + +msgid "WikiEdit|There is already a page with the same title in that path." +msgstr "" + msgid "WikiEmptyPageError|You are not allowed to create wiki pages" msgstr "Vous n’êtes pas autorisé·e à créer des pages wiki" @@ -2911,9 +3609,21 @@ msgstr "Vous allez supprimer la relation de fourche avec le projet source %{fork msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" msgstr "Vous allez transférer %{project_name_with_namespace} à un nouveau propriétaire. Êtes vous ABSOLUMENT sûr·e ?" +msgid "You can also star a label to make it a priority label." +msgstr "" + +msgid "You can move around the graph by using the arrow keys." +msgstr "" + +msgid "You can move around the graph by using the arrow keys." +msgstr "" + msgid "You can only add files when you are on a branch" msgstr "Vous ne pouvez ajouter de fichier que dans une branche" +msgid "You can only edit files when you are on a branch" +msgstr "" + msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead." msgstr "Vous ne pouvez pas écrire sur une instance GitLab Geo secondaire en lecture-seule. Veuillez utiliser le %{link_to_primary_node} à la place." @@ -2953,6 +3663,12 @@ msgstr "Vous ne pourrez pas récupérer ou pousser de code par SSH tant que vous msgid "You won't be able to pull or push project code via SSH until you add an SSH key to your profile" msgstr "Vous ne pourrez pas récupérer ou pousser de code par SSH tant que vous n’aurez pas ajouté de clé SSH à votre profil" +msgid "You'll need to use different branch names to get a valid comparison." +msgstr "" + +msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure" +msgstr "" + msgid "Your comment will not be visible to the public." msgstr "Votre commentaire ne sera pas visible publiquement." @@ -2965,26 +3681,220 @@ msgstr "Votre nom" msgid "Your projects" msgstr "Vos projets" +msgid "assign yourself" +msgstr "" + msgid "branch name" msgstr "nom de la branche" msgid "by" msgstr "par" +msgid "ciReport|Code quality" +msgstr "" + +msgid "ciReport|DAST detected no alerts by analyzing the review app" +msgstr "" + +msgid "ciReport|Failed to load ${type} report" +msgstr "" + +msgid "ciReport|Fixed:" +msgstr "" + +msgid "ciReport|Instances" +msgstr "" + +msgid "ciReport|Learn more about whitelisting" +msgstr "" + +msgid "ciReport|Loading ${type} report" +msgstr "" + +msgid "ciReport|No changes to code quality" +msgstr "" + +msgid "ciReport|No changes to performance metrics" +msgstr "" + +msgid "ciReport|Performance metrics" +msgstr "" + +msgid "ciReport|SAST" +msgstr "" + +msgid "ciReport|SAST detected no security vulnerabilities" +msgstr "" + +msgid "ciReport|SAST:container no vulnerabilities were found" +msgstr "" + +msgid "ciReport|Show complete code vulnerabilities report" +msgstr "" + +msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}" +msgstr "" + msgid "commit" msgstr "validation" +msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue." +msgstr "" + +msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue." +msgstr "" + msgid "day" msgid_plural "days" msgstr[0] "jour" msgstr[1] "jours" +msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command." +msgstr "" + +msgid "merge request" +msgid_plural "merge requests" +msgstr[0] "" +msgstr[1] "" + +msgid "mrWidget|Cancel automatic merge" +msgstr "" + +msgid "mrWidget|Check out branch" +msgstr "" + +msgid "mrWidget|Checking ability to merge automatically" +msgstr "" + +msgid "mrWidget|Cherry-pick" +msgstr "" + +msgid "mrWidget|Cherry-pick this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Closed" +msgstr "" + +msgid "mrWidget|Closed by" +msgstr "" + +msgid "mrWidget|Closes" +msgstr "" + +msgid "mrWidget|Did not close" +msgstr "" + +msgid "mrWidget|Email patches" +msgstr "" + +msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|Mentions" +msgstr "" + +msgid "mrWidget|Merge" +msgstr "" + +msgid "mrWidget|Merge failed." +msgstr "" + +msgid "mrWidget|Merge locally" +msgstr "" + +msgid "mrWidget|Merged by" +msgstr "" + +msgid "mrWidget|Plain diff" +msgstr "" + +msgid "mrWidget|Refresh" +msgstr "" + +msgid "mrWidget|Refresh now" +msgstr "" + +msgid "mrWidget|Refreshing now" +msgstr "" + +msgid "mrWidget|Remove Source Branch" +msgstr "" + +msgid "mrWidget|Remove source branch" +msgstr "" + +msgid "mrWidget|Request to merge" +msgstr "" + +msgid "mrWidget|Resolve conflicts" +msgstr "" + +msgid "mrWidget|Revert" +msgstr "" + +msgid "mrWidget|Revert this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Set by" +msgstr "" + +msgid "mrWidget|The changes were merged into" +msgstr "" + +msgid "mrWidget|The changes were not merged into" +msgstr "" + +msgid "mrWidget|The changes will be merged into" +msgstr "" + +msgid "mrWidget|The source branch has been removed" +msgstr "" + +msgid "mrWidget|The source branch is being removed" +msgstr "" + +msgid "mrWidget|The source branch will be removed" +msgstr "" + +msgid "mrWidget|The source branch will not be removed" +msgstr "" + +msgid "mrWidget|There are merge conflicts" +msgstr "" + +msgid "mrWidget|This merge request failed to be merged automatically" +msgstr "" + +msgid "mrWidget|This merge request is in the process of being merged" +msgstr "" + +msgid "mrWidget|This project is archived, write access has been disabled" +msgstr "" + +msgid "mrWidget|You can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|You can remove source branch now" +msgstr "" + +msgid "mrWidget|command line" +msgstr "" + +msgid "mrWidget|into" +msgstr "" + +msgid "mrWidget|to be merged automatically when the pipeline succeeds" +msgstr "" + msgid "new merge request" msgstr "nouvelle demande de fusion" msgid "notification emails" msgstr "courriels de notification" +msgid "or" +msgstr "" + msgid "parent" msgid_plural "parents" msgstr[0] "parent" @@ -2996,12 +3906,21 @@ msgstr "mot de passe" msgid "personal access token" msgstr "jeton d’accès personnel" +msgid "remove due date" +msgstr "" + msgid "source" msgstr "source" +msgid "spendCommand|%{slash_command} will update the sum of the time spent." +msgstr "" + msgid "to help your contributors communicate effectively!" msgstr "pour aider vos contributeurs à communiquer efficacement !" msgid "username" msgstr "nom d’utilisateur" +msgid "uses Kubernetes clusters to deploy your code!" +msgstr "" + diff --git a/locale/gitlab.pot b/locale/gitlab.pot index d5cb5400d4f..fadc17a659d 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-02-06 10:02+0100\n" -"PO-Revision-Date: 2018-02-06 10:02+0100\n" +"POT-Creation-Date: 2018-02-07 11:38-0600\n" +"PO-Revision-Date: 2018-02-07 11:38-0600\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "Language: \n" @@ -129,6 +129,9 @@ msgstr "" msgid "Add new directory" msgstr "" +msgid "Add todo" +msgstr "" + msgid "AdminArea|Stop all jobs" msgstr "" @@ -228,6 +231,21 @@ msgstr "" msgid "Artifacts" msgstr "" +msgid "Assign custom color like #FF0000" +msgstr "" + +msgid "Assign labels" +msgstr "" + +msgid "Assign milestone" +msgstr "" + +msgid "Assign to" +msgstr "" + +msgid "Assignee" +msgstr "" + msgid "Attach a file by drag & drop or %{upload_link}" msgstr "" @@ -243,6 +261,9 @@ msgstr "" msgid "Author" msgstr "" +msgid "Authors: %{authors}" +msgstr "" + msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." msgstr "" @@ -276,6 +297,12 @@ msgstr "" msgid "Avatar will be removed. Are you sure?" msgstr "" +msgid "Average per day: %{average}" +msgstr "" + +msgid "Begin with the selected commit" +msgstr "" + msgid "Branch" msgid_plural "Branches" msgstr[0] "" @@ -524,15 +551,24 @@ msgstr "" msgid "CiVariables|Remove variable row" msgstr "" +msgid "CiVariable|* (All environments)" +msgstr "" + msgid "CiVariable|All environments" msgstr "" +msgid "CiVariable|Error occured while saving variables" +msgstr "" + msgid "CiVariable|Protected" msgstr "" msgid "CiVariable|Toggle protected" msgstr "" +msgid "CiVariable|Validation failed" +msgstr "" + msgid "CircuitBreakerApiLink|circuitbreaker api" msgstr "" @@ -695,9 +731,15 @@ msgstr "" msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters" msgstr "" +msgid "ClusterIntegration|Manage" +msgstr "" + msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}" msgstr "" +msgid "ClusterIntegration|More information" +msgstr "" + msgid "ClusterIntegration|Note:" msgstr "" @@ -826,6 +868,9 @@ msgstr "" msgid "Commit message" msgstr "" +msgid "Commit statistics for %{ref} %{start_time} - %{end_time}" +msgstr "" + msgid "CommitBoxTitle|Commit" msgstr "" @@ -838,6 +883,15 @@ msgstr "" msgid "Commits feed" msgstr "" +msgid "Commits per day hour (UTC)" +msgstr "" + +msgid "Commits per day of month" +msgstr "" + +msgid "Commits per weekday" +msgstr "" + msgid "Commits|An error occurred while fetching merge requests data." msgstr "" @@ -877,6 +931,9 @@ msgstr "" msgid "CompareBranches|There isn't anything to compare." msgstr "" +msgid "Confidentiality" +msgstr "" + msgid "Container Registry" msgstr "" @@ -949,6 +1006,12 @@ msgstr "" msgid "Copy commit SHA to clipboard" msgstr "" +msgid "Copy reference to clipboard" +msgstr "" + +msgid "Create" +msgstr "" + msgid "Create New Directory" msgstr "" @@ -964,6 +1027,9 @@ msgstr "" msgid "Create file" msgstr "" +msgid "Create lists from labels. Issues with that label appear in that list." +msgstr "" + msgid "Create merge request" msgstr "" @@ -976,6 +1042,9 @@ msgstr "" msgid "Create new file" msgstr "" +msgid "Create new label" +msgstr "" + msgid "Create new..." msgstr "" @@ -1098,6 +1167,9 @@ msgstr "" msgid "DownloadSource|Download" msgstr "" +msgid "Due date" +msgstr "" + msgid "Edit" msgstr "" @@ -1277,6 +1349,9 @@ msgstr "" msgid "Generate a default set of labels" msgstr "" +msgid "Git revision" +msgstr "" + msgid "Git storage health information has been reset" msgstr "" @@ -1512,6 +1587,9 @@ msgstr "" msgid "LastPushEvent|at" msgstr "" +msgid "Learn more" +msgstr "" + msgid "Learn more in the" msgstr "" @@ -1533,18 +1611,30 @@ msgstr "" msgid "Lock" msgstr "" +msgid "Lock %{issuableDisplayName}" +msgstr "" + +msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment." +msgstr "" + msgid "Locked" msgstr "" msgid "Login" msgstr "" +msgid "Manage labels" +msgstr "" + msgid "Mar" msgstr "" msgid "March" msgstr "" +msgid "Mark done" +msgstr "" + msgid "Maximum git storage failures" msgstr "" @@ -1575,6 +1665,9 @@ msgstr "" msgid "Messages" msgstr "" +msgid "Milestone" +msgstr "" + msgid "Milestones|Delete milestone" msgstr "" @@ -1596,6 +1689,15 @@ msgstr "" msgid "More information is available|here" msgstr "" +msgid "Move" +msgstr "" + +msgid "Move issue" +msgstr "" + +msgid "Name new label" +msgstr "" + msgid "New Issue" msgid_plural "New Issues" msgstr[0] "" @@ -1649,9 +1751,18 @@ msgstr "" msgid "New tag" msgstr "" +msgid "No assignee" +msgstr "" + msgid "No connection could be made to a Gitaly Server, please check your logs!" msgstr "" +msgid "No due date" +msgstr "" + +msgid "No estimate or time spent" +msgstr "" + msgid "No file chosen" msgstr "" @@ -1667,9 +1778,15 @@ msgstr "" msgid "None" msgstr "" +msgid "Not allowed to merge" +msgstr "" + msgid "Not available" msgstr "" +msgid "Not confidential" +msgstr "" + msgid "Not enough data" msgstr "" @@ -1937,6 +2054,9 @@ msgstr "" msgid "Profiles|your account" msgstr "" +msgid "Programming languages used in this repository" +msgstr "" + msgid "Project '%{project_name}' is in the process of being deleted." msgstr "" @@ -2051,7 +2171,7 @@ msgstr "" msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/" msgstr "" -msgid "PrometheusService|Prometheus monitoring" +msgid "PrometheusService|Time-series monitoring service" msgstr "" msgid "PrometheusService|View environments" @@ -2069,6 +2189,9 @@ msgstr "" msgid "Push events" msgstr "" +msgid "Quick actions can be used in the issues description and comment boxes." +msgstr "" + msgid "Read more" msgstr "" @@ -2081,6 +2204,9 @@ msgstr "" msgid "RefSwitcher|Tags" msgstr "" +msgid "Reference:" +msgstr "" + msgid "Register / Sign In" msgstr "" @@ -2146,6 +2272,9 @@ msgstr "" msgid "Save pipeline schedule" msgstr "" +msgid "Save variables" +msgstr "" + msgid "Schedule a new pipeline" msgstr "" @@ -2158,18 +2287,33 @@ msgstr "" msgid "Search branches and tags" msgstr "" +msgid "Search milestones" +msgstr "" + +msgid "Search project" +msgstr "" + +msgid "Search users" +msgstr "" + msgid "Seconds before reseting failure information" msgstr "" msgid "Seconds to wait for a storage access attempt" msgstr "" +msgid "Secret variables" +msgstr "" + msgid "Select Archive Format" msgstr "" msgid "Select a timezone" msgstr "" +msgid "Select assignee" +msgstr "" + msgid "Select branch/tag" msgstr "" @@ -2226,6 +2370,9 @@ msgstr "" msgid "Something went wrong on our end." msgstr "" +msgid "Something went wrong trying to change the confidentiality of this issue" +msgstr "" + msgid "Something went wrong when toggling the button" msgstr "" @@ -2558,6 +2705,9 @@ msgstr "" msgid "This is the author's first Merge Request to this project." msgstr "" +msgid "This issue is confidential" +msgstr "" + msgid "This issue is confidential and locked." msgstr "" @@ -2603,9 +2753,21 @@ msgstr "" msgid "Time between merge request creation and merge/close" msgstr "" +msgid "Time tracking" +msgstr "" + msgid "Time until first merge request" msgstr "" +msgid "TimeTrackingEstimated|Est" +msgstr "" + +msgid "TimeTracking|Estimated:" +msgstr "" + +msgid "TimeTracking|Spent" +msgstr "" + msgid "Timeago|%s days ago" msgstr "" @@ -2742,6 +2904,12 @@ msgstr[1] "" msgid "Time|s" msgstr "" +msgid "Todo" +msgstr "" + +msgid "Toggle sidebar" +msgstr "" + msgid "ToggleButton|Toggle Status: OFF" msgstr "" @@ -2757,6 +2925,12 @@ msgstr "" msgid "Total test time for all commits/merges" msgstr "" +msgid "Total: %{total}" +msgstr "" + +msgid "Track time with quick actions" +msgstr "" + msgid "Trigger this manual action" msgstr "" @@ -2769,6 +2943,9 @@ msgstr "" msgid "Unlock" msgstr "" +msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment." +msgstr "" + msgid "Unlocked" msgstr "" @@ -2796,9 +2973,15 @@ msgstr "" msgid "Use your global notification setting" msgstr "" +msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." +msgstr "" + msgid "View file @ " msgstr "" +msgid "View labels" +msgstr "" + msgid "View open merge request" msgstr "" @@ -2958,6 +3141,9 @@ msgstr "" msgid "You can also star a label to make it a priority label." msgstr "" +msgid "You can move around the graph by using the arrow keys." +msgstr "" + msgid "You can only add files when you are on a branch" msgstr "" @@ -3018,14 +3204,26 @@ msgstr "" msgid "Your projects" msgstr "" +msgid "assign yourself" +msgstr "" + msgid "branch name" msgstr "" +msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue." +msgstr "" + +msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue." +msgstr "" + msgid "day" msgid_plural "days" msgstr[0] "" msgstr[1] "" +msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command." +msgstr "" + msgid "merge request" msgid_plural "merge requests" msgstr[0] "" @@ -3180,9 +3378,15 @@ msgstr "" msgid "personal access token" msgstr "" +msgid "remove due date" +msgstr "" + msgid "source" msgstr "" +msgid "spendCommand|%{slash_command} will update the sum of the time spent." +msgstr "" + msgid "username" msgstr "" diff --git a/locale/it/gitlab.po b/locale/it/gitlab.po index 52bbc28ac10..62a6da1604a 100644 --- a/locale/it/gitlab.po +++ b/locale/it/gitlab.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab-ee\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-12-12 18:31+0000\n" -"PO-Revision-Date: 2018-01-05 04:42-0500\n" +"POT-Creation-Date: 2018-02-07 11:38-0600\n" +"PO-Revision-Date: 2018-02-12 04:00-0500\n" "Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n" "Language-Team: Italian\n" "Language: it_IT\n" @@ -16,23 +16,41 @@ msgstr "" "X-Crowdin-Language: it\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" +msgid " and" +msgstr "" + msgid "%d commit" msgid_plural "%d commits" msgstr[0] "%d commit" msgstr[1] "%d commits" +msgid "%d commit behind" +msgid_plural "%d commits behind" +msgstr[0] "" +msgstr[1] "" + +msgid "%d issue" +msgid_plural "%d issues" +msgstr[0] "" +msgstr[1] "" + msgid "%d layer" msgid_plural "%d layers" msgstr[0] "%d livello" msgstr[1] "%d livelli" +msgid "%d merge request" +msgid_plural "%d merge requests" +msgstr[0] "" +msgstr[1] "" + msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "%s additional commits have been omitted to prevent performance issues." msgstr[0] "%s commit aggiuntivo è stato omesso per evitare degradi di prestazioni negli issues." msgstr[1] "%s commit aggiuntivi sono stati omessi per evitare degradi di prestazioni negli issues." -msgid "%{commit_author_link} committed %{commit_timeago}" -msgstr "%{commit_author_link} ha committato %{commit_timeago}" +msgid "%{commit_author_link} authored %{commit_timeago}" +msgstr "" msgid "%{count} participant" msgid_plural "%{count} participants" @@ -45,9 +63,6 @@ msgstr "%{number_commits_behind} commits precedenti %{default_branch}, %{number_ msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will allow access on the next attempt." msgstr "%{number_of_failures} di %{maximum_failures} fallimenti. GitLab consentirà l'accesso al prossimo tentativo." -msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will block access for %{number_of_seconds} seconds." -msgstr "%{number_of_failures} di %{maximum_failures} fallimenti. Gitlab bloccherà l'accesso per %{number_of_seconds} secondi." - msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved." msgstr "%{number_of_failures} di %{maximum_failures} fallimenti. Gitlab non ritenterà automaticamente. Ripristina l'informazioni d'archiviazione quando il problema è risolto." @@ -121,24 +136,81 @@ msgstr "Aggiungi Licenza" msgid "Add new directory" msgstr "Aggiungi una directory (cartella)" +msgid "Add todo" +msgstr "" + +msgid "AdminArea|Stop all jobs" +msgstr "" + +msgid "AdminArea|Stop all jobs?" +msgstr "" + +msgid "AdminArea|Stop jobs" +msgstr "" + +msgid "AdminArea|Stopping jobs failed" +msgstr "" + +msgid "AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running." +msgstr "" + msgid "AdminHealthPageLink|health page" msgstr "Pagina di stato" +msgid "Advanced" +msgstr "" + msgid "Advanced settings" msgstr "Impostazioni Avanzate" msgid "All" msgstr "Tutto" +msgid "All changes are committed" +msgstr "" + +msgid "Allows you to add and manage Kubernetes clusters." +msgstr "" + +msgid "An error occurred previewing the blob" +msgstr "" + msgid "An error occurred when toggling the notification subscription" msgstr "Errore durante l'attivazione/disattivazione della sottoscrizione per l'iscrizione" msgid "An error occurred when updating the issue weight" msgstr "" +msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." +msgstr "" + +msgid "An error occurred while fetching markdown preview" +msgstr "" + msgid "An error occurred while fetching sidebar data" msgstr "Errore durante il recupero dei dati della barra laterale" +msgid "An error occurred while getting projects" +msgstr "" + +msgid "An error occurred while loading filenames" +msgstr "" + +msgid "An error occurred while rendering KaTeX" +msgstr "" + +msgid "An error occurred while rendering preview broadcast message" +msgstr "" + +msgid "An error occurred while retrieving calendar activity" +msgstr "" + +msgid "An error occurred while retrieving diff" +msgstr "" + +msgid "An error occurred while validating username" +msgstr "" + msgid "An error occurred. Please try again." msgstr "Si è verificato un errore. Riprova." @@ -163,9 +235,6 @@ msgstr "Sei sicuro di voler cancellare questa pipeline programmata?" msgid "Are you sure you want to discard your changes?" msgstr "Vuoi davvero ignorare le modifiche?" -msgid "Are you sure you want to leave this group?" -msgstr "Vuoi davvero lasciare questo gruppo?" - msgid "Are you sure you want to reset registration token?" msgstr "Sei sicuro di voler ripristinare il token di registrazione?" @@ -178,6 +247,21 @@ msgstr "Sei sicuro?" msgid "Artifacts" msgstr "Artefatti" +msgid "Assign custom color like #FF0000" +msgstr "" + +msgid "Assign labels" +msgstr "" + +msgid "Assign milestone" +msgstr "" + +msgid "Assign to" +msgstr "" + +msgid "Assignee" +msgstr "" + msgid "Attach a file by drag & drop or %{upload_link}" msgstr "Aggiungi un file tramite trascina & rilascia ( drag & drop) o %{upload_link}" @@ -193,15 +277,18 @@ msgstr "Log di autenticazione" msgid "Author" msgstr "Autore" -msgid "Auto Review Apps and Auto Deploy need a domain name and the %{kubernetes} to work correctly." -msgstr "Le app d'auto-review e l'Auto Deploy (rilascio automatico) necessita di un nome dominio e il servizio %{kubernetes} per funzionare correttamente." +msgid "Authors: %{authors}" +msgstr "" + +msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." +msgstr "" + +msgid "Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly." +msgstr "" msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly." msgstr "Le app d'auto-review e l'Auto Deploy (rilascio automatico) necessita di un nome dominio per funzionare correttamente." -msgid "Auto Review Apps and Auto Deploy need the %{kubernetes} to work correctly." -msgstr "Le app d'auto-review e l'Auto Deploy (rilascio automatico) necessita del servizio %{kubernetes} per funzionare correttamente." - msgid "AutoDevOps|Auto DevOps (Beta)" msgstr "Auto DevOps (Béta)" @@ -223,6 +310,12 @@ msgstr "Puoi attivare %{link_to_settings} per questo progetto." msgid "Available" msgstr "Disponibile" +msgid "Avatar will be removed. Are you sure?" +msgstr "" + +msgid "Average per day: %{average}" +msgstr "" + msgid "Billing" msgstr "" @@ -277,6 +370,9 @@ msgstr "" msgid "BillingPlans|per user" msgstr "" +msgid "Begin with the selected commit" +msgstr "" + msgid "Branch" msgid_plural "Branches" msgstr[0] "" @@ -405,8 +501,8 @@ msgstr "per" msgid "CI / CD" msgstr "CI / CD" -msgid "CI configuration" -msgstr "Configurazione CI (Integrazione Continua)" +msgid "CI/CD configuration" +msgstr "" msgid "CICD|Jobs" msgstr "Jobs" @@ -417,6 +513,9 @@ msgstr "Cancella" msgid "Cancel edit" msgstr "Annulla modifica" +msgid "Cannot modify managed Kubernetes cluster" +msgstr "" + msgid "Change Weight" msgstr "" @@ -432,15 +531,24 @@ msgstr "Cherry-pick" msgid "ChangeTypeAction|Revert" msgstr "Ripristina" +msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes." +msgstr "" + msgid "Changelog" msgstr "Changelog" +msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision." +msgstr "" + msgid "Charts" msgstr "Grafici" msgid "Chat" msgstr "Chat" +msgid "Check interval" +msgstr "" + msgid "Checking %{text} availability…" msgstr "Controllo disponibilità per %{text}…" @@ -453,7 +561,19 @@ msgstr "Cherry-pick di questo commit" msgid "Cherry-pick this merge request" msgstr "Cherry-pick questa richiesta di merge" -msgid "Choose which groups you wish to replicate to this secondary node. Leave blank to replicate all." +msgid "Choose File ..." +msgstr "" + +msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request." +msgstr "" + +msgid "Choose file..." +msgstr "" + +msgid "Choose which groups you wish to synchronize to this secondary node." +msgstr "" + +msgid "Choose which shards you wish to synchronize to this secondary node." msgstr "" msgid "CiStatusLabel|canceled" @@ -510,80 +630,92 @@ msgstr "saltata" msgid "CiStatus|running" msgstr "in corso" +msgid "CiVariables|Input variable key" +msgstr "" + +msgid "CiVariables|Input variable value" +msgstr "" + +msgid "CiVariables|Remove variable row" +msgstr "" + +msgid "CiVariable|* (All environments)" +msgstr "" + +msgid "CiVariable|All environments" +msgstr "" + +msgid "CiVariable|Create wildcard" +msgstr "" + +msgid "CiVariable|Error occured while saving variables" +msgstr "" + +msgid "CiVariable|New environment" +msgstr "" + +msgid "CiVariable|Protected" +msgstr "" + +msgid "CiVariable|Search environments" +msgstr "" + +msgid "CiVariable|Toggle protected" +msgstr "" + +msgid "CiVariable|Validation failed" +msgstr "" + msgid "CircuitBreakerApiLink|circuitbreaker api" msgstr "api circuitbreaker" +msgid "Click to expand text" +msgstr "" + msgid "Clone repository" msgstr "Clona repository" msgid "Close" msgstr "" -msgid "Cluster" -msgstr "Cluster" - -msgid "ClusterIntegration|%{appList} was successfully installed on your cluster" -msgstr "%{appList} è stata installata con successo nel tuo cluster" +msgid "Closed" +msgstr "" -msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which incur additional costs. See %{pricingLink}" -msgstr "%{boldNotice} Ciò richiederà delle risorse extra come un load balancer, dove si applicheranno costi aggiuntivi. Consulta %{pricingLink}" +msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster" +msgstr "" msgid "ClusterIntegration|API URL" msgstr "API URL" -msgid "ClusterIntegration|Active" -msgstr "Attivo" - -msgid "ClusterIntegration|Add an existing cluster" -msgstr "Aggiungi un cluster esistente" +msgid "ClusterIntegration|Add Kubernetes cluster" +msgstr "" -msgid "ClusterIntegration|Add cluster" -msgstr "Aggiungi cluster" +msgid "ClusterIntegration|Add an existing Kubernetes cluster" +msgstr "" -msgid "ClusterIntegration|All" -msgstr "Tutti" +msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration" +msgstr "" msgid "ClusterIntegration|Applications" msgstr "Applicazioni" +msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster." +msgstr "" + msgid "ClusterIntegration|CA Certificate" msgstr "Certificato CA" msgid "ClusterIntegration|Certificate Authority bundle (PEM format)" msgstr "Certificate Authority bundle (formato PEM)" -msgid "ClusterIntegration|Choose how to set up cluster integration" -msgstr "Scegli come impostare l'integrazione cluster" - -msgid "ClusterIntegration|Cluster" -msgstr "Cluster" - -msgid "ClusterIntegration|Cluster details" -msgstr "Dettagli Cluster" - -msgid "ClusterIntegration|Cluster integration" -msgstr "Integrazione Cluster" - -msgid "ClusterIntegration|Cluster integration is disabled for this project." -msgstr "L'integrazione dei cluster è disabilitata per questo progetto." - -msgid "ClusterIntegration|Cluster integration is enabled for this project." -msgstr "L'integrazione dei cluster è abilitata per questo progetto." - -msgid "ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab's connection to it." -msgstr "L'integrazione dei cluster è abilitata per questo progetto. Se la disabiliti il tuo cluster non sarà modificato, sarà solo spenta la connessione a Gitlab." - -msgid "ClusterIntegration|Cluster is being created on Google Kubernetes Engine..." +msgid "ClusterIntegration|Choose how to set up Kubernetes cluster integration" msgstr "" -msgid "ClusterIntegration|Cluster name" -msgstr "Nome Cluster" - -msgid "ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine. Refresh the page to see cluster's details" +msgid "ClusterIntegration|Choose which of your project's environments will use this Kubernetes cluster." msgstr "" -msgid "ClusterIntegration|Clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" -msgstr "I cluster di consentono di revisionare le app, effettuare rilasci, eseguire pipelines, e molto altro in modo semplice. %{link_to_help_page}" +msgid "ClusterIntegration|Control how your Kubernetes cluster integrates with GitLab" +msgstr "" msgid "ClusterIntegration|Copy API URL" msgstr "Copia URL API" @@ -591,38 +723,35 @@ msgstr "Copia URL API" msgid "ClusterIntegration|Copy CA Certificate" msgstr "Copia Certificato CA" +msgid "ClusterIntegration|Copy Kubernetes cluster name" +msgstr "" + msgid "ClusterIntegration|Copy Token" msgstr "Copia Token" -msgid "ClusterIntegration|Copy cluster name" -msgstr "Copia nome del cluster" - -msgid "ClusterIntegration|Create a new cluster on Google Engine right from GitLab" -msgstr "Crea un nuovo cluster su Google Engine direttamente da Gitlab" +msgid "ClusterIntegration|Create Kubernetes cluster" +msgstr "" -msgid "ClusterIntegration|Create cluster" -msgstr "Crea cluster" +msgid "ClusterIntegration|Create Kubernetes cluster on Google Kubernetes Engine" +msgstr "" -msgid "ClusterIntegration|Create cluster on Google Kubernetes Engine" +msgid "ClusterIntegration|Create a new Kubernetes cluster on Google Kubernetes Engine right from GitLab" msgstr "" msgid "ClusterIntegration|Create on GKE" msgstr "Crea su GKE" -msgid "ClusterIntegration|Enable cluster integration" -msgstr "Abilita integrazione cluster" - msgid "ClusterIntegration|Enter the details for an existing Kubernetes cluster" msgstr "Inserisci i dettagli per un cluster Kubernetes esistente" -msgid "ClusterIntegration|Enter the details for your cluster" -msgstr "Inserisci i dettagli per il tuo cluster" +msgid "ClusterIntegration|Enter the details for your Kubernetes cluster" +msgstr "" -msgid "ClusterIntegration|Environment pattern" -msgstr "Environment pattern" +msgid "ClusterIntegration|Environment scope" +msgstr "" -msgid "ClusterIntegration|GKE pricing" -msgstr "Prezzi GKE" +msgid "ClusterIntegration|GitLab Integration" +msgstr "" msgid "ClusterIntegration|GitLab Runner" msgstr "Gitlab Runner" @@ -639,47 +768,83 @@ msgstr "" msgid "ClusterIntegration|Helm Tiller" msgstr "Helm Tiller" -msgid "ClusterIntegration|Inactive" -msgstr "Inattivo" - msgid "ClusterIntegration|Ingress" msgstr "Ingresso" msgid "ClusterIntegration|Install" msgstr "Installa" -msgid "ClusterIntegration|Install applications on your cluster. Read more about %{helpLink}" -msgstr "Installa applicazioni sul tuo cluster. Leggi di più a riguardo %{helpLink}" - msgid "ClusterIntegration|Installed" msgstr "Installato" msgid "ClusterIntegration|Installing" msgstr "" -msgid "ClusterIntegration|Integrate cluster automation" +msgid "ClusterIntegration|Integrate Kubernetes cluster automation" +msgstr "" + +msgid "ClusterIntegration|Integration status" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster details" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is disabled for this project." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project. Disabling this integration will not affect your Kubernetes cluster, it will only temporarily turn off GitLab's connection to it." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster name" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project" msgstr "" msgid "ClusterIntegration|Learn more about %{link_to_documentation}" msgstr "" -msgid "ClusterIntegration|Learn more about Clusters" -msgstr "Approfondisci riguardo i Clusters" +msgid "ClusterIntegration|Learn more about Kubernetes" +msgstr "" + +msgid "ClusterIntegration|Learn more about environments" +msgstr "" msgid "ClusterIntegration|Machine type" msgstr "Tipo di macchina" -msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create clusters" -msgstr "Assicurati che il tuo account %{link_to_requirements} per creare i cluster" +msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters" +msgstr "" + +msgid "ClusterIntegration|Manage" +msgstr "" -msgid "ClusterIntegration|Manage cluster integration on your GitLab project" -msgstr "Gestisci l'integrazione dei cluster nel tuo progetto GitLab" +msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}" +msgstr "" -msgid "ClusterIntegration|Manage your cluster by visiting %{link_gke}" -msgstr "Gestisci i tuoi cluster visitando %{link_gke}" +msgid "ClusterIntegration|More information" +msgstr "" -msgid "ClusterIntegration|Multiple clusters are available in GitLab Entreprise Edition Premium and Ultimate" -msgstr "I cluster multipli sono disponibili nell'edizione Enterprise di Gitlab (Premium e Ultimate)" +msgid "ClusterIntegration|Multiple Kubernetes clusters are available in GitLab Enterprise Edition Premium and Ultimate" +msgstr "" msgid "ClusterIntegration|Note:" msgstr "Nota:" @@ -687,18 +852,12 @@ msgstr "Nota:" msgid "ClusterIntegration|Number of nodes" msgstr "Numero di nodi" -msgid "ClusterIntegration|Please enter access information for your cluster. If you need help, you can read our %{link_to_help_page} on clusters" +msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes" msgstr "" msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:" msgstr "" -msgid "ClusterIntegration|Problem setting up the cluster" -msgstr "" - -msgid "ClusterIntegration|Problem setting up the clusters list" -msgstr "" - msgid "ClusterIntegration|Project ID" msgstr "" @@ -708,16 +867,19 @@ msgstr "" msgid "ClusterIntegration|Project namespace (optional, unique)" msgstr "" -msgid "ClusterIntegration|Read our %{link_to_help_page} on cluster integration." +msgid "ClusterIntegration|Prometheus" msgstr "" -msgid "ClusterIntegration|Remove cluster integration" +msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration." +msgstr "" + +msgid "ClusterIntegration|Remove Kubernetes cluster integration" msgstr "" msgid "ClusterIntegration|Remove integration" msgstr "" -msgid "ClusterIntegration|Removing cluster integration will remove the cluster configuration you have added to this project. It will not delete your cluster on Google Kubernetes Engine." +msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster." msgstr "" msgid "ClusterIntegration|Request to begin installing failed" @@ -726,7 +888,7 @@ msgstr "" msgid "ClusterIntegration|Save changes" msgstr "" -msgid "ClusterIntegration|See and edit the details for your cluster" +msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster" msgstr "" msgid "ClusterIntegration|See machine types" @@ -747,25 +909,25 @@ msgstr "" msgid "ClusterIntegration|Something went wrong on our end." msgstr "" -msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine" +msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine" msgstr "" msgid "ClusterIntegration|Something went wrong while installing %{title}" msgstr "" -msgid "ClusterIntegration|There are no clusters to show" +msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below" msgstr "" -msgid "ClusterIntegration|This account must have permissions to create a cluster in the %{link_to_container_project} specified below" +msgid "ClusterIntegration|Toggle Kubernetes Cluster" msgstr "" -msgid "ClusterIntegration|Toggle Cluster" +msgid "ClusterIntegration|Toggle Kubernetes cluster" msgstr "" msgid "ClusterIntegration|Token" msgstr "" -msgid "ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." +msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." msgstr "" msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}" @@ -777,7 +939,7 @@ msgstr "" msgid "ClusterIntegration|access to Google Kubernetes Engine" msgstr "" -msgid "ClusterIntegration|cluster" +msgid "ClusterIntegration|check the pricing here" msgstr "" msgid "ClusterIntegration|documentation" @@ -795,6 +957,9 @@ msgstr "" msgid "ClusterIntegration|properly configured" msgstr "" +msgid "Collapse" +msgstr "" + msgid "Comments" msgstr "Commenti" @@ -812,6 +977,9 @@ msgstr "Durata del commit (in minuti) per gli ultimi 30 commit" msgid "Commit message" msgstr "Messaggio del commit" +msgid "Commit statistics for %{ref} %{start_time} - %{end_time}" +msgstr "" + msgid "CommitBoxTitle|Commit" msgstr "Commit" @@ -824,15 +992,57 @@ msgstr "Commits" msgid "Commits feed" msgstr "Feed dei Commits" +msgid "Commits per day hour (UTC)" +msgstr "" + +msgid "Commits per day of month" +msgstr "" + +msgid "Commits per weekday" +msgstr "" + +msgid "Commits|An error occurred while fetching merge requests data." +msgstr "" + +msgid "Commits|Commit: %{commitText}" +msgstr "" + msgid "Commits|History" msgstr "Cronologia" +msgid "Commits|No related merge requests found" +msgstr "" + msgid "Committed by" msgstr "Committato da " msgid "Compare" msgstr "Confronta" +msgid "Compare Git revisions" +msgstr "" + +msgid "Compare Revisions" +msgstr "" + +msgid "CompareBranches|%{source_branch} and %{target_branch} are the same." +msgstr "" + +msgid "CompareBranches|Compare" +msgstr "" + +msgid "CompareBranches|Source" +msgstr "" + +msgid "CompareBranches|Target" +msgstr "" + +msgid "CompareBranches|There isn't anything to compare." +msgstr "" + +msgid "Confidentiality" +msgstr "" + msgid "Container Registry" msgstr "" @@ -884,6 +1094,9 @@ msgstr "Guida per contribuire" msgid "Contributors" msgstr "Collaboratori" +msgid "ContributorsPage|%{startDate} – %{endDate}" +msgstr "" + msgid "ContributorsPage|Building repository graph." msgstr "Genero grafico della repository." @@ -905,9 +1118,18 @@ msgstr "" msgid "Copy URL to clipboard" msgstr "Copia URL negli appunti" +msgid "Copy branch name to clipboard" +msgstr "" + msgid "Copy commit SHA to clipboard" msgstr "Copia l'SHA del commit negli appunti" +msgid "Copy reference to clipboard" +msgstr "" + +msgid "Create" +msgstr "" + msgid "Create New Directory" msgstr "Crea una nuova cartella" @@ -926,6 +1148,9 @@ msgstr "" msgid "Create file" msgstr "Crea file" +msgid "Create lists from labels. Issues with that label appear in that list." +msgstr "" + msgid "Create merge request" msgstr "Crea una richiesta di merge" @@ -938,6 +1163,9 @@ msgstr "Crea una nuova cartella" msgid "Create new file" msgstr "Crea un nuovo File" +msgid "Create new label" +msgstr "" + msgid "Create new..." msgstr "Crea nuovo..." @@ -959,6 +1187,9 @@ msgstr "Timezone del Cron" msgid "Cron syntax" msgstr "Sintassi Cron" +msgid "Current node" +msgstr "" + msgid "Custom notification events" msgstr "Eventi-Notifica personalizzati" @@ -968,9 +1199,6 @@ msgstr "I livelli di notifica personalizzati sono uguali a quelli di partecipazi msgid "Cycle Analytics" msgstr "Statistiche Cicliche" -msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project." -msgstr "L'Analisi Ciclica fornisce una panoramica sul tempo che trascorre tra l'idea ed il rilascio in produzione del tuo progetto" - msgid "CycleAnalyticsStage|Code" msgstr "Codice" @@ -1027,12 +1255,21 @@ msgstr "" msgid "Details" msgstr "Dettagli" +msgid "Diffs|No file name available" +msgstr "" + msgid "Directory name" msgstr "Nome cartella" +msgid "Disable" +msgstr "" + msgid "Discard changes" msgstr "Annulla modifiche" +msgid "Discover GitLab Geo." +msgstr "" + msgid "Dismiss Cycle Analytics introduction box" msgstr "Chiudi l'introduzione alle Analisi Cicliche" @@ -1069,15 +1306,24 @@ msgstr "Differenze" msgid "DownloadSource|Download" msgstr "Scarica" +msgid "Due date" +msgstr "" + msgid "Edit" msgstr "Modifica" msgid "Edit Pipeline Schedule %{id}" msgstr "Cambia programmazione della pipeline %{id}" +msgid "Edit files in the editor and commit changes here" +msgstr "" + msgid "Emails" msgstr "E-mail" +msgid "Enable" +msgstr "" + msgid "Environments|An error occurred while fetching the environments." msgstr "Errore durante il fetch degli ambienti." @@ -1096,9 +1342,6 @@ msgstr "Ambiente" msgid "Environments|Environments" msgstr "Ambienti" -msgid "Environments|Environments are places where code gets deployed, such as staging or production." -msgstr "Gli ambienti sono gli spazi dove il codice viene rilasciato, come staging o produzione." - msgid "Environments|Job" msgstr "Job" @@ -1141,9 +1384,33 @@ msgstr "" msgid "Error creating epic" msgstr "" +msgid "Error fetching contributors data." +msgstr "" + +msgid "Error fetching labels." +msgstr "" + +msgid "Error fetching network graph." +msgstr "" + +msgid "Error fetching refs" +msgstr "" + +msgid "Error fetching usage ping data." +msgstr "" + msgid "Error occurred when toggling the notification subscription" msgstr "Errore durante l'attivazione/disattivazione della sottoscrizione per l'iscrizione" +msgid "Error saving label update." +msgstr "" + +msgid "Error updating status for all todos." +msgstr "" + +msgid "Error updating todo status." +msgstr "" + msgid "EventFilterBy|Filter by all" msgstr "Filtra per tutti" @@ -1171,6 +1438,9 @@ msgstr "Ogni primo giorno del mese (alle 4 del mattino)" msgid "Every week (Sundays at 4:00am)" msgstr "Ogni settimana (Di domenica alle 4 del mattino)" +msgid "Expand" +msgstr "" + msgid "Explore projects" msgstr "Esplora progetti" @@ -1189,6 +1459,9 @@ msgstr "Feb" msgid "February" msgstr "Febbraio" +msgid "Fields on this page are now uneditable, you can configure" +msgstr "" + msgid "File name" msgstr "Nome file" @@ -1233,10 +1506,10 @@ msgstr "Dalla richiesta di merge fino effettua il merge fino al rilascio in prod msgid "GPG Keys" msgstr "Chiavi GPG" -msgid "Geo Nodes" +msgid "Generate a default set of labels" msgstr "" -msgid "GeoNodeSyncStatus|Failed" +msgid "Geo Nodes" msgstr "" msgid "GeoNodeSyncStatus|Node is failing or broken." @@ -1245,16 +1518,100 @@ msgstr "" msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage." msgstr "" -msgid "GeoNodeSyncStatus|Out of sync" +msgid "GeoNodes|Database replication lag:" +msgstr "" + +msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?" +msgstr "" + +msgid "GeoNodes|Does not match the primary storage configuration" +msgstr "" + +msgid "GeoNodes|Failed" +msgstr "" + +msgid "GeoNodes|Full" +msgstr "" + +msgid "GeoNodes|GitLab version does not match the primary node version" msgstr "" -msgid "GeoNodeSyncStatus|Synced" +msgid "GeoNodes|GitLab version:" +msgstr "" + +msgid "GeoNodes|Health status:" +msgstr "" + +msgid "GeoNodes|Last event ID processed by cursor:" +msgstr "" + +msgid "GeoNodes|Last event ID seen from primary:" +msgstr "" + +msgid "GeoNodes|Loading nodes" +msgstr "" + +msgid "GeoNodes|Local Attachments:" +msgstr "" + +msgid "GeoNodes|Local LFS objects:" +msgstr "" + +msgid "GeoNodes|Local job artifacts:" +msgstr "" + +msgid "GeoNodes|New node" +msgstr "" + +msgid "GeoNodes|Out of sync" +msgstr "" + +msgid "GeoNodes|Replication slot WAL:" +msgstr "" + +msgid "GeoNodes|Replication slots:" +msgstr "" + +msgid "GeoNodes|Repositories:" +msgstr "" + +msgid "GeoNodes|Selective" +msgstr "" + +msgid "GeoNodes|Storage config:" +msgstr "" + +msgid "GeoNodes|Sync settings:" +msgstr "" + +msgid "GeoNodes|Synced" +msgstr "" + +msgid "GeoNodes|Unused slots" +msgstr "" + +msgid "GeoNodes|Used slots" +msgstr "" + +msgid "GeoNodes|Wikis:" +msgstr "" + +msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS." +msgstr "" + +msgid "Geo|All projects" msgstr "" msgid "Geo|File sync capacity" msgstr "" -msgid "Geo|Groups to replicate" +msgid "Geo|Groups to synchronize" +msgstr "" + +msgid "Geo|Projects in certain groups" +msgstr "" + +msgid "Geo|Projects in certain storage shards" msgstr "" msgid "Geo|Repository sync capacity" @@ -1263,12 +1620,24 @@ msgstr "" msgid "Geo|Select groups to replicate." msgstr "" +msgid "Geo|Shards to synchronize" +msgstr "" + +msgid "Git revision" +msgstr "" + msgid "Git storage health information has been reset" msgstr "Le informazioni sullo stato dell'archiviazione Git è stata ripristinata" +msgid "Git version" +msgstr "" + msgid "GitLab Runner section" msgstr "Sezione Gitlab Runner" +msgid "Gitaly Servers" +msgstr "" + msgid "Go to your fork" msgstr "Vai il tuo fork" @@ -1278,6 +1647,9 @@ msgstr "Fork" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "L'autenticazione Google non è %{link_to_documentation}. Richiedi al tuo amministratore Gitlab se desideri utilizzare il servizio." +msgid "Got it!" +msgstr "" + msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" msgstr "Blocca la condivisione di un progetto di %{group} con altri gruppi" @@ -1314,7 +1686,7 @@ msgstr "" msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group." msgstr "" -msgid "GroupsTree|Are you sure you want to leave the \"${this.group.fullName}\" group?" +msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?" msgstr "" msgid "GroupsTree|Create a project in this group." @@ -1365,6 +1737,11 @@ msgstr "" msgid "HealthCheck|Unhealthy" msgstr "" +msgid "Hide value" +msgid_plural "Hide values" +msgstr[0] "" +msgstr[1] "" + msgid "History" msgstr "Cronologia" @@ -1391,6 +1768,12 @@ msgid_plural "Instances" msgstr[0] "" msgstr[1] "" +msgid "Instance does not support multiple Kubernetes clusters" +msgstr "" + +msgid "Interested parties can even contribute by pushing commits if they want to." +msgstr "" + msgid "Internal - The group and any internal projects can be viewed by any logged in user." msgstr "" @@ -1418,6 +1801,9 @@ msgstr "" msgid "Issues" msgstr "" +msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable." +msgstr "" + msgid "Jan" msgstr "Gen" @@ -1436,6 +1822,27 @@ msgstr "Giu" msgid "June" msgstr "Giugno" +msgid "Kubernetes" +msgstr "" + +msgid "Kubernetes Cluster" +msgstr "" + +msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}" +msgstr "" + +msgid "Kubernetes cluster integration was not removed." +msgstr "" + +msgid "Kubernetes cluster integration was successfully removed." +msgstr "" + +msgid "Kubernetes cluster was successfully updated." +msgstr "" + +msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page" +msgstr "" + msgid "LFSStatus|Disabled" msgstr "Disabilitato" @@ -1445,6 +1852,9 @@ msgstr "Abilitato" msgid "Labels" msgstr "" +msgid "Labels can be applied to issues and merge requests to categorize them." +msgstr "" + msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "L'ultimo %d giorno" @@ -1474,6 +1884,9 @@ msgstr "" msgid "LastPushEvent|at" msgstr "" +msgid "Learn more" +msgstr "" + msgid "Learn more in the" msgstr "Leggi di più su" @@ -1492,14 +1905,18 @@ msgstr "Abbandona il progetto" msgid "License" msgstr "" -msgid "Limited to showing %d event at most" -msgid_plural "Limited to showing %d events at most" -msgstr[0] "Limita visualizzazione %d d'evento" -msgstr[1] "Limita visualizzazione %d di eventi" +msgid "Loading the GitLab IDE..." +msgstr "" msgid "Lock" msgstr "" +msgid "Lock %{issuableDisplayName}" +msgstr "" + +msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment." +msgstr "" + msgid "Locked" msgstr "Bloccato" @@ -1509,12 +1926,21 @@ msgstr "" msgid "Login" msgstr "Login" +msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos." +msgstr "" + +msgid "Manage labels" +msgstr "" + msgid "Mar" msgstr "Mar" msgid "March" msgstr "Marzo" +msgid "Mark done" +msgstr "" + msgid "Maximum git storage failures" msgstr "" @@ -1527,6 +1953,9 @@ msgstr "Mediano" msgid "Members" msgstr "Membri" +msgid "Merge Request" +msgstr "" + msgid "Merge Requests" msgstr "Richieste di merge" @@ -1536,9 +1965,30 @@ msgstr "" msgid "Merge request" msgstr "Richiesta di merge" +msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others" +msgstr "" + +msgid "Merged" +msgstr "" + msgid "Messages" msgstr "Messaggi" +msgid "Milestone" +msgstr "" + +msgid "Milestones|Delete milestone" +msgstr "" + +msgid "Milestones|Delete milestone %{milestoneTitle}?" +msgstr "" + +msgid "Milestones|Failed to delete milestone %{milestoneTitle}" +msgstr "" + +msgid "Milestones|Milestone %{milestoneTitle} was not found" +msgstr "" + msgid "MissingSSHKeyWarningLink|add an SSH key" msgstr "aggiungi una chiave SSH" @@ -1548,17 +1998,29 @@ msgstr "Monitoraggio" msgid "More information is available|here" msgstr "Ulteriori informazioni sono disponibili | qui" +msgid "Move" +msgstr "" + +msgid "Move issue" +msgstr "" + msgid "Multiple issue boards" msgstr "" -msgid "New Cluster" -msgstr "Nuovo Cluster" +msgid "Name new label" +msgstr "" msgid "New Issue" msgid_plural "New Issues" msgstr[0] "Nuovo Issue" msgstr[1] "Nuovi Issues" +msgid "New Kubernetes Cluster" +msgstr "" + +msgid "New Kubernetes cluster" +msgstr "" + msgid "New Pipeline Schedule" msgstr "Nuova pianificazione Pipeline" @@ -1583,6 +2045,9 @@ msgstr "Nuovo gruppo" msgid "New issue" msgstr "Nuovo Issue" +msgid "New label" +msgstr "" + msgid "New merge request" msgstr "Nuova richiesta di merge" @@ -1601,7 +2066,22 @@ msgstr "Nuovo sottogruppo" msgid "New tag" msgstr "Nuovo tag" -msgid "No container images stored for this project. Add one by following the instructions above." +msgid "No assignee" +msgstr "" + +msgid "No changes" +msgstr "" + +msgid "No connection could be made to a Gitaly Server, please check your logs!" +msgstr "" + +msgid "No due date" +msgstr "" + +msgid "No estimate or time spent" +msgstr "" + +msgid "No file chosen" msgstr "" msgid "No repository" @@ -1616,9 +2096,15 @@ msgstr "" msgid "None" msgstr "Nessuno" +msgid "Not allowed to merge" +msgstr "" + msgid "Not available" msgstr "Non disponibile" +msgid "Not confidential" +msgstr "" + msgid "Not enough data" msgstr "Dati insufficienti " @@ -1679,6 +2165,12 @@ msgstr "Osserva" msgid "Notifications" msgstr "Notifiche" +msgid "Notifications off" +msgstr "" + +msgid "Notifications on" +msgstr "" + msgid "Nov" msgstr "Nov" @@ -1688,7 +2180,7 @@ msgstr "Novembre" msgid "Number of access attempts" msgstr "Numero di tentativi di accesso raggiunto" -msgid "Number of failures before backing off" +msgid "OK" msgstr "" msgid "Oct" @@ -1703,6 +2195,9 @@ msgstr "Filtra" msgid "Only project members can comment." msgstr "Solo i membri del progetto possono commentare." +msgid "Open" +msgstr "" + msgid "Opened" msgstr "" @@ -1736,9 +2231,6 @@ msgstr "« Prima" msgid "Password" msgstr "Password" -msgid "People without permission will never get a notification and won\\'t be able to comment." -msgstr "Le persone che non hanno il permesso non saranno notificate e non potranno commentare." - msgid "Pipeline" msgstr "Pipeline" @@ -1781,12 +2273,6 @@ msgstr "Tutto" msgid "PipelineSchedules|Inactive" msgstr "Inattiva" -msgid "PipelineSchedules|Input variable key" -msgstr "Chiave della variabile" - -msgid "PipelineSchedules|Input variable value" -msgstr "Valore della variabile" - msgid "PipelineSchedules|Next Run" msgstr "Prossima esecuzione" @@ -1796,9 +2282,6 @@ msgstr "Nessuna" msgid "PipelineSchedules|Provide a short description for this pipeline" msgstr "Fornisci una breve descrizione per questa pipeline" -msgid "PipelineSchedules|Remove variable row" -msgstr "Rimuovi riga della variabile" - msgid "PipelineSchedules|Take ownership" msgstr "Prendi possesso" @@ -1826,6 +2309,12 @@ msgstr "Pipeline per la settimana scorsa" msgid "Pipelines for last year" msgstr "Pipeline per l'ultimo anno" +msgid "Pipelines|Build with confidence" +msgstr "" + +msgid "Pipelines|Get started with Pipelines" +msgstr "" + msgid "Pipeline|all" msgstr "tutto" @@ -1838,12 +2327,21 @@ msgstr "con stadio" msgid "Pipeline|with stages" msgstr "con più stadi" +msgid "Play" +msgstr "" + +msgid "Please <a href=%{link_to_billing} target=\"_blank\" rel=\"noopener noreferrer\">enable billing for one of your projects to be able to create a Kubernetes cluster</a>, then try again." +msgstr "" + msgid "Please solve the reCAPTCHA" msgstr "" msgid "Preferences" msgstr "Preferenze" +msgid "Primary" +msgstr "" + msgid "Private - Project access must be granted explicitly to each user." msgstr "Privato - L'accesso al progetto deve essere fornito esplicitamente ad ogni utente." @@ -1889,6 +2387,9 @@ msgstr "Il tuo account è attualmente proprietario in questi gruppi:" msgid "Profiles|your account" msgstr "il tuo account" +msgid "Programming languages used in this repository" +msgstr "" + msgid "Project '%{project_name}' is in the process of being deleted." msgstr "Il progetto '%{project_name}' è in fase di eliminazione." @@ -1904,6 +2405,15 @@ msgstr "Il Progetto '%{project_name}' è stato aggiornato con successo." msgid "Project access must be granted explicitly to each user." msgstr "L'accesso al progetto dev'esser fornito esplicitamente ad ogni utente" +msgid "Project avatar" +msgstr "" + +msgid "Project avatar in repository: %{link}" +msgstr "" + +msgid "Project cache successfully reset." +msgstr "" + msgid "Project details" msgstr "Dettagli del progetto" @@ -1922,6 +2432,21 @@ msgstr "Esportazione del progetto iniziata. Un link di download sarà inviato vi msgid "ProjectActivityRSS|Subscribe" msgstr "Iscriviti" +msgid "ProjectCreationLevel|Allowed to create projects" +msgstr "" + +msgid "ProjectCreationLevel|Default project creation protection" +msgstr "" + +msgid "ProjectCreationLevel|Developers + Masters" +msgstr "" + +msgid "ProjectCreationLevel|Masters" +msgstr "" + +msgid "ProjectCreationLevel|No one" +msgstr "" + msgid "ProjectFeature|Disabled" msgstr "Disabilitato" @@ -1946,15 +2471,9 @@ msgstr "Grafico" msgid "ProjectSettings|Contact an admin to change this setting." msgstr "" -msgid "ProjectSettings|Immediately run a pipeline on the default branch" -msgstr "Esegui subito una pipeline sulla branch di default" - msgid "ProjectSettings|Only signed commits can be pushed to this repository." msgstr "" -msgid "ProjectSettings|Problem setting up the CI/CD settings JavaScript" -msgstr "Problemi durante l'impostazione delle CI/CD JavaScript settings" - msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin." msgstr "" @@ -2018,12 +2537,15 @@ msgstr "Nessuna metrica è stata monitorata. Per iniziare a monitorare, rilascia msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/" msgstr "" -msgid "PrometheusService|Prometheus monitoring" +msgid "PrometheusService|Time-series monitoring service" msgstr "" msgid "PrometheusService|View environments" msgstr "" +msgid "Protip:" +msgstr "" + msgid "Public - The group and any public projects can be viewed without any authentication." msgstr "Pubblico - il gruppo e tutti i progetti pubblici possono essere visualizzati senza alcuna autenticazione." @@ -2039,6 +2561,9 @@ msgstr "" msgid "PushRule|Committer restriction" msgstr "" +msgid "Quick actions can be used in the issues description and comment boxes." +msgstr "" + msgid "Read more" msgstr "Vedi altro" @@ -2051,6 +2576,12 @@ msgstr "Branches" msgid "RefSwitcher|Tags" msgstr "Tags" +msgid "Reference:" +msgstr "" + +msgid "Register / Sign In" +msgstr "" + msgid "Registry" msgstr "" @@ -2075,9 +2606,18 @@ msgstr "Richieste di Merge Completate Correlate" msgid "Remind later" msgstr "Ricordamelo più tardi" +msgid "Remove" +msgstr "" + +msgid "Remove avatar" +msgstr "" + msgid "Remove project" msgstr "Rimuovi progetto" +msgid "Repair authentication" +msgstr "" + msgid "Repository" msgstr "" @@ -2093,6 +2633,11 @@ msgstr "" msgid "Reset runners registration token" msgstr "" +msgid "Reveal value" +msgid_plural "Reveal values" +msgstr[0] "" +msgstr[1] "" + msgid "Revert this commit" msgstr "Ripristina questo commit" @@ -2102,15 +2647,15 @@ msgstr "Ripristina questa richiesta di merge" msgid "SSH Keys" msgstr "Chiavi SSH" -msgid "Save" -msgstr "Salva" - msgid "Save changes" msgstr "Salva modifiche" msgid "Save pipeline schedule" msgstr "Salva pianificazione pipeline" +msgid "Save variables" +msgstr "" + msgid "Schedule a new pipeline" msgstr "Pianifica una nuova Pipeline" @@ -2126,38 +2671,59 @@ msgstr "" msgid "Search branches and tags" msgstr "Ricerca branches e tags" -msgid "Seconds before reseting failure information" +msgid "Search milestones" msgstr "" -msgid "Seconds to wait after a storage failure" +msgid "Search project" +msgstr "" + +msgid "Search users" +msgstr "" + +msgid "Seconds before reseting failure information" msgstr "" msgid "Seconds to wait for a storage access attempt" msgstr "" +msgid "Secret variables" +msgstr "" + msgid "Select Archive Format" msgstr "Seleziona formato d'archivio" msgid "Select a timezone" msgstr "Seleziona una timezone" +msgid "Select assignee" +msgstr "" + +msgid "Select branch/tag" +msgstr "" + msgid "Select target branch" msgstr "Seleziona una branch di destinazione" +msgid "Selective synchronization" +msgstr "" + msgid "Sep" msgstr "Set" msgid "September" msgstr "Settembre" +msgid "Server version" +msgstr "" + msgid "Service Templates" msgstr "" msgid "Set a password on your account to pull or push via %{protocol}." msgstr "Establezca una contraseña en su cuenta para actualizar o enviar a través de %{protocol}." -msgid "Set up CI" -msgstr "Configura CI" +msgid "Set up CI/CD" +msgstr "" msgid "Set up Koding" msgstr "Configura Koding" @@ -2171,6 +2737,15 @@ msgstr "imposta una password" msgid "Settings" msgstr "Impostazioni" +msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero." +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset pipeline minutes" +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes" +msgstr "" + msgid "Show parent pages" msgstr "" @@ -2185,9 +2760,6 @@ msgstr[1] "Visualizza %d eventi" msgid "Sidebar|Change weight" msgstr "" -msgid "Sidebar|Edit" -msgstr "" - msgid "Sidebar|No" msgstr "" @@ -2200,18 +2772,30 @@ msgstr "" msgid "Snippets" msgstr "Snippet" +msgid "Something went wrong on our end" +msgstr "" + msgid "Something went wrong on our end." msgstr "Si è verificato un problema con il nostro server." +msgid "Something went wrong trying to change the confidentiality of this issue" +msgstr "" + msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}" msgstr "" +msgid "Something went wrong when toggling the button" +msgstr "" + msgid "Something went wrong while fetching the projects." msgstr "Qualcosa è andato storto durante il fetch dei progetti." msgid "Something went wrong while fetching the registry list." msgstr "Qualcosa è andato storto durante il recupero dell'elenco dei registri." +msgid "Something went wrong. Please try again." +msgstr "" + msgid "Sort by" msgstr "Ordina per" @@ -2341,10 +2925,10 @@ msgstr "" msgid "Stopped" msgstr "" -msgid "Subgroups" +msgid "Storage" msgstr "" -msgid "Subscribe" +msgid "Subgroups" msgstr "" msgid "Switch branch/tag" @@ -2442,7 +3026,10 @@ msgstr "" msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project." msgstr "" -msgid "The circuitbreaker backoff threshold should be lower than the failure count threshold" +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project" +msgstr "" + +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project." msgstr "" msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request." @@ -2457,10 +3044,10 @@ msgstr "La relazione del fork è stata rimossa" msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage." msgstr "Lo stadio di Issue mostra il tempo che impiega un issue ad esser correlato ad una Milestone, o ad esser aggiunto ad una tua Lavagna. Inizia la creazione di problemi per visualizzare i dati in questo stadio." -msgid "The number of attempts GitLab will make to access a storage." +msgid "The maximum file size allowed is 200KB." msgstr "" -msgid "The number of failures after which GitLab will start temporarily disabling access to a storage shard on a host" +msgid "The number of attempts GitLab will make to access a storage." msgstr "" msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}." @@ -2469,9 +3056,6 @@ msgstr "" msgid "The phase of the development lifecycle." msgstr "Il ciclo vitale della fase di sviluppo." -msgid "The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user." -msgstr "Le pipelines pianificate vengono eseguite nel futuro, ripetitivamente, per specifici tag o branch ed ereditano restrizioni di progetto basate sull'utente ad esse associato." - msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit." msgstr "Lo stadio di pianificazione mostra il tempo trascorso dal primo commit al suo step precedente. Questo periodo sarà disponibile automaticamente nel momento in cui farai il primo commit." @@ -2502,19 +3086,46 @@ msgstr "" msgid "The time in seconds GitLab will try to access storage. After this time a timeout error will be raised." msgstr "" +msgid "The time in seconds between storage checks. When a previous check did complete yet, GitLab will skip a check." +msgstr "" + msgid "The time taken by each data entry gathered by that stage." msgstr "Il tempo aggregato relativo eventi/data entry raccolto in quello stadio." msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6." msgstr "Il valore falsato nel mezzo di una serie di dati osservati. ES: tra 3,5,9 il mediano è 5. Tra 3,5,7,8 il mediano è (5+7)/2 quindi 6." +msgid "There are no issues to show" +msgstr "" + +msgid "There are no merge requests to show" +msgstr "" + msgid "There are problems accessing Git storage: " msgstr "" +msgid "There was an error loading users activity calendar." +msgstr "" + +msgid "There was an error saving your notification settings." +msgstr "" + +msgid "There was an error subscribing to this label." +msgstr "" + +msgid "There was an error when reseting email token." +msgstr "" + +msgid "There was an error when subscribing to this label." +msgstr "" + +msgid "There was an error when unsubscribing from this label." +msgstr "" + msgid "This board\\'s scope is reduced" msgstr "" -msgid "This branch has changed since you started editing. Would you like to create a new branch?" +msgid "This directory" msgstr "" msgid "This is a confidential issue." @@ -2523,18 +3134,45 @@ msgstr "" msgid "This is the author's first Merge Request to this project." msgstr "" +msgid "This issue is confidential" +msgstr "" + msgid "This issue is confidential and locked." msgstr "" msgid "This issue is locked." msgstr "" +msgid "This job depends on a user to trigger its process. Often they are used to deploy code to production environments" +msgstr "" + +msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered" +msgstr "" + +msgid "This job has not been triggered yet" +msgstr "" + +msgid "This job has not started yet" +msgstr "" + +msgid "This job is in pending state and is waiting to be picked by a runner" +msgstr "" + +msgid "This job requires a manual action" +msgstr "" + msgid "This means you can not push code until you create an empty repository or import existing one." msgstr "Questo significa che non è possibile effettuare push di codice fino a che non crei una repository vuota o ne importi una esistente" msgid "This merge request is locked." msgstr "" +msgid "This project" +msgstr "" + +msgid "This repository" +msgstr "" + msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here." msgstr "" @@ -2547,9 +3185,21 @@ msgstr "Il tempo che impiega un issue per esser implementato" msgid "Time between merge request creation and merge/close" msgstr "Il tempo tra la creazione di una richiesta di merge ed il merge/close" +msgid "Time tracking" +msgstr "" + msgid "Time until first merge request" msgstr "Il tempo fino alla prima richiesta di merge" +msgid "TimeTrackingEstimated|Est" +msgstr "" + +msgid "TimeTracking|Estimated:" +msgstr "" + +msgid "TimeTracking|Spent" +msgstr "" + msgid "Timeago|%s days ago" msgstr "%s giorni fa" @@ -2689,6 +3339,18 @@ msgstr "s" msgid "Title" msgstr "" +msgid "Todo" +msgstr "" + +msgid "Toggle sidebar" +msgstr "" + +msgid "ToggleButton|Toggle Status: OFF" +msgstr "" + +msgid "ToggleButton|Toggle Status: ON" +msgstr "" + msgid "Total Time" msgstr "Tempo Totale" @@ -2704,19 +3366,40 @@ msgstr "" msgid "Track groups of issues that share a theme, across projects and milestones" msgstr "" +msgid "Total: %{total}" +msgstr "" + +msgid "Track time with quick actions" +msgstr "" + +msgid "Trigger this manual action" +msgstr "" + msgid "Turn on Service Desk" msgstr "" +msgid "Type %{value} to confirm:" +msgstr "" + +msgid "Unable to reset project cache." +msgstr "" + +msgid "Unknown" +msgstr "" + msgid "Unlock" msgstr "" +msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment." +msgstr "" + msgid "Unlocked" msgstr "" msgid "Unstar" msgstr "" -msgid "Unsubscribe" +msgid "Up to date" msgstr "" msgid "Upgrade your plan to activate Advanced Global Search." @@ -2740,6 +3423,9 @@ msgstr "Carica un nuovo file" msgid "Upload file" msgstr "Carica file" +msgid "Upload new avatar" +msgstr "" + msgid "UploadLink|click to upload" msgstr "clicca per caricare" @@ -2752,9 +3438,15 @@ msgstr "" msgid "Use your global notification setting" msgstr "Usa le tue impostazioni globali " +msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." +msgstr "" + msgid "View file @ " msgstr "" +msgid "View labels" +msgstr "" + msgid "View open merge request" msgstr "Mostra la richieste di merge aperte" @@ -2776,6 +3468,9 @@ msgstr "Sconosciuto" msgid "Want to see the data? Please ask an administrator for access." msgstr "Vuoi visualizzare i dati? Richiedi l'accesso ad un amministratore, grazie." +msgid "We could not verify that one of your projects on GCP has billing enabled. Please try again." +msgstr "" + msgid "We don't have enough data to show this stage." msgstr "Non ci sono sufficienti dati da mostrare su questo stadio" @@ -2788,9 +3483,6 @@ msgstr "" msgid "Weight" msgstr "" -msgid "When access to a storage fails. GitLab will prevent access to the storage for the time specified here. This allows the filesystem to recover. Repositories on failing shards are temporarly unavailable" -msgstr "" - msgid "Wiki" msgstr "" @@ -2809,6 +3501,12 @@ msgstr "" msgid "WikiClone|Start Gollum and edit locally" msgstr "" +msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title." +msgstr "" + +msgid "WikiEdit|There is already a page with the same title in that path." +msgstr "" + msgid "WikiEmptyPageError|You are not allowed to create wiki pages" msgstr "" @@ -2911,9 +3609,21 @@ msgstr "Stai per rimuovere la relazione con il progetto sorgente %{forked_from_p msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" msgstr "Stai per trasferire %{project_name_with_namespace} ad un altro owner. Sei ASSOLUTAMENTE sicuro?" +msgid "You can also star a label to make it a priority label." +msgstr "" + +msgid "You can move around the graph by using the arrow keys." +msgstr "" + +msgid "You can move around the graph by using the arrow keys." +msgstr "" + msgid "You can only add files when you are on a branch" msgstr "Puoi aggiungere files solo quando sei in una branch" +msgid "You can only edit files when you are on a branch" +msgstr "" + msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead." msgstr "" @@ -2953,6 +3663,12 @@ msgstr "Non sarai in grado di effettuare push o pull tramite SSH fino a che %{ad msgid "You won't be able to pull or push project code via SSH until you add an SSH key to your profile" msgstr "" +msgid "You'll need to use different branch names to get a valid comparison." +msgstr "" + +msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure" +msgstr "" + msgid "Your comment will not be visible to the public." msgstr "" @@ -2965,26 +3681,220 @@ msgstr "Il tuo nome" msgid "Your projects" msgstr "" +msgid "assign yourself" +msgstr "" + msgid "branch name" msgstr "" msgid "by" msgstr "" +msgid "ciReport|Code quality" +msgstr "" + +msgid "ciReport|DAST detected no alerts by analyzing the review app" +msgstr "" + +msgid "ciReport|Failed to load ${type} report" +msgstr "" + +msgid "ciReport|Fixed:" +msgstr "" + +msgid "ciReport|Instances" +msgstr "" + +msgid "ciReport|Learn more about whitelisting" +msgstr "" + +msgid "ciReport|Loading ${type} report" +msgstr "" + +msgid "ciReport|No changes to code quality" +msgstr "" + +msgid "ciReport|No changes to performance metrics" +msgstr "" + +msgid "ciReport|Performance metrics" +msgstr "" + +msgid "ciReport|SAST" +msgstr "" + +msgid "ciReport|SAST detected no security vulnerabilities" +msgstr "" + +msgid "ciReport|SAST:container no vulnerabilities were found" +msgstr "" + +msgid "ciReport|Show complete code vulnerabilities report" +msgstr "" + +msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}" +msgstr "" + msgid "commit" msgstr "" +msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue." +msgstr "" + +msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue." +msgstr "" + msgid "day" msgid_plural "days" msgstr[0] "giorno" msgstr[1] "giorni" +msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command." +msgstr "" + +msgid "merge request" +msgid_plural "merge requests" +msgstr[0] "" +msgstr[1] "" + +msgid "mrWidget|Cancel automatic merge" +msgstr "" + +msgid "mrWidget|Check out branch" +msgstr "" + +msgid "mrWidget|Checking ability to merge automatically" +msgstr "" + +msgid "mrWidget|Cherry-pick" +msgstr "" + +msgid "mrWidget|Cherry-pick this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Closed" +msgstr "" + +msgid "mrWidget|Closed by" +msgstr "" + +msgid "mrWidget|Closes" +msgstr "" + +msgid "mrWidget|Did not close" +msgstr "" + +msgid "mrWidget|Email patches" +msgstr "" + +msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|Mentions" +msgstr "" + +msgid "mrWidget|Merge" +msgstr "" + +msgid "mrWidget|Merge failed." +msgstr "" + +msgid "mrWidget|Merge locally" +msgstr "" + +msgid "mrWidget|Merged by" +msgstr "" + +msgid "mrWidget|Plain diff" +msgstr "" + +msgid "mrWidget|Refresh" +msgstr "" + +msgid "mrWidget|Refresh now" +msgstr "" + +msgid "mrWidget|Refreshing now" +msgstr "" + +msgid "mrWidget|Remove Source Branch" +msgstr "" + +msgid "mrWidget|Remove source branch" +msgstr "" + +msgid "mrWidget|Request to merge" +msgstr "" + +msgid "mrWidget|Resolve conflicts" +msgstr "" + +msgid "mrWidget|Revert" +msgstr "" + +msgid "mrWidget|Revert this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Set by" +msgstr "" + +msgid "mrWidget|The changes were merged into" +msgstr "" + +msgid "mrWidget|The changes were not merged into" +msgstr "" + +msgid "mrWidget|The changes will be merged into" +msgstr "" + +msgid "mrWidget|The source branch has been removed" +msgstr "" + +msgid "mrWidget|The source branch is being removed" +msgstr "" + +msgid "mrWidget|The source branch will be removed" +msgstr "" + +msgid "mrWidget|The source branch will not be removed" +msgstr "" + +msgid "mrWidget|There are merge conflicts" +msgstr "" + +msgid "mrWidget|This merge request failed to be merged automatically" +msgstr "" + +msgid "mrWidget|This merge request is in the process of being merged" +msgstr "" + +msgid "mrWidget|This project is archived, write access has been disabled" +msgstr "" + +msgid "mrWidget|You can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|You can remove source branch now" +msgstr "" + +msgid "mrWidget|command line" +msgstr "" + +msgid "mrWidget|into" +msgstr "" + +msgid "mrWidget|to be merged automatically when the pipeline succeeds" +msgstr "" + msgid "new merge request" msgstr "Nuova richiesta di merge" msgid "notification emails" msgstr "Notifiche via email" +msgid "or" +msgstr "" + msgid "parent" msgid_plural "parents" msgstr[0] "" @@ -2996,12 +3906,21 @@ msgstr "" msgid "personal access token" msgstr "" +msgid "remove due date" +msgstr "" + msgid "source" msgstr "" +msgid "spendCommand|%{slash_command} will update the sum of the time spent." +msgstr "" + msgid "to help your contributors communicate effectively!" msgstr "" msgid "username" msgstr "" +msgid "uses Kubernetes clusters to deploy your code!" +msgstr "" + diff --git a/locale/ja/gitlab.po b/locale/ja/gitlab.po index 1314bad87fe..31c4422c928 100644 --- a/locale/ja/gitlab.po +++ b/locale/ja/gitlab.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab-ee\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-12-12 18:31+0000\n" -"PO-Revision-Date: 2018-01-05 04:40-0500\n" +"POT-Creation-Date: 2018-02-07 11:38-0600\n" +"PO-Revision-Date: 2018-02-12 04:00-0500\n" "Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n" "Language-Team: Japanese\n" "Language: ja_JP\n" @@ -16,20 +16,35 @@ msgstr "" "X-Crowdin-Language: ja\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" +msgid " and" +msgstr "" + msgid "%d commit" msgid_plural "%d commits" msgstr[0] "%d個ã®ã‚³ãƒŸãƒƒãƒˆ" +msgid "%d commit behind" +msgid_plural "%d commits behind" +msgstr[0] "" + +msgid "%d issue" +msgid_plural "%d issues" +msgstr[0] "" + msgid "%d layer" msgid_plural "%d layers" msgstr[0] "" +msgid "%d merge request" +msgid_plural "%d merge requests" +msgstr[0] "" + msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "%s additional commits have been omitted to prevent performance issues." msgstr[0] "パフォーマンス低下をé¿ã‘ã‚‹ãŸã‚ %s 個ã®ã‚³ãƒŸãƒƒãƒˆã‚’çœç•¥ã—ã¾ã—ãŸã€‚" -msgid "%{commit_author_link} committed %{commit_timeago}" -msgstr "%{commit_timeago}ã«%{commit_author_link}ãŒã‚³ãƒŸãƒƒãƒˆã—ã¾ã—ãŸã€‚" +msgid "%{commit_author_link} authored %{commit_timeago}" +msgstr "" msgid "%{count} participant" msgid_plural "%{count} participants" @@ -41,9 +56,6 @@ msgstr "" msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will allow access on the next attempt." msgstr "" -msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will block access for %{number_of_seconds} seconds." -msgstr "" - msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved." msgstr "" @@ -115,24 +127,81 @@ msgstr "ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚’è¿½åŠ " msgid "Add new directory" msgstr "æ–°è¦ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã‚’è¿½åŠ " +msgid "Add todo" +msgstr "" + +msgid "AdminArea|Stop all jobs" +msgstr "" + +msgid "AdminArea|Stop all jobs?" +msgstr "" + +msgid "AdminArea|Stop jobs" +msgstr "" + +msgid "AdminArea|Stopping jobs failed" +msgstr "" + +msgid "AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running." +msgstr "" + msgid "AdminHealthPageLink|health page" msgstr "" +msgid "Advanced" +msgstr "" + msgid "Advanced settings" msgstr "" msgid "All" msgstr "" +msgid "All changes are committed" +msgstr "" + +msgid "Allows you to add and manage Kubernetes clusters." +msgstr "" + +msgid "An error occurred previewing the blob" +msgstr "" + msgid "An error occurred when toggling the notification subscription" msgstr "" msgid "An error occurred when updating the issue weight" msgstr "" +msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." +msgstr "" + +msgid "An error occurred while fetching markdown preview" +msgstr "" + msgid "An error occurred while fetching sidebar data" msgstr "" +msgid "An error occurred while getting projects" +msgstr "" + +msgid "An error occurred while loading filenames" +msgstr "" + +msgid "An error occurred while rendering KaTeX" +msgstr "" + +msgid "An error occurred while rendering preview broadcast message" +msgstr "" + +msgid "An error occurred while retrieving calendar activity" +msgstr "" + +msgid "An error occurred while retrieving diff" +msgstr "" + +msgid "An error occurred while validating username" +msgstr "" + msgid "An error occurred. Please try again." msgstr "" @@ -157,9 +226,6 @@ msgstr "ã“ã®ãƒ‘イプラインスケジュールを削除ã—ã¾ã™ã‹ï¼Ÿ" msgid "Are you sure you want to discard your changes?" msgstr "" -msgid "Are you sure you want to leave this group?" -msgstr "" - msgid "Are you sure you want to reset registration token?" msgstr "" @@ -172,6 +238,21 @@ msgstr "" msgid "Artifacts" msgstr "" +msgid "Assign custom color like #FF0000" +msgstr "" + +msgid "Assign labels" +msgstr "" + +msgid "Assign milestone" +msgstr "" + +msgid "Assign to" +msgstr "" + +msgid "Assignee" +msgstr "" + msgid "Attach a file by drag & drop or %{upload_link}" msgstr "ドラッグ&ドãƒãƒƒãƒ—ã¾ãŸã¯ %{upload_link} ã§ãƒ•ã‚¡ã‚¤ãƒ«ã‚’添付" @@ -187,13 +268,16 @@ msgstr "" msgid "Author" msgstr "" -msgid "Auto Review Apps and Auto Deploy need a domain name and the %{kubernetes} to work correctly." +msgid "Authors: %{authors}" msgstr "" -msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly." +msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." msgstr "" -msgid "Auto Review Apps and Auto Deploy need the %{kubernetes} to work correctly." +msgid "Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly." +msgstr "" + +msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly." msgstr "" msgid "AutoDevOps|Auto DevOps (Beta)" @@ -217,6 +301,12 @@ msgstr "" msgid "Available" msgstr "" +msgid "Avatar will be removed. Are you sure?" +msgstr "" + +msgid "Average per day: %{average}" +msgstr "" + msgid "Billing" msgstr "" @@ -271,6 +361,9 @@ msgstr "" msgid "BillingPlans|per user" msgstr "" +msgid "Begin with the selected commit" +msgstr "" + msgid "Branch" msgid_plural "Branches" msgstr[0] "ブランãƒ" @@ -398,8 +491,8 @@ msgstr "作者" msgid "CI / CD" msgstr "" -msgid "CI configuration" -msgstr "CI è¨å®š" +msgid "CI/CD configuration" +msgstr "" msgid "CICD|Jobs" msgstr "" @@ -410,6 +503,9 @@ msgstr "ã‚ャンセル" msgid "Cancel edit" msgstr "" +msgid "Cannot modify managed Kubernetes cluster" +msgstr "" + msgid "Change Weight" msgstr "" @@ -425,15 +521,24 @@ msgstr "ãƒã‚§ãƒªãƒ¼ãƒ”ック" msgid "ChangeTypeAction|Revert" msgstr "リãƒãƒ¼ãƒˆ" +msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes." +msgstr "" + msgid "Changelog" msgstr "変更履æ´" +msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision." +msgstr "" + msgid "Charts" msgstr "ãƒãƒ£ãƒ¼ãƒˆ" msgid "Chat" msgstr "" +msgid "Check interval" +msgstr "" + msgid "Checking %{text} availability…" msgstr "" @@ -446,7 +551,19 @@ msgstr "ã“ã®ã‚³ãƒŸãƒƒãƒˆã‚’ãƒã‚§ãƒªãƒ¼ãƒ”ック" msgid "Cherry-pick this merge request" msgstr "ã“ã®ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆã‚’ãƒã‚§ãƒªãƒ¼ãƒ”ック" -msgid "Choose which groups you wish to replicate to this secondary node. Leave blank to replicate all." +msgid "Choose File ..." +msgstr "" + +msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request." +msgstr "" + +msgid "Choose file..." +msgstr "" + +msgid "Choose which groups you wish to synchronize to this secondary node." +msgstr "" + +msgid "Choose which shards you wish to synchronize to this secondary node." msgstr "" msgid "CiStatusLabel|canceled" @@ -503,79 +620,91 @@ msgstr "スã‚ップ済ã¿" msgid "CiStatus|running" msgstr "実行ä¸" -msgid "CircuitBreakerApiLink|circuitbreaker api" +msgid "CiVariables|Input variable key" msgstr "" -msgid "Clone repository" +msgid "CiVariables|Input variable value" msgstr "" -msgid "Close" +msgid "CiVariables|Remove variable row" msgstr "" -msgid "Cluster" +msgid "CiVariable|* (All environments)" msgstr "" -msgid "ClusterIntegration|%{appList} was successfully installed on your cluster" +msgid "CiVariable|All environments" msgstr "" -msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which incur additional costs. See %{pricingLink}" +msgid "CiVariable|Create wildcard" msgstr "" -msgid "ClusterIntegration|API URL" +msgid "CiVariable|Error occured while saving variables" msgstr "" -msgid "ClusterIntegration|Active" +msgid "CiVariable|New environment" msgstr "" -msgid "ClusterIntegration|Add an existing cluster" +msgid "CiVariable|Protected" msgstr "" -msgid "ClusterIntegration|Add cluster" +msgid "CiVariable|Search environments" msgstr "" -msgid "ClusterIntegration|All" +msgid "CiVariable|Toggle protected" msgstr "" -msgid "ClusterIntegration|Applications" +msgid "CiVariable|Validation failed" msgstr "" -msgid "ClusterIntegration|CA Certificate" +msgid "CircuitBreakerApiLink|circuitbreaker api" msgstr "" -msgid "ClusterIntegration|Certificate Authority bundle (PEM format)" +msgid "Click to expand text" +msgstr "" + +msgid "Clone repository" +msgstr "" + +msgid "Close" msgstr "" -msgid "ClusterIntegration|Choose how to set up cluster integration" +msgid "Closed" msgstr "" -msgid "ClusterIntegration|Cluster" +msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Cluster details" +msgid "ClusterIntegration|API URL" msgstr "" -msgid "ClusterIntegration|Cluster integration" +msgid "ClusterIntegration|Add Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Cluster integration is disabled for this project." +msgid "ClusterIntegration|Add an existing Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Cluster integration is enabled for this project." +msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration" msgstr "" -msgid "ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab's connection to it." +msgid "ClusterIntegration|Applications" msgstr "" -msgid "ClusterIntegration|Cluster is being created on Google Kubernetes Engine..." +msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster." msgstr "" -msgid "ClusterIntegration|Cluster name" +msgid "ClusterIntegration|CA Certificate" msgstr "" -msgid "ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine. Refresh the page to see cluster's details" +msgid "ClusterIntegration|Certificate Authority bundle (PEM format)" msgstr "" -msgid "ClusterIntegration|Clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" +msgid "ClusterIntegration|Choose how to set up Kubernetes cluster integration" +msgstr "" + +msgid "ClusterIntegration|Choose which of your project's environments will use this Kubernetes cluster." +msgstr "" + +msgid "ClusterIntegration|Control how your Kubernetes cluster integrates with GitLab" msgstr "" msgid "ClusterIntegration|Copy API URL" @@ -584,37 +713,34 @@ msgstr "" msgid "ClusterIntegration|Copy CA Certificate" msgstr "" -msgid "ClusterIntegration|Copy Token" +msgid "ClusterIntegration|Copy Kubernetes cluster name" msgstr "" -msgid "ClusterIntegration|Copy cluster name" +msgid "ClusterIntegration|Copy Token" msgstr "" -msgid "ClusterIntegration|Create a new cluster on Google Engine right from GitLab" +msgid "ClusterIntegration|Create Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Create cluster" +msgid "ClusterIntegration|Create Kubernetes cluster on Google Kubernetes Engine" msgstr "" -msgid "ClusterIntegration|Create cluster on Google Kubernetes Engine" +msgid "ClusterIntegration|Create a new Kubernetes cluster on Google Kubernetes Engine right from GitLab" msgstr "" msgid "ClusterIntegration|Create on GKE" msgstr "" -msgid "ClusterIntegration|Enable cluster integration" -msgstr "" - msgid "ClusterIntegration|Enter the details for an existing Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Enter the details for your cluster" +msgid "ClusterIntegration|Enter the details for your Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Environment pattern" +msgid "ClusterIntegration|Environment scope" msgstr "" -msgid "ClusterIntegration|GKE pricing" +msgid "ClusterIntegration|GitLab Integration" msgstr "" msgid "ClusterIntegration|GitLab Runner" @@ -632,64 +758,94 @@ msgstr "" msgid "ClusterIntegration|Helm Tiller" msgstr "" -msgid "ClusterIntegration|Inactive" -msgstr "" - msgid "ClusterIntegration|Ingress" msgstr "" msgid "ClusterIntegration|Install" msgstr "" -msgid "ClusterIntegration|Install applications on your cluster. Read more about %{helpLink}" -msgstr "" - msgid "ClusterIntegration|Installed" msgstr "" msgid "ClusterIntegration|Installing" msgstr "" -msgid "ClusterIntegration|Integrate cluster automation" +msgid "ClusterIntegration|Integrate Kubernetes cluster automation" +msgstr "" + +msgid "ClusterIntegration|Integration status" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster details" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is disabled for this project." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project. Disabling this integration will not affect your Kubernetes cluster, it will only temporarily turn off GitLab's connection to it." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster name" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project" msgstr "" msgid "ClusterIntegration|Learn more about %{link_to_documentation}" msgstr "" -msgid "ClusterIntegration|Learn more about Clusters" +msgid "ClusterIntegration|Learn more about Kubernetes" msgstr "" -msgid "ClusterIntegration|Machine type" +msgid "ClusterIntegration|Learn more about environments" msgstr "" -msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create clusters" +msgid "ClusterIntegration|Machine type" msgstr "" -msgid "ClusterIntegration|Manage cluster integration on your GitLab project" +msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters" msgstr "" -msgid "ClusterIntegration|Manage your cluster by visiting %{link_gke}" +msgid "ClusterIntegration|Manage" msgstr "" -msgid "ClusterIntegration|Multiple clusters are available in GitLab Entreprise Edition Premium and Ultimate" +msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}" msgstr "" -msgid "ClusterIntegration|Note:" +msgid "ClusterIntegration|More information" msgstr "" -msgid "ClusterIntegration|Number of nodes" +msgid "ClusterIntegration|Multiple Kubernetes clusters are available in GitLab Enterprise Edition Premium and Ultimate" msgstr "" -msgid "ClusterIntegration|Please enter access information for your cluster. If you need help, you can read our %{link_to_help_page} on clusters" +msgid "ClusterIntegration|Note:" msgstr "" -msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:" +msgid "ClusterIntegration|Number of nodes" msgstr "" -msgid "ClusterIntegration|Problem setting up the cluster" +msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes" msgstr "" -msgid "ClusterIntegration|Problem setting up the clusters list" +msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:" msgstr "" msgid "ClusterIntegration|Project ID" @@ -701,16 +857,19 @@ msgstr "" msgid "ClusterIntegration|Project namespace (optional, unique)" msgstr "" -msgid "ClusterIntegration|Read our %{link_to_help_page} on cluster integration." +msgid "ClusterIntegration|Prometheus" msgstr "" -msgid "ClusterIntegration|Remove cluster integration" +msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration." +msgstr "" + +msgid "ClusterIntegration|Remove Kubernetes cluster integration" msgstr "" msgid "ClusterIntegration|Remove integration" msgstr "" -msgid "ClusterIntegration|Removing cluster integration will remove the cluster configuration you have added to this project. It will not delete your cluster on Google Kubernetes Engine." +msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster." msgstr "" msgid "ClusterIntegration|Request to begin installing failed" @@ -719,7 +878,7 @@ msgstr "" msgid "ClusterIntegration|Save changes" msgstr "" -msgid "ClusterIntegration|See and edit the details for your cluster" +msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster" msgstr "" msgid "ClusterIntegration|See machine types" @@ -740,25 +899,25 @@ msgstr "" msgid "ClusterIntegration|Something went wrong on our end." msgstr "" -msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine" +msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine" msgstr "" msgid "ClusterIntegration|Something went wrong while installing %{title}" msgstr "" -msgid "ClusterIntegration|There are no clusters to show" +msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below" msgstr "" -msgid "ClusterIntegration|This account must have permissions to create a cluster in the %{link_to_container_project} specified below" +msgid "ClusterIntegration|Toggle Kubernetes Cluster" msgstr "" -msgid "ClusterIntegration|Toggle Cluster" +msgid "ClusterIntegration|Toggle Kubernetes cluster" msgstr "" msgid "ClusterIntegration|Token" msgstr "" -msgid "ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." +msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." msgstr "" msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}" @@ -770,7 +929,7 @@ msgstr "" msgid "ClusterIntegration|access to Google Kubernetes Engine" msgstr "" -msgid "ClusterIntegration|cluster" +msgid "ClusterIntegration|check the pricing here" msgstr "" msgid "ClusterIntegration|documentation" @@ -788,6 +947,9 @@ msgstr "" msgid "ClusterIntegration|properly configured" msgstr "" +msgid "Collapse" +msgstr "" + msgid "Comments" msgstr "" @@ -804,6 +966,9 @@ msgstr "ç›´è¿‘30コミットã®æ‰€è¦æ™‚é–“(分)" msgid "Commit message" msgstr "コミットメッセージ" +msgid "Commit statistics for %{ref} %{start_time} - %{end_time}" +msgstr "" + msgid "CommitBoxTitle|Commit" msgstr "コミット" @@ -816,15 +981,57 @@ msgstr "コミット" msgid "Commits feed" msgstr "コミットフィード" +msgid "Commits per day hour (UTC)" +msgstr "" + +msgid "Commits per day of month" +msgstr "" + +msgid "Commits per weekday" +msgstr "" + +msgid "Commits|An error occurred while fetching merge requests data." +msgstr "" + +msgid "Commits|Commit: %{commitText}" +msgstr "" + msgid "Commits|History" msgstr "å±¥æ´" +msgid "Commits|No related merge requests found" +msgstr "" + msgid "Committed by" msgstr "コミット担当者: " msgid "Compare" msgstr "比較" +msgid "Compare Git revisions" +msgstr "" + +msgid "Compare Revisions" +msgstr "" + +msgid "CompareBranches|%{source_branch} and %{target_branch} are the same." +msgstr "" + +msgid "CompareBranches|Compare" +msgstr "" + +msgid "CompareBranches|Source" +msgstr "" + +msgid "CompareBranches|Target" +msgstr "" + +msgid "CompareBranches|There isn't anything to compare." +msgstr "" + +msgid "Confidentiality" +msgstr "" + msgid "Container Registry" msgstr "" @@ -876,6 +1083,9 @@ msgstr "貢献者å‘ã‘ガイド" msgid "Contributors" msgstr "貢献者" +msgid "ContributorsPage|%{startDate} – %{endDate}" +msgstr "" + msgid "ContributorsPage|Building repository graph." msgstr "" @@ -897,9 +1107,18 @@ msgstr "" msgid "Copy URL to clipboard" msgstr "クリップボードã«URLをコピー" +msgid "Copy branch name to clipboard" +msgstr "" + msgid "Copy commit SHA to clipboard" msgstr "コミットã®SHAをクリップボードã«ã‚³ãƒ”ー" +msgid "Copy reference to clipboard" +msgstr "" + +msgid "Create" +msgstr "" + msgid "Create New Directory" msgstr "æ–°è¦ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã‚’作æˆ" @@ -918,6 +1137,9 @@ msgstr "" msgid "Create file" msgstr "" +msgid "Create lists from labels. Issues with that label appear in that list." +msgstr "" + msgid "Create merge request" msgstr "マージリクエストを作æˆ" @@ -930,6 +1152,9 @@ msgstr "" msgid "Create new file" msgstr "" +msgid "Create new label" +msgstr "" + msgid "Create new..." msgstr "æ–°è¦ä½œæˆ" @@ -951,6 +1176,9 @@ msgstr "Cron ã®ã‚¿ã‚¤ãƒ ゾーン" msgid "Cron syntax" msgstr "Cron ã®æ§‹æ–‡" +msgid "Current node" +msgstr "" + msgid "Custom notification events" msgstr "カスタム通知è¨å®š" @@ -960,9 +1188,6 @@ msgstr "\"カスタム\" ã®é€šçŸ¥ãƒ¬ãƒ™ãƒ«ã®åŸºæœ¬ã¯ \"å‚åŠ \" ã¨åŒã˜ã§ã msgid "Cycle Analytics" msgstr "サイクル分æž" -msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project." -msgstr "サイクル分æžã«ã‚ˆã‚Šã€ã‚ãªãŸã®ãƒ—ãƒã‚¸ã‚§ã‚¯ãƒˆãŒã‚¢ã‚¤ãƒ‡ã‚£ã‚¢ã®æ®µéšŽã‹ã‚‰ãƒ—ãƒãƒ€ã‚¯ã‚·ãƒ§ãƒ³ç’°å¢ƒã«ãƒªãƒªãƒ¼ã‚¹ã•ã‚Œã‚‹ã¾ã§ã©ã‚Œãらã„時間ãŒã‹ã‹ã£ãŸã‹ä¿¯çž°ã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚" - msgid "CycleAnalyticsStage|Code" msgstr "コード" @@ -1018,12 +1243,21 @@ msgstr "" msgid "Details" msgstr "" +msgid "Diffs|No file name available" +msgstr "" + msgid "Directory name" msgstr "ディレクトリå" +msgid "Disable" +msgstr "" + msgid "Discard changes" msgstr "" +msgid "Discover GitLab Geo." +msgstr "" + msgid "Dismiss Cycle Analytics introduction box" msgstr "" @@ -1060,15 +1294,24 @@ msgstr "プレーン差分" msgid "DownloadSource|Download" msgstr "ダウンãƒãƒ¼ãƒ‰" +msgid "Due date" +msgstr "" + msgid "Edit" msgstr "編集" msgid "Edit Pipeline Schedule %{id}" msgstr "パイプラインスケジュール %{id} を編集" +msgid "Edit files in the editor and commit changes here" +msgstr "" + msgid "Emails" msgstr "" +msgid "Enable" +msgstr "" + msgid "Environments|An error occurred while fetching the environments." msgstr "" @@ -1087,9 +1330,6 @@ msgstr "" msgid "Environments|Environments" msgstr "" -msgid "Environments|Environments are places where code gets deployed, such as staging or production." -msgstr "" - msgid "Environments|Job" msgstr "" @@ -1132,9 +1372,33 @@ msgstr "" msgid "Error creating epic" msgstr "" +msgid "Error fetching contributors data." +msgstr "" + +msgid "Error fetching labels." +msgstr "" + +msgid "Error fetching network graph." +msgstr "" + +msgid "Error fetching refs" +msgstr "" + +msgid "Error fetching usage ping data." +msgstr "" + msgid "Error occurred when toggling the notification subscription" msgstr "" +msgid "Error saving label update." +msgstr "" + +msgid "Error updating status for all todos." +msgstr "" + +msgid "Error updating todo status." +msgstr "" + msgid "EventFilterBy|Filter by all" msgstr "" @@ -1162,6 +1426,9 @@ msgstr "毎月 (1æ—¥ã®åˆå‰4:00)" msgid "Every week (Sundays at 4:00am)" msgstr "毎週 (日曜日ã®åˆå‰4:00)" +msgid "Expand" +msgstr "" + msgid "Explore projects" msgstr "" @@ -1180,6 +1447,9 @@ msgstr "" msgid "February" msgstr "" +msgid "Fields on this page are now uneditable, you can configure" +msgstr "" + msgid "File name" msgstr "" @@ -1223,10 +1493,10 @@ msgstr "マージリクエストãŒãƒžãƒ¼ã‚¸ã•ã‚Œã¦ã‹ã‚‰ãƒ—ãƒãƒ€ã‚¯ã‚·ãƒ§ãƒ³ msgid "GPG Keys" msgstr "" -msgid "Geo Nodes" +msgid "Generate a default set of labels" msgstr "" -msgid "GeoNodeSyncStatus|Failed" +msgid "Geo Nodes" msgstr "" msgid "GeoNodeSyncStatus|Node is failing or broken." @@ -1235,16 +1505,100 @@ msgstr "" msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage." msgstr "" -msgid "GeoNodeSyncStatus|Out of sync" +msgid "GeoNodes|Database replication lag:" +msgstr "" + +msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?" +msgstr "" + +msgid "GeoNodes|Does not match the primary storage configuration" +msgstr "" + +msgid "GeoNodes|Failed" +msgstr "" + +msgid "GeoNodes|Full" +msgstr "" + +msgid "GeoNodes|GitLab version does not match the primary node version" +msgstr "" + +msgid "GeoNodes|GitLab version:" +msgstr "" + +msgid "GeoNodes|Health status:" +msgstr "" + +msgid "GeoNodes|Last event ID processed by cursor:" +msgstr "" + +msgid "GeoNodes|Last event ID seen from primary:" +msgstr "" + +msgid "GeoNodes|Loading nodes" msgstr "" -msgid "GeoNodeSyncStatus|Synced" +msgid "GeoNodes|Local Attachments:" +msgstr "" + +msgid "GeoNodes|Local LFS objects:" +msgstr "" + +msgid "GeoNodes|Local job artifacts:" +msgstr "" + +msgid "GeoNodes|New node" +msgstr "" + +msgid "GeoNodes|Out of sync" +msgstr "" + +msgid "GeoNodes|Replication slot WAL:" +msgstr "" + +msgid "GeoNodes|Replication slots:" +msgstr "" + +msgid "GeoNodes|Repositories:" +msgstr "" + +msgid "GeoNodes|Selective" +msgstr "" + +msgid "GeoNodes|Storage config:" +msgstr "" + +msgid "GeoNodes|Sync settings:" +msgstr "" + +msgid "GeoNodes|Synced" +msgstr "" + +msgid "GeoNodes|Unused slots" +msgstr "" + +msgid "GeoNodes|Used slots" +msgstr "" + +msgid "GeoNodes|Wikis:" +msgstr "" + +msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS." +msgstr "" + +msgid "Geo|All projects" msgstr "" msgid "Geo|File sync capacity" msgstr "" -msgid "Geo|Groups to replicate" +msgid "Geo|Groups to synchronize" +msgstr "" + +msgid "Geo|Projects in certain groups" +msgstr "" + +msgid "Geo|Projects in certain storage shards" msgstr "" msgid "Geo|Repository sync capacity" @@ -1253,12 +1607,24 @@ msgstr "" msgid "Geo|Select groups to replicate." msgstr "" +msgid "Geo|Shards to synchronize" +msgstr "" + +msgid "Git revision" +msgstr "" + msgid "Git storage health information has been reset" msgstr "" +msgid "Git version" +msgstr "" + msgid "GitLab Runner section" msgstr "" +msgid "Gitaly Servers" +msgstr "" + msgid "Go to your fork" msgstr "自分ã®ãƒ•ã‚©ãƒ¼ã‚¯ã¸ç§»å‹•" @@ -1268,6 +1634,9 @@ msgstr "フォーク" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it!" +msgstr "" + msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" msgstr "" @@ -1304,7 +1673,7 @@ msgstr "" msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group." msgstr "" -msgid "GroupsTree|Are you sure you want to leave the \"${this.group.fullName}\" group?" +msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?" msgstr "" msgid "GroupsTree|Create a project in this group." @@ -1355,6 +1724,10 @@ msgstr "" msgid "HealthCheck|Unhealthy" msgstr "" +msgid "Hide value" +msgid_plural "Hide values" +msgstr[0] "" + msgid "History" msgstr "" @@ -1380,6 +1753,12 @@ msgid "Instance" msgid_plural "Instances" msgstr[0] "" +msgid "Instance does not support multiple Kubernetes clusters" +msgstr "" + +msgid "Interested parties can even contribute by pushing commits if they want to." +msgstr "" + msgid "Internal - The group and any internal projects can be viewed by any logged in user." msgstr "" @@ -1407,6 +1786,9 @@ msgstr "" msgid "Issues" msgstr "" +msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable." +msgstr "" + msgid "Jan" msgstr "" @@ -1425,6 +1807,27 @@ msgstr "" msgid "June" msgstr "" +msgid "Kubernetes" +msgstr "" + +msgid "Kubernetes Cluster" +msgstr "" + +msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}" +msgstr "" + +msgid "Kubernetes cluster integration was not removed." +msgstr "" + +msgid "Kubernetes cluster integration was successfully removed." +msgstr "" + +msgid "Kubernetes cluster was successfully updated." +msgstr "" + +msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page" +msgstr "" + msgid "LFSStatus|Disabled" msgstr "無効" @@ -1434,6 +1837,9 @@ msgstr "有効" msgid "Labels" msgstr "" +msgid "Labels can be applied to issues and merge requests to categorize them." +msgstr "" + msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "éŽåŽ»%d日間" @@ -1462,6 +1868,9 @@ msgstr "" msgid "LastPushEvent|at" msgstr "" +msgid "Learn more" +msgstr "" + msgid "Learn more in the" msgstr "詳ã—ã見る:" @@ -1480,13 +1889,18 @@ msgstr "プãƒã‚¸ã‚§ã‚¯ãƒˆã‚’離脱" msgid "License" msgstr "" -msgid "Limited to showing %d event at most" -msgid_plural "Limited to showing %d events at most" -msgstr[0] "イベント表示数を最大 %d 個ã«åˆ¶é™" +msgid "Loading the GitLab IDE..." +msgstr "" msgid "Lock" msgstr "" +msgid "Lock %{issuableDisplayName}" +msgstr "" + +msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment." +msgstr "" + msgid "Locked" msgstr "" @@ -1496,12 +1910,21 @@ msgstr "" msgid "Login" msgstr "" +msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos." +msgstr "" + +msgid "Manage labels" +msgstr "" + msgid "Mar" msgstr "" msgid "March" msgstr "" +msgid "Mark done" +msgstr "" + msgid "Maximum git storage failures" msgstr "" @@ -1514,6 +1937,9 @@ msgstr "ä¸å¤®å€¤" msgid "Members" msgstr "" +msgid "Merge Request" +msgstr "" + msgid "Merge Requests" msgstr "" @@ -1523,9 +1949,30 @@ msgstr "" msgid "Merge request" msgstr "" +msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others" +msgstr "" + +msgid "Merged" +msgstr "" + msgid "Messages" msgstr "" +msgid "Milestone" +msgstr "" + +msgid "Milestones|Delete milestone" +msgstr "" + +msgid "Milestones|Delete milestone %{milestoneTitle}?" +msgstr "" + +msgid "Milestones|Failed to delete milestone %{milestoneTitle}" +msgstr "" + +msgid "Milestones|Milestone %{milestoneTitle} was not found" +msgstr "" + msgid "MissingSSHKeyWarningLink|add an SSH key" msgstr "SSH éµã‚’è¿½åŠ " @@ -1535,16 +1982,28 @@ msgstr "" msgid "More information is available|here" msgstr "" +msgid "Move" +msgstr "" + +msgid "Move issue" +msgstr "" + msgid "Multiple issue boards" msgstr "" -msgid "New Cluster" +msgid "Name new label" msgstr "" msgid "New Issue" msgid_plural "New Issues" msgstr[0] "æ–°è¦èª²é¡Œ" +msgid "New Kubernetes Cluster" +msgstr "" + +msgid "New Kubernetes cluster" +msgstr "" + msgid "New Pipeline Schedule" msgstr "æ–°è¦ãƒ‘イプラインスケジュール" @@ -1569,6 +2028,9 @@ msgstr "" msgid "New issue" msgstr "æ–°è¦èª²é¡Œ" +msgid "New label" +msgstr "" + msgid "New merge request" msgstr "æ–°è¦ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆ" @@ -1587,7 +2049,22 @@ msgstr "" msgid "New tag" msgstr "æ–°è¦ã‚¿ã‚°" -msgid "No container images stored for this project. Add one by following the instructions above." +msgid "No assignee" +msgstr "" + +msgid "No changes" +msgstr "" + +msgid "No connection could be made to a Gitaly Server, please check your logs!" +msgstr "" + +msgid "No due date" +msgstr "" + +msgid "No estimate or time spent" +msgstr "" + +msgid "No file chosen" msgstr "" msgid "No repository" @@ -1602,9 +2079,15 @@ msgstr "" msgid "None" msgstr "" +msgid "Not allowed to merge" +msgstr "" + msgid "Not available" msgstr "利用ã§ãã¾ã›ã‚“" +msgid "Not confidential" +msgstr "" + msgid "Not enough data" msgstr "データä¸è¶³" @@ -1665,6 +2148,12 @@ msgstr "ã™ã¹ã¦é€šçŸ¥" msgid "Notifications" msgstr "" +msgid "Notifications off" +msgstr "" + +msgid "Notifications on" +msgstr "" + msgid "Nov" msgstr "" @@ -1674,7 +2163,7 @@ msgstr "" msgid "Number of access attempts" msgstr "" -msgid "Number of failures before backing off" +msgid "OK" msgstr "" msgid "Oct" @@ -1689,6 +2178,9 @@ msgstr "フィルター" msgid "Only project members can comment." msgstr "" +msgid "Open" +msgstr "" + msgid "Opened" msgstr "" @@ -1722,9 +2214,6 @@ msgstr "" msgid "Password" msgstr "" -msgid "People without permission will never get a notification and won\\'t be able to comment." -msgstr "" - msgid "Pipeline" msgstr "パイプライン" @@ -1767,12 +2256,6 @@ msgstr "全件" msgid "PipelineSchedules|Inactive" msgstr "無効" -msgid "PipelineSchedules|Input variable key" -msgstr "変数ã®åå‰ã‚’入力" - -msgid "PipelineSchedules|Input variable value" -msgstr "変数ã®å€¤ã‚’入力" - msgid "PipelineSchedules|Next Run" msgstr "次ã®å®Ÿè¡Œ" @@ -1782,9 +2265,6 @@ msgstr "ãªã—" msgid "PipelineSchedules|Provide a short description for this pipeline" msgstr "ã“ã®ãƒ‘イプラインã«ã¤ã„ã¦ç°¡å˜ã«è¨˜è¿°ã—ã¦ãã ã•ã„。" -msgid "PipelineSchedules|Remove variable row" -msgstr "変数を削除" - msgid "PipelineSchedules|Take ownership" msgstr "権é™ã‚’å–å¾—ã™ã‚‹" @@ -1812,6 +2292,12 @@ msgstr "" msgid "Pipelines for last year" msgstr "" +msgid "Pipelines|Build with confidence" +msgstr "" + +msgid "Pipelines|Get started with Pipelines" +msgstr "" + msgid "Pipeline|all" msgstr "全件" @@ -1824,12 +2310,21 @@ msgstr "ステージã‚ã‚Š" msgid "Pipeline|with stages" msgstr "ステージã‚ã‚Š" +msgid "Play" +msgstr "" + +msgid "Please <a href=%{link_to_billing} target=\"_blank\" rel=\"noopener noreferrer\">enable billing for one of your projects to be able to create a Kubernetes cluster</a>, then try again." +msgstr "" + msgid "Please solve the reCAPTCHA" msgstr "" msgid "Preferences" msgstr "" +msgid "Primary" +msgstr "" + msgid "Private - Project access must be granted explicitly to each user." msgstr "" @@ -1875,6 +2370,9 @@ msgstr "" msgid "Profiles|your account" msgstr "" +msgid "Programming languages used in this repository" +msgstr "" + msgid "Project '%{project_name}' is in the process of being deleted." msgstr "" @@ -1890,6 +2388,15 @@ msgstr "'%{project_name}' プãƒã‚¸ã‚§ã‚¯ãƒˆã¯æ£å¸¸ã«æ›´æ–°ã•ã‚Œã¾ã—ãŸã€‚ msgid "Project access must be granted explicitly to each user." msgstr "ユーザーã”ã¨ã«ãƒ—ãƒã‚¸ã‚§ã‚¯ãƒˆã‚¢ã‚¯ã‚»ã‚¹ã®æ¨©é™ã‚’指定ã—ãªã‘ã‚Œã°ãªã‚Šã¾ã›ã‚“。" +msgid "Project avatar" +msgstr "" + +msgid "Project avatar in repository: %{link}" +msgstr "" + +msgid "Project cache successfully reset." +msgstr "" + msgid "Project details" msgstr "" @@ -1908,6 +2415,21 @@ msgstr "プãƒã‚¸ã‚§ã‚¯ãƒˆã®ã‚¨ã‚¯ã‚¹ãƒãƒ¼ãƒˆã‚’開始ã—ã¾ã—ãŸã€‚ダウン msgid "ProjectActivityRSS|Subscribe" msgstr "" +msgid "ProjectCreationLevel|Allowed to create projects" +msgstr "" + +msgid "ProjectCreationLevel|Default project creation protection" +msgstr "" + +msgid "ProjectCreationLevel|Developers + Masters" +msgstr "" + +msgid "ProjectCreationLevel|Masters" +msgstr "" + +msgid "ProjectCreationLevel|No one" +msgstr "" + msgid "ProjectFeature|Disabled" msgstr "無効" @@ -1932,15 +2454,9 @@ msgstr "ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ã‚°ãƒ©ãƒ•" msgid "ProjectSettings|Contact an admin to change this setting." msgstr "" -msgid "ProjectSettings|Immediately run a pipeline on the default branch" -msgstr "" - msgid "ProjectSettings|Only signed commits can be pushed to this repository." msgstr "" -msgid "ProjectSettings|Problem setting up the CI/CD settings JavaScript" -msgstr "" - msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin." msgstr "" @@ -2004,12 +2520,15 @@ msgstr "" msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/" msgstr "" -msgid "PrometheusService|Prometheus monitoring" +msgid "PrometheusService|Time-series monitoring service" msgstr "" msgid "PrometheusService|View environments" msgstr "" +msgid "Protip:" +msgstr "" + msgid "Public - The group and any public projects can be viewed without any authentication." msgstr "" @@ -2025,6 +2544,9 @@ msgstr "" msgid "PushRule|Committer restriction" msgstr "" +msgid "Quick actions can be used in the issues description and comment boxes." +msgstr "" + msgid "Read more" msgstr "続ãã‚’èªã‚€" @@ -2037,6 +2559,12 @@ msgstr "ブランãƒ" msgid "RefSwitcher|Tags" msgstr "ã‚¿ã‚°" +msgid "Reference:" +msgstr "" + +msgid "Register / Sign In" +msgstr "" + msgid "Registry" msgstr "" @@ -2061,9 +2589,18 @@ msgstr "関連ã™ã‚‹ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆ" msgid "Remind later" msgstr "後ã§é€šçŸ¥" +msgid "Remove" +msgstr "" + +msgid "Remove avatar" +msgstr "" + msgid "Remove project" msgstr "プãƒã‚¸ã‚§ã‚¯ãƒˆã‚’削除" +msgid "Repair authentication" +msgstr "" + msgid "Repository" msgstr "" @@ -2079,6 +2616,10 @@ msgstr "" msgid "Reset runners registration token" msgstr "" +msgid "Reveal value" +msgid_plural "Reveal values" +msgstr[0] "" + msgid "Revert this commit" msgstr "ã“ã®ã‚³ãƒŸãƒƒãƒˆã‚’リãƒãƒ¼ãƒˆ" @@ -2088,15 +2629,15 @@ msgstr "ã“ã®ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆã‚’リãƒãƒ¼ãƒˆ" msgid "SSH Keys" msgstr "" -msgid "Save" -msgstr "" - msgid "Save changes" msgstr "" msgid "Save pipeline schedule" msgstr "パイプラインスケジュールをä¿å˜" +msgid "Save variables" +msgstr "" + msgid "Schedule a new pipeline" msgstr "æ–°ã—ã„パイプラインã®ã‚¹ã‚±ã‚¸ãƒ¥ãƒ¼ãƒ«ã‚’作æˆ" @@ -2112,38 +2653,59 @@ msgstr "" msgid "Search branches and tags" msgstr "ブランãƒã¾ãŸã¯ã‚¿ã‚°ã‚’検索" -msgid "Seconds before reseting failure information" +msgid "Search milestones" msgstr "" -msgid "Seconds to wait after a storage failure" +msgid "Search project" +msgstr "" + +msgid "Search users" +msgstr "" + +msgid "Seconds before reseting failure information" msgstr "" msgid "Seconds to wait for a storage access attempt" msgstr "" +msgid "Secret variables" +msgstr "" + msgid "Select Archive Format" msgstr "アーカイブã®ãƒ•ã‚©ãƒ¼ãƒžãƒƒãƒˆã‚’é¸æŠž" msgid "Select a timezone" msgstr "タイムゾーンをé¸æŠž" +msgid "Select assignee" +msgstr "" + +msgid "Select branch/tag" +msgstr "" + msgid "Select target branch" msgstr "ターゲットブランãƒã‚’é¸æŠž" +msgid "Selective synchronization" +msgstr "" + msgid "Sep" msgstr "" msgid "September" msgstr "" +msgid "Server version" +msgstr "" + msgid "Service Templates" msgstr "" msgid "Set a password on your account to pull or push via %{protocol}." msgstr "%{protocol} プãƒã‚³ãƒˆãƒ«çµŒç”±ã§ãƒ—ルã€ãƒ—ッシュã™ã‚‹ãŸã‚ã«ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã®ãƒ‘スワードをè¨å®šã€‚" -msgid "Set up CI" -msgstr "CI ã‚’è¨å®š" +msgid "Set up CI/CD" +msgstr "" msgid "Set up Koding" msgstr "Koding ã‚’è¨å®š" @@ -2157,6 +2719,15 @@ msgstr "パスワードをè¨å®š" msgid "Settings" msgstr "" +msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero." +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset pipeline minutes" +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes" +msgstr "" + msgid "Show parent pages" msgstr "" @@ -2170,9 +2741,6 @@ msgstr[0] "%d ã®ã‚¤ãƒ™ãƒ³ãƒˆã‚’表示ä¸" msgid "Sidebar|Change weight" msgstr "" -msgid "Sidebar|Edit" -msgstr "" - msgid "Sidebar|No" msgstr "" @@ -2185,18 +2753,30 @@ msgstr "" msgid "Snippets" msgstr "" +msgid "Something went wrong on our end" +msgstr "" + msgid "Something went wrong on our end." msgstr "" +msgid "Something went wrong trying to change the confidentiality of this issue" +msgstr "" + msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}" msgstr "" +msgid "Something went wrong when toggling the button" +msgstr "" + msgid "Something went wrong while fetching the projects." msgstr "" msgid "Something went wrong while fetching the registry list." msgstr "" +msgid "Something went wrong. Please try again." +msgstr "" + msgid "Sort by" msgstr "" @@ -2326,10 +2906,10 @@ msgstr "" msgid "Stopped" msgstr "" -msgid "Subgroups" +msgid "Storage" msgstr "" -msgid "Subscribe" +msgid "Subgroups" msgstr "" msgid "Switch branch/tag" @@ -2426,7 +3006,10 @@ msgstr "" msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project." msgstr "" -msgid "The circuitbreaker backoff threshold should be lower than the failure count threshold" +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project" +msgstr "" + +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project." msgstr "" msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request." @@ -2441,10 +3024,10 @@ msgstr "フォークã®ãƒªãƒ¬ãƒ¼ã‚·ãƒ§ãƒ³ãŒå‰Šé™¤ã•ã‚Œã¾ã—ãŸã€‚" msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage." msgstr "課題ステージã§ã¯ã€èª²é¡ŒãŒç™»éŒ²ã•ã‚Œã¦ã‹ã‚‰ãƒžã‚¤ãƒ«ã‚¹ãƒˆãƒ¼ãƒ³ã«å‰²ã‚Šå½“ã¦ã‚‰ã‚Œã‚‹ã‹ã€èª²é¡Œãƒœãƒ¼ãƒ‰ã®ãƒªã‚¹ãƒˆã«è¿½åŠ ã•ã‚Œã‚‹ã¾ã§ã®æ™‚é–“ãŒè¡¨ç¤ºã•ã‚Œã¾ã™ã€‚ã“ã®ãƒªã‚¹ãƒˆã«è¡¨ç¤ºã™ã‚‹ã«ã¯èª²é¡Œã‚’最åˆã«ä½œæˆã—ã¦ãã ã•ã„。" -msgid "The number of attempts GitLab will make to access a storage." +msgid "The maximum file size allowed is 200KB." msgstr "" -msgid "The number of failures after which GitLab will start temporarily disabling access to a storage shard on a host" +msgid "The number of attempts GitLab will make to access a storage." msgstr "" msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}." @@ -2453,9 +3036,6 @@ msgstr "" msgid "The phase of the development lifecycle." msgstr "開発ライフサイクルã®æ®µéšŽ" -msgid "The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user." -msgstr "パイプラインスケジュールã¯æŒ‡å®šã®ãƒ–ランãƒã¾ãŸã¯ã‚¿ã‚°ã«å¯¾ã—ã¦è‡ªå‹•çš„ã«ãƒ‘イプラインを実行ã—ã¾ã™ã€‚計画済ã¿ãƒ‘イプラインã¯ãれらã®ç´ä»˜ã‘られãŸãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ãƒ—ãƒã‚¸ã‚§ã‚¯ãƒˆã¨åŒã˜æ¨©é™ã‚’継承ã—ã¾ã™ã€‚" - msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit." msgstr "計画ステージã§ã¯ã€èª²é¡Œã‚¹ãƒ†ãƒ¼ã‚¸ã«ç™»éŒ²ã•ã‚Œã¦ã‹ã‚‰ãƒ—ッシュã•ã‚ŒãŸæœ€åˆã®ã‚³ãƒŸãƒƒãƒˆæ™‚刻ã¾ã§ã®æ™‚é–“ãŒè¡¨ç¤ºã•ã‚Œã¾ã™ã€‚最åˆã®ã‚³ãƒŸãƒƒãƒˆãŒãƒ—ッシュã•ã‚Œã¨ãã«è‡ªå‹•çš„ã«è¿½åŠ ã•ã‚Œã¾ã™ã€‚" @@ -2486,19 +3066,46 @@ msgstr "" msgid "The time in seconds GitLab will try to access storage. After this time a timeout error will be raised." msgstr "" +msgid "The time in seconds between storage checks. When a previous check did complete yet, GitLab will skip a check." +msgstr "" + msgid "The time taken by each data entry gathered by that stage." msgstr "ã“ã®ã‚¹ãƒ†ãƒ¼ã‚¸ã«åŽé›†ã•ã‚ŒãŸãƒ‡ãƒ¼ã‚¿æ¯Žã®æ™‚é–“" msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6." msgstr "得られãŸä¸€é€£ã®ãƒ‡ãƒ¼ã‚¿ã‚’å°ã•ã„é †ã«ä¸¦ã¹ãŸã¨ãã«ä¸å¤®ã«ä½ç½®ã™ã‚‹å€¤ã€‚例ãˆã°ã€3, 5, 9ã®ä¸å¤®å€¤ã¯5。3, 5, 7, 8ã®ä¸å¤®å€¤ã¯ (5+7)/2 = 6。" +msgid "There are no issues to show" +msgstr "" + +msgid "There are no merge requests to show" +msgstr "" + msgid "There are problems accessing Git storage: " msgstr "" +msgid "There was an error loading users activity calendar." +msgstr "" + +msgid "There was an error saving your notification settings." +msgstr "" + +msgid "There was an error subscribing to this label." +msgstr "" + +msgid "There was an error when reseting email token." +msgstr "" + +msgid "There was an error when subscribing to this label." +msgstr "" + +msgid "There was an error when unsubscribing from this label." +msgstr "" + msgid "This board\\'s scope is reduced" msgstr "" -msgid "This branch has changed since you started editing. Would you like to create a new branch?" +msgid "This directory" msgstr "" msgid "This is a confidential issue." @@ -2507,18 +3114,45 @@ msgstr "" msgid "This is the author's first Merge Request to this project." msgstr "" +msgid "This issue is confidential" +msgstr "" + msgid "This issue is confidential and locked." msgstr "" msgid "This issue is locked." msgstr "" +msgid "This job depends on a user to trigger its process. Often they are used to deploy code to production environments" +msgstr "" + +msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered" +msgstr "" + +msgid "This job has not been triggered yet" +msgstr "" + +msgid "This job has not started yet" +msgstr "" + +msgid "This job is in pending state and is waiting to be picked by a runner" +msgstr "" + +msgid "This job requires a manual action" +msgstr "" + msgid "This means you can not push code until you create an empty repository or import existing one." msgstr "空レãƒã‚¸ãƒˆãƒªãƒ¼ã‚’作æˆã¾ãŸã¯æ—¢å˜ãƒ¬ãƒã‚¸ãƒˆãƒªãƒ¼ã‚’インãƒãƒ¼ãƒˆã‚’ã—ãªã‘ã‚Œã°ã€ã‚³ãƒ¼ãƒ‰ã®ãƒ—ッシュã¯ã§ãã¾ã›ã‚“。" msgid "This merge request is locked." msgstr "" +msgid "This project" +msgstr "" + +msgid "This repository" +msgstr "" + msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here." msgstr "" @@ -2531,9 +3165,21 @@ msgstr "課題ã®å®Ÿè£…ãŒé–‹å§‹ã•ã‚Œã‚‹ã¾ã§ã®æ™‚é–“" msgid "Time between merge request creation and merge/close" msgstr "マージリクエストãŒä½œæˆã•ã‚Œã¦ã‹ã‚‰ãƒžãƒ¼ã‚¸ã¾ãŸã¯ã‚¯ãƒãƒ¼ã‚ºã•ã‚Œã‚‹ã¾ã§ã®æ™‚é–“" +msgid "Time tracking" +msgstr "" + msgid "Time until first merge request" msgstr "最åˆã®ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆã¾ã§ã®æ™‚é–“" +msgid "TimeTrackingEstimated|Est" +msgstr "" + +msgid "TimeTracking|Estimated:" +msgstr "" + +msgid "TimeTracking|Spent" +msgstr "" + msgid "Timeago|%s days ago" msgstr "%sæ—¥å‰" @@ -2671,6 +3317,18 @@ msgstr "秒" msgid "Title" msgstr "" +msgid "Todo" +msgstr "" + +msgid "Toggle sidebar" +msgstr "" + +msgid "ToggleButton|Toggle Status: OFF" +msgstr "" + +msgid "ToggleButton|Toggle Status: ON" +msgstr "" + msgid "Total Time" msgstr "åˆè¨ˆæ™‚é–“" @@ -2686,19 +3344,40 @@ msgstr "" msgid "Track groups of issues that share a theme, across projects and milestones" msgstr "" +msgid "Total: %{total}" +msgstr "" + +msgid "Track time with quick actions" +msgstr "" + +msgid "Trigger this manual action" +msgstr "" + msgid "Turn on Service Desk" msgstr "" +msgid "Type %{value} to confirm:" +msgstr "" + +msgid "Unable to reset project cache." +msgstr "" + +msgid "Unknown" +msgstr "" + msgid "Unlock" msgstr "" +msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment." +msgstr "" + msgid "Unlocked" msgstr "" msgid "Unstar" msgstr "スターを外ã™" -msgid "Unsubscribe" +msgid "Up to date" msgstr "" msgid "Upgrade your plan to activate Advanced Global Search." @@ -2722,6 +3401,9 @@ msgstr "æ–°è¦ãƒ•ã‚¡ã‚¤ãƒ«ã‚’アップãƒãƒ¼ãƒ‰" msgid "Upload file" msgstr "ファイルをアップãƒãƒ¼ãƒ‰" +msgid "Upload new avatar" +msgstr "" + msgid "UploadLink|click to upload" msgstr "クリックã—ã¦ã‚¢ãƒƒãƒ—ãƒãƒ¼ãƒ‰" @@ -2734,9 +3416,15 @@ msgstr "" msgid "Use your global notification setting" msgstr "全体通知è¨å®šã‚’利用" +msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." +msgstr "" + msgid "View file @ " msgstr "" +msgid "View labels" +msgstr "" + msgid "View open merge request" msgstr "オープンãªãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆã‚’表示" @@ -2758,6 +3446,9 @@ msgstr "ä¸æ˜Ž" msgid "Want to see the data? Please ask an administrator for access." msgstr "ã“ã®ãƒ‡ãƒ¼ã‚¿ã‚’å‚ç…§ã—ãŸã„ã§ã™ã‹ï¼Ÿã‚¢ã‚¯ã‚»ã‚¹ã™ã‚‹ã«ã¯ç®¡ç†è€…ã«å•ã„åˆã‚ã›ã¦ãã ã•ã„。" +msgid "We could not verify that one of your projects on GCP has billing enabled. Please try again." +msgstr "" + msgid "We don't have enough data to show this stage." msgstr "データä¸è¶³ã®ãŸã‚ã€ã“ã®ã‚¹ãƒ†ãƒ¼ã‚¸ã®è¡¨ç¤ºã¯ã§ãã¾ã›ã‚“。" @@ -2770,9 +3461,6 @@ msgstr "" msgid "Weight" msgstr "" -msgid "When access to a storage fails. GitLab will prevent access to the storage for the time specified here. This allows the filesystem to recover. Repositories on failing shards are temporarly unavailable" -msgstr "" - msgid "Wiki" msgstr "" @@ -2791,6 +3479,12 @@ msgstr "" msgid "WikiClone|Start Gollum and edit locally" msgstr "" +msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title." +msgstr "" + +msgid "WikiEdit|There is already a page with the same title in that path." +msgstr "" + msgid "WikiEmptyPageError|You are not allowed to create wiki pages" msgstr "" @@ -2893,9 +3587,21 @@ msgstr "å…ƒã®ãƒ—ãƒã‚¸ã‚§ã‚¯ãƒˆ (%{forked_from_project}) ã¨ã®ãƒªãƒ¬ãƒ¼ã‚·ãƒ§ã msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" msgstr "%{project_name_with_namespace} プãƒã‚¸ã‚§ã‚¯ãƒˆã‚’別ã®ã‚ªãƒ¼ãƒŠãƒ¼ã«ç§»è²ã—よã†ã¨ã—ã¦ã„ã¾ã™ã€‚本当ã«ã‚ˆã‚ã—ã„ã§ã™ã‹ï¼Ÿ" +msgid "You can also star a label to make it a priority label." +msgstr "" + +msgid "You can move around the graph by using the arrow keys." +msgstr "" + +msgid "You can move around the graph by using the arrow keys." +msgstr "" + msgid "You can only add files when you are on a branch" msgstr "ãƒ•ã‚¡ã‚¤ãƒ«ã‚’è¿½åŠ ã™ã‚‹ã«ã¯ã€ã©ã“ã‹ã®ãƒ–ランãƒã«ã„ãªã‘ã‚Œã°ã„ã‘ã¾ã›ã‚“" +msgid "You can only edit files when you are on a branch" +msgstr "" + msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead." msgstr "" @@ -2935,6 +3641,12 @@ msgstr "%{add_ssh_key_link} をプãƒãƒ•ã‚¡ã‚¤ãƒ«ã«è¿½åŠ ã—ã¦ã„ãªã„ã®ã§ã msgid "You won't be able to pull or push project code via SSH until you add an SSH key to your profile" msgstr "" +msgid "You'll need to use different branch names to get a valid comparison." +msgstr "" + +msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure" +msgstr "" + msgid "Your comment will not be visible to the public." msgstr "" @@ -2947,25 +3659,218 @@ msgstr "åå‰" msgid "Your projects" msgstr "" +msgid "assign yourself" +msgstr "" + msgid "branch name" msgstr "" msgid "by" msgstr "" +msgid "ciReport|Code quality" +msgstr "" + +msgid "ciReport|DAST detected no alerts by analyzing the review app" +msgstr "" + +msgid "ciReport|Failed to load ${type} report" +msgstr "" + +msgid "ciReport|Fixed:" +msgstr "" + +msgid "ciReport|Instances" +msgstr "" + +msgid "ciReport|Learn more about whitelisting" +msgstr "" + +msgid "ciReport|Loading ${type} report" +msgstr "" + +msgid "ciReport|No changes to code quality" +msgstr "" + +msgid "ciReport|No changes to performance metrics" +msgstr "" + +msgid "ciReport|Performance metrics" +msgstr "" + +msgid "ciReport|SAST" +msgstr "" + +msgid "ciReport|SAST detected no security vulnerabilities" +msgstr "" + +msgid "ciReport|SAST:container no vulnerabilities were found" +msgstr "" + +msgid "ciReport|Show complete code vulnerabilities report" +msgstr "" + +msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}" +msgstr "" + msgid "commit" msgstr "" +msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue." +msgstr "" + +msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue." +msgstr "" + msgid "day" msgid_plural "days" msgstr[0] "æ—¥" +msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command." +msgstr "" + +msgid "merge request" +msgid_plural "merge requests" +msgstr[0] "" + +msgid "mrWidget|Cancel automatic merge" +msgstr "" + +msgid "mrWidget|Check out branch" +msgstr "" + +msgid "mrWidget|Checking ability to merge automatically" +msgstr "" + +msgid "mrWidget|Cherry-pick" +msgstr "" + +msgid "mrWidget|Cherry-pick this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Closed" +msgstr "" + +msgid "mrWidget|Closed by" +msgstr "" + +msgid "mrWidget|Closes" +msgstr "" + +msgid "mrWidget|Did not close" +msgstr "" + +msgid "mrWidget|Email patches" +msgstr "" + +msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|Mentions" +msgstr "" + +msgid "mrWidget|Merge" +msgstr "" + +msgid "mrWidget|Merge failed." +msgstr "" + +msgid "mrWidget|Merge locally" +msgstr "" + +msgid "mrWidget|Merged by" +msgstr "" + +msgid "mrWidget|Plain diff" +msgstr "" + +msgid "mrWidget|Refresh" +msgstr "" + +msgid "mrWidget|Refresh now" +msgstr "" + +msgid "mrWidget|Refreshing now" +msgstr "" + +msgid "mrWidget|Remove Source Branch" +msgstr "" + +msgid "mrWidget|Remove source branch" +msgstr "" + +msgid "mrWidget|Request to merge" +msgstr "" + +msgid "mrWidget|Resolve conflicts" +msgstr "" + +msgid "mrWidget|Revert" +msgstr "" + +msgid "mrWidget|Revert this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Set by" +msgstr "" + +msgid "mrWidget|The changes were merged into" +msgstr "" + +msgid "mrWidget|The changes were not merged into" +msgstr "" + +msgid "mrWidget|The changes will be merged into" +msgstr "" + +msgid "mrWidget|The source branch has been removed" +msgstr "" + +msgid "mrWidget|The source branch is being removed" +msgstr "" + +msgid "mrWidget|The source branch will be removed" +msgstr "" + +msgid "mrWidget|The source branch will not be removed" +msgstr "" + +msgid "mrWidget|There are merge conflicts" +msgstr "" + +msgid "mrWidget|This merge request failed to be merged automatically" +msgstr "" + +msgid "mrWidget|This merge request is in the process of being merged" +msgstr "" + +msgid "mrWidget|This project is archived, write access has been disabled" +msgstr "" + +msgid "mrWidget|You can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|You can remove source branch now" +msgstr "" + +msgid "mrWidget|command line" +msgstr "" + +msgid "mrWidget|into" +msgstr "" + +msgid "mrWidget|to be merged automatically when the pipeline succeeds" +msgstr "" + msgid "new merge request" msgstr "æ–°è¦ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆ" msgid "notification emails" msgstr "メール通知" +msgid "or" +msgstr "" + msgid "parent" msgid_plural "parents" msgstr[0] "親" @@ -2976,12 +3881,21 @@ msgstr "" msgid "personal access token" msgstr "" +msgid "remove due date" +msgstr "" + msgid "source" msgstr "" +msgid "spendCommand|%{slash_command} will update the sum of the time spent." +msgstr "" + msgid "to help your contributors communicate effectively!" msgstr "" msgid "username" msgstr "" +msgid "uses Kubernetes clusters to deploy your code!" +msgstr "" + diff --git a/locale/ko/gitlab.po b/locale/ko/gitlab.po index 9ec3d395c15..73909e6f6de 100644 --- a/locale/ko/gitlab.po +++ b/locale/ko/gitlab.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab-ee\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-12-12 18:31+0000\n" -"PO-Revision-Date: 2018-01-05 04:41-0500\n" +"POT-Creation-Date: 2018-02-07 11:38-0600\n" +"PO-Revision-Date: 2018-02-12 04:00-0500\n" "Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n" "Language-Team: Korean\n" "Language: ko_KR\n" @@ -16,20 +16,35 @@ msgstr "" "X-Crowdin-Language: ko\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" +msgid " and" +msgstr "" + msgid "%d commit" msgid_plural "%d commits" msgstr[0] "%d 커밋" +msgid "%d commit behind" +msgid_plural "%d commits behind" +msgstr[0] "" + +msgid "%d issue" +msgid_plural "%d issues" +msgstr[0] "" + msgid "%d layer" msgid_plural "%d layers" msgstr[0] "" +msgid "%d merge request" +msgid_plural "%d merge requests" +msgstr[0] "" + msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "%s additional commits have been omitted to prevent performance issues." msgstr[0] "%s 추가 ì»¤ë°‹ì€ ì„±ëŠ¥ ì´ìŠˆë¥¼ 방지하기 위해 ìƒëžµë˜ì—ˆìŠµë‹ˆë‹¤." -msgid "%{commit_author_link} committed %{commit_timeago}" -msgstr "%{commit_timeago} ì— %{commit_author_link} ë‹˜ì´ ì»¤ë°‹í•˜ì˜€ìŠµë‹ˆë‹¤. " +msgid "%{commit_author_link} authored %{commit_timeago}" +msgstr "" msgid "%{count} participant" msgid_plural "%{count} participants" @@ -41,9 +56,6 @@ msgstr "" msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will allow access on the next attempt." msgstr "%{number_of_failures} / %{maximum_failures} 실패. GitLab ì€ ë‹¤ìŒ ì‹œë„ì—ì„œ 성공하면 ì ‘ê·¼ì„ í—ˆìš©í• ê²ƒìž…ë‹ˆë‹¤." -msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will block access for %{number_of_seconds} seconds." -msgstr "%{number_of_failures} / %{maximum_failures} 실패. GitLab ì€ %{number_of_seconds} ì´ˆ ê°„ ì ‘ê·¼ì„ ì œí•œí•˜ê² ìŠµë‹ˆë‹¤." - msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved." msgstr "%{number_of_failures} / %{maximum_failures} 실패. GitLab ì€ ìžë™ìœ¼ë¡œ 다시 ì‹œë„하지 않습니다. ë¬¸ì œê°€ í•´ê²°ë˜ë©´ ì €ìž¥ 공간 ì •ë³´ë¥¼ 초기화 해주세요. " @@ -115,24 +127,81 @@ msgstr "ë¼ì´ì„ 스 추가" msgid "Add new directory" msgstr "새 ë””ë ‰í† ë¦¬ 추가" +msgid "Add todo" +msgstr "" + +msgid "AdminArea|Stop all jobs" +msgstr "" + +msgid "AdminArea|Stop all jobs?" +msgstr "" + +msgid "AdminArea|Stop jobs" +msgstr "" + +msgid "AdminArea|Stopping jobs failed" +msgstr "" + +msgid "AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running." +msgstr "" + msgid "AdminHealthPageLink|health page" msgstr "" +msgid "Advanced" +msgstr "" + msgid "Advanced settings" msgstr "" msgid "All" msgstr "ì „ì²´" +msgid "All changes are committed" +msgstr "" + +msgid "Allows you to add and manage Kubernetes clusters." +msgstr "" + +msgid "An error occurred previewing the blob" +msgstr "" + msgid "An error occurred when toggling the notification subscription" msgstr "" msgid "An error occurred when updating the issue weight" msgstr "" +msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." +msgstr "" + +msgid "An error occurred while fetching markdown preview" +msgstr "" + msgid "An error occurred while fetching sidebar data" msgstr "" +msgid "An error occurred while getting projects" +msgstr "" + +msgid "An error occurred while loading filenames" +msgstr "" + +msgid "An error occurred while rendering KaTeX" +msgstr "" + +msgid "An error occurred while rendering preview broadcast message" +msgstr "" + +msgid "An error occurred while retrieving calendar activity" +msgstr "" + +msgid "An error occurred while retrieving diff" +msgstr "" + +msgid "An error occurred while validating username" +msgstr "" + msgid "An error occurred. Please try again." msgstr "" @@ -157,9 +226,6 @@ msgstr "ì´ íŒŒì´í”„ë¼ì¸ ìŠ¤ì¼€ì¥´ì„ ì‚ì œ í•˜ì‹œê² ìŠµë‹ˆê¹Œ?" msgid "Are you sure you want to discard your changes?" msgstr "변경 ë‚´ìš©ì„ ì·¨ì†Œí•˜ì‹œê² ìŠµë‹ˆê¹Œ?" -msgid "Are you sure you want to leave this group?" -msgstr "" - msgid "Are you sure you want to reset registration token?" msgstr "ë“±ë¡ í† í°ì„ 초기화 í•˜ì‹œê² ìŠµë‹ˆê¹Œ?" @@ -172,6 +238,21 @@ msgstr "확실합니까?" msgid "Artifacts" msgstr "" +msgid "Assign custom color like #FF0000" +msgstr "" + +msgid "Assign labels" +msgstr "" + +msgid "Assign milestone" +msgstr "" + +msgid "Assign to" +msgstr "" + +msgid "Assignee" +msgstr "" + msgid "Attach a file by drag & drop or %{upload_link}" msgstr "드래그 & ë“œë¡ ë˜ëŠ” %{upload_link}" @@ -187,13 +268,16 @@ msgstr "" msgid "Author" msgstr "" -msgid "Auto Review Apps and Auto Deploy need a domain name and the %{kubernetes} to work correctly." +msgid "Authors: %{authors}" msgstr "" -msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly." +msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." msgstr "" -msgid "Auto Review Apps and Auto Deploy need the %{kubernetes} to work correctly." +msgid "Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly." +msgstr "" + +msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly." msgstr "" msgid "AutoDevOps|Auto DevOps (Beta)" @@ -217,6 +301,12 @@ msgstr "" msgid "Available" msgstr "" +msgid "Avatar will be removed. Are you sure?" +msgstr "" + +msgid "Average per day: %{average}" +msgstr "" + msgid "Billing" msgstr "" @@ -271,6 +361,9 @@ msgstr "" msgid "BillingPlans|per user" msgstr "" +msgid "Begin with the selected commit" +msgstr "" + msgid "Branch" msgid_plural "Branches" msgstr[0] "브랜치" @@ -398,8 +491,8 @@ msgstr "작성ìž" msgid "CI / CD" msgstr "" -msgid "CI configuration" -msgstr "CI ì„¤ì •" +msgid "CI/CD configuration" +msgstr "" msgid "CICD|Jobs" msgstr "" @@ -410,6 +503,9 @@ msgstr "취소" msgid "Cancel edit" msgstr "" +msgid "Cannot modify managed Kubernetes cluster" +msgstr "" + msgid "Change Weight" msgstr "" @@ -425,15 +521,24 @@ msgstr "Cherry-pick" msgid "ChangeTypeAction|Revert" msgstr "Revert" +msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes." +msgstr "" + msgid "Changelog" msgstr "변경사í•" +msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision." +msgstr "" + msgid "Charts" msgstr "차트" msgid "Chat" msgstr "" +msgid "Check interval" +msgstr "" + msgid "Checking %{text} availability…" msgstr "" @@ -446,7 +551,19 @@ msgstr "ì´ ì»¤ë°‹ì„ Cherry-pick" msgid "Cherry-pick this merge request" msgstr "ì´ ë¨¸ì§€ 리퀘스트를 Cherry-pick" -msgid "Choose which groups you wish to replicate to this secondary node. Leave blank to replicate all." +msgid "Choose File ..." +msgstr "" + +msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request." +msgstr "" + +msgid "Choose file..." +msgstr "" + +msgid "Choose which groups you wish to synchronize to this secondary node." +msgstr "" + +msgid "Choose which shards you wish to synchronize to this secondary node." msgstr "" msgid "CiStatusLabel|canceled" @@ -503,79 +620,91 @@ msgstr "건너 뜀" msgid "CiStatus|running" msgstr "실행 중" -msgid "CircuitBreakerApiLink|circuitbreaker api" +msgid "CiVariables|Input variable key" msgstr "" -msgid "Clone repository" +msgid "CiVariables|Input variable value" msgstr "" -msgid "Close" +msgid "CiVariables|Remove variable row" msgstr "" -msgid "Cluster" +msgid "CiVariable|* (All environments)" msgstr "" -msgid "ClusterIntegration|%{appList} was successfully installed on your cluster" +msgid "CiVariable|All environments" msgstr "" -msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which incur additional costs. See %{pricingLink}" +msgid "CiVariable|Create wildcard" msgstr "" -msgid "ClusterIntegration|API URL" +msgid "CiVariable|Error occured while saving variables" msgstr "" -msgid "ClusterIntegration|Active" +msgid "CiVariable|New environment" msgstr "" -msgid "ClusterIntegration|Add an existing cluster" +msgid "CiVariable|Protected" msgstr "" -msgid "ClusterIntegration|Add cluster" +msgid "CiVariable|Search environments" msgstr "" -msgid "ClusterIntegration|All" +msgid "CiVariable|Toggle protected" msgstr "" -msgid "ClusterIntegration|Applications" +msgid "CiVariable|Validation failed" msgstr "" -msgid "ClusterIntegration|CA Certificate" +msgid "CircuitBreakerApiLink|circuitbreaker api" msgstr "" -msgid "ClusterIntegration|Certificate Authority bundle (PEM format)" +msgid "Click to expand text" +msgstr "" + +msgid "Clone repository" +msgstr "" + +msgid "Close" msgstr "" -msgid "ClusterIntegration|Choose how to set up cluster integration" +msgid "Closed" msgstr "" -msgid "ClusterIntegration|Cluster" +msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Cluster details" +msgid "ClusterIntegration|API URL" msgstr "" -msgid "ClusterIntegration|Cluster integration" +msgid "ClusterIntegration|Add Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Cluster integration is disabled for this project." +msgid "ClusterIntegration|Add an existing Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Cluster integration is enabled for this project." +msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration" msgstr "" -msgid "ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab's connection to it." +msgid "ClusterIntegration|Applications" msgstr "" -msgid "ClusterIntegration|Cluster is being created on Google Kubernetes Engine..." +msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster." msgstr "" -msgid "ClusterIntegration|Cluster name" +msgid "ClusterIntegration|CA Certificate" msgstr "" -msgid "ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine. Refresh the page to see cluster's details" +msgid "ClusterIntegration|Certificate Authority bundle (PEM format)" msgstr "" -msgid "ClusterIntegration|Clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" +msgid "ClusterIntegration|Choose how to set up Kubernetes cluster integration" +msgstr "" + +msgid "ClusterIntegration|Choose which of your project's environments will use this Kubernetes cluster." +msgstr "" + +msgid "ClusterIntegration|Control how your Kubernetes cluster integrates with GitLab" msgstr "" msgid "ClusterIntegration|Copy API URL" @@ -584,37 +713,34 @@ msgstr "" msgid "ClusterIntegration|Copy CA Certificate" msgstr "" -msgid "ClusterIntegration|Copy Token" +msgid "ClusterIntegration|Copy Kubernetes cluster name" msgstr "" -msgid "ClusterIntegration|Copy cluster name" +msgid "ClusterIntegration|Copy Token" msgstr "" -msgid "ClusterIntegration|Create a new cluster on Google Engine right from GitLab" +msgid "ClusterIntegration|Create Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Create cluster" +msgid "ClusterIntegration|Create Kubernetes cluster on Google Kubernetes Engine" msgstr "" -msgid "ClusterIntegration|Create cluster on Google Kubernetes Engine" +msgid "ClusterIntegration|Create a new Kubernetes cluster on Google Kubernetes Engine right from GitLab" msgstr "" msgid "ClusterIntegration|Create on GKE" msgstr "" -msgid "ClusterIntegration|Enable cluster integration" -msgstr "" - msgid "ClusterIntegration|Enter the details for an existing Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Enter the details for your cluster" +msgid "ClusterIntegration|Enter the details for your Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Environment pattern" +msgid "ClusterIntegration|Environment scope" msgstr "" -msgid "ClusterIntegration|GKE pricing" +msgid "ClusterIntegration|GitLab Integration" msgstr "" msgid "ClusterIntegration|GitLab Runner" @@ -632,64 +758,94 @@ msgstr "" msgid "ClusterIntegration|Helm Tiller" msgstr "" -msgid "ClusterIntegration|Inactive" -msgstr "" - msgid "ClusterIntegration|Ingress" msgstr "" msgid "ClusterIntegration|Install" msgstr "" -msgid "ClusterIntegration|Install applications on your cluster. Read more about %{helpLink}" -msgstr "" - msgid "ClusterIntegration|Installed" msgstr "" msgid "ClusterIntegration|Installing" msgstr "" -msgid "ClusterIntegration|Integrate cluster automation" +msgid "ClusterIntegration|Integrate Kubernetes cluster automation" +msgstr "" + +msgid "ClusterIntegration|Integration status" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster details" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is disabled for this project." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project. Disabling this integration will not affect your Kubernetes cluster, it will only temporarily turn off GitLab's connection to it." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster name" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project" msgstr "" msgid "ClusterIntegration|Learn more about %{link_to_documentation}" msgstr "" -msgid "ClusterIntegration|Learn more about Clusters" +msgid "ClusterIntegration|Learn more about Kubernetes" msgstr "" -msgid "ClusterIntegration|Machine type" +msgid "ClusterIntegration|Learn more about environments" msgstr "" -msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create clusters" +msgid "ClusterIntegration|Machine type" msgstr "" -msgid "ClusterIntegration|Manage cluster integration on your GitLab project" +msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters" msgstr "" -msgid "ClusterIntegration|Manage your cluster by visiting %{link_gke}" +msgid "ClusterIntegration|Manage" msgstr "" -msgid "ClusterIntegration|Multiple clusters are available in GitLab Entreprise Edition Premium and Ultimate" +msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}" msgstr "" -msgid "ClusterIntegration|Note:" +msgid "ClusterIntegration|More information" msgstr "" -msgid "ClusterIntegration|Number of nodes" +msgid "ClusterIntegration|Multiple Kubernetes clusters are available in GitLab Enterprise Edition Premium and Ultimate" msgstr "" -msgid "ClusterIntegration|Please enter access information for your cluster. If you need help, you can read our %{link_to_help_page} on clusters" +msgid "ClusterIntegration|Note:" msgstr "" -msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:" +msgid "ClusterIntegration|Number of nodes" msgstr "" -msgid "ClusterIntegration|Problem setting up the cluster" +msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes" msgstr "" -msgid "ClusterIntegration|Problem setting up the clusters list" +msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:" msgstr "" msgid "ClusterIntegration|Project ID" @@ -701,16 +857,19 @@ msgstr "" msgid "ClusterIntegration|Project namespace (optional, unique)" msgstr "" -msgid "ClusterIntegration|Read our %{link_to_help_page} on cluster integration." +msgid "ClusterIntegration|Prometheus" msgstr "" -msgid "ClusterIntegration|Remove cluster integration" +msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration." +msgstr "" + +msgid "ClusterIntegration|Remove Kubernetes cluster integration" msgstr "" msgid "ClusterIntegration|Remove integration" msgstr "" -msgid "ClusterIntegration|Removing cluster integration will remove the cluster configuration you have added to this project. It will not delete your cluster on Google Kubernetes Engine." +msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster." msgstr "" msgid "ClusterIntegration|Request to begin installing failed" @@ -719,7 +878,7 @@ msgstr "" msgid "ClusterIntegration|Save changes" msgstr "" -msgid "ClusterIntegration|See and edit the details for your cluster" +msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster" msgstr "" msgid "ClusterIntegration|See machine types" @@ -740,25 +899,25 @@ msgstr "" msgid "ClusterIntegration|Something went wrong on our end." msgstr "" -msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine" +msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine" msgstr "" msgid "ClusterIntegration|Something went wrong while installing %{title}" msgstr "" -msgid "ClusterIntegration|There are no clusters to show" +msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below" msgstr "" -msgid "ClusterIntegration|This account must have permissions to create a cluster in the %{link_to_container_project} specified below" +msgid "ClusterIntegration|Toggle Kubernetes Cluster" msgstr "" -msgid "ClusterIntegration|Toggle Cluster" +msgid "ClusterIntegration|Toggle Kubernetes cluster" msgstr "" msgid "ClusterIntegration|Token" msgstr "" -msgid "ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." +msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." msgstr "" msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}" @@ -770,7 +929,7 @@ msgstr "" msgid "ClusterIntegration|access to Google Kubernetes Engine" msgstr "" -msgid "ClusterIntegration|cluster" +msgid "ClusterIntegration|check the pricing here" msgstr "" msgid "ClusterIntegration|documentation" @@ -788,6 +947,9 @@ msgstr "" msgid "ClusterIntegration|properly configured" msgstr "" +msgid "Collapse" +msgstr "" + msgid "Comments" msgstr "" @@ -804,6 +966,9 @@ msgstr "최근 30 ê±´ì˜ ì»¤ë°‹ 소요시간 (분)" msgid "Commit message" msgstr "커밋 메시지" +msgid "Commit statistics for %{ref} %{start_time} - %{end_time}" +msgstr "" + msgid "CommitBoxTitle|Commit" msgstr "커밋" @@ -816,15 +981,57 @@ msgstr "커밋" msgid "Commits feed" msgstr "커밋 피드" +msgid "Commits per day hour (UTC)" +msgstr "" + +msgid "Commits per day of month" +msgstr "" + +msgid "Commits per weekday" +msgstr "" + +msgid "Commits|An error occurred while fetching merge requests data." +msgstr "" + +msgid "Commits|Commit: %{commitText}" +msgstr "" + msgid "Commits|History" msgstr "ì´ë ¥" +msgid "Commits|No related merge requests found" +msgstr "" + msgid "Committed by" msgstr "커밋한 사용ìž" msgid "Compare" msgstr "비êµ" +msgid "Compare Git revisions" +msgstr "" + +msgid "Compare Revisions" +msgstr "" + +msgid "CompareBranches|%{source_branch} and %{target_branch} are the same." +msgstr "" + +msgid "CompareBranches|Compare" +msgstr "" + +msgid "CompareBranches|Source" +msgstr "" + +msgid "CompareBranches|Target" +msgstr "" + +msgid "CompareBranches|There isn't anything to compare." +msgstr "" + +msgid "Confidentiality" +msgstr "" + msgid "Container Registry" msgstr "" @@ -876,6 +1083,9 @@ msgstr "ê¸°ì—¬ì— ëŒ€í•œ 안내" msgid "Contributors" msgstr "기여해 ì£¼ì‹ ë¶„ë“¤" +msgid "ContributorsPage|%{startDate} – %{endDate}" +msgstr "" + msgid "ContributorsPage|Building repository graph." msgstr "" @@ -897,9 +1107,18 @@ msgstr "" msgid "Copy URL to clipboard" msgstr "URLì„ í´ë¦½ë³´ë“œì— 복사" +msgid "Copy branch name to clipboard" +msgstr "" + msgid "Copy commit SHA to clipboard" msgstr "ì»¤ë°‹ì˜ SHA를 í´ë¦½ë³´ë“œë¡œ 복사합니다" +msgid "Copy reference to clipboard" +msgstr "" + +msgid "Create" +msgstr "" + msgid "Create New Directory" msgstr "새 ë””ë ‰í† ë¦¬ 만들기" @@ -918,6 +1137,9 @@ msgstr "" msgid "Create file" msgstr "" +msgid "Create lists from labels. Issues with that label appear in that list." +msgstr "" + msgid "Create merge request" msgstr "머지 리퀘스트 만들기" @@ -930,6 +1152,9 @@ msgstr "" msgid "Create new file" msgstr "" +msgid "Create new label" +msgstr "" + msgid "Create new..." msgstr "새로 만들기 ..." @@ -951,6 +1176,9 @@ msgstr "Cron 시간대" msgid "Cron syntax" msgstr "í¬ë¡ 구문" +msgid "Current node" +msgstr "" + msgid "Custom notification events" msgstr "ì‚¬ìš©ìž ì •ì˜ ì•Œë¦¼ ì´ë²¤íŠ¸" @@ -960,9 +1188,6 @@ msgstr "ì‚¬ìš©ìž ì •ì˜ ì•Œë¦¼ ìˆ˜ì¤€ì€ ì°¸ì—¬ 수준과 ë™ì¼í•©ë‹ˆë‹¤. 맞ì msgid "Cycle Analytics" msgstr "" -msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project." -msgstr "Cycle Analytics는 프로ì 트ì—ì„œ ì•„ì´ë””어를 프로ë•ì…˜ìœ¼ë¡œ 옮기는 ë° ê±¸ë¦¬ëŠ” ì‹œê°„ì„ ëŒ€ëžµì 으로 ë³´ì—¬ì¤ë‹ˆë‹¤." - msgid "CycleAnalyticsStage|Code" msgstr "코드" @@ -1018,12 +1243,21 @@ msgstr "" msgid "Details" msgstr "ìƒì„¸" +msgid "Diffs|No file name available" +msgstr "" + msgid "Directory name" msgstr "ë””ë ‰í† ë¦¬ ì´ë¦„" +msgid "Disable" +msgstr "" + msgid "Discard changes" msgstr "변경 ë‚´ìš© 취소" +msgid "Discover GitLab Geo." +msgstr "" + msgid "Dismiss Cycle Analytics introduction box" msgstr "" @@ -1060,15 +1294,24 @@ msgstr "Plain Diff" msgid "DownloadSource|Download" msgstr "다운로드" +msgid "Due date" +msgstr "" + msgid "Edit" msgstr "편집" msgid "Edit Pipeline Schedule %{id}" msgstr "파ì´í”„ë¼ì¸ 스케줄 편집 %{id}" +msgid "Edit files in the editor and commit changes here" +msgstr "" + msgid "Emails" msgstr "" +msgid "Enable" +msgstr "" + msgid "Environments|An error occurred while fetching the environments." msgstr "" @@ -1087,9 +1330,6 @@ msgstr "" msgid "Environments|Environments" msgstr "" -msgid "Environments|Environments are places where code gets deployed, such as staging or production." -msgstr "" - msgid "Environments|Job" msgstr "" @@ -1132,9 +1372,33 @@ msgstr "" msgid "Error creating epic" msgstr "" +msgid "Error fetching contributors data." +msgstr "" + +msgid "Error fetching labels." +msgstr "" + +msgid "Error fetching network graph." +msgstr "" + +msgid "Error fetching refs" +msgstr "" + +msgid "Error fetching usage ping data." +msgstr "" + msgid "Error occurred when toggling the notification subscription" msgstr "" +msgid "Error saving label update." +msgstr "" + +msgid "Error updating status for all todos." +msgstr "" + +msgid "Error updating todo status." +msgstr "" + msgid "EventFilterBy|Filter by all" msgstr "ëª¨ë“ ê°’ì„ ê¸°ì¤€ìœ¼ë¡œ í•„í„°" @@ -1162,6 +1426,9 @@ msgstr "매월 (1ì¼ ì˜¤ì „ 4ì‹œ)" msgid "Every week (Sundays at 4:00am)" msgstr "매주 (ì¼ìš”ì¼ ì˜¤ì „ 4ì‹œì—)" +msgid "Expand" +msgstr "" + msgid "Explore projects" msgstr "" @@ -1180,6 +1447,9 @@ msgstr "" msgid "February" msgstr "" +msgid "Fields on this page are now uneditable, you can configure" +msgstr "" + msgid "File name" msgstr "" @@ -1223,10 +1493,10 @@ msgstr "머지 리퀘스트 머지ì—ì„œ 프로ë•ì…˜ í™˜ê²½ì— ë°°í¬ê¹Œì§€" msgid "GPG Keys" msgstr "" -msgid "Geo Nodes" +msgid "Generate a default set of labels" msgstr "" -msgid "GeoNodeSyncStatus|Failed" +msgid "Geo Nodes" msgstr "" msgid "GeoNodeSyncStatus|Node is failing or broken." @@ -1235,16 +1505,100 @@ msgstr "" msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage." msgstr "" -msgid "GeoNodeSyncStatus|Out of sync" +msgid "GeoNodes|Database replication lag:" +msgstr "" + +msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?" +msgstr "" + +msgid "GeoNodes|Does not match the primary storage configuration" +msgstr "" + +msgid "GeoNodes|Failed" +msgstr "" + +msgid "GeoNodes|Full" +msgstr "" + +msgid "GeoNodes|GitLab version does not match the primary node version" +msgstr "" + +msgid "GeoNodes|GitLab version:" +msgstr "" + +msgid "GeoNodes|Health status:" +msgstr "" + +msgid "GeoNodes|Last event ID processed by cursor:" +msgstr "" + +msgid "GeoNodes|Last event ID seen from primary:" +msgstr "" + +msgid "GeoNodes|Loading nodes" +msgstr "" + +msgid "GeoNodes|Local Attachments:" +msgstr "" + +msgid "GeoNodes|Local LFS objects:" +msgstr "" + +msgid "GeoNodes|Local job artifacts:" +msgstr "" + +msgid "GeoNodes|New node" +msgstr "" + +msgid "GeoNodes|Out of sync" +msgstr "" + +msgid "GeoNodes|Replication slot WAL:" +msgstr "" + +msgid "GeoNodes|Replication slots:" +msgstr "" + +msgid "GeoNodes|Repositories:" +msgstr "" + +msgid "GeoNodes|Selective" +msgstr "" + +msgid "GeoNodes|Storage config:" +msgstr "" + +msgid "GeoNodes|Sync settings:" msgstr "" -msgid "GeoNodeSyncStatus|Synced" +msgid "GeoNodes|Synced" +msgstr "" + +msgid "GeoNodes|Unused slots" +msgstr "" + +msgid "GeoNodes|Used slots" +msgstr "" + +msgid "GeoNodes|Wikis:" +msgstr "" + +msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS." +msgstr "" + +msgid "Geo|All projects" msgstr "" msgid "Geo|File sync capacity" msgstr "" -msgid "Geo|Groups to replicate" +msgid "Geo|Groups to synchronize" +msgstr "" + +msgid "Geo|Projects in certain groups" +msgstr "" + +msgid "Geo|Projects in certain storage shards" msgstr "" msgid "Geo|Repository sync capacity" @@ -1253,12 +1607,24 @@ msgstr "" msgid "Geo|Select groups to replicate." msgstr "" +msgid "Geo|Shards to synchronize" +msgstr "" + +msgid "Git revision" +msgstr "" + msgid "Git storage health information has been reset" msgstr "git storage ìƒíƒœ ì •ë³´ê°€ 초기화ë˜ì—ˆìŠµë‹ˆë‹¤." +msgid "Git version" +msgstr "" + msgid "GitLab Runner section" msgstr "GitLab Runner 섹션" +msgid "Gitaly Servers" +msgstr "" + msgid "Go to your fork" msgstr "ë‹¹ì‹ ì˜ í¬í¬ë¡œ ì´ë™í•˜ì„¸ìš”" @@ -1268,6 +1634,9 @@ msgstr "í¬í¬" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it!" +msgstr "" + msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" msgstr "" @@ -1304,7 +1673,7 @@ msgstr "" msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group." msgstr "" -msgid "GroupsTree|Are you sure you want to leave the \"${this.group.fullName}\" group?" +msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?" msgstr "" msgid "GroupsTree|Create a project in this group." @@ -1355,6 +1724,10 @@ msgstr " 헬스 ë¬¸ì œê°€ 발견ë˜ì§€ 않았습니다." msgid "HealthCheck|Unhealthy" msgstr "ë¹„ì •ìƒ" +msgid "Hide value" +msgid_plural "Hide values" +msgstr[0] "" + msgid "History" msgstr "" @@ -1380,6 +1753,12 @@ msgid "Instance" msgid_plural "Instances" msgstr[0] "" +msgid "Instance does not support multiple Kubernetes clusters" +msgstr "" + +msgid "Interested parties can even contribute by pushing commits if they want to." +msgstr "" + msgid "Internal - The group and any internal projects can be viewed by any logged in user." msgstr "" @@ -1407,6 +1786,9 @@ msgstr "" msgid "Issues" msgstr "" +msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable." +msgstr "" + msgid "Jan" msgstr "" @@ -1425,6 +1807,27 @@ msgstr "" msgid "June" msgstr "" +msgid "Kubernetes" +msgstr "" + +msgid "Kubernetes Cluster" +msgstr "" + +msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}" +msgstr "" + +msgid "Kubernetes cluster integration was not removed." +msgstr "" + +msgid "Kubernetes cluster integration was successfully removed." +msgstr "" + +msgid "Kubernetes cluster was successfully updated." +msgstr "" + +msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page" +msgstr "" + msgid "LFSStatus|Disabled" msgstr "Disabled" @@ -1434,6 +1837,9 @@ msgstr "Enabled" msgid "Labels" msgstr "" +msgid "Labels can be applied to issues and merge requests to categorize them." +msgstr "" + msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "최근 %d ì¼" @@ -1462,6 +1868,9 @@ msgstr "푸쉬: " msgid "LastPushEvent|at" msgstr "at" +msgid "Learn more" +msgstr "" + msgid "Learn more in the" msgstr "ë” ìžì„¸ížˆ 알아보기" @@ -1480,13 +1889,18 @@ msgstr "프로ì 트ì—ì„œ 나가기" msgid "License" msgstr "" -msgid "Limited to showing %d event at most" -msgid_plural "Limited to showing %d events at most" -msgstr[0] "최대 %d ì´ë²¤íŠ¸ 만 표시하는 것으로 ì œí•œë©ë‹ˆë‹¤." +msgid "Loading the GitLab IDE..." +msgstr "" msgid "Lock" msgstr "" +msgid "Lock %{issuableDisplayName}" +msgstr "" + +msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment." +msgstr "" + msgid "Locked" msgstr "" @@ -1496,12 +1910,21 @@ msgstr "" msgid "Login" msgstr "" +msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos." +msgstr "" + +msgid "Manage labels" +msgstr "" + msgid "Mar" msgstr "" msgid "March" msgstr "" +msgid "Mark done" +msgstr "" + msgid "Maximum git storage failures" msgstr "" @@ -1514,6 +1937,9 @@ msgstr "중앙값" msgid "Members" msgstr "" +msgid "Merge Request" +msgstr "" + msgid "Merge Requests" msgstr "" @@ -1523,9 +1949,30 @@ msgstr "머지 ì´ë²¤íŠ¸" msgid "Merge request" msgstr "" +msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others" +msgstr "" + +msgid "Merged" +msgstr "" + msgid "Messages" msgstr "" +msgid "Milestone" +msgstr "" + +msgid "Milestones|Delete milestone" +msgstr "" + +msgid "Milestones|Delete milestone %{milestoneTitle}?" +msgstr "" + +msgid "Milestones|Failed to delete milestone %{milestoneTitle}" +msgstr "" + +msgid "Milestones|Milestone %{milestoneTitle} was not found" +msgstr "" + msgid "MissingSSHKeyWarningLink|add an SSH key" msgstr "SSH 키 추가" @@ -1535,16 +1982,28 @@ msgstr "" msgid "More information is available|here" msgstr "여기" +msgid "Move" +msgstr "" + +msgid "Move issue" +msgstr "" + msgid "Multiple issue boards" msgstr "" -msgid "New Cluster" +msgid "Name new label" msgstr "" msgid "New Issue" msgid_plural "New Issues" msgstr[0] "새 ì´ìŠˆ" +msgid "New Kubernetes Cluster" +msgstr "" + +msgid "New Kubernetes cluster" +msgstr "" + msgid "New Pipeline Schedule" msgstr "새로운 파ì´í”„ë¼ì¸ ì¼ì •" @@ -1569,6 +2028,9 @@ msgstr "" msgid "New issue" msgstr "새 ì´ìŠˆ" +msgid "New label" +msgstr "" + msgid "New merge request" msgstr "새 머지 리퀘스트" @@ -1587,7 +2049,22 @@ msgstr "" msgid "New tag" msgstr "새 태그 " -msgid "No container images stored for this project. Add one by following the instructions above." +msgid "No assignee" +msgstr "" + +msgid "No changes" +msgstr "" + +msgid "No connection could be made to a Gitaly Server, please check your logs!" +msgstr "" + +msgid "No due date" +msgstr "" + +msgid "No estimate or time spent" +msgstr "" + +msgid "No file chosen" msgstr "" msgid "No repository" @@ -1602,9 +2079,15 @@ msgstr "" msgid "None" msgstr "" +msgid "Not allowed to merge" +msgstr "" + msgid "Not available" msgstr "ì‚¬ìš©í• ìˆ˜ ì—†ìŒ" +msgid "Not confidential" +msgstr "" + msgid "Not enough data" msgstr "ë°ì´í„°ê°€ 충분하지 않습니다." @@ -1665,6 +2148,12 @@ msgstr "Watch" msgid "Notifications" msgstr "" +msgid "Notifications off" +msgstr "" + +msgid "Notifications on" +msgstr "" + msgid "Nov" msgstr "" @@ -1674,7 +2163,7 @@ msgstr "" msgid "Number of access attempts" msgstr "" -msgid "Number of failures before backing off" +msgid "OK" msgstr "" msgid "Oct" @@ -1689,6 +2178,9 @@ msgstr "í•„í„°" msgid "Only project members can comment." msgstr "" +msgid "Open" +msgstr "" + msgid "Opened" msgstr "" @@ -1722,9 +2214,6 @@ msgstr "" msgid "Password" msgstr "" -msgid "People without permission will never get a notification and won\\'t be able to comment." -msgstr "" - msgid "Pipeline" msgstr "파ì´í”„ë¼ì¸" @@ -1767,12 +2256,6 @@ msgstr "모ë‘" msgid "PipelineSchedules|Inactive" msgstr "비활성" -msgid "PipelineSchedules|Input variable key" -msgstr "ìž…ë ¥ 변수 키" - -msgid "PipelineSchedules|Input variable value" -msgstr "ìž…ë ¥ 변수 ê°’" - msgid "PipelineSchedules|Next Run" msgstr "ë‹¤ìŒ ì‹¤í–‰" @@ -1782,9 +2265,6 @@ msgstr "ì—†ìŒ" msgid "PipelineSchedules|Provide a short description for this pipeline" msgstr "ì´ íŒŒì´í”„ë¼ì¸ì— 대한 간단한 설명 ì œê³µ" -msgid "PipelineSchedules|Remove variable row" -msgstr "변수 í–‰ ì œê±°" - msgid "PipelineSchedules|Take ownership" msgstr "ì†Œìœ ê¶Œ ê°€ì ¸ 오기" @@ -1812,6 +2292,12 @@ msgstr "" msgid "Pipelines for last year" msgstr "" +msgid "Pipelines|Build with confidence" +msgstr "" + +msgid "Pipelines|Get started with Pipelines" +msgstr "" + msgid "Pipeline|all" msgstr "모ë‘" @@ -1824,12 +2310,21 @@ msgstr "스테ì´ì§•" msgid "Pipeline|with stages" msgstr "스테ì´ì§•" +msgid "Play" +msgstr "" + +msgid "Please <a href=%{link_to_billing} target=\"_blank\" rel=\"noopener noreferrer\">enable billing for one of your projects to be able to create a Kubernetes cluster</a>, then try again." +msgstr "" + msgid "Please solve the reCAPTCHA" msgstr "" msgid "Preferences" msgstr "" +msgid "Primary" +msgstr "" + msgid "Private - Project access must be granted explicitly to each user." msgstr "" @@ -1875,6 +2370,9 @@ msgstr "" msgid "Profiles|your account" msgstr "" +msgid "Programming languages used in this repository" +msgstr "" + msgid "Project '%{project_name}' is in the process of being deleted." msgstr "" @@ -1890,6 +2388,15 @@ msgstr "'%{project_name}'프로ì 트가 성공ì 으로 ì—…ë°ì´íŠ¸ë˜ì—ˆìŠµë‹ msgid "Project access must be granted explicitly to each user." msgstr "프로ì 트 액세스는 ê° ì‚¬ìš©ìžì—게 명시ì 으로 부여ë˜ì–´ì•¼í•©ë‹ˆë‹¤." +msgid "Project avatar" +msgstr "" + +msgid "Project avatar in repository: %{link}" +msgstr "" + +msgid "Project cache successfully reset." +msgstr "" + msgid "Project details" msgstr "프로ì 트 ìƒì„¸" @@ -1908,6 +2415,21 @@ msgstr "프로ì 트 내보내기가 시작ë˜ì—ˆìŠµë‹ˆë‹¤. 다운로드 ë§í¬ë msgid "ProjectActivityRSS|Subscribe" msgstr "구ë…" +msgid "ProjectCreationLevel|Allowed to create projects" +msgstr "" + +msgid "ProjectCreationLevel|Default project creation protection" +msgstr "" + +msgid "ProjectCreationLevel|Developers + Masters" +msgstr "" + +msgid "ProjectCreationLevel|Masters" +msgstr "" + +msgid "ProjectCreationLevel|No one" +msgstr "" + msgid "ProjectFeature|Disabled" msgstr "사용 안 함" @@ -1932,15 +2454,9 @@ msgstr "그래프" msgid "ProjectSettings|Contact an admin to change this setting." msgstr "" -msgid "ProjectSettings|Immediately run a pipeline on the default branch" -msgstr "" - msgid "ProjectSettings|Only signed commits can be pushed to this repository." msgstr "" -msgid "ProjectSettings|Problem setting up the CI/CD settings JavaScript" -msgstr "" - msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin." msgstr "" @@ -2004,12 +2520,15 @@ msgstr "" msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/" msgstr "" -msgid "PrometheusService|Prometheus monitoring" +msgid "PrometheusService|Time-series monitoring service" msgstr "" msgid "PrometheusService|View environments" msgstr "" +msgid "Protip:" +msgstr "" + msgid "Public - The group and any public projects can be viewed without any authentication." msgstr "" @@ -2025,6 +2544,9 @@ msgstr "푸쉬 ì´ë²¤íŠ¸" msgid "PushRule|Committer restriction" msgstr "" +msgid "Quick actions can be used in the issues description and comment boxes." +msgstr "" + msgid "Read more" msgstr "ë” ì½ê¸°" @@ -2037,6 +2559,12 @@ msgstr "브랜치" msgid "RefSwitcher|Tags" msgstr "태그" +msgid "Reference:" +msgstr "" + +msgid "Register / Sign In" +msgstr "" + msgid "Registry" msgstr "" @@ -2061,9 +2589,18 @@ msgstr "ê´€ë ¨ 머지 리퀘스트" msgid "Remind later" msgstr "ë‚˜ì¤‘ì— ë‹¤ì‹œ 알림" +msgid "Remove" +msgstr "" + +msgid "Remove avatar" +msgstr "" + msgid "Remove project" msgstr "프로ì 트 ì‚ì œ" +msgid "Repair authentication" +msgstr "" + msgid "Repository" msgstr "" @@ -2079,6 +2616,10 @@ msgstr "헬스 ì²´í¬ ì ‘ê·¼ í† í° ì´ˆê¸°í™”" msgid "Reset runners registration token" msgstr "runner ë“±ë¡ í† í° ì´ˆê¸°í™”" +msgid "Reveal value" +msgid_plural "Reveal values" +msgstr[0] "" + msgid "Revert this commit" msgstr "ì´ ì»¤ë°‹ ë˜ëŒë¦¬ê¸°" @@ -2088,15 +2629,15 @@ msgstr "ì´ ë¨¸ì§€ 리퀘스트 ë˜ëŒë¦¬ê¸°" msgid "SSH Keys" msgstr "" -msgid "Save" -msgstr "" - msgid "Save changes" msgstr "" msgid "Save pipeline schedule" msgstr "파ì´í”„ë¼ì¸ 스케줄 ì €ìž¥" +msgid "Save variables" +msgstr "" + msgid "Schedule a new pipeline" msgstr "새로운 파ì´í”„ë¼ì¸ 스케줄 잡기" @@ -2112,38 +2653,59 @@ msgstr "" msgid "Search branches and tags" msgstr "브랜치 ë° íƒœê·¸ 검색" -msgid "Seconds before reseting failure information" +msgid "Search milestones" msgstr "" -msgid "Seconds to wait after a storage failure" +msgid "Search project" +msgstr "" + +msgid "Search users" +msgstr "" + +msgid "Seconds before reseting failure information" msgstr "" msgid "Seconds to wait for a storage access attempt" msgstr "" +msgid "Secret variables" +msgstr "" + msgid "Select Archive Format" msgstr "ì•„ì¹´ì´ë¸Œ í¬ë§· ì„ íƒ" msgid "Select a timezone" msgstr "시간대 ì„ íƒ" +msgid "Select assignee" +msgstr "" + +msgid "Select branch/tag" +msgstr "" + msgid "Select target branch" msgstr "ëŒ€ìƒ ë¸Œëžœì¹˜ ì„ íƒ" +msgid "Selective synchronization" +msgstr "" + msgid "Sep" msgstr "" msgid "September" msgstr "" +msgid "Server version" +msgstr "" + msgid "Service Templates" msgstr "" msgid "Set a password on your account to pull or push via %{protocol}." msgstr "%{protocol} í”„ë¡œí† ì½œì„ í†µí•´ Pull 하거나 Pushí•˜ë ¤ë©´ ê³„ì •ì— íŒ¨ìŠ¤ì›Œë“œë¥¼ ì„¤ì •í•˜ì‹ì‹œì˜¤." -msgid "Set up CI" -msgstr "CI ì„¤ì •" +msgid "Set up CI/CD" +msgstr "" msgid "Set up Koding" msgstr "Koding ì„¤ì •" @@ -2157,6 +2719,15 @@ msgstr "패스워드 ì„¤ì •" msgid "Settings" msgstr "" +msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero." +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset pipeline minutes" +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes" +msgstr "" + msgid "Show parent pages" msgstr "" @@ -2170,9 +2741,6 @@ msgstr[0] "%d ê°œì˜ ì´ë²¤íŠ¸ 표시 중" msgid "Sidebar|Change weight" msgstr "" -msgid "Sidebar|Edit" -msgstr "" - msgid "Sidebar|No" msgstr "" @@ -2185,18 +2753,30 @@ msgstr "" msgid "Snippets" msgstr "" +msgid "Something went wrong on our end" +msgstr "" + msgid "Something went wrong on our end." msgstr "" +msgid "Something went wrong trying to change the confidentiality of this issue" +msgstr "" + msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}" msgstr "" +msgid "Something went wrong when toggling the button" +msgstr "" + msgid "Something went wrong while fetching the projects." msgstr "" msgid "Something went wrong while fetching the registry list." msgstr "" +msgid "Something went wrong. Please try again." +msgstr "" + msgid "Sort by" msgstr "" @@ -2326,10 +2906,10 @@ msgstr "Runner 시작!" msgid "Stopped" msgstr "" -msgid "Subgroups" +msgid "Storage" msgstr "" -msgid "Subscribe" +msgid "Subgroups" msgstr "" msgid "Switch branch/tag" @@ -2426,7 +3006,10 @@ msgstr "" msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project." msgstr "" -msgid "The circuitbreaker backoff threshold should be lower than the failure count threshold" +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project" +msgstr "" + +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project." msgstr "" msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request." @@ -2441,10 +3024,10 @@ msgstr "í¬í¬ 관계가 ì œê±°ë˜ì—ˆìŠµë‹ˆë‹¤." msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage." msgstr "ì´ìŠˆ 단계ì—는 ì´ìŠˆë¥¼ 작성하여 마ì¼ìŠ¤í†¤ìœ¼ë¡œ ì§€ì •í•˜ëŠ” ë° ê±¸ë¦¬ëŠ” 시간 ë˜ëŠ” ì´ìŠˆ ë³´ë“œì˜ ëª©ë¡ì— ì´ìŠˆë¥¼ 추가하는 ì‹œê°„ì´ í‘œì‹œë©ë‹ˆë‹¤. ì´ ë‹¨ê³„ì˜ ë°ì´í„°ë¥¼ 보기 위해서는 ì´ìŠˆë¥¼ ë¨¼ì € 작성해야 합니다." -msgid "The number of attempts GitLab will make to access a storage." +msgid "The maximum file size allowed is 200KB." msgstr "" -msgid "The number of failures after which GitLab will start temporarily disabling access to a storage shard on a host" +msgid "The number of attempts GitLab will make to access a storage." msgstr "" msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}." @@ -2453,9 +3036,6 @@ msgstr "" msgid "The phase of the development lifecycle." msgstr "개발 ìˆ˜ëª…ì£¼ê¸°ì˜ ë‹¨ê³„." -msgid "The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user." -msgstr "파ì´í”„ë¼ì¸ ì¼ì •ì€ ë¯¸ëž˜ì— íŠ¹ì • 브랜치 ë˜ëŠ” íƒœê·¸ì— ëŒ€í•´ 반복ì 으로 파ì´í”„ë¼ì¸ì„ 실행합니다. ì˜ˆì •ëœ íŒŒì´í”„ë¼ì¸ì€ ê´€ë ¨ 사용ìžë¥¼ 기반으로 ì œí•œëœ í”„ë¡œì 트 액세스 ê¶Œí•œì„ ìƒì†ë°›ìŠµë‹ˆë‹¤." - msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit." msgstr "ê³„íš ë‹¨ê³„ì—서는 ì´ì „ 단계ì—ì„œ 첫 번째 커밋 ì‹œê°„ì´ í‘œì‹œë©ë‹ˆë‹¤. ì´ ì‹œê°„ì€ ì²« 번째 ì»¤ë°‹ì„ ëˆ„ë¥´ë©´ ìžë™ìœ¼ë¡œ 추가ë©ë‹ˆë‹¤." @@ -2486,19 +3066,46 @@ msgstr "" msgid "The time in seconds GitLab will try to access storage. After this time a timeout error will be raised." msgstr "" +msgid "The time in seconds between storage checks. When a previous check did complete yet, GitLab will skip a check." +msgstr "" + msgid "The time taken by each data entry gathered by that stage." msgstr "해당 단계ì—ì„œ 수집 í•œ ê° ë°ì´í„° ìž…ë ¥ì— ì†Œìš” ëœ ì‹œê°„" msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6." msgstr "ê°’ì€ ì¼ë ¨ì˜ 관측 ê°’ 중ì ì— ìžˆìŠµë‹ˆë‹¤. 예를 들어, 3, 5, 9 사ì´ì˜ 중간 ê°’ì€ 5입니다. 3, 5, 7, 8 사ì´ì˜ 중간 ê°’ì€ (5 + 7) / 2 = 6입니다." +msgid "There are no issues to show" +msgstr "" + +msgid "There are no merge requests to show" +msgstr "" + msgid "There are problems accessing Git storage: " msgstr "git storageì— ì ‘ê·¼í•˜ëŠ”ë° ë¬¸ì œê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤. " +msgid "There was an error loading users activity calendar." +msgstr "" + +msgid "There was an error saving your notification settings." +msgstr "" + +msgid "There was an error subscribing to this label." +msgstr "" + +msgid "There was an error when reseting email token." +msgstr "" + +msgid "There was an error when subscribing to this label." +msgstr "" + +msgid "There was an error when unsubscribing from this label." +msgstr "" + msgid "This board\\'s scope is reduced" msgstr "" -msgid "This branch has changed since you started editing. Would you like to create a new branch?" +msgid "This directory" msgstr "" msgid "This is a confidential issue." @@ -2507,18 +3114,45 @@ msgstr "" msgid "This is the author's first Merge Request to this project." msgstr "" +msgid "This issue is confidential" +msgstr "" + msgid "This issue is confidential and locked." msgstr "" msgid "This issue is locked." msgstr "" +msgid "This job depends on a user to trigger its process. Often they are used to deploy code to production environments" +msgstr "" + +msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered" +msgstr "" + +msgid "This job has not been triggered yet" +msgstr "" + +msgid "This job has not started yet" +msgstr "" + +msgid "This job is in pending state and is waiting to be picked by a runner" +msgstr "" + +msgid "This job requires a manual action" +msgstr "" + msgid "This means you can not push code until you create an empty repository or import existing one." msgstr "즉, 빈 ì €ìž¥ì†Œë¥¼ 만들거나 기존 ì €ìž¥ì†Œë¥¼ ê°€ì ¸ì˜¬ 때까지 코드를 Push í• ìˆ˜ 없습니다." msgid "This merge request is locked." msgstr "" +msgid "This project" +msgstr "" + +msgid "This repository" +msgstr "" + msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here." msgstr "" @@ -2531,9 +3165,21 @@ msgstr "ì´ìŠˆê°€ 구현ë˜ê¸° ì „ì˜ ì‹œê°„" msgid "Time between merge request creation and merge/close" msgstr "머지 리퀘스트 ìƒì„±ê³¼ 머지 / 닫기 사ì´ì˜ 시간" +msgid "Time tracking" +msgstr "" + msgid "Time until first merge request" msgstr "첫 번째 머지 ë¦¬í€˜ìŠ¤íŠ¸ê¹Œì§€ì˜ ì‹œê°„" +msgid "TimeTrackingEstimated|Est" +msgstr "" + +msgid "TimeTracking|Estimated:" +msgstr "" + +msgid "TimeTracking|Spent" +msgstr "" + msgid "Timeago|%s days ago" msgstr "%s ì¼ ì „" @@ -2671,6 +3317,18 @@ msgstr "ì´ˆ" msgid "Title" msgstr "" +msgid "Todo" +msgstr "" + +msgid "Toggle sidebar" +msgstr "" + +msgid "ToggleButton|Toggle Status: OFF" +msgstr "" + +msgid "ToggleButton|Toggle Status: ON" +msgstr "" + msgid "Total Time" msgstr "시간 합계:" @@ -2686,19 +3344,40 @@ msgstr "" msgid "Track groups of issues that share a theme, across projects and milestones" msgstr "" +msgid "Total: %{total}" +msgstr "" + +msgid "Track time with quick actions" +msgstr "" + +msgid "Trigger this manual action" +msgstr "" + msgid "Turn on Service Desk" msgstr "" +msgid "Type %{value} to confirm:" +msgstr "" + +msgid "Unable to reset project cache." +msgstr "" + +msgid "Unknown" +msgstr "" + msgid "Unlock" msgstr "" +msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment." +msgstr "" + msgid "Unlocked" msgstr "" msgid "Unstar" msgstr "별표 ì œê±°" -msgid "Unsubscribe" +msgid "Up to date" msgstr "" msgid "Upgrade your plan to activate Advanced Global Search." @@ -2722,6 +3401,9 @@ msgstr "새 íŒŒì¼ ì—…ë¡œë“œ" msgid "Upload file" msgstr "íŒŒì¼ ì—…ë¡œë“œ" +msgid "Upload new avatar" +msgstr "" + msgid "UploadLink|click to upload" msgstr "ì—…ë¡œë“œí•˜ë ¤ë©´ í´ë¦í•˜ì‹ì‹œì˜¤." @@ -2734,9 +3416,15 @@ msgstr "ì„¤ì • ì¤‘ì— ë‹¤ìŒ ë“±ë¡ í† í° ì´ìš© : " msgid "Use your global notification setting" msgstr "ì „ì²´ 알림 ì„¤ì • 사용" +msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." +msgstr "" + msgid "View file @ " msgstr "" +msgid "View labels" +msgstr "" + msgid "View open merge request" msgstr "열린 머지 리퀘스트보기" @@ -2758,6 +3446,9 @@ msgstr "ì•Œ 수 ì—†ìŒ" msgid "Want to see the data? Please ask an administrator for access." msgstr "ì´ ë°ì´í„°ë¥¼ ë³´ê³ ì‹¶ì€ê°€ìš”? 관리ìžì—게 액세스 ê¶Œí•œì„ ìš”ì²í•˜ì„¸ìš”." +msgid "We could not verify that one of your projects on GCP has billing enabled. Please try again." +msgstr "" + msgid "We don't have enough data to show this stage." msgstr "ì´ ë‹¨ê³„ë¥¼ ë³´ì—¬ì£¼ê¸°ì— ì¶©ë¶„í•œ ë°ì´í„°ê°€ 없습니다." @@ -2770,9 +3461,6 @@ msgstr "" msgid "Weight" msgstr "" -msgid "When access to a storage fails. GitLab will prevent access to the storage for the time specified here. This allows the filesystem to recover. Repositories on failing shards are temporarly unavailable" -msgstr "" - msgid "Wiki" msgstr "" @@ -2791,6 +3479,12 @@ msgstr "" msgid "WikiClone|Start Gollum and edit locally" msgstr "" +msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title." +msgstr "" + +msgid "WikiEdit|There is already a page with the same title in that path." +msgstr "" + msgid "WikiEmptyPageError|You are not allowed to create wiki pages" msgstr "" @@ -2893,9 +3587,21 @@ msgstr "í¬í¬ 관계를 소스 프로ì 트 %{forked_from_project}ì— ëŒ€í•´ ì msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" msgstr "%{project_name_with_namespace}ì„ ë‹¤ë¥¸ ì†Œìœ ìžì—게 ì´ì „í•˜ë ¤ê³ í•©ë‹ˆë‹¤. \"ì •ë§ë¡œ\" 확실합니까?" +msgid "You can also star a label to make it a priority label." +msgstr "" + +msgid "You can move around the graph by using the arrow keys." +msgstr "" + +msgid "You can move around the graph by using the arrow keys." +msgstr "" + msgid "You can only add files when you are on a branch" msgstr "ë¸Œëžœì¹˜ì— ìžˆì„ ë•Œì—만 파ì¼ì„ 추가 í• ìˆ˜ 있습니다." +msgid "You can only edit files when you are on a branch" +msgstr "" + msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead." msgstr "" @@ -2935,6 +3641,12 @@ msgstr "ë‹¹ì‹ ì˜ í”„ë¡œí•„ì— %{add_ssh_key_link} 를 하기 ì „ì—는 SSH를 í msgid "You won't be able to pull or push project code via SSH until you add an SSH key to your profile" msgstr "" +msgid "You'll need to use different branch names to get a valid comparison." +msgstr "" + +msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure" +msgstr "" + msgid "Your comment will not be visible to the public." msgstr "" @@ -2947,25 +3659,218 @@ msgstr "ê·€í•˜ì˜ ì´ë¦„" msgid "Your projects" msgstr "" +msgid "assign yourself" +msgstr "" + msgid "branch name" msgstr "" msgid "by" msgstr "" +msgid "ciReport|Code quality" +msgstr "" + +msgid "ciReport|DAST detected no alerts by analyzing the review app" +msgstr "" + +msgid "ciReport|Failed to load ${type} report" +msgstr "" + +msgid "ciReport|Fixed:" +msgstr "" + +msgid "ciReport|Instances" +msgstr "" + +msgid "ciReport|Learn more about whitelisting" +msgstr "" + +msgid "ciReport|Loading ${type} report" +msgstr "" + +msgid "ciReport|No changes to code quality" +msgstr "" + +msgid "ciReport|No changes to performance metrics" +msgstr "" + +msgid "ciReport|Performance metrics" +msgstr "" + +msgid "ciReport|SAST" +msgstr "" + +msgid "ciReport|SAST detected no security vulnerabilities" +msgstr "" + +msgid "ciReport|SAST:container no vulnerabilities were found" +msgstr "" + +msgid "ciReport|Show complete code vulnerabilities report" +msgstr "" + +msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}" +msgstr "" + msgid "commit" msgstr "" +msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue." +msgstr "" + +msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue." +msgstr "" + msgid "day" msgid_plural "days" msgstr[0] "ì¼" +msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command." +msgstr "" + +msgid "merge request" +msgid_plural "merge requests" +msgstr[0] "" + +msgid "mrWidget|Cancel automatic merge" +msgstr "" + +msgid "mrWidget|Check out branch" +msgstr "" + +msgid "mrWidget|Checking ability to merge automatically" +msgstr "" + +msgid "mrWidget|Cherry-pick" +msgstr "" + +msgid "mrWidget|Cherry-pick this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Closed" +msgstr "" + +msgid "mrWidget|Closed by" +msgstr "" + +msgid "mrWidget|Closes" +msgstr "" + +msgid "mrWidget|Did not close" +msgstr "" + +msgid "mrWidget|Email patches" +msgstr "" + +msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|Mentions" +msgstr "" + +msgid "mrWidget|Merge" +msgstr "" + +msgid "mrWidget|Merge failed." +msgstr "" + +msgid "mrWidget|Merge locally" +msgstr "" + +msgid "mrWidget|Merged by" +msgstr "" + +msgid "mrWidget|Plain diff" +msgstr "" + +msgid "mrWidget|Refresh" +msgstr "" + +msgid "mrWidget|Refresh now" +msgstr "" + +msgid "mrWidget|Refreshing now" +msgstr "" + +msgid "mrWidget|Remove Source Branch" +msgstr "" + +msgid "mrWidget|Remove source branch" +msgstr "" + +msgid "mrWidget|Request to merge" +msgstr "" + +msgid "mrWidget|Resolve conflicts" +msgstr "" + +msgid "mrWidget|Revert" +msgstr "" + +msgid "mrWidget|Revert this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Set by" +msgstr "" + +msgid "mrWidget|The changes were merged into" +msgstr "" + +msgid "mrWidget|The changes were not merged into" +msgstr "" + +msgid "mrWidget|The changes will be merged into" +msgstr "" + +msgid "mrWidget|The source branch has been removed" +msgstr "" + +msgid "mrWidget|The source branch is being removed" +msgstr "" + +msgid "mrWidget|The source branch will be removed" +msgstr "" + +msgid "mrWidget|The source branch will not be removed" +msgstr "" + +msgid "mrWidget|There are merge conflicts" +msgstr "" + +msgid "mrWidget|This merge request failed to be merged automatically" +msgstr "" + +msgid "mrWidget|This merge request is in the process of being merged" +msgstr "" + +msgid "mrWidget|This project is archived, write access has been disabled" +msgstr "" + +msgid "mrWidget|You can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|You can remove source branch now" +msgstr "" + +msgid "mrWidget|command line" +msgstr "" + +msgid "mrWidget|into" +msgstr "" + +msgid "mrWidget|to be merged automatically when the pipeline succeeds" +msgstr "" + msgid "new merge request" msgstr "새 머지 리퀘스트" msgid "notification emails" msgstr "알림 ì´ë©”ì¼" +msgid "or" +msgstr "" + msgid "parent" msgid_plural "parents" msgstr[0] "부모" @@ -2976,12 +3881,21 @@ msgstr "" msgid "personal access token" msgstr "" +msgid "remove due date" +msgstr "" + msgid "source" msgstr "" +msgid "spendCommand|%{slash_command} will update the sum of the time spent." +msgstr "" + msgid "to help your contributors communicate effectively!" msgstr "" msgid "username" msgstr "" +msgid "uses Kubernetes clusters to deploy your code!" +msgstr "" + diff --git a/locale/nl_NL/gitlab.po b/locale/nl_NL/gitlab.po index 0abb727037c..451be6434db 100644 --- a/locale/nl_NL/gitlab.po +++ b/locale/nl_NL/gitlab.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab-ee\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-12-12 18:31+0000\n" -"PO-Revision-Date: 2018-01-05 04:42-0500\n" +"POT-Creation-Date: 2018-02-07 11:38-0600\n" +"PO-Revision-Date: 2018-02-12 03:59-0500\n" "Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n" "Language-Team: Dutch\n" "Language: nl_NL\n" @@ -16,22 +16,40 @@ msgstr "" "X-Crowdin-Language: nl\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" +msgid " and" +msgstr "" + msgid "%d commit" msgid_plural "%d commits" msgstr[0] "%d commit" msgstr[1] "%d commits" +msgid "%d commit behind" +msgid_plural "%d commits behind" +msgstr[0] "" +msgstr[1] "" + +msgid "%d issue" +msgid_plural "%d issues" +msgstr[0] "" +msgstr[1] "" + msgid "%d layer" msgid_plural "%d layers" msgstr[0] "" msgstr[1] "" +msgid "%d merge request" +msgid_plural "%d merge requests" +msgstr[0] "" +msgstr[1] "" + msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "%s additional commits have been omitted to prevent performance issues." msgstr[0] "%s andere commit is weggelaten om prestatieproblemen te voorkomen." msgstr[1] "%s andere commits zijn weggelaten om prestatieproblemen te voorkomen." -msgid "%{commit_author_link} committed %{commit_timeago}" +msgid "%{commit_author_link} authored %{commit_timeago}" msgstr "" msgid "%{count} participant" @@ -45,9 +63,6 @@ msgstr "" msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will allow access on the next attempt." msgstr "" -msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will block access for %{number_of_seconds} seconds." -msgstr "" - msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved." msgstr "" @@ -121,24 +136,81 @@ msgstr "Licentie toevoegen" msgid "Add new directory" msgstr "Nieuwe map toevoegen" +msgid "Add todo" +msgstr "" + +msgid "AdminArea|Stop all jobs" +msgstr "" + +msgid "AdminArea|Stop all jobs?" +msgstr "" + +msgid "AdminArea|Stop jobs" +msgstr "" + +msgid "AdminArea|Stopping jobs failed" +msgstr "" + +msgid "AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running." +msgstr "" + msgid "AdminHealthPageLink|health page" msgstr "" +msgid "Advanced" +msgstr "" + msgid "Advanced settings" msgstr "" msgid "All" msgstr "Alles" +msgid "All changes are committed" +msgstr "" + +msgid "Allows you to add and manage Kubernetes clusters." +msgstr "" + +msgid "An error occurred previewing the blob" +msgstr "" + msgid "An error occurred when toggling the notification subscription" msgstr "" msgid "An error occurred when updating the issue weight" msgstr "" +msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." +msgstr "" + +msgid "An error occurred while fetching markdown preview" +msgstr "" + msgid "An error occurred while fetching sidebar data" msgstr "" +msgid "An error occurred while getting projects" +msgstr "" + +msgid "An error occurred while loading filenames" +msgstr "" + +msgid "An error occurred while rendering KaTeX" +msgstr "" + +msgid "An error occurred while rendering preview broadcast message" +msgstr "" + +msgid "An error occurred while retrieving calendar activity" +msgstr "" + +msgid "An error occurred while retrieving diff" +msgstr "" + +msgid "An error occurred while validating username" +msgstr "" + msgid "An error occurred. Please try again." msgstr "" @@ -163,9 +235,6 @@ msgstr "" msgid "Are you sure you want to discard your changes?" msgstr "" -msgid "Are you sure you want to leave this group?" -msgstr "" - msgid "Are you sure you want to reset registration token?" msgstr "" @@ -178,6 +247,21 @@ msgstr "" msgid "Artifacts" msgstr "" +msgid "Assign custom color like #FF0000" +msgstr "" + +msgid "Assign labels" +msgstr "" + +msgid "Assign milestone" +msgstr "" + +msgid "Assign to" +msgstr "" + +msgid "Assignee" +msgstr "" + msgid "Attach a file by drag & drop or %{upload_link}" msgstr "" @@ -193,13 +277,16 @@ msgstr "" msgid "Author" msgstr "Auteur" -msgid "Auto Review Apps and Auto Deploy need a domain name and the %{kubernetes} to work correctly." +msgid "Authors: %{authors}" msgstr "" -msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly." +msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." msgstr "" -msgid "Auto Review Apps and Auto Deploy need the %{kubernetes} to work correctly." +msgid "Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly." +msgstr "" + +msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly." msgstr "" msgid "AutoDevOps|Auto DevOps (Beta)" @@ -223,6 +310,12 @@ msgstr "" msgid "Available" msgstr "" +msgid "Avatar will be removed. Are you sure?" +msgstr "" + +msgid "Average per day: %{average}" +msgstr "" + msgid "Billing" msgstr "" @@ -277,6 +370,9 @@ msgstr "" msgid "BillingPlans|per user" msgstr "" +msgid "Begin with the selected commit" +msgstr "" + msgid "Branch" msgid_plural "Branches" msgstr[0] "" @@ -405,8 +501,8 @@ msgstr "door" msgid "CI / CD" msgstr "CI / CD" -msgid "CI configuration" -msgstr "CI Configuratie" +msgid "CI/CD configuration" +msgstr "" msgid "CICD|Jobs" msgstr "" @@ -417,6 +513,9 @@ msgstr "Annuleren" msgid "Cancel edit" msgstr "Bewerken annuleren" +msgid "Cannot modify managed Kubernetes cluster" +msgstr "" + msgid "Change Weight" msgstr "" @@ -432,15 +531,24 @@ msgstr "" msgid "ChangeTypeAction|Revert" msgstr "" +msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes." +msgstr "" + msgid "Changelog" msgstr "" +msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision." +msgstr "" + msgid "Charts" msgstr "Grafieken" msgid "Chat" msgstr "Chat" +msgid "Check interval" +msgstr "" + msgid "Checking %{text} availability…" msgstr "" @@ -453,7 +561,19 @@ msgstr "Cherry-pick deze commit" msgid "Cherry-pick this merge request" msgstr "" -msgid "Choose which groups you wish to replicate to this secondary node. Leave blank to replicate all." +msgid "Choose File ..." +msgstr "" + +msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request." +msgstr "" + +msgid "Choose file..." +msgstr "" + +msgid "Choose which groups you wish to synchronize to this secondary node." +msgstr "" + +msgid "Choose which shards you wish to synchronize to this secondary node." msgstr "" msgid "CiStatusLabel|canceled" @@ -510,79 +630,91 @@ msgstr "overgeslagen" msgid "CiStatus|running" msgstr "" -msgid "CircuitBreakerApiLink|circuitbreaker api" +msgid "CiVariables|Input variable key" msgstr "" -msgid "Clone repository" +msgid "CiVariables|Input variable value" msgstr "" -msgid "Close" +msgid "CiVariables|Remove variable row" msgstr "" -msgid "Cluster" +msgid "CiVariable|* (All environments)" msgstr "" -msgid "ClusterIntegration|%{appList} was successfully installed on your cluster" +msgid "CiVariable|All environments" msgstr "" -msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which incur additional costs. See %{pricingLink}" +msgid "CiVariable|Create wildcard" msgstr "" -msgid "ClusterIntegration|API URL" +msgid "CiVariable|Error occured while saving variables" msgstr "" -msgid "ClusterIntegration|Active" +msgid "CiVariable|New environment" msgstr "" -msgid "ClusterIntegration|Add an existing cluster" +msgid "CiVariable|Protected" msgstr "" -msgid "ClusterIntegration|Add cluster" +msgid "CiVariable|Search environments" msgstr "" -msgid "ClusterIntegration|All" +msgid "CiVariable|Toggle protected" msgstr "" -msgid "ClusterIntegration|Applications" +msgid "CiVariable|Validation failed" msgstr "" -msgid "ClusterIntegration|CA Certificate" +msgid "CircuitBreakerApiLink|circuitbreaker api" msgstr "" -msgid "ClusterIntegration|Certificate Authority bundle (PEM format)" +msgid "Click to expand text" msgstr "" -msgid "ClusterIntegration|Choose how to set up cluster integration" +msgid "Clone repository" msgstr "" -msgid "ClusterIntegration|Cluster" +msgid "Close" msgstr "" -msgid "ClusterIntegration|Cluster details" +msgid "Closed" msgstr "" -msgid "ClusterIntegration|Cluster integration" +msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Cluster integration is disabled for this project." +msgid "ClusterIntegration|API URL" msgstr "" -msgid "ClusterIntegration|Cluster integration is enabled for this project." +msgid "ClusterIntegration|Add Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab's connection to it." +msgid "ClusterIntegration|Add an existing Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Cluster is being created on Google Kubernetes Engine..." +msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration" msgstr "" -msgid "ClusterIntegration|Cluster name" +msgid "ClusterIntegration|Applications" msgstr "" -msgid "ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine. Refresh the page to see cluster's details" +msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster." msgstr "" -msgid "ClusterIntegration|Clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" +msgid "ClusterIntegration|CA Certificate" +msgstr "" + +msgid "ClusterIntegration|Certificate Authority bundle (PEM format)" +msgstr "" + +msgid "ClusterIntegration|Choose how to set up Kubernetes cluster integration" +msgstr "" + +msgid "ClusterIntegration|Choose which of your project's environments will use this Kubernetes cluster." +msgstr "" + +msgid "ClusterIntegration|Control how your Kubernetes cluster integrates with GitLab" msgstr "" msgid "ClusterIntegration|Copy API URL" @@ -591,37 +723,34 @@ msgstr "" msgid "ClusterIntegration|Copy CA Certificate" msgstr "" -msgid "ClusterIntegration|Copy Token" +msgid "ClusterIntegration|Copy Kubernetes cluster name" msgstr "" -msgid "ClusterIntegration|Copy cluster name" +msgid "ClusterIntegration|Copy Token" msgstr "" -msgid "ClusterIntegration|Create a new cluster on Google Engine right from GitLab" +msgid "ClusterIntegration|Create Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Create cluster" +msgid "ClusterIntegration|Create Kubernetes cluster on Google Kubernetes Engine" msgstr "" -msgid "ClusterIntegration|Create cluster on Google Kubernetes Engine" +msgid "ClusterIntegration|Create a new Kubernetes cluster on Google Kubernetes Engine right from GitLab" msgstr "" msgid "ClusterIntegration|Create on GKE" msgstr "" -msgid "ClusterIntegration|Enable cluster integration" -msgstr "" - msgid "ClusterIntegration|Enter the details for an existing Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Enter the details for your cluster" +msgid "ClusterIntegration|Enter the details for your Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Environment pattern" +msgid "ClusterIntegration|Environment scope" msgstr "" -msgid "ClusterIntegration|GKE pricing" +msgid "ClusterIntegration|GitLab Integration" msgstr "" msgid "ClusterIntegration|GitLab Runner" @@ -639,64 +768,94 @@ msgstr "" msgid "ClusterIntegration|Helm Tiller" msgstr "" -msgid "ClusterIntegration|Inactive" -msgstr "" - msgid "ClusterIntegration|Ingress" msgstr "" msgid "ClusterIntegration|Install" msgstr "" -msgid "ClusterIntegration|Install applications on your cluster. Read more about %{helpLink}" -msgstr "" - msgid "ClusterIntegration|Installed" msgstr "" msgid "ClusterIntegration|Installing" msgstr "" -msgid "ClusterIntegration|Integrate cluster automation" +msgid "ClusterIntegration|Integrate Kubernetes cluster automation" +msgstr "" + +msgid "ClusterIntegration|Integration status" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster details" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is disabled for this project." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project. Disabling this integration will not affect your Kubernetes cluster, it will only temporarily turn off GitLab's connection to it." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster name" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project" msgstr "" msgid "ClusterIntegration|Learn more about %{link_to_documentation}" msgstr "" -msgid "ClusterIntegration|Learn more about Clusters" +msgid "ClusterIntegration|Learn more about Kubernetes" msgstr "" -msgid "ClusterIntegration|Machine type" +msgid "ClusterIntegration|Learn more about environments" msgstr "" -msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create clusters" +msgid "ClusterIntegration|Machine type" msgstr "" -msgid "ClusterIntegration|Manage cluster integration on your GitLab project" +msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters" msgstr "" -msgid "ClusterIntegration|Manage your cluster by visiting %{link_gke}" +msgid "ClusterIntegration|Manage" msgstr "" -msgid "ClusterIntegration|Multiple clusters are available in GitLab Entreprise Edition Premium and Ultimate" +msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}" msgstr "" -msgid "ClusterIntegration|Note:" +msgid "ClusterIntegration|More information" msgstr "" -msgid "ClusterIntegration|Number of nodes" +msgid "ClusterIntegration|Multiple Kubernetes clusters are available in GitLab Enterprise Edition Premium and Ultimate" msgstr "" -msgid "ClusterIntegration|Please enter access information for your cluster. If you need help, you can read our %{link_to_help_page} on clusters" +msgid "ClusterIntegration|Note:" msgstr "" -msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:" +msgid "ClusterIntegration|Number of nodes" msgstr "" -msgid "ClusterIntegration|Problem setting up the cluster" +msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes" msgstr "" -msgid "ClusterIntegration|Problem setting up the clusters list" +msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:" msgstr "" msgid "ClusterIntegration|Project ID" @@ -708,16 +867,19 @@ msgstr "" msgid "ClusterIntegration|Project namespace (optional, unique)" msgstr "" -msgid "ClusterIntegration|Read our %{link_to_help_page} on cluster integration." +msgid "ClusterIntegration|Prometheus" msgstr "" -msgid "ClusterIntegration|Remove cluster integration" +msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration." +msgstr "" + +msgid "ClusterIntegration|Remove Kubernetes cluster integration" msgstr "" msgid "ClusterIntegration|Remove integration" msgstr "" -msgid "ClusterIntegration|Removing cluster integration will remove the cluster configuration you have added to this project. It will not delete your cluster on Google Kubernetes Engine." +msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster." msgstr "" msgid "ClusterIntegration|Request to begin installing failed" @@ -726,7 +888,7 @@ msgstr "" msgid "ClusterIntegration|Save changes" msgstr "" -msgid "ClusterIntegration|See and edit the details for your cluster" +msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster" msgstr "" msgid "ClusterIntegration|See machine types" @@ -747,25 +909,25 @@ msgstr "" msgid "ClusterIntegration|Something went wrong on our end." msgstr "" -msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine" +msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine" msgstr "" msgid "ClusterIntegration|Something went wrong while installing %{title}" msgstr "" -msgid "ClusterIntegration|There are no clusters to show" +msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below" msgstr "" -msgid "ClusterIntegration|This account must have permissions to create a cluster in the %{link_to_container_project} specified below" +msgid "ClusterIntegration|Toggle Kubernetes Cluster" msgstr "" -msgid "ClusterIntegration|Toggle Cluster" +msgid "ClusterIntegration|Toggle Kubernetes cluster" msgstr "" msgid "ClusterIntegration|Token" msgstr "" -msgid "ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." +msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." msgstr "" msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}" @@ -777,7 +939,7 @@ msgstr "" msgid "ClusterIntegration|access to Google Kubernetes Engine" msgstr "" -msgid "ClusterIntegration|cluster" +msgid "ClusterIntegration|check the pricing here" msgstr "" msgid "ClusterIntegration|documentation" @@ -795,6 +957,9 @@ msgstr "" msgid "ClusterIntegration|properly configured" msgstr "" +msgid "Collapse" +msgstr "" + msgid "Comments" msgstr "Opmerkingen" @@ -812,6 +977,9 @@ msgstr "" msgid "Commit message" msgstr "" +msgid "Commit statistics for %{ref} %{start_time} - %{end_time}" +msgstr "" + msgid "CommitBoxTitle|Commit" msgstr "Commit" @@ -824,15 +992,57 @@ msgstr "Commits" msgid "Commits feed" msgstr "" +msgid "Commits per day hour (UTC)" +msgstr "" + +msgid "Commits per day of month" +msgstr "" + +msgid "Commits per weekday" +msgstr "" + +msgid "Commits|An error occurred while fetching merge requests data." +msgstr "" + +msgid "Commits|Commit: %{commitText}" +msgstr "" + msgid "Commits|History" msgstr "Geschiedenis" +msgid "Commits|No related merge requests found" +msgstr "" + msgid "Committed by" msgstr "Gecommit door" msgid "Compare" msgstr "Vergelijk" +msgid "Compare Git revisions" +msgstr "" + +msgid "Compare Revisions" +msgstr "" + +msgid "CompareBranches|%{source_branch} and %{target_branch} are the same." +msgstr "" + +msgid "CompareBranches|Compare" +msgstr "" + +msgid "CompareBranches|Source" +msgstr "" + +msgid "CompareBranches|Target" +msgstr "" + +msgid "CompareBranches|There isn't anything to compare." +msgstr "" + +msgid "Confidentiality" +msgstr "" + msgid "Container Registry" msgstr "" @@ -884,6 +1094,9 @@ msgstr "" msgid "Contributors" msgstr "" +msgid "ContributorsPage|%{startDate} – %{endDate}" +msgstr "" + msgid "ContributorsPage|Building repository graph." msgstr "" @@ -905,9 +1118,18 @@ msgstr "" msgid "Copy URL to clipboard" msgstr "" +msgid "Copy branch name to clipboard" +msgstr "" + msgid "Copy commit SHA to clipboard" msgstr "" +msgid "Copy reference to clipboard" +msgstr "" + +msgid "Create" +msgstr "" + msgid "Create New Directory" msgstr "" @@ -926,6 +1148,9 @@ msgstr "" msgid "Create file" msgstr "" +msgid "Create lists from labels. Issues with that label appear in that list." +msgstr "" + msgid "Create merge request" msgstr "" @@ -938,6 +1163,9 @@ msgstr "" msgid "Create new file" msgstr "" +msgid "Create new label" +msgstr "" + msgid "Create new..." msgstr "" @@ -959,6 +1187,9 @@ msgstr "" msgid "Cron syntax" msgstr "" +msgid "Current node" +msgstr "" + msgid "Custom notification events" msgstr "" @@ -968,9 +1199,6 @@ msgstr "" msgid "Cycle Analytics" msgstr "" -msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project." -msgstr "" - msgid "CycleAnalyticsStage|Code" msgstr "Code" @@ -1027,12 +1255,21 @@ msgstr "" msgid "Details" msgstr "" +msgid "Diffs|No file name available" +msgstr "" + msgid "Directory name" msgstr "" +msgid "Disable" +msgstr "" + msgid "Discard changes" msgstr "" +msgid "Discover GitLab Geo." +msgstr "" + msgid "Dismiss Cycle Analytics introduction box" msgstr "" @@ -1069,15 +1306,24 @@ msgstr "" msgid "DownloadSource|Download" msgstr "" +msgid "Due date" +msgstr "" + msgid "Edit" msgstr "" msgid "Edit Pipeline Schedule %{id}" msgstr "" +msgid "Edit files in the editor and commit changes here" +msgstr "" + msgid "Emails" msgstr "" +msgid "Enable" +msgstr "" + msgid "Environments|An error occurred while fetching the environments." msgstr "" @@ -1096,9 +1342,6 @@ msgstr "" msgid "Environments|Environments" msgstr "" -msgid "Environments|Environments are places where code gets deployed, such as staging or production." -msgstr "" - msgid "Environments|Job" msgstr "" @@ -1141,9 +1384,33 @@ msgstr "" msgid "Error creating epic" msgstr "" +msgid "Error fetching contributors data." +msgstr "" + +msgid "Error fetching labels." +msgstr "" + +msgid "Error fetching network graph." +msgstr "" + +msgid "Error fetching refs" +msgstr "" + +msgid "Error fetching usage ping data." +msgstr "" + msgid "Error occurred when toggling the notification subscription" msgstr "" +msgid "Error saving label update." +msgstr "" + +msgid "Error updating status for all todos." +msgstr "" + +msgid "Error updating todo status." +msgstr "" + msgid "EventFilterBy|Filter by all" msgstr "" @@ -1171,6 +1438,9 @@ msgstr "" msgid "Every week (Sundays at 4:00am)" msgstr "" +msgid "Expand" +msgstr "" + msgid "Explore projects" msgstr "" @@ -1189,6 +1459,9 @@ msgstr "" msgid "February" msgstr "" +msgid "Fields on this page are now uneditable, you can configure" +msgstr "" + msgid "File name" msgstr "" @@ -1233,10 +1506,10 @@ msgstr "" msgid "GPG Keys" msgstr "" -msgid "Geo Nodes" +msgid "Generate a default set of labels" msgstr "" -msgid "GeoNodeSyncStatus|Failed" +msgid "Geo Nodes" msgstr "" msgid "GeoNodeSyncStatus|Node is failing or broken." @@ -1245,16 +1518,100 @@ msgstr "" msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage." msgstr "" -msgid "GeoNodeSyncStatus|Out of sync" +msgid "GeoNodes|Database replication lag:" +msgstr "" + +msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?" +msgstr "" + +msgid "GeoNodes|Does not match the primary storage configuration" +msgstr "" + +msgid "GeoNodes|Failed" +msgstr "" + +msgid "GeoNodes|Full" +msgstr "" + +msgid "GeoNodes|GitLab version does not match the primary node version" +msgstr "" + +msgid "GeoNodes|GitLab version:" +msgstr "" + +msgid "GeoNodes|Health status:" +msgstr "" + +msgid "GeoNodes|Last event ID processed by cursor:" +msgstr "" + +msgid "GeoNodes|Last event ID seen from primary:" +msgstr "" + +msgid "GeoNodes|Loading nodes" +msgstr "" + +msgid "GeoNodes|Local Attachments:" +msgstr "" + +msgid "GeoNodes|Local LFS objects:" +msgstr "" + +msgid "GeoNodes|Local job artifacts:" +msgstr "" + +msgid "GeoNodes|New node" +msgstr "" + +msgid "GeoNodes|Out of sync" +msgstr "" + +msgid "GeoNodes|Replication slot WAL:" msgstr "" -msgid "GeoNodeSyncStatus|Synced" +msgid "GeoNodes|Replication slots:" +msgstr "" + +msgid "GeoNodes|Repositories:" +msgstr "" + +msgid "GeoNodes|Selective" +msgstr "" + +msgid "GeoNodes|Storage config:" +msgstr "" + +msgid "GeoNodes|Sync settings:" +msgstr "" + +msgid "GeoNodes|Synced" +msgstr "" + +msgid "GeoNodes|Unused slots" +msgstr "" + +msgid "GeoNodes|Used slots" +msgstr "" + +msgid "GeoNodes|Wikis:" +msgstr "" + +msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS." +msgstr "" + +msgid "Geo|All projects" msgstr "" msgid "Geo|File sync capacity" msgstr "" -msgid "Geo|Groups to replicate" +msgid "Geo|Groups to synchronize" +msgstr "" + +msgid "Geo|Projects in certain groups" +msgstr "" + +msgid "Geo|Projects in certain storage shards" msgstr "" msgid "Geo|Repository sync capacity" @@ -1263,12 +1620,24 @@ msgstr "" msgid "Geo|Select groups to replicate." msgstr "" +msgid "Geo|Shards to synchronize" +msgstr "" + +msgid "Git revision" +msgstr "" + msgid "Git storage health information has been reset" msgstr "" +msgid "Git version" +msgstr "" + msgid "GitLab Runner section" msgstr "" +msgid "Gitaly Servers" +msgstr "" + msgid "Go to your fork" msgstr "" @@ -1278,6 +1647,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it!" +msgstr "" + msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" msgstr "" @@ -1314,7 +1686,7 @@ msgstr "" msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group." msgstr "" -msgid "GroupsTree|Are you sure you want to leave the \"${this.group.fullName}\" group?" +msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?" msgstr "" msgid "GroupsTree|Create a project in this group." @@ -1365,6 +1737,11 @@ msgstr "" msgid "HealthCheck|Unhealthy" msgstr "" +msgid "Hide value" +msgid_plural "Hide values" +msgstr[0] "" +msgstr[1] "" + msgid "History" msgstr "" @@ -1391,6 +1768,12 @@ msgid_plural "Instances" msgstr[0] "" msgstr[1] "" +msgid "Instance does not support multiple Kubernetes clusters" +msgstr "" + +msgid "Interested parties can even contribute by pushing commits if they want to." +msgstr "" + msgid "Internal - The group and any internal projects can be viewed by any logged in user." msgstr "" @@ -1418,6 +1801,9 @@ msgstr "" msgid "Issues" msgstr "" +msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable." +msgstr "" + msgid "Jan" msgstr "" @@ -1436,6 +1822,27 @@ msgstr "" msgid "June" msgstr "" +msgid "Kubernetes" +msgstr "" + +msgid "Kubernetes Cluster" +msgstr "" + +msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}" +msgstr "" + +msgid "Kubernetes cluster integration was not removed." +msgstr "" + +msgid "Kubernetes cluster integration was successfully removed." +msgstr "" + +msgid "Kubernetes cluster was successfully updated." +msgstr "" + +msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page" +msgstr "" + msgid "LFSStatus|Disabled" msgstr "" @@ -1445,6 +1852,9 @@ msgstr "" msgid "Labels" msgstr "" +msgid "Labels can be applied to issues and merge requests to categorize them." +msgstr "" + msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "" @@ -1474,6 +1884,9 @@ msgstr "" msgid "LastPushEvent|at" msgstr "" +msgid "Learn more" +msgstr "" + msgid "Learn more in the" msgstr "" @@ -1492,14 +1905,18 @@ msgstr "" msgid "License" msgstr "" -msgid "Limited to showing %d event at most" -msgid_plural "Limited to showing %d events at most" -msgstr[0] "" -msgstr[1] "" +msgid "Loading the GitLab IDE..." +msgstr "" msgid "Lock" msgstr "" +msgid "Lock %{issuableDisplayName}" +msgstr "" + +msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment." +msgstr "" + msgid "Locked" msgstr "" @@ -1509,12 +1926,21 @@ msgstr "" msgid "Login" msgstr "" +msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos." +msgstr "" + +msgid "Manage labels" +msgstr "" + msgid "Mar" msgstr "" msgid "March" msgstr "" +msgid "Mark done" +msgstr "" + msgid "Maximum git storage failures" msgstr "" @@ -1527,6 +1953,9 @@ msgstr "" msgid "Members" msgstr "" +msgid "Merge Request" +msgstr "" + msgid "Merge Requests" msgstr "" @@ -1536,9 +1965,30 @@ msgstr "" msgid "Merge request" msgstr "" +msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others" +msgstr "" + +msgid "Merged" +msgstr "" + msgid "Messages" msgstr "" +msgid "Milestone" +msgstr "" + +msgid "Milestones|Delete milestone" +msgstr "" + +msgid "Milestones|Delete milestone %{milestoneTitle}?" +msgstr "" + +msgid "Milestones|Failed to delete milestone %{milestoneTitle}" +msgstr "" + +msgid "Milestones|Milestone %{milestoneTitle} was not found" +msgstr "" + msgid "MissingSSHKeyWarningLink|add an SSH key" msgstr "" @@ -1548,10 +1998,16 @@ msgstr "" msgid "More information is available|here" msgstr "" +msgid "Move" +msgstr "" + +msgid "Move issue" +msgstr "" + msgid "Multiple issue boards" msgstr "" -msgid "New Cluster" +msgid "Name new label" msgstr "" msgid "New Issue" @@ -1559,6 +2015,12 @@ msgid_plural "New Issues" msgstr[0] "Nieuwe issue" msgstr[1] "Nieuwe issues" +msgid "New Kubernetes Cluster" +msgstr "" + +msgid "New Kubernetes cluster" +msgstr "" + msgid "New Pipeline Schedule" msgstr "" @@ -1583,6 +2045,9 @@ msgstr "" msgid "New issue" msgstr "" +msgid "New label" +msgstr "" + msgid "New merge request" msgstr "" @@ -1601,7 +2066,22 @@ msgstr "" msgid "New tag" msgstr "" -msgid "No container images stored for this project. Add one by following the instructions above." +msgid "No assignee" +msgstr "" + +msgid "No changes" +msgstr "" + +msgid "No connection could be made to a Gitaly Server, please check your logs!" +msgstr "" + +msgid "No due date" +msgstr "" + +msgid "No estimate or time spent" +msgstr "" + +msgid "No file chosen" msgstr "" msgid "No repository" @@ -1616,9 +2096,15 @@ msgstr "" msgid "None" msgstr "" +msgid "Not allowed to merge" +msgstr "" + msgid "Not available" msgstr "" +msgid "Not confidential" +msgstr "" + msgid "Not enough data" msgstr "" @@ -1679,6 +2165,12 @@ msgstr "" msgid "Notifications" msgstr "" +msgid "Notifications off" +msgstr "" + +msgid "Notifications on" +msgstr "" + msgid "Nov" msgstr "" @@ -1688,7 +2180,7 @@ msgstr "" msgid "Number of access attempts" msgstr "" -msgid "Number of failures before backing off" +msgid "OK" msgstr "" msgid "Oct" @@ -1703,6 +2195,9 @@ msgstr "" msgid "Only project members can comment." msgstr "" +msgid "Open" +msgstr "" + msgid "Opened" msgstr "" @@ -1736,9 +2231,6 @@ msgstr "" msgid "Password" msgstr "" -msgid "People without permission will never get a notification and won\\'t be able to comment." -msgstr "" - msgid "Pipeline" msgstr "" @@ -1781,12 +2273,6 @@ msgstr "" msgid "PipelineSchedules|Inactive" msgstr "" -msgid "PipelineSchedules|Input variable key" -msgstr "" - -msgid "PipelineSchedules|Input variable value" -msgstr "" - msgid "PipelineSchedules|Next Run" msgstr "" @@ -1796,9 +2282,6 @@ msgstr "" msgid "PipelineSchedules|Provide a short description for this pipeline" msgstr "" -msgid "PipelineSchedules|Remove variable row" -msgstr "" - msgid "PipelineSchedules|Take ownership" msgstr "" @@ -1826,6 +2309,12 @@ msgstr "" msgid "Pipelines for last year" msgstr "" +msgid "Pipelines|Build with confidence" +msgstr "" + +msgid "Pipelines|Get started with Pipelines" +msgstr "" + msgid "Pipeline|all" msgstr "" @@ -1838,12 +2327,21 @@ msgstr "" msgid "Pipeline|with stages" msgstr "" +msgid "Play" +msgstr "" + +msgid "Please <a href=%{link_to_billing} target=\"_blank\" rel=\"noopener noreferrer\">enable billing for one of your projects to be able to create a Kubernetes cluster</a>, then try again." +msgstr "" + msgid "Please solve the reCAPTCHA" msgstr "" msgid "Preferences" msgstr "" +msgid "Primary" +msgstr "" + msgid "Private - Project access must be granted explicitly to each user." msgstr "" @@ -1889,6 +2387,9 @@ msgstr "" msgid "Profiles|your account" msgstr "" +msgid "Programming languages used in this repository" +msgstr "" + msgid "Project '%{project_name}' is in the process of being deleted." msgstr "" @@ -1904,6 +2405,15 @@ msgstr "" msgid "Project access must be granted explicitly to each user." msgstr "" +msgid "Project avatar" +msgstr "" + +msgid "Project avatar in repository: %{link}" +msgstr "" + +msgid "Project cache successfully reset." +msgstr "" + msgid "Project details" msgstr "" @@ -1922,6 +2432,21 @@ msgstr "" msgid "ProjectActivityRSS|Subscribe" msgstr "" +msgid "ProjectCreationLevel|Allowed to create projects" +msgstr "" + +msgid "ProjectCreationLevel|Default project creation protection" +msgstr "" + +msgid "ProjectCreationLevel|Developers + Masters" +msgstr "" + +msgid "ProjectCreationLevel|Masters" +msgstr "" + +msgid "ProjectCreationLevel|No one" +msgstr "" + msgid "ProjectFeature|Disabled" msgstr "" @@ -1946,15 +2471,9 @@ msgstr "" msgid "ProjectSettings|Contact an admin to change this setting." msgstr "" -msgid "ProjectSettings|Immediately run a pipeline on the default branch" -msgstr "" - msgid "ProjectSettings|Only signed commits can be pushed to this repository." msgstr "" -msgid "ProjectSettings|Problem setting up the CI/CD settings JavaScript" -msgstr "" - msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin." msgstr "" @@ -2018,12 +2537,15 @@ msgstr "" msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/" msgstr "" -msgid "PrometheusService|Prometheus monitoring" +msgid "PrometheusService|Time-series monitoring service" msgstr "" msgid "PrometheusService|View environments" msgstr "" +msgid "Protip:" +msgstr "" + msgid "Public - The group and any public projects can be viewed without any authentication." msgstr "" @@ -2039,6 +2561,9 @@ msgstr "" msgid "PushRule|Committer restriction" msgstr "" +msgid "Quick actions can be used in the issues description and comment boxes." +msgstr "" + msgid "Read more" msgstr "" @@ -2051,6 +2576,12 @@ msgstr "" msgid "RefSwitcher|Tags" msgstr "" +msgid "Reference:" +msgstr "" + +msgid "Register / Sign In" +msgstr "" + msgid "Registry" msgstr "" @@ -2075,9 +2606,18 @@ msgstr "" msgid "Remind later" msgstr "" +msgid "Remove" +msgstr "" + +msgid "Remove avatar" +msgstr "" + msgid "Remove project" msgstr "" +msgid "Repair authentication" +msgstr "" + msgid "Repository" msgstr "" @@ -2093,6 +2633,11 @@ msgstr "" msgid "Reset runners registration token" msgstr "" +msgid "Reveal value" +msgid_plural "Reveal values" +msgstr[0] "" +msgstr[1] "" + msgid "Revert this commit" msgstr "" @@ -2102,15 +2647,15 @@ msgstr "" msgid "SSH Keys" msgstr "" -msgid "Save" -msgstr "" - msgid "Save changes" msgstr "" msgid "Save pipeline schedule" msgstr "" +msgid "Save variables" +msgstr "" + msgid "Schedule a new pipeline" msgstr "" @@ -2126,37 +2671,58 @@ msgstr "" msgid "Search branches and tags" msgstr "" -msgid "Seconds before reseting failure information" +msgid "Search milestones" +msgstr "" + +msgid "Search project" +msgstr "" + +msgid "Search users" msgstr "" -msgid "Seconds to wait after a storage failure" +msgid "Seconds before reseting failure information" msgstr "" msgid "Seconds to wait for a storage access attempt" msgstr "" +msgid "Secret variables" +msgstr "" + msgid "Select Archive Format" msgstr "" msgid "Select a timezone" msgstr "" +msgid "Select assignee" +msgstr "" + +msgid "Select branch/tag" +msgstr "" + msgid "Select target branch" msgstr "" +msgid "Selective synchronization" +msgstr "" + msgid "Sep" msgstr "" msgid "September" msgstr "" +msgid "Server version" +msgstr "" + msgid "Service Templates" msgstr "" msgid "Set a password on your account to pull or push via %{protocol}." msgstr "" -msgid "Set up CI" +msgid "Set up CI/CD" msgstr "" msgid "Set up Koding" @@ -2171,6 +2737,15 @@ msgstr "" msgid "Settings" msgstr "" +msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero." +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset pipeline minutes" +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes" +msgstr "" + msgid "Show parent pages" msgstr "" @@ -2185,9 +2760,6 @@ msgstr[1] "" msgid "Sidebar|Change weight" msgstr "" -msgid "Sidebar|Edit" -msgstr "" - msgid "Sidebar|No" msgstr "" @@ -2200,18 +2772,30 @@ msgstr "" msgid "Snippets" msgstr "" +msgid "Something went wrong on our end" +msgstr "" + msgid "Something went wrong on our end." msgstr "" +msgid "Something went wrong trying to change the confidentiality of this issue" +msgstr "" + msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}" msgstr "" +msgid "Something went wrong when toggling the button" +msgstr "" + msgid "Something went wrong while fetching the projects." msgstr "" msgid "Something went wrong while fetching the registry list." msgstr "" +msgid "Something went wrong. Please try again." +msgstr "" + msgid "Sort by" msgstr "" @@ -2341,10 +2925,10 @@ msgstr "" msgid "Stopped" msgstr "" -msgid "Subgroups" +msgid "Storage" msgstr "" -msgid "Subscribe" +msgid "Subgroups" msgstr "" msgid "Switch branch/tag" @@ -2442,7 +3026,10 @@ msgstr "" msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project." msgstr "" -msgid "The circuitbreaker backoff threshold should be lower than the failure count threshold" +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project" +msgstr "" + +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project." msgstr "" msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request." @@ -2457,10 +3044,10 @@ msgstr "" msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage." msgstr "" -msgid "The number of attempts GitLab will make to access a storage." +msgid "The maximum file size allowed is 200KB." msgstr "" -msgid "The number of failures after which GitLab will start temporarily disabling access to a storage shard on a host" +msgid "The number of attempts GitLab will make to access a storage." msgstr "" msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}." @@ -2469,9 +3056,6 @@ msgstr "" msgid "The phase of the development lifecycle." msgstr "" -msgid "The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user." -msgstr "" - msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit." msgstr "" @@ -2502,19 +3086,46 @@ msgstr "" msgid "The time in seconds GitLab will try to access storage. After this time a timeout error will be raised." msgstr "" +msgid "The time in seconds between storage checks. When a previous check did complete yet, GitLab will skip a check." +msgstr "" + msgid "The time taken by each data entry gathered by that stage." msgstr "" msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6." msgstr "" +msgid "There are no issues to show" +msgstr "" + +msgid "There are no merge requests to show" +msgstr "" + msgid "There are problems accessing Git storage: " msgstr "" +msgid "There was an error loading users activity calendar." +msgstr "" + +msgid "There was an error saving your notification settings." +msgstr "" + +msgid "There was an error subscribing to this label." +msgstr "" + +msgid "There was an error when reseting email token." +msgstr "" + +msgid "There was an error when subscribing to this label." +msgstr "" + +msgid "There was an error when unsubscribing from this label." +msgstr "" + msgid "This board\\'s scope is reduced" msgstr "" -msgid "This branch has changed since you started editing. Would you like to create a new branch?" +msgid "This directory" msgstr "" msgid "This is a confidential issue." @@ -2523,18 +3134,45 @@ msgstr "" msgid "This is the author's first Merge Request to this project." msgstr "" +msgid "This issue is confidential" +msgstr "" + msgid "This issue is confidential and locked." msgstr "" msgid "This issue is locked." msgstr "" +msgid "This job depends on a user to trigger its process. Often they are used to deploy code to production environments" +msgstr "" + +msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered" +msgstr "" + +msgid "This job has not been triggered yet" +msgstr "" + +msgid "This job has not started yet" +msgstr "" + +msgid "This job is in pending state and is waiting to be picked by a runner" +msgstr "" + +msgid "This job requires a manual action" +msgstr "" + msgid "This means you can not push code until you create an empty repository or import existing one." msgstr "" msgid "This merge request is locked." msgstr "" +msgid "This project" +msgstr "" + +msgid "This repository" +msgstr "" + msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here." msgstr "" @@ -2547,9 +3185,21 @@ msgstr "" msgid "Time between merge request creation and merge/close" msgstr "" +msgid "Time tracking" +msgstr "" + msgid "Time until first merge request" msgstr "" +msgid "TimeTrackingEstimated|Est" +msgstr "" + +msgid "TimeTracking|Estimated:" +msgstr "" + +msgid "TimeTracking|Spent" +msgstr "" + msgid "Timeago|%s days ago" msgstr "" @@ -2689,6 +3339,18 @@ msgstr "s" msgid "Title" msgstr "" +msgid "Todo" +msgstr "" + +msgid "Toggle sidebar" +msgstr "" + +msgid "ToggleButton|Toggle Status: OFF" +msgstr "" + +msgid "ToggleButton|Toggle Status: ON" +msgstr "" + msgid "Total Time" msgstr "" @@ -2704,19 +3366,40 @@ msgstr "" msgid "Track groups of issues that share a theme, across projects and milestones" msgstr "" +msgid "Total: %{total}" +msgstr "" + +msgid "Track time with quick actions" +msgstr "" + +msgid "Trigger this manual action" +msgstr "" + msgid "Turn on Service Desk" msgstr "" +msgid "Type %{value} to confirm:" +msgstr "" + +msgid "Unable to reset project cache." +msgstr "" + +msgid "Unknown" +msgstr "" + msgid "Unlock" msgstr "" +msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment." +msgstr "" + msgid "Unlocked" msgstr "" msgid "Unstar" msgstr "" -msgid "Unsubscribe" +msgid "Up to date" msgstr "" msgid "Upgrade your plan to activate Advanced Global Search." @@ -2740,6 +3423,9 @@ msgstr "" msgid "Upload file" msgstr "" +msgid "Upload new avatar" +msgstr "" + msgid "UploadLink|click to upload" msgstr "" @@ -2752,9 +3438,15 @@ msgstr "" msgid "Use your global notification setting" msgstr "" +msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." +msgstr "" + msgid "View file @ " msgstr "" +msgid "View labels" +msgstr "" + msgid "View open merge request" msgstr "" @@ -2776,6 +3468,9 @@ msgstr "" msgid "Want to see the data? Please ask an administrator for access." msgstr "" +msgid "We could not verify that one of your projects on GCP has billing enabled. Please try again." +msgstr "" + msgid "We don't have enough data to show this stage." msgstr "" @@ -2788,9 +3483,6 @@ msgstr "" msgid "Weight" msgstr "" -msgid "When access to a storage fails. GitLab will prevent access to the storage for the time specified here. This allows the filesystem to recover. Repositories on failing shards are temporarly unavailable" -msgstr "" - msgid "Wiki" msgstr "" @@ -2809,6 +3501,12 @@ msgstr "" msgid "WikiClone|Start Gollum and edit locally" msgstr "" +msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title." +msgstr "" + +msgid "WikiEdit|There is already a page with the same title in that path." +msgstr "" + msgid "WikiEmptyPageError|You are not allowed to create wiki pages" msgstr "" @@ -2911,9 +3609,21 @@ msgstr "" msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" msgstr "" +msgid "You can also star a label to make it a priority label." +msgstr "" + +msgid "You can move around the graph by using the arrow keys." +msgstr "" + +msgid "You can move around the graph by using the arrow keys." +msgstr "" + msgid "You can only add files when you are on a branch" msgstr "" +msgid "You can only edit files when you are on a branch" +msgstr "" + msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead." msgstr "" @@ -2953,6 +3663,12 @@ msgstr "" msgid "You won't be able to pull or push project code via SSH until you add an SSH key to your profile" msgstr "" +msgid "You'll need to use different branch names to get a valid comparison." +msgstr "" + +msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure" +msgstr "" + msgid "Your comment will not be visible to the public." msgstr "" @@ -2965,26 +3681,220 @@ msgstr "" msgid "Your projects" msgstr "" +msgid "assign yourself" +msgstr "" + msgid "branch name" msgstr "" msgid "by" msgstr "" +msgid "ciReport|Code quality" +msgstr "" + +msgid "ciReport|DAST detected no alerts by analyzing the review app" +msgstr "" + +msgid "ciReport|Failed to load ${type} report" +msgstr "" + +msgid "ciReport|Fixed:" +msgstr "" + +msgid "ciReport|Instances" +msgstr "" + +msgid "ciReport|Learn more about whitelisting" +msgstr "" + +msgid "ciReport|Loading ${type} report" +msgstr "" + +msgid "ciReport|No changes to code quality" +msgstr "" + +msgid "ciReport|No changes to performance metrics" +msgstr "" + +msgid "ciReport|Performance metrics" +msgstr "" + +msgid "ciReport|SAST" +msgstr "" + +msgid "ciReport|SAST detected no security vulnerabilities" +msgstr "" + +msgid "ciReport|SAST:container no vulnerabilities were found" +msgstr "" + +msgid "ciReport|Show complete code vulnerabilities report" +msgstr "" + +msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}" +msgstr "" + msgid "commit" msgstr "" +msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue." +msgstr "" + +msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue." +msgstr "" + msgid "day" msgid_plural "days" msgstr[0] "" msgstr[1] "" +msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command." +msgstr "" + +msgid "merge request" +msgid_plural "merge requests" +msgstr[0] "" +msgstr[1] "" + +msgid "mrWidget|Cancel automatic merge" +msgstr "" + +msgid "mrWidget|Check out branch" +msgstr "" + +msgid "mrWidget|Checking ability to merge automatically" +msgstr "" + +msgid "mrWidget|Cherry-pick" +msgstr "" + +msgid "mrWidget|Cherry-pick this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Closed" +msgstr "" + +msgid "mrWidget|Closed by" +msgstr "" + +msgid "mrWidget|Closes" +msgstr "" + +msgid "mrWidget|Did not close" +msgstr "" + +msgid "mrWidget|Email patches" +msgstr "" + +msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|Mentions" +msgstr "" + +msgid "mrWidget|Merge" +msgstr "" + +msgid "mrWidget|Merge failed." +msgstr "" + +msgid "mrWidget|Merge locally" +msgstr "" + +msgid "mrWidget|Merged by" +msgstr "" + +msgid "mrWidget|Plain diff" +msgstr "" + +msgid "mrWidget|Refresh" +msgstr "" + +msgid "mrWidget|Refresh now" +msgstr "" + +msgid "mrWidget|Refreshing now" +msgstr "" + +msgid "mrWidget|Remove Source Branch" +msgstr "" + +msgid "mrWidget|Remove source branch" +msgstr "" + +msgid "mrWidget|Request to merge" +msgstr "" + +msgid "mrWidget|Resolve conflicts" +msgstr "" + +msgid "mrWidget|Revert" +msgstr "" + +msgid "mrWidget|Revert this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Set by" +msgstr "" + +msgid "mrWidget|The changes were merged into" +msgstr "" + +msgid "mrWidget|The changes were not merged into" +msgstr "" + +msgid "mrWidget|The changes will be merged into" +msgstr "" + +msgid "mrWidget|The source branch has been removed" +msgstr "" + +msgid "mrWidget|The source branch is being removed" +msgstr "" + +msgid "mrWidget|The source branch will be removed" +msgstr "" + +msgid "mrWidget|The source branch will not be removed" +msgstr "" + +msgid "mrWidget|There are merge conflicts" +msgstr "" + +msgid "mrWidget|This merge request failed to be merged automatically" +msgstr "" + +msgid "mrWidget|This merge request is in the process of being merged" +msgstr "" + +msgid "mrWidget|This project is archived, write access has been disabled" +msgstr "" + +msgid "mrWidget|You can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|You can remove source branch now" +msgstr "" + +msgid "mrWidget|command line" +msgstr "" + +msgid "mrWidget|into" +msgstr "" + +msgid "mrWidget|to be merged automatically when the pipeline succeeds" +msgstr "" + msgid "new merge request" msgstr "" msgid "notification emails" msgstr "" +msgid "or" +msgstr "" + msgid "parent" msgid_plural "parents" msgstr[0] "" @@ -2996,12 +3906,21 @@ msgstr "" msgid "personal access token" msgstr "" +msgid "remove due date" +msgstr "" + msgid "source" msgstr "" +msgid "spendCommand|%{slash_command} will update the sum of the time spent." +msgstr "" + msgid "to help your contributors communicate effectively!" msgstr "" msgid "username" msgstr "" +msgid "uses Kubernetes clusters to deploy your code!" +msgstr "" + diff --git a/locale/pl_PL/gitlab.po b/locale/pl_PL/gitlab.po index 5b65c42097e..9c5455eac67 100644 --- a/locale/pl_PL/gitlab.po +++ b/locale/pl_PL/gitlab.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab-ee\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-12-12 18:31+0000\n" -"PO-Revision-Date: 2018-01-05 04:41-0500\n" +"POT-Creation-Date: 2018-02-07 11:38-0600\n" +"PO-Revision-Date: 2018-02-12 04:01-0500\n" "Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n" "Language-Team: Polish\n" "Language: pl_PL\n" @@ -16,25 +16,46 @@ msgstr "" "X-Crowdin-Language: pl\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" +msgid " and" +msgstr "" + msgid "%d commit" msgid_plural "%d commits" msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgid "%d commit behind" +msgid_plural "%d commits behind" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "%d issue" +msgid_plural "%d issues" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + msgid "%d layer" msgid_plural "%d layers" msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgid "%d merge request" +msgid_plural "%d merge requests" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "%s additional commits have been omitted to prevent performance issues." msgstr[0] "" msgstr[1] "" msgstr[2] "" -msgid "%{commit_author_link} committed %{commit_timeago}" +msgid "%{commit_author_link} authored %{commit_timeago}" msgstr "" msgid "%{count} participant" @@ -49,9 +70,6 @@ msgstr "" msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will allow access on the next attempt." msgstr "" -msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will block access for %{number_of_seconds} seconds." -msgstr "" - msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved." msgstr "" @@ -127,24 +145,81 @@ msgstr "" msgid "Add new directory" msgstr "" +msgid "Add todo" +msgstr "" + +msgid "AdminArea|Stop all jobs" +msgstr "" + +msgid "AdminArea|Stop all jobs?" +msgstr "" + +msgid "AdminArea|Stop jobs" +msgstr "" + +msgid "AdminArea|Stopping jobs failed" +msgstr "" + +msgid "AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running." +msgstr "" + msgid "AdminHealthPageLink|health page" msgstr "" +msgid "Advanced" +msgstr "" + msgid "Advanced settings" msgstr "" msgid "All" msgstr "" +msgid "All changes are committed" +msgstr "" + +msgid "Allows you to add and manage Kubernetes clusters." +msgstr "" + +msgid "An error occurred previewing the blob" +msgstr "" + msgid "An error occurred when toggling the notification subscription" msgstr "" msgid "An error occurred when updating the issue weight" msgstr "" +msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." +msgstr "" + +msgid "An error occurred while fetching markdown preview" +msgstr "" + msgid "An error occurred while fetching sidebar data" msgstr "" +msgid "An error occurred while getting projects" +msgstr "" + +msgid "An error occurred while loading filenames" +msgstr "" + +msgid "An error occurred while rendering KaTeX" +msgstr "" + +msgid "An error occurred while rendering preview broadcast message" +msgstr "" + +msgid "An error occurred while retrieving calendar activity" +msgstr "" + +msgid "An error occurred while retrieving diff" +msgstr "" + +msgid "An error occurred while validating username" +msgstr "" + msgid "An error occurred. Please try again." msgstr "" @@ -169,9 +244,6 @@ msgstr "" msgid "Are you sure you want to discard your changes?" msgstr "" -msgid "Are you sure you want to leave this group?" -msgstr "" - msgid "Are you sure you want to reset registration token?" msgstr "" @@ -184,6 +256,21 @@ msgstr "" msgid "Artifacts" msgstr "" +msgid "Assign custom color like #FF0000" +msgstr "" + +msgid "Assign labels" +msgstr "" + +msgid "Assign milestone" +msgstr "" + +msgid "Assign to" +msgstr "" + +msgid "Assignee" +msgstr "" + msgid "Attach a file by drag & drop or %{upload_link}" msgstr "" @@ -199,13 +286,16 @@ msgstr "" msgid "Author" msgstr "" -msgid "Auto Review Apps and Auto Deploy need a domain name and the %{kubernetes} to work correctly." +msgid "Authors: %{authors}" msgstr "" -msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly." +msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." msgstr "" -msgid "Auto Review Apps and Auto Deploy need the %{kubernetes} to work correctly." +msgid "Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly." +msgstr "" + +msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly." msgstr "" msgid "AutoDevOps|Auto DevOps (Beta)" @@ -229,6 +319,12 @@ msgstr "" msgid "Available" msgstr "" +msgid "Avatar will be removed. Are you sure?" +msgstr "" + +msgid "Average per day: %{average}" +msgstr "" + msgid "Billing" msgstr "" @@ -283,6 +379,9 @@ msgstr "" msgid "BillingPlans|per user" msgstr "" +msgid "Begin with the selected commit" +msgstr "" + msgid "Branch" msgid_plural "Branches" msgstr[0] "" @@ -412,7 +511,7 @@ msgstr "" msgid "CI / CD" msgstr "" -msgid "CI configuration" +msgid "CI/CD configuration" msgstr "" msgid "CICD|Jobs" @@ -424,6 +523,9 @@ msgstr "" msgid "Cancel edit" msgstr "" +msgid "Cannot modify managed Kubernetes cluster" +msgstr "" + msgid "Change Weight" msgstr "" @@ -439,15 +541,24 @@ msgstr "" msgid "ChangeTypeAction|Revert" msgstr "" +msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes." +msgstr "" + msgid "Changelog" msgstr "" +msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision." +msgstr "" + msgid "Charts" msgstr "" msgid "Chat" msgstr "" +msgid "Check interval" +msgstr "" + msgid "Checking %{text} availability…" msgstr "" @@ -460,7 +571,19 @@ msgstr "" msgid "Cherry-pick this merge request" msgstr "" -msgid "Choose which groups you wish to replicate to this secondary node. Leave blank to replicate all." +msgid "Choose File ..." +msgstr "" + +msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request." +msgstr "" + +msgid "Choose file..." +msgstr "" + +msgid "Choose which groups you wish to synchronize to this secondary node." +msgstr "" + +msgid "Choose which shards you wish to synchronize to this secondary node." msgstr "" msgid "CiStatusLabel|canceled" @@ -517,79 +640,91 @@ msgstr "" msgid "CiStatus|running" msgstr "" -msgid "CircuitBreakerApiLink|circuitbreaker api" +msgid "CiVariables|Input variable key" msgstr "" -msgid "Clone repository" +msgid "CiVariables|Input variable value" msgstr "" -msgid "Close" +msgid "CiVariables|Remove variable row" msgstr "" -msgid "Cluster" +msgid "CiVariable|* (All environments)" msgstr "" -msgid "ClusterIntegration|%{appList} was successfully installed on your cluster" +msgid "CiVariable|All environments" msgstr "" -msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which incur additional costs. See %{pricingLink}" +msgid "CiVariable|Create wildcard" msgstr "" -msgid "ClusterIntegration|API URL" +msgid "CiVariable|Error occured while saving variables" msgstr "" -msgid "ClusterIntegration|Active" +msgid "CiVariable|New environment" msgstr "" -msgid "ClusterIntegration|Add an existing cluster" +msgid "CiVariable|Protected" msgstr "" -msgid "ClusterIntegration|Add cluster" +msgid "CiVariable|Search environments" msgstr "" -msgid "ClusterIntegration|All" +msgid "CiVariable|Toggle protected" msgstr "" -msgid "ClusterIntegration|Applications" +msgid "CiVariable|Validation failed" msgstr "" -msgid "ClusterIntegration|CA Certificate" +msgid "CircuitBreakerApiLink|circuitbreaker api" msgstr "" -msgid "ClusterIntegration|Certificate Authority bundle (PEM format)" +msgid "Click to expand text" msgstr "" -msgid "ClusterIntegration|Choose how to set up cluster integration" +msgid "Clone repository" msgstr "" -msgid "ClusterIntegration|Cluster" +msgid "Close" msgstr "" -msgid "ClusterIntegration|Cluster details" +msgid "Closed" msgstr "" -msgid "ClusterIntegration|Cluster integration" +msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Cluster integration is disabled for this project." +msgid "ClusterIntegration|API URL" msgstr "" -msgid "ClusterIntegration|Cluster integration is enabled for this project." +msgid "ClusterIntegration|Add Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab's connection to it." +msgid "ClusterIntegration|Add an existing Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Cluster is being created on Google Kubernetes Engine..." +msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration" +msgstr "" + +msgid "ClusterIntegration|Applications" +msgstr "" + +msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster." +msgstr "" + +msgid "ClusterIntegration|CA Certificate" +msgstr "" + +msgid "ClusterIntegration|Certificate Authority bundle (PEM format)" msgstr "" -msgid "ClusterIntegration|Cluster name" +msgid "ClusterIntegration|Choose how to set up Kubernetes cluster integration" msgstr "" -msgid "ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine. Refresh the page to see cluster's details" +msgid "ClusterIntegration|Choose which of your project's environments will use this Kubernetes cluster." msgstr "" -msgid "ClusterIntegration|Clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" +msgid "ClusterIntegration|Control how your Kubernetes cluster integrates with GitLab" msgstr "" msgid "ClusterIntegration|Copy API URL" @@ -598,37 +733,34 @@ msgstr "" msgid "ClusterIntegration|Copy CA Certificate" msgstr "" -msgid "ClusterIntegration|Copy Token" +msgid "ClusterIntegration|Copy Kubernetes cluster name" msgstr "" -msgid "ClusterIntegration|Copy cluster name" +msgid "ClusterIntegration|Copy Token" msgstr "" -msgid "ClusterIntegration|Create a new cluster on Google Engine right from GitLab" +msgid "ClusterIntegration|Create Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Create cluster" +msgid "ClusterIntegration|Create Kubernetes cluster on Google Kubernetes Engine" msgstr "" -msgid "ClusterIntegration|Create cluster on Google Kubernetes Engine" +msgid "ClusterIntegration|Create a new Kubernetes cluster on Google Kubernetes Engine right from GitLab" msgstr "" msgid "ClusterIntegration|Create on GKE" msgstr "" -msgid "ClusterIntegration|Enable cluster integration" -msgstr "" - msgid "ClusterIntegration|Enter the details for an existing Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Enter the details for your cluster" +msgid "ClusterIntegration|Enter the details for your Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Environment pattern" +msgid "ClusterIntegration|Environment scope" msgstr "" -msgid "ClusterIntegration|GKE pricing" +msgid "ClusterIntegration|GitLab Integration" msgstr "" msgid "ClusterIntegration|GitLab Runner" @@ -646,64 +778,94 @@ msgstr "" msgid "ClusterIntegration|Helm Tiller" msgstr "" -msgid "ClusterIntegration|Inactive" -msgstr "" - msgid "ClusterIntegration|Ingress" msgstr "" msgid "ClusterIntegration|Install" msgstr "" -msgid "ClusterIntegration|Install applications on your cluster. Read more about %{helpLink}" -msgstr "" - msgid "ClusterIntegration|Installed" msgstr "" msgid "ClusterIntegration|Installing" msgstr "" -msgid "ClusterIntegration|Integrate cluster automation" +msgid "ClusterIntegration|Integrate Kubernetes cluster automation" +msgstr "" + +msgid "ClusterIntegration|Integration status" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster details" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is disabled for this project." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project. Disabling this integration will not affect your Kubernetes cluster, it will only temporarily turn off GitLab's connection to it." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster name" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project" msgstr "" msgid "ClusterIntegration|Learn more about %{link_to_documentation}" msgstr "" -msgid "ClusterIntegration|Learn more about Clusters" +msgid "ClusterIntegration|Learn more about Kubernetes" msgstr "" -msgid "ClusterIntegration|Machine type" +msgid "ClusterIntegration|Learn more about environments" msgstr "" -msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create clusters" +msgid "ClusterIntegration|Machine type" msgstr "" -msgid "ClusterIntegration|Manage cluster integration on your GitLab project" +msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters" msgstr "" -msgid "ClusterIntegration|Manage your cluster by visiting %{link_gke}" +msgid "ClusterIntegration|Manage" msgstr "" -msgid "ClusterIntegration|Multiple clusters are available in GitLab Entreprise Edition Premium and Ultimate" +msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}" msgstr "" -msgid "ClusterIntegration|Note:" +msgid "ClusterIntegration|More information" msgstr "" -msgid "ClusterIntegration|Number of nodes" +msgid "ClusterIntegration|Multiple Kubernetes clusters are available in GitLab Enterprise Edition Premium and Ultimate" msgstr "" -msgid "ClusterIntegration|Please enter access information for your cluster. If you need help, you can read our %{link_to_help_page} on clusters" +msgid "ClusterIntegration|Note:" msgstr "" -msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:" +msgid "ClusterIntegration|Number of nodes" msgstr "" -msgid "ClusterIntegration|Problem setting up the cluster" +msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes" msgstr "" -msgid "ClusterIntegration|Problem setting up the clusters list" +msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:" msgstr "" msgid "ClusterIntegration|Project ID" @@ -715,16 +877,19 @@ msgstr "" msgid "ClusterIntegration|Project namespace (optional, unique)" msgstr "" -msgid "ClusterIntegration|Read our %{link_to_help_page} on cluster integration." +msgid "ClusterIntegration|Prometheus" msgstr "" -msgid "ClusterIntegration|Remove cluster integration" +msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration." +msgstr "" + +msgid "ClusterIntegration|Remove Kubernetes cluster integration" msgstr "" msgid "ClusterIntegration|Remove integration" msgstr "" -msgid "ClusterIntegration|Removing cluster integration will remove the cluster configuration you have added to this project. It will not delete your cluster on Google Kubernetes Engine." +msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster." msgstr "" msgid "ClusterIntegration|Request to begin installing failed" @@ -733,7 +898,7 @@ msgstr "" msgid "ClusterIntegration|Save changes" msgstr "" -msgid "ClusterIntegration|See and edit the details for your cluster" +msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster" msgstr "" msgid "ClusterIntegration|See machine types" @@ -754,25 +919,25 @@ msgstr "" msgid "ClusterIntegration|Something went wrong on our end." msgstr "" -msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine" +msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine" msgstr "" msgid "ClusterIntegration|Something went wrong while installing %{title}" msgstr "" -msgid "ClusterIntegration|There are no clusters to show" +msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below" msgstr "" -msgid "ClusterIntegration|This account must have permissions to create a cluster in the %{link_to_container_project} specified below" +msgid "ClusterIntegration|Toggle Kubernetes Cluster" msgstr "" -msgid "ClusterIntegration|Toggle Cluster" +msgid "ClusterIntegration|Toggle Kubernetes cluster" msgstr "" msgid "ClusterIntegration|Token" msgstr "" -msgid "ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." +msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." msgstr "" msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}" @@ -784,7 +949,7 @@ msgstr "" msgid "ClusterIntegration|access to Google Kubernetes Engine" msgstr "" -msgid "ClusterIntegration|cluster" +msgid "ClusterIntegration|check the pricing here" msgstr "" msgid "ClusterIntegration|documentation" @@ -802,6 +967,9 @@ msgstr "" msgid "ClusterIntegration|properly configured" msgstr "" +msgid "Collapse" +msgstr "" + msgid "Comments" msgstr "" @@ -820,6 +988,9 @@ msgstr "" msgid "Commit message" msgstr "" +msgid "Commit statistics for %{ref} %{start_time} - %{end_time}" +msgstr "" + msgid "CommitBoxTitle|Commit" msgstr "" @@ -832,15 +1003,57 @@ msgstr "" msgid "Commits feed" msgstr "" +msgid "Commits per day hour (UTC)" +msgstr "" + +msgid "Commits per day of month" +msgstr "" + +msgid "Commits per weekday" +msgstr "" + +msgid "Commits|An error occurred while fetching merge requests data." +msgstr "" + +msgid "Commits|Commit: %{commitText}" +msgstr "" + msgid "Commits|History" msgstr "" +msgid "Commits|No related merge requests found" +msgstr "" + msgid "Committed by" msgstr "" msgid "Compare" msgstr "" +msgid "Compare Git revisions" +msgstr "" + +msgid "Compare Revisions" +msgstr "" + +msgid "CompareBranches|%{source_branch} and %{target_branch} are the same." +msgstr "" + +msgid "CompareBranches|Compare" +msgstr "" + +msgid "CompareBranches|Source" +msgstr "" + +msgid "CompareBranches|Target" +msgstr "" + +msgid "CompareBranches|There isn't anything to compare." +msgstr "" + +msgid "Confidentiality" +msgstr "" + msgid "Container Registry" msgstr "" @@ -892,6 +1105,9 @@ msgstr "" msgid "Contributors" msgstr "" +msgid "ContributorsPage|%{startDate} – %{endDate}" +msgstr "" + msgid "ContributorsPage|Building repository graph." msgstr "" @@ -913,9 +1129,18 @@ msgstr "" msgid "Copy URL to clipboard" msgstr "" +msgid "Copy branch name to clipboard" +msgstr "" + msgid "Copy commit SHA to clipboard" msgstr "" +msgid "Copy reference to clipboard" +msgstr "" + +msgid "Create" +msgstr "" + msgid "Create New Directory" msgstr "" @@ -934,6 +1159,9 @@ msgstr "" msgid "Create file" msgstr "" +msgid "Create lists from labels. Issues with that label appear in that list." +msgstr "" + msgid "Create merge request" msgstr "" @@ -946,6 +1174,9 @@ msgstr "" msgid "Create new file" msgstr "" +msgid "Create new label" +msgstr "" + msgid "Create new..." msgstr "" @@ -967,6 +1198,9 @@ msgstr "" msgid "Cron syntax" msgstr "" +msgid "Current node" +msgstr "" + msgid "Custom notification events" msgstr "" @@ -976,9 +1210,6 @@ msgstr "" msgid "Cycle Analytics" msgstr "" -msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project." -msgstr "" - msgid "CycleAnalyticsStage|Code" msgstr "" @@ -1036,12 +1267,21 @@ msgstr "" msgid "Details" msgstr "" +msgid "Diffs|No file name available" +msgstr "" + msgid "Directory name" msgstr "" +msgid "Disable" +msgstr "" + msgid "Discard changes" msgstr "" +msgid "Discover GitLab Geo." +msgstr "" + msgid "Dismiss Cycle Analytics introduction box" msgstr "" @@ -1078,15 +1318,24 @@ msgstr "" msgid "DownloadSource|Download" msgstr "" +msgid "Due date" +msgstr "" + msgid "Edit" msgstr "" msgid "Edit Pipeline Schedule %{id}" msgstr "" +msgid "Edit files in the editor and commit changes here" +msgstr "" + msgid "Emails" msgstr "" +msgid "Enable" +msgstr "" + msgid "Environments|An error occurred while fetching the environments." msgstr "" @@ -1105,9 +1354,6 @@ msgstr "" msgid "Environments|Environments" msgstr "" -msgid "Environments|Environments are places where code gets deployed, such as staging or production." -msgstr "" - msgid "Environments|Job" msgstr "" @@ -1150,9 +1396,33 @@ msgstr "" msgid "Error creating epic" msgstr "" +msgid "Error fetching contributors data." +msgstr "" + +msgid "Error fetching labels." +msgstr "" + +msgid "Error fetching network graph." +msgstr "" + +msgid "Error fetching refs" +msgstr "" + +msgid "Error fetching usage ping data." +msgstr "" + msgid "Error occurred when toggling the notification subscription" msgstr "" +msgid "Error saving label update." +msgstr "" + +msgid "Error updating status for all todos." +msgstr "" + +msgid "Error updating todo status." +msgstr "" + msgid "EventFilterBy|Filter by all" msgstr "" @@ -1180,6 +1450,9 @@ msgstr "" msgid "Every week (Sundays at 4:00am)" msgstr "" +msgid "Expand" +msgstr "" + msgid "Explore projects" msgstr "" @@ -1198,6 +1471,9 @@ msgstr "" msgid "February" msgstr "" +msgid "Fields on this page are now uneditable, you can configure" +msgstr "" + msgid "File name" msgstr "" @@ -1243,10 +1519,10 @@ msgstr "" msgid "GPG Keys" msgstr "" -msgid "Geo Nodes" +msgid "Generate a default set of labels" msgstr "" -msgid "GeoNodeSyncStatus|Failed" +msgid "Geo Nodes" msgstr "" msgid "GeoNodeSyncStatus|Node is failing or broken." @@ -1255,16 +1531,100 @@ msgstr "" msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage." msgstr "" -msgid "GeoNodeSyncStatus|Out of sync" +msgid "GeoNodes|Database replication lag:" +msgstr "" + +msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?" +msgstr "" + +msgid "GeoNodes|Does not match the primary storage configuration" +msgstr "" + +msgid "GeoNodes|Failed" +msgstr "" + +msgid "GeoNodes|Full" +msgstr "" + +msgid "GeoNodes|GitLab version does not match the primary node version" +msgstr "" + +msgid "GeoNodes|GitLab version:" +msgstr "" + +msgid "GeoNodes|Health status:" +msgstr "" + +msgid "GeoNodes|Last event ID processed by cursor:" +msgstr "" + +msgid "GeoNodes|Last event ID seen from primary:" +msgstr "" + +msgid "GeoNodes|Loading nodes" +msgstr "" + +msgid "GeoNodes|Local Attachments:" +msgstr "" + +msgid "GeoNodes|Local LFS objects:" +msgstr "" + +msgid "GeoNodes|Local job artifacts:" +msgstr "" + +msgid "GeoNodes|New node" +msgstr "" + +msgid "GeoNodes|Out of sync" +msgstr "" + +msgid "GeoNodes|Replication slot WAL:" msgstr "" -msgid "GeoNodeSyncStatus|Synced" +msgid "GeoNodes|Replication slots:" +msgstr "" + +msgid "GeoNodes|Repositories:" +msgstr "" + +msgid "GeoNodes|Selective" +msgstr "" + +msgid "GeoNodes|Storage config:" +msgstr "" + +msgid "GeoNodes|Sync settings:" +msgstr "" + +msgid "GeoNodes|Synced" +msgstr "" + +msgid "GeoNodes|Unused slots" +msgstr "" + +msgid "GeoNodes|Used slots" +msgstr "" + +msgid "GeoNodes|Wikis:" +msgstr "" + +msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS." +msgstr "" + +msgid "Geo|All projects" msgstr "" msgid "Geo|File sync capacity" msgstr "" -msgid "Geo|Groups to replicate" +msgid "Geo|Groups to synchronize" +msgstr "" + +msgid "Geo|Projects in certain groups" +msgstr "" + +msgid "Geo|Projects in certain storage shards" msgstr "" msgid "Geo|Repository sync capacity" @@ -1273,12 +1633,24 @@ msgstr "" msgid "Geo|Select groups to replicate." msgstr "" +msgid "Geo|Shards to synchronize" +msgstr "" + +msgid "Git revision" +msgstr "" + msgid "Git storage health information has been reset" msgstr "" +msgid "Git version" +msgstr "" + msgid "GitLab Runner section" msgstr "" +msgid "Gitaly Servers" +msgstr "" + msgid "Go to your fork" msgstr "" @@ -1288,6 +1660,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it!" +msgstr "" + msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" msgstr "" @@ -1324,7 +1699,7 @@ msgstr "" msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group." msgstr "" -msgid "GroupsTree|Are you sure you want to leave the \"${this.group.fullName}\" group?" +msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?" msgstr "" msgid "GroupsTree|Create a project in this group." @@ -1375,6 +1750,12 @@ msgstr "" msgid "HealthCheck|Unhealthy" msgstr "" +msgid "Hide value" +msgid_plural "Hide values" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + msgid "History" msgstr "" @@ -1402,6 +1783,12 @@ msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgid "Instance does not support multiple Kubernetes clusters" +msgstr "" + +msgid "Interested parties can even contribute by pushing commits if they want to." +msgstr "" + msgid "Internal - The group and any internal projects can be viewed by any logged in user." msgstr "" @@ -1429,6 +1816,9 @@ msgstr "" msgid "Issues" msgstr "" +msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable." +msgstr "" + msgid "Jan" msgstr "" @@ -1447,6 +1837,27 @@ msgstr "" msgid "June" msgstr "" +msgid "Kubernetes" +msgstr "" + +msgid "Kubernetes Cluster" +msgstr "" + +msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}" +msgstr "" + +msgid "Kubernetes cluster integration was not removed." +msgstr "" + +msgid "Kubernetes cluster integration was successfully removed." +msgstr "" + +msgid "Kubernetes cluster was successfully updated." +msgstr "" + +msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page" +msgstr "" + msgid "LFSStatus|Disabled" msgstr "" @@ -1456,6 +1867,9 @@ msgstr "" msgid "Labels" msgstr "" +msgid "Labels can be applied to issues and merge requests to categorize them." +msgstr "" + msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "" @@ -1486,6 +1900,9 @@ msgstr "" msgid "LastPushEvent|at" msgstr "" +msgid "Learn more" +msgstr "" + msgid "Learn more in the" msgstr "" @@ -1504,15 +1921,18 @@ msgstr "" msgid "License" msgstr "" -msgid "Limited to showing %d event at most" -msgid_plural "Limited to showing %d events at most" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgid "Loading the GitLab IDE..." +msgstr "" msgid "Lock" msgstr "" +msgid "Lock %{issuableDisplayName}" +msgstr "" + +msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment." +msgstr "" + msgid "Locked" msgstr "" @@ -1522,12 +1942,21 @@ msgstr "" msgid "Login" msgstr "" +msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos." +msgstr "" + +msgid "Manage labels" +msgstr "" + msgid "Mar" msgstr "" msgid "March" msgstr "" +msgid "Mark done" +msgstr "" + msgid "Maximum git storage failures" msgstr "" @@ -1540,6 +1969,9 @@ msgstr "" msgid "Members" msgstr "" +msgid "Merge Request" +msgstr "" + msgid "Merge Requests" msgstr "" @@ -1549,9 +1981,30 @@ msgstr "" msgid "Merge request" msgstr "" +msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others" +msgstr "" + +msgid "Merged" +msgstr "" + msgid "Messages" msgstr "" +msgid "Milestone" +msgstr "" + +msgid "Milestones|Delete milestone" +msgstr "" + +msgid "Milestones|Delete milestone %{milestoneTitle}?" +msgstr "" + +msgid "Milestones|Failed to delete milestone %{milestoneTitle}" +msgstr "" + +msgid "Milestones|Milestone %{milestoneTitle} was not found" +msgstr "" + msgid "MissingSSHKeyWarningLink|add an SSH key" msgstr "" @@ -1561,10 +2014,16 @@ msgstr "" msgid "More information is available|here" msgstr "" +msgid "Move" +msgstr "" + +msgid "Move issue" +msgstr "" + msgid "Multiple issue boards" msgstr "" -msgid "New Cluster" +msgid "Name new label" msgstr "" msgid "New Issue" @@ -1573,6 +2032,12 @@ msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgid "New Kubernetes Cluster" +msgstr "" + +msgid "New Kubernetes cluster" +msgstr "" + msgid "New Pipeline Schedule" msgstr "" @@ -1597,6 +2062,9 @@ msgstr "" msgid "New issue" msgstr "" +msgid "New label" +msgstr "" + msgid "New merge request" msgstr "" @@ -1615,7 +2083,22 @@ msgstr "" msgid "New tag" msgstr "" -msgid "No container images stored for this project. Add one by following the instructions above." +msgid "No assignee" +msgstr "" + +msgid "No changes" +msgstr "" + +msgid "No connection could be made to a Gitaly Server, please check your logs!" +msgstr "" + +msgid "No due date" +msgstr "" + +msgid "No estimate or time spent" +msgstr "" + +msgid "No file chosen" msgstr "" msgid "No repository" @@ -1630,9 +2113,15 @@ msgstr "" msgid "None" msgstr "" +msgid "Not allowed to merge" +msgstr "" + msgid "Not available" msgstr "" +msgid "Not confidential" +msgstr "" + msgid "Not enough data" msgstr "" @@ -1693,6 +2182,12 @@ msgstr "" msgid "Notifications" msgstr "" +msgid "Notifications off" +msgstr "" + +msgid "Notifications on" +msgstr "" + msgid "Nov" msgstr "" @@ -1702,7 +2197,7 @@ msgstr "" msgid "Number of access attempts" msgstr "" -msgid "Number of failures before backing off" +msgid "OK" msgstr "" msgid "Oct" @@ -1717,6 +2212,9 @@ msgstr "" msgid "Only project members can comment." msgstr "" +msgid "Open" +msgstr "" + msgid "Opened" msgstr "" @@ -1750,9 +2248,6 @@ msgstr "" msgid "Password" msgstr "" -msgid "People without permission will never get a notification and won\\'t be able to comment." -msgstr "" - msgid "Pipeline" msgstr "" @@ -1795,12 +2290,6 @@ msgstr "" msgid "PipelineSchedules|Inactive" msgstr "" -msgid "PipelineSchedules|Input variable key" -msgstr "" - -msgid "PipelineSchedules|Input variable value" -msgstr "" - msgid "PipelineSchedules|Next Run" msgstr "" @@ -1810,9 +2299,6 @@ msgstr "" msgid "PipelineSchedules|Provide a short description for this pipeline" msgstr "" -msgid "PipelineSchedules|Remove variable row" -msgstr "" - msgid "PipelineSchedules|Take ownership" msgstr "" @@ -1840,6 +2326,12 @@ msgstr "" msgid "Pipelines for last year" msgstr "" +msgid "Pipelines|Build with confidence" +msgstr "" + +msgid "Pipelines|Get started with Pipelines" +msgstr "" + msgid "Pipeline|all" msgstr "" @@ -1852,12 +2344,21 @@ msgstr "" msgid "Pipeline|with stages" msgstr "" +msgid "Play" +msgstr "" + +msgid "Please <a href=%{link_to_billing} target=\"_blank\" rel=\"noopener noreferrer\">enable billing for one of your projects to be able to create a Kubernetes cluster</a>, then try again." +msgstr "" + msgid "Please solve the reCAPTCHA" msgstr "" msgid "Preferences" msgstr "" +msgid "Primary" +msgstr "" + msgid "Private - Project access must be granted explicitly to each user." msgstr "" @@ -1903,6 +2404,9 @@ msgstr "" msgid "Profiles|your account" msgstr "" +msgid "Programming languages used in this repository" +msgstr "" + msgid "Project '%{project_name}' is in the process of being deleted." msgstr "" @@ -1918,6 +2422,15 @@ msgstr "" msgid "Project access must be granted explicitly to each user." msgstr "" +msgid "Project avatar" +msgstr "" + +msgid "Project avatar in repository: %{link}" +msgstr "" + +msgid "Project cache successfully reset." +msgstr "" + msgid "Project details" msgstr "" @@ -1936,6 +2449,21 @@ msgstr "" msgid "ProjectActivityRSS|Subscribe" msgstr "" +msgid "ProjectCreationLevel|Allowed to create projects" +msgstr "" + +msgid "ProjectCreationLevel|Default project creation protection" +msgstr "" + +msgid "ProjectCreationLevel|Developers + Masters" +msgstr "" + +msgid "ProjectCreationLevel|Masters" +msgstr "" + +msgid "ProjectCreationLevel|No one" +msgstr "" + msgid "ProjectFeature|Disabled" msgstr "" @@ -1960,15 +2488,9 @@ msgstr "" msgid "ProjectSettings|Contact an admin to change this setting." msgstr "" -msgid "ProjectSettings|Immediately run a pipeline on the default branch" -msgstr "" - msgid "ProjectSettings|Only signed commits can be pushed to this repository." msgstr "" -msgid "ProjectSettings|Problem setting up the CI/CD settings JavaScript" -msgstr "" - msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin." msgstr "" @@ -2032,12 +2554,15 @@ msgstr "" msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/" msgstr "" -msgid "PrometheusService|Prometheus monitoring" +msgid "PrometheusService|Time-series monitoring service" msgstr "" msgid "PrometheusService|View environments" msgstr "" +msgid "Protip:" +msgstr "" + msgid "Public - The group and any public projects can be viewed without any authentication." msgstr "" @@ -2053,6 +2578,9 @@ msgstr "" msgid "PushRule|Committer restriction" msgstr "" +msgid "Quick actions can be used in the issues description and comment boxes." +msgstr "" + msgid "Read more" msgstr "" @@ -2065,6 +2593,12 @@ msgstr "" msgid "RefSwitcher|Tags" msgstr "" +msgid "Reference:" +msgstr "" + +msgid "Register / Sign In" +msgstr "" + msgid "Registry" msgstr "" @@ -2089,9 +2623,18 @@ msgstr "" msgid "Remind later" msgstr "" +msgid "Remove" +msgstr "" + +msgid "Remove avatar" +msgstr "" + msgid "Remove project" msgstr "" +msgid "Repair authentication" +msgstr "" + msgid "Repository" msgstr "" @@ -2107,6 +2650,12 @@ msgstr "" msgid "Reset runners registration token" msgstr "" +msgid "Reveal value" +msgid_plural "Reveal values" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + msgid "Revert this commit" msgstr "" @@ -2116,15 +2665,15 @@ msgstr "" msgid "SSH Keys" msgstr "" -msgid "Save" -msgstr "" - msgid "Save changes" msgstr "" msgid "Save pipeline schedule" msgstr "" +msgid "Save variables" +msgstr "" + msgid "Schedule a new pipeline" msgstr "" @@ -2140,37 +2689,58 @@ msgstr "" msgid "Search branches and tags" msgstr "" -msgid "Seconds before reseting failure information" +msgid "Search milestones" +msgstr "" + +msgid "Search project" msgstr "" -msgid "Seconds to wait after a storage failure" +msgid "Search users" +msgstr "" + +msgid "Seconds before reseting failure information" msgstr "" msgid "Seconds to wait for a storage access attempt" msgstr "" +msgid "Secret variables" +msgstr "" + msgid "Select Archive Format" msgstr "" msgid "Select a timezone" msgstr "" +msgid "Select assignee" +msgstr "" + +msgid "Select branch/tag" +msgstr "" + msgid "Select target branch" msgstr "" +msgid "Selective synchronization" +msgstr "" + msgid "Sep" msgstr "" msgid "September" msgstr "" +msgid "Server version" +msgstr "" + msgid "Service Templates" msgstr "" msgid "Set a password on your account to pull or push via %{protocol}." msgstr "" -msgid "Set up CI" +msgid "Set up CI/CD" msgstr "" msgid "Set up Koding" @@ -2185,6 +2755,15 @@ msgstr "" msgid "Settings" msgstr "" +msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero." +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset pipeline minutes" +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes" +msgstr "" + msgid "Show parent pages" msgstr "" @@ -2200,9 +2779,6 @@ msgstr[2] "" msgid "Sidebar|Change weight" msgstr "" -msgid "Sidebar|Edit" -msgstr "" - msgid "Sidebar|No" msgstr "" @@ -2215,18 +2791,30 @@ msgstr "" msgid "Snippets" msgstr "" +msgid "Something went wrong on our end" +msgstr "" + msgid "Something went wrong on our end." msgstr "" +msgid "Something went wrong trying to change the confidentiality of this issue" +msgstr "" + msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}" msgstr "" +msgid "Something went wrong when toggling the button" +msgstr "" + msgid "Something went wrong while fetching the projects." msgstr "" msgid "Something went wrong while fetching the registry list." msgstr "" +msgid "Something went wrong. Please try again." +msgstr "" + msgid "Sort by" msgstr "" @@ -2356,10 +2944,10 @@ msgstr "" msgid "Stopped" msgstr "" -msgid "Subgroups" +msgid "Storage" msgstr "" -msgid "Subscribe" +msgid "Subgroups" msgstr "" msgid "Switch branch/tag" @@ -2458,7 +3046,10 @@ msgstr "" msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project." msgstr "" -msgid "The circuitbreaker backoff threshold should be lower than the failure count threshold" +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project" +msgstr "" + +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project." msgstr "" msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request." @@ -2473,10 +3064,10 @@ msgstr "" msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage." msgstr "" -msgid "The number of attempts GitLab will make to access a storage." +msgid "The maximum file size allowed is 200KB." msgstr "" -msgid "The number of failures after which GitLab will start temporarily disabling access to a storage shard on a host" +msgid "The number of attempts GitLab will make to access a storage." msgstr "" msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}." @@ -2485,9 +3076,6 @@ msgstr "" msgid "The phase of the development lifecycle." msgstr "" -msgid "The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user." -msgstr "" - msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit." msgstr "" @@ -2518,19 +3106,46 @@ msgstr "" msgid "The time in seconds GitLab will try to access storage. After this time a timeout error will be raised." msgstr "" +msgid "The time in seconds between storage checks. When a previous check did complete yet, GitLab will skip a check." +msgstr "" + msgid "The time taken by each data entry gathered by that stage." msgstr "" msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6." msgstr "" +msgid "There are no issues to show" +msgstr "" + +msgid "There are no merge requests to show" +msgstr "" + msgid "There are problems accessing Git storage: " msgstr "" +msgid "There was an error loading users activity calendar." +msgstr "" + +msgid "There was an error saving your notification settings." +msgstr "" + +msgid "There was an error subscribing to this label." +msgstr "" + +msgid "There was an error when reseting email token." +msgstr "" + +msgid "There was an error when subscribing to this label." +msgstr "" + +msgid "There was an error when unsubscribing from this label." +msgstr "" + msgid "This board\\'s scope is reduced" msgstr "" -msgid "This branch has changed since you started editing. Would you like to create a new branch?" +msgid "This directory" msgstr "" msgid "This is a confidential issue." @@ -2539,18 +3154,45 @@ msgstr "" msgid "This is the author's first Merge Request to this project." msgstr "" +msgid "This issue is confidential" +msgstr "" + msgid "This issue is confidential and locked." msgstr "" msgid "This issue is locked." msgstr "" +msgid "This job depends on a user to trigger its process. Often they are used to deploy code to production environments" +msgstr "" + +msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered" +msgstr "" + +msgid "This job has not been triggered yet" +msgstr "" + +msgid "This job has not started yet" +msgstr "" + +msgid "This job is in pending state and is waiting to be picked by a runner" +msgstr "" + +msgid "This job requires a manual action" +msgstr "" + msgid "This means you can not push code until you create an empty repository or import existing one." msgstr "" msgid "This merge request is locked." msgstr "" +msgid "This project" +msgstr "" + +msgid "This repository" +msgstr "" + msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here." msgstr "" @@ -2563,9 +3205,21 @@ msgstr "" msgid "Time between merge request creation and merge/close" msgstr "" +msgid "Time tracking" +msgstr "" + msgid "Time until first merge request" msgstr "" +msgid "TimeTrackingEstimated|Est" +msgstr "" + +msgid "TimeTracking|Estimated:" +msgstr "" + +msgid "TimeTracking|Spent" +msgstr "" + msgid "Timeago|%s days ago" msgstr "" @@ -2707,6 +3361,18 @@ msgstr "" msgid "Title" msgstr "" +msgid "Todo" +msgstr "" + +msgid "Toggle sidebar" +msgstr "" + +msgid "ToggleButton|Toggle Status: OFF" +msgstr "" + +msgid "ToggleButton|Toggle Status: ON" +msgstr "" + msgid "Total Time" msgstr "" @@ -2722,19 +3388,40 @@ msgstr "" msgid "Track groups of issues that share a theme, across projects and milestones" msgstr "" +msgid "Total: %{total}" +msgstr "" + +msgid "Track time with quick actions" +msgstr "" + +msgid "Trigger this manual action" +msgstr "" + msgid "Turn on Service Desk" msgstr "" +msgid "Type %{value} to confirm:" +msgstr "" + +msgid "Unable to reset project cache." +msgstr "" + +msgid "Unknown" +msgstr "" + msgid "Unlock" msgstr "" +msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment." +msgstr "" + msgid "Unlocked" msgstr "" msgid "Unstar" msgstr "" -msgid "Unsubscribe" +msgid "Up to date" msgstr "" msgid "Upgrade your plan to activate Advanced Global Search." @@ -2758,6 +3445,9 @@ msgstr "" msgid "Upload file" msgstr "" +msgid "Upload new avatar" +msgstr "" + msgid "UploadLink|click to upload" msgstr "" @@ -2770,9 +3460,15 @@ msgstr "" msgid "Use your global notification setting" msgstr "" +msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." +msgstr "" + msgid "View file @ " msgstr "" +msgid "View labels" +msgstr "" + msgid "View open merge request" msgstr "" @@ -2794,6 +3490,9 @@ msgstr "" msgid "Want to see the data? Please ask an administrator for access." msgstr "" +msgid "We could not verify that one of your projects on GCP has billing enabled. Please try again." +msgstr "" + msgid "We don't have enough data to show this stage." msgstr "" @@ -2806,9 +3505,6 @@ msgstr "" msgid "Weight" msgstr "" -msgid "When access to a storage fails. GitLab will prevent access to the storage for the time specified here. This allows the filesystem to recover. Repositories on failing shards are temporarly unavailable" -msgstr "" - msgid "Wiki" msgstr "" @@ -2827,6 +3523,12 @@ msgstr "" msgid "WikiClone|Start Gollum and edit locally" msgstr "" +msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title." +msgstr "" + +msgid "WikiEdit|There is already a page with the same title in that path." +msgstr "" + msgid "WikiEmptyPageError|You are not allowed to create wiki pages" msgstr "" @@ -2929,9 +3631,21 @@ msgstr "" msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" msgstr "" +msgid "You can also star a label to make it a priority label." +msgstr "" + +msgid "You can move around the graph by using the arrow keys." +msgstr "" + +msgid "You can move around the graph by using the arrow keys." +msgstr "" + msgid "You can only add files when you are on a branch" msgstr "" +msgid "You can only edit files when you are on a branch" +msgstr "" + msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead." msgstr "" @@ -2971,6 +3685,12 @@ msgstr "" msgid "You won't be able to pull or push project code via SSH until you add an SSH key to your profile" msgstr "" +msgid "You'll need to use different branch names to get a valid comparison." +msgstr "" + +msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure" +msgstr "" + msgid "Your comment will not be visible to the public." msgstr "" @@ -2983,27 +3703,222 @@ msgstr "" msgid "Your projects" msgstr "" +msgid "assign yourself" +msgstr "" + msgid "branch name" msgstr "" msgid "by" msgstr "" +msgid "ciReport|Code quality" +msgstr "" + +msgid "ciReport|DAST detected no alerts by analyzing the review app" +msgstr "" + +msgid "ciReport|Failed to load ${type} report" +msgstr "" + +msgid "ciReport|Fixed:" +msgstr "" + +msgid "ciReport|Instances" +msgstr "" + +msgid "ciReport|Learn more about whitelisting" +msgstr "" + +msgid "ciReport|Loading ${type} report" +msgstr "" + +msgid "ciReport|No changes to code quality" +msgstr "" + +msgid "ciReport|No changes to performance metrics" +msgstr "" + +msgid "ciReport|Performance metrics" +msgstr "" + +msgid "ciReport|SAST" +msgstr "" + +msgid "ciReport|SAST detected no security vulnerabilities" +msgstr "" + +msgid "ciReport|SAST:container no vulnerabilities were found" +msgstr "" + +msgid "ciReport|Show complete code vulnerabilities report" +msgstr "" + +msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}" +msgstr "" + msgid "commit" msgstr "" +msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue." +msgstr "" + +msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue." +msgstr "" + msgid "day" msgid_plural "days" msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command." +msgstr "" + +msgid "merge request" +msgid_plural "merge requests" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "mrWidget|Cancel automatic merge" +msgstr "" + +msgid "mrWidget|Check out branch" +msgstr "" + +msgid "mrWidget|Checking ability to merge automatically" +msgstr "" + +msgid "mrWidget|Cherry-pick" +msgstr "" + +msgid "mrWidget|Cherry-pick this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Closed" +msgstr "" + +msgid "mrWidget|Closed by" +msgstr "" + +msgid "mrWidget|Closes" +msgstr "" + +msgid "mrWidget|Did not close" +msgstr "" + +msgid "mrWidget|Email patches" +msgstr "" + +msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|Mentions" +msgstr "" + +msgid "mrWidget|Merge" +msgstr "" + +msgid "mrWidget|Merge failed." +msgstr "" + +msgid "mrWidget|Merge locally" +msgstr "" + +msgid "mrWidget|Merged by" +msgstr "" + +msgid "mrWidget|Plain diff" +msgstr "" + +msgid "mrWidget|Refresh" +msgstr "" + +msgid "mrWidget|Refresh now" +msgstr "" + +msgid "mrWidget|Refreshing now" +msgstr "" + +msgid "mrWidget|Remove Source Branch" +msgstr "" + +msgid "mrWidget|Remove source branch" +msgstr "" + +msgid "mrWidget|Request to merge" +msgstr "" + +msgid "mrWidget|Resolve conflicts" +msgstr "" + +msgid "mrWidget|Revert" +msgstr "" + +msgid "mrWidget|Revert this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Set by" +msgstr "" + +msgid "mrWidget|The changes were merged into" +msgstr "" + +msgid "mrWidget|The changes were not merged into" +msgstr "" + +msgid "mrWidget|The changes will be merged into" +msgstr "" + +msgid "mrWidget|The source branch has been removed" +msgstr "" + +msgid "mrWidget|The source branch is being removed" +msgstr "" + +msgid "mrWidget|The source branch will be removed" +msgstr "" + +msgid "mrWidget|The source branch will not be removed" +msgstr "" + +msgid "mrWidget|There are merge conflicts" +msgstr "" + +msgid "mrWidget|This merge request failed to be merged automatically" +msgstr "" + +msgid "mrWidget|This merge request is in the process of being merged" +msgstr "" + +msgid "mrWidget|This project is archived, write access has been disabled" +msgstr "" + +msgid "mrWidget|You can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|You can remove source branch now" +msgstr "" + +msgid "mrWidget|command line" +msgstr "" + +msgid "mrWidget|into" +msgstr "" + +msgid "mrWidget|to be merged automatically when the pipeline succeeds" +msgstr "" + msgid "new merge request" msgstr "" msgid "notification emails" msgstr "" +msgid "or" +msgstr "" + msgid "parent" msgid_plural "parents" msgstr[0] "" @@ -3016,12 +3931,21 @@ msgstr "" msgid "personal access token" msgstr "" +msgid "remove due date" +msgstr "" + msgid "source" msgstr "" +msgid "spendCommand|%{slash_command} will update the sum of the time spent." +msgstr "" + msgid "to help your contributors communicate effectively!" msgstr "" msgid "username" msgstr "" +msgid "uses Kubernetes clusters to deploy your code!" +msgstr "" + diff --git a/locale/pt_BR/gitlab.po b/locale/pt_BR/gitlab.po index 9fe1cc3c11a..5aef8f45234 100644 --- a/locale/pt_BR/gitlab.po +++ b/locale/pt_BR/gitlab.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab-ee\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-12-12 18:31+0000\n" -"PO-Revision-Date: 2018-01-05 04:41-0500\n" +"POT-Creation-Date: 2018-02-07 11:38-0600\n" +"PO-Revision-Date: 2018-02-12 04:01-0500\n" "Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n" "Language-Team: Portuguese, Brazilian\n" "Language: pt_BR\n" @@ -16,23 +16,41 @@ msgstr "" "X-Crowdin-Language: pt-BR\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" +msgid " and" +msgstr "" + msgid "%d commit" msgid_plural "%d commits" msgstr[0] "%d commit" msgstr[1] "%d commits" +msgid "%d commit behind" +msgid_plural "%d commits behind" +msgstr[0] "" +msgstr[1] "" + +msgid "%d issue" +msgid_plural "%d issues" +msgstr[0] "" +msgstr[1] "" + msgid "%d layer" msgid_plural "%d layers" msgstr[0] "%d camada" msgstr[1] "%d camadas" +msgid "%d merge request" +msgid_plural "%d merge requests" +msgstr[0] "" +msgstr[1] "" + msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "%s additional commits have been omitted to prevent performance issues." msgstr[0] "%s commit adicional foi omitido para prevenir problemas de performance." msgstr[1] "%s commits adicionais foram omitidos para prevenir problemas de performance." -msgid "%{commit_author_link} committed %{commit_timeago}" -msgstr "%{commit_author_link} fez commit %{commit_timeago}" +msgid "%{commit_author_link} authored %{commit_timeago}" +msgstr "" msgid "%{count} participant" msgid_plural "%{count} participants" @@ -45,9 +63,6 @@ msgstr "%{number_commits_behind} commits atrás de %{default_branch}, %{number_c msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will allow access on the next attempt." msgstr "%{number_of_failures} de %{maximum_failures} falhas. O GitLab permitirá o acesso na próxima tentativa." -msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will block access for %{number_of_seconds} seconds." -msgstr "%{number_of_failures} de %{maximum_failures} falhas. O GitLab irá bloquear o acesso por %{number_of_seconds} segundos." - msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved." msgstr "%{number_of_failures} de %{maximum_failures} falhas. O GitLab não tentará mais automaticamente. Redefina as informações de storage quando o problema for resolvido." @@ -121,24 +136,81 @@ msgstr "Adicionar Licença" msgid "Add new directory" msgstr "Adicionar novo diretório" +msgid "Add todo" +msgstr "" + +msgid "AdminArea|Stop all jobs" +msgstr "" + +msgid "AdminArea|Stop all jobs?" +msgstr "" + +msgid "AdminArea|Stop jobs" +msgstr "" + +msgid "AdminArea|Stopping jobs failed" +msgstr "" + +msgid "AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running." +msgstr "" + msgid "AdminHealthPageLink|health page" msgstr "página de saúde" +msgid "Advanced" +msgstr "" + msgid "Advanced settings" msgstr "Configurações avançadas" msgid "All" msgstr "Todos" +msgid "All changes are committed" +msgstr "" + +msgid "Allows you to add and manage Kubernetes clusters." +msgstr "" + +msgid "An error occurred previewing the blob" +msgstr "" + msgid "An error occurred when toggling the notification subscription" msgstr "Erro ao modificar notificação de assinatura" msgid "An error occurred when updating the issue weight" msgstr "Um erro aconteceu ao atualizar o peso da issue" +msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." +msgstr "" + +msgid "An error occurred while fetching markdown preview" +msgstr "" + msgid "An error occurred while fetching sidebar data" msgstr "Erro ao recuperar informações da barra lateral" +msgid "An error occurred while getting projects" +msgstr "" + +msgid "An error occurred while loading filenames" +msgstr "" + +msgid "An error occurred while rendering KaTeX" +msgstr "" + +msgid "An error occurred while rendering preview broadcast message" +msgstr "" + +msgid "An error occurred while retrieving calendar activity" +msgstr "" + +msgid "An error occurred while retrieving diff" +msgstr "" + +msgid "An error occurred while validating username" +msgstr "" + msgid "An error occurred. Please try again." msgstr "Ocorreu um erro. Tente novamente." @@ -163,9 +235,6 @@ msgstr "Tem certeza que deseja excluir este agendamento de pipeline?" msgid "Are you sure you want to discard your changes?" msgstr "Você tem certeza que deseja descartar suas alterações?" -msgid "Are you sure you want to leave this group?" -msgstr "Tem certeza que quer sair desse grupo?" - msgid "Are you sure you want to reset registration token?" msgstr "Você tem certeza que quer recriar o token de registro?" @@ -178,6 +247,21 @@ msgstr "Você tem certeza?" msgid "Artifacts" msgstr "Artefatos" +msgid "Assign custom color like #FF0000" +msgstr "" + +msgid "Assign labels" +msgstr "" + +msgid "Assign milestone" +msgstr "" + +msgid "Assign to" +msgstr "" + +msgid "Assignee" +msgstr "" + msgid "Attach a file by drag & drop or %{upload_link}" msgstr "Para anexar arquivo, arraste e solte ou %{upload_link}" @@ -193,15 +277,18 @@ msgstr "Log de autenticação" msgid "Author" msgstr "Autor" -msgid "Auto Review Apps and Auto Deploy need a domain name and the %{kubernetes} to work correctly." -msgstr "Apps de revisão automática e Auto Deploy precisam de um nome de domÃnio e o %{kubernetes} para que funcione corretamente." +msgid "Authors: %{authors}" +msgstr "" + +msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." +msgstr "" + +msgid "Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly." +msgstr "" msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly." msgstr "Apps de revisão automática e Auto Deploy precisam de um nome de domÃnio para que funcione corretamente." -msgid "Auto Review Apps and Auto Deploy need the %{kubernetes} to work correctly." -msgstr "Apps de revisão automática e Auto Deploy precisam do %{kubernetes} para que funcione corretamente." - msgid "AutoDevOps|Auto DevOps (Beta)" msgstr "Auto DevOps (Beta)" @@ -223,6 +310,12 @@ msgstr "Você pode ativar %{link_to_settings} para esse projeto." msgid "Available" msgstr "DisponÃvel" +msgid "Avatar will be removed. Are you sure?" +msgstr "" + +msgid "Average per day: %{average}" +msgstr "" + msgid "Billing" msgstr "Cobrança" @@ -277,6 +370,9 @@ msgstr "pago %{price_per_year} anualmente" msgid "BillingPlans|per user" msgstr "por usuário" +msgid "Begin with the selected commit" +msgstr "" + msgid "Branch" msgid_plural "Branches" msgstr[0] "Branch" @@ -370,13 +466,13 @@ msgid "Branches|To confirm, type %{branch_name_confirmation}:" msgstr "Para confirmar, digite %{branch_name_confirmation}:" msgid "Branches|To discard the local changes and overwrite the branch with the upstream version, delete it here and choose 'Update Now' above." -msgstr "" +msgstr "Para descartar as mudanças locais e sobrescrever a branch com a versão de upstream, apague-o aqui e escolha 'Atualizar agora', acima." msgid "Branches|You’re about to permanently delete the protected branch %{branch_name}." msgstr "Você irá apagar irreparavelmente a branch protegida '%{branch_name}'." msgid "Branches|diverged from upstream" -msgstr "" +msgstr "divergiu do upstream" msgid "Branches|merged" msgstr "merge realizado" @@ -405,8 +501,8 @@ msgstr "por" msgid "CI / CD" msgstr "CI / CD" -msgid "CI configuration" -msgstr "Configuração da IC" +msgid "CI/CD configuration" +msgstr "" msgid "CICD|Jobs" msgstr "Jobs" @@ -417,6 +513,9 @@ msgstr "Cancelar" msgid "Cancel edit" msgstr "Cancelar edição" +msgid "Cannot modify managed Kubernetes cluster" +msgstr "" + msgid "Change Weight" msgstr "Alterar peso" @@ -432,15 +531,24 @@ msgstr "Cherry-pick" msgid "ChangeTypeAction|Revert" msgstr "Reverter" +msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes." +msgstr "" + msgid "Changelog" msgstr "Registro de mudanças" +msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision." +msgstr "" + msgid "Charts" msgstr "Gráficos" msgid "Chat" msgstr "Bate-papo" +msgid "Check interval" +msgstr "" + msgid "Checking %{text} availability…" msgstr "Verificando disponibilidade de %{text}…" @@ -453,8 +561,20 @@ msgstr "Cherry-pick esse commit" msgid "Cherry-pick this merge request" msgstr "Cherry-pick esse merge request" -msgid "Choose which groups you wish to replicate to this secondary node. Leave blank to replicate all." -msgstr "Escolha quais grupos você deseja replicar para este nó secundário. Deixe em branco para replicar tudo." +msgid "Choose File ..." +msgstr "" + +msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request." +msgstr "" + +msgid "Choose file..." +msgstr "" + +msgid "Choose which groups you wish to synchronize to this secondary node." +msgstr "" + +msgid "Choose which shards you wish to synchronize to this secondary node." +msgstr "" msgid "CiStatusLabel|canceled" msgstr "cancelado" @@ -510,80 +630,92 @@ msgstr "ignorado" msgid "CiStatus|running" msgstr "executando" +msgid "CiVariables|Input variable key" +msgstr "" + +msgid "CiVariables|Input variable value" +msgstr "" + +msgid "CiVariables|Remove variable row" +msgstr "" + +msgid "CiVariable|* (All environments)" +msgstr "" + +msgid "CiVariable|All environments" +msgstr "" + +msgid "CiVariable|Create wildcard" +msgstr "" + +msgid "CiVariable|Error occured while saving variables" +msgstr "" + +msgid "CiVariable|New environment" +msgstr "" + +msgid "CiVariable|Protected" +msgstr "" + +msgid "CiVariable|Search environments" +msgstr "" + +msgid "CiVariable|Toggle protected" +msgstr "" + +msgid "CiVariable|Validation failed" +msgstr "" + msgid "CircuitBreakerApiLink|circuitbreaker api" msgstr "interruptor da api" +msgid "Click to expand text" +msgstr "" + msgid "Clone repository" msgstr "Clonar repositório" msgid "Close" msgstr "Fechar" -msgid "Cluster" -msgstr "Cluster" - -msgid "ClusterIntegration|%{appList} was successfully installed on your cluster" -msgstr "%{appList} foi instalado no seu cluster" +msgid "Closed" +msgstr "" -msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which incur additional costs. See %{pricingLink}" -msgstr "%{boldNotice} isso irá adicionar recursos extras como balanceamento de carga, que acarretará em custos adicionais. Veja %{pricingLink}" +msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster" +msgstr "" msgid "ClusterIntegration|API URL" msgstr "API URL" -msgid "ClusterIntegration|Active" -msgstr "Ativo" - -msgid "ClusterIntegration|Add an existing cluster" -msgstr "Adicionar um cluster existente" +msgid "ClusterIntegration|Add Kubernetes cluster" +msgstr "" -msgid "ClusterIntegration|Add cluster" -msgstr "Adicionar cluster" +msgid "ClusterIntegration|Add an existing Kubernetes cluster" +msgstr "" -msgid "ClusterIntegration|All" -msgstr "Tudo" +msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration" +msgstr "" msgid "ClusterIntegration|Applications" msgstr "Aplicações" +msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster." +msgstr "" + msgid "ClusterIntegration|CA Certificate" msgstr "Certificado CA" msgid "ClusterIntegration|Certificate Authority bundle (PEM format)" msgstr "Pacote de autoridade certificadora (Formato PEM)" -msgid "ClusterIntegration|Choose how to set up cluster integration" -msgstr "Escolher como configurar a integração de cluster" - -msgid "ClusterIntegration|Cluster" -msgstr "Cluster" - -msgid "ClusterIntegration|Cluster details" -msgstr "Detalhes do cluster" - -msgid "ClusterIntegration|Cluster integration" -msgstr "Integração do cluster" - -msgid "ClusterIntegration|Cluster integration is disabled for this project." -msgstr "Integração do cluster está desabilitada para esse projeto." - -msgid "ClusterIntegration|Cluster integration is enabled for this project." -msgstr "Integração do cluster está ativada nesse projeto." - -msgid "ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab's connection to it." -msgstr "Integração do cluster está ativada para esse projeto. Desabilitar a integração não afetará seu cluster, mas desligará temporariamente a conexão do Gitlab com ele." - -msgid "ClusterIntegration|Cluster is being created on Google Kubernetes Engine..." -msgstr "O Cluster está sendo criado no Google Kubernetes Engine..." - -msgid "ClusterIntegration|Cluster name" -msgstr "Nome do cluster" +msgid "ClusterIntegration|Choose how to set up Kubernetes cluster integration" +msgstr "" -msgid "ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine. Refresh the page to see cluster's details" +msgid "ClusterIntegration|Choose which of your project's environments will use this Kubernetes cluster." msgstr "" -msgid "ClusterIntegration|Clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" -msgstr "Clusters permitem que você utilize review apps, faça deploy de suas aplicações, rode pipelines, e muito mais de um jeito simples. %{link_to_help_page}" +msgid "ClusterIntegration|Control how your Kubernetes cluster integrates with GitLab" +msgstr "" msgid "ClusterIntegration|Copy API URL" msgstr "Copiar URL da API" @@ -591,38 +723,35 @@ msgstr "Copiar URL da API" msgid "ClusterIntegration|Copy CA Certificate" msgstr "Copiar certificado CA" +msgid "ClusterIntegration|Copy Kubernetes cluster name" +msgstr "" + msgid "ClusterIntegration|Copy Token" msgstr "Copiar token" -msgid "ClusterIntegration|Copy cluster name" -msgstr "Copiar nome do cluster" - -msgid "ClusterIntegration|Create a new cluster on Google Engine right from GitLab" -msgstr "Criar um novo cluster do Google Engine pelo GitLab" +msgid "ClusterIntegration|Create Kubernetes cluster" +msgstr "" -msgid "ClusterIntegration|Create cluster" -msgstr "Criar cluster" +msgid "ClusterIntegration|Create Kubernetes cluster on Google Kubernetes Engine" +msgstr "" -msgid "ClusterIntegration|Create cluster on Google Kubernetes Engine" +msgid "ClusterIntegration|Create a new Kubernetes cluster on Google Kubernetes Engine right from GitLab" msgstr "" msgid "ClusterIntegration|Create on GKE" msgstr "Criar no GKE" -msgid "ClusterIntegration|Enable cluster integration" -msgstr "Ativar integração com o cluster" - msgid "ClusterIntegration|Enter the details for an existing Kubernetes cluster" msgstr "Insira os detalhes para o cluster Kubernetes existente" -msgid "ClusterIntegration|Enter the details for your cluster" -msgstr "Insira os detalhes para seu cluster" +msgid "ClusterIntegration|Enter the details for your Kubernetes cluster" +msgstr "" -msgid "ClusterIntegration|Environment pattern" -msgstr "Padrão de ambiente" +msgid "ClusterIntegration|Environment scope" +msgstr "" -msgid "ClusterIntegration|GKE pricing" -msgstr "Preços do GKE" +msgid "ClusterIntegration|GitLab Integration" +msgstr "" msgid "ClusterIntegration|GitLab Runner" msgstr "Gitlab Runner" @@ -631,55 +760,91 @@ msgid "ClusterIntegration|Google Cloud Platform project ID" msgstr "ID do projeto no Google Cloud Platform" msgid "ClusterIntegration|Google Kubernetes Engine" -msgstr "" +msgstr "Google Kubernetes Engine" msgid "ClusterIntegration|Google Kubernetes Engine project" -msgstr "" +msgstr "Projeto do Google Kubernetes Engine" msgid "ClusterIntegration|Helm Tiller" msgstr "Helm Tiller" -msgid "ClusterIntegration|Inactive" -msgstr "Inativo" - msgid "ClusterIntegration|Ingress" msgstr "Ingressar" msgid "ClusterIntegration|Install" msgstr "Instalar" -msgid "ClusterIntegration|Install applications on your cluster. Read more about %{helpLink}" -msgstr "Instalar aplicações no seu cluster. Leia mais em %{helpLink}" - msgid "ClusterIntegration|Installed" msgstr "Instalado" msgid "ClusterIntegration|Installing" msgstr "Instalando" -msgid "ClusterIntegration|Integrate cluster automation" -msgstr "Integrar cluster de automação" +msgid "ClusterIntegration|Integrate Kubernetes cluster automation" +msgstr "" + +msgid "ClusterIntegration|Integration status" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster details" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is disabled for this project." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project. Disabling this integration will not affect your Kubernetes cluster, it will only temporarily turn off GitLab's connection to it." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster name" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project" +msgstr "" msgid "ClusterIntegration|Learn more about %{link_to_documentation}" msgstr "Leia mais sobre %{link_to_documentation}" -msgid "ClusterIntegration|Learn more about Clusters" -msgstr "Ler mais sobre clusters" +msgid "ClusterIntegration|Learn more about Kubernetes" +msgstr "" + +msgid "ClusterIntegration|Learn more about environments" +msgstr "" msgid "ClusterIntegration|Machine type" msgstr "Tipo de máquina" -msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create clusters" -msgstr "Confira se sua conta %{link_to_requirements} para criar clusters" +msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters" +msgstr "" -msgid "ClusterIntegration|Manage cluster integration on your GitLab project" -msgstr "Gerenciar cluster de integração no projeto do GitLab" +msgid "ClusterIntegration|Manage" +msgstr "" -msgid "ClusterIntegration|Manage your cluster by visiting %{link_gke}" -msgstr "Gerencie seu cluster visitando %{link_gke}" +msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}" +msgstr "" + +msgid "ClusterIntegration|More information" +msgstr "" -msgid "ClusterIntegration|Multiple clusters are available in GitLab Entreprise Edition Premium and Ultimate" -msgstr "Múltiplos clusters estão disponÃveis no GitLab Enterprise Premium e Ultimate" +msgid "ClusterIntegration|Multiple Kubernetes clusters are available in GitLab Enterprise Edition Premium and Ultimate" +msgstr "" msgid "ClusterIntegration|Note:" msgstr "Nota:" @@ -687,18 +852,12 @@ msgstr "Nota:" msgid "ClusterIntegration|Number of nodes" msgstr "Número de nós" -msgid "ClusterIntegration|Please enter access information for your cluster. If you need help, you can read our %{link_to_help_page} on clusters" -msgstr "Por favor, insira informações de acesso para seu cluster. Se precisar de ajuda, você pode ler %{link_to_help_page} em cluster" +msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes" +msgstr "" msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:" msgstr "Por favor, tenha certeza que sua conta no Google cumpre com os requisitos:" -msgid "ClusterIntegration|Problem setting up the cluster" -msgstr "Problema ao configurar o cluster" - -msgid "ClusterIntegration|Problem setting up the clusters list" -msgstr "Problema ao configurar a lista de cluster" - msgid "ClusterIntegration|Project ID" msgstr "ID do projeto" @@ -708,16 +867,19 @@ msgstr "Namespace do projeto" msgid "ClusterIntegration|Project namespace (optional, unique)" msgstr "Namespace do projeto (opcional, único)" -msgid "ClusterIntegration|Read our %{link_to_help_page} on cluster integration." -msgstr "Ler nossa %{link_to_help_page} na integração com cluster." +msgid "ClusterIntegration|Prometheus" +msgstr "" + +msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration." +msgstr "" -msgid "ClusterIntegration|Remove cluster integration" -msgstr "Remover integração com cluster" +msgid "ClusterIntegration|Remove Kubernetes cluster integration" +msgstr "" msgid "ClusterIntegration|Remove integration" msgstr "Remover integração" -msgid "ClusterIntegration|Removing cluster integration will remove the cluster configuration you have added to this project. It will not delete your cluster on Google Kubernetes Engine." +msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster." msgstr "" msgid "ClusterIntegration|Request to begin installing failed" @@ -726,8 +888,8 @@ msgstr "Solicitação para inÃcio de instalação falhou" msgid "ClusterIntegration|Save changes" msgstr "Salvar alterações" -msgid "ClusterIntegration|See and edit the details for your cluster" -msgstr "Ver e editar os detalhes para seu cluster" +msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster" +msgstr "" msgid "ClusterIntegration|See machine types" msgstr "Ver tipos de máquina" @@ -747,38 +909,38 @@ msgstr "Mostrar" msgid "ClusterIntegration|Something went wrong on our end." msgstr "Alguma coisa deu errado do nosso lado." -msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine" +msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine" msgstr "" msgid "ClusterIntegration|Something went wrong while installing %{title}" msgstr "Algo deu errado ao instalar %{title}" -msgid "ClusterIntegration|There are no clusters to show" -msgstr "Não há clusters para mostrar" +msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below" +msgstr "" -msgid "ClusterIntegration|This account must have permissions to create a cluster in the %{link_to_container_project} specified below" -msgstr "Essa conta precisa de permissão para criar um cluster no %{link_to_container_project} especificado." +msgid "ClusterIntegration|Toggle Kubernetes Cluster" +msgstr "" -msgid "ClusterIntegration|Toggle Cluster" -msgstr "Alternar cluster" +msgid "ClusterIntegration|Toggle Kubernetes cluster" +msgstr "" msgid "ClusterIntegration|Token" msgstr "Token" -msgid "ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." -msgstr "Com um cluster associado à esse projeto, você pode usar revisão de apps, fazer deploy de suas aplicações, rodar suas pipelines e muito mais de um jeito simples." +msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." +msgstr "" msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}" -msgstr "" +msgstr "Sua conta precisa de %{link_to_kubernetes_engine}" msgid "ClusterIntegration|Zone" msgstr "Zona" msgid "ClusterIntegration|access to Google Kubernetes Engine" -msgstr "" +msgstr "acesso ao Google Container Engine" -msgid "ClusterIntegration|cluster" -msgstr "cluster" +msgid "ClusterIntegration|check the pricing here" +msgstr "" msgid "ClusterIntegration|documentation" msgstr "documentação" @@ -795,6 +957,9 @@ msgstr "atende aos requisitos" msgid "ClusterIntegration|properly configured" msgstr "configurado corretamente" +msgid "Collapse" +msgstr "" + msgid "Comments" msgstr "Comentários" @@ -812,6 +977,9 @@ msgstr "Duração do commit em minutos para os últimos 30 commits" msgid "Commit message" msgstr "Mensagem de commit" +msgid "Commit statistics for %{ref} %{start_time} - %{end_time}" +msgstr "" + msgid "CommitBoxTitle|Commit" msgstr "Commit" @@ -824,15 +992,57 @@ msgstr "Commits" msgid "Commits feed" msgstr "Feed de commits" +msgid "Commits per day hour (UTC)" +msgstr "" + +msgid "Commits per day of month" +msgstr "" + +msgid "Commits per weekday" +msgstr "" + +msgid "Commits|An error occurred while fetching merge requests data." +msgstr "" + +msgid "Commits|Commit: %{commitText}" +msgstr "" + msgid "Commits|History" msgstr "Histórico" +msgid "Commits|No related merge requests found" +msgstr "" + msgid "Committed by" msgstr "Commit feito por" msgid "Compare" msgstr "Comparar" +msgid "Compare Git revisions" +msgstr "" + +msgid "Compare Revisions" +msgstr "" + +msgid "CompareBranches|%{source_branch} and %{target_branch} are the same." +msgstr "" + +msgid "CompareBranches|Compare" +msgstr "" + +msgid "CompareBranches|Source" +msgstr "" + +msgid "CompareBranches|Target" +msgstr "" + +msgid "CompareBranches|There isn't anything to compare." +msgstr "" + +msgid "Confidentiality" +msgstr "" + msgid "Container Registry" msgstr "Container Registry" @@ -884,6 +1094,9 @@ msgstr "Guia de contribuição" msgid "Contributors" msgstr "Contribuidores" +msgid "ContributorsPage|%{startDate} – %{endDate}" +msgstr "" + msgid "ContributorsPage|Building repository graph." msgstr "Gerando gráfico do repositório." @@ -894,20 +1107,29 @@ msgid "ContributorsPage|Please wait a moment, this page will automatically refre msgstr "Por favor, espere um momento, essa página será atualizada automaticamente." msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node" -msgstr "" +msgstr "Controle a concorrência máxima de LFS/preenchimento de repositórios para esse nó secundário" msgid "Control the maximum concurrency of repository backfill for this secondary node" -msgstr "" +msgstr "Controle a concorrência máxima de preenchimento de repositório para esse nó secundário" msgid "Copy SSH public key to clipboard" -msgstr "" +msgstr "Copiar chave públic SSH para área de transferência" msgid "Copy URL to clipboard" msgstr "Copiar URL para área de transferência" +msgid "Copy branch name to clipboard" +msgstr "" + msgid "Copy commit SHA to clipboard" msgstr "Copiar SHA do commit para a área de transferência" +msgid "Copy reference to clipboard" +msgstr "" + +msgid "Create" +msgstr "" + msgid "Create New Directory" msgstr "Criar Novo Diretório" @@ -926,6 +1148,9 @@ msgstr "Criar épico" msgid "Create file" msgstr "Criar arquivo" +msgid "Create lists from labels. Issues with that label appear in that list." +msgstr "" + msgid "Create merge request" msgstr "Criar merge request" @@ -938,6 +1163,9 @@ msgstr "Criar nova pasta" msgid "Create new file" msgstr "Criar novo arquivo" +msgid "Create new label" +msgstr "" + msgid "Create new..." msgstr "Criar novo..." @@ -959,6 +1187,9 @@ msgstr "Fuso horário do cron" msgid "Cron syntax" msgstr "Sintaxe do cron" +msgid "Current node" +msgstr "" + msgid "Custom notification events" msgstr "Eventos de notificação personalizados" @@ -968,9 +1199,6 @@ msgstr "NÃveis de notificação personalizados são equivalentes a nÃveis de p msgid "Cycle Analytics" msgstr "Análise de Ciclo" -msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project." -msgstr "A Análise de Ciclo fornece uma visão geral de quanto tempo uma ideia demora para ir para produção em seu projeto." - msgid "CycleAnalyticsStage|Code" msgstr "Código" @@ -1027,12 +1255,21 @@ msgstr "Modelos de descrição permitem que você defina modelos de contextos es msgid "Details" msgstr "Detalhes" +msgid "Diffs|No file name available" +msgstr "" + msgid "Directory name" msgstr "Nome do diretório" +msgid "Disable" +msgstr "" + msgid "Discard changes" msgstr "Descartar alterações" +msgid "Discover GitLab Geo." +msgstr "" + msgid "Dismiss Cycle Analytics introduction box" msgstr "Ignorar introdução do Cycle Analytics" @@ -1069,15 +1306,24 @@ msgstr "Arquivo de texto com as mudanças" msgid "DownloadSource|Download" msgstr "Baixar" +msgid "Due date" +msgstr "" + msgid "Edit" msgstr "Alterar" msgid "Edit Pipeline Schedule %{id}" msgstr "Alterar Agendamento do Pipeline %{id}" +msgid "Edit files in the editor and commit changes here" +msgstr "" + msgid "Emails" msgstr "Emails" +msgid "Enable" +msgstr "" + msgid "Environments|An error occurred while fetching the environments." msgstr "Um erro ocorreu ao recuperar ambientes." @@ -1096,9 +1342,6 @@ msgstr "Ambiente" msgid "Environments|Environments" msgstr "Ambientes" -msgid "Environments|Environments are places where code gets deployed, such as staging or production." -msgstr "Ambientes são lugares onde códigos são implantados (deploy), como homologação ou produção." - msgid "Environments|Job" msgstr "Job" @@ -1141,9 +1384,33 @@ msgstr "Epics permite que você gerencie seu portfólio de projetos de forma mai msgid "Error creating epic" msgstr "Erro ao criar épico" +msgid "Error fetching contributors data." +msgstr "" + +msgid "Error fetching labels." +msgstr "" + +msgid "Error fetching network graph." +msgstr "" + +msgid "Error fetching refs" +msgstr "" + +msgid "Error fetching usage ping data." +msgstr "" + msgid "Error occurred when toggling the notification subscription" msgstr "Erro ao alterar configuração de notificação de assinatura" +msgid "Error saving label update." +msgstr "" + +msgid "Error updating status for all todos." +msgstr "" + +msgid "Error updating todo status." +msgstr "" + msgid "EventFilterBy|Filter by all" msgstr "EventFilterBy|Filtrar por tudo" @@ -1171,6 +1438,9 @@ msgstr "Todos os meses (no dia primeiro à s 4:00)" msgid "Every week (Sundays at 4:00am)" msgstr "Toda semana (domingos à s 4:00)" +msgid "Expand" +msgstr "" + msgid "Explore projects" msgstr "Explorar projetos" @@ -1189,6 +1459,9 @@ msgstr "Fev" msgid "February" msgstr "Fevereiro" +msgid "Fields on this page are now uneditable, you can configure" +msgstr "" + msgid "File name" msgstr "Nome do arquivo" @@ -1233,42 +1506,138 @@ msgstr "Do merge request até a implantação em produção" msgid "GPG Keys" msgstr "Chaves GPG" +msgid "Generate a default set of labels" +msgstr "" + msgid "Geo Nodes" msgstr "Nós de geo" -msgid "GeoNodeSyncStatus|Failed" -msgstr "Falhou" - msgid "GeoNodeSyncStatus|Node is failing or broken." msgstr "Nó está falhando ou quebrado." msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage." msgstr "Nó está lento, sobrecarregado, ou acabou de recuperar após uma interrupção." -msgid "GeoNodeSyncStatus|Out of sync" -msgstr "Sem sincronia" +msgid "GeoNodes|Database replication lag:" +msgstr "" + +msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?" +msgstr "" + +msgid "GeoNodes|Does not match the primary storage configuration" +msgstr "" + +msgid "GeoNodes|Failed" +msgstr "" + +msgid "GeoNodes|Full" +msgstr "" + +msgid "GeoNodes|GitLab version does not match the primary node version" +msgstr "" + +msgid "GeoNodes|GitLab version:" +msgstr "" + +msgid "GeoNodes|Health status:" +msgstr "" + +msgid "GeoNodes|Last event ID processed by cursor:" +msgstr "" + +msgid "GeoNodes|Last event ID seen from primary:" +msgstr "" + +msgid "GeoNodes|Loading nodes" +msgstr "" + +msgid "GeoNodes|Local Attachments:" +msgstr "" + +msgid "GeoNodes|Local LFS objects:" +msgstr "" + +msgid "GeoNodes|Local job artifacts:" +msgstr "" -msgid "GeoNodeSyncStatus|Synced" -msgstr "Sincronizado" +msgid "GeoNodes|New node" +msgstr "" + +msgid "GeoNodes|Out of sync" +msgstr "" + +msgid "GeoNodes|Replication slot WAL:" +msgstr "" + +msgid "GeoNodes|Replication slots:" +msgstr "" + +msgid "GeoNodes|Repositories:" +msgstr "" + +msgid "GeoNodes|Selective" +msgstr "" + +msgid "GeoNodes|Storage config:" +msgstr "" + +msgid "GeoNodes|Sync settings:" +msgstr "" + +msgid "GeoNodes|Synced" +msgstr "" + +msgid "GeoNodes|Unused slots" +msgstr "" + +msgid "GeoNodes|Used slots" +msgstr "" + +msgid "GeoNodes|Wikis:" +msgstr "" + +msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS." +msgstr "" + +msgid "Geo|All projects" +msgstr "" msgid "Geo|File sync capacity" msgstr "Capacidade de sincronização de arquivos" -msgid "Geo|Groups to replicate" -msgstr "Grupos para replicação" +msgid "Geo|Groups to synchronize" +msgstr "" + +msgid "Geo|Projects in certain groups" +msgstr "" + +msgid "Geo|Projects in certain storage shards" +msgstr "" msgid "Geo|Repository sync capacity" msgstr "Capacidade de sincronização do repositório" msgid "Geo|Select groups to replicate." +msgstr "Selecione grupos para replicar." + +msgid "Geo|Shards to synchronize" +msgstr "" + +msgid "Git revision" msgstr "" msgid "Git storage health information has been reset" msgstr "Informações sobre o status de saúde do storage Git foram reiniciadas" +msgid "Git version" +msgstr "" + msgid "GitLab Runner section" msgstr "Seção GitLab Runner" +msgid "Gitaly Servers" +msgstr "" + msgid "Go to your fork" msgstr "Ir para seu fork" @@ -1278,6 +1647,9 @@ msgstr "Fork" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "Autenticação do Google não está %{link_to_documentation}. Peça ao administrador do Gitlab se você deseja usar esse serviço." +msgid "Got it!" +msgstr "" + msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" msgstr "Bloquear compartilhamento de projetos do grupo %{group} com outros grupos" @@ -1314,8 +1686,8 @@ msgstr "Nenhum grupo encontrado" msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group." msgstr "Você pode gerenciar permissões de membros e acesso do seu grupo para cada projeto no grupo." -msgid "GroupsTree|Are you sure you want to leave the \"${this.group.fullName}\" group?" -msgstr "Você tem certeza que deseja sair do grupo \"${this.group.fullName}\"?" +msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?" +msgstr "" msgid "GroupsTree|Create a project in this group." msgstr "Criar um projeto nesse grupo." @@ -1345,7 +1717,7 @@ msgid "GroupsTree|Sorry, no groups or projects matched your search" msgstr "Desculpe, nenhum grupo ou projeto correspondem à sua pesquisa" msgid "Have your users email" -msgstr "" +msgstr "E-mail para abertura de issues" msgid "Health Check" msgstr "Status de Saúde" @@ -1365,6 +1737,11 @@ msgstr "Nenhum problema de saúde detectado" msgid "HealthCheck|Unhealthy" msgstr "Não saudável" +msgid "Hide value" +msgid_plural "Hide values" +msgstr[0] "" +msgstr[1] "" + msgid "History" msgstr "Histórico" @@ -1375,21 +1752,27 @@ msgid "Import repository" msgstr "Importar repositório" msgid "Improve Issue boards with GitLab Enterprise Edition." -msgstr "" +msgstr "Melhorar issue boards com o GitLab Enterprise Edition." msgid "Improve issues management with Issue weight and GitLab Enterprise Edition." -msgstr "" +msgstr "Melhore a gerência de issues com pesos no GitLab Enterprise Edition." msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition." -msgstr "" +msgstr "Encontre o que precisa mais facilmente com a pesquisa global avançada com GitLab Enterprise Edition." msgid "Install a Runner compatible with GitLab CI" msgstr "Instalar um Runner compatÃvel com o GitLab CI" msgid "Instance" msgid_plural "Instances" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "Instância" +msgstr[1] "Instâncias" + +msgid "Instance does not support multiple Kubernetes clusters" +msgstr "" + +msgid "Interested parties can even contribute by pushing commits if they want to." +msgstr "" msgid "Internal - The group and any internal projects can be viewed by any logged in user." msgstr "Interno - O grupo e projetos internos podem ser visualizados por qualquer usuário autenticado." @@ -1404,7 +1787,7 @@ msgid "Introducing Cycle Analytics" msgstr "Apresentando a Análise de Ciclo" msgid "Issue board focus mode" -msgstr "" +msgstr "Focus mode no issue board" msgid "Issue events" msgstr "Eventos de issue" @@ -1413,11 +1796,14 @@ msgid "IssueBoards|Board" msgstr "Board" msgid "IssueBoards|Boards" -msgstr "" +msgstr "Boards" msgid "Issues" msgstr "Issues" +msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable." +msgstr "" + msgid "Jan" msgstr "Jan" @@ -1436,6 +1822,27 @@ msgstr "Jun" msgid "June" msgstr "Junho" +msgid "Kubernetes" +msgstr "" + +msgid "Kubernetes Cluster" +msgstr "" + +msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}" +msgstr "" + +msgid "Kubernetes cluster integration was not removed." +msgstr "" + +msgid "Kubernetes cluster integration was successfully removed." +msgstr "" + +msgid "Kubernetes cluster was successfully updated." +msgstr "" + +msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page" +msgstr "" + msgid "LFSStatus|Disabled" msgstr "Desabilitado" @@ -1445,6 +1852,9 @@ msgstr "Habilitado" msgid "Labels" msgstr "Etiquetas" +msgid "Labels can be applied to issues and merge requests to categorize them." +msgstr "" + msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "Último %d dia" @@ -1474,6 +1884,9 @@ msgstr "Você fez o push para" msgid "LastPushEvent|at" msgstr "em" +msgid "Learn more" +msgstr "" + msgid "Learn more in the" msgstr "Saiba mais em" @@ -1490,31 +1903,44 @@ msgid "Leave project" msgstr "Sair do projeto" msgid "License" -msgstr "" +msgstr "Licença" -msgid "Limited to showing %d event at most" -msgid_plural "Limited to showing %d events at most" -msgstr[0] "Limitado a mostrar %d evento, no máximo" -msgstr[1] "Limitado a mostrar %d eventos, no máximo" +msgid "Loading the GitLab IDE..." +msgstr "" msgid "Lock" msgstr "Bloquear" +msgid "Lock %{issuableDisplayName}" +msgstr "" + +msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment." +msgstr "" + msgid "Locked" msgstr "Bloqueado" msgid "Locked Files" -msgstr "" +msgstr "Arquivos bloqueados" msgid "Login" msgstr "Entrar" +msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos." +msgstr "" + +msgid "Manage labels" +msgstr "" + msgid "Mar" msgstr "Mar" msgid "March" msgstr "Março" +msgid "Mark done" +msgstr "" + msgid "Maximum git storage failures" msgstr "Máximo de falhas do git storage" @@ -1527,6 +1953,9 @@ msgstr "Mediana" msgid "Members" msgstr "Membros" +msgid "Merge Request" +msgstr "" + msgid "Merge Requests" msgstr "Merge Requests" @@ -1536,9 +1965,30 @@ msgstr "Eventos de merge" msgid "Merge request" msgstr "Merge requests" +msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others" +msgstr "" + +msgid "Merged" +msgstr "" + msgid "Messages" msgstr "Mensagens" +msgid "Milestone" +msgstr "" + +msgid "Milestones|Delete milestone" +msgstr "" + +msgid "Milestones|Delete milestone %{milestoneTitle}?" +msgstr "" + +msgid "Milestones|Failed to delete milestone %{milestoneTitle}" +msgstr "" + +msgid "Milestones|Milestone %{milestoneTitle} was not found" +msgstr "" + msgid "MissingSSHKeyWarningLink|add an SSH key" msgstr "adicione uma chave SSH" @@ -1548,17 +1998,29 @@ msgstr "Monitoramento" msgid "More information is available|here" msgstr "Mais informações estão disponÃveis|aqui" -msgid "Multiple issue boards" +msgid "Move" msgstr "" -msgid "New Cluster" -msgstr "Novo cluster" +msgid "Move issue" +msgstr "" + +msgid "Multiple issue boards" +msgstr "Múltiplos issue boards" + +msgid "Name new label" +msgstr "" msgid "New Issue" msgid_plural "New Issues" msgstr[0] "Nova Issue" msgstr[1] "Novas Issues" +msgid "New Kubernetes Cluster" +msgstr "" + +msgid "New Kubernetes cluster" +msgstr "" + msgid "New Pipeline Schedule" msgstr "Novo Agendamento de Pipeline" @@ -1572,7 +2034,7 @@ msgid "New directory" msgstr "Novo diretório" msgid "New epic" -msgstr "" +msgstr "Novo épico" msgid "New file" msgstr "Novo arquivo" @@ -1583,6 +2045,9 @@ msgstr "Novo grupo" msgid "New issue" msgstr "Nova issue" +msgid "New label" +msgstr "" + msgid "New merge request" msgstr "Novo merge request" @@ -1601,8 +2066,23 @@ msgstr "Novo subgrupo" msgid "New tag" msgstr "Nova tag" -msgid "No container images stored for this project. Add one by following the instructions above." -msgstr "Nenhuma imagem gravada para esse projeto. Adiciona uma com as instruções a seguir." +msgid "No assignee" +msgstr "" + +msgid "No changes" +msgstr "" + +msgid "No connection could be made to a Gitaly Server, please check your logs!" +msgstr "" + +msgid "No due date" +msgstr "" + +msgid "No estimate or time spent" +msgstr "" + +msgid "No file chosen" +msgstr "" msgid "No repository" msgstr "Nenhum repositório" @@ -1616,9 +2096,15 @@ msgstr "Nenhum tempo gasto" msgid "None" msgstr "Nenhum" +msgid "Not allowed to merge" +msgstr "" + msgid "Not available" msgstr "Não disponÃvel" +msgid "Not confidential" +msgstr "" + msgid "Not enough data" msgstr "Dados insuficientes" @@ -1679,6 +2165,12 @@ msgstr "Observar" msgid "Notifications" msgstr "Notificações" +msgid "Notifications off" +msgstr "" + +msgid "Notifications on" +msgstr "" + msgid "Nov" msgstr "Nov" @@ -1688,8 +2180,8 @@ msgstr "Novembro" msgid "Number of access attempts" msgstr "Número de tentativas de acesso" -msgid "Number of failures before backing off" -msgstr "Número de falhas antes de reverter" +msgid "OK" +msgstr "" msgid "Oct" msgstr "Out" @@ -1703,9 +2195,12 @@ msgstr "Filtrar" msgid "Only project members can comment." msgstr "Somente membros do projeto podem comentar." -msgid "Opened" +msgid "Open" msgstr "" +msgid "Opened" +msgstr "Aberto" + msgid "OpenedNDaysAgo|Opened" msgstr "Aberto" @@ -1736,9 +2231,6 @@ msgstr "<< Primeiro" msgid "Password" msgstr "Senha" -msgid "People without permission will never get a notification and won\\'t be able to comment." -msgstr "Pessoas sem permissão nunca receberão uma notificação e não serão capazes de comentar." - msgid "Pipeline" msgstr "Pipeline" @@ -1752,7 +2244,7 @@ msgid "Pipeline Schedules" msgstr "Agendamentos da Pipeline" msgid "Pipeline quota" -msgstr "" +msgstr "Cota de pipeline" msgid "PipelineCharts|Failed:" msgstr "Falhou:" @@ -1781,12 +2273,6 @@ msgstr "Todos" msgid "PipelineSchedules|Inactive" msgstr "Inativo" -msgid "PipelineSchedules|Input variable key" -msgstr "Chave da variável de entrada" - -msgid "PipelineSchedules|Input variable value" -msgstr "Valor da variável de entrada" - msgid "PipelineSchedules|Next Run" msgstr "Próxima Execução" @@ -1796,9 +2282,6 @@ msgstr "Nenhum" msgid "PipelineSchedules|Provide a short description for this pipeline" msgstr "Digite uma descrição curta para esta pipeline" -msgid "PipelineSchedules|Remove variable row" -msgstr "Remova a linha da variável" - msgid "PipelineSchedules|Take ownership" msgstr "Tornar-se proprietário" @@ -1826,6 +2309,12 @@ msgstr "Pipelines para a última semana" msgid "Pipelines for last year" msgstr "Pipelines para o último ano" +msgid "Pipelines|Build with confidence" +msgstr "" + +msgid "Pipelines|Get started with Pipelines" +msgstr "" + msgid "Pipeline|all" msgstr "todos" @@ -1838,12 +2327,21 @@ msgstr "com etapa" msgid "Pipeline|with stages" msgstr "com etapas" -msgid "Please solve the reCAPTCHA" +msgid "Play" +msgstr "" + +msgid "Please <a href=%{link_to_billing} target=\"_blank\" rel=\"noopener noreferrer\">enable billing for one of your projects to be able to create a Kubernetes cluster</a>, then try again." msgstr "" +msgid "Please solve the reCAPTCHA" +msgstr "Por favor, resolva o reCAPTCHA" + msgid "Preferences" msgstr "Preferências" +msgid "Primary" +msgstr "" + msgid "Private - Project access must be granted explicitly to each user." msgstr "Privado - O acesso ao projeto deve ser concedido explicitamente para cada usuário." @@ -1889,6 +2387,9 @@ msgstr "Sua conta é atualmente proprietária dos seguintes grupos:" msgid "Profiles|your account" msgstr "sua conta" +msgid "Programming languages used in this repository" +msgstr "" + msgid "Project '%{project_name}' is in the process of being deleted." msgstr "O projeto '%{project_name}' está sendo excluÃdo." @@ -1904,6 +2405,15 @@ msgstr "Projeto '%{project_name}' atualizado com sucesso." msgid "Project access must be granted explicitly to each user." msgstr "Acesso ao projeto deve ser concedido explicitamente para cada usuário." +msgid "Project avatar" +msgstr "" + +msgid "Project avatar in repository: %{link}" +msgstr "" + +msgid "Project cache successfully reset." +msgstr "" + msgid "Project details" msgstr "Detalhes do projeto" @@ -1922,6 +2432,21 @@ msgstr "Exportação do projeto iniciada. Um link para baixá-la será enviado p msgid "ProjectActivityRSS|Subscribe" msgstr "Inscreva-se" +msgid "ProjectCreationLevel|Allowed to create projects" +msgstr "" + +msgid "ProjectCreationLevel|Default project creation protection" +msgstr "" + +msgid "ProjectCreationLevel|Developers + Masters" +msgstr "" + +msgid "ProjectCreationLevel|Masters" +msgstr "" + +msgid "ProjectCreationLevel|No one" +msgstr "" + msgid "ProjectFeature|Disabled" msgstr "Desabilitado" @@ -1944,28 +2469,22 @@ msgid "ProjectNetworkGraph|Graph" msgstr "Ãrvore" msgid "ProjectSettings|Contact an admin to change this setting." -msgstr "" - -msgid "ProjectSettings|Immediately run a pipeline on the default branch" -msgstr "Rodar pipeline na branch default imediatamente" +msgstr "Fale com um administrador para mudar essa configuração." msgid "ProjectSettings|Only signed commits can be pushed to this repository." -msgstr "" - -msgid "ProjectSettings|Problem setting up the CI/CD settings JavaScript" -msgstr "Problema ao definir configurações de CI/CD Javascript" +msgstr "Esse repositório só aceita push de commits assinados." msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin." -msgstr "" +msgstr "Essa configuração é aplicada em nÃvel de servidor e pode ser sobrescrita por qualquer administrador." msgid "ProjectSettings|This setting is applied on the server level but has been overridden for this project." -msgstr "" +msgstr "Essa configuração está aplicada à nivel de servidor mas foi sobrescrita para esse projeto." msgid "ProjectSettings|This setting will be applied to all projects unless overridden by an admin." -msgstr "" +msgstr "Essa configuração será aplicada à todos os projetos, a não ser que seja sobrescrita pelo administrador." msgid "ProjectSettings|Users can only push commits to this repository that were committed with one of their own verified emails." -msgstr "" +msgstr "Usuários só podem fazer push de commits para esse repositório se os commits estiverem assinados com um de seus próprios e-mails verificados." msgid "Projects" msgstr "Projetos" @@ -2018,12 +2537,15 @@ msgstr "Nenhuma métrica está sendo monitorada. Para inicar o monitoramento, fa msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/" msgstr "URL da API base do Prometheus. como http://prometheus.example.com/" -msgid "PrometheusService|Prometheus monitoring" -msgstr "Monitoramento com Prometheus" +msgid "PrometheusService|Time-series monitoring service" +msgstr "" msgid "PrometheusService|View environments" msgstr "Ver ambientes" +msgid "Protip:" +msgstr "" + msgid "Public - The group and any public projects can be viewed without any authentication." msgstr "Público - O grupo e seus projetos podem ser visualizados por todos sem autenticação." @@ -2031,12 +2553,15 @@ msgid "Public - The project can be accessed without any authentication." msgstr "Público - O projeto pode ser acessado sem nenhuma autenticação." msgid "Push Rules" -msgstr "" +msgstr "Regras de push" msgid "Push events" msgstr "Eventos de push" msgid "PushRule|Committer restriction" +msgstr "Restrição de commit" + +msgid "Quick actions can be used in the issues description and comment boxes." msgstr "" msgid "Read more" @@ -2051,6 +2576,12 @@ msgstr "Branches" msgid "RefSwitcher|Tags" msgstr "Tags" +msgid "Reference:" +msgstr "" + +msgid "Register / Sign In" +msgstr "" + msgid "Registry" msgstr "Registro" @@ -2075,9 +2606,18 @@ msgstr "Merge Requests Relacionados" msgid "Remind later" msgstr "Lembrar mais tarde" +msgid "Remove" +msgstr "" + +msgid "Remove avatar" +msgstr "" + msgid "Remove project" msgstr "Remover projeto" +msgid "Repair authentication" +msgstr "" + msgid "Repository" msgstr "Repositório" @@ -2093,6 +2633,11 @@ msgstr "Recriar o token de status de saúde" msgid "Reset runners registration token" msgstr "Recriar o token de registro de runners" +msgid "Reveal value" +msgid_plural "Reveal values" +msgstr[0] "" +msgstr[1] "" + msgid "Revert this commit" msgstr "Reverter este commit" @@ -2102,15 +2647,15 @@ msgstr "Reverter esse merge request" msgid "SSH Keys" msgstr "Chaves SSH" -msgid "Save" -msgstr "Salvar" - msgid "Save changes" msgstr "Salvar alterações" msgid "Save pipeline schedule" msgstr "Salvar agendamento da pipeline" +msgid "Save variables" +msgstr "" + msgid "Schedule a new pipeline" msgstr "Agendar nova pipeline" @@ -2121,43 +2666,64 @@ msgid "Scheduling Pipelines" msgstr "Agendando pipelines" msgid "Scoped issue boards" -msgstr "" +msgstr "Issue board de escopo" msgid "Search branches and tags" msgstr "Procurar branch e tags" +msgid "Search milestones" +msgstr "" + +msgid "Search project" +msgstr "" + +msgid "Search users" +msgstr "" + msgid "Seconds before reseting failure information" msgstr "Segundos antes de redefinir as informações de falha" -msgid "Seconds to wait after a storage failure" -msgstr "Segundos a esperar após uma falha de armazenamento" - msgid "Seconds to wait for a storage access attempt" msgstr "Segundo de espera para tentativa de acesso ao storage" +msgid "Secret variables" +msgstr "" + msgid "Select Archive Format" msgstr "Selecionar Formato do Arquivo" msgid "Select a timezone" msgstr "Selecionar fuso horário" +msgid "Select assignee" +msgstr "" + +msgid "Select branch/tag" +msgstr "" + msgid "Select target branch" msgstr "Selecionar branch de destino" +msgid "Selective synchronization" +msgstr "" + msgid "Sep" msgstr "Set" msgid "September" msgstr "Setembro" +msgid "Server version" +msgstr "" + msgid "Service Templates" msgstr "Modelos de serviço" msgid "Set a password on your account to pull or push via %{protocol}." msgstr "Defina uma senha para sua conta para aceitar ou entregar código via %{protocol}." -msgid "Set up CI" -msgstr "Configurar CI" +msgid "Set up CI/CD" +msgstr "" msgid "Set up Koding" msgstr "Configurar Koding" @@ -2171,6 +2737,15 @@ msgstr "defina uma senha" msgid "Settings" msgstr "Configurações" +msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero." +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset pipeline minutes" +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes" +msgstr "" + msgid "Show parent pages" msgstr "Mostrar páginas acima" @@ -2183,35 +2758,44 @@ msgstr[0] "Mostrando %d evento" msgstr[1] "Mostrando %d eventos" msgid "Sidebar|Change weight" -msgstr "" - -msgid "Sidebar|Edit" -msgstr "" +msgstr "Mudar peso" msgid "Sidebar|No" -msgstr "" +msgstr "Não" msgid "Sidebar|None" -msgstr "" +msgstr "Nenhum" msgid "Sidebar|Weight" -msgstr "" +msgstr "Peso" msgid "Snippets" msgstr "Snippets" +msgid "Something went wrong on our end" +msgstr "" + msgid "Something went wrong on our end." msgstr "Algo deu errado do nosso lado." +msgid "Something went wrong trying to change the confidentiality of this issue" +msgstr "" + msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}" msgstr "Algo deu errado ao tentar mudar o estado de ${this.issuableDisplayName}" +msgid "Something went wrong when toggling the button" +msgstr "" + msgid "Something went wrong while fetching the projects." msgstr "Algo deu errado ao recuperar os projetos." msgid "Something went wrong while fetching the registry list." msgstr "Algo deu errado ao recuperar a lista de registro." +msgid "Something went wrong. Please try again." +msgstr "" + msgid "Sort by" msgstr "Ordenar por" @@ -2255,7 +2839,7 @@ msgid "SortOptions|Least popular" msgstr "Menos populares" msgid "SortOptions|Less weight" -msgstr "" +msgstr "Menos peso" msgid "SortOptions|Milestone" msgstr "Milestone" @@ -2267,7 +2851,7 @@ msgid "SortOptions|Milestone due soon" msgstr "Milestone de fim mais próximo" msgid "SortOptions|More weight" -msgstr "" +msgstr "Mais peso" msgid "SortOptions|Most popular" msgstr "Mais populares" @@ -2309,7 +2893,7 @@ msgid "SortOptions|Start soon" msgstr "Iniciar mais próximo" msgid "SortOptions|Weight" -msgstr "" +msgstr "Peso" msgid "Source" msgstr "Origem" @@ -2341,12 +2925,12 @@ msgstr "Inicie o Runner!" msgid "Stopped" msgstr "Parado" +msgid "Storage" +msgstr "" + msgid "Subgroups" msgstr "Subgrupos" -msgid "Subscribe" -msgstr "Assine" - msgid "Switch branch/tag" msgstr "Trocar branch/tag" @@ -2437,13 +3021,16 @@ msgid "Team" msgstr "Equipe" msgid "Thanks! Don't show me this again" -msgstr "" +msgstr "Obrigado! Não mostrar novamente" msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project." +msgstr "A pesquisa global avançada no GitLab é um serviço de pesquisa poderoso que economiza seu tempo. Ao invés de criar códigos duplicados e perder seu tempo, você pode agora pesquisar códigos de outros times que podem ajudar em seu projeto." + +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project" msgstr "" -msgid "The circuitbreaker backoff threshold should be lower than the failure count threshold" -msgstr "O limite do recuso do circuitbreaker deve ser inferior ao limite de contagem de falhas" +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project." +msgstr "" msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request." msgstr "A etapa de codificação mostra o tempo desde a entrega do primeiro commit até a criação do merge request. Os dados serão automaticamente adicionados aqui desde o momento de criação do merge request." @@ -2457,21 +3044,18 @@ msgstr "O relacionamento como fork foi removido." msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage." msgstr "A etapa de planejamento mostra o tempo que se leva desde a criação de uma issue até sua atribuição à um milestone, ou sua adição a uma lista no seu Issue Board. Comece a criar issues para ver dados para esta etapa." +msgid "The maximum file size allowed is 200KB." +msgstr "" + msgid "The number of attempts GitLab will make to access a storage." msgstr "O número de tentativas que gitlab fará para acessar um storage." -msgid "The number of failures after which GitLab will start temporarily disabling access to a storage shard on a host" -msgstr "O número de falhas até o GitLab começar a desabilitar temporariamente o acesso a um nó de storage em um host" - msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}." msgstr "O número de falhas para que o GitLab desabilite o acesso ao storage. O número de falhas pode ser redefinido na interface do administrador: %{link_to_health_page} ou %{api_documentation_link}." msgid "The phase of the development lifecycle." msgstr "A fase do ciclo de vida do desenvolvimento." -msgid "The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user." -msgstr "O agendamento de pipeline executa pipelines no futuro, repetidamente, para branches ou tags especÃficas. Essas pipelines agendadas terão acesso limitado ao projeto baseado no seu usuário associado." - msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit." msgstr "A etapa de planejamento mostra o tempo do passo anterior até a publicação de seu primeiro conjunto de mudanças. Este tempo será adicionado automaticamente assim que você enviar seu primeiro conjunto de mudanças." @@ -2502,20 +3086,47 @@ msgstr "Tempo em segundos para o GitLab manter as informações de falha. Se nen msgid "The time in seconds GitLab will try to access storage. After this time a timeout error will be raised." msgstr "Tempo em segundos que o GitLab tentará acessar o storage. Depois desse tempo, um erro de tempo excedido será disparado." +msgid "The time in seconds between storage checks. When a previous check did complete yet, GitLab will skip a check." +msgstr "" + msgid "The time taken by each data entry gathered by that stage." msgstr "O tempo necessário por cada entrada de dados reunida por essa etapa." msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6." msgstr "O valor situado no ponto médio de uma série de valores observados. Ex., entre 3, 5, 9, a mediana é 5. Entre 3, 5, 7, 8, a mediana é (5+7)/2 = 6." +msgid "There are no issues to show" +msgstr "" + +msgid "There are no merge requests to show" +msgstr "" + msgid "There are problems accessing Git storage: " msgstr "Há problemas para acessar o storage Git: " -msgid "This board\\'s scope is reduced" +msgid "There was an error loading users activity calendar." msgstr "" -msgid "This branch has changed since you started editing. Would you like to create a new branch?" -msgstr "Esse branch mudou desde quando você começou sua edição. Você quer criar um novo branch?" +msgid "There was an error saving your notification settings." +msgstr "" + +msgid "There was an error subscribing to this label." +msgstr "" + +msgid "There was an error when reseting email token." +msgstr "" + +msgid "There was an error when subscribing to this label." +msgstr "" + +msgid "There was an error when unsubscribing from this label." +msgstr "" + +msgid "This board\\'s scope is reduced" +msgstr "O escopo desse board está reduzido" + +msgid "This directory" +msgstr "" msgid "This is a confidential issue." msgstr "Essa issue é confidencial." @@ -2523,21 +3134,48 @@ msgstr "Essa issue é confidencial." msgid "This is the author's first Merge Request to this project." msgstr "Esse é o autor do primeiro merge request desse projeto." +msgid "This issue is confidential" +msgstr "" + msgid "This issue is confidential and locked." msgstr "Essa issue é confidencial e está bloqueada." msgid "This issue is locked." msgstr "Essa issue está bloqueada." +msgid "This job depends on a user to trigger its process. Often they are used to deploy code to production environments" +msgstr "" + +msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered" +msgstr "" + +msgid "This job has not been triggered yet" +msgstr "" + +msgid "This job has not started yet" +msgstr "" + +msgid "This job is in pending state and is waiting to be picked by a runner" +msgstr "" + +msgid "This job requires a manual action" +msgstr "" + msgid "This means you can not push code until you create an empty repository or import existing one." msgstr "Isto significa que você não pode entregar código até que crie um repositório vazio ou importe um existente." msgid "This merge request is locked." msgstr "Esse merge request está bloqueado." -msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here." +msgid "This project" msgstr "" +msgid "This repository" +msgstr "" + +msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here." +msgstr "Esses e-mails se tornarão issues automaticamente (com os comentários se tornando uma conversa de e-mail) listadas aqui." + msgid "Time before an issue gets scheduled" msgstr "Tempo até que uma issue seja agendada" @@ -2547,9 +3185,21 @@ msgstr "Tempo até que uma issue comece a ser implementado" msgid "Time between merge request creation and merge/close" msgstr "Tempo entre a criação da solicitação de incorporação e a aceitação/fechamento" +msgid "Time tracking" +msgstr "" + msgid "Time until first merge request" msgstr "Tempo até a primeira solicitação de incorporação" +msgid "TimeTrackingEstimated|Est" +msgstr "" + +msgid "TimeTracking|Estimated:" +msgstr "" + +msgid "TimeTracking|Spent" +msgstr "" + msgid "Timeago|%s days ago" msgstr "há %s dias" @@ -2689,6 +3339,18 @@ msgstr "s" msgid "Title" msgstr "TÃtulo" +msgid "Todo" +msgstr "" + +msgid "Toggle sidebar" +msgstr "" + +msgid "ToggleButton|Toggle Status: OFF" +msgstr "" + +msgid "ToggleButton|Toggle Status: ON" +msgstr "" + msgid "Total Time" msgstr "Tempo Total" @@ -2704,20 +3366,41 @@ msgstr "Acompanhe a atividade com o Contribution Analytics." msgid "Track groups of issues that share a theme, across projects and milestones" msgstr "Acompanhe grupos de questões que compartilhem um tema, em projetos e milestones" +msgid "Total: %{total}" +msgstr "" + +msgid "Track time with quick actions" +msgstr "" + +msgid "Trigger this manual action" +msgstr "" + msgid "Turn on Service Desk" msgstr "Ativar Service Desk" +msgid "Type %{value} to confirm:" +msgstr "" + +msgid "Unable to reset project cache." +msgstr "" + +msgid "Unknown" +msgstr "" + msgid "Unlock" msgstr "Desbloquear" +msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment." +msgstr "" + msgid "Unlocked" msgstr "Desbloqueado" msgid "Unstar" msgstr "Desmarcar" -msgid "Unsubscribe" -msgstr "Desassinar" +msgid "Up to date" +msgstr "" msgid "Upgrade your plan to activate Advanced Global Search." msgstr "Atualize seu plano para ativar a Pesquisa Global Avançada." @@ -2740,6 +3423,9 @@ msgstr "Enviar Novo Arquivo" msgid "Upload file" msgstr "Enviar arquivo" +msgid "Upload new avatar" +msgstr "" + msgid "UploadLink|click to upload" msgstr "clique para fazer upload" @@ -2752,9 +3438,15 @@ msgstr "Use o seguinte token de registro durante a configuração:" msgid "Use your global notification setting" msgstr "Utilizar configuração de notificação global" +msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." +msgstr "" + msgid "View file @ " msgstr "Ver arquivo @ " +msgid "View labels" +msgstr "" + msgid "View open merge request" msgstr "Ver merge request aberto" @@ -2776,6 +3468,9 @@ msgstr "Desconhecido" msgid "Want to see the data? Please ask an administrator for access." msgstr "Precisa visualizar os dados? Solicite acesso ao administrador." +msgid "We could not verify that one of your projects on GCP has billing enabled. Please try again." +msgstr "" + msgid "We don't have enough data to show this stage." msgstr "Esta etapa não possui dados suficientes para exibição." @@ -2788,9 +3483,6 @@ msgstr "Webhooks permitem que você acione uma URL se, por exemplo, quando um no msgid "Weight" msgstr "Peso" -msgid "When access to a storage fails. GitLab will prevent access to the storage for the time specified here. This allows the filesystem to recover. Repositories on failing shards are temporarly unavailable" -msgstr "Falha ao acessar o storage. Gitlab impedirá o acesso ao storage pelo tempo especificado aqui. Isso permite que o sistema de arquivos se recupere. Repositórios que estiverem em nós com falha ficarão temporariamente indisponÃveis" - msgid "Wiki" msgstr "Wiki" @@ -2809,6 +3501,12 @@ msgstr "É recomendado instalar %{markdown} para que as funções GFM sejam rend msgid "WikiClone|Start Gollum and edit locally" msgstr "Inicie o Gollum e edite localmente" +msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title." +msgstr "" + +msgid "WikiEdit|There is already a page with the same title in that path." +msgstr "" + msgid "WikiEmptyPageError|You are not allowed to create wiki pages" msgstr "Você não tem permissão para criar páginas web" @@ -2911,9 +3609,21 @@ msgstr "Você está prestes a remover a relação de fork do projeto original %{ msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" msgstr "Você irá transferir %{project_name_with_namespace} para outro proprietário. Tem certeza ABSOLUTA?" +msgid "You can also star a label to make it a priority label." +msgstr "" + +msgid "You can move around the graph by using the arrow keys." +msgstr "" + +msgid "You can move around the graph by using the arrow keys." +msgstr "" + msgid "You can only add files when you are on a branch" msgstr "Você somente pode adicionar arquivos quando estiver em um branch" +msgid "You can only edit files when you are on a branch" +msgstr "" + msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead." msgstr "Você não pode escrever numa instância secundária de somente leitura do GitLab Geo. Por favor use %{link_to_primary_node}." @@ -2953,6 +3663,12 @@ msgstr "Você não conseguirá fazer pull ou push no projeto via SSH até que a msgid "You won't be able to pull or push project code via SSH until you add an SSH key to your profile" msgstr "Você não poderá fazer push ou pull do código via SSH enquanto não adicionar sua chave SSH no seu perfil" +msgid "You'll need to use different branch names to get a valid comparison." +msgstr "" + +msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure" +msgstr "" + msgid "Your comment will not be visible to the public." msgstr "Seu comentário não estará visÃvel ao público." @@ -2965,26 +3681,220 @@ msgstr "Seu nome" msgid "Your projects" msgstr "Seus projetos" +msgid "assign yourself" +msgstr "" + msgid "branch name" msgstr "nome da branch" msgid "by" msgstr "por" +msgid "ciReport|Code quality" +msgstr "" + +msgid "ciReport|DAST detected no alerts by analyzing the review app" +msgstr "" + +msgid "ciReport|Failed to load ${type} report" +msgstr "" + +msgid "ciReport|Fixed:" +msgstr "" + +msgid "ciReport|Instances" +msgstr "" + +msgid "ciReport|Learn more about whitelisting" +msgstr "" + +msgid "ciReport|Loading ${type} report" +msgstr "" + +msgid "ciReport|No changes to code quality" +msgstr "" + +msgid "ciReport|No changes to performance metrics" +msgstr "" + +msgid "ciReport|Performance metrics" +msgstr "" + +msgid "ciReport|SAST" +msgstr "" + +msgid "ciReport|SAST detected no security vulnerabilities" +msgstr "" + +msgid "ciReport|SAST:container no vulnerabilities were found" +msgstr "" + +msgid "ciReport|Show complete code vulnerabilities report" +msgstr "" + +msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}" +msgstr "" + msgid "commit" msgstr "commit" +msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue." +msgstr "" + +msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue." +msgstr "" + msgid "day" msgid_plural "days" msgstr[0] "dia" msgstr[1] "dias" +msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command." +msgstr "" + +msgid "merge request" +msgid_plural "merge requests" +msgstr[0] "" +msgstr[1] "" + +msgid "mrWidget|Cancel automatic merge" +msgstr "" + +msgid "mrWidget|Check out branch" +msgstr "" + +msgid "mrWidget|Checking ability to merge automatically" +msgstr "" + +msgid "mrWidget|Cherry-pick" +msgstr "" + +msgid "mrWidget|Cherry-pick this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Closed" +msgstr "" + +msgid "mrWidget|Closed by" +msgstr "" + +msgid "mrWidget|Closes" +msgstr "" + +msgid "mrWidget|Did not close" +msgstr "" + +msgid "mrWidget|Email patches" +msgstr "" + +msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|Mentions" +msgstr "" + +msgid "mrWidget|Merge" +msgstr "" + +msgid "mrWidget|Merge failed." +msgstr "" + +msgid "mrWidget|Merge locally" +msgstr "" + +msgid "mrWidget|Merged by" +msgstr "" + +msgid "mrWidget|Plain diff" +msgstr "" + +msgid "mrWidget|Refresh" +msgstr "" + +msgid "mrWidget|Refresh now" +msgstr "" + +msgid "mrWidget|Refreshing now" +msgstr "" + +msgid "mrWidget|Remove Source Branch" +msgstr "" + +msgid "mrWidget|Remove source branch" +msgstr "" + +msgid "mrWidget|Request to merge" +msgstr "" + +msgid "mrWidget|Resolve conflicts" +msgstr "" + +msgid "mrWidget|Revert" +msgstr "" + +msgid "mrWidget|Revert this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Set by" +msgstr "" + +msgid "mrWidget|The changes were merged into" +msgstr "" + +msgid "mrWidget|The changes were not merged into" +msgstr "" + +msgid "mrWidget|The changes will be merged into" +msgstr "" + +msgid "mrWidget|The source branch has been removed" +msgstr "" + +msgid "mrWidget|The source branch is being removed" +msgstr "" + +msgid "mrWidget|The source branch will be removed" +msgstr "" + +msgid "mrWidget|The source branch will not be removed" +msgstr "" + +msgid "mrWidget|There are merge conflicts" +msgstr "" + +msgid "mrWidget|This merge request failed to be merged automatically" +msgstr "" + +msgid "mrWidget|This merge request is in the process of being merged" +msgstr "" + +msgid "mrWidget|This project is archived, write access has been disabled" +msgstr "" + +msgid "mrWidget|You can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|You can remove source branch now" +msgstr "" + +msgid "mrWidget|command line" +msgstr "" + +msgid "mrWidget|into" +msgstr "" + +msgid "mrWidget|to be merged automatically when the pipeline succeeds" +msgstr "" + msgid "new merge request" msgstr "novo merge request" msgid "notification emails" msgstr "emails de notificação" +msgid "or" +msgstr "" + msgid "parent" msgid_plural "parents" msgstr[0] "pai" @@ -2996,12 +3906,21 @@ msgstr "senha" msgid "personal access token" msgstr "token de acesso pessoal" +msgid "remove due date" +msgstr "" + msgid "source" msgstr "origem" +msgid "spendCommand|%{slash_command} will update the sum of the time spent." +msgstr "" + msgid "to help your contributors communicate effectively!" msgstr "para ajudar seus contribuintes à se comunicar de maneira eficaz!" msgid "username" msgstr "nome do usuário" +msgid "uses Kubernetes clusters to deploy your code!" +msgstr "" + diff --git a/locale/ru/gitlab.po b/locale/ru/gitlab.po index 898d55e7d4e..0adb8e5b716 100644 --- a/locale/ru/gitlab.po +++ b/locale/ru/gitlab.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab-ee\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-12-12 18:31+0000\n" -"PO-Revision-Date: 2018-01-05 04:40-0500\n" +"POT-Creation-Date: 2018-02-07 11:38-0600\n" +"PO-Revision-Date: 2018-02-12 04:01-0500\n" "Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n" "Language-Team: Russian\n" "Language: ru_RU\n" @@ -16,26 +16,47 @@ msgstr "" "X-Crowdin-Language: ru\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" +msgid " and" +msgstr "" + msgid "%d commit" msgid_plural "%d commits" msgstr[0] "%d коммит" msgstr[1] "%d коммита" msgstr[2] "%d коммитов" +msgid "%d commit behind" +msgid_plural "%d commits behind" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "%d issue" +msgid_plural "%d issues" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + msgid "%d layer" msgid_plural "%d layers" msgstr[0] "%d Ñлой" msgstr[1] "%d ÑлоÑ" msgstr[2] "%d Ñлоёв" +msgid "%d merge request" +msgid_plural "%d merge requests" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "%s additional commits have been omitted to prevent performance issues." msgstr[0] "%s добавленный коммит был иÑключен Ð´Ð»Ñ Ð¿Ñ€ÐµÐ´Ð¾Ñ‚Ð²Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼ Ñ Ð¿Ñ€Ð¾Ð¸Ð·Ð²Ð¾Ð´Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð¾Ñтью." msgstr[1] "%s добавленных коммита были иÑключены Ð´Ð»Ñ Ð¿Ñ€ÐµÐ´Ð¾Ñ‚Ð²Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼ Ñ Ð¿Ñ€Ð¾Ð¸Ð·Ð²Ð¾Ð´Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð¾Ñтью." msgstr[2] "%s добавленных коммитов были иÑключены Ð´Ð»Ñ Ð¿Ñ€ÐµÐ´Ð¾Ñ‚Ð²Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼ Ñ Ð¿Ñ€Ð¾Ð¸Ð·Ð²Ð¾Ð´Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð¾Ñтью." -msgid "%{commit_author_link} committed %{commit_timeago}" -msgstr "%{commit_author_link} добавил коммит %{commit_timeago}" +msgid "%{commit_author_link} authored %{commit_timeago}" +msgstr "" msgid "%{count} participant" msgid_plural "%{count} participants" @@ -49,9 +70,6 @@ msgstr "на %{number_commits_behind} коммитов позади %{default_br msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will allow access on the next attempt." msgstr "%{number_of_failures} из %{maximum_failures} возможных неудачных попыток. GitLab будет доÑтупен поÑле Ñледующей попытки." -msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will block access for %{number_of_seconds} seconds." -msgstr "%{number_of_failures} из %{maximum_failures} возможных неудачных попыток. GitLab заблокирует доÑтуп на %{number_of_seconds} Ñекунд." - msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved." msgstr "%{number_of_failures} из %{maximum_failures} возможных неудачных попыток. GitLab не будет автоматичеÑки повторÑÑ‚ÑŒ попытку. СброÑьте информацию хранилища поÑле уÑÑ‚Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ñ‹." @@ -62,7 +80,7 @@ msgstr[1] "%{storage_name}: %{failed_attempts} - неудачные попытк msgstr[2] "%{storage_name}: %{failed_attempts} - неудачные попытки доÑтупа к хранилищу:" msgid "%{text} is available" -msgstr "" +msgstr "%{text} доÑтупен" msgid "(checkout the %{link} for information on how to install it)." msgstr "(перейдите по ÑÑылке %{link} Ð´Ð»Ñ Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ð¸ об уÑтановке)." @@ -110,7 +128,7 @@ msgid "Activity" msgstr "ÐктивноÑÑ‚ÑŒ" msgid "Add" -msgstr "" +msgstr "Добавить" msgid "Add Changelog" msgstr "Добавить Журнал Изменений" @@ -119,7 +137,7 @@ msgid "Add Contribution guide" msgstr "Добавить РуководÑтво учаÑтника" msgid "Add Group Webhooks and GitLab Enterprise Edition." -msgstr "" +msgstr "Добавить групповые веб-обработчики и GitLab Enterprise Edition." msgid "Add License" msgstr "Добавить Лицензию" @@ -127,22 +145,79 @@ msgstr "Добавить Лицензию" msgid "Add new directory" msgstr "Добавить новый каталог" +msgid "Add todo" +msgstr "" + +msgid "AdminArea|Stop all jobs" +msgstr "" + +msgid "AdminArea|Stop all jobs?" +msgstr "" + +msgid "AdminArea|Stop jobs" +msgstr "" + +msgid "AdminArea|Stopping jobs failed" +msgstr "" + +msgid "AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running." +msgstr "" + msgid "AdminHealthPageLink|health page" msgstr "Ñтраница работоÑпоÑобноÑти" +msgid "Advanced" +msgstr "" + msgid "Advanced settings" msgstr "РаÑширенные наÑтройки" msgid "All" msgstr "Ð’Ñе" -msgid "An error occurred when toggling the notification subscription" +msgid "All changes are committed" +msgstr "" + +msgid "Allows you to add and manage Kubernetes clusters." +msgstr "" + +msgid "An error occurred previewing the blob" msgstr "" +msgid "An error occurred when toggling the notification subscription" +msgstr "Произошла ошибка при переключении подпиÑки на оповещениÑ" + msgid "An error occurred when updating the issue weight" +msgstr "Произошла ошибка при обновлении веÑа обÑуждениÑ" + +msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." +msgstr "" + +msgid "An error occurred while fetching markdown preview" msgstr "" msgid "An error occurred while fetching sidebar data" +msgstr "Произошла ошибка при получении денег данных Ð´Ð»Ñ Ð±Ð¾ÐºÐ¾Ð²Ð¾Ð¹ панели" + +msgid "An error occurred while getting projects" +msgstr "" + +msgid "An error occurred while loading filenames" +msgstr "" + +msgid "An error occurred while rendering KaTeX" +msgstr "" + +msgid "An error occurred while rendering preview broadcast message" +msgstr "" + +msgid "An error occurred while retrieving calendar activity" +msgstr "" + +msgid "An error occurred while retrieving diff" +msgstr "" + +msgid "An error occurred while validating username" msgstr "" msgid "An error occurred. Please try again." @@ -169,9 +244,6 @@ msgstr "Ð’Ñ‹ дейÑтвительно хотите удалить Ñто Ñ€Ð°Ñ msgid "Are you sure you want to discard your changes?" msgstr "Ð’Ñ‹ уверены, что хотите отменить ваши изменениÑ?" -msgid "Are you sure you want to leave this group?" -msgstr "Ð’Ñ‹ уверены, что хотите покинуть Ñту группу?" - msgid "Are you sure you want to reset registration token?" msgstr "Ð’Ñ‹ уверены, что хотите ÑброÑить Ñтот региÑтрационный токен?" @@ -184,6 +256,21 @@ msgstr "Ð’Ñ‹ уверены?" msgid "Artifacts" msgstr "Ðртефакты" +msgid "Assign custom color like #FF0000" +msgstr "" + +msgid "Assign labels" +msgstr "" + +msgid "Assign milestone" +msgstr "" + +msgid "Assign to" +msgstr "" + +msgid "Assignee" +msgstr "" + msgid "Attach a file by drag & drop or %{upload_link}" msgstr "Приложить файл через drag & drop или %{upload_link}" @@ -199,15 +286,18 @@ msgstr "Журнал аутентификации" msgid "Author" msgstr "Ðвтор" -msgid "Auto Review Apps and Auto Deploy need a domain name and the %{kubernetes} to work correctly." -msgstr "ÐŸÑ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑкого ревью и автоматичеÑкого Ñ€Ð°Ð·Ð²Ñ‘Ñ€Ñ‚Ñ‹Ð²Ð°Ð½Ð¸Ñ Ñ‚Ñ€ÐµÐ±ÑƒÑŽÑ‚ ÑƒÐºÐ°Ð·Ð°Ð½Ð¸Ñ Ð¸Ð¼ÐµÐ½Ð¸ домена и %{kubernetes} Ð´Ð»Ñ ÐºÐ¾Ñ€Ñ€ÐµÐºÑ‚Ð½Ð¾Ð¹ работы." +msgid "Authors: %{authors}" +msgstr "" + +msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." +msgstr "" + +msgid "Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly." +msgstr "" msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly." msgstr "ÐŸÑ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑкого ревью и автоматичеÑкого Ñ€Ð°Ð·Ð²Ñ‘Ñ€Ñ‚Ñ‹Ð²Ð°Ð½Ð¸Ñ Ñ‚Ñ€ÐµÐ±ÑƒÑŽÑ‚ ÑƒÐºÐ°Ð·Ð°Ð½Ð¸Ñ Ð¸Ð¼ÐµÐ½Ð¸ домена Ð´Ð»Ñ ÐºÐ¾Ñ€Ñ€ÐµÐºÑ‚Ð½Ð¾Ð¹ работы." -msgid "Auto Review Apps and Auto Deploy need the %{kubernetes} to work correctly." -msgstr "ÐŸÑ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑкого ревью и автоматичеÑкого Ñ€Ð°Ð·Ð²Ñ‘Ñ€Ñ‚Ñ‹Ð²Ð°Ð½Ð¸Ñ Ñ‚Ñ€ÐµÐ±ÑƒÑŽÑ‚ ÑƒÐºÐ°Ð·Ð°Ð½Ð¸Ñ %{kubernetes} Ð´Ð»Ñ ÐºÐ¾Ñ€Ñ€ÐµÐºÑ‚Ð½Ð¾Ð¹ работы." - msgid "AutoDevOps|Auto DevOps (Beta)" msgstr "Auto DevOps (бета)" @@ -227,11 +317,17 @@ msgid "AutoDevOps|You can activate %{link_to_settings} for this project." msgstr "Ð’Ñ‹ можете активировать %{link_to_settings} Ð´Ð»Ñ Ñтого проекта." msgid "Available" +msgstr "ДоÑтупен" + +msgid "Avatar will be removed. Are you sure?" msgstr "" -msgid "Billing" +msgid "Average per day: %{average}" msgstr "" +msgid "Billing" +msgstr "Тариф" + msgid "BillingPlans|%{group_name} is currently on the %{plan_link} plan." msgstr "%{group_name} иÑпользует тарифный план %{plan_link}." @@ -283,6 +379,9 @@ msgstr "оплачиваетÑÑ ÐµÐ¶ÐµÐ³Ð¾Ð´Ð½Ð¾ в размере %{price_per_ msgid "BillingPlans|per user" msgstr "за пользователÑ" +msgid "Begin with the selected commit" +msgstr "" + msgid "Branch" msgid_plural "Branches" msgstr[0] "Ветка" @@ -296,10 +395,10 @@ msgid "Branch has changed" msgstr "Ветка была изменена" msgid "Branch is already taken" -msgstr "" +msgstr "Ветвь уже ÑущеÑтвует" msgid "Branch name" -msgstr "" +msgstr "Ð˜Ð¼Ñ Ð²ÐµÑ‚Ð²Ð¸" msgid "BranchSwitcherPlaceholder|Search branches" msgstr "ПоиÑк веток" @@ -362,7 +461,7 @@ msgid "Branches|Sort by" msgstr "Сортировать по" msgid "Branches|The branch could not be updated automatically because it has diverged from its upstream counterpart." -msgstr "" +msgstr "Ветвь не может быть обновлена автоматичеÑки, потому что она имеет раÑÑ…Ð¾Ð¶Ð´ÐµÐ½Ð¸Ñ Ñ Ñ€Ð¾Ð´Ð¸Ñ‚ÐµÐ»ÑŒÑким репозиторием." msgid "Branches|The default branch cannot be deleted" msgstr "Ветка \"по умолчанию\" не может быть удалена" @@ -377,13 +476,13 @@ msgid "Branches|To confirm, type %{branch_name_confirmation}:" msgstr "Ð”Ð»Ñ Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ, введите %{branch_name_confirmation}:" msgid "Branches|To discard the local changes and overwrite the branch with the upstream version, delete it here and choose 'Update Now' above." -msgstr "" +msgstr "Чтобы отменить локальные Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¸ перезапиÑать ветвь верÑией из родительÑкого репозиториÑ, удалите её здеÑÑŒ и выберите \"Обновить ÑейчаÑ\" выше." msgid "Branches|You’re about to permanently delete the protected branch %{branch_name}." msgstr "Ð’Ñ‹ ÑобираетеÑÑŒ безвозвратно удалить защищённую ветку %{branch_name}." msgid "Branches|diverged from upstream" -msgstr "" +msgstr "раÑходитÑÑ Ñ Ñ€Ð¾Ð´Ð¸Ñ‚ÐµÐ»ÑŒÑким репозиторием" msgid "Branches|merged" msgstr "влита" @@ -412,8 +511,8 @@ msgstr "по автору" msgid "CI / CD" msgstr "CI / CD" -msgid "CI configuration" -msgstr "ÐаÑтройка CI" +msgid "CI/CD configuration" +msgstr "" msgid "CICD|Jobs" msgstr "ЗаданиÑ" @@ -424,9 +523,12 @@ msgstr "Отмена" msgid "Cancel edit" msgstr "Отменить редактирование" -msgid "Change Weight" +msgid "Cannot modify managed Kubernetes cluster" msgstr "" +msgid "Change Weight" +msgstr "Изменить ВеÑ" + msgid "ChangeTypeActionLabel|Pick into branch" msgstr "Выбрать в ветке" @@ -439,20 +541,29 @@ msgstr "Подобрать" msgid "ChangeTypeAction|Revert" msgstr "Отменить" +msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes." +msgstr "" + msgid "Changelog" msgstr "Журнал изменений" +msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision." +msgstr "" + msgid "Charts" msgstr "Диаграммы" msgid "Chat" msgstr "Чат" -msgid "Checking %{text} availability…" +msgid "Check interval" msgstr "" +msgid "Checking %{text} availability…" +msgstr "Проверка доÑтупноÑти %{text} ..." + msgid "Checking branch availability..." -msgstr "" +msgstr "Проверка доÑтупноÑти ветви..." msgid "Cherry-pick this commit" msgstr "Подобрать в Ñтом коммите" @@ -460,7 +571,19 @@ msgstr "Подобрать в Ñтом коммите" msgid "Cherry-pick this merge request" msgstr "Подобрать Ñтот Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние" -msgid "Choose which groups you wish to replicate to this secondary node. Leave blank to replicate all." +msgid "Choose File ..." +msgstr "" + +msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request." +msgstr "" + +msgid "Choose file..." +msgstr "" + +msgid "Choose which groups you wish to synchronize to this secondary node." +msgstr "" + +msgid "Choose which shards you wish to synchronize to this secondary node." msgstr "" msgid "CiStatusLabel|canceled" @@ -517,175 +640,220 @@ msgstr "пропущено" msgid "CiStatus|running" msgstr "выполнÑетÑÑ" -msgid "CircuitBreakerApiLink|circuitbreaker api" -msgstr "CircuitBreaker API" - -msgid "Clone repository" -msgstr "Клонировать репозиторий" +msgid "CiVariables|Input variable key" +msgstr "" -msgid "Close" +msgid "CiVariables|Input variable value" msgstr "" -msgid "Cluster" -msgstr "КлаÑтер" +msgid "CiVariables|Remove variable row" +msgstr "" -msgid "ClusterIntegration|%{appList} was successfully installed on your cluster" +msgid "CiVariable|* (All environments)" msgstr "" -msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which incur additional costs. See %{pricingLink}" +msgid "CiVariable|All environments" msgstr "" -msgid "ClusterIntegration|API URL" +msgid "CiVariable|Create wildcard" msgstr "" -msgid "ClusterIntegration|Active" +msgid "CiVariable|Error occured while saving variables" msgstr "" -msgid "ClusterIntegration|Add an existing cluster" +msgid "CiVariable|New environment" msgstr "" -msgid "ClusterIntegration|Add cluster" +msgid "CiVariable|Protected" msgstr "" -msgid "ClusterIntegration|All" +msgid "CiVariable|Search environments" msgstr "" -msgid "ClusterIntegration|Applications" +msgid "CiVariable|Toggle protected" msgstr "" -msgid "ClusterIntegration|CA Certificate" +msgid "CiVariable|Validation failed" msgstr "" -msgid "ClusterIntegration|Certificate Authority bundle (PEM format)" +msgid "CircuitBreakerApiLink|circuitbreaker api" +msgstr "CircuitBreaker API" + +msgid "Click to expand text" msgstr "" -msgid "ClusterIntegration|Choose how to set up cluster integration" +msgid "Clone repository" +msgstr "Клонировать репозиторий" + +msgid "Close" +msgstr "Закрыть" + +msgid "Closed" msgstr "" -msgid "ClusterIntegration|Cluster" +msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Cluster details" -msgstr "Параметры клаÑтера" +msgid "ClusterIntegration|API URL" +msgstr "ÐÐ´Ñ€ÐµÑ API" -msgid "ClusterIntegration|Cluster integration" -msgstr "Ð˜Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ð¸Ñ ÐºÐ»Ð°Ñтеров" +msgid "ClusterIntegration|Add Kubernetes cluster" +msgstr "" -msgid "ClusterIntegration|Cluster integration is disabled for this project." -msgstr "Ð˜Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ð¸Ñ ÐºÐ»Ð°Ñтеров отключена Ð´Ð»Ñ Ñтого проекта." +msgid "ClusterIntegration|Add an existing Kubernetes cluster" +msgstr "" -msgid "ClusterIntegration|Cluster integration is enabled for this project." -msgstr "Ð˜Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ð¸Ñ ÐºÐ»Ð°Ñтеров включена Ð´Ð»Ñ Ñтого проекта." +msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration" +msgstr "" -msgid "ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab's connection to it." -msgstr "Ð”Ð»Ñ Ñтого проекта включена Ð¸Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ð¸Ñ ÐºÐ»Ð°Ñтеров. Отключение интеграции не повлиÑет на клаÑтер, но Ñоединение Ñ GitLab будет временно отключено." +msgid "ClusterIntegration|Applications" +msgstr "ПриложениÑ" -msgid "ClusterIntegration|Cluster is being created on Google Kubernetes Engine..." +msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster." msgstr "" -msgid "ClusterIntegration|Cluster name" -msgstr "Ðазвание клаÑтера" +msgid "ClusterIntegration|CA Certificate" +msgstr "Сертификат удоÑтоверÑющего центра" + +msgid "ClusterIntegration|Certificate Authority bundle (PEM format)" +msgstr "Комплект Ñертификатов удоÑтоверÑющего центра (формат PEM)" -msgid "ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine. Refresh the page to see cluster's details" +msgid "ClusterIntegration|Choose how to set up Kubernetes cluster integration" msgstr "" -msgid "ClusterIntegration|Clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" +msgid "ClusterIntegration|Choose which of your project's environments will use this Kubernetes cluster." msgstr "" -msgid "ClusterIntegration|Copy API URL" +msgid "ClusterIntegration|Control how your Kubernetes cluster integrates with GitLab" msgstr "" +msgid "ClusterIntegration|Copy API URL" +msgstr "Скопировать Ð°Ð´Ñ€ÐµÑ API" + msgid "ClusterIntegration|Copy CA Certificate" -msgstr "" +msgstr "Копировать Сертификат УдоÑтоверÑющего Центра" -msgid "ClusterIntegration|Copy Token" +msgid "ClusterIntegration|Copy Kubernetes cluster name" msgstr "" -msgid "ClusterIntegration|Copy cluster name" -msgstr "Копировать название клаÑтера" +msgid "ClusterIntegration|Copy Token" +msgstr "Скопировать Токен" -msgid "ClusterIntegration|Create a new cluster on Google Engine right from GitLab" +msgid "ClusterIntegration|Create Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Create cluster" -msgstr "Создать клаÑтер" - -msgid "ClusterIntegration|Create cluster on Google Kubernetes Engine" +msgid "ClusterIntegration|Create Kubernetes cluster on Google Kubernetes Engine" msgstr "" -msgid "ClusterIntegration|Create on GKE" +msgid "ClusterIntegration|Create a new Kubernetes cluster on Google Kubernetes Engine right from GitLab" msgstr "" -msgid "ClusterIntegration|Enable cluster integration" -msgstr "Включить интеграцию Ñ ÐºÐ»Ð°Ñтерами" +msgid "ClusterIntegration|Create on GKE" +msgstr "Создать в GKE" msgid "ClusterIntegration|Enter the details for an existing Kubernetes cluster" -msgstr "" +msgstr "Укажите параметры ÑущеÑтвующего клаÑтера Kubernetes" -msgid "ClusterIntegration|Enter the details for your cluster" +msgid "ClusterIntegration|Enter the details for your Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Environment pattern" +msgid "ClusterIntegration|Environment scope" msgstr "" -msgid "ClusterIntegration|GKE pricing" +msgid "ClusterIntegration|GitLab Integration" msgstr "" msgid "ClusterIntegration|GitLab Runner" -msgstr "" +msgstr "GitLab Runner" msgid "ClusterIntegration|Google Cloud Platform project ID" msgstr "Идентификатор проекта в Google Cloud Platform" msgid "ClusterIntegration|Google Kubernetes Engine" -msgstr "" +msgstr "Google Kubernetes Engine" msgid "ClusterIntegration|Google Kubernetes Engine project" -msgstr "" +msgstr "Проект Google Kubernetes Engine" msgid "ClusterIntegration|Helm Tiller" +msgstr "Helm Tiller" + +msgid "ClusterIntegration|Ingress" +msgstr "Ingress" + +msgid "ClusterIntegration|Install" +msgstr "УÑтановить" + +msgid "ClusterIntegration|Installed" +msgstr "УÑтановлен" + +msgid "ClusterIntegration|Installing" +msgstr "УÑтановка" + +msgid "ClusterIntegration|Integrate Kubernetes cluster automation" msgstr "" -msgid "ClusterIntegration|Inactive" +msgid "ClusterIntegration|Integration status" msgstr "" -msgid "ClusterIntegration|Ingress" +msgid "ClusterIntegration|Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Install" +msgid "ClusterIntegration|Kubernetes cluster details" msgstr "" -msgid "ClusterIntegration|Install applications on your cluster. Read more about %{helpLink}" +msgid "ClusterIntegration|Kubernetes cluster integration" msgstr "" -msgid "ClusterIntegration|Installed" +msgid "ClusterIntegration|Kubernetes cluster integration is disabled for this project." msgstr "" -msgid "ClusterIntegration|Installing" +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project. Disabling this integration will not affect your Kubernetes cluster, it will only temporarily turn off GitLab's connection to it." msgstr "" -msgid "ClusterIntegration|Integrate cluster automation" +msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster name" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project" msgstr "" msgid "ClusterIntegration|Learn more about %{link_to_documentation}" msgstr "Узнайте больше на %{link_to_documentation}" -msgid "ClusterIntegration|Learn more about Clusters" +msgid "ClusterIntegration|Learn more about Kubernetes" +msgstr "" + +msgid "ClusterIntegration|Learn more about environments" msgstr "" msgid "ClusterIntegration|Machine type" msgstr "Тип машины" -msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create clusters" -msgstr "УбедитеÑÑŒ, что ваша ÑƒÑ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ %{link_to_requirements} Ð´Ð»Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ ÐºÐ»Ð°Ñтеров" +msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters" +msgstr "" + +msgid "ClusterIntegration|Manage" +msgstr "" -msgid "ClusterIntegration|Manage cluster integration on your GitLab project" +msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}" msgstr "" -msgid "ClusterIntegration|Manage your cluster by visiting %{link_gke}" -msgstr "УправлÑйте клаÑтером, Ð¿ÐµÑ€ÐµÐ¹Ð´Ñ Ð¿Ð¾ ÑÑылке %{link_gke}" +msgid "ClusterIntegration|More information" +msgstr "" -msgid "ClusterIntegration|Multiple clusters are available in GitLab Entreprise Edition Premium and Ultimate" +msgid "ClusterIntegration|Multiple Kubernetes clusters are available in GitLab Enterprise Edition Premium and Ultimate" msgstr "" msgid "ClusterIntegration|Note:" @@ -694,47 +862,44 @@ msgstr "Примечание:" msgid "ClusterIntegration|Number of nodes" msgstr "КоличеÑтво узлов" -msgid "ClusterIntegration|Please enter access information for your cluster. If you need help, you can read our %{link_to_help_page} on clusters" +msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes" msgstr "" msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:" msgstr "ПожалуйÑта, убедитеÑÑŒ, что ваш аккаунт Google отвечает Ñледующим требованиÑм:" -msgid "ClusterIntegration|Problem setting up the cluster" -msgstr "" - -msgid "ClusterIntegration|Problem setting up the clusters list" -msgstr "" - msgid "ClusterIntegration|Project ID" -msgstr "" +msgstr "ID проекта" msgid "ClusterIntegration|Project namespace" -msgstr "" +msgstr "ПроÑтранÑтво имён проекта" msgid "ClusterIntegration|Project namespace (optional, unique)" msgstr "ПроÑтранÑтво имен проекта (необÑзательное, уникальное)" -msgid "ClusterIntegration|Read our %{link_to_help_page} on cluster integration." -msgstr "Прочтите нашу документацию %{link_to_help_page} по интеграции клаÑтера." +msgid "ClusterIntegration|Prometheus" +msgstr "" -msgid "ClusterIntegration|Remove cluster integration" -msgstr "Удалить интеграцию Ñ ÐºÐ»Ð°Ñтером" +msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration." +msgstr "" + +msgid "ClusterIntegration|Remove Kubernetes cluster integration" +msgstr "" msgid "ClusterIntegration|Remove integration" msgstr "Удалить интеграцию" -msgid "ClusterIntegration|Removing cluster integration will remove the cluster configuration you have added to this project. It will not delete your cluster on Google Kubernetes Engine." +msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster." msgstr "" msgid "ClusterIntegration|Request to begin installing failed" -msgstr "" +msgstr "Ðе удалоÑÑŒ выполнить Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° запуÑк процеÑÑа уÑтановки" msgid "ClusterIntegration|Save changes" -msgstr "" +msgstr "Сохранить изменениÑ" -msgid "ClusterIntegration|See and edit the details for your cluster" -msgstr "ПроÑмотреть и отредактировать параметры Ð´Ð»Ñ Ð²Ð°ÑˆÐµÐ³Ð¾ клаÑтера" +msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster" +msgstr "" msgid "ClusterIntegration|See machine types" msgstr "См. типы машин" @@ -746,55 +911,55 @@ msgid "ClusterIntegration|See zones" msgstr "См. зоны" msgid "ClusterIntegration|Service token" -msgstr "" +msgstr "Служебный токен" msgid "ClusterIntegration|Show" -msgstr "" +msgstr "Показать" msgid "ClusterIntegration|Something went wrong on our end." msgstr " У Ð½Ð°Ñ Ñ‡Ñ‚Ð¾-то пошло не так." -msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine" +msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine" msgstr "" msgid "ClusterIntegration|Something went wrong while installing %{title}" -msgstr "" +msgstr "Произошли ошибки во Ð²Ñ€ÐµÐ¼Ñ ÑƒÑтановки %{title}" -msgid "ClusterIntegration|There are no clusters to show" +msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below" msgstr "" -msgid "ClusterIntegration|This account must have permissions to create a cluster in the %{link_to_container_project} specified below" +msgid "ClusterIntegration|Toggle Kubernetes Cluster" msgstr "" -msgid "ClusterIntegration|Toggle Cluster" -msgstr "Переключить КлаÑтер" +msgid "ClusterIntegration|Toggle Kubernetes cluster" +msgstr "" msgid "ClusterIntegration|Token" -msgstr "" +msgstr "Токен" -msgid "ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." -msgstr "ЕÑли привÑзать клаÑтер к Ñтому проекту, вы Ñ Ð»Ñ‘Ð³ÐºÐ¾Ñтью Ñможете иÑпользовать Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ñ€ÐµÐ²ÑŒÑŽ, развертывать ваши приложениÑ, запуÑкать Ñборочные линии и многое другое." +msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." +msgstr "" msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}" -msgstr "" +msgstr "Ваша ÑƒÑ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ должна иметь %{link_to_kubernetes_engine}" msgid "ClusterIntegration|Zone" msgstr "Зона" msgid "ClusterIntegration|access to Google Kubernetes Engine" -msgstr "" +msgstr "доÑтуп к Google Kubernetes Engine" -msgid "ClusterIntegration|cluster" -msgstr "клаÑтер" +msgid "ClusterIntegration|check the pricing here" +msgstr "" msgid "ClusterIntegration|documentation" -msgstr "" +msgstr "документациÑ" msgid "ClusterIntegration|help page" msgstr "Ñтраница Ñправки" msgid "ClusterIntegration|installing applications" -msgstr "" +msgstr "ClusterIntegration | уÑтановка приложений" msgid "ClusterIntegration|meets the requirements" msgstr "отвечает требованиÑм" @@ -802,6 +967,9 @@ msgstr "отвечает требованиÑм" msgid "ClusterIntegration|properly configured" msgstr "правильно наÑтроен" +msgid "Collapse" +msgstr "" + msgid "Comments" msgstr "Комментарии" @@ -820,6 +988,9 @@ msgstr "ПродолжительноÑÑ‚ÑŒ поÑледних 30 коммитоРmsgid "Commit message" msgstr "ОпиÑание коммита" +msgid "Commit statistics for %{ref} %{start_time} - %{end_time}" +msgstr "" + msgid "CommitBoxTitle|Commit" msgstr "Коммит" @@ -832,15 +1003,57 @@ msgstr "Коммиты" msgid "Commits feed" msgstr "Лента коммитов" +msgid "Commits per day hour (UTC)" +msgstr "" + +msgid "Commits per day of month" +msgstr "" + +msgid "Commits per weekday" +msgstr "" + +msgid "Commits|An error occurred while fetching merge requests data." +msgstr "" + +msgid "Commits|Commit: %{commitText}" +msgstr "" + msgid "Commits|History" msgstr "ИÑториÑ" +msgid "Commits|No related merge requests found" +msgstr "" + msgid "Committed by" msgstr "ЗафикÑировано автором" msgid "Compare" msgstr "Сравнить" +msgid "Compare Git revisions" +msgstr "" + +msgid "Compare Revisions" +msgstr "" + +msgid "CompareBranches|%{source_branch} and %{target_branch} are the same." +msgstr "" + +msgid "CompareBranches|Compare" +msgstr "" + +msgid "CompareBranches|Source" +msgstr "" + +msgid "CompareBranches|Target" +msgstr "" + +msgid "CompareBranches|There isn't anything to compare." +msgstr "" + +msgid "Confidentiality" +msgstr "" + msgid "Container Registry" msgstr "РееÑÑ‚Ñ€ Контейнеров" @@ -892,30 +1105,42 @@ msgstr "РуководÑтво учаÑтника" msgid "Contributors" msgstr "УчаÑтники" -msgid "ContributorsPage|Building repository graph." +msgid "ContributorsPage|%{startDate} – %{endDate}" msgstr "" +msgid "ContributorsPage|Building repository graph." +msgstr "ПоÑтроение графа репозиториÑ." + msgid "ContributorsPage|Commits to %{branch_name}, excluding merge commits. Limited to 6,000 commits." -msgstr "" +msgstr "Коммиты в %{branch_name}, за иÑключением коммитов ÑлиÑниÑ. Ограничено 6,000 коммитами." msgid "ContributorsPage|Please wait a moment, this page will automatically refresh when ready." -msgstr "" +msgstr "ПожалуйÑта подождите, Ñта Ñтраница автоматичеÑки обновитÑÑ Ð¿Ð¾ готовноÑти." msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node" -msgstr "" +msgstr "Контролировать макÑимальное количеÑтво потоков фоновой загрузки LFS/вложений Ð´Ð»Ñ Ñтого вторичного узла" msgid "Control the maximum concurrency of repository backfill for this secondary node" -msgstr "" +msgstr "Контролировать макÑимальное количеÑтво потоков фоновой загрузки хранилища Ð´Ð»Ñ Ñтого вторичного узла" msgid "Copy SSH public key to clipboard" -msgstr "" +msgstr "Скопировать публичный ключ SSH в буфер обмена" msgid "Copy URL to clipboard" msgstr "Копировать URL в буфер обмена" +msgid "Copy branch name to clipboard" +msgstr "" + msgid "Copy commit SHA to clipboard" msgstr "Копировать SHA коммита в буфер обмена" +msgid "Copy reference to clipboard" +msgstr "" + +msgid "Create" +msgstr "" + msgid "Create New Directory" msgstr "Создать Ðовый каталог" @@ -929,11 +1154,14 @@ msgid "Create empty bare repository" msgstr "Создать пуÑтой репозиторий" msgid "Create epic" -msgstr "" +msgstr "Создать Ñпик" msgid "Create file" msgstr "Создать файл" +msgid "Create lists from labels. Issues with that label appear in that list." +msgstr "" + msgid "Create merge request" msgstr "Создать Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние" @@ -946,6 +1174,9 @@ msgstr "Создать новый каталог" msgid "Create new file" msgstr "Создать новый файл" +msgid "Create new label" +msgstr "" + msgid "Create new..." msgstr "Ðовый" @@ -959,7 +1190,7 @@ msgid "CreateTokenToCloneLink|create a personal access token" msgstr "Ñоздать перÑональный токен доÑтупа" msgid "Creating epic" -msgstr "" +msgstr "Создание Ñпика" msgid "Cron Timezone" msgstr "Ð’Ñ€ÐµÐ¼ÐµÐ½Ð½Ð°Ñ Ð·Ð¾Ð½Ð° Cron" @@ -967,6 +1198,9 @@ msgstr "Ð’Ñ€ÐµÐ¼ÐµÐ½Ð½Ð°Ñ Ð·Ð¾Ð½Ð° Cron" msgid "Cron syntax" msgstr "СинтакÑÐ¸Ñ Cron" +msgid "Current node" +msgstr "" + msgid "Custom notification events" msgstr "Ð¡Ð¾Ð±Ñ‹Ñ‚Ð¸Ñ Ð½Ð°Ñтраиваемых уведомлений" @@ -976,9 +1210,6 @@ msgstr "ÐаÑтраиваемые уровни уведомлений аналРmsgid "Cycle Analytics" msgstr "Ðналитика Цикла" -msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project." -msgstr "Ðналитика Цикла дает предÑтавление о том, Ñколько времени требуетÑÑ, чтобы перейти от идеи к производÑтву в вашем проекте." - msgid "CycleAnalyticsStage|Code" msgstr "ÐапиÑание кода" @@ -1031,22 +1262,31 @@ msgid "Description" msgstr "ОпиÑание" msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project." -msgstr "" +msgstr "Шаблоны опиÑаний позволÑÑŽÑ‚ вам определить контекÑтно-завиÑимые шаблоны Ð·Ð°Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð¾Ð±Ñуждений и запроÑов на ÑлиÑние в вашем проекте." msgid "Details" msgstr "ÐŸÐ¾Ð´Ñ€Ð¾Ð±Ð½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ" +msgid "Diffs|No file name available" +msgstr "" + msgid "Directory name" msgstr "Ð˜Ð¼Ñ ÐºÐ°Ñ‚Ð°Ð»Ð¾Ð³Ð°" +msgid "Disable" +msgstr "" + msgid "Discard changes" msgstr "Отменить изменениÑ" +msgid "Discover GitLab Geo." +msgstr "" + msgid "Dismiss Cycle Analytics introduction box" msgstr "Отключить блок Ð²Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð² Ðналитику Цикла" msgid "Dismiss Merge Request promotion" -msgstr "" +msgstr "Отключить анонÑÑ‹ Ð´Ð»Ñ Ð—Ð°Ð¿Ñ€Ð¾Ñов на ÑлиÑние" msgid "Don't show again" msgstr "Ðе показывать Ñнова" @@ -1078,79 +1318,109 @@ msgstr "ПроÑтой Diff" msgid "DownloadSource|Download" msgstr "Скачать" +msgid "Due date" +msgstr "" + msgid "Edit" msgstr "Редактировать" msgid "Edit Pipeline Schedule %{id}" msgstr "Изменить раÑпиÑание Ñборочной линии %{id}" +msgid "Edit files in the editor and commit changes here" +msgstr "" + msgid "Emails" msgstr "Email-адреÑа" -msgid "Environments|An error occurred while fetching the environments." +msgid "Enable" msgstr "" +msgid "Environments|An error occurred while fetching the environments." +msgstr "Произошла ошибка при получении окружений." + msgid "Environments|An error occurred while making the request." -msgstr "" +msgstr "Произошла ошибка во Ð²Ñ€ÐµÐ¼Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð·Ð°Ð¿Ñ€Ð¾Ñа." msgid "Environments|Commit" -msgstr "" +msgstr "Коммит" msgid "Environments|Deployment" -msgstr "" +msgstr "Развертывание" msgid "Environments|Environment" -msgstr "" +msgstr "Окружение" msgid "Environments|Environments" -msgstr "" - -msgid "Environments|Environments are places where code gets deployed, such as staging or production." -msgstr "" +msgstr "ОкружениÑ" msgid "Environments|Job" -msgstr "" +msgstr "Задание" msgid "Environments|New environment" -msgstr "" +msgstr "Ðовое окружение" msgid "Environments|No deployments yet" -msgstr "" +msgstr "Еще нет развертываний" msgid "Environments|Open" -msgstr "" +msgstr "Открыть" msgid "Environments|Re-deploy" -msgstr "" +msgstr "Переразвернуть" msgid "Environments|Read more about environments" -msgstr "" +msgstr "Подробнее об окружениÑÑ…" msgid "Environments|Rollback" -msgstr "" +msgstr "Откатить" msgid "Environments|Show all" msgstr "Показать вÑе" msgid "Environments|Updated" -msgstr "" +msgstr "Обновлено" msgid "Environments|You don't have any environments right now." -msgstr "" +msgstr "Ð’Ñ‹ пока не наÑтроили ни одного окружениÑ." msgid "Epic will be removed! Are you sure?" -msgstr "" +msgstr "Ðпик будет удален! Ð’Ñ‹ уверены?" msgid "Epics" -msgstr "" +msgstr "Ðпики" msgid "Epics let you manage your portfolio of projects more efficiently and with less effort" -msgstr "" +msgstr "Ðпики позволÑÑ‚ вам управлÑÑ‚ÑŒ портфелем проектов более Ñффективно и Ñ Ð¼ÐµÐ½ÑŒÑˆÐ¸Ð¼Ð¸ уÑилиÑми" msgid "Error creating epic" +msgstr "Ошибка ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ñпика" + +msgid "Error fetching contributors data." +msgstr "" + +msgid "Error fetching labels." +msgstr "" + +msgid "Error fetching network graph." +msgstr "" + +msgid "Error fetching refs" +msgstr "" + +msgid "Error fetching usage ping data." msgstr "" msgid "Error occurred when toggling the notification subscription" +msgstr "Произошла ошибка при переключении подпиÑки на оповещение" + +msgid "Error saving label update." +msgstr "" + +msgid "Error updating status for all todos." +msgstr "" + +msgid "Error updating todo status." msgstr "" msgid "EventFilterBy|Filter by all" @@ -1180,6 +1450,9 @@ msgstr "ЕжемеÑÑчно (каждое 1-е чиÑло в 4:00)" msgid "Every week (Sundays at 4:00am)" msgstr "Еженедельно (по воÑкреÑениÑм в 4:00)" +msgid "Expand" +msgstr "" + msgid "Explore projects" msgstr "Обзор проектов" @@ -1198,6 +1471,9 @@ msgstr "Фев." msgid "February" msgstr "Февраль" +msgid "Fields on this page are now uneditable, you can configure" +msgstr "" + msgid "File name" msgstr "Ð˜Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð°" @@ -1243,42 +1519,138 @@ msgstr "От запроÑа на ÑлиÑние до Ñ€Ð°Ð·Ð²ÐµÑ€Ñ‚Ñ‹Ð²Ð°Ð½Ð¸Ñ msgid "GPG Keys" msgstr "GPG Ключи" +msgid "Generate a default set of labels" +msgstr "" + msgid "Geo Nodes" +msgstr "ГеографичеÑкие Узлы" + +msgid "GeoNodeSyncStatus|Node is failing or broken." +msgstr "Ðа узле Ñбой или он не работает." + +msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage." +msgstr "Узел функционирует медленно, перегужен или только что воÑÑтановлен поÑле ÑбоÑ." + +msgid "GeoNodes|Database replication lag:" msgstr "" -msgid "GeoNodeSyncStatus|Failed" +msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?" msgstr "" -msgid "GeoNodeSyncStatus|Node is failing or broken." +msgid "GeoNodes|Does not match the primary storage configuration" msgstr "" -msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage." +msgid "GeoNodes|Failed" msgstr "" -msgid "GeoNodeSyncStatus|Out of sync" +msgid "GeoNodes|Full" msgstr "" -msgid "GeoNodeSyncStatus|Synced" +msgid "GeoNodes|GitLab version does not match the primary node version" +msgstr "" + +msgid "GeoNodes|GitLab version:" +msgstr "" + +msgid "GeoNodes|Health status:" +msgstr "" + +msgid "GeoNodes|Last event ID processed by cursor:" +msgstr "" + +msgid "GeoNodes|Last event ID seen from primary:" +msgstr "" + +msgid "GeoNodes|Loading nodes" +msgstr "" + +msgid "GeoNodes|Local Attachments:" +msgstr "" + +msgid "GeoNodes|Local LFS objects:" +msgstr "" + +msgid "GeoNodes|Local job artifacts:" +msgstr "" + +msgid "GeoNodes|New node" +msgstr "" + +msgid "GeoNodes|Out of sync" +msgstr "" + +msgid "GeoNodes|Replication slot WAL:" +msgstr "" + +msgid "GeoNodes|Replication slots:" +msgstr "" + +msgid "GeoNodes|Repositories:" +msgstr "" + +msgid "GeoNodes|Selective" +msgstr "" + +msgid "GeoNodes|Storage config:" +msgstr "" + +msgid "GeoNodes|Sync settings:" +msgstr "" + +msgid "GeoNodes|Synced" +msgstr "" + +msgid "GeoNodes|Unused slots" +msgstr "" + +msgid "GeoNodes|Used slots" +msgstr "" + +msgid "GeoNodes|Wikis:" +msgstr "" + +msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS." +msgstr "" + +msgid "Geo|All projects" msgstr "" msgid "Geo|File sync capacity" +msgstr "Объем хранилища Ð´Ð»Ñ Ñинхронизации файлов" + +msgid "Geo|Groups to synchronize" msgstr "" -msgid "Geo|Groups to replicate" +msgid "Geo|Projects in certain groups" msgstr "" -msgid "Geo|Repository sync capacity" +msgid "Geo|Projects in certain storage shards" msgstr "" +msgid "Geo|Repository sync capacity" +msgstr "Объем хранилища Ð´Ð»Ñ Ñинхронизации репозиториÑ" + msgid "Geo|Select groups to replicate." +msgstr "Выберите группы Ð´Ð»Ñ Ñ€ÐµÐ¿Ð»Ð¸ÐºÐ°Ñ†Ð¸Ð¸." + +msgid "Geo|Shards to synchronize" +msgstr "" + +msgid "Git revision" msgstr "" msgid "Git storage health information has been reset" msgstr "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ ÑтабильноÑти Git хранилища была Ñброшена" +msgid "Git version" +msgstr "" + msgid "GitLab Runner section" msgstr "Ð¡ÐµÐºÑ†Ð¸Ñ Gitlab Runner" +msgid "Gitaly Servers" +msgstr "" + msgid "Go to your fork" msgstr "Перейти к вашему ответвлению" @@ -1288,6 +1660,9 @@ msgstr "Ответвление" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "ÐÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ Google не %{link_to_documentation}. ПопроÑите Ñвоего админиÑтратора GitLab, еÑли вы хотите воÑпользоватьÑÑ Ñтим ÑервиÑом." +msgid "Got it!" +msgstr "" + msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" msgstr "Запретить публикацию проектов из %{group} в других группах" @@ -1324,8 +1699,8 @@ msgstr "Группы не найдены" msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group." msgstr "Ð’Ñ‹ можете управлÑÑ‚ÑŒ правами и доÑтупом учаÑтников вашей группы к каждому проекту в группе." -msgid "GroupsTree|Are you sure you want to leave the \"${this.group.fullName}\" group?" -msgstr "Ð’Ñ‹ уверены, что вы хотите покинуть группу \"${this.group.fullName}\"?" +msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?" +msgstr "" msgid "GroupsTree|Create a project in this group." msgstr "Создать проект в Ñтой группе." @@ -1355,7 +1730,7 @@ msgid "GroupsTree|Sorry, no groups or projects matched your search" msgstr "К Ñожалению, по вашему запроÑу групп или проектов не найдено" msgid "Have your users email" -msgstr "" +msgstr "ÐÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð°Ñ Ð¿Ð¾Ñ‡Ñ‚Ð° Ð´Ð»Ñ Ð¾Ð±Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ð¹ пользователей" msgid "Health Check" msgstr "Проверка работоÑпоÑобноÑти" @@ -1375,6 +1750,12 @@ msgstr "Проблем работоÑпоÑобноÑти не обнаружеРmsgid "HealthCheck|Unhealthy" msgstr "ÐеÑтабильный" +msgid "Hide value" +msgid_plural "Hide values" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + msgid "History" msgstr "ИÑториÑ" @@ -1385,22 +1766,28 @@ msgid "Import repository" msgstr "Импорт репозиториÑ" msgid "Improve Issue boards with GitLab Enterprise Edition." -msgstr "" +msgstr "Улучшить доÑки обÑуждений Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ GitLab Enterprise Edition." msgid "Improve issues management with Issue weight and GitLab Enterprise Edition." -msgstr "" +msgstr "Улучшить управление обÑуждениÑми возможноÑтью Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ Ð²ÐµÑа обÑÑƒÐ¶Ð´ÐµÐ½Ð¸Ñ Ð¸ GitLab Enterprise Edition." msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition." -msgstr "" +msgstr "Улучшить поиÑк при помощи РаÑширенного Глобального ПоиÑка и GitLab Enterprise Edition." msgid "Install a Runner compatible with GitLab CI" msgstr "УÑтановите Gitlab Runner ÑовмеÑтимый Ñ Gitlab CI" msgid "Instance" msgid_plural "Instances" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "ÐкземплÑÑ€" +msgstr[1] "ÐкземплÑра" +msgstr[2] "ÐкземплÑров" + +msgid "Instance does not support multiple Kubernetes clusters" +msgstr "" + +msgid "Interested parties can even contribute by pushing commits if they want to." +msgstr "" msgid "Internal - The group and any internal projects can be viewed by any logged in user." msgstr "Внутренний - Группу и включённые в неё проекты может видеть любой зарегиÑтрированный пользователь." @@ -1415,7 +1802,7 @@ msgid "Introducing Cycle Analytics" msgstr "Внедрение Цикла Ðналитик" msgid "Issue board focus mode" -msgstr "" +msgstr "Режим фокуÑировки над доÑкой обÑуждений" msgid "Issue events" msgstr "Ð¡Ð¾Ð±Ñ‹Ñ‚Ð¸Ñ Ð¾Ð±Ñуждений" @@ -1424,11 +1811,14 @@ msgid "IssueBoards|Board" msgstr "ДоÑка" msgid "IssueBoards|Boards" -msgstr "" +msgstr "ДоÑки" msgid "Issues" msgstr "ОбÑуждениÑ" +msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable." +msgstr "" + msgid "Jan" msgstr "Янв." @@ -1447,6 +1837,27 @@ msgstr "Июн." msgid "June" msgstr "Июнь" +msgid "Kubernetes" +msgstr "" + +msgid "Kubernetes Cluster" +msgstr "" + +msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}" +msgstr "" + +msgid "Kubernetes cluster integration was not removed." +msgstr "" + +msgid "Kubernetes cluster integration was successfully removed." +msgstr "" + +msgid "Kubernetes cluster was successfully updated." +msgstr "" + +msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page" +msgstr "" + msgid "LFSStatus|Disabled" msgstr "Отключено" @@ -1456,6 +1867,9 @@ msgstr "Включено" msgid "Labels" msgstr "Метки" +msgid "Labels can be applied to issues and merge requests to categorize them." +msgstr "" + msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "ПоÑледний %d день" @@ -1486,6 +1900,9 @@ msgstr "Ð’Ñ‹ отправили в" msgid "LastPushEvent|at" msgstr "в" +msgid "Learn more" +msgstr "" + msgid "Learn more in the" msgstr "Узнайте больше в" @@ -1502,32 +1919,44 @@ msgid "Leave project" msgstr "Покинуть проект" msgid "License" -msgstr "" +msgstr "ЛицензиÑ" -msgid "Limited to showing %d event at most" -msgid_plural "Limited to showing %d events at most" -msgstr[0] "Показывать %d Ñобытие макÑимум" -msgstr[1] "Показывать %d ÑÐ¾Ð±Ñ‹Ñ‚Ð¸Ñ Ð¼Ð°ÐºÑимум" -msgstr[2] "Показывать %d Ñобытий макÑимум" +msgid "Loading the GitLab IDE..." +msgstr "" msgid "Lock" msgstr "Блокировка" +msgid "Lock %{issuableDisplayName}" +msgstr "" + +msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment." +msgstr "" + msgid "Locked" msgstr "Заблокировано" msgid "Locked Files" -msgstr "" +msgstr "Заблокированные Файлы" msgid "Login" msgstr "Войти" +msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos." +msgstr "" + +msgid "Manage labels" +msgstr "" + msgid "Mar" msgstr "Мар." msgid "March" msgstr "Март" +msgid "Mark done" +msgstr "" + msgid "Maximum git storage failures" msgstr "МакÑимальное количеÑтво Ñбоев хранилища git" @@ -1540,6 +1969,9 @@ msgstr "Среднее" msgid "Members" msgstr "УчаÑтники" +msgid "Merge Request" +msgstr "" + msgid "Merge Requests" msgstr "ЗапроÑÑ‹ на СлиÑние" @@ -1549,9 +1981,30 @@ msgstr "Ð¡Ð¾Ð±Ñ‹Ñ‚Ð¸Ñ ÑлиÑний" msgid "Merge request" msgstr "Ð—Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние" +msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others" +msgstr "" + +msgid "Merged" +msgstr "" + msgid "Messages" msgstr "СообщениÑ" +msgid "Milestone" +msgstr "" + +msgid "Milestones|Delete milestone" +msgstr "" + +msgid "Milestones|Delete milestone %{milestoneTitle}?" +msgstr "" + +msgid "Milestones|Failed to delete milestone %{milestoneTitle}" +msgstr "" + +msgid "Milestones|Milestone %{milestoneTitle} was not found" +msgstr "" + msgid "MissingSSHKeyWarningLink|add an SSH key" msgstr "добавить ключ SSH" @@ -1561,11 +2014,17 @@ msgstr "Мониторинг" msgid "More information is available|here" msgstr "Больше информации доÑтупно|тут" -msgid "Multiple issue boards" +msgid "Move" +msgstr "" + +msgid "Move issue" msgstr "" -msgid "New Cluster" -msgstr "Ðовый КлаÑтер" +msgid "Multiple issue boards" +msgstr "Сводные доÑки обÑуждений" + +msgid "Name new label" +msgstr "" msgid "New Issue" msgid_plural "New Issues" @@ -1573,6 +2032,12 @@ msgstr[0] "Ðовое ОбÑуждение" msgstr[1] "Ðовых ОбÑуждениÑ" msgstr[2] "Ðовых ОбÑуждений" +msgid "New Kubernetes Cluster" +msgstr "" + +msgid "New Kubernetes cluster" +msgstr "" + msgid "New Pipeline Schedule" msgstr "Ðовое РаÑпиÑание Сборочной Линии" @@ -1580,13 +2045,13 @@ msgid "New branch" msgstr "ÐÐ¾Ð²Ð°Ñ Ð²ÐµÑ‚ÐºÐ°" msgid "New branch unavailable" -msgstr "" +msgstr "ÐÐ¾Ð²Ð°Ñ Ð²ÐµÑ‚ÐºÐ° недоÑтупна" msgid "New directory" msgstr "Ðовый каталог" msgid "New epic" -msgstr "" +msgstr "Ðовый Ñпик" msgid "New file" msgstr "Ðовый файл" @@ -1597,6 +2062,9 @@ msgstr "ÐÐ¾Ð²Ð°Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ð°" msgid "New issue" msgstr "Ðовое обÑуждение" +msgid "New label" +msgstr "" + msgid "New merge request" msgstr "Ðовый Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние" @@ -1615,8 +2083,23 @@ msgstr "ÐÐ¾Ð²Ð°Ñ Ð¿Ð¾Ð´Ð³Ñ€ÑƒÐ¿Ð¿Ð°" msgid "New tag" msgstr "Ðовый тег" -msgid "No container images stored for this project. Add one by following the instructions above." -msgstr "Ðет образов контейнеров Ð´Ð»Ñ Ñтого проекта. Добавьте образ, ÑÐ»ÐµÐ´ÑƒÑ Ð¸Ð½ÑтрукциÑм выше." +msgid "No assignee" +msgstr "" + +msgid "No changes" +msgstr "" + +msgid "No connection could be made to a Gitaly Server, please check your logs!" +msgstr "" + +msgid "No due date" +msgstr "" + +msgid "No estimate or time spent" +msgstr "" + +msgid "No file chosen" +msgstr "" msgid "No repository" msgstr "Ðет репозиториÑ" @@ -1625,14 +2108,20 @@ msgid "No schedules" msgstr "Ðет раÑпиÑаний" msgid "No time spent" -msgstr "" +msgstr "Ðет затраченного времени" msgid "None" msgstr "ПуÑто" +msgid "Not allowed to merge" +msgstr "" + msgid "Not available" msgstr "ÐедоÑтупно" +msgid "Not confidential" +msgstr "" + msgid "Not enough data" msgstr "ÐедоÑтаточно данных" @@ -1693,6 +2182,12 @@ msgstr "ОтÑлеживать" msgid "Notifications" msgstr "УведомлениÑ" +msgid "Notifications off" +msgstr "" + +msgid "Notifications on" +msgstr "" + msgid "Nov" msgstr "ÐоÑб." @@ -1702,8 +2197,8 @@ msgstr "ÐоÑбрь" msgid "Number of access attempts" msgstr "КоличеÑтво попыток доÑтупа" -msgid "Number of failures before backing off" -msgstr "КоличеÑтво ошибок перед откатом" +msgid "OK" +msgstr "" msgid "Oct" msgstr "Окт." @@ -1717,9 +2212,12 @@ msgstr "Фильтр" msgid "Only project members can comment." msgstr "Только учаÑтники проекта могут оÑтавлÑÑ‚ÑŒ комментарии." -msgid "Opened" +msgid "Open" msgstr "" +msgid "Opened" +msgstr "Открыт" + msgid "OpenedNDaysAgo|Opened" msgstr "Открыто" @@ -1750,9 +2248,6 @@ msgstr "« ПерваÑ" msgid "Password" msgstr "Пароль" -msgid "People without permission will never get a notification and won\\'t be able to comment." -msgstr "Люди без разрешений не получат уведомление и не Ñмогут комментировать." - msgid "Pipeline" msgstr "Ð¡Ð±Ð¾Ñ€Ð¾Ñ‡Ð½Ð°Ñ Ð»Ð¸Ð½Ð¸Ñ" @@ -1766,7 +2261,7 @@ msgid "Pipeline Schedules" msgstr "РаÑпиÑÐ°Ð½Ð¸Ñ Ð¡Ð±Ð¾Ñ€Ð¾Ñ‡Ð½Ñ‹Ñ… Линий" msgid "Pipeline quota" -msgstr "" +msgstr "Квота Ñборочной линии" msgid "PipelineCharts|Failed:" msgstr "Ðеудача:" @@ -1795,12 +2290,6 @@ msgstr "Ð’Ñе" msgid "PipelineSchedules|Inactive" msgstr "Ðеактивно" -msgid "PipelineSchedules|Input variable key" -msgstr "Ввод ключевой переменной" - -msgid "PipelineSchedules|Input variable value" -msgstr "Ð’Ñтавить значение" - msgid "PipelineSchedules|Next Run" msgstr "Следующий запуÑк" @@ -1810,9 +2299,6 @@ msgstr "ОтÑутÑтвует" msgid "PipelineSchedules|Provide a short description for this pipeline" msgstr "ПредоÑтавьте краткое опиÑание Ñтой Ñборочной линии" -msgid "PipelineSchedules|Remove variable row" -msgstr "Удалить значение" - msgid "PipelineSchedules|Take ownership" msgstr "Стать владельцем" @@ -1840,6 +2326,12 @@ msgstr "Сборочные линии за поÑледнюю неделю" msgid "Pipelines for last year" msgstr "Сборочные линии за поÑледний год" +msgid "Pipelines|Build with confidence" +msgstr "" + +msgid "Pipelines|Get started with Pipelines" +msgstr "" + msgid "Pipeline|all" msgstr "вÑе" @@ -1852,12 +2344,21 @@ msgstr "Ñо Ñтадией" msgid "Pipeline|with stages" msgstr "Ñо ÑтадиÑми" -msgid "Please solve the reCAPTCHA" +msgid "Play" msgstr "" +msgid "Please <a href=%{link_to_billing} target=\"_blank\" rel=\"noopener noreferrer\">enable billing for one of your projects to be able to create a Kubernetes cluster</a>, then try again." +msgstr "" + +msgid "Please solve the reCAPTCHA" +msgstr "ПожалуйÑта, решите reCAPTCHA" + msgid "Preferences" msgstr "ПредпочтениÑ" +msgid "Primary" +msgstr "" + msgid "Private - Project access must be granted explicitly to each user." msgstr "Приватный - ДоÑтуп к проекту должен предоÑтавлÑÑ‚ÑŒÑÑ Ñвно каждому пользователю." @@ -1903,6 +2404,9 @@ msgstr "Ваша ÑƒÑ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ в наÑтоÑщее Ð²Ñ€ÐµÐ¼Ñ Ñ msgid "Profiles|your account" msgstr "ваша ÑƒÑ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ" +msgid "Programming languages used in this repository" +msgstr "" + msgid "Project '%{project_name}' is in the process of being deleted." msgstr "Проект '%{project_name}' находитÑÑ Ð² процеÑÑе удалениÑ." @@ -1918,6 +2422,15 @@ msgstr "Проект '%{project_name}' уÑпешно обновлен." msgid "Project access must be granted explicitly to each user." msgstr "ДоÑтуп к проекту должен предоÑтавлÑÑ‚ÑŒÑÑ Ñвно каждому пользователю." +msgid "Project avatar" +msgstr "" + +msgid "Project avatar in repository: %{link}" +msgstr "" + +msgid "Project cache successfully reset." +msgstr "" + msgid "Project details" msgstr "Детали проекта" @@ -1936,6 +2449,21 @@ msgstr "Ðачат ÑкÑпорт проекта. СÑылка Ð´Ð»Ñ Ñкачи msgid "ProjectActivityRSS|Subscribe" msgstr "ПодпиÑатьÑÑ" +msgid "ProjectCreationLevel|Allowed to create projects" +msgstr "" + +msgid "ProjectCreationLevel|Default project creation protection" +msgstr "" + +msgid "ProjectCreationLevel|Developers + Masters" +msgstr "" + +msgid "ProjectCreationLevel|Masters" +msgstr "" + +msgid "ProjectCreationLevel|No one" +msgstr "" + msgid "ProjectFeature|Disabled" msgstr "Отключено" @@ -1958,28 +2486,22 @@ msgid "ProjectNetworkGraph|Graph" msgstr "Граф" msgid "ProjectSettings|Contact an admin to change this setting." -msgstr "" - -msgid "ProjectSettings|Immediately run a pipeline on the default branch" -msgstr "" +msgstr "ОбратитеÑÑŒ к админиÑтратору Ð´Ð»Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ñтой наÑтройки." msgid "ProjectSettings|Only signed commits can be pushed to this repository." -msgstr "" - -msgid "ProjectSettings|Problem setting up the CI/CD settings JavaScript" -msgstr "" +msgstr "Только подпиÑанные коммиты могут быть помещены в Ñтот репозиторий." msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin." -msgstr "" +msgstr "Ðта наÑтройка применÑетÑÑ Ð½Ð° уровне Ñервера и может быть переопределена админиÑтратором." msgid "ProjectSettings|This setting is applied on the server level but has been overridden for this project." -msgstr "" +msgstr "Ðта наÑтройка применÑетÑÑ Ð½Ð° уровне Ñервера, но была переопределена Ð´Ð»Ñ Ñтого проекта." msgid "ProjectSettings|This setting will be applied to all projects unless overridden by an admin." -msgstr "" +msgstr "Ðта наÑтройка будет применена Ð´Ð»Ñ Ð²Ñех проектов, еÑли иное поведение не переопределено админиÑтратором." msgid "ProjectSettings|Users can only push commits to this repository that were committed with one of their own verified emails." -msgstr "" +msgstr "Пользователи могут отправлÑÑ‚ÑŒ коммиты в данный репозиторий, только в Ñлучае еÑли коммит подпиÑан одним из подтвержденных адреÑов почты." msgid "Projects" msgstr "Проекты" @@ -2006,36 +2528,39 @@ msgid "ProjectsDropdown|This feature requires browser localStorage support" msgstr "Ðта функциональноÑÑ‚ÑŒ требует поддержки localStorage в вашем браузере" msgid "PrometheusService|By default, Prometheus listens on ‘http://localhost:9090’. It’s not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server." -msgstr "" +msgstr "По умолчанию, Prometheus запуÑкаетÑÑ Ð¿Ð¾ адреÑу ‘http://localhost:9090’. Ðе рекомендуетÑÑ Ð¸Ð·Ð¼ÐµÐ½ÑÑ‚ÑŒ Ð°Ð´Ñ€ÐµÑ Ð¸ порт по умолчанию, так как Ñто может привеÑти к конфликту Ñ Ð´Ñ€ÑƒÐ³Ð¸Ð¼ ÑервиÑами запущенными на GitLab Ñервере." msgid "PrometheusService|Finding and configuring metrics..." -msgstr "" +msgstr "Определение и наÑтройка метрик..." msgid "PrometheusService|Metrics" -msgstr "" +msgstr "Метрики" msgid "PrometheusService|Metrics are automatically configured and monitored based on a library of metrics from popular exporters." -msgstr "" +msgstr "Метрики автоматичеÑки наÑтраиваютÑÑ Ð¸ отÑлеживаютÑÑ Ð½Ð° оÑнове популÑрных библиотек метрик." msgid "PrometheusService|Missing environment variable" -msgstr "" +msgstr "Пропущена Ð¿ÐµÑ€ÐµÐ¼ÐµÐ½Ð½Ð°Ñ Ð¾ÐºÑ€ÑƒÐ¶ÐµÐ½Ð¸Ñ" msgid "PrometheusService|Monitored" -msgstr "" +msgstr "Мониторинг подключен" msgid "PrometheusService|More information" -msgstr "" +msgstr "Ð”Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ" msgid "PrometheusService|No metrics are being monitored. To start monitoring, deploy to an environment." -msgstr "" +msgstr "Ðи одной метрики не отÑлеживаетÑÑ. Ð”Ð»Ñ Ð½Ð°Ñ‡Ð°Ð»Ð° мониторинга разверните окружение." msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/" -msgstr "" +msgstr "Базовый Ð°Ð´Ñ€ÐµÑ Prometheus API, например http://prometheus.example.com/" -msgid "PrometheusService|Prometheus monitoring" +msgid "PrometheusService|Time-series monitoring service" msgstr "" msgid "PrometheusService|View environments" +msgstr "ПроÑмотр окружений" + +msgid "Protip:" msgstr "" msgid "Public - The group and any public projects can be viewed without any authentication." @@ -2045,12 +2570,15 @@ msgid "Public - The project can be accessed without any authentication." msgstr "Публичный - ДоÑтуп к проекту возможен без какой-либо проверки подлинноÑти." msgid "Push Rules" -msgstr "" +msgstr "Правила Отправки" msgid "Push events" msgstr "Ð¡Ð¾Ð±Ñ‹Ñ‚Ð¸Ñ Ð¾Ñ‚Ð¿Ñ€Ð°Ð²ÐºÐ¸" msgid "PushRule|Committer restriction" +msgstr "ÐžÐ³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ñ Ð´Ð»Ñ ÐºÐ¾Ð¼Ð¼Ð¸Ñ‚ÐµÑ€Ð°" + +msgid "Quick actions can be used in the issues description and comment boxes." msgstr "" msgid "Read more" @@ -2065,9 +2593,15 @@ msgstr "Ветки" msgid "RefSwitcher|Tags" msgstr "Теги" -msgid "Registry" +msgid "Reference:" msgstr "" +msgid "Register / Sign In" +msgstr "" + +msgid "Registry" +msgstr "РееÑÑ‚Ñ€" + msgid "Related Commits" msgstr "СвÑзанные коммиты" @@ -2089,9 +2623,18 @@ msgstr "СвÑзанные Влитые ЗапроÑÑ‹" msgid "Remind later" msgstr "Ðапомнить позже" +msgid "Remove" +msgstr "" + +msgid "Remove avatar" +msgstr "" + msgid "Remove project" msgstr "Удалить проект" +msgid "Repair authentication" +msgstr "" + msgid "Repository" msgstr "Репозиторий" @@ -2107,6 +2650,12 @@ msgstr "СброÑить ключ доÑтупа проверки Ñ€Ð°Ð±Ð¾Ñ‚Ð¾Ñ msgid "Reset runners registration token" msgstr "СброÑить ключ региÑтрации Gitlab Runners" +msgid "Reveal value" +msgid_plural "Reveal values" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + msgid "Revert this commit" msgstr "Отменить Ñто коммит" @@ -2116,15 +2665,15 @@ msgstr "Отменить Ñтот Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние" msgid "SSH Keys" msgstr "SSH Ключи" -msgid "Save" -msgstr "Сохранить" - msgid "Save changes" msgstr "Сохранить изменениÑ" msgid "Save pipeline schedule" msgstr "Сохранить раÑпиÑание Ñборочной лини" +msgid "Save variables" +msgstr "" + msgid "Schedule a new pipeline" msgstr "РаÑпиÑание новой Ñборочной линии" @@ -2135,43 +2684,64 @@ msgid "Scheduling Pipelines" msgstr "Планирование Сборочных Линий" msgid "Scoped issue boards" -msgstr "" +msgstr "ТематичеÑкие доÑки обÑуждений" msgid "Search branches and tags" msgstr "Ðайти ветки и теги" +msgid "Search milestones" +msgstr "" + +msgid "Search project" +msgstr "" + +msgid "Search users" +msgstr "" + msgid "Seconds before reseting failure information" msgstr "Секунд до очиÑтки информации о ÑбоÑÑ…" -msgid "Seconds to wait after a storage failure" -msgstr "Секунд задержки поÑле ÑÐ±Ð¾Ñ Ð² хранилище" - msgid "Seconds to wait for a storage access attempt" msgstr "Секунд задержки между попытками доÑтупа к хранилищу" +msgid "Secret variables" +msgstr "" + msgid "Select Archive Format" msgstr "Выбрать формат архива" msgid "Select a timezone" msgstr "Выбор временной зоны" +msgid "Select assignee" +msgstr "" + +msgid "Select branch/tag" +msgstr "" + msgid "Select target branch" msgstr "Выбор целевой ветки" +msgid "Selective synchronization" +msgstr "" + msgid "Sep" msgstr "Сент." msgid "September" msgstr "СентÑбрь" +msgid "Server version" +msgstr "" + msgid "Service Templates" msgstr "Шаблоны Служб" msgid "Set a password on your account to pull or push via %{protocol}." msgstr "УÑтановите пароль в Ñвоем аккаунте, чтобы отправлÑÑ‚ÑŒ или получать код через %{protocol}." -msgid "Set up CI" -msgstr "ÐаÑтройка CI" +msgid "Set up CI/CD" +msgstr "" msgid "Set up Koding" msgstr "ÐаÑтройка Koding" @@ -2185,6 +2755,15 @@ msgstr "уÑтановите пароль" msgid "Settings" msgstr "ÐаÑтройки" +msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero." +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset pipeline minutes" +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes" +msgstr "" + msgid "Show parent pages" msgstr "Показать родительÑкие Ñтраницы" @@ -2198,27 +2777,33 @@ msgstr[1] "Показано %d Ñобытий" msgstr[2] "Показано %d Ñобытий" msgid "Sidebar|Change weight" -msgstr "" - -msgid "Sidebar|Edit" -msgstr "" +msgstr "Изменить веÑ" msgid "Sidebar|No" -msgstr "" +msgstr "Ðет" msgid "Sidebar|None" -msgstr "" +msgstr "ОтÑутÑтвует" msgid "Sidebar|Weight" -msgstr "" +msgstr "ВеÑ" msgid "Snippets" msgstr "Сниппеты" +msgid "Something went wrong on our end" +msgstr "" + msgid "Something went wrong on our end." msgstr "У Ð½Ð°Ñ Ñ‡Ñ‚Ð¾-то пошло не так." +msgid "Something went wrong trying to change the confidentiality of this issue" +msgstr "" + msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}" +msgstr "Что-то пошло не так при попытке Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ ÑоÑтоÑÐ½Ð¸Ñ Ð±Ð»Ð¾ÐºÐ¸Ñ€Ð¾Ð²ÐºÐ¸ ${this.issuableDisplayName}" + +msgid "Something went wrong when toggling the button" msgstr "" msgid "Something went wrong while fetching the projects." @@ -2227,6 +2812,9 @@ msgstr "Что-то пошло не так при получении Ð¿Ñ€Ð¾ÐµÐºÑ msgid "Something went wrong while fetching the registry list." msgstr "Что-то пошло не так при получении ÑпиÑка рееÑтров." +msgid "Something went wrong. Please try again." +msgstr "" + msgid "Sort by" msgstr "Сортировать по" @@ -2270,7 +2858,7 @@ msgid "SortOptions|Least popular" msgstr "Ðаименее популÑрный" msgid "SortOptions|Less weight" -msgstr "" +msgstr "Меньший веÑ" msgid "SortOptions|Milestone" msgstr "Веха" @@ -2282,7 +2870,7 @@ msgid "SortOptions|Milestone due soon" msgstr "Веха, наÑÑ‚ÑƒÐ¿Ð°ÑŽÑ‰Ð°Ñ Ñ€Ð°Ð½ÑŒÑˆÐµ" msgid "SortOptions|More weight" -msgstr "" +msgstr "Больший веÑ" msgid "SortOptions|Most popular" msgstr "Ðаиболее популÑрный" @@ -2324,7 +2912,7 @@ msgid "SortOptions|Start soon" msgstr "Ðачатые недавно" msgid "SortOptions|Weight" -msgstr "" +msgstr "ВеÑ" msgid "Source" msgstr "ИÑточник" @@ -2333,7 +2921,7 @@ msgid "Source code" msgstr "ИÑходный код" msgid "Source is not available" -msgstr "" +msgstr "ИÑходный текÑÑ‚ недоÑтупен" msgid "Spam Logs" msgstr "Спам Логи" @@ -2354,14 +2942,14 @@ msgid "Start the Runner!" msgstr "ЗапуÑтить GitLab Runner!" msgid "Stopped" +msgstr "ОÑтановлен" + +msgid "Storage" msgstr "" msgid "Subgroups" msgstr "Подгруппы" -msgid "Subscribe" -msgstr "ПодпиÑатьÑÑ" - msgid "Switch branch/tag" msgstr "Переключить ветка/тег" @@ -2378,52 +2966,52 @@ msgid "Tags" msgstr "Теги" msgid "TagsPage|Browse commits" -msgstr "" +msgstr "ПроÑмотреть коммиты" msgid "TagsPage|Browse files" -msgstr "" +msgstr "ПроÑмотреть файлы" msgid "TagsPage|Can't find HEAD commit for this tag" -msgstr "" +msgstr "Ðевозможно найти HEAD коммит Ð´Ð»Ñ Ñтого тега" msgid "TagsPage|Cancel" -msgstr "" +msgstr "Отмена" msgid "TagsPage|Create tag" -msgstr "" +msgstr "Создать тег" msgid "TagsPage|Delete tag" -msgstr "" +msgstr "Удалить тег" msgid "TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?" -msgstr "" +msgstr "Удаление тега %{tag_name} не может быть отменено. Ð’Ñ‹ уверены?" msgid "TagsPage|Edit release notes" -msgstr "" +msgstr "Редактировать заметки к релизу" msgid "TagsPage|Existing branch name, tag, or commit SHA" -msgstr "" +msgstr "Ð˜Ð¼Ñ ÑущеÑтвующей ветки, тега или SHA коммита" msgid "TagsPage|Filter by tag name" -msgstr "" +msgstr "Фильтр по имени тега" msgid "TagsPage|New Tag" -msgstr "" +msgstr "Ðовый Тег" msgid "TagsPage|New tag" -msgstr "" +msgstr "Ðовый тег" msgid "TagsPage|Optionally, add a message to the tag." -msgstr "" +msgstr "При желании вы можете добавить Ñообщение в тег." msgid "TagsPage|Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page." -msgstr "" +msgstr "Опционально, добавьте опиÑание релиза к тегу. Оно будет Ñохранено в базе данных GitLab и отобразитÑÑ Ð½Ð° Ñтранице тегов." msgid "TagsPage|Release notes" -msgstr "" +msgstr "Заметки к релизу" msgid "TagsPage|Repository has no tags yet." -msgstr "" +msgstr "Репозитарий не Ñодержит тегов." msgid "TagsPage|Sort by" msgstr "Сортировать по" @@ -2432,19 +3020,19 @@ msgid "TagsPage|Tags" msgstr "Теги" msgid "TagsPage|Tags give the ability to mark specific points in history as being important" -msgstr "" +msgstr "Теги дают возможноÑÑ‚ÑŒ отмечать определенные точки в иÑтории как важные" msgid "TagsPage|This tag has no release notes." -msgstr "" +msgstr "Ðтот тег не Ñодержит заметок к релизу." msgid "TagsPage|Use git tag command to add a new one:" -msgstr "" +msgstr "ИÑпользуйте команду git tag, чтобы добавить новый тег:" msgid "TagsPage|Write your release notes or drag files here..." -msgstr "" +msgstr "Ðапишите Ñвои заметки к релизу или перетащите файлы Ñюда..." msgid "TagsPage|protected" -msgstr "" +msgstr "защищенный" msgid "Target Branch" msgstr "Ветка" @@ -2453,13 +3041,16 @@ msgid "Team" msgstr "Команда" msgid "Thanks! Don't show me this again" -msgstr "" +msgstr "СпаÑибо! Больше не показывайте мне Ñто Ñообщение" msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project." +msgstr "РаÑширенный глобальный поиÑк в GitLab - Ñто мощный инÑтрумент Ð´Ð»Ñ Ð¿Ð¾Ð¸Ñка, который Ñокращает ваше времÑ. ВмеÑто ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð´ÑƒÐ±Ð»Ð¸Ñ€ÑƒÑŽÑ‰ÐµÐ³Ð¾ кода и траты времени, вы можете иÑкать код внутри других команд, который поможет вам в вашем проекте." + +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project" msgstr "" -msgid "The circuitbreaker backoff threshold should be lower than the failure count threshold" -msgstr "Порог ÑÑ€Ð°Ð±Ð°Ñ‚Ñ‹Ð²Ð°Ð½Ð¸Ñ Ð´Ð»Ñ Ð¡ircuitBreaker должен быть меньше, чем порог ÑÑ€Ð°Ð±Ð°Ñ‚Ñ‹Ð²Ð°Ð½Ð¸Ñ Ð´Ð»Ñ Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ ÑбоÑ" +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project." +msgstr "" msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request." msgstr "Ðтап напиÑÐ°Ð½Ð¸Ñ ÐºÐ¾Ð´Ð° показывает Ð²Ñ€ÐµÐ¼Ñ Ñ Ð¿ÐµÑ€Ð²Ð¾Ð³Ð¾ коммита до ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð·Ð°Ð¿Ñ€Ð¾Ñа на ÑлиÑние. Данные автоматичеÑки добавÑÑ‚ÑÑ Ñюда поÑле того, как вы Ñоздать Ñвой первый Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние." @@ -2473,21 +3064,18 @@ msgstr "СвÑзь Ñ Ð¾Ñ‚Ð²ÐµÑ‚Ð²Ð»ÐµÐ½Ð¸ÐµÐ¼ удалена." msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage." msgstr "Ð¡Ñ‚Ð°Ð´Ð¸Ñ Ð¾Ð±ÑÑƒÐ¶Ð´ÐµÐ½Ð¸Ñ Ð¿Ð¾ÐºÐ°Ð·Ñ‹Ð²Ð°ÐµÑ‚ времÑ, которое потребуетÑÑ Ñ Ð¼Ð¾Ð¼ÐµÐ½Ñ‚Ð° ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð¾Ð±ÑÑƒÐ¶Ð´ÐµÐ½Ð¸Ñ Ð´Ð¾ Ð½Ð°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð¾Ð±Ñуждению вехи, или Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð¾Ð±ÑÑƒÐ¶Ð´ÐµÐ½Ð¸Ñ Ð½Ð° вашу доÑку задач. Ðачните Ñоздавать обÑуждениÑ, чтобы увидеть ÑÐ²ÐµÐ´ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ñтой Ñтадии." +msgid "The maximum file size allowed is 200KB." +msgstr "" + msgid "The number of attempts GitLab will make to access a storage." msgstr "КоличеÑтво попыток, которые GitLab будет предпринимать Ð´Ð»Ñ Ð´Ð¾Ñтупа к хранилищу." -msgid "The number of failures after which GitLab will start temporarily disabling access to a storage shard on a host" -msgstr "КоличеÑтво ошибок, поÑле которого GitLab начнёт временно отключать доÑтуп к шарду хранилища на хоÑте" - msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}." msgstr "КоличеÑтво Ñбоев, поÑле которого Gitlab полноÑтью прекратит доÑтуп к хранилищу. Изменение Ñчётчика \"количеÑтво Ñбоев\" может быть произведено через админиÑтративный интерфейÑ: %{link_to_health_page} или при помощи API %{api_documentation_link}." msgid "The phase of the development lifecycle." msgstr "Фаза жизненного цикла разработки." -msgid "The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user." -msgstr "РаÑпиÑание Ñборочных линий регулÑрно запуÑкает Ñборочные линии Ð´Ð»Ñ Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð½Ñ‹Ñ… ветвей или тегов. Запланированные Ñборочные линии наÑледуют Ð¾Ð³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ñ Ð½Ð° доÑтуп к проекту на оÑнове ÑвÑзанного Ñ Ð½Ð¸Ð¼Ð¸ пользователÑ." - msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit." msgstr "Ðтап Ð¿Ð»Ð°Ð½Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¿Ð¾ÐºÐ°Ð·Ñ‹Ð²Ð°ÐµÑ‚ Ð²Ñ€ÐµÐ¼Ñ Ð¾Ñ‚ предыдущего шага до отправки первого коммита. ДобавлÑетÑÑ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑки, как только отправите Ñвой первый коммит." @@ -2518,20 +3106,47 @@ msgstr "Ð’Ñ€ÐµÐ¼Ñ Ð² Ñекундах, в течение которого GitLa msgid "The time in seconds GitLab will try to access storage. After this time a timeout error will be raised." msgstr "Ð’Ñ€ÐµÐ¼Ñ Ð² Ñекундах в течении которого GitLab будет пытатьÑÑ Ð¿Ð¾Ð»ÑƒÑ‡Ð¸Ñ‚ÑŒ доÑтуп к хранилищу. ПоÑле Ñтого времени будет зафикÑирована ошибка Ð¿Ñ€ÐµÐ²Ñ‹ÑˆÐµÐ½Ð¸Ñ Ð²Ñ€ÐµÐ¼ÐµÐ½Ð¸ ожиданиÑ." +msgid "The time in seconds between storage checks. When a previous check did complete yet, GitLab will skip a check." +msgstr "" + msgid "The time taken by each data entry gathered by that stage." msgstr "ВремÑ, затраченное каждым Ñлементом, Ñобранным на Ñтом Ñтапе." msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6." msgstr "Среднее значение в Ñ€Ñду. Пример: между 3, 5, 9, Ñреднее 5, между 3, 5, 7, 8, Ñреднее (5+7)/2 = 6." +msgid "There are no issues to show" +msgstr "" + +msgid "There are no merge requests to show" +msgstr "" + msgid "There are problems accessing Git storage: " msgstr "Проблемы Ñ Ð´Ð¾Ñтупом к Git хранилищу: " -msgid "This board\\'s scope is reduced" +msgid "There was an error loading users activity calendar." msgstr "" -msgid "This branch has changed since you started editing. Would you like to create a new branch?" -msgstr "Ðта ветка была изменена, пока вы её редактировали. Ð’Ñ‹ хотите Ñоздать новую ветку?" +msgid "There was an error saving your notification settings." +msgstr "" + +msgid "There was an error subscribing to this label." +msgstr "" + +msgid "There was an error when reseting email token." +msgstr "" + +msgid "There was an error when subscribing to this label." +msgstr "" + +msgid "There was an error when unsubscribing from this label." +msgstr "" + +msgid "This board\\'s scope is reduced" +msgstr "УÑтановлен отбор" + +msgid "This directory" +msgstr "" msgid "This is a confidential issue." msgstr "Ðто конфиденциальное обÑуждение." @@ -2539,21 +3154,48 @@ msgstr "Ðто конфиденциальное обÑуждение." msgid "This is the author's first Merge Request to this project." msgstr "Ðто первый Ð—Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние от автора в Ñтот проект." +msgid "This issue is confidential" +msgstr "" + msgid "This issue is confidential and locked." msgstr "Ðто обÑуждение конфиденциально и заблокировано." msgid "This issue is locked." msgstr "ОбÑуждение заблокировано." +msgid "This job depends on a user to trigger its process. Often they are used to deploy code to production environments" +msgstr "" + +msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered" +msgstr "" + +msgid "This job has not been triggered yet" +msgstr "" + +msgid "This job has not started yet" +msgstr "" + +msgid "This job is in pending state and is waiting to be picked by a runner" +msgstr "" + +msgid "This job requires a manual action" +msgstr "" + msgid "This means you can not push code until you create an empty repository or import existing one." msgstr "Ðто означает, что вы не можете отправить код, пока не Ñоздадите пуÑтой репозиторий или не импортируете ÑущеÑтвующий." msgid "This merge request is locked." msgstr "Ð—Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние заблокирован." -msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here." +msgid "This project" +msgstr "" + +msgid "This repository" msgstr "" +msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here." +msgstr "Ðти Ñлектронные пиÑьма автоматичеÑки преобразуютÑÑ Ð² обÑÑƒÐ¶Ð´ÐµÐ½Ð¸Ñ (комментарии раÑÑылаютÑÑ ÐºÐ°Ðº ветвь обÑÑƒÐ¶Ð´ÐµÐ½Ð¸Ñ Ð² почте), перечиÑленные здеÑÑŒ." + msgid "Time before an issue gets scheduled" msgstr "Ð’Ñ€ÐµÐ¼Ñ Ð´Ð¾ начала Ð¿Ð¾Ð¿Ð°Ð´Ð°Ð½Ð¸Ñ Ð¾Ð±ÑÑƒÐ¶Ð´ÐµÐ½Ð¸Ñ Ð² планировщик" @@ -2563,9 +3205,21 @@ msgstr "Ð’Ñ€ÐµÐ¼Ñ Ð´Ð¾ начала работы над обÑуждением" msgid "Time between merge request creation and merge/close" msgstr "Ð’Ñ€ÐµÐ¼Ñ Ð¼ÐµÐ¶Ð´Ñƒ Ñозданием запроÑа ÑлиÑÐ½Ð¸Ñ Ð¸ ÑлиÑнием / закрытием" +msgid "Time tracking" +msgstr "" + msgid "Time until first merge request" msgstr "Ð’Ñ€ÐµÐ¼Ñ Ð´Ð¾ первого запроÑа на ÑлиÑние" +msgid "TimeTrackingEstimated|Est" +msgstr "" + +msgid "TimeTracking|Estimated:" +msgstr "" + +msgid "TimeTracking|Spent" +msgstr "" + msgid "Timeago|%s days ago" msgstr "%s дней назад" @@ -2705,52 +3359,85 @@ msgid "Time|s" msgstr "Ñ" msgid "Title" +msgstr "Заголовок" + +msgid "Todo" +msgstr "" + +msgid "Toggle sidebar" +msgstr "" + +msgid "ToggleButton|Toggle Status: OFF" +msgstr "" + +msgid "ToggleButton|Toggle Status: ON" msgstr "" msgid "Total Time" msgstr "Общее времÑ" msgid "Total issue time spent" -msgstr "" +msgstr "Общее времÑ, затраченное на обÑуждение" msgid "Total test time for all commits/merges" msgstr "Общее Ð²Ñ€ÐµÐ¼Ñ Ñ‚ÐµÑÑ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ñ„Ð¸ÐºÑаций/ÑлиÑний" msgid "Track activity with Contribution Analytics." -msgstr "" +msgstr "ОтÑлеживать активноÑÑ‚ÑŒ Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ Ðналитики УчаÑтников." msgid "Track groups of issues that share a theme, across projects and milestones" +msgstr "Следите за обÑуждениÑми, Ñгруппированными по темам, Ñразу из неÑкольких проектов и Ñтапов" + +msgid "Total: %{total}" +msgstr "" + +msgid "Track time with quick actions" +msgstr "" + +msgid "Trigger this manual action" msgstr "" msgid "Turn on Service Desk" +msgstr "Включить Службу Поддержки" + +msgid "Type %{value} to confirm:" +msgstr "" + +msgid "Unable to reset project cache." +msgstr "" + +msgid "Unknown" msgstr "" msgid "Unlock" msgstr "Разблокировать" +msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment." +msgstr "" + msgid "Unlocked" msgstr "Разблокировано" msgid "Unstar" msgstr "СнÑÑ‚ÑŒ отметку" -msgid "Unsubscribe" -msgstr "ОтпиÑатьÑÑ" +msgid "Up to date" +msgstr "" msgid "Upgrade your plan to activate Advanced Global Search." -msgstr "" +msgstr "ПовыÑьте ваш тарифный план, чтобы активировать Улучшенный Глобальный ПоиÑк." msgid "Upgrade your plan to activate Contribution Analytics." -msgstr "" +msgstr "ПовыÑьте ваш тарифный план, чтобы активировать Ðналитики УчаÑтников." msgid "Upgrade your plan to activate Group Webhooks." -msgstr "" +msgstr "ПовыÑьте ваш тарифный план, чтобы активировать Групповые Веб-Обработчики." msgid "Upgrade your plan to activate Issue weight." -msgstr "" +msgstr "ПовыÑьте ваш тарифный план чтобы активировать Ð²ÐµÑ Ð¾Ð±Ñуждений." msgid "Upgrade your plan to improve Issue boards." -msgstr "" +msgstr "ПовыÑьте ваш тарифный план, чтобы улучшить доÑки обÑуждений." msgid "Upload New File" msgstr "Загрузить новый файл" @@ -2758,11 +3445,14 @@ msgstr "Загрузить новый файл" msgid "Upload file" msgstr "Загрузить файл" +msgid "Upload new avatar" +msgstr "" + msgid "UploadLink|click to upload" msgstr "кликните Ð´Ð»Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸" msgid "Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab" -msgstr "" +msgstr "ИÑпользуйте Службу поддержки Ð´Ð»Ñ ÑвÑзи Ñ Ð²Ð°ÑˆÐ¸Ð¼Ð¸ пользователÑми (например, Ð´Ð»Ñ Ð¾ÑущеÑÑ‚Ð²Ð»ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶ÐºÐ¸ клиентов) через Ñлектронную почту непоÑредÑтвенно в GitLab" msgid "Use the following registration token during setup:" msgstr "ИÑпользуйте Ñледующий токен региÑтрации в процеÑÑе уÑтановки:" @@ -2770,9 +3460,15 @@ msgstr "ИÑпользуйте Ñледующий токен региÑтрацРmsgid "Use your global notification setting" msgstr "ИÑпользуютÑÑ Ð³Ð»Ð¾Ð±Ð°Ð»ÑŒÐ½Ñ‹Ð¹ наÑтройки уведомлений" +msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." +msgstr "" + msgid "View file @ " msgstr "ПроÑмотр файла @ " +msgid "View labels" +msgstr "" + msgid "View open merge request" msgstr "ПроÑмотреть открытый Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние" @@ -2794,20 +3490,20 @@ msgstr "Ðе определен" msgid "Want to see the data? Please ask an administrator for access." msgstr "Хотите увидеть данные? ОбратитеÑÑŒ к админиÑтратору за доÑтупом." +msgid "We could not verify that one of your projects on GCP has billing enabled. Please try again." +msgstr "" + msgid "We don't have enough data to show this stage." msgstr "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¿Ð¾ Ñтапу отÑутÑтвует." msgid "We want to be sure it is you, please confirm you are not a robot." -msgstr "" +msgstr "Мы хотим быть уверены, что Ñто вы, пожалуйÑта, подтвердите, что вы не робот." msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group." -msgstr "" +msgstr "Веб-обработчики позволÑÑŽÑ‚ вам вызывать Ð°Ð´Ñ€ÐµÑ URL еÑли, например, отправлен новый код или Ñоздано новое обÑуждение. Ð’Ñ‹ можете наÑтроить веб-обработчики так, чтобы они реагировали на определённые ÑобытиÑ, такие как отправки кода, обÑÑƒÐ¶Ð´ÐµÐ½Ð¸Ñ Ð¸Ð»Ð¸ запроÑÑ‹ на ÑлиÑние. Групповые веб-обработчики применÑÑŽÑ‚ÑÑ ÐºÐ¾ вÑем проектам в группе и позволÑÑŽÑ‚ вам Ñтандартизовать функциональноÑÑ‚ÑŒ веб-обработчиков Ð´Ð»Ñ Ð²Ñей вашей группы." msgid "Weight" -msgstr "" - -msgid "When access to a storage fails. GitLab will prevent access to the storage for the time specified here. This allows the filesystem to recover. Repositories on failing shards are temporarly unavailable" -msgstr "Когда доÑтуп к хранилищу получить не удалоÑÑŒ, GitLab приоÑтановит доÑтуп к хранилищу на времÑ, указанное здеÑÑŒ. Ðто позволит файловой ÑиÑтеме воÑÑтановитьÑÑ. Репозитории на Ñбойных \"шардах\" будут временно недоÑтупны" +msgstr "ВеÑ" msgid "Wiki" msgstr "Wiki" @@ -2827,6 +3523,12 @@ msgstr "РекомендуетÑÑ ÑƒÑтановить %{markdown}, чтобы msgid "WikiClone|Start Gollum and edit locally" msgstr "ЗапуÑтите Gollum и редактируете локально" +msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title." +msgstr "" + +msgid "WikiEdit|There is already a page with the same title in that path." +msgstr "" + msgid "WikiEmptyPageError|You are not allowed to create wiki pages" msgstr "Ð’Ñ‹ не можете Ñоздавать вики-Ñтраницы" @@ -2912,7 +3614,7 @@ msgid "Wiki|Wiki Pages" msgstr "Вики Страницы" msgid "With contribution analytics you can have an overview for the activity of issues, merge requests and push events of your organization and its members." -msgstr "" +msgstr "С аналитикой учаÑтников вы можете изучать активноÑÑ‚ÑŒ в обÑуждениÑÑ…, запроÑах на ÑлиÑние и Ñобытий отправки кода Ð´Ð»Ñ Ð²Ð°ÑˆÐµÐ¹ организации и её учаÑтников." msgid "Withdraw Access Request" msgstr "Отменить Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð´Ð¾Ñтупа" @@ -2929,12 +3631,24 @@ msgstr "Ð’Ñ‹ ÑобираетеÑÑŒ удалить ÑвÑзь ответвлен msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" msgstr "Ð’Ñ‹ ÑобираетеÑÑŒ передать проект %{project_name_with_namespace} другому владельцу. Ð’Ñ‹ ÐБСОЛЮТÐО уверены?" +msgid "You can also star a label to make it a priority label." +msgstr "" + +msgid "You can move around the graph by using the arrow keys." +msgstr "" + +msgid "You can move around the graph by using the arrow keys." +msgstr "" + msgid "You can only add files when you are on a branch" msgstr "Ð’Ñ‹ можете добавлÑÑ‚ÑŒ только файлы, когда находитеÑÑŒ в ветке" -msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead." +msgid "You can only edit files when you are on a branch" msgstr "" +msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead." +msgstr "Ð’Ñ‹ не можете запиÑывать на подчиненные ÑкземплÑры \"только Ð´Ð»Ñ Ñ‡Ñ‚ÐµÐ½Ð¸Ñ\" клаÑтера GitLab Geo. ИÑпользуйте вмеÑто Ñтого %{link_to_primary_node}." + msgid "You cannot write to this read-only GitLab instance." msgstr "Ð’Ñ‹ не можете запиÑывать на Ñтот ÑкземплÑÑ€ \"только Ð´Ð»Ñ Ñ‡Ñ‚ÐµÐ½Ð¸Ñ\" клаÑтера GitLab." @@ -2969,6 +3683,12 @@ msgid "You won't be able to pull or push project code via SSH until you %{add_ss msgstr "Ð’Ñ‹ не Ñможете получать и отправлÑÑ‚ÑŒ код проекта через SSH пока %{add_ssh_key_link} в ваш профиль." msgid "You won't be able to pull or push project code via SSH until you add an SSH key to your profile" +msgstr "Ð’Ñ‹ не Ñможете работать Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚Ð¾Ð¼ через SSH, пока не добавите в Ñвой профиль SSH ключ" + +msgid "You'll need to use different branch names to get a valid comparison." +msgstr "" + +msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure" msgstr "" msgid "Your comment will not be visible to the public." @@ -2983,13 +3703,67 @@ msgstr "Ваше имÑ" msgid "Your projects" msgstr "Ваши проекты" -msgid "branch name" +msgid "assign yourself" msgstr "" +msgid "branch name" +msgstr "Ð¸Ð¼Ñ Ð²ÐµÑ‚Ð²Ð¸" + msgid "by" +msgstr "по" + +msgid "ciReport|Code quality" +msgstr "" + +msgid "ciReport|DAST detected no alerts by analyzing the review app" +msgstr "" + +msgid "ciReport|Failed to load ${type} report" +msgstr "" + +msgid "ciReport|Fixed:" +msgstr "" + +msgid "ciReport|Instances" +msgstr "" + +msgid "ciReport|Learn more about whitelisting" +msgstr "" + +msgid "ciReport|Loading ${type} report" +msgstr "" + +msgid "ciReport|No changes to code quality" +msgstr "" + +msgid "ciReport|No changes to performance metrics" +msgstr "" + +msgid "ciReport|Performance metrics" +msgstr "" + +msgid "ciReport|SAST" +msgstr "" + +msgid "ciReport|SAST detected no security vulnerabilities" +msgstr "" + +msgid "ciReport|SAST:container no vulnerabilities were found" +msgstr "" + +msgid "ciReport|Show complete code vulnerabilities report" +msgstr "" + +msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}" msgstr "" msgid "commit" +msgstr "коммит" + +msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue." +msgstr "" + +msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue." msgstr "" msgid "day" @@ -2998,12 +3772,153 @@ msgstr[0] "день" msgstr[1] "дней" msgstr[2] "дней" +msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command." +msgstr "" + +msgid "merge request" +msgid_plural "merge requests" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "mrWidget|Cancel automatic merge" +msgstr "" + +msgid "mrWidget|Check out branch" +msgstr "" + +msgid "mrWidget|Checking ability to merge automatically" +msgstr "" + +msgid "mrWidget|Cherry-pick" +msgstr "" + +msgid "mrWidget|Cherry-pick this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Closed" +msgstr "" + +msgid "mrWidget|Closed by" +msgstr "" + +msgid "mrWidget|Closes" +msgstr "" + +msgid "mrWidget|Did not close" +msgstr "" + +msgid "mrWidget|Email patches" +msgstr "" + +msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|Mentions" +msgstr "" + +msgid "mrWidget|Merge" +msgstr "" + +msgid "mrWidget|Merge failed." +msgstr "" + +msgid "mrWidget|Merge locally" +msgstr "" + +msgid "mrWidget|Merged by" +msgstr "" + +msgid "mrWidget|Plain diff" +msgstr "" + +msgid "mrWidget|Refresh" +msgstr "" + +msgid "mrWidget|Refresh now" +msgstr "" + +msgid "mrWidget|Refreshing now" +msgstr "" + +msgid "mrWidget|Remove Source Branch" +msgstr "" + +msgid "mrWidget|Remove source branch" +msgstr "" + +msgid "mrWidget|Request to merge" +msgstr "" + +msgid "mrWidget|Resolve conflicts" +msgstr "" + +msgid "mrWidget|Revert" +msgstr "" + +msgid "mrWidget|Revert this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Set by" +msgstr "" + +msgid "mrWidget|The changes were merged into" +msgstr "" + +msgid "mrWidget|The changes were not merged into" +msgstr "" + +msgid "mrWidget|The changes will be merged into" +msgstr "" + +msgid "mrWidget|The source branch has been removed" +msgstr "" + +msgid "mrWidget|The source branch is being removed" +msgstr "" + +msgid "mrWidget|The source branch will be removed" +msgstr "" + +msgid "mrWidget|The source branch will not be removed" +msgstr "" + +msgid "mrWidget|There are merge conflicts" +msgstr "" + +msgid "mrWidget|This merge request failed to be merged automatically" +msgstr "" + +msgid "mrWidget|This merge request is in the process of being merged" +msgstr "" + +msgid "mrWidget|This project is archived, write access has been disabled" +msgstr "" + +msgid "mrWidget|You can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|You can remove source branch now" +msgstr "" + +msgid "mrWidget|command line" +msgstr "" + +msgid "mrWidget|into" +msgstr "" + +msgid "mrWidget|to be merged automatically when the pipeline succeeds" +msgstr "" + msgid "new merge request" msgstr "новый Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние" msgid "notification emails" msgstr "email Ð´Ð»Ñ ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ð¹" +msgid "or" +msgstr "" + msgid "parent" msgid_plural "parents" msgstr[0] "иÑточник" @@ -3016,12 +3931,21 @@ msgstr "пароль" msgid "personal access token" msgstr "токен Ð´Ð»Ñ Ð¿ÐµÑ€Ñонального доÑтупа" +msgid "remove due date" +msgstr "" + msgid "source" +msgstr "иÑходный текÑÑ‚" + +msgid "spendCommand|%{slash_command} will update the sum of the time spent." msgstr "" msgid "to help your contributors communicate effectively!" -msgstr "" +msgstr "чтобы помочь учаÑтникам взаимодейÑтвовать Ñффективнее!" msgid "username" msgstr "Ð¸Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ" +msgid "uses Kubernetes clusters to deploy your code!" +msgstr "" + diff --git a/locale/uk/gitlab.po b/locale/uk/gitlab.po index fc62776a7a4..f775a511780 100644 --- a/locale/uk/gitlab.po +++ b/locale/uk/gitlab.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab-ee\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-12-12 18:31+0000\n" -"PO-Revision-Date: 2018-01-05 06:39-0500\n" +"POT-Creation-Date: 2018-02-07 11:38-0600\n" +"PO-Revision-Date: 2018-02-12 06:15-0500\n" "Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n" "Language-Team: Ukrainian\n" "Language: uk_UA\n" @@ -16,25 +16,46 @@ msgstr "" "X-Crowdin-Language: uk\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" +msgid " and" +msgstr " Ñ–" + msgid "%d commit" msgid_plural "%d commits" msgstr[0] "%d коміт" msgstr[1] "%d коміта" msgstr[2] "%d комітів" +msgid "%d commit behind" +msgid_plural "%d commits behind" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "%d issue" +msgid_plural "%d issues" +msgstr[0] "%d проблема" +msgstr[1] "%d проблеми" +msgstr[2] "%d проблем" + msgid "%d layer" msgid_plural "%d layers" msgstr[0] "%d шар" msgstr[1] "%d шари" msgstr[2] "%d шарів" +msgid "%d merge request" +msgid_plural "%d merge requests" +msgstr[0] "%d запит на злиттÑ" +msgstr[1] "%d запита на злиттÑ" +msgstr[2] "%d запитів на злиттÑ" + msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "%s additional commits have been omitted to prevent performance issues." msgstr[0] "%s доданий коміт був виключений Ð´Ð»Ñ Ð·Ð°Ð¿Ð¾Ð±Ñ–Ð³Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼ із швидкодією." msgstr[1] "%s доданих коміта були виключені Ð´Ð»Ñ Ð·Ð°Ð¿Ð¾Ð±Ñ–Ð³Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼ із швидкодією." msgstr[2] "%s доданих комітів були виключені Ð´Ð»Ñ Ð·Ð°Ð¿Ð¾Ð±Ñ–Ð³Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼ із швидкодією." -msgid "%{commit_author_link} committed %{commit_timeago}" +msgid "%{commit_author_link} authored %{commit_timeago}" msgstr "%{commit_author_link} закомітив %{commit_timeago}" msgid "%{count} participant" @@ -49,9 +70,6 @@ msgstr "на %{number_commits_behind} комітів позаду %{default_bran msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will allow access on the next attempt." msgstr "%{number_of_failures} від %{maximum_failures} невдач. GitLab надаÑÑ‚ÑŒ доÑтуп на наÑтупну Ñпробу." -msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will block access for %{number_of_seconds} seconds." -msgstr "%{number_of_failures} із %{maximum_failures} невдач. GitLab заблокує доÑтуп на %{number_of_seconds} Ñекунд." - msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved." msgstr "%{number_of_failures} від %{maximum_failures} невдач. GitLab автоматично не повторюватиме Ñпробу. Скиньте інформацію Ñховища при уÑуненні проблеми." @@ -113,13 +131,13 @@ msgid "Add" msgstr "Додати" msgid "Add Changelog" -msgstr "Додати ÑпиÑок змін (Changelog)" +msgstr "Додати ÑпиÑок змін" msgid "Add Contribution guide" -msgstr "" +msgstr "Додати керівництво Ð´Ð»Ñ ÑƒÑ‡Ð°Ñників" msgid "Add Group Webhooks and GitLab Enterprise Edition." -msgstr "" +msgstr "Додайте групові веб-гуки та GitLab Enterprise Edition." msgid "Add License" msgstr "Додати ліцензію" @@ -127,24 +145,81 @@ msgstr "Додати ліцензію" msgid "Add new directory" msgstr "Додати новий каталог" +msgid "Add todo" +msgstr "Додати задачу" + +msgid "AdminArea|Stop all jobs" +msgstr "Зупинити вÑÑ– завданнÑ" + +msgid "AdminArea|Stop all jobs?" +msgstr "Зупинити вÑÑ– завданнÑ?" + +msgid "AdminArea|Stop jobs" +msgstr "Зупинити завданнÑ" + +msgid "AdminArea|Stopping jobs failed" +msgstr "Зупинка завдань пройшла невдало" + +msgid "AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running." +msgstr "" + msgid "AdminHealthPageLink|health page" msgstr "Ñторінка ÑтатуÑу" +msgid "Advanced" +msgstr "Розширений" + msgid "Advanced settings" msgstr "Додаткові параметри" msgid "All" msgstr "Ð’ÑÑ–" +msgid "All changes are committed" +msgstr "" + +msgid "Allows you to add and manage Kubernetes clusters." +msgstr "" + +msgid "An error occurred previewing the blob" +msgstr "" + msgid "An error occurred when toggling the notification subscription" msgstr "Виникла помилка під Ñ‡Ð°Ñ Ð·Ð¼Ñ–Ð½Ð¸ підпиÑки на ÑповіщеннÑ" msgid "An error occurred when updating the issue weight" msgstr "Збій під Ñ‡Ð°Ñ Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð²Ð°Ð³Ð¸ проблеми" +msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." +msgstr "" + +msgid "An error occurred while fetching markdown preview" +msgstr "" + msgid "An error occurred while fetching sidebar data" msgstr "Виникла помилка під Ñ‡Ð°Ñ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ð´Ð°Ð½Ð¸Ñ… Ð´Ð»Ñ Ð±Ñ–Ñ‡Ð½Ð¾Ñ— панелі" +msgid "An error occurred while getting projects" +msgstr "" + +msgid "An error occurred while loading filenames" +msgstr "" + +msgid "An error occurred while rendering KaTeX" +msgstr "" + +msgid "An error occurred while rendering preview broadcast message" +msgstr "" + +msgid "An error occurred while retrieving calendar activity" +msgstr "" + +msgid "An error occurred while retrieving diff" +msgstr "" + +msgid "An error occurred while validating username" +msgstr "" + msgid "An error occurred. Please try again." msgstr "СталаÑÑŒ помилка. Спробуйте ще раз." @@ -152,7 +227,7 @@ msgid "Appearance" msgstr "Зовнішній виглÑд" msgid "Applications" -msgstr "Додатки" +msgstr "ЗаÑтоÑунки" msgid "Apr" msgstr "квіт." @@ -164,14 +239,11 @@ msgid "Archived project! Repository is read-only" msgstr "Заархівований проект! Репозиторій доÑтупний лише Ð´Ð»Ñ Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ" msgid "Are you sure you want to delete this pipeline schedule?" -msgstr "" +msgstr "Ви впевнені, що хочете видалити цей розклад Ð´Ð»Ñ ÐºÐ¾Ð½Ð²ÐµÑ”Ñ€Ð°?" msgid "Are you sure you want to discard your changes?" msgstr "Ви впевнені, що бажаєте ÑкаÑувати ваші зміни?" -msgid "Are you sure you want to leave this group?" -msgstr "Ви впевнені що хочете залишити цю групу?" - msgid "Are you sure you want to reset registration token?" msgstr "Ви впевнені, що бажаєте Ñкинути реєÑтраційний токен?" @@ -184,6 +256,21 @@ msgstr "Ви впевнені?" msgid "Artifacts" msgstr "Ðртефакти" +msgid "Assign custom color like #FF0000" +msgstr "Призначити влаÑний колір типу #FF0000" + +msgid "Assign labels" +msgstr "Призначити мітку" + +msgid "Assign milestone" +msgstr "Призначити етап" + +msgid "Assign to" +msgstr "Призначити" + +msgid "Assignee" +msgstr "Виконавець" + msgid "Attach a file by drag & drop or %{upload_link}" msgstr "Прикріпити файл за допомогою перетÑÐ³ÑƒÐ²Ð°Ð½Ð½Ñ Ð°Ð±Ð¾ %{upload_link}" @@ -199,15 +286,18 @@ msgstr "Журнал автентифікації" msgid "Author" msgstr "Ðвтор" -msgid "Auto Review Apps and Auto Deploy need a domain name and the %{kubernetes} to work correctly." -msgstr "Ð”Ð»Ñ ÐºÐ¾Ñ€ÐµÐºÑ‚Ð½Ð¾Ñ— роботи Auto Review Apps та Auto Deploy необхідно вказати доменне Ñ–Ð¼â€™Ñ Ñ‚Ð° %{kubernetes}." +msgid "Authors: %{authors}" +msgstr "Ðвтори: %{authors}" + +msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." +msgstr "" + +msgid "Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly." +msgstr "" msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly." msgstr "Ð”Ð»Ñ ÐºÐ¾Ñ€ÐµÐºÑ‚Ð½Ð¾Ñ— роботи Auto Review Apps та Auto Deploy необхідно вказати доменне ім’Ñ." -msgid "Auto Review Apps and Auto Deploy need the %{kubernetes} to work correctly." -msgstr "Ð”Ð»Ñ ÐºÐ¾Ñ€ÐµÐºÑ‚Ð½Ð¾Ñ— роботи Auto Review Apps та Auto Deploy необхіден %{kubernetes}." - msgid "AutoDevOps|Auto DevOps (Beta)" msgstr "Auto DevOps (бета)" @@ -218,7 +308,7 @@ msgid "AutoDevOps|Enable in settings" msgstr "Включити в налаштуваннÑÑ…" msgid "AutoDevOps|It will automatically build, test, and deploy your application based on a predefined CI/CD configuration." -msgstr "AutoDevOps буде автоматично збирати, теÑтувати та розгортати вашу програму на оÑнові визначеної CI/CD конфігурації." +msgstr "AutoDevOps буде автоматично збирати, теÑтувати та розгортати ваш заÑтоÑунок на оÑнові визначеної CI/CD конфігурації." msgid "AutoDevOps|Learn more in the %{link_to_documentation}" msgstr "ДізнайтеÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ в %{link_to_documentation}" @@ -229,6 +319,12 @@ msgstr "Ви можете активувати %{link_to_settings} Ð´Ð»Ñ Ñ†ÑŒÐ¾ msgid "Available" msgstr "ДоÑтупний" +msgid "Avatar will be removed. Are you sure?" +msgstr "Ðватар буде видалено. Ви впевнені?" + +msgid "Average per day: %{average}" +msgstr "Ð’ Ñередньому за день: %{average}" + msgid "Billing" msgstr "Білінг" @@ -281,6 +377,9 @@ msgid "BillingPlans|paid annually at %{price_per_year}" msgstr "ОплачуєтьÑÑ Ñ‰Ð¾Ñ€Ñ–Ñ‡Ð½Ð¾ %{price_per_year}" msgid "BillingPlans|per user" +msgstr "за кориÑтувача" + +msgid "Begin with the selected commit" msgstr "" msgid "Branch" @@ -305,7 +404,7 @@ msgid "BranchSwitcherPlaceholder|Search branches" msgstr "Пошук гілок" msgid "BranchSwitcherTitle|Switch branch" -msgstr "" +msgstr "Перейти в гілку" msgid "Branches" msgstr "Гілки" @@ -412,8 +511,8 @@ msgstr "від" msgid "CI / CD" msgstr "CI / CD" -msgid "CI configuration" -msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ CI" +msgid "CI/CD configuration" +msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ CI/CD" msgid "CICD|Jobs" msgstr "ЗавданнÑ" @@ -424,6 +523,9 @@ msgstr "СкаÑувати" msgid "Cancel edit" msgstr "Відмінити правку" +msgid "Cannot modify managed Kubernetes cluster" +msgstr "" + msgid "Change Weight" msgstr "Вага зміни" @@ -434,13 +536,19 @@ msgid "ChangeTypeActionLabel|Revert in branch" msgstr "Ðнулювати у гілці" msgid "ChangeTypeAction|Cherry-pick" -msgstr "" +msgstr "Вибрати (cherry-pick)" msgid "ChangeTypeAction|Revert" msgstr "Ðнулювати коміт" +msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes." +msgstr "" + msgid "Changelog" -msgstr "СпиÑок змін (Changelog)" +msgstr "СпиÑок змін" + +msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision." +msgstr "" msgid "Charts" msgstr "Графіки" @@ -448,6 +556,9 @@ msgstr "Графіки" msgid "Chat" msgstr "Чат" +msgid "Check interval" +msgstr "Інтервал перевірки" + msgid "Checking %{text} availability…" msgstr "Перевірка доÑтупноÑÑ‚Ñ– %{text}…" @@ -455,12 +566,24 @@ msgid "Checking branch availability..." msgstr "Перевірка доÑтупноÑÑ‚Ñ– гілки..." msgid "Cherry-pick this commit" -msgstr "" +msgstr "Вибрати (cherry-pick) цей коміт" msgid "Cherry-pick this merge request" +msgstr "Вибрати (cherry-pick) цей запит на злиттÑ" + +msgid "Choose File ..." +msgstr "Виберіть файл ..." + +msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request." +msgstr "" + +msgid "Choose file..." +msgstr "Виберіть файл..." + +msgid "Choose which groups you wish to synchronize to this secondary node." msgstr "" -msgid "Choose which groups you wish to replicate to this secondary node. Leave blank to replicate all." +msgid "Choose which shards you wish to synchronize to this secondary node." msgstr "" msgid "CiStatusLabel|canceled" @@ -517,41 +640,77 @@ msgstr "пропущено" msgid "CiStatus|running" msgstr "виконуєтьÑÑ" +msgid "CiVariables|Input variable key" +msgstr "Ключ вхідної змінної" + +msgid "CiVariables|Input variable value" +msgstr "Ð—Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð²Ñ…Ñ–Ð´Ð½Ð¾Ñ— змінної" + +msgid "CiVariables|Remove variable row" +msgstr "" + +msgid "CiVariable|* (All environments)" +msgstr "* (Ð’ÑÑ– Ñередовища)" + +msgid "CiVariable|All environments" +msgstr "Ð’ÑÑ– Ñередовища" + +msgid "CiVariable|Create wildcard" +msgstr "" + +msgid "CiVariable|Error occured while saving variables" +msgstr "" + +msgid "CiVariable|New environment" +msgstr "Ðове Ñередовище" + +msgid "CiVariable|Protected" +msgstr "Захищений" + +msgid "CiVariable|Search environments" +msgstr "" + +msgid "CiVariable|Toggle protected" +msgstr "" + +msgid "CiVariable|Validation failed" +msgstr "Перевірка невдала" + msgid "CircuitBreakerApiLink|circuitbreaker api" msgstr "circuitbreaker api" +msgid "Click to expand text" +msgstr "ÐатиÑніть, щоб розгорнути текÑÑ‚" + msgid "Clone repository" msgstr "Клонувати репозиторій" msgid "Close" msgstr "Закрити" -msgid "Cluster" -msgstr "КлаÑтер" - -msgid "ClusterIntegration|%{appList} was successfully installed on your cluster" -msgstr "%{appList} уÑпішно вÑтановлені на вашому клаÑтері" +msgid "Closed" +msgstr "Закрито" -msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which incur additional costs. See %{pricingLink}" -msgstr "%{boldNotice} Це додаÑÑ‚ÑŒ реÑурÑи (наприклад баланÑер навантаженнÑ), що Ñпричинить додаткові витрати. ПереглÑньте %{pricingLink}" +msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster" +msgstr "%{appList} були уÑпішно вÑтановлені на ваш Kubernetes-клаÑтер" msgid "ClusterIntegration|API URL" msgstr "API URL" -msgid "ClusterIntegration|Active" -msgstr "Ðктивний" - -msgid "ClusterIntegration|Add an existing cluster" -msgstr "Додати Ñ–Ñнуючий клаÑтер" +msgid "ClusterIntegration|Add Kubernetes cluster" +msgstr "Додати Kubernetes клаÑтер" -msgid "ClusterIntegration|Add cluster" -msgstr "Додати клаÑтер" +msgid "ClusterIntegration|Add an existing Kubernetes cluster" +msgstr "Додати Ñ–Ñнуючий Kubernetes-клаÑтер" -msgid "ClusterIntegration|All" -msgstr "Ð’ÑÑ–" +msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration" +msgstr "Детальні Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñ–Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ñ–Ñ— із цим Kubernetes-клаÑтером" msgid "ClusterIntegration|Applications" -msgstr "Додатки" +msgstr "ЗаÑтоÑунки" + +msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster." +msgstr "Ви впевнені, що хочете видалити інтеграцію із цим Kubernetes-клаÑтером? Це не призведе до Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ñамого клаÑтера." msgid "ClusterIntegration|CA Certificate" msgstr "Сертифікат центру Ñертифікації" @@ -559,38 +718,14 @@ msgstr "Сертифікат центру Ñертифікації" msgid "ClusterIntegration|Certificate Authority bundle (PEM format)" msgstr "Ðабір Ñертифікатів (формат PEM)" -msgid "ClusterIntegration|Choose how to set up cluster integration" -msgstr "Виберіть ÑпоÑіб Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñ–Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ñ–Ñ— з клаÑтером" - -msgid "ClusterIntegration|Cluster" -msgstr "КлаÑтер" - -msgid "ClusterIntegration|Cluster details" -msgstr "Параметри клаÑтера" - -msgid "ClusterIntegration|Cluster integration" -msgstr "Ð†Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ñ–Ñ Ñ–Ð· клаÑтером" - -msgid "ClusterIntegration|Cluster integration is disabled for this project." -msgstr "Ð†Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ñ–Ñ Ñ–Ð· клаÑтером вимкнена Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ проекту." +msgid "ClusterIntegration|Choose how to set up Kubernetes cluster integration" +msgstr "Виберіть ÑпоÑіб Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñ–Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ñ–Ñ— із Kubernetes-клаÑтером" -msgid "ClusterIntegration|Cluster integration is enabled for this project." -msgstr "Ð†Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ñ–Ñ Ñ–Ð· клаÑтером увімкнена Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ проекту." +msgid "ClusterIntegration|Choose which of your project's environments will use this Kubernetes cluster." +msgstr "Виберіть Ñкі з Ñередовищ вашого проекту викориÑтовуватимуть цей Kubernetes-клаÑтер." -msgid "ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab's connection to it." -msgstr "" - -msgid "ClusterIntegration|Cluster is being created on Google Kubernetes Engine..." -msgstr "СтворюєтьÑÑ ÐºÐ»Ð°Ñтер в Google Kubernetes Engine..." - -msgid "ClusterIntegration|Cluster name" -msgstr "Ім'Ñ ÐºÐ»Ð°Ñтера" - -msgid "ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine. Refresh the page to see cluster's details" -msgstr "КлаÑтер був уÑпішно Ñтворено в Google Kubernetes Engine. Оновіть Ñторінку, щоб переглÑнути додатову інформацію" - -msgid "ClusterIntegration|Clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" -msgstr "КлаÑтери дозволÑÑŽÑ‚ÑŒ вам викориÑтовувати Review Apps, розгортати ваші програми, запуÑкати ваші конвеєри Ñ– багато іншого проÑтим ÑпоÑобом. %{link_to_help_page}" +msgid "ClusterIntegration|Control how your Kubernetes cluster integrates with GitLab" +msgstr "Керуйте ÑпоÑобом інтеграції вашого Kubernetes-клаÑтера з GitLab" msgid "ClusterIntegration|Copy API URL" msgstr "Скопіювати URL API" @@ -598,38 +733,35 @@ msgstr "Скопіювати URL API" msgid "ClusterIntegration|Copy CA Certificate" msgstr "Скопіювати Ñертифікат центру Ñертифікації" +msgid "ClusterIntegration|Copy Kubernetes cluster name" +msgstr "Скопіювати Ñ–Ð¼â€™Ñ Kubernetes-клаÑтера" + msgid "ClusterIntegration|Copy Token" msgstr "Скопіювати Токен" -msgid "ClusterIntegration|Copy cluster name" -msgstr "Копіювати назву клаÑтера" +msgid "ClusterIntegration|Create Kubernetes cluster" +msgstr "Створити Kubernetes-клаÑтер" -msgid "ClusterIntegration|Create a new cluster on Google Engine right from GitLab" -msgstr "Створити новий клаÑтер у Google Engine прÑмо з GitLab" +msgid "ClusterIntegration|Create Kubernetes cluster on Google Kubernetes Engine" +msgstr "Створити Kubernetes-клаÑтер на Google Kubernetes Engine" -msgid "ClusterIntegration|Create cluster" -msgstr "Створити клаÑтер" - -msgid "ClusterIntegration|Create cluster on Google Kubernetes Engine" -msgstr "Створити клаÑтер в Google Kubernetes Engine" +msgid "ClusterIntegration|Create a new Kubernetes cluster on Google Kubernetes Engine right from GitLab" +msgstr "Створити Kubernetes-клаÑтер на Google Kubernetes Engine прÑмо із GitLab" msgid "ClusterIntegration|Create on GKE" msgstr "Створити в GKE" -msgid "ClusterIntegration|Enable cluster integration" -msgstr "Увімкнути інтеграцію із клаÑтерами" - msgid "ClusterIntegration|Enter the details for an existing Kubernetes cluster" msgstr "Вкажіть параметри Ñ–Ñнуючого клаÑтера Kubernetes" -msgid "ClusterIntegration|Enter the details for your cluster" -msgstr "Введіть докладний Ð¾Ð¿Ð¸Ñ Ð²Ð°ÑˆÐ¾Ð³Ð¾ клаÑтера" +msgid "ClusterIntegration|Enter the details for your Kubernetes cluster" +msgstr "Введіть параметри вашого Kubernetes-клаÑтера" -msgid "ClusterIntegration|Environment pattern" -msgstr "Шаблон Ñередовища" +msgid "ClusterIntegration|Environment scope" +msgstr "" -msgid "ClusterIntegration|GKE pricing" -msgstr "ВартіÑÑ‚ÑŒ GKE" +msgid "ClusterIntegration|GitLab Integration" +msgstr "Ð†Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ñ–Ñ Ð· GitLab" msgid "ClusterIntegration|GitLab Runner" msgstr "GitLab Runner" @@ -646,47 +778,83 @@ msgstr "Проект Google Kubernetes Engine" msgid "ClusterIntegration|Helm Tiller" msgstr "Helm Tiller" -msgid "ClusterIntegration|Inactive" -msgstr "Ðеактивні" - msgid "ClusterIntegration|Ingress" msgstr "Ingress" msgid "ClusterIntegration|Install" msgstr "Ð’Ñтановити" -msgid "ClusterIntegration|Install applications on your cluster. Read more about %{helpLink}" -msgstr "Ð’Ñтановіть додатки у ваш клаÑтер. Докладніше про %{helpLink}" - msgid "ClusterIntegration|Installed" msgstr "Ð’Ñтановлений" msgid "ClusterIntegration|Installing" msgstr "Ð’ÑтановленнÑ" -msgid "ClusterIntegration|Integrate cluster automation" -msgstr "Ð†Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ñ–Ñ ÐºÐ»Ð°Ñтерної автоматизації" +msgid "ClusterIntegration|Integrate Kubernetes cluster automation" +msgstr "" + +msgid "ClusterIntegration|Integration status" +msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ñ–Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ñ–Ñ—" + +msgid "ClusterIntegration|Kubernetes cluster" +msgstr "Kubernetes-клаÑтер" + +msgid "ClusterIntegration|Kubernetes cluster details" +msgstr "Параметри Kubernetes-клаÑтера" + +msgid "ClusterIntegration|Kubernetes cluster integration" +msgstr "Ð†Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ñ–Ñ Ñ–Ð· Kubernetes-клаÑтером" + +msgid "ClusterIntegration|Kubernetes cluster integration is disabled for this project." +msgstr "Ð†Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ñ–Ñ Ñ–Ð· Kubernetes-клаÑтером вимкнена Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ проекту." + +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project." +msgstr "Ð†Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ñ–Ñ Ñ–Ð· Kubernetes-клаÑтером увімкнена Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ проекту." + +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project. Disabling this integration will not affect your Kubernetes cluster, it will only temporarily turn off GitLab's connection to it." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster name" +msgstr "Ð†Ð¼â€™Ñ Kubernetes-клаÑтера" + +msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project" +msgstr "" msgid "ClusterIntegration|Learn more about %{link_to_documentation}" msgstr "ДізнайтеÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ про %{link_to_documentation}" -msgid "ClusterIntegration|Learn more about Clusters" -msgstr "ДізнайтеÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ про клаÑтери" +msgid "ClusterIntegration|Learn more about Kubernetes" +msgstr "ДізнайтеÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ про Kubernetes" + +msgid "ClusterIntegration|Learn more about environments" +msgstr "ДізнайтеÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ про Ñередовища" msgid "ClusterIntegration|Machine type" msgstr "Тип машини" -msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create clusters" -msgstr "ПереконайтеÑÑ, що ваш обліковий Ð·Ð°Ð¿Ð¸Ñ %{link_to_requirements} Ð´Ð»Ñ ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ ÐºÐ»Ð°Ñтерів" +msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters" +msgstr "" + +msgid "ClusterIntegration|Manage" +msgstr "УправліннÑ" -msgid "ClusterIntegration|Manage cluster integration on your GitLab project" -msgstr "Ð£Ð¿Ñ€Ð°Ð²Ð»Ñ–Ð½Ð½Ñ Ñ–Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ñ–Ñ”ÑŽ із клаÑтером у вашому Gitlab-проекті" +msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}" +msgstr "" -msgid "ClusterIntegration|Manage your cluster by visiting %{link_gke}" -msgstr "Ð”Ð»Ñ ÐºÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ñвоїм клаÑтером перейдіть на %{link_gke}" +msgid "ClusterIntegration|More information" +msgstr "Додаткова інформаціÑ" -msgid "ClusterIntegration|Multiple clusters are available in GitLab Entreprise Edition Premium and Ultimate" -msgstr "Кілька клаÑтерів доÑтупні в GitLab Enterprise Edition Premium Ñ– Ultimate" +msgid "ClusterIntegration|Multiple Kubernetes clusters are available in GitLab Enterprise Edition Premium and Ultimate" +msgstr "" msgid "ClusterIntegration|Note:" msgstr "Примітка:" @@ -694,38 +862,35 @@ msgstr "Примітка:" msgid "ClusterIntegration|Number of nodes" msgstr "КількіÑÑ‚ÑŒ вузлів" -msgid "ClusterIntegration|Please enter access information for your cluster. If you need help, you can read our %{link_to_help_page} on clusters" -msgstr "Введіть інформацію про доÑтуп до Ñвого клаÑтера. Якщо вам потрібна допомога, ви можете прочитати наші %{link_to_help_page} по клаÑтерам" - -msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:" +msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes" msgstr "" -msgid "ClusterIntegration|Problem setting up the cluster" -msgstr "Проблема Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ ÐºÐ»Ð°Ñтера" - -msgid "ClusterIntegration|Problem setting up the clusters list" -msgstr "Проблема Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ ÑпиÑку клаÑтерів" +msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:" +msgstr "Будь-лаÑка впевнітьÑÑ, що ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Google задовольнÑÑ” наÑтупним вимогам:" msgid "ClusterIntegration|Project ID" -msgstr "" +msgstr "Ідентифікатор проекту" msgid "ClusterIntegration|Project namespace" -msgstr "" +msgstr "ПроÑÑ‚Ñ–Ñ€ імен проекту" msgid "ClusterIntegration|Project namespace (optional, unique)" -msgstr "" +msgstr "ПроÑÑ‚Ñ–Ñ€ імен проекту (не обов’Ñзковий, унікальний)" -msgid "ClusterIntegration|Read our %{link_to_help_page} on cluster integration." -msgstr "Прочитайте нашу документацію %{link_to_help_page} по інтеграції із клаÑтером." +msgid "ClusterIntegration|Prometheus" +msgstr "Prometheus" -msgid "ClusterIntegration|Remove cluster integration" -msgstr "Видалити інтеграцію з клаÑтером" +msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration." +msgstr "" + +msgid "ClusterIntegration|Remove Kubernetes cluster integration" +msgstr "Відалити інтеграцію із Kubernetes-клаÑтером" msgid "ClusterIntegration|Remove integration" msgstr "Видалити інтеграцію" -msgid "ClusterIntegration|Removing cluster integration will remove the cluster configuration you have added to this project. It will not delete your cluster on Google Kubernetes Engine." -msgstr "Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ ÐºÐ»Ð°Ñтерної інтеграції призведе до Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ ÐºÐ¾Ð½Ñ„Ñ–Ð³ÑƒÑ€Ð°Ñ†Ñ–Ñ— клаÑтера, Ñку ви додали до цього проекту. Ð¦Ñ Ð´Ñ–Ñ Ð½Ðµ буде видалÑти ваш клаÑтер у Google Kubernetes Engine." +msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster." +msgstr "" msgid "ClusterIntegration|Request to begin installing failed" msgstr "Запит про початок вÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð½Ðµ виконано" @@ -733,8 +898,8 @@ msgstr "Запит про початок вÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð½Ðµ викоРmsgid "ClusterIntegration|Save changes" msgstr "Зберегти зміни" -msgid "ClusterIntegration|See and edit the details for your cluster" -msgstr "ПереглÑнути та редагувати параметри вашого клаÑтера" +msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster" +msgstr "" msgid "ClusterIntegration|See machine types" msgstr "ПереглÑнути типи машин" @@ -754,26 +919,26 @@ msgstr "Показати" msgid "ClusterIntegration|Something went wrong on our end." msgstr "ЩоÑÑŒ пішло не так з нашого боку." -msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine" -msgstr "ЩоÑÑŒ пішло не так під Ñ‡Ð°Ñ ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ ÐºÐ»Ð°Ñтера в Google Kubernetes Engine" +msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine" +msgstr "" msgid "ClusterIntegration|Something went wrong while installing %{title}" msgstr "Під Ñ‡Ð°Ñ Ð²ÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ %{title} ÑталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ°" -msgid "ClusterIntegration|There are no clusters to show" -msgstr "Ðемає клаÑтерів Ð´Ð»Ñ Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ" +msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below" +msgstr "" -msgid "ClusterIntegration|This account must have permissions to create a cluster in the %{link_to_container_project} specified below" -msgstr "Цей обліковий Ð·Ð°Ð¿Ð¸Ñ Ð¼Ð°Ñ” мати дозволи Ð´Ð»Ñ ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ ÐºÐ»Ð°Ñтера в %{link_to_container_project}, зазначеному нижче" +msgid "ClusterIntegration|Toggle Kubernetes Cluster" +msgstr "Увімкнути/вимкнути Kubernetes-клаÑтер" -msgid "ClusterIntegration|Toggle Cluster" -msgstr "Переключити КлаÑтер" +msgid "ClusterIntegration|Toggle Kubernetes cluster" +msgstr "Увімкнути/вимкнути Kubernetes-клаÑтер" msgid "ClusterIntegration|Token" msgstr "Токен" -msgid "ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." -msgstr "За допомогою підключеного до цього проекту клаÑтера, ви можете викориÑтовувати Review Apps, розгортати ваші проекти, запуÑкати конвеєри збірки та багато іншого." +msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." +msgstr "" msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}" msgstr "Ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ð¿Ð¾Ð²Ð¸Ð½ÐµÐ½ мати %{link_to_kubernetes_engine}" @@ -784,17 +949,17 @@ msgstr "Зона" msgid "ClusterIntegration|access to Google Kubernetes Engine" msgstr "доÑтуп до Google Kubernetes Engine" -msgid "ClusterIntegration|cluster" -msgstr "клаÑтер" +msgid "ClusterIntegration|check the pricing here" +msgstr "" msgid "ClusterIntegration|documentation" -msgstr "документаціÑ" +msgstr "документації" msgid "ClusterIntegration|help page" msgstr "Ñторінка допомоги" msgid "ClusterIntegration|installing applications" -msgstr "вÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð´Ð¾Ð´Ð°Ñ‚ÐºÑ–Ð²" +msgstr "вÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð·Ð°ÑтоÑунків" msgid "ClusterIntegration|meets the requirements" msgstr "задовольнÑÑ” вимогам" @@ -802,6 +967,9 @@ msgstr "задовольнÑÑ” вимогам" msgid "ClusterIntegration|properly configured" msgstr "правильно налаштований" +msgid "Collapse" +msgstr "Згорнути" + msgid "Comments" msgstr "Коментарі" @@ -820,6 +988,9 @@ msgstr "ТриваліÑÑ‚ÑŒ оÑтанніх 30 комітів у хвилинРmsgid "Commit message" msgstr "Коміт-повідомленнÑ" +msgid "Commit statistics for %{ref} %{start_time} - %{end_time}" +msgstr "" + msgid "CommitBoxTitle|Commit" msgstr "Коміт" @@ -832,15 +1003,57 @@ msgstr "Коміти" msgid "Commits feed" msgstr "Канал комітів" +msgid "Commits per day hour (UTC)" +msgstr "" + +msgid "Commits per day of month" +msgstr "" + +msgid "Commits per weekday" +msgstr "" + +msgid "Commits|An error occurred while fetching merge requests data." +msgstr "" + +msgid "Commits|Commit: %{commitText}" +msgstr "Коміт: %{commitText}" + msgid "Commits|History" msgstr "ІÑторіÑ" +msgid "Commits|No related merge requests found" +msgstr "" + msgid "Committed by" msgstr "Коміт від" msgid "Compare" msgstr "ПорівнÑти" +msgid "Compare Git revisions" +msgstr "" + +msgid "Compare Revisions" +msgstr "ПорівнÑÐ½Ð½Ñ Ñ€ÐµÐ´Ð°ÐºÑ†Ñ–Ð¹" + +msgid "CompareBranches|%{source_branch} and %{target_branch} are the same." +msgstr "" + +msgid "CompareBranches|Compare" +msgstr "ПорівнÑти" + +msgid "CompareBranches|Source" +msgstr "Джерело" + +msgid "CompareBranches|Target" +msgstr "" + +msgid "CompareBranches|There isn't anything to compare." +msgstr "" + +msgid "Confidentiality" +msgstr "КонфіденційніÑÑ‚ÑŒ" + msgid "Container Registry" msgstr "РеєÑÑ‚Ñ€ Контейнерів" @@ -878,7 +1091,7 @@ msgid "ContainerRegistry|Tag" msgstr "Тег" msgid "ContainerRegistry|Tag ID" -msgstr "" +msgstr "Ідентифікатор тегу" msgid "ContainerRegistry|Use different image names" msgstr "ВикориÑтовуйте різні імена образів" @@ -887,13 +1100,16 @@ msgid "ContainerRegistry|With the Docker Container Registry integrated into GitL msgstr "За допомогою вбудованого в GitLab реєÑтру Docker контейнерів кожен проект може мати влаÑне міÑце Ð´Ð»Ñ Ð·Ð±ÐµÑ€Ñ–Ð³Ð°Ð½Ð½Ñ Docker образів." msgid "Contribution guide" -msgstr "" +msgstr "ІнÑÑ‚Ñ€ÑƒÐºÑ†Ñ–Ñ Ð´Ð»Ñ ÑƒÑ‡Ð°Ñників" msgid "Contributors" msgstr "Контриб’ютори" +msgid "ContributorsPage|%{startDate} – %{endDate}" +msgstr "%{startDate} – %{endDate}" + msgid "ContributorsPage|Building repository graph." -msgstr "" +msgstr "Ð¡Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ñхеми репозиторію." msgid "ContributorsPage|Commits to %{branch_name}, excluding merge commits. Limited to 6,000 commits." msgstr "Коміти в %{branch_name}, за винÑтком комітів злиттÑ. Обмежено 6000 комітів." @@ -913,14 +1129,23 @@ msgstr "Скопіюйте відкритий SSH-ключ в буфер Ð¾Ð±Ð¼Ñ msgid "Copy URL to clipboard" msgstr "Скопіювати URL в буфер обміну" +msgid "Copy branch name to clipboard" +msgstr "Скопіювати назву гілки в буфер обміну" + msgid "Copy commit SHA to clipboard" msgstr "Скопіювати ідентифікатор в буфер обміну" +msgid "Copy reference to clipboard" +msgstr "" + +msgid "Create" +msgstr "Створити" + msgid "Create New Directory" msgstr "Створити новий каталог" msgid "Create a personal access token on your account to pull or push via %{protocol}." -msgstr "" +msgstr "Створіть токен доÑтупу Ð´Ð»Ñ Ð²Ð°ÑˆÐ¾Ð³Ð¾ аккаунта, щоб відправлÑти та отримувати через %{protocol}." msgid "Create directory" msgstr "Створити каталог" @@ -934,6 +1159,9 @@ msgstr "Створити епік" msgid "Create file" msgstr "Створити файл" +msgid "Create lists from labels. Issues with that label appear in that list." +msgstr "" + msgid "Create merge request" msgstr "Створити запит на злиттÑ" @@ -946,6 +1174,9 @@ msgstr "Створити новий каталог" msgid "Create new file" msgstr "Створити новий файл" +msgid "Create new label" +msgstr "Створити нову мітку" + msgid "Create new..." msgstr "Створити..." @@ -967,6 +1198,9 @@ msgstr "ЧаÑовий поÑÑ Cron" msgid "Cron syntax" msgstr "СинтакÑÐ¸Ñ Cron" +msgid "Current node" +msgstr "Поточний вузол" + msgid "Custom notification events" msgstr "КориÑтувацькі Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½ÑŒ про події" @@ -976,11 +1210,8 @@ msgstr "Спеціальні рівні Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ ÑпівпадРmsgid "Cycle Analytics" msgstr "Ðналіз циклу" -msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project." -msgstr "" - msgid "CycleAnalyticsStage|Code" -msgstr "" +msgstr "ÐапиÑÐ°Ð½Ð½Ñ ÐºÐ¾Ð´Ñƒ" msgid "CycleAnalyticsStage|Issue" msgstr "Проблема" @@ -989,7 +1220,7 @@ msgid "CycleAnalyticsStage|Plan" msgstr "ПлануваннÑ" msgid "CycleAnalyticsStage|Production" -msgstr "" +msgstr "Production" msgid "CycleAnalyticsStage|Review" msgstr "ЗатвердженнÑ" @@ -1025,23 +1256,32 @@ msgstr[1] "РозгортаннÑ" msgstr[2] "Розгортань" msgid "Deploy Keys" -msgstr "" +msgstr "Ключі Ð´Ð»Ñ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ" msgid "Description" msgstr "ОпиÑ" msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project." -msgstr "" +msgstr "Шаблони опиÑу дозволÑÑŽÑ‚ÑŒ визначити конкретні шаблони обговорень проблем та запитів на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð´Ð»Ñ Ð²Ð°ÑˆÐ¾Ð³Ð¾ проекту." msgid "Details" msgstr "Деталі" +msgid "Diffs|No file name available" +msgstr "" + msgid "Directory name" msgstr "Ім'Ñ ÐºÐ°Ñ‚Ð°Ð»Ð¾Ð³Ñƒ" +msgid "Disable" +msgstr "Вимкнути" + msgid "Discard changes" msgstr "СкаÑувати зміни" +msgid "Discover GitLab Geo." +msgstr "" + msgid "Dismiss Cycle Analytics introduction box" msgstr "Відмінити блок вÑтупу до Ðналитики Циклу" @@ -1073,20 +1313,29 @@ msgid "DownloadCommit|Email Patches" msgstr "Email-патчи" msgid "DownloadCommit|Plain Diff" -msgstr "" +msgstr "ПроÑте порівнÑÐ½Ð½Ñ (diff)" msgid "DownloadSource|Download" msgstr "Завантажити" +msgid "Due date" +msgstr "Запланована дата завершеннÑ" + msgid "Edit" msgstr "Редагувати" msgid "Edit Pipeline Schedule %{id}" msgstr "Редагувати Розклад Конвеєра %{id}" +msgid "Edit files in the editor and commit changes here" +msgstr "" + msgid "Emails" msgstr "ÐдреÑи електронної пошти" +msgid "Enable" +msgstr "Увімкнути" + msgid "Environments|An error occurred while fetching the environments." msgstr "Виникла помилка при завантаженні Ñередовищ." @@ -1105,9 +1354,6 @@ msgstr "Середовище" msgid "Environments|Environments" msgstr "Середовища" -msgid "Environments|Environments are places where code gets deployed, such as staging or production." -msgstr "" - msgid "Environments|Job" msgstr "ЗавданнÑ" @@ -1150,9 +1396,33 @@ msgstr "Епіки дозволÑÑŽÑ‚ÑŒ керувати вашим портфе msgid "Error creating epic" msgstr "Помилка при Ñтворенні епіку" +msgid "Error fetching contributors data." +msgstr "" + +msgid "Error fetching labels." +msgstr "Помилка Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ð¼Ñ–Ñ‚Ð¾Ðº." + +msgid "Error fetching network graph." +msgstr "" + +msgid "Error fetching refs" +msgstr "" + +msgid "Error fetching usage ping data." +msgstr "" + msgid "Error occurred when toggling the notification subscription" msgstr "СталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° під Ñ‡Ð°Ñ Ð¿Ñ–Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð½Ñ Ð¿Ñ–Ð´Ð¿Ð¸Ñки на ÑповіщеннÑ" +msgid "Error saving label update." +msgstr "" + +msgid "Error updating status for all todos." +msgstr "Помилка Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ ÑтатуÑу Ð´Ð»Ñ Ð²ÑÑ–Ñ… задач." + +msgid "Error updating todo status." +msgstr "Помилка при оновленні ÑтатуÑу задачі." + msgid "EventFilterBy|Filter by all" msgstr "Фільтрувати по вÑім" @@ -1166,7 +1436,7 @@ msgid "EventFilterBy|Filter by merge events" msgstr "Фільтрувати по запитам на злиттÑ" msgid "EventFilterBy|Filter by push events" -msgstr "" +msgstr "Фільтрувати за подіÑми Ð²Ñ–Ð´Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð½Ñ (push)" msgid "EventFilterBy|Filter by team" msgstr "Фільтрувати по команді" @@ -1180,6 +1450,9 @@ msgstr "Кожен міÑÑць (1-го чиÑла о 4:00 ранку)" msgid "Every week (Sundays at 4:00am)" msgstr "Ð©Ð¾Ñ‚Ð¸Ð¶Ð½Ñ (в неділю о 4:00 ранку)" +msgid "Expand" +msgstr "Розгорнути" + msgid "Explore projects" msgstr "ОглÑд проектів" @@ -1190,7 +1463,7 @@ msgid "Failed to change the owner" msgstr "Ðе вдалоÑÑ Ð·Ð¼Ñ–Ð½Ð¸Ñ‚Ð¸ влаÑника" msgid "Failed to remove the pipeline schedule" -msgstr "" +msgstr "Ðе вдалоÑÑ Ð²Ð¸Ð´Ð°Ð»Ð¸Ñ‚Ð¸ розклад конвеєра" msgid "Feb" msgstr "лют." @@ -1198,6 +1471,9 @@ msgstr "лют." msgid "February" msgstr "лютий" +msgid "Fields on this page are now uneditable, you can configure" +msgstr "" + msgid "File name" msgstr "Ім'Ñ Ñ„Ð°Ð¹Ð»Ñƒ" @@ -1217,7 +1493,7 @@ msgid "FirstPushedBy|First" msgstr "Перший" msgid "FirstPushedBy|pushed by" -msgstr "" +msgstr "відправлено" msgid "Fork" msgid_plural "Forks" @@ -1235,37 +1511,121 @@ msgid "Format" msgstr "Формат" msgid "From issue creation until deploy to production" -msgstr "" +msgstr "З моменту ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ð¸ до Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð½Ð° production" msgid "From merge request merge until deploy to production" -msgstr "" +msgstr "Від Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ð·Ð°Ð¿Ð¸Ñ‚Ñƒ на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð´Ð¾ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð½Ð° production" msgid "GPG Keys" msgstr "GPG ключі" +msgid "Generate a default set of labels" +msgstr "" + msgid "Geo Nodes" msgstr "Гео-Вузли" -msgid "GeoNodeSyncStatus|Failed" -msgstr "Ðевдало" - msgid "GeoNodeSyncStatus|Node is failing or broken." msgstr "Вузол не працює або зламаний." msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage." msgstr "Вузол працює повільно, перевантажений або тільки що відновивÑÑ Ð¿Ñ–ÑÐ»Ñ Ð·Ð±Ð¾ÑŽ." -msgid "GeoNodeSyncStatus|Out of sync" -msgstr "ÐеÑинхронізовано" +msgid "GeoNodes|Database replication lag:" +msgstr "" + +msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?" +msgstr "" + +msgid "GeoNodes|Does not match the primary storage configuration" +msgstr "" + +msgid "GeoNodes|Failed" +msgstr "Ðевдало" + +msgid "GeoNodes|Full" +msgstr "Повний" + +msgid "GeoNodes|GitLab version does not match the primary node version" +msgstr "" + +msgid "GeoNodes|GitLab version:" +msgstr "" + +msgid "GeoNodes|Health status:" +msgstr "" + +msgid "GeoNodes|Last event ID processed by cursor:" +msgstr "" + +msgid "GeoNodes|Last event ID seen from primary:" +msgstr "" + +msgid "GeoNodes|Loading nodes" +msgstr "" + +msgid "GeoNodes|Local Attachments:" +msgstr "" + +msgid "GeoNodes|Local LFS objects:" +msgstr "" -msgid "GeoNodeSyncStatus|Synced" -msgstr "Синхронізовано" +msgid "GeoNodes|Local job artifacts:" +msgstr "" + +msgid "GeoNodes|New node" +msgstr "" + +msgid "GeoNodes|Out of sync" +msgstr "" + +msgid "GeoNodes|Replication slot WAL:" +msgstr "" + +msgid "GeoNodes|Replication slots:" +msgstr "" + +msgid "GeoNodes|Repositories:" +msgstr "" + +msgid "GeoNodes|Selective" +msgstr "" + +msgid "GeoNodes|Storage config:" +msgstr "" + +msgid "GeoNodes|Sync settings:" +msgstr "" + +msgid "GeoNodes|Synced" +msgstr "" + +msgid "GeoNodes|Unused slots" +msgstr "" + +msgid "GeoNodes|Used slots" +msgstr "" + +msgid "GeoNodes|Wikis:" +msgstr "" + +msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS." +msgstr "" + +msgid "Geo|All projects" +msgstr "" msgid "Geo|File sync capacity" msgstr "ПропуÑкна здатніÑÑ‚ÑŒ Ñинхронізації файлів" -msgid "Geo|Groups to replicate" -msgstr "Групи Ð´Ð»Ñ Ñ€ÐµÐ¿Ð»Ñ–ÐºÐ°Ñ†Ñ–Ñ—" +msgid "Geo|Groups to synchronize" +msgstr "" + +msgid "Geo|Projects in certain groups" +msgstr "" + +msgid "Geo|Projects in certain storage shards" +msgstr "" msgid "Geo|Repository sync capacity" msgstr "ПропуÑкна здатніÑÑ‚ÑŒ Ñинхронізації репозиторіїв" @@ -1273,12 +1633,24 @@ msgstr "ПропуÑкна здатніÑÑ‚ÑŒ Ñинхронізації репРmsgid "Geo|Select groups to replicate." msgstr "Виберіть групи Ð´Ð»Ñ Ñ€ÐµÐ¿Ð»Ñ–ÐºÐ°Ñ†Ñ–Ñ—." +msgid "Geo|Shards to synchronize" +msgstr "" + +msgid "Git revision" +msgstr "" + msgid "Git storage health information has been reset" msgstr "Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ ÑÑ‚Ð°Ñ‚ÑƒÑ Ð·Ð±ÐµÑ€Ñ–Ð³Ð°Ð½Ð½Ñ Git була Ñкинута" +msgid "Git version" +msgstr "" + msgid "GitLab Runner section" msgstr "Розділ GitLab Runner" +msgid "Gitaly Servers" +msgstr "" + msgid "Go to your fork" msgstr "Перейти до вашого форку" @@ -1288,6 +1660,9 @@ msgstr "Форк" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "ÐÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ Google не %{link_to_documentation}. ПопроÑÑ–Ñ‚ÑŒ Ñвого адмініÑтратора GitLab, Ñкщо ви хочете ÑкориÑтатиÑÑ Ñ†Ð¸Ð¼ ÑервіÑом." +msgid "Got it!" +msgstr "" + msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" msgstr "Заборонити Ñпільний доÑтуп до проекту в рамках %{group} з іншими групами" @@ -1324,8 +1699,8 @@ msgstr "Групи не знайдені" msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group." msgstr "Ви можете керувати правами доÑтупу членів групи мати доÑтуп до кожного проекту в ній." -msgid "GroupsTree|Are you sure you want to leave the \"${this.group.fullName}\" group?" -msgstr "Ви впевнені, що хочете залишити групу \"${this.group.fullName}\"?" +msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?" +msgstr "" msgid "GroupsTree|Create a project in this group." msgstr "Створити проект у групі." @@ -1358,22 +1733,28 @@ msgid "Have your users email" msgstr "Електронна пошта Ð´Ð»Ñ Ð·Ð²ÐµÑ€Ñ‚Ð°Ð½ÑŒ кориÑтувачів" msgid "Health Check" -msgstr "Перевірки працездатноÑÑ‚Ñ–" +msgstr "Перевірка ПрацездатноÑÑ‚Ñ–" msgid "Health information can be retrieved from the following endpoints. More information is available" msgstr "Інформацію про працездатніÑÑ‚ÑŒ можна отримати з наÑтупних ендпойнтів. Більше інформації доÑтупно" msgid "HealthCheck|Access token is" -msgstr "Токен доÑтупу Ñ”" +msgstr "Токен доÑтупу" msgid "HealthCheck|Healthy" msgstr "Здоровий" msgid "HealthCheck|No Health Problems Detected" -msgstr "Жодних проблем із здоров'Ñм не виÑвлено" +msgstr "Проблем із здоров'Ñм не виÑвлено" msgid "HealthCheck|Unhealthy" -msgstr "Ðездорові" +msgstr "Ðездоровий" + +msgid "Hide value" +msgid_plural "Hide values" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" msgid "History" msgstr "ІÑторіÑ" @@ -1382,10 +1763,10 @@ msgid "Housekeeping successfully started" msgstr "ÐžÑ‡Ð¸Ñ‰ÐµÐ½Ð½Ñ ÑƒÑпішно розпочато" msgid "Import repository" -msgstr "" +msgstr "Імпорт репозиторію" msgid "Improve Issue boards with GitLab Enterprise Edition." -msgstr "" +msgstr "Покращити дошки обговорень проблем за допомогою верÑÑ–Ñ— GitLab Enterprise Edition." msgid "Improve issues management with Issue weight and GitLab Enterprise Edition." msgstr "Покращити ÑƒÐ¿Ñ€Ð°Ð²Ð»Ñ–Ð½Ð½Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ð°Ð¼Ð¸ з можливіÑÑ‚ÑŽ Ð²Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð²Ð°Ð³Ð¸ проблеми за допомогою GitLab Enterprise Edition." @@ -1402,6 +1783,12 @@ msgstr[0] "ІнÑтанÑ" msgstr[1] "IнÑтанÑи" msgstr[2] "ІнÑтанÑів" +msgid "Instance does not support multiple Kubernetes clusters" +msgstr "" + +msgid "Interested parties can even contribute by pushing commits if they want to." +msgstr "" + msgid "Internal - The group and any internal projects can be viewed by any logged in user." msgstr "Ð’Ð½ÑƒÑ‚Ñ€Ñ–ÑˆÐ½Ñ â€” будь-Ñкий автентифікований кориÑтувач має доÑтуп до цієї групи та уÑÑ–Ñ… Ñ—Ñ— внутрішніх проектів." @@ -1429,6 +1816,9 @@ msgstr "Дошки" msgid "Issues" msgstr "Проблеми" +msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable." +msgstr "" + msgid "Jan" msgstr "Ñіч." @@ -1447,6 +1837,27 @@ msgstr "чер." msgid "June" msgstr "червень" +msgid "Kubernetes" +msgstr "Kubernetes" + +msgid "Kubernetes Cluster" +msgstr "КлаÑтер Kubernetes" + +msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}" +msgstr "" + +msgid "Kubernetes cluster integration was not removed." +msgstr "" + +msgid "Kubernetes cluster integration was successfully removed." +msgstr "" + +msgid "Kubernetes cluster was successfully updated." +msgstr "" + +msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page" +msgstr "" + msgid "LFSStatus|Disabled" msgstr "Вимкнено" @@ -1456,6 +1867,9 @@ msgstr "Увімкнено" msgid "Labels" msgstr "Мітки" +msgid "Labels can be applied to issues and merge requests to categorize them." +msgstr "" + msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "ОÑтанній %d день" @@ -1481,11 +1895,14 @@ msgid "Last updated" msgstr "ВоÑтаннє оновленно" msgid "LastPushEvent|You pushed to" -msgstr "" +msgstr "Ви відправили зміни до" msgid "LastPushEvent|at" msgstr "в" +msgid "Learn more" +msgstr "ДізнатиÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ" + msgid "Learn more in the" msgstr "ДізнайтеÑÑŒ більше" @@ -1504,15 +1921,18 @@ msgstr "Залишити проект" msgid "License" msgstr "ЛіцензіÑ" -msgid "Limited to showing %d event at most" -msgid_plural "Limited to showing %d events at most" -msgstr[0] "ÐžÐ±Ð¼ÐµÐ¶ÐµÐ½Ð½Ñ %d події" -msgstr[1] "ÐžÐ±Ð¼ÐµÐ¶ÐµÐ½Ð½Ñ %d подій" -msgstr[2] "ÐžÐ±Ð¼ÐµÐ¶ÐµÐ½Ð½Ñ %d подій" +msgid "Loading the GitLab IDE..." +msgstr "Ð—Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ IDE GitLab..." msgid "Lock" msgstr "БлокуваннÑ" +msgid "Lock %{issuableDisplayName}" +msgstr "Заблокувати %{issuableDisplayName}" + +msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment." +msgstr "" + msgid "Locked" msgstr "Заблоковано" @@ -1522,12 +1942,21 @@ msgstr "Заблоковані файли" msgid "Login" msgstr "Вхід" +msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos." +msgstr "" + +msgid "Manage labels" +msgstr "Керувати мітками" + msgid "Mar" msgstr "бер." msgid "March" msgstr "березень" +msgid "Mark done" +msgstr "" + msgid "Maximum git storage failures" msgstr "МакÑимальна кількіÑÑ‚ÑŒ невдач в Ñховищі даних git" @@ -1540,18 +1969,42 @@ msgstr "Медіана" msgid "Members" msgstr "КориÑтувачі" +msgid "Merge Request" +msgstr "Запит на злиттÑ" + msgid "Merge Requests" msgstr "Запити на злиттÑ" msgid "Merge events" -msgstr "" +msgstr "Події злиттÑ" msgid "Merge request" msgstr "Запит на злиттÑ" +msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others" +msgstr "" + +msgid "Merged" +msgstr "" + msgid "Messages" msgstr "ПовідомленнÑ" +msgid "Milestone" +msgstr "Етап" + +msgid "Milestones|Delete milestone" +msgstr "Видалити етап" + +msgid "Milestones|Delete milestone %{milestoneTitle}?" +msgstr "Видалити етап %{milestoneTitle}?" + +msgid "Milestones|Failed to delete milestone %{milestoneTitle}" +msgstr "Ðе вдалоÑÑ Ð²Ð¸Ð´Ð°Ð»Ð¸Ñ‚Ð¸ етап %{milestoneTitle}" + +msgid "Milestones|Milestone %{milestoneTitle} was not found" +msgstr "Етап %{milestoneTitle} не знайдено" + msgid "MissingSSHKeyWarningLink|add an SSH key" msgstr "не додаÑте SSH ключ" @@ -1561,11 +2014,17 @@ msgstr "Моніторинг" msgid "More information is available|here" msgstr "тут" +msgid "Move" +msgstr "ПереміÑтити" + +msgid "Move issue" +msgstr "ПереміÑтити проблему" + msgid "Multiple issue boards" msgstr "Кілька дошок обговореннÑ" -msgid "New Cluster" -msgstr "Ðовий клаÑтер" +msgid "Name new label" +msgstr "" msgid "New Issue" msgid_plural "New Issues" @@ -1573,6 +2032,12 @@ msgstr[0] "Ðова проблема" msgstr[1] "Ðові проблеми" msgstr[2] "Ðових проблем" +msgid "New Kubernetes Cluster" +msgstr "" + +msgid "New Kubernetes cluster" +msgstr "" + msgid "New Pipeline Schedule" msgstr "Ðовий розклад Конвеєра" @@ -1597,6 +2062,9 @@ msgstr "Ðова група" msgid "New issue" msgstr "Ðова проблема" +msgid "New label" +msgstr "Ðова мітка" + msgid "New merge request" msgstr "Ðовий запит на злиттÑ" @@ -1607,7 +2075,7 @@ msgid "New schedule" msgstr "Ðовий Розклад" msgid "New snippet" -msgstr "" +msgstr "Ðовий Ñніпет" msgid "New subgroup" msgstr "Ðова підгрупа" @@ -1615,12 +2083,27 @@ msgstr "Ðова підгрупа" msgid "New tag" msgstr "Ðовий тег" -msgid "No container images stored for this project. Add one by following the instructions above." -msgstr "Ð’ цьому проекті немає жодного образа контейнера. Додайте його за інÑтрукціÑми вище." +msgid "No assignee" +msgstr "Ðемає виконавцÑ" -msgid "No repository" +msgid "No changes" +msgstr "Ðемає змін" + +msgid "No connection could be made to a Gitaly Server, please check your logs!" msgstr "" +msgid "No due date" +msgstr "Ðемає запланованої дати завершеннÑ" + +msgid "No estimate or time spent" +msgstr "" + +msgid "No file chosen" +msgstr "Файл не вибрано" + +msgid "No repository" +msgstr "Ðемає репозиторію" + msgid "No schedules" msgstr "немає Розкладів" @@ -1630,9 +2113,15 @@ msgstr "Ðемає витраченого чаÑу" msgid "None" msgstr "Жоден" +msgid "Not allowed to merge" +msgstr "" + msgid "Not available" msgstr "ÐедоÑтупний" +msgid "Not confidential" +msgstr "Ðе конфіденційно" + msgid "Not enough data" msgstr "ÐедоÑтатньо даних" @@ -1643,13 +2132,13 @@ msgid "NotificationEvent|Close issue" msgstr "Проблема закрита" msgid "NotificationEvent|Close merge request" -msgstr "" +msgstr "Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð·Ð°ÐºÑ€Ð¸Ñ‚Ð¸Ð¹" msgid "NotificationEvent|Failed pipeline" msgstr "Ðевдача в конвеєрі" msgid "NotificationEvent|Merge merge request" -msgstr "" +msgstr "Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð¾" msgid "NotificationEvent|New issue" msgstr "Ðова проблема" @@ -1664,7 +2153,7 @@ msgid "NotificationEvent|Reassign issue" msgstr "Перепризначити проблему" msgid "NotificationEvent|Reassign merge request" -msgstr "" +msgstr "Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¿ÐµÑ€ÐµÐ¿Ñ€Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¾" msgid "NotificationEvent|Reopen issue" msgstr "Повторне Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ñ‚Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ñƒ" @@ -1693,6 +2182,12 @@ msgstr "ВідÑтежувати" msgid "Notifications" msgstr "СповіщеннÑ" +msgid "Notifications off" +msgstr "Ð¡Ð¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ Ð²Ð¸Ð¼ÐºÐ½ÐµÐ½Ð¾" + +msgid "Notifications on" +msgstr "Ð¡Ð¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ ÑƒÐ²Ñ–Ð¼ÐºÐ½ÐµÐ½Ð¾" + msgid "Nov" msgstr "лиÑÑ‚." @@ -1702,8 +2197,8 @@ msgstr "лиÑтопад" msgid "Number of access attempts" msgstr "КількіÑÑ‚ÑŒ Ñпроб доÑтупу" -msgid "Number of failures before backing off" -msgstr "КількіÑÑ‚ÑŒ помилок до призупиненнÑ" +msgid "OK" +msgstr "OK" msgid "Oct" msgstr "жовт." @@ -1717,6 +2212,9 @@ msgstr "Фільтр" msgid "Only project members can comment." msgstr "Тільки учаÑники проекту можуть залишати коментарі." +msgid "Open" +msgstr "Відкрити" + msgid "Opened" msgstr "Відкрито" @@ -1750,9 +2248,6 @@ msgstr "« Перша" msgid "Password" msgstr "Пароль" -msgid "People without permission will never get a notification and won\\'t be able to comment." -msgstr "Люди без дозволу ніколи не отримуватимуть Ñповіщень Ñ– не зможуть коментувати." - msgid "Pipeline" msgstr "Конвеєр" @@ -1769,7 +2264,7 @@ msgid "Pipeline quota" msgstr "Квота на конвеєри" msgid "PipelineCharts|Failed:" -msgstr "Ðе вдалоÑÑ:" +msgstr "Ðевдалі:" msgid "PipelineCharts|Overall statistics" msgstr "Загальна ÑтатиÑтика" @@ -1795,12 +2290,6 @@ msgstr "Ð’ÑÑ–" msgid "PipelineSchedules|Inactive" msgstr "Ðеактивні" -msgid "PipelineSchedules|Input variable key" -msgstr "Введіть ім'Ñ Ð·Ð¼Ñ–Ð½Ð½Ð¾Ñ—" - -msgid "PipelineSchedules|Input variable value" -msgstr "" - msgid "PipelineSchedules|Next Run" msgstr "ÐаÑтупний запуÑк" @@ -1808,10 +2297,7 @@ msgid "PipelineSchedules|None" msgstr "Ðемає" msgid "PipelineSchedules|Provide a short description for this pipeline" -msgstr "" - -msgid "PipelineSchedules|Remove variable row" -msgstr "Видалити змінні" +msgstr "Задайте короткий Ð¾Ð¿Ð¸Ñ Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ конвеєру" msgid "PipelineSchedules|Take ownership" msgstr "Стати влаÑником" @@ -1823,7 +2309,7 @@ msgid "PipelineSchedules|Variables" msgstr "Змінні" msgid "PipelineSheduleIntervalPattern|Custom" -msgstr "" +msgstr "Спеціальні" msgid "Pipelines" msgstr "Конвеєри" @@ -1840,6 +2326,12 @@ msgstr "Конвеєри за оÑтанній тиждень" msgid "Pipelines for last year" msgstr "Конвеєри за оÑтанній рік" +msgid "Pipelines|Build with confidence" +msgstr "" + +msgid "Pipelines|Get started with Pipelines" +msgstr "" + msgid "Pipeline|all" msgstr "вÑÑ–" @@ -1852,15 +2344,24 @@ msgstr "зі Ñтадією" msgid "Pipeline|with stages" msgstr "зі ÑтадіÑми" +msgid "Play" +msgstr "Відтворити" + +msgid "Please <a href=%{link_to_billing} target=\"_blank\" rel=\"noopener noreferrer\">enable billing for one of your projects to be able to create a Kubernetes cluster</a>, then try again." +msgstr "" + msgid "Please solve the reCAPTCHA" msgstr "Будь лаÑка, пройдіть reCAPTCHA" msgid "Preferences" msgstr "ÐалаштуваннÑ" -msgid "Private - Project access must be granted explicitly to each user." +msgid "Primary" msgstr "" +msgid "Private - Project access must be granted explicitly to each user." +msgstr "Приватний — доÑтуп до проекту повинен надаватиÑÑ ÐºÐ¾Ð¶Ð½Ð¾Ð¼Ñƒ кориÑтувачеві." + msgid "Private - The group and its projects can only be viewed by members." msgstr "Приватна — цю групу та Ñ—Ñ— проекти можуть бачити тільки Ñ—Ñ— кориÑтувачі." @@ -1903,6 +2404,9 @@ msgstr "Ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ñ” влаÑником в цих Ð³Ñ msgid "Profiles|your account" msgstr "ваш обліковий запиÑ" +msgid "Programming languages used in this repository" +msgstr "" + msgid "Project '%{project_name}' is in the process of being deleted." msgstr "Проект '%{project_name}' перебуває в процеÑÑ– видаленнÑ." @@ -1916,8 +2420,17 @@ msgid "Project '%{project_name}' was successfully updated." msgstr "Проект '%{project_name}' уÑпішно оновлено." msgid "Project access must be granted explicitly to each user." +msgstr "ДоÑтуп до проекту повинен надаватиÑÑ ÐºÐ¾Ð¶Ð½Ð¾Ð¼Ñƒ кориÑтувачеві." + +msgid "Project avatar" +msgstr "Ðватар проекту" + +msgid "Project avatar in repository: %{link}" msgstr "" +msgid "Project cache successfully reset." +msgstr "Кеш проекту уÑпішно Ñкинуто." + msgid "Project details" msgstr "Деталі проекту" @@ -1936,11 +2449,26 @@ msgstr "Розпочато екÑпорт проекту. ПоÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð´Ð msgid "ProjectActivityRSS|Subscribe" msgstr "ПідпиÑатиÑÑ" +msgid "ProjectCreationLevel|Allowed to create projects" +msgstr "" + +msgid "ProjectCreationLevel|Default project creation protection" +msgstr "" + +msgid "ProjectCreationLevel|Developers + Masters" +msgstr "" + +msgid "ProjectCreationLevel|Masters" +msgstr "Керівники" + +msgid "ProjectCreationLevel|No one" +msgstr "Ðіхто" + msgid "ProjectFeature|Disabled" msgstr "Вимкнено" msgid "ProjectFeature|Everyone with access" -msgstr "" +msgstr "Ð’ÑÑ– із доÑтупом" msgid "ProjectFeature|Only team members" msgstr "Тільки члени команди" @@ -1960,15 +2488,9 @@ msgstr "ІÑторіÑ" msgid "ProjectSettings|Contact an admin to change this setting." msgstr "ЗвернітьÑÑ Ð´Ð¾ адмініÑтратора, щоб змінити це налаштуваннÑ." -msgid "ProjectSettings|Immediately run a pipeline on the default branch" -msgstr "Зразу запуÑкати конвеєр у гілкці за замовчуваннÑм" - msgid "ProjectSettings|Only signed commits can be pushed to this repository." msgstr "Тільки підпиÑані коміти можуть бути надіÑлані в цей репозиторій." -msgid "ProjectSettings|Problem setting up the CI/CD settings JavaScript" -msgstr "Проблема Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð°Ñ€Ð°Ð¼ÐµÑ‚Ñ€Ñ–Ð² CI / CD JavaScript" - msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin." msgstr "Цей параметр заÑтоÑовуєтьÑÑ Ð½Ð° рівні Ñервера та може бути перевизначений адмініÑтратором." @@ -1979,7 +2501,7 @@ msgid "ProjectSettings|This setting will be applied to all projects unless overr msgstr "Цей параметр буде заÑтоÑовано до вÑÑ–Ñ… проектів, Ñкщо адмініÑтратор не змінить його." msgid "ProjectSettings|Users can only push commits to this repository that were committed with one of their own verified emails." -msgstr "" +msgstr "КориÑтувачі можуть відправлÑти в цей репозиторій лише Ñ‚Ñ– коміти, Ñкі міÑÑ‚ÑÑ‚ÑŒ одну із їхніх підтверджених Ð°Ð´Ñ€ÐµÑ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð¾Ñ— пошти." msgid "Projects" msgstr "Проекти" @@ -2032,12 +2554,15 @@ msgstr "Жодні метрики не відÑлідковуютьÑÑ. Ð”Ð»Ñ msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/" msgstr "Базова адреÑа Prometheus API, наприклад http://prometheus.example.com/" -msgid "PrometheusService|Prometheus monitoring" -msgstr "Моніторинг Prometheus" +msgid "PrometheusService|Time-series monitoring service" +msgstr "" msgid "PrometheusService|View environments" msgstr "ПереглÑд Ñередовищ" +msgid "Protip:" +msgstr "Підказка:" + msgid "Public - The group and any public projects can be viewed without any authentication." msgstr "Публічна — група та вÑÑ– публічні проекти можуть переглÑдатиÑÑ Ð±ÐµÐ· автентифікації." @@ -2045,14 +2570,17 @@ msgid "Public - The project can be accessed without any authentication." msgstr "Публічний — проект может переглÑдатиÑÑ Ð±ÐµÐ· автентифікації." msgid "Push Rules" -msgstr "" +msgstr "Правила відправленнÑ" msgid "Push events" -msgstr "" +msgstr "Події Ð²Ñ–Ð´Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð½Ñ (push)" msgid "PushRule|Committer restriction" msgstr "ÐžÐ±Ð¼ÐµÐ¶ÐµÐ½Ð½Ñ Ð´Ð»Ñ ÐºÐ¾Ð¼Ñ–Ñ‚Ñ‚ÐµÑ€Ð°" +msgid "Quick actions can be used in the issues description and comment boxes." +msgstr "" + msgid "Read more" msgstr "Докладніше" @@ -2065,6 +2593,12 @@ msgstr "Гілки" msgid "RefSwitcher|Tags" msgstr "Теги" +msgid "Reference:" +msgstr "" + +msgid "Register / Sign In" +msgstr "ЗареєÑтруватиÑÑ / Увійти" + msgid "Registry" msgstr "РеєÑÑ‚Ñ€" @@ -2084,14 +2618,23 @@ msgid "Related Merge Requests" msgstr "Пов'Ñзані запити на злиттÑ" msgid "Related Merged Requests" -msgstr "" +msgstr "Пов'Ñзані виконані запити" msgid "Remind later" msgstr "Ðагадати пізніше" +msgid "Remove" +msgstr "Видалити" + +msgid "Remove avatar" +msgstr "Видалити аватар" + msgid "Remove project" msgstr "Видалити проект" +msgid "Repair authentication" +msgstr "" + msgid "Repository" msgstr "Репозиторій" @@ -2107,6 +2650,12 @@ msgstr "Оновити токен доÑтупу Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€ÐºÐ¸ прРmsgid "Reset runners registration token" msgstr "Скинути реєÑтраційний токен runner-ів" +msgid "Reveal value" +msgid_plural "Reveal values" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + msgid "Revert this commit" msgstr "Ðнулювати цей коміт" @@ -2116,13 +2665,13 @@ msgstr "Ðнулювати цей запит на злиттÑ" msgid "SSH Keys" msgstr "Ключі SSH" -msgid "Save" -msgstr "Зберегти" - msgid "Save changes" msgstr "Зберегти зміни" msgid "Save pipeline schedule" +msgstr "Зберегти розклад конвеєра" + +msgid "Save variables" msgstr "" msgid "Schedule a new pipeline" @@ -2140,44 +2689,65 @@ msgstr "Тематичні дошки проблем" msgid "Search branches and tags" msgstr "Пошук гілок та тегів" +msgid "Search milestones" +msgstr "Пошук етапів" + +msgid "Search project" +msgstr "" + +msgid "Search users" +msgstr "Пошук кориÑтувачів" + msgid "Seconds before reseting failure information" msgstr "КількіÑÑ‚ÑŒ Ñекунд до ÑÐºÐ¸Ð´Ð°Ð½Ð½Ñ Ñ–Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ— про збої" -msgid "Seconds to wait after a storage failure" -msgstr "Скільки Ñекунд очікувати піÑÐ»Ñ Ð·Ð±Ð¾ÑŽ в Ñховищі даних" - msgid "Seconds to wait for a storage access attempt" msgstr "КількіÑÑ‚ÑŒ Ñекунд Ð¾Ñ‡Ñ–ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ð¿ÐµÑ€ÐµÐ´ повторною Ñпробою доÑтупу до Ñховища даних" +msgid "Secret variables" +msgstr "" + msgid "Select Archive Format" msgstr "Виберіть формат архіву" msgid "Select a timezone" msgstr "Вибрати чаÑовий поÑÑ" +msgid "Select assignee" +msgstr "Виберіть виконавцÑ" + +msgid "Select branch/tag" +msgstr "Виберіть гілку або тег" + msgid "Select target branch" msgstr "Вибір цільової гілки" +msgid "Selective synchronization" +msgstr "" + msgid "Sep" msgstr "вер." msgid "September" msgstr "вереÑень" +msgid "Server version" +msgstr "ВерÑÑ–Ñ Ñервера" + msgid "Service Templates" msgstr "Ð¡ÐµÑ€Ð²Ñ–Ñ ÑˆÐ°Ð±Ð»Ð¾Ð½Ñ–Ð²" msgid "Set a password on your account to pull or push via %{protocol}." -msgstr "" +msgstr "Ð’Ñтановіть пароль Ð´Ð»Ñ Ñвого облікового запиÑу, щоб мати можливіÑÑ‚ÑŒ відправлÑти та отримувати через %{protocol}." -msgid "Set up CI" -msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ CI" +msgid "Set up CI/CD" +msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ CI/CD" msgid "Set up Koding" msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Koding" msgid "Set up auto deploy" -msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡Ð½Ðµ розгортаннÑ" +msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡Ð½Ð¾Ð³Ð¾ розгортаннÑ" msgid "SetPasswordToCloneLink|set a password" msgstr "вÑтановити пароль" @@ -2185,6 +2755,15 @@ msgstr "вÑтановити пароль" msgid "Settings" msgstr "ÐалаштуваннÑ" +msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero." +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset pipeline minutes" +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes" +msgstr "" + msgid "Show parent pages" msgstr "Показати батьківÑькі Ñторінки" @@ -2200,9 +2779,6 @@ msgstr[2] "Показано %d подій" msgid "Sidebar|Change weight" msgstr "Змінити вагу" -msgid "Sidebar|Edit" -msgstr "Редагувати" - msgid "Sidebar|No" msgstr "ÐÑ–" @@ -2213,20 +2789,32 @@ msgid "Sidebar|Weight" msgstr "Вага" msgid "Snippets" +msgstr "Сніпети" + +msgid "Something went wrong on our end" msgstr "" msgid "Something went wrong on our end." msgstr "ЩоÑÑŒ пішло не так з нашого боку" +msgid "Something went wrong trying to change the confidentiality of this issue" +msgstr "" + msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}" msgstr "ЩоÑÑŒ пішло не так, при Ñпробі зміни Ñтану Ð±Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ ${this.issuableDisplayName}" +msgid "Something went wrong when toggling the button" +msgstr "" + msgid "Something went wrong while fetching the projects." msgstr "ЩоÑÑŒ пішло не так під Ñ‡Ð°Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚Ñ–Ð²" msgid "Something went wrong while fetching the registry list." msgstr "ЩоÑÑŒ пішло не так при отриманні ÑпиÑку із реєÑтру." +msgid "Something went wrong. Please try again." +msgstr "" + msgid "Sort by" msgstr "Сортувати за" @@ -2342,10 +2930,10 @@ msgid "Specify the following URL during the Runner setup:" msgstr "Зазначте наÑтупний URL під Ñ‡Ð°Ñ Ð²ÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Runner-а:" msgid "StarProject|Star" -msgstr "ПідпиÑатиÑÑ" +msgstr "Ð’ обрані" msgid "Starred projects" -msgstr "Відмічені проекти" +msgstr "Обрані проекти" msgid "Start a %{new_merge_request} with these changes" msgstr "Почати %{new_merge_request} з цими змінами" @@ -2356,17 +2944,17 @@ msgstr "ЗапуÑÑ‚Ñ–Ñ‚ÑŒ Runner!" msgid "Stopped" msgstr "Зупинено" +msgid "Storage" +msgstr "" + msgid "Subgroups" msgstr "Підгрупи" -msgid "Subscribe" -msgstr "ПідпиÑатиÑÑ" - msgid "Switch branch/tag" -msgstr "" +msgstr "Перейти в гілку/тег" msgid "System Hooks" -msgstr "" +msgstr "СиÑтемні гуки" msgid "Tag" msgid_plural "Tags" @@ -2417,7 +3005,7 @@ msgid "TagsPage|Optionally, add a message to the tag." msgstr "При бажанні Ви можете додати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð² тег." msgid "TagsPage|Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page." -msgstr "" +msgstr "При бажанні, додайте Ð¾Ð¿Ð¸Ñ Ñ€ÐµÐ»Ñ–Ð·Ñƒ до тегу. Він буде збережений в базі даних GitLab Ñ– відображатиметьÑÑ Ð½Ð° Ñторінці тегів." msgid "TagsPage|Release notes" msgstr "ÐžÐ¿Ð¸Ñ Ñ€ÐµÐ»Ñ–Ð·Ñƒ" @@ -2438,7 +3026,7 @@ msgid "TagsPage|This tag has no release notes." msgstr "Цей тег не міÑтить опиÑу релізу." msgid "TagsPage|Use git tag command to add a new one:" -msgstr "ВикориÑтовуйте команду git tag, щоб додати новий:" +msgstr "ВикориÑтовуйте команду git tag, щоб додати новий тег:" msgid "TagsPage|Write your release notes or drag files here..." msgstr "Ðапишіть Ñвій Ð¾Ð¿Ð¸Ñ Ñ€ÐµÐ»Ñ–Ð·Ñƒ або перетÑгніть файли Ñюди..." @@ -2458,41 +3046,41 @@ msgstr "ДÑкую! Більше не показувати це повідомл msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project." msgstr "Розширений глобальний пошук в GitLab - це потужний інÑтрумент Ñкий заощаджує ваш чаÑ. ЗаміÑÑ‚ÑŒ Ð´ÑƒÐ±Ð»ÑŽÐ²Ð°Ð½Ð½Ñ ÐºÐ¾Ð´Ñƒ Ñ– витрати чаÑу, ви можете шукати код інших команд, Ñкий може допомогти у вашому проекті." -msgid "The circuitbreaker backoff threshold should be lower than the failure count threshold" -msgstr "Поріг Ð¿Ñ€Ð¸Ð·ÑƒÐ¿Ð¸Ð½ÐµÐ½Ð½Ñ circuitbreaker має бути нижчий за поріг повного відключеннÑ" +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project" +msgstr "" -msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request." +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project." msgstr "" +msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request." +msgstr "Ð¡Ñ‚Ð°Ð´Ñ–Ñ \"ÐапиÑÐ°Ð½Ð½Ñ ÐºÐ¾Ð´Ñƒ\" показує Ñ‡Ð°Ñ Ð²Ñ–Ð´ першого коміту до ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð·Ð°Ð¿Ð¸Ñ‚Ñƒ на злиттÑ. Дані будуть автоматично додані піÑÐ»Ñ ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð²Ð°ÑˆÐ¾Ð³Ð¾ першого запиту на злиттÑ." + msgid "The collection of events added to the data gathered for that stage." -msgstr "" +msgstr "ÐšÐ¾Ð»ÐµÐºÑ†Ñ–Ñ Ð¿Ð¾Ð´Ñ–Ð¹ додана до даних, зібраних Ð´Ð»Ñ Ñ†Ñ–Ñ”Ñ— Ñтадії." msgid "The fork relationship has been removed." -msgstr "" +msgstr "Зв'Ñзок форку видалено." msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage." +msgstr "Ð¡Ñ‚Ð°Ð´Ñ–Ñ \"Проблема\" показує, Ñкільки чаÑу потрібно від ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ð¸ до Ð²ÐºÐ»ÑŽÑ‡ÐµÐ½Ð½Ñ Ñ—Ñ— до ÑкогоÑÑŒ етапу, або Ð´Ð¾Ð´Ð°Ð²Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ð¸ на дошку. Почніть Ñтворювати проблеми, щоб переглÑдати дані Ð´Ð»Ñ Ñ†Ñ–Ñ”Ñ— Ñтадії." + +msgid "The maximum file size allowed is 200KB." msgstr "" msgid "The number of attempts GitLab will make to access a storage." msgstr "КількіÑÑ‚ÑŒ Ñпроб, Ñкі зробить GitLab Ð´Ð»Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð´Ð¾Ñтупу до Ñховища даних." -msgid "The number of failures after which GitLab will start temporarily disabling access to a storage shard on a host" -msgstr "КількіÑÑ‚ÑŒ невдач, піÑÐ»Ñ Ñ‡Ð¾Ð³Ð¾ GitLab почне тимчаÑово блокувати доÑтуп до Ñховища на хоÑÑ‚Ñ–" - msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}." msgstr "КількіÑÑ‚ÑŒ збоїв піÑÐ»Ñ Ñкої Gitlab повніÑÑ‚ÑŽ заблокує доÑтуп до Ñховища данних. Лічильник кількоÑÑ‚Ñ– збоїв може бути Ñкинутий в інтерфейÑÑ– адмініÑтратора (%{link_to_health_page}), або через %{api_documentation_link}." msgid "The phase of the development lifecycle." msgstr "Фаза життєвого циклу розробки." -msgid "The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user." -msgstr "Розклад конвеєрів запуÑкає в майбутньому конвеєри, Ð´Ð»Ñ Ð¿ÐµÐ²Ð½Ð¸Ñ… гілок або тегів. Заплановані конвеєри уÑпадковують Ð¾Ð±Ð¼ÐµÐ¶ÐµÐ½Ð½Ñ Ð½Ð° доÑтуп до проекту на оÑнові пов'Ñзаного з ними кориÑтувача." - msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit." -msgstr "" +msgstr "Ð¡Ñ‚Ð°Ð´Ñ–Ñ \"ПлануваннÑ\" відображаєтьÑÑ Ñ‡Ð°Ñ Ð²Ñ–Ð´ попереднього кроку до першого коміту. ДодаєтьÑÑ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡Ð½Ð¾, Ñк тільки відправитьÑÑ Ð¿ÐµÑ€ÑˆÐ¸Ð¹ коміт." msgid "The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle." -msgstr "" +msgstr "Ð¡Ñ‚Ð°Ð´Ñ–Ñ \"Production\" показує загальний Ñ‡Ð°Ñ Ð¼Ñ–Ð¶ ÑтвореннÑм проблеми та розгортаннÑм коду у production. Дані будуть автоматично додані піÑÐ»Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ Ð¿Ð¾Ð²Ð½Ð¾Ñ— ідеї до production циклу." msgid "The project can be accessed by any logged in user." msgstr "ДоÑтуп до проекту можливий будь-Ñким зареєÑтрованим кориÑтувачем." @@ -2504,13 +3092,13 @@ msgid "The repository for this project does not exist." msgstr "Репозиторій Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ проекту не Ñ–Ñнує." msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request." -msgstr "" +msgstr "Ð¡Ñ‚Ð°Ð´Ñ–Ñ \"ЗатвердженнÑ\" показує Ñ‡Ð°Ñ Ð²Ñ–Ð´ ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð·Ð°Ð¿Ð¸Ñ‚Ñƒ про об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð´Ð¾ його виконаннÑ. Дані будуть автоматично додані піÑÐ»Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ Ð¿ÐµÑ€ÑˆÐ¾Ð³Ð¾ запиту на злиттÑ." msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time." -msgstr "" +msgstr "Ð¡Ñ‚Ð°Ð´Ñ–Ñ \"Staging\" показує Ñ‡Ð°Ñ Ð¼Ñ–Ð¶ Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ð·Ð°Ð¿Ð¸Ñ‚Ñƒ на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñ‚Ð° розгортаннÑм коду у production. Дані автоматично додаютьÑÑ Ð¿Ñ–ÑÐ»Ñ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ñƒ production вперше." msgid "The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running." -msgstr "" +msgstr "Ð¡Ñ‚Ð°Ð´Ñ–Ñ \"ТеÑтуваннÑ\" показує чаÑ, Ñкий GitLab CI витрачає Ð´Ð»Ñ Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ ÐºÐ¾Ð¶Ð½Ð¾Ð³Ð¾ конвеєра Ð´Ð»Ñ Ð²Ñ–Ð´Ð¿Ð¾Ð²Ñ–Ð´Ð½Ð¾Ð³Ð¾ запиту злиттÑ. Дані будуть автоматично додані піÑÐ»Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ Ð¿ÐµÑ€ÑˆÐ¾Ð³Ð¾ конвеєра." msgid "The time in seconds GitLab will keep failure information. When no failures occur during this time, information about the mount is reset." msgstr "КількіÑÑ‚ÑŒ Ñекунд, протÑгом Ñкої GitLab зберігає інформацію про збої. Якщо протÑгом цього періоду жодних збоїв не відбуваєтьÑÑ, Ñ–Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ точку Ð¼Ð¾Ð½Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ ÑкидаєтьÑÑ." @@ -2518,20 +3106,47 @@ msgstr "КількіÑÑ‚ÑŒ Ñекунд, протÑгом Ñкої GitLab збе msgid "The time in seconds GitLab will try to access storage. After this time a timeout error will be raised." msgstr "КількіÑÑ‚ÑŒ Ñекунд, протÑгом Ñкої GitLab намагатиметьÑÑ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ñ‚Ð¸ доÑтуп до Ñховища даних. По завершенню цього періоду буде згенерована помилка про Ð¿ÐµÑ€ÐµÐ²Ð¸Ñ‰ÐµÐ½Ð½Ñ Ð»Ñ–Ð¼Ñ–Ñ‚Ñƒ чаÑу." -msgid "The time taken by each data entry gathered by that stage." +msgid "The time in seconds between storage checks. When a previous check did complete yet, GitLab will skip a check." msgstr "" +msgid "The time taken by each data entry gathered by that stage." +msgstr "ЧаÑ, витрачений на кожен елемент, зібраний на цій Ñтадії." + msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6." msgstr "Середнє Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð² Ñ€Ñдку. Приклад: між 3, 5, 9, Ñередніми 5, між 3, 5, 7, 8, Ñередніми (5 + 7) / 2 = 6." +msgid "There are no issues to show" +msgstr "" + +msgid "There are no merge requests to show" +msgstr "" + msgid "There are problems accessing Git storage: " +msgstr "Є проблеми з доÑтупом до Ñховища git: " + +msgid "There was an error loading users activity calendar." +msgstr "" + +msgid "There was an error saving your notification settings." +msgstr "" + +msgid "There was an error subscribing to this label." +msgstr "" + +msgid "There was an error when reseting email token." +msgstr "" + +msgid "There was an error when subscribing to this label." +msgstr "" + +msgid "There was an error when unsubscribing from this label." msgstr "" msgid "This board\\'s scope is reduced" msgstr "ВидиміÑÑ‚ÑŒ цієї дошки обмежена" -msgid "This branch has changed since you started editing. Would you like to create a new branch?" -msgstr "Ð¦Ñ Ð³Ñ–Ð»ÐºÐ° була змінена піÑÐ»Ñ Ñ‚Ð¾Ð³Ð¾ моменту, коли ви почали Ñ—Ñ— редагувати. Ви хотіли б Ñтворити нову?" +msgid "This directory" +msgstr "Цей каталог" msgid "This is a confidential issue." msgstr "Це конфіденційна проблема." @@ -2539,20 +3154,47 @@ msgstr "Це конфіденційна проблема." msgid "This is the author's first Merge Request to this project." msgstr "Це перший запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð²Ñ–Ð´ цього автора Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ проекту." +msgid "This issue is confidential" +msgstr "Ð¦Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ð° Ñ” конфіденційною" + msgid "This issue is confidential and locked." msgstr "Ð¦Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ð° конфіденційна Ñ– заблокована." msgid "This issue is locked." msgstr "Ð¦Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ð° заблокована." -msgid "This means you can not push code until you create an empty repository or import existing one." +msgid "This job depends on a user to trigger its process. Often they are used to deploy code to production environments" +msgstr "" + +msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered" msgstr "" +msgid "This job has not been triggered yet" +msgstr "" + +msgid "This job has not started yet" +msgstr "" + +msgid "This job is in pending state and is waiting to be picked by a runner" +msgstr "" + +msgid "This job requires a manual action" +msgstr "" + +msgid "This means you can not push code until you create an empty repository or import existing one." +msgstr "Це означає, що ви не можете відправлÑти код, поки не Ñтворите порожній репозиторій або не імпортуєте Ñ–Ñнуючий." + msgid "This merge request is locked." msgstr "Цей запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð·Ð°Ð±Ð»Ð¾ÐºÐ¾Ð²Ð°Ð½Ð¾." +msgid "This project" +msgstr "Цей проект" + +msgid "This repository" +msgstr "Цей репозиторій" + msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here." -msgstr "" +msgstr "Ці Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð¾Ñ— пошти автоматично Ñтануть обговореннÑми проблем, Ñкі відображатимутьÑÑ Ñ‚ÑƒÑ‚ (причому коментарі Ñтануть чаÑтиною перепиÑки)." msgid "Time before an issue gets scheduled" msgstr "Ð§Ð°Ñ Ð´Ð¾ початку потраплÑÐ½Ð½Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ð¸ в планувальник" @@ -2561,11 +3203,23 @@ msgid "Time before an issue starts implementation" msgstr "Ð§Ð°Ñ Ð´Ð¾ початку роботи над проблемою" msgid "Time between merge request creation and merge/close" -msgstr "" +msgstr "Ð§Ð°Ñ Ð¼Ñ–Ð¶ ÑтвореннÑм запиту Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñ– його виконаннÑм або закриттÑм" + +msgid "Time tracking" +msgstr "ВідÑÑ‚ÐµÐ¶ÐµÐ½Ð½Ñ Ñ‡Ð°Ñу" msgid "Time until first merge request" msgstr "Ð§Ð°Ñ Ð´Ð¾ першого запиту на злиттÑ" +msgid "TimeTrackingEstimated|Est" +msgstr "" + +msgid "TimeTracking|Estimated:" +msgstr "" + +msgid "TimeTracking|Spent" +msgstr "" + msgid "Timeago|%s days ago" msgstr "%s днів тому" @@ -2707,6 +3361,18 @@ msgstr "Ñекунд(а)" msgid "Title" msgstr "Ðазва" +msgid "Todo" +msgstr "Задача" + +msgid "Toggle sidebar" +msgstr "Перемикач бічної панелі" + +msgid "ToggleButton|Toggle Status: OFF" +msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿ÐµÑ€ÐµÐ¼Ð¸ÐºÐ°Ñ‡Ð°: ВИМКÐЕÐО" + +msgid "ToggleButton|Toggle Status: ON" +msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿ÐµÑ€ÐµÐ¼Ð¸ÐºÐ°Ñ‡Ð°: УВІМКÐЕÐО" + msgid "Total Time" msgstr "Загальний чаÑ" @@ -2717,40 +3383,61 @@ msgid "Total test time for all commits/merges" msgstr "Загальний чаÑ, щоб перевірити вÑÑ– коміти/злиттÑ" msgid "Track activity with Contribution Analytics." -msgstr "" +msgstr "ВідÑтежувати активніÑÑ‚ÑŒ за допомогою Ðналітики учаÑників." msgid "Track groups of issues that share a theme, across projects and milestones" msgstr "ВідÑтежуйте групи проблем зі Ñпільною темою з різних проектів та етапів" +msgid "Total: %{total}" +msgstr "" + +msgid "Track time with quick actions" +msgstr "" + +msgid "Trigger this manual action" +msgstr "" + msgid "Turn on Service Desk" msgstr "Ввімкнути Service Desk" +msgid "Type %{value} to confirm:" +msgstr "" + +msgid "Unable to reset project cache." +msgstr "" + +msgid "Unknown" +msgstr "Ðевідомо" + msgid "Unlock" msgstr "Розблокувати" +msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment." +msgstr "" + msgid "Unlocked" msgstr "Розблоковано" msgid "Unstar" -msgstr "ВідпиÑатиÑÑŒ" +msgstr "Видалити із обраних" -msgid "Unsubscribe" -msgstr "ВідпиÑатиÑÑ" +msgid "Up to date" +msgstr "" msgid "Upgrade your plan to activate Advanced Global Search." msgstr "Перейдіть на вищий тарифний план щоб активувати Покращений Глобальний Пошук." msgid "Upgrade your plan to activate Contribution Analytics." -msgstr "" +msgstr "Перейдіть на вищий тарифний план Ð´Ð»Ñ Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ñ–Ñ— Ðналітики учаÑників." msgid "Upgrade your plan to activate Group Webhooks." -msgstr "" +msgstr "Перейдіть на вищий тарифний план щоб активувати групові веб-гуки." msgid "Upgrade your plan to activate Issue weight." -msgstr "" +msgstr "Перейдіть на вищий тарифний план щоб активувати вагу обговорень проблем." msgid "Upgrade your plan to improve Issue boards." -msgstr "" +msgstr "Перейдіть на вищий тарифний план щоб покращити дошки обговорень." msgid "Upload New File" msgstr "Завантажити новий файл" @@ -2758,6 +3445,9 @@ msgstr "Завантажити новий файл" msgid "Upload file" msgstr "Завантажити файл" +msgid "Upload new avatar" +msgstr "" + msgid "UploadLink|click to upload" msgstr "ÐатиÑніть, щоб завантажити" @@ -2770,9 +3460,15 @@ msgstr "ВикориÑтовувати токен під Ñ‡Ð°Ñ ÑƒÑтановк msgid "Use your global notification setting" msgstr "ВикориÑтовуютьÑÑ Ð³Ð»Ð¾Ð±Ð°Ð»ÑŒÐ½Ñ– Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½ÑŒ" +msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." +msgstr "" + msgid "View file @ " msgstr "ПереглÑд файла @ " +msgid "View labels" +msgstr "" + msgid "View open merge request" msgstr "ПереглÑд відкритих запитів на злиттÑ" @@ -2794,21 +3490,21 @@ msgstr "Ðевідомий" msgid "Want to see the data? Please ask an administrator for access." msgstr "Хочете побачити дані? Будь лаÑка, попроÑить у адмініÑтратора доÑтуп." -msgid "We don't have enough data to show this stage." +msgid "We could not verify that one of your projects on GCP has billing enabled. Please try again." msgstr "" +msgid "We don't have enough data to show this stage." +msgstr "Ми не маємо доÑтатньо даних Ð´Ð»Ñ Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ñ†Ñ–Ñ”Ñ— Ñтадії." + msgid "We want to be sure it is you, please confirm you are not a robot." msgstr "Ми хочемо бути впевнені, що це ви, будь лаÑка, підтвердіть, що ви не робот." msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group." -msgstr "" +msgstr "Веб-гук дозволÑÑ” вам викликати URL Ñкщо, наприклад, був відправлений новий код або Ñтворено нову проблему. Ви можете налаштувати його так, щоб він реагував на певні події (відправки коду, проблеми або запити на злиттÑ). Групові веб-гуки заÑтоÑовуютьÑÑ Ð´Ð¾ вÑÑ–Ñ… проектів в групі Ñ– дозволÑÑŽÑ‚ÑŒ вам Ñтандартизувати Ñ—Ñ… Ð´Ð»Ñ Ð²Ñієї вашої групи." msgid "Weight" msgstr "Вага" -msgid "When access to a storage fails. GitLab will prevent access to the storage for the time specified here. This allows the filesystem to recover. Repositories on failing shards are temporarly unavailable" -msgstr "Коли відбуваєтьÑÑ Ð·Ð±Ñ–Ð¹ при доÑтупі до Ñховища даних, GitLab блокує доÑуп до нього протÑгом періоду чаÑу, заданому тут. Це дає можливіÑÑ‚ÑŒ файловій ÑиÑтемі відновитиÑÑ. Репозиторії на шардах (shards) зі збоÑми тимчаÑово не доÑтупні" - msgid "Wiki" msgstr "Wiki" @@ -2827,9 +3523,15 @@ msgstr "РекомендуєтьÑÑ Ð²Ñтановити %{markdown}, з тим msgid "WikiClone|Start Gollum and edit locally" msgstr "ЗапуÑÑ‚Ñ–Ñ‚ÑŒ Gollum Ñ– редагуйте локально" -msgid "WikiEmptyPageError|You are not allowed to create wiki pages" +msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title." msgstr "" +msgid "WikiEdit|There is already a page with the same title in that path." +msgstr "" + +msgid "WikiEmptyPageError|You are not allowed to create wiki pages" +msgstr "Ви не можете Ñтворювати wiki-Ñторінки" + msgid "WikiHistoricalPage|This is an old version of this page." msgstr "Це — Ñтара верÑÑ–Ñ Ñторінки." @@ -2846,7 +3548,7 @@ msgid "WikiMarkdownDocs|More examples are in the %{docs_link}" msgstr "Більше прикладів знаходитьÑÑ Ð² %{docs_link}" msgid "WikiMarkdownDocs|documentation" -msgstr "документаціÑ" +msgstr "документації" msgid "WikiMarkdownTip|To link to a (new) page, simply type %{link_example}" msgstr "Ð”Ð»Ñ ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð¿Ð¾ÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ð° (нову) Ñторінку, проÑто введіть %{link_example}" @@ -2858,7 +3560,7 @@ msgid "WikiNewPageTip|Tip: You can specify the full path for the new file. We wi msgstr "Порада: можна вказати повний шлÑÑ… до нового файлу. Ми автоматично Ñтворимо вÑÑ– відÑутні каталоги." msgid "WikiNewPageTitle|New Wiki Page" -msgstr "" +msgstr "Ðова wiki-Ñторінка" msgid "WikiPageConfirmDelete|Are you sure you want to delete this page?" msgstr "Ви дійÑно бажаєте видалити цю Ñторінку?" @@ -2909,10 +3611,10 @@ msgid "Wiki|Pages" msgstr "Сторінки" msgid "Wiki|Wiki Pages" -msgstr "" +msgstr "Wiki-Ñторінки" msgid "With contribution analytics you can have an overview for the activity of issues, merge requests and push events of your organization and its members." -msgstr "" +msgstr "З аналітикою учаÑників ви може вивчати активніÑÑ‚ÑŒ в обговореннÑÑ…, запитах на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñ– змінах у коді Ð´Ð»Ñ Ð²Ð°ÑˆÐ¾Ñ— організації та Ñ—Ñ— учаÑників." msgid "Withdraw Access Request" msgstr "СкаÑувати запит доÑтупу" @@ -2929,7 +3631,19 @@ msgstr "Ви збираєтеÑÑ Ð²Ð¸Ð´Ð°Ð»Ð¸Ñ‚Ð¸ зв'Ñзок з форка Ð msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" msgstr "Ви збираєтеÑÑ Ð¿ÐµÑ€ÐµÐ´Ð°Ñ‚Ð¸ проект %{project_name_with_namespace} іншому влаÑнику. Ви ÐБСОЛЮТÐО впевнені?" +msgid "You can also star a label to make it a priority label." +msgstr "" + +msgid "You can move around the graph by using the arrow keys." +msgstr "" + +msgid "You can move around the graph by using the arrow keys." +msgstr "" + msgid "You can only add files when you are on a branch" +msgstr "Ви можете додавати файли тільки коли перебуваєте в гілці" + +msgid "You can only edit files when you are on a branch" msgstr "" msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead." @@ -2963,12 +3677,18 @@ msgid "You will receive notifications only for comments in which you were @menti msgstr "Ви будете отримувати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ñ‚Ñ–Ð»ÑŒÐºÐ¸ Ð´Ð»Ñ ÐºÐ¾Ð¼ÐµÐ½Ñ‚Ð°Ñ€Ñ–Ð², в Ñких ви були @згадані" msgid "You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account" -msgstr "" +msgstr "Ви не зможете відправлÑти та отримувати код проекту через %{protocol} поки не %{set_password_link} Ð´Ð»Ñ Ð²Ð°ÑˆÐ¾Ð³Ð¾ облікового запиÑу" msgid "You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile" -msgstr "" +msgstr "Ви не зможете відправлÑти та отримувати код проекту через SSH поки не %{add_ssh_key_link} до вашого профілю" msgid "You won't be able to pull or push project code via SSH until you add an SSH key to your profile" +msgstr "Ви не зможете відправлÑти та отримувати код проекту через SSH, поки не додаÑте в Ñвій профіль SSH ключ" + +msgid "You'll need to use different branch names to get a valid comparison." +msgstr "" + +msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure" msgstr "" msgid "Your comment will not be visible to the public." @@ -2983,32 +3703,227 @@ msgstr "Ваше ім'Ñ" msgid "Your projects" msgstr "Ваші проекти" +msgid "assign yourself" +msgstr "призначити Ñебе" + msgid "branch name" msgstr "ім'Ñ Ð³Ñ–Ð»ÐºÐ¸" msgid "by" msgstr "від" +msgid "ciReport|Code quality" +msgstr "ЯкіÑÑ‚ÑŒ коду" + +msgid "ciReport|DAST detected no alerts by analyzing the review app" +msgstr "" + +msgid "ciReport|Failed to load ${type} report" +msgstr "Ð—Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ð·Ð²Ñ–Ñ‚Ñƒ ${type} пройшло невдало" + +msgid "ciReport|Fixed:" +msgstr "" + +msgid "ciReport|Instances" +msgstr "ІнÑтанÑи" + +msgid "ciReport|Learn more about whitelisting" +msgstr "" + +msgid "ciReport|Loading ${type} report" +msgstr "" + +msgid "ciReport|No changes to code quality" +msgstr "" + +msgid "ciReport|No changes to performance metrics" +msgstr "" + +msgid "ciReport|Performance metrics" +msgstr "" + +msgid "ciReport|SAST" +msgstr "SAST" + +msgid "ciReport|SAST detected no security vulnerabilities" +msgstr "" + +msgid "ciReport|SAST:container no vulnerabilities were found" +msgstr "" + +msgid "ciReport|Show complete code vulnerabilities report" +msgstr "" + +msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}" +msgstr "" + msgid "commit" msgstr "коміт" +msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue." +msgstr "" + +msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue." +msgstr "" + msgid "day" msgid_plural "days" msgstr[0] "день" msgstr[1] "дні" msgstr[2] "днів" +msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command." +msgstr "" + +msgid "merge request" +msgid_plural "merge requests" +msgstr[0] "запит на злиттÑ" +msgstr[1] "запити на злиттÑ" +msgstr[2] "запитів на злиттÑ" + +msgid "mrWidget|Cancel automatic merge" +msgstr "СкаÑувати автоматичне злиттÑ" + +msgid "mrWidget|Check out branch" +msgstr "" + +msgid "mrWidget|Checking ability to merge automatically" +msgstr "" + +msgid "mrWidget|Cherry-pick" +msgstr "" + +msgid "mrWidget|Cherry-pick this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Closed" +msgstr "" + +msgid "mrWidget|Closed by" +msgstr "" + +msgid "mrWidget|Closes" +msgstr "" + +msgid "mrWidget|Did not close" +msgstr "" + +msgid "mrWidget|Email patches" +msgstr "" + +msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|Mentions" +msgstr "" + +msgid "mrWidget|Merge" +msgstr "ЗлиттÑ" + +msgid "mrWidget|Merge failed." +msgstr "Ð—Ð»Ð¸Ñ‚Ñ‚Ñ Ð¿Ñ€Ð¾Ð¹ÑˆÐ»Ð¾ невдало." + +msgid "mrWidget|Merge locally" +msgstr "" + +msgid "mrWidget|Merged by" +msgstr "" + +msgid "mrWidget|Plain diff" +msgstr "" + +msgid "mrWidget|Refresh" +msgstr "Оновити" + +msgid "mrWidget|Refresh now" +msgstr "Оновити зараз" + +msgid "mrWidget|Refreshing now" +msgstr "" + +msgid "mrWidget|Remove Source Branch" +msgstr "" + +msgid "mrWidget|Remove source branch" +msgstr "" + +msgid "mrWidget|Request to merge" +msgstr "" + +msgid "mrWidget|Resolve conflicts" +msgstr "" + +msgid "mrWidget|Revert" +msgstr "" + +msgid "mrWidget|Revert this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Set by" +msgstr "" + +msgid "mrWidget|The changes were merged into" +msgstr "" + +msgid "mrWidget|The changes were not merged into" +msgstr "" + +msgid "mrWidget|The changes will be merged into" +msgstr "" + +msgid "mrWidget|The source branch has been removed" +msgstr "" + +msgid "mrWidget|The source branch is being removed" +msgstr "" + +msgid "mrWidget|The source branch will be removed" +msgstr "" + +msgid "mrWidget|The source branch will not be removed" +msgstr "" + +msgid "mrWidget|There are merge conflicts" +msgstr "" + +msgid "mrWidget|This merge request failed to be merged automatically" +msgstr "ВідбулаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° при автоматичному злитті цього запиту" + +msgid "mrWidget|This merge request is in the process of being merged" +msgstr "" + +msgid "mrWidget|This project is archived, write access has been disabled" +msgstr "" + +msgid "mrWidget|You can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|You can remove source branch now" +msgstr "" + +msgid "mrWidget|command line" +msgstr "" + +msgid "mrWidget|into" +msgstr "" + +msgid "mrWidget|to be merged automatically when the pipeline succeeds" +msgstr "" + msgid "new merge request" msgstr "Ðовий запит на злиттÑ" msgid "notification emails" msgstr "ÐŸÐ¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð¾ÑŽ поштою" +msgid "or" +msgstr "або" + msgid "parent" msgid_plural "parents" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "батьківÑький об’єкт" +msgstr[1] "батьківÑькі об’єкти" +msgstr[2] "батьківÑький об’єктів" msgid "password" msgstr "пароль" @@ -3016,12 +3931,21 @@ msgstr "пароль" msgid "personal access token" msgstr "оÑобиÑтий токен доÑтупу" +msgid "remove due date" +msgstr "видалити заплановану дату завершеннÑ" + msgid "source" msgstr "джерело" -msgid "to help your contributors communicate effectively!" +msgid "spendCommand|%{slash_command} will update the sum of the time spent." msgstr "" +msgid "to help your contributors communicate effectively!" +msgstr "щоб допомогти учаÑникам ефективно ÑпілкуватиÑÑ!" + msgid "username" msgstr "ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача" +msgid "uses Kubernetes clusters to deploy your code!" +msgstr "" + diff --git a/locale/zh_CN/gitlab.po b/locale/zh_CN/gitlab.po index f0a5453f224..441f080596c 100644 --- a/locale/zh_CN/gitlab.po +++ b/locale/zh_CN/gitlab.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab-ee\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-12-12 18:31+0000\n" -"PO-Revision-Date: 2018-01-05 04:42-0500\n" +"POT-Creation-Date: 2018-02-07 11:38-0600\n" +"PO-Revision-Date: 2018-02-12 03:58-0500\n" "Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n" "Language-Team: Chinese Simplified\n" "Language: zh_CN\n" @@ -16,20 +16,35 @@ msgstr "" "X-Crowdin-Language: zh-CN\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" +msgid " and" +msgstr "" + msgid "%d commit" msgid_plural "%d commits" msgstr[0] "%d 次æ交" +msgid "%d commit behind" +msgid_plural "%d commits behind" +msgstr[0] "" + +msgid "%d issue" +msgid_plural "%d issues" +msgstr[0] "" + msgid "%d layer" msgid_plural "%d layers" msgstr[0] "%d 层" +msgid "%d merge request" +msgid_plural "%d merge requests" +msgstr[0] "" + msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "%s additional commits have been omitted to prevent performance issues." msgstr[0] "为æ高页é¢åŠ 载速度åŠæ€§èƒ½ï¼Œå·²çœç•¥äº† %s 次æ交。" -msgid "%{commit_author_link} committed %{commit_timeago}" -msgstr "ç”± %{commit_author_link} æ交于 %{commit_timeago}" +msgid "%{commit_author_link} authored %{commit_timeago}" +msgstr "" msgid "%{count} participant" msgid_plural "%{count} participants" @@ -41,9 +56,6 @@ msgstr "%{number_commits_behind} 个è½åŽ %{default_branch} 分支的æ交, %{ msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will allow access on the next attempt." msgstr "已失败 %{number_of_failures} 次/最多å…许失败失败 %{maximum_failures} 次,GitLab 将继ç»é‡è¯•ã€‚" -msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will block access for %{number_of_seconds} seconds." -msgstr "已失败 %{number_of_failures} 次/最多å…许失败 %{maximum_failures} 次,GitLab 将在 %{number_of_seconds} 秒åŽé‡è¯•ã€‚" - msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved." msgstr "已失败 %{number_of_failures} 次/最多å…许失败 %{maximum_failures} 次,GitLab ä¸ä¼šç»§ç»è‡ªåŠ¨é‡è¯•ã€‚请在问题解决åŽé‡ç½®å˜å‚¨å¥åº·ä¿¡æ¯ã€‚" @@ -115,24 +127,81 @@ msgstr "æ·»åŠ è®¸å¯è¯" msgid "Add new directory" msgstr "æ·»åŠ ç›®å½•" +msgid "Add todo" +msgstr "" + +msgid "AdminArea|Stop all jobs" +msgstr "" + +msgid "AdminArea|Stop all jobs?" +msgstr "" + +msgid "AdminArea|Stop jobs" +msgstr "" + +msgid "AdminArea|Stopping jobs failed" +msgstr "" + +msgid "AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running." +msgstr "" + msgid "AdminHealthPageLink|health page" msgstr "å¥åº·é¡µé¢" +msgid "Advanced" +msgstr "" + msgid "Advanced settings" msgstr "高级设置" msgid "All" msgstr "全部" +msgid "All changes are committed" +msgstr "" + +msgid "Allows you to add and manage Kubernetes clusters." +msgstr "" + +msgid "An error occurred previewing the blob" +msgstr "" + msgid "An error occurred when toggling the notification subscription" msgstr "切æ¢é€šçŸ¥è®¢é˜…æ—¶å‘生错误" msgid "An error occurred when updating the issue weight" msgstr "更新议题æƒé‡æ—¶å‘生错误" +msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." +msgstr "" + +msgid "An error occurred while fetching markdown preview" +msgstr "" + msgid "An error occurred while fetching sidebar data" msgstr "获å–侧边æ æ•°æ®æ—¶å‘生错误" +msgid "An error occurred while getting projects" +msgstr "" + +msgid "An error occurred while loading filenames" +msgstr "" + +msgid "An error occurred while rendering KaTeX" +msgstr "" + +msgid "An error occurred while rendering preview broadcast message" +msgstr "" + +msgid "An error occurred while retrieving calendar activity" +msgstr "" + +msgid "An error occurred while retrieving diff" +msgstr "" + +msgid "An error occurred while validating username" +msgstr "" + msgid "An error occurred. Please try again." msgstr "å‘生了错误,请å†è¯•ä¸€æ¬¡ã€‚" @@ -157,9 +226,6 @@ msgstr "确定è¦åˆ 除æ¤æµæ°´çº¿è®¡åˆ’å—?" msgid "Are you sure you want to discard your changes?" msgstr "确定è¦æ”¾å¼ƒä¿®æ”¹å—?" -msgid "Are you sure you want to leave this group?" -msgstr "确定è¦ç¦»å¼€è¿™ä¸ªç¾¤ç»„å—?" - msgid "Are you sure you want to reset registration token?" msgstr "确定è¦é‡ç½®æ³¨å†Œä»¤ç‰Œå—?" @@ -172,6 +238,21 @@ msgstr "确定å—?" msgid "Artifacts" msgstr "产物" +msgid "Assign custom color like #FF0000" +msgstr "" + +msgid "Assign labels" +msgstr "" + +msgid "Assign milestone" +msgstr "" + +msgid "Assign to" +msgstr "" + +msgid "Assignee" +msgstr "" + msgid "Attach a file by drag & drop or %{upload_link}" msgstr "拖放文件到æ¤å¤„或者 %{upload_link}" @@ -187,15 +268,18 @@ msgstr "认è¯æ—¥å¿—" msgid "Author" msgstr "作者" -msgid "Auto Review Apps and Auto Deploy need a domain name and the %{kubernetes} to work correctly." -msgstr "自动审查程åºå’Œè‡ªåŠ¨éƒ¨ç½²ç¨‹åºéœ€è¦ä¸€ä¸ªåŸŸåå’Œ %{kubernetes} æ‰èƒ½æ£å¸¸å·¥ä½œã€‚" +msgid "Authors: %{authors}" +msgstr "" + +msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." +msgstr "" + +msgid "Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly." +msgstr "" msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly." msgstr "自动审查程åºå’Œè‡ªåŠ¨éƒ¨ç½²ç¨‹åºéœ€è¦ä¸€ä¸ªåŸŸåæ‰èƒ½æ£å¸¸å·¥ä½œã€‚" -msgid "Auto Review Apps and Auto Deploy need the %{kubernetes} to work correctly." -msgstr "自动审查程åºå’Œè‡ªåŠ¨éƒ¨ç½²ç¨‹åºéœ€è¦ %{kubernetes} æ‰èƒ½æ£å¸¸å·¥ä½œã€‚" - msgid "AutoDevOps|Auto DevOps (Beta)" msgstr "DevOps 自动化(测试版)" @@ -217,6 +301,12 @@ msgstr "您å¯ä»¥ä¸ºæ¤é¡¹ç›®æ¿€æ´» %{link_to_settings}。" msgid "Available" msgstr "å¯ç”¨çš„" +msgid "Avatar will be removed. Are you sure?" +msgstr "" + +msgid "Average per day: %{average}" +msgstr "" + msgid "Billing" msgstr "è´¦å•" @@ -271,6 +361,9 @@ msgstr "æ¯å¹´æ”¯ä»˜ %{price_per_year}" msgid "BillingPlans|per user" msgstr "æ¯ç”¨æˆ·" +msgid "Begin with the selected commit" +msgstr "" + msgid "Branch" msgid_plural "Branches" msgstr[0] "分支" @@ -398,8 +491,8 @@ msgstr "作者:" msgid "CI / CD" msgstr "CI / CD" -msgid "CI configuration" -msgstr "CI é…ç½®" +msgid "CI/CD configuration" +msgstr "" msgid "CICD|Jobs" msgstr "作业" @@ -410,6 +503,9 @@ msgstr "å–消" msgid "Cancel edit" msgstr "å–消编辑" +msgid "Cannot modify managed Kubernetes cluster" +msgstr "" + msgid "Change Weight" msgstr "改å˜æƒé‡" @@ -425,15 +521,24 @@ msgstr "优选" msgid "ChangeTypeAction|Revert" msgstr "还原" +msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes." +msgstr "" + msgid "Changelog" msgstr "更新日志" +msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision." +msgstr "" + msgid "Charts" msgstr "统计图" msgid "Chat" msgstr "å³æ—¶é€šè®¯" +msgid "Check interval" +msgstr "" + msgid "Checking %{text} availability…" msgstr "æ£åœ¨æ£€æŸ¥%{text}çš„å¯ç”¨æ€§..." @@ -446,8 +551,20 @@ msgstr "优选æ¤æ交" msgid "Cherry-pick this merge request" msgstr "优选æ¤åˆå¹¶è¯·æ±‚" -msgid "Choose which groups you wish to replicate to this secondary node. Leave blank to replicate all." -msgstr "选择è¦å¤åˆ¶åˆ°æ¤èŠ‚点的群组。留空则å¤åˆ¶æ‰€æœ‰ã€‚" +msgid "Choose File ..." +msgstr "" + +msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request." +msgstr "" + +msgid "Choose file..." +msgstr "" + +msgid "Choose which groups you wish to synchronize to this secondary node." +msgstr "" + +msgid "Choose which shards you wish to synchronize to this secondary node." +msgstr "" msgid "CiStatusLabel|canceled" msgstr "å·²å–消" @@ -503,80 +620,92 @@ msgstr "已跳过" msgid "CiStatus|running" msgstr "è¿è¡Œä¸" +msgid "CiVariables|Input variable key" +msgstr "" + +msgid "CiVariables|Input variable value" +msgstr "" + +msgid "CiVariables|Remove variable row" +msgstr "" + +msgid "CiVariable|* (All environments)" +msgstr "" + +msgid "CiVariable|All environments" +msgstr "" + +msgid "CiVariable|Create wildcard" +msgstr "" + +msgid "CiVariable|Error occured while saving variables" +msgstr "" + +msgid "CiVariable|New environment" +msgstr "" + +msgid "CiVariable|Protected" +msgstr "" + +msgid "CiVariable|Search environments" +msgstr "" + +msgid "CiVariable|Toggle protected" +msgstr "" + +msgid "CiVariable|Validation failed" +msgstr "" + msgid "CircuitBreakerApiLink|circuitbreaker api" msgstr "æ–路器 API" +msgid "Click to expand text" +msgstr "" + msgid "Clone repository" msgstr "克隆å˜å‚¨åº“" msgid "Close" msgstr "å…³é—" -msgid "Cluster" -msgstr "集群" - -msgid "ClusterIntegration|%{appList} was successfully installed on your cluster" -msgstr "%{appList}å·²æˆåŠŸå®‰è£…在您的群集上" +msgid "Closed" +msgstr "" -msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which incur additional costs. See %{pricingLink}" -msgstr "%{boldNotice}è¿™ä¼šå¢žåŠ ä¸€äº›é¢å¤–的资æºï¼Œå¦‚è´Ÿè½½å‡è¡¡å™¨ï¼Œè¿™ä¼šäº§ç”Ÿé¢å¤–çš„æˆæœ¬ã€‚请å‚阅%{pricingLink}" +msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster" +msgstr "" msgid "ClusterIntegration|API URL" msgstr "API地å€" -msgid "ClusterIntegration|Active" -msgstr "å¯ç”¨" - -msgid "ClusterIntegration|Add an existing cluster" -msgstr "æ·»åŠ ä¸€ä¸ªçŽ°æœ‰çš„é›†ç¾¤" +msgid "ClusterIntegration|Add Kubernetes cluster" +msgstr "" -msgid "ClusterIntegration|Add cluster" -msgstr "æ·»åŠ é›†ç¾¤" +msgid "ClusterIntegration|Add an existing Kubernetes cluster" +msgstr "" -msgid "ClusterIntegration|All" -msgstr "所有" +msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration" +msgstr "" msgid "ClusterIntegration|Applications" msgstr "应用程åº" +msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster." +msgstr "" + msgid "ClusterIntegration|CA Certificate" msgstr "CAè¯ä¹¦" msgid "ClusterIntegration|Certificate Authority bundle (PEM format)" msgstr "è¯ä¹¦æŽˆæƒåŒ…(PEMæ ¼å¼)" -msgid "ClusterIntegration|Choose how to set up cluster integration" -msgstr "选择如何设置集群集æˆ" - -msgid "ClusterIntegration|Cluster" -msgstr "集群" - -msgid "ClusterIntegration|Cluster details" -msgstr "集群详情" - -msgid "ClusterIntegration|Cluster integration" -msgstr "集群集æˆ" - -msgid "ClusterIntegration|Cluster integration is disabled for this project." -msgstr "æ¤é¡¹ç›®å·²ç¦ç”¨é›†ç¾¤é›†æˆã€‚" - -msgid "ClusterIntegration|Cluster integration is enabled for this project." -msgstr "æ¤é¡¹ç›®å·²å¯ç”¨é›†ç¾¤é›†æˆã€‚" - -msgid "ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab's connection to it." -msgstr "æ¤é¡¹ç›®å·²å¯ç”¨é›†ç¾¤é›†æˆã€‚ç¦ç”¨æ¤é›†æˆä¸ä¼šå½±å“您的集群,它åªä¼šæš‚æ—¶å…³é— GitLab 的连接。" - -msgid "ClusterIntegration|Cluster is being created on Google Kubernetes Engine..." -msgstr "群集æ£åœ¨Google Kubernetes Engine上创建..." - -msgid "ClusterIntegration|Cluster name" -msgstr "集群å称" +msgid "ClusterIntegration|Choose how to set up Kubernetes cluster integration" +msgstr "" -msgid "ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine. Refresh the page to see cluster's details" -msgstr "集群已在Google Kubernetes Engine上æˆåŠŸåˆ›å»ºã€‚刷新页é¢ä»¥æŸ¥çœ‹é›†ç¾¤çš„详细信æ¯" +msgid "ClusterIntegration|Choose which of your project's environments will use this Kubernetes cluster." +msgstr "" -msgid "ClusterIntegration|Clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" -msgstr "集群å…许您使用审阅应用程åºã€éƒ¨ç½²åº”用程åºã€è¿è¡Œæµæ°´çº¿ç‰ç‰ã€‚%{link_to_help_page}" +msgid "ClusterIntegration|Control how your Kubernetes cluster integrates with GitLab" +msgstr "" msgid "ClusterIntegration|Copy API URL" msgstr "å¤åˆ¶API地å€" @@ -584,38 +713,35 @@ msgstr "å¤åˆ¶API地å€" msgid "ClusterIntegration|Copy CA Certificate" msgstr "å¤åˆ¶CAè¯ä¹¦" +msgid "ClusterIntegration|Copy Kubernetes cluster name" +msgstr "" + msgid "ClusterIntegration|Copy Token" msgstr "å¤åˆ¶ä»¤ç‰Œ" -msgid "ClusterIntegration|Copy cluster name" -msgstr "å¤åˆ¶é›†ç¾¤å称" - -msgid "ClusterIntegration|Create a new cluster on Google Engine right from GitLab" -msgstr "在 GitLab 上创建一个 Google Engine 集群" +msgid "ClusterIntegration|Create Kubernetes cluster" +msgstr "" -msgid "ClusterIntegration|Create cluster" -msgstr "创建集群" +msgid "ClusterIntegration|Create Kubernetes cluster on Google Kubernetes Engine" +msgstr "" -msgid "ClusterIntegration|Create cluster on Google Kubernetes Engine" -msgstr "在 Google Kubernetes Engine 上创建集群" +msgid "ClusterIntegration|Create a new Kubernetes cluster on Google Kubernetes Engine right from GitLab" +msgstr "" msgid "ClusterIntegration|Create on GKE" msgstr "在GKEä¸åˆ›å»º" -msgid "ClusterIntegration|Enable cluster integration" -msgstr "å¯ç”¨é›†ç¾¤é›†æˆ" - msgid "ClusterIntegration|Enter the details for an existing Kubernetes cluster" msgstr "输入现有的 Kubernetes 集群详细信æ¯" -msgid "ClusterIntegration|Enter the details for your cluster" -msgstr "输入您的集群详细信æ¯" +msgid "ClusterIntegration|Enter the details for your Kubernetes cluster" +msgstr "" -msgid "ClusterIntegration|Environment pattern" -msgstr "环境模å¼" +msgid "ClusterIntegration|Environment scope" +msgstr "" -msgid "ClusterIntegration|GKE pricing" -msgstr "GKEä»·æ ¼" +msgid "ClusterIntegration|GitLab Integration" +msgstr "" msgid "ClusterIntegration|GitLab Runner" msgstr "GitLab Runner" @@ -632,47 +758,83 @@ msgstr "Google Kubernetes Engine 项目" msgid "ClusterIntegration|Helm Tiller" msgstr "Helm Tiller" -msgid "ClusterIntegration|Inactive" -msgstr "待用" - msgid "ClusterIntegration|Ingress" msgstr "å…¥å£" msgid "ClusterIntegration|Install" msgstr "安装" -msgid "ClusterIntegration|Install applications on your cluster. Read more about %{helpLink}" -msgstr "在集群上安装应用程åºã€‚阅读更多关于%{helpLink}" - msgid "ClusterIntegration|Installed" msgstr "已安装" msgid "ClusterIntegration|Installing" msgstr "安装ä¸" -msgid "ClusterIntegration|Integrate cluster automation" -msgstr "集群自动化集æˆ" +msgid "ClusterIntegration|Integrate Kubernetes cluster automation" +msgstr "" + +msgid "ClusterIntegration|Integration status" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster details" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is disabled for this project." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project. Disabling this integration will not affect your Kubernetes cluster, it will only temporarily turn off GitLab's connection to it." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster name" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project" +msgstr "" msgid "ClusterIntegration|Learn more about %{link_to_documentation}" msgstr "了解详细%{link_to_documentation}" -msgid "ClusterIntegration|Learn more about Clusters" -msgstr "了解更多集群的信æ¯" +msgid "ClusterIntegration|Learn more about Kubernetes" +msgstr "" + +msgid "ClusterIntegration|Learn more about environments" +msgstr "" msgid "ClusterIntegration|Machine type" msgstr "机器类型" -msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create clusters" -msgstr "ç¡®ä¿æ‚¨çš„å¸æˆ·ç¬¦åˆåˆ›å»ºé›†ç¾¤çš„%{link_to_requirements}" +msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters" +msgstr "" -msgid "ClusterIntegration|Manage cluster integration on your GitLab project" -msgstr "在 GitLab 项目上管ç†é›†ç¾¤é›†æˆ" +msgid "ClusterIntegration|Manage" +msgstr "" -msgid "ClusterIntegration|Manage your cluster by visiting %{link_gke}" -msgstr "访问%{link_gke}æ¥ç®¡ç†æ‚¨çš„集群" +msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}" +msgstr "" -msgid "ClusterIntegration|Multiple clusters are available in GitLab Entreprise Edition Premium and Ultimate" -msgstr "GitLabä¼ä¸šé«˜çº§ç‰ˆå’Œæ——舰版æ供了多个集群" +msgid "ClusterIntegration|More information" +msgstr "" + +msgid "ClusterIntegration|Multiple Kubernetes clusters are available in GitLab Enterprise Edition Premium and Ultimate" +msgstr "" msgid "ClusterIntegration|Note:" msgstr "注æ„:" @@ -680,18 +842,12 @@ msgstr "注æ„:" msgid "ClusterIntegration|Number of nodes" msgstr "节点数é‡" -msgid "ClusterIntegration|Please enter access information for your cluster. If you need help, you can read our %{link_to_help_page} on clusters" -msgstr "请为您的群集输入访问信æ¯ã€‚如果您需è¦å¸®åŠ©ï¼Œå¯ä»¥é˜…读我们关于集群的 %{link_to_help_page}" +msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes" +msgstr "" msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:" msgstr "请确ä¿æ‚¨çš„ Google å¸æˆ·ç¬¦åˆä»¥ä¸‹è¦æ±‚:" -msgid "ClusterIntegration|Problem setting up the cluster" -msgstr "设置集群时出现问题" - -msgid "ClusterIntegration|Problem setting up the clusters list" -msgstr "设置集群列表时出现问题" - msgid "ClusterIntegration|Project ID" msgstr "项目 ID" @@ -701,17 +857,20 @@ msgstr "项目命å空间" msgid "ClusterIntegration|Project namespace (optional, unique)" msgstr "项目命å空间(å¯é€‰ï¼Œå”¯ä¸€)" -msgid "ClusterIntegration|Read our %{link_to_help_page} on cluster integration." -msgstr "请阅读关于集群集æˆçš„%{link_to_help_page}。" +msgid "ClusterIntegration|Prometheus" +msgstr "" -msgid "ClusterIntegration|Remove cluster integration" -msgstr "åˆ é™¤é›†ç¾¤é›†æˆ" +msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration." +msgstr "" + +msgid "ClusterIntegration|Remove Kubernetes cluster integration" +msgstr "" msgid "ClusterIntegration|Remove integration" msgstr "åˆ é™¤é›†æˆ" -msgid "ClusterIntegration|Removing cluster integration will remove the cluster configuration you have added to this project. It will not delete your cluster on Google Kubernetes Engine." -msgstr "åˆ é™¤é›†ç¾¤é›†æˆå°†åˆ é™¤å·²æ·»åŠ åˆ°æ¤é¡¹ç›®çš„集群é…置。它ä¸ä¼šåˆ 除 Google Kubernetes Engine 上的集群。" +msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster." +msgstr "" msgid "ClusterIntegration|Request to begin installing failed" msgstr "请求安装失败" @@ -719,8 +878,8 @@ msgstr "请求安装失败" msgid "ClusterIntegration|Save changes" msgstr "ä¿å˜æ›´æ”¹" -msgid "ClusterIntegration|See and edit the details for your cluster" -msgstr "查看并编辑集群的详细信æ¯" +msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster" +msgstr "" msgid "ClusterIntegration|See machine types" msgstr "å‚è§æœºå™¨ç±»åž‹" @@ -740,26 +899,26 @@ msgstr "显示" msgid "ClusterIntegration|Something went wrong on our end." msgstr "å‘生了内部错误" -msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine" -msgstr "在 Google Kubernetes Engine 上创建集群时å‘生错误" +msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine" +msgstr "" msgid "ClusterIntegration|Something went wrong while installing %{title}" msgstr "安装 %{title} æ—¶å‘生故障" -msgid "ClusterIntegration|There are no clusters to show" -msgstr "没有è¦æ˜¾ç¤ºçš„集群" +msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below" +msgstr "" -msgid "ClusterIntegration|This account must have permissions to create a cluster in the %{link_to_container_project} specified below" -msgstr "æ¤å¸æˆ·å¿…须有æƒåœ¨ä¸‹é¢æŒ‡å®šçš„%{link_to_container_project}ä¸åˆ›å»ºé›†ç¾¤" +msgid "ClusterIntegration|Toggle Kubernetes Cluster" +msgstr "" -msgid "ClusterIntegration|Toggle Cluster" -msgstr "切æ¢é›†ç¾¤" +msgid "ClusterIntegration|Toggle Kubernetes cluster" +msgstr "" msgid "ClusterIntegration|Token" msgstr "令牌" -msgid "ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." -msgstr "使用与æ¤é¡¹ç›®å…³è”的集群,您å¯ä»¥ä½¿ç”¨å®¡é˜…应用程åºï¼Œéƒ¨ç½²åº”用程åºï¼Œè¿è¡Œæµæ°´çº¿ç‰ç‰ã€‚" +msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." +msgstr "" msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}" msgstr "您的å¸æˆ·å¿…须拥有%{link_to_kubernetes_engine}" @@ -770,8 +929,8 @@ msgstr "区域" msgid "ClusterIntegration|access to Google Kubernetes Engine" msgstr "访问 Google Kubernetes Engine" -msgid "ClusterIntegration|cluster" -msgstr "集群" +msgid "ClusterIntegration|check the pricing here" +msgstr "" msgid "ClusterIntegration|documentation" msgstr "文档" @@ -788,6 +947,9 @@ msgstr "符åˆè¦æ±‚" msgid "ClusterIntegration|properly configured" msgstr "æ£ç¡®é…ç½®" +msgid "Collapse" +msgstr "" + msgid "Comments" msgstr "评论" @@ -804,6 +966,9 @@ msgstr "最近30次æ交相应æŒç»é›†æˆèŠ±è´¹çš„时间(分钟)" msgid "Commit message" msgstr "æ交信æ¯" +msgid "Commit statistics for %{ref} %{start_time} - %{end_time}" +msgstr "" + msgid "CommitBoxTitle|Commit" msgstr "æ交" @@ -816,15 +981,57 @@ msgstr "æ交" msgid "Commits feed" msgstr "æ交动æ€" +msgid "Commits per day hour (UTC)" +msgstr "" + +msgid "Commits per day of month" +msgstr "" + +msgid "Commits per weekday" +msgstr "" + +msgid "Commits|An error occurred while fetching merge requests data." +msgstr "" + +msgid "Commits|Commit: %{commitText}" +msgstr "" + msgid "Commits|History" msgstr "历å²" +msgid "Commits|No related merge requests found" +msgstr "" + msgid "Committed by" msgstr "æ交者:" msgid "Compare" msgstr "比较" +msgid "Compare Git revisions" +msgstr "" + +msgid "Compare Revisions" +msgstr "" + +msgid "CompareBranches|%{source_branch} and %{target_branch} are the same." +msgstr "" + +msgid "CompareBranches|Compare" +msgstr "" + +msgid "CompareBranches|Source" +msgstr "" + +msgid "CompareBranches|Target" +msgstr "" + +msgid "CompareBranches|There isn't anything to compare." +msgstr "" + +msgid "Confidentiality" +msgstr "" + msgid "Container Registry" msgstr "容器注册" @@ -876,6 +1083,9 @@ msgstr "贡献指å—" msgid "Contributors" msgstr "贡献者" +msgid "ContributorsPage|%{startDate} – %{endDate}" +msgstr "" + msgid "ContributorsPage|Building repository graph." msgstr "构建å˜å‚¨åº“å›¾æ ‡ã€‚" @@ -897,9 +1107,18 @@ msgstr "å¤åˆ¶ SSH 公钥到剪贴æ¿" msgid "Copy URL to clipboard" msgstr "å¤åˆ¶ URL 到剪贴æ¿" +msgid "Copy branch name to clipboard" +msgstr "" + msgid "Copy commit SHA to clipboard" msgstr "å¤åˆ¶æ交 SHA 的值到剪贴æ¿" +msgid "Copy reference to clipboard" +msgstr "" + +msgid "Create" +msgstr "" + msgid "Create New Directory" msgstr "创建新目录" @@ -918,6 +1137,9 @@ msgstr "创建EPIC" msgid "Create file" msgstr "创建文件" +msgid "Create lists from labels. Issues with that label appear in that list." +msgstr "" + msgid "Create merge request" msgstr "创建åˆå¹¶è¯·æ±‚" @@ -930,6 +1152,9 @@ msgstr "创建新目录" msgid "Create new file" msgstr "创建新文件" +msgid "Create new label" +msgstr "" + msgid "Create new..." msgstr "创建..." @@ -951,6 +1176,9 @@ msgstr "Cron 时区" msgid "Cron syntax" msgstr "Cron è¯æ³•" +msgid "Current node" +msgstr "" + msgid "Custom notification events" msgstr "自定义通知事件" @@ -960,9 +1188,6 @@ msgstr "自定义通知级别继承自å‚与级别。使用自定义通知级别 msgid "Cycle Analytics" msgstr "周期分æž" -msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project." -msgstr "周期分æžæ¦‚述了项目从想法到产å“实现的å„阶段所需的时间。" - msgid "CycleAnalyticsStage|Code" msgstr "ç¼–ç " @@ -1018,12 +1243,21 @@ msgstr "æ述模æ¿å…许您为项目的问题和åˆå¹¶è¯·æ±‚定义æè¿°å—段 msgid "Details" msgstr "详情" +msgid "Diffs|No file name available" +msgstr "" + msgid "Directory name" msgstr "目录å称" +msgid "Disable" +msgstr "" + msgid "Discard changes" msgstr "放弃更改" +msgid "Discover GitLab Geo." +msgstr "" + msgid "Dismiss Cycle Analytics introduction box" msgstr "å…³é—循环分æžä»‹ç»æ¡†" @@ -1060,15 +1294,24 @@ msgstr "差异文件" msgid "DownloadSource|Download" msgstr "下载" +msgid "Due date" +msgstr "" + msgid "Edit" msgstr "编辑" msgid "Edit Pipeline Schedule %{id}" msgstr "编辑 %{id} æµæ°´çº¿è®¡åˆ’" +msgid "Edit files in the editor and commit changes here" +msgstr "" + msgid "Emails" msgstr "电å邮件" +msgid "Enable" +msgstr "" + msgid "Environments|An error occurred while fetching the environments." msgstr "获å–环境时å‘生错误。" @@ -1087,9 +1330,6 @@ msgstr "环境" msgid "Environments|Environments" msgstr "环境" -msgid "Environments|Environments are places where code gets deployed, such as staging or production." -msgstr "环境是部署代ç 的地方,例如预生产或生产。" - msgid "Environments|Job" msgstr "作业" @@ -1132,9 +1372,33 @@ msgstr "EPICè®©ä½ æ›´æœ‰æ•ˆçŽ‡åœ°ç®¡ç†ä½ 的项目组åˆï¼Œè€Œä¸”ä¸è´¹å¹ç°ä¹ msgid "Error creating epic" msgstr "创建EPIC时出错" +msgid "Error fetching contributors data." +msgstr "" + +msgid "Error fetching labels." +msgstr "" + +msgid "Error fetching network graph." +msgstr "" + +msgid "Error fetching refs" +msgstr "" + +msgid "Error fetching usage ping data." +msgstr "" + msgid "Error occurred when toggling the notification subscription" msgstr "切æ¢é€šçŸ¥è®¢é˜…æ—¶å‘生错误" +msgid "Error saving label update." +msgstr "" + +msgid "Error updating status for all todos." +msgstr "" + +msgid "Error updating todo status." +msgstr "" + msgid "EventFilterBy|Filter by all" msgstr "全部" @@ -1162,6 +1426,9 @@ msgstr "æ¯æœˆæ‰§è¡Œï¼ˆæ¯æœˆ 1 日凌晨 4 点)" msgid "Every week (Sundays at 4:00am)" msgstr "æ¯å‘¨æ‰§è¡Œï¼ˆå‘¨æ—¥å‡Œæ™¨ 4 点)" +msgid "Expand" +msgstr "" + msgid "Explore projects" msgstr "查看项目" @@ -1180,6 +1447,9 @@ msgstr "二" msgid "February" msgstr "二月" +msgid "Fields on this page are now uneditable, you can configure" +msgstr "" + msgid "File name" msgstr "文件å" @@ -1223,29 +1493,113 @@ msgstr "从åˆå¹¶è¯·æ±‚被åˆå¹¶åŽåˆ°éƒ¨ç½²è‡³ç”Ÿäº§çŽ¯å¢ƒ" msgid "GPG Keys" msgstr "GPG 密钥" +msgid "Generate a default set of labels" +msgstr "" + msgid "Geo Nodes" msgstr "Geo 节点" -msgid "GeoNodeSyncStatus|Failed" -msgstr "失败" - msgid "GeoNodeSyncStatus|Node is failing or broken." msgstr "节点出现故障或æŸå。" msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage." msgstr "节点è¿è¡Œç¼“æ…¢ã€è¶…è½½, 或者在åœæœºåŽåˆšåˆšæ¢å¤ã€‚" -msgid "GeoNodeSyncStatus|Out of sync" -msgstr "未åŒæ¥" +msgid "GeoNodes|Database replication lag:" +msgstr "" -msgid "GeoNodeSyncStatus|Synced" -msgstr "å·²åŒæ¥" +msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?" +msgstr "" + +msgid "GeoNodes|Does not match the primary storage configuration" +msgstr "" + +msgid "GeoNodes|Failed" +msgstr "" + +msgid "GeoNodes|Full" +msgstr "" + +msgid "GeoNodes|GitLab version does not match the primary node version" +msgstr "" + +msgid "GeoNodes|GitLab version:" +msgstr "" + +msgid "GeoNodes|Health status:" +msgstr "" + +msgid "GeoNodes|Last event ID processed by cursor:" +msgstr "" + +msgid "GeoNodes|Last event ID seen from primary:" +msgstr "" + +msgid "GeoNodes|Loading nodes" +msgstr "" + +msgid "GeoNodes|Local Attachments:" +msgstr "" + +msgid "GeoNodes|Local LFS objects:" +msgstr "" + +msgid "GeoNodes|Local job artifacts:" +msgstr "" + +msgid "GeoNodes|New node" +msgstr "" + +msgid "GeoNodes|Out of sync" +msgstr "" + +msgid "GeoNodes|Replication slot WAL:" +msgstr "" + +msgid "GeoNodes|Replication slots:" +msgstr "" + +msgid "GeoNodes|Repositories:" +msgstr "" + +msgid "GeoNodes|Selective" +msgstr "" + +msgid "GeoNodes|Storage config:" +msgstr "" + +msgid "GeoNodes|Sync settings:" +msgstr "" + +msgid "GeoNodes|Synced" +msgstr "" + +msgid "GeoNodes|Unused slots" +msgstr "" + +msgid "GeoNodes|Used slots" +msgstr "" + +msgid "GeoNodes|Wikis:" +msgstr "" + +msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS." +msgstr "" + +msgid "Geo|All projects" +msgstr "" msgid "Geo|File sync capacity" msgstr "文件åŒæ¥é‡" -msgid "Geo|Groups to replicate" -msgstr "å¤åˆ¶ç¾¤ç»„" +msgid "Geo|Groups to synchronize" +msgstr "" + +msgid "Geo|Projects in certain groups" +msgstr "" + +msgid "Geo|Projects in certain storage shards" +msgstr "" msgid "Geo|Repository sync capacity" msgstr "å˜å‚¨åº“åŒæ¥é‡" @@ -1253,12 +1607,24 @@ msgstr "å˜å‚¨åº“åŒæ¥é‡" msgid "Geo|Select groups to replicate." msgstr "选择è¦å¤åˆ¶çš„群组。" +msgid "Geo|Shards to synchronize" +msgstr "" + +msgid "Git revision" +msgstr "" + msgid "Git storage health information has been reset" msgstr "Git å˜å‚¨å¥åº·ä¿¡æ¯å·²é‡ç½®" +msgid "Git version" +msgstr "" + msgid "GitLab Runner section" msgstr "GitLab Runner" +msgid "Gitaly Servers" +msgstr "" + msgid "Go to your fork" msgstr "跳转到派生项目" @@ -1268,6 +1634,9 @@ msgstr "跳转到派生项目" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "Google 身份验è¯ä¸æ˜¯%{link_to_documentation}。如果您想使用æ¤æœåŠ¡ï¼Œè¯·å’¨è¯¢æ‚¨çš„ GitLab 管ç†å‘˜ã€‚" +msgid "Got it!" +msgstr "" + msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" msgstr "ç¦æ¢ä¸Žå…¶ä»–群组共享 %{group} ä¸çš„项目" @@ -1304,8 +1673,8 @@ msgstr "找ä¸åˆ°ç¾¤ç»„" msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group." msgstr "您å¯ä»¥ç®¡ç†ç¾¤ç»„æˆå‘˜çš„æƒé™å¹¶è®¿é—®ç¾¤ç»„ä¸çš„æ¯ä¸ªé¡¹ç›®ã€‚" -msgid "GroupsTree|Are you sure you want to leave the \"${this.group.fullName}\" group?" -msgstr "您确定è¦ç¦»å¼€ç¾¤ç»„“${this.group.fullName}â€å—?" +msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?" +msgstr "" msgid "GroupsTree|Create a project in this group." msgstr "在æ¤ç¾¤ç»„ä¸åˆ›å»ºä¸€ä¸ªé¡¹ç›®ã€‚" @@ -1355,6 +1724,10 @@ msgstr "没有检测到å¥åº·é—®é¢˜" msgid "HealthCheck|Unhealthy" msgstr "éžå¥åº·" +msgid "Hide value" +msgid_plural "Hide values" +msgstr[0] "" + msgid "History" msgstr "历å²" @@ -1380,6 +1753,12 @@ msgid "Instance" msgid_plural "Instances" msgstr[0] "例å" +msgid "Instance does not support multiple Kubernetes clusters" +msgstr "" + +msgid "Interested parties can even contribute by pushing commits if they want to." +msgstr "" + msgid "Internal - The group and any internal projects can be viewed by any logged in user." msgstr "内部 - 任何登录的用户都å¯ä»¥æŸ¥çœ‹è¯¥ç¾¤ç»„和任何内部项目。" @@ -1407,6 +1786,9 @@ msgstr "看æ¿" msgid "Issues" msgstr "议题" +msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable." +msgstr "" + msgid "Jan" msgstr "一" @@ -1425,6 +1807,27 @@ msgstr "å…" msgid "June" msgstr "å…月" +msgid "Kubernetes" +msgstr "" + +msgid "Kubernetes Cluster" +msgstr "" + +msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}" +msgstr "" + +msgid "Kubernetes cluster integration was not removed." +msgstr "" + +msgid "Kubernetes cluster integration was successfully removed." +msgstr "" + +msgid "Kubernetes cluster was successfully updated." +msgstr "" + +msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page" +msgstr "" + msgid "LFSStatus|Disabled" msgstr "åœç”¨" @@ -1434,6 +1837,9 @@ msgstr "å¯ç”¨" msgid "Labels" msgstr "æ ‡ç¾" +msgid "Labels can be applied to issues and merge requests to categorize them." +msgstr "" + msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "最近 %d 天" @@ -1462,6 +1868,9 @@ msgstr "您推é€äº†" msgid "LastPushEvent|at" msgstr "于" +msgid "Learn more" +msgstr "" + msgid "Learn more in the" msgstr "了解更多" @@ -1480,13 +1889,18 @@ msgstr "退出项目" msgid "License" msgstr "许å¯åè®®" -msgid "Limited to showing %d event at most" -msgid_plural "Limited to showing %d events at most" -msgstr[0] "最多显示 %d 个事件" +msgid "Loading the GitLab IDE..." +msgstr "" msgid "Lock" msgstr "é”定" +msgid "Lock %{issuableDisplayName}" +msgstr "" + +msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment." +msgstr "" + msgid "Locked" msgstr "å·²é”定" @@ -1496,12 +1910,21 @@ msgstr "å·²é”定文件" msgid "Login" msgstr "登录" +msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos." +msgstr "" + +msgid "Manage labels" +msgstr "" + msgid "Mar" msgstr "三" msgid "March" msgstr "三月" +msgid "Mark done" +msgstr "" + msgid "Maximum git storage failures" msgstr "最大 git å˜å‚¨å¤±è´¥" @@ -1514,6 +1937,9 @@ msgstr "ä¸ä½æ•°" msgid "Members" msgstr "æˆå‘˜" +msgid "Merge Request" +msgstr "" + msgid "Merge Requests" msgstr "åˆå¹¶è¯·æ±‚" @@ -1523,9 +1949,30 @@ msgstr "åˆå¹¶äº‹ä»¶" msgid "Merge request" msgstr "åˆå¹¶è¯·æ±‚" +msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others" +msgstr "" + +msgid "Merged" +msgstr "" + msgid "Messages" msgstr "消æ¯" +msgid "Milestone" +msgstr "" + +msgid "Milestones|Delete milestone" +msgstr "" + +msgid "Milestones|Delete milestone %{milestoneTitle}?" +msgstr "" + +msgid "Milestones|Failed to delete milestone %{milestoneTitle}" +msgstr "" + +msgid "Milestones|Milestone %{milestoneTitle} was not found" +msgstr "" + msgid "MissingSSHKeyWarningLink|add an SSH key" msgstr "新建 SSH 公钥" @@ -1535,16 +1982,28 @@ msgstr "监控" msgid "More information is available|here" msgstr "帮助文档" +msgid "Move" +msgstr "" + +msgid "Move issue" +msgstr "" + msgid "Multiple issue boards" msgstr "多个议题看æ¿" -msgid "New Cluster" -msgstr "新集群" +msgid "Name new label" +msgstr "" msgid "New Issue" msgid_plural "New Issues" msgstr[0] "新建议题" +msgid "New Kubernetes Cluster" +msgstr "" + +msgid "New Kubernetes cluster" +msgstr "" + msgid "New Pipeline Schedule" msgstr "创建æµæ°´çº¿è®¡åˆ’" @@ -1569,6 +2028,9 @@ msgstr "新群组" msgid "New issue" msgstr "新建议题" +msgid "New label" +msgstr "" + msgid "New merge request" msgstr "新建åˆå¹¶è¯·æ±‚" @@ -1587,8 +2049,23 @@ msgstr "æ–°å群组" msgid "New tag" msgstr "æ–°å»ºæ ‡ç¾" -msgid "No container images stored for this project. Add one by following the instructions above." -msgstr "æ¤é¡¹ç›®å½“å‰æœªå˜å‚¨å®¹å™¨é•œåƒã€‚如需使用,请å‚照上述说明新建容器镜åƒã€‚" +msgid "No assignee" +msgstr "" + +msgid "No changes" +msgstr "" + +msgid "No connection could be made to a Gitaly Server, please check your logs!" +msgstr "" + +msgid "No due date" +msgstr "" + +msgid "No estimate or time spent" +msgstr "" + +msgid "No file chosen" +msgstr "" msgid "No repository" msgstr "没有å˜å‚¨åº“" @@ -1602,9 +2079,15 @@ msgstr "没有花费时间" msgid "None" msgstr "æ— " +msgid "Not allowed to merge" +msgstr "" + msgid "Not available" msgstr "æ•°æ®ä¸è¶³" +msgid "Not confidential" +msgstr "" + msgid "Not enough data" msgstr "æ•°æ®ä¸è¶³" @@ -1665,6 +2148,12 @@ msgstr "关注" msgid "Notifications" msgstr "通知" +msgid "Notifications off" +msgstr "" + +msgid "Notifications on" +msgstr "" + msgid "Nov" msgstr "å一" @@ -1674,8 +2163,8 @@ msgstr "å一月" msgid "Number of access attempts" msgstr "å°è¯•è®¿é—®æ¬¡æ•°" -msgid "Number of failures before backing off" -msgstr "退出å‰çš„失败次数" +msgid "OK" +msgstr "" msgid "Oct" msgstr "å" @@ -1689,6 +2178,9 @@ msgstr "ç›é€‰" msgid "Only project members can comment." msgstr "åªæœ‰é¡¹ç›®æˆå‘˜å¯ä»¥å‘表评论。" +msgid "Open" +msgstr "" + msgid "Opened" msgstr "已打开" @@ -1722,9 +2214,6 @@ msgstr "« 首页" msgid "Password" msgstr "密ç " -msgid "People without permission will never get a notification and won\\'t be able to comment." -msgstr "未ç»è®¸å¯çš„人将永远ä¸ä¼šæ”¶åˆ°é€šçŸ¥å¹¶ä¸”æ— æ³•è¯„è®ºã€‚" - msgid "Pipeline" msgstr "æµæ°´çº¿" @@ -1767,12 +2256,6 @@ msgstr "所有" msgid "PipelineSchedules|Inactive" msgstr "未å¯ç”¨" -msgid "PipelineSchedules|Input variable key" -msgstr "输入å˜é‡å" - -msgid "PipelineSchedules|Input variable value" -msgstr "输入å˜é‡å€¼" - msgid "PipelineSchedules|Next Run" msgstr "下次è¿è¡Œæ—¶é—´" @@ -1782,9 +2265,6 @@ msgstr "æ— " msgid "PipelineSchedules|Provide a short description for this pipeline" msgstr "为æ¤æµæ°´çº¿æ供简çŸæè¿°" -msgid "PipelineSchedules|Remove variable row" -msgstr "åˆ é™¤å˜é‡" - msgid "PipelineSchedules|Take ownership" msgstr "å–得所有æƒ" @@ -1812,6 +2292,12 @@ msgstr "上周的æµæ°´çº¿" msgid "Pipelines for last year" msgstr "去年的æµæ°´çº¿" +msgid "Pipelines|Build with confidence" +msgstr "" + +msgid "Pipelines|Get started with Pipelines" +msgstr "" + msgid "Pipeline|all" msgstr "所有" @@ -1824,12 +2310,21 @@ msgstr "于阶段" msgid "Pipeline|with stages" msgstr "于阶段" +msgid "Play" +msgstr "" + +msgid "Please <a href=%{link_to_billing} target=\"_blank\" rel=\"noopener noreferrer\">enable billing for one of your projects to be able to create a Kubernetes cluster</a>, then try again." +msgstr "" + msgid "Please solve the reCAPTCHA" msgstr "请填写验è¯ç 。" msgid "Preferences" msgstr "å好设置" +msgid "Primary" +msgstr "" + msgid "Private - Project access must be granted explicitly to each user." msgstr "ç§äºº - å¿…é¡»å‘æ¯ä¸ªç”¨æˆ·æ˜Žç¡®æŽˆäºˆé¡¹ç›®è®¿é—®æƒé™ã€‚" @@ -1875,6 +2370,9 @@ msgstr "您的å¸æˆ·ç›®å‰æ˜¯è¿™äº›ç¾¤ç»„的所有者:" msgid "Profiles|your account" msgstr "您的å¸æˆ·" +msgid "Programming languages used in this repository" +msgstr "" + msgid "Project '%{project_name}' is in the process of being deleted." msgstr "项目 “%{project_name}†æ£åœ¨è¢«åˆ 除。" @@ -1890,6 +2388,15 @@ msgstr "项目 '%{project_name}' 已更新完æˆã€‚" msgid "Project access must be granted explicitly to each user." msgstr "项目访问æƒé™å¿…须明确授æƒç»™æ¯ä¸ªç”¨æˆ·ã€‚" +msgid "Project avatar" +msgstr "" + +msgid "Project avatar in repository: %{link}" +msgstr "" + +msgid "Project cache successfully reset." +msgstr "" + msgid "Project details" msgstr "项目详情" @@ -1908,6 +2415,21 @@ msgstr "项目导出已开始。下载链接将通过电å邮件å‘é€ã€‚" msgid "ProjectActivityRSS|Subscribe" msgstr "订阅" +msgid "ProjectCreationLevel|Allowed to create projects" +msgstr "" + +msgid "ProjectCreationLevel|Default project creation protection" +msgstr "" + +msgid "ProjectCreationLevel|Developers + Masters" +msgstr "" + +msgid "ProjectCreationLevel|Masters" +msgstr "" + +msgid "ProjectCreationLevel|No one" +msgstr "" + msgid "ProjectFeature|Disabled" msgstr "åœç”¨" @@ -1932,15 +2454,9 @@ msgstr "分支图" msgid "ProjectSettings|Contact an admin to change this setting." msgstr "è”系管ç†å‘˜æ›´æ”¹æ¤è®¾ç½®ã€‚" -msgid "ProjectSettings|Immediately run a pipeline on the default branch" -msgstr "ç«‹å³åœ¨é»˜è®¤åˆ†æ”¯ä¸Šè¿è¡Œæµæ°´çº¿" - msgid "ProjectSettings|Only signed commits can be pushed to this repository." msgstr "åªæœ‰å·²ç¾ç½²æ交æ‰å¯ä»¥æŽ¨é€åˆ°æ¤å˜å‚¨åº“。" -msgid "ProjectSettings|Problem setting up the CI/CD settings JavaScript" -msgstr "设置CI/CD时出现JavaScript问题" - msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin." msgstr "æ¤è®¾ç½®å·²åº”用于æœåŠ¡å™¨çº§åˆ«ï¼Œå¯ç”±ç®¡ç†å‘˜è¦†ç›–。" @@ -2004,12 +2520,15 @@ msgstr "æ²¡æœ‰ç›‘æµ‹æŒ‡æ ‡ã€‚è¦å¼€å§‹ç›‘测,请部署到环境ä¸ã€‚" msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/" msgstr "Prometheus API 地å€ï¼Œä¾‹å¦‚ http://prometheus.example.com/" -msgid "PrometheusService|Prometheus monitoring" -msgstr "Prometheus 监测" +msgid "PrometheusService|Time-series monitoring service" +msgstr "" msgid "PrometheusService|View environments" msgstr "查看环境" +msgid "Protip:" +msgstr "" + msgid "Public - The group and any public projects can be viewed without any authentication." msgstr "公开 - 群组和任何公共项目å¯ä»¥åœ¨æ²¡æœ‰ä»»ä½•èº«ä»½éªŒè¯çš„情况下查看。" @@ -2025,6 +2544,9 @@ msgstr "推é€äº‹ä»¶" msgid "PushRule|Committer restriction" msgstr "æ交é™åˆ¶" +msgid "Quick actions can be used in the issues description and comment boxes." +msgstr "" + msgid "Read more" msgstr "了解更多" @@ -2037,6 +2559,12 @@ msgstr "分支" msgid "RefSwitcher|Tags" msgstr "æ ‡ç¾" +msgid "Reference:" +msgstr "" + +msgid "Register / Sign In" +msgstr "" + msgid "Registry" msgstr "注册表" @@ -2061,9 +2589,18 @@ msgstr "相关已åˆå¹¶çš„åˆå¹¶è¯·æ±‚" msgid "Remind later" msgstr "ç¨åŽæ醒" +msgid "Remove" +msgstr "" + +msgid "Remove avatar" +msgstr "" + msgid "Remove project" msgstr "åˆ é™¤é¡¹ç›®" +msgid "Repair authentication" +msgstr "" + msgid "Repository" msgstr "å˜å‚¨åº“" @@ -2079,6 +2616,10 @@ msgstr "é‡ç½®å¥åº·æ£€æŸ¥è®¿é—®ä»¤ç‰Œ" msgid "Reset runners registration token" msgstr "é‡ç½® Runner 注册令牌" +msgid "Reveal value" +msgid_plural "Reveal values" +msgstr[0] "" + msgid "Revert this commit" msgstr "还原æ¤æ交" @@ -2088,15 +2629,15 @@ msgstr "还原æ¤åˆå¹¶è¯·æ±‚" msgid "SSH Keys" msgstr "SSH 密钥" -msgid "Save" -msgstr "ä¿å˜" - msgid "Save changes" msgstr "ä¿å˜ä¿®æ”¹" msgid "Save pipeline schedule" msgstr "ä¿å˜æµæ°´çº¿è®¡åˆ’" +msgid "Save variables" +msgstr "" + msgid "Schedule a new pipeline" msgstr "新建æµæ°´çº¿è®¡åˆ’" @@ -2112,38 +2653,59 @@ msgstr "议题看æ¿èŒƒå›´" msgid "Search branches and tags" msgstr "æœç´¢åˆ†æ”¯å’Œæ ‡ç¾" +msgid "Search milestones" +msgstr "" + +msgid "Search project" +msgstr "" + +msgid "Search users" +msgstr "" + msgid "Seconds before reseting failure information" msgstr "é‡ç½®å¤±è´¥ä¿¡æ¯ç‰å¾…时间(秒)" -msgid "Seconds to wait after a storage failure" -msgstr "å˜å‚¨å¤±è´¥åŽç‰å¾…时间(秒)" - msgid "Seconds to wait for a storage access attempt" msgstr "ç‰å¾…å˜å‚¨è®¿é—®å°è¯•æ—¶é—´(秒)" +msgid "Secret variables" +msgstr "" + msgid "Select Archive Format" msgstr "é€‰æ‹©ä¸‹è½½æ ¼å¼" msgid "Select a timezone" msgstr "选择时区" +msgid "Select assignee" +msgstr "" + +msgid "Select branch/tag" +msgstr "" + msgid "Select target branch" msgstr "é€‰æ‹©ç›®æ ‡åˆ†æ”¯" +msgid "Selective synchronization" +msgstr "" + msgid "Sep" msgstr "ä¹" msgid "September" msgstr "ä¹æœˆ" +msgid "Server version" +msgstr "" + msgid "Service Templates" msgstr "æœåŠ¡æ¨¡æ¿" msgid "Set a password on your account to pull or push via %{protocol}." msgstr "为账å·åˆ›å»ºä¸€ä¸ªç”¨äºŽæŽ¨é€æˆ–拉å–çš„ %{protocol} 密ç 。" -msgid "Set up CI" -msgstr "设置 CI" +msgid "Set up CI/CD" +msgstr "" msgid "Set up Koding" msgstr "设置 Koding" @@ -2157,6 +2719,15 @@ msgstr "设置密ç " msgid "Settings" msgstr "设置" +msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero." +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset pipeline minutes" +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes" +msgstr "" + msgid "Show parent pages" msgstr "查看上级页é¢" @@ -2168,10 +2739,7 @@ msgid_plural "Showing %d events" msgstr[0] "显示 %d 个事件" msgid "Sidebar|Change weight" -msgstr "编辑宽度" - -msgid "Sidebar|Edit" -msgstr "编辑" +msgstr "编辑æƒé‡" msgid "Sidebar|No" msgstr "æ— " @@ -2185,18 +2753,30 @@ msgstr "宽度" msgid "Snippets" msgstr "代ç 片段" +msgid "Something went wrong on our end" +msgstr "" + msgid "Something went wrong on our end." msgstr "å‘生了错误。" +msgid "Something went wrong trying to change the confidentiality of this issue" +msgstr "" + msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}" msgstr "è¯•å›¾æ”¹å˜ ${this.issuableDisplayName} çš„é”定状æ€æ—¶å‡ºé”™äº†" +msgid "Something went wrong when toggling the button" +msgstr "" + msgid "Something went wrong while fetching the projects." msgstr "拉å–项目时å‘生错误。" msgid "Something went wrong while fetching the registry list." msgstr "拉å–注册表列表时å‘生错误。" +msgid "Something went wrong. Please try again." +msgstr "" + msgid "Sort by" msgstr "排åº" @@ -2326,12 +2906,12 @@ msgstr "å¯åŠ¨ Runner!" msgid "Stopped" msgstr "å·²åœæ¢" +msgid "Storage" +msgstr "" + msgid "Subgroups" msgstr "å群组" -msgid "Subscribe" -msgstr "订阅" - msgid "Switch branch/tag" msgstr "切æ¢åˆ†æ”¯/æ ‡ç¾" @@ -2426,8 +3006,11 @@ msgstr "谢谢 ! 请ä¸è¦å†æ˜¾ç¤º" msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project." msgstr "GitLab ä¸çš„高级全局æœç´¢åŠŸèƒ½æ˜¯éžå¸¸å¼ºå¤§çš„æœç´¢æœåŠ¡ã€‚您å¯ä»¥æœç´¢å…¶ä»–团队的代ç 以帮助您完善自己项目ä¸çš„代ç 。从而é¿å…创建é‡å¤çš„代ç 和浪费时间。" -msgid "The circuitbreaker backoff threshold should be lower than the failure count threshold" -msgstr "æ–路器关é—阈值应该低于故障计数阈值" +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project" +msgstr "" + +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project." +msgstr "" msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request." msgstr "ç¼–ç 阶段概述了从第一次æ交到创建åˆå¹¶è¯·æ±‚的时间。创建第一个åˆå¹¶è¯·æ±‚åŽï¼Œæ•°æ®å°†è‡ªåŠ¨æ·»åŠ 到æ¤å¤„。" @@ -2441,21 +3024,18 @@ msgstr "æ´¾ç”Ÿå…³ç³»å·²è¢«åˆ é™¤ã€‚" msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage." msgstr "è®®é¢˜é˜¶æ®µæ¦‚è¿°äº†ä»Žåˆ›å»ºè®®é¢˜åˆ°å°†è®®é¢˜æ·»åŠ åˆ°é‡Œç¨‹ç¢‘æˆ–è®®é¢˜çœ‹æ¿æ‰€èŠ±è´¹çš„时间。创建第一个议题åŽï¼Œæ•°æ®å°†è‡ªåŠ¨æ·»åŠ 到æ¤å¤„.。" +msgid "The maximum file size allowed is 200KB." +msgstr "" + msgid "The number of attempts GitLab will make to access a storage." msgstr "GitLab 访问å˜å‚¨çš„次数。" -msgid "The number of failures after which GitLab will start temporarily disabling access to a storage shard on a host" -msgstr "GitLab 将临时ç¦ç”¨å¯¹ä¸»å˜å‚¨åˆ†ç‰‡çš„访问" - msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}." msgstr "GitLab 将完全阻æ¢è®¿é—®å˜å‚¨çš„故障次数。å¯ä»¥åœ¨ç®¡ç†ç•Œé¢%{link_to_health_page}或使用%{api_documentation_link}é‡ç½®æ•…障次数。" msgid "The phase of the development lifecycle." msgstr "项目生命周期ä¸çš„å„个阶段。" -msgid "The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user." -msgstr "æµæ°´çº¿è®¡åˆ’会周期性é‡å¤è¿è¡ŒæŒ‡å®šåˆ†æ”¯æˆ–æ ‡ç¾çš„æµæ°´çº¿ã€‚这些æµæ°´çº¿å°†æ ¹æ®å…¶å…³è”用户继承有é™çš„项目访问æƒé™ã€‚" - msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit." msgstr "è®¡åˆ’é˜¶æ®µæ¦‚è¿°äº†ä»Žè®®é¢˜æ·»åŠ åˆ°æ—¥ç¨‹åˆ°æŽ¨é€é¦–次æ交的时间。当首次推é€æ交åŽï¼Œæ•°æ®å°†è‡ªåŠ¨æ·»åŠ 到æ¤å¤„。" @@ -2486,20 +3066,47 @@ msgstr "GitLab å°†ä¿æŒå¤±è´¥ä¿¡æ¯çš„时间(秒)。在æ¤æœŸé—´ä¸å‘生故障 msgid "The time in seconds GitLab will try to access storage. After this time a timeout error will be raised." msgstr "GitLab å°†å°è¯•è®¿é—®å˜å‚¨çš„时间(秒)。在æ¤æ—¶é—´ä¹‹åŽå°†å¼•å‘超时错误。" +msgid "The time in seconds between storage checks. When a previous check did complete yet, GitLab will skip a check." +msgstr "" + msgid "The time taken by each data entry gathered by that stage." msgstr "该阶段æ¯æ¡æ•°æ®æ‰€èŠ±çš„时间" msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6." msgstr "ä¸ä½æ•°æ˜¯ä¸€ä¸ªæ•°åˆ—ä¸æœ€ä¸é—´çš„值。例如在 3ã€5ã€9 之间,ä¸ä½æ•°æ˜¯ 5。在 3ã€5ã€7ã€8 之间,ä¸ä½æ•°æ˜¯ (5 + 7)/ 2 = 6。" +msgid "There are no issues to show" +msgstr "" + +msgid "There are no merge requests to show" +msgstr "" + msgid "There are problems accessing Git storage: " msgstr "访问 Git å˜å‚¨æ—¶å‡ºçŽ°é—®é¢˜ï¼š" +msgid "There was an error loading users activity calendar." +msgstr "" + +msgid "There was an error saving your notification settings." +msgstr "" + +msgid "There was an error subscribing to this label." +msgstr "" + +msgid "There was an error when reseting email token." +msgstr "" + +msgid "There was an error when subscribing to this label." +msgstr "" + +msgid "There was an error when unsubscribing from this label." +msgstr "" + msgid "This board\\'s scope is reduced" msgstr "这个看æ¿çš„范围缩å°äº†" -msgid "This branch has changed since you started editing. Would you like to create a new branch?" -msgstr "自您开始编辑åŽ, æ¤åˆ†æ”¯å·²æ›´æ”¹ã€‚您想创建一个新的分支å—?" +msgid "This directory" +msgstr "" msgid "This is a confidential issue." msgstr "这是一个机密议题。" @@ -2507,18 +3114,45 @@ msgstr "这是一个机密议题。" msgid "This is the author's first Merge Request to this project." msgstr "这是作者为项目贡献的第一个åˆå¹¶è¯·æ±‚。" +msgid "This issue is confidential" +msgstr "" + msgid "This issue is confidential and locked." msgstr "这个是机密且已é”定的议题。" msgid "This issue is locked." msgstr "æ¤è®®é¢˜å·²é”定。" +msgid "This job depends on a user to trigger its process. Often they are used to deploy code to production environments" +msgstr "" + +msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered" +msgstr "" + +msgid "This job has not been triggered yet" +msgstr "" + +msgid "This job has not started yet" +msgstr "" + +msgid "This job is in pending state and is waiting to be picked by a runner" +msgstr "" + +msgid "This job requires a manual action" +msgstr "" + msgid "This means you can not push code until you create an empty repository or import existing one." msgstr "在创建一个空的å˜å‚¨åº“或导入现有å˜å‚¨åº“之å‰ï¼Œå°†æ— 法推é€ä»£ç 。" msgid "This merge request is locked." msgstr "æ¤åˆå¹¶è¯·æ±‚å·²é”定。" +msgid "This project" +msgstr "" + +msgid "This repository" +msgstr "" + msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here." msgstr "这些电å邮件自动生æˆä¸ºé—®é¢˜(评论生æˆä¸ºç”µå邮件对è¯)在这里列出。" @@ -2531,9 +3165,21 @@ msgstr "开始进行编ç å‰çš„时间" msgid "Time between merge request creation and merge/close" msgstr "从创建åˆå¹¶è¯·æ±‚到被åˆå¹¶æˆ–å…³é—的时间" +msgid "Time tracking" +msgstr "" + msgid "Time until first merge request" msgstr "创建第一个åˆå¹¶è¯·æ±‚之å‰çš„时间" +msgid "TimeTrackingEstimated|Est" +msgstr "" + +msgid "TimeTracking|Estimated:" +msgstr "" + +msgid "TimeTracking|Spent" +msgstr "" + msgid "Timeago|%s days ago" msgstr " %s 天å‰" @@ -2671,6 +3317,18 @@ msgstr "秒" msgid "Title" msgstr "æ ‡é¢˜" +msgid "Todo" +msgstr "" + +msgid "Toggle sidebar" +msgstr "" + +msgid "ToggleButton|Toggle Status: OFF" +msgstr "" + +msgid "ToggleButton|Toggle Status: ON" +msgstr "" + msgid "Total Time" msgstr "总时间" @@ -2686,20 +3344,41 @@ msgstr "跟踪活动与贡献的分æžã€‚" msgid "Track groups of issues that share a theme, across projects and milestones" msgstr "在项目和里程碑之间跟踪共享主题的议题组" +msgid "Total: %{total}" +msgstr "" + +msgid "Track time with quick actions" +msgstr "" + +msgid "Trigger this manual action" +msgstr "" + msgid "Turn on Service Desk" msgstr "打开æœåŠ¡å°" +msgid "Type %{value} to confirm:" +msgstr "" + +msgid "Unable to reset project cache." +msgstr "" + +msgid "Unknown" +msgstr "" + msgid "Unlock" msgstr "解é”" +msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment." +msgstr "" + msgid "Unlocked" msgstr "已解é”" msgid "Unstar" msgstr "å–æ¶ˆæ˜Ÿæ ‡" -msgid "Unsubscribe" -msgstr "退订" +msgid "Up to date" +msgstr "" msgid "Upgrade your plan to activate Advanced Global Search." msgstr "å‡çº§æ‚¨çš„方案以å¯ç”¨é«˜çº§å…¨å±€æœç´¢ã€‚" @@ -2722,6 +3401,9 @@ msgstr "ä¸Šä¼ æ–°æ–‡ä»¶" msgid "Upload file" msgstr "ä¸Šä¼ æ–‡ä»¶" +msgid "Upload new avatar" +msgstr "" + msgid "UploadLink|click to upload" msgstr "ç‚¹å‡»ä¸Šä¼ " @@ -2734,9 +3416,15 @@ msgstr "在安装过程ä¸ä½¿ç”¨ä»¥ä¸‹æ³¨å†Œä»¤ç‰Œï¼š" msgid "Use your global notification setting" msgstr "使用全局通知设置" +msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." +msgstr "" + msgid "View file @ " msgstr "æµè§ˆæ–‡ä»¶ @ " +msgid "View labels" +msgstr "" + msgid "View open merge request" msgstr "查看待处ç†çš„åˆå¹¶è¯·æ±‚" @@ -2758,6 +3446,9 @@ msgstr "未知" msgid "Want to see the data? Please ask an administrator for access." msgstr "æƒé™ä¸è¶³ã€‚如需查看相关数æ®ï¼Œè¯·å‘管ç†å‘˜ç”³è¯·æƒé™ã€‚" +msgid "We could not verify that one of your projects on GCP has billing enabled. Please try again." +msgstr "" + msgid "We don't have enough data to show this stage." msgstr "该阶段的数æ®ä¸è¶³ï¼Œæ— 法显示。" @@ -2770,9 +3461,6 @@ msgstr "如果有新的推é€æˆ–新的议题,Webhook将自动触å‘您设置UR msgid "Weight" msgstr "æƒé‡" -msgid "When access to a storage fails. GitLab will prevent access to the storage for the time specified here. This allows the filesystem to recover. Repositories on failing shards are temporarly unavailable" -msgstr "访问å˜å‚¨å¤±è´¥æ—¶ã€‚ GitLab 将在æ¤å¤„指定的时间内阻æ¢å¯¹å˜å‚¨çš„访问。这å…许文件系统æ¢å¤ã€‚故障分片上的å˜å‚¨åº“æš‚æ—¶æ— æ³•ä½¿ç”¨" - msgid "Wiki" msgstr "Wiki" @@ -2791,6 +3479,12 @@ msgstr "建议安装 %{markdown},以便 GFM 功能在本地渲染:" msgid "WikiClone|Start Gollum and edit locally" msgstr "å¯åŠ¨ Gollum 并在本地编辑" +msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title." +msgstr "" + +msgid "WikiEdit|There is already a page with the same title in that path." +msgstr "" + msgid "WikiEmptyPageError|You are not allowed to create wiki pages" msgstr "您ä¸èƒ½åˆ›å»º wiki 页é¢" @@ -2893,9 +3587,21 @@ msgstr "å³å°†åˆ 除与æºé¡¹ç›® %{forked_from_project} 的派生关系。确定 msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" msgstr "å³å°† %{project_name_with_namespace} 转移给å¦ä¸€ä¸ªæ‰€æœ‰è€…。确定继ç»å—?" +msgid "You can also star a label to make it a priority label." +msgstr "" + +msgid "You can move around the graph by using the arrow keys." +msgstr "" + +msgid "You can move around the graph by using the arrow keys." +msgstr "" + msgid "You can only add files when you are on a branch" msgstr "åªèƒ½åœ¨åˆ†æ”¯ä¸Šæ·»åŠ 文件" +msgid "You can only edit files when you are on a branch" +msgstr "" + msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead." msgstr "您ä¸èƒ½å†™å…¥åªè¯»çš„辅助 GitLab Geo 实例。请改用%{link_to_primary_node}。" @@ -2935,6 +3641,12 @@ msgstr "在账å·ä¸ %{add_ssh_key_link} 之å‰å°†æ— 法通过 SSH 拉å–或推é msgid "You won't be able to pull or push project code via SSH until you add an SSH key to your profile" msgstr "在您的个人资料ä¸æ·»åŠ SSH密钥之å‰ï¼Œæ‚¨ä¸èƒ½é€šè¿‡SSHæ¥æ‹‰å–或推é€é¡¹ç›®ä»£ç 。" +msgid "You'll need to use different branch names to get a valid comparison." +msgstr "" + +msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure" +msgstr "" + msgid "Your comment will not be visible to the public." msgstr "您的评论将ä¸ä¼šå…¬å¼€æ˜¾ç¤ºã€‚" @@ -2947,25 +3659,218 @@ msgstr "您的åå—" msgid "Your projects" msgstr "您的项目" +msgid "assign yourself" +msgstr "" + msgid "branch name" msgstr "分支å称" msgid "by" msgstr "æ¥è‡ª" +msgid "ciReport|Code quality" +msgstr "" + +msgid "ciReport|DAST detected no alerts by analyzing the review app" +msgstr "" + +msgid "ciReport|Failed to load ${type} report" +msgstr "" + +msgid "ciReport|Fixed:" +msgstr "" + +msgid "ciReport|Instances" +msgstr "" + +msgid "ciReport|Learn more about whitelisting" +msgstr "" + +msgid "ciReport|Loading ${type} report" +msgstr "" + +msgid "ciReport|No changes to code quality" +msgstr "" + +msgid "ciReport|No changes to performance metrics" +msgstr "" + +msgid "ciReport|Performance metrics" +msgstr "" + +msgid "ciReport|SAST" +msgstr "" + +msgid "ciReport|SAST detected no security vulnerabilities" +msgstr "" + +msgid "ciReport|SAST:container no vulnerabilities were found" +msgstr "" + +msgid "ciReport|Show complete code vulnerabilities report" +msgstr "" + +msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}" +msgstr "" + msgid "commit" msgstr "æ交" +msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue." +msgstr "" + +msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue." +msgstr "" + msgid "day" msgid_plural "days" msgstr[0] "天" +msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command." +msgstr "" + +msgid "merge request" +msgid_plural "merge requests" +msgstr[0] "" + +msgid "mrWidget|Cancel automatic merge" +msgstr "" + +msgid "mrWidget|Check out branch" +msgstr "" + +msgid "mrWidget|Checking ability to merge automatically" +msgstr "" + +msgid "mrWidget|Cherry-pick" +msgstr "" + +msgid "mrWidget|Cherry-pick this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Closed" +msgstr "" + +msgid "mrWidget|Closed by" +msgstr "" + +msgid "mrWidget|Closes" +msgstr "" + +msgid "mrWidget|Did not close" +msgstr "" + +msgid "mrWidget|Email patches" +msgstr "" + +msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|Mentions" +msgstr "" + +msgid "mrWidget|Merge" +msgstr "" + +msgid "mrWidget|Merge failed." +msgstr "" + +msgid "mrWidget|Merge locally" +msgstr "" + +msgid "mrWidget|Merged by" +msgstr "" + +msgid "mrWidget|Plain diff" +msgstr "" + +msgid "mrWidget|Refresh" +msgstr "" + +msgid "mrWidget|Refresh now" +msgstr "" + +msgid "mrWidget|Refreshing now" +msgstr "" + +msgid "mrWidget|Remove Source Branch" +msgstr "" + +msgid "mrWidget|Remove source branch" +msgstr "" + +msgid "mrWidget|Request to merge" +msgstr "" + +msgid "mrWidget|Resolve conflicts" +msgstr "" + +msgid "mrWidget|Revert" +msgstr "" + +msgid "mrWidget|Revert this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Set by" +msgstr "" + +msgid "mrWidget|The changes were merged into" +msgstr "" + +msgid "mrWidget|The changes were not merged into" +msgstr "" + +msgid "mrWidget|The changes will be merged into" +msgstr "" + +msgid "mrWidget|The source branch has been removed" +msgstr "" + +msgid "mrWidget|The source branch is being removed" +msgstr "" + +msgid "mrWidget|The source branch will be removed" +msgstr "" + +msgid "mrWidget|The source branch will not be removed" +msgstr "" + +msgid "mrWidget|There are merge conflicts" +msgstr "" + +msgid "mrWidget|This merge request failed to be merged automatically" +msgstr "" + +msgid "mrWidget|This merge request is in the process of being merged" +msgstr "" + +msgid "mrWidget|This project is archived, write access has been disabled" +msgstr "" + +msgid "mrWidget|You can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|You can remove source branch now" +msgstr "" + +msgid "mrWidget|command line" +msgstr "" + +msgid "mrWidget|into" +msgstr "" + +msgid "mrWidget|to be merged automatically when the pipeline succeeds" +msgstr "" + msgid "new merge request" msgstr "新建åˆå¹¶è¯·æ±‚" msgid "notification emails" msgstr "通知邮件" +msgid "or" +msgstr "" + msgid "parent" msgid_plural "parents" msgstr[0] "上级" @@ -2976,12 +3881,21 @@ msgstr "密ç " msgid "personal access token" msgstr "个人访问令牌" +msgid "remove due date" +msgstr "" + msgid "source" msgstr "æº" +msgid "spendCommand|%{slash_command} will update the sum of the time spent." +msgstr "" + msgid "to help your contributors communicate effectively!" msgstr "帮助您的贡献者进行有效沟通ï¼" msgid "username" msgstr "用户å" +msgid "uses Kubernetes clusters to deploy your code!" +msgstr "" + diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po index b368487ac71..c79a46c93f7 100644 --- a/locale/zh_HK/gitlab.po +++ b/locale/zh_HK/gitlab.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab-ee\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-12-12 18:31+0000\n" -"PO-Revision-Date: 2018-01-05 04:42-0500\n" +"POT-Creation-Date: 2018-02-07 11:38-0600\n" +"PO-Revision-Date: 2018-02-12 03:58-0500\n" "Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n" "Language-Team: Chinese Traditional, Hong Kong\n" "Language: zh_HK\n" @@ -16,20 +16,35 @@ msgstr "" "X-Crowdin-Language: zh-HK\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" +msgid " and" +msgstr "" + msgid "%d commit" msgid_plural "%d commits" msgstr[0] " %d 次æ交" +msgid "%d commit behind" +msgid_plural "%d commits behind" +msgstr[0] "" + +msgid "%d issue" +msgid_plural "%d issues" +msgstr[0] "" + msgid "%d layer" msgid_plural "%d layers" msgstr[0] "" +msgid "%d merge request" +msgid_plural "%d merge requests" +msgstr[0] "" + msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "%s additional commits have been omitted to prevent performance issues." msgstr[0] "為æ高é é¢åŠ 載速度åŠæ€§èƒ½ï¼Œå·²çœç•¥äº† %s 次æ交。" -msgid "%{commit_author_link} committed %{commit_timeago}" -msgstr "ç”± %{commit_author_link} æ交於 %{commit_timeago}" +msgid "%{commit_author_link} authored %{commit_timeago}" +msgstr "" msgid "%{count} participant" msgid_plural "%{count} participants" @@ -41,9 +56,6 @@ msgstr "" msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will allow access on the next attempt." msgstr "已失敗 %{number_of_failures} 次,最大失敗 %{maximum_failures} 次,GitLab å°‡é‡è©¦ã€‚" -msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will block access for %{number_of_seconds} seconds." -msgstr "已失敗 %{number_of_failures} 次,最大失敗 %{maximum_failures} 次,GitLab 將在 %{number_of_seconds} 秒後é‡è©¦ã€‚" - msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved." msgstr "已失敗 %{number_of_failures} 次,最大失敗 %{maximum_failures} 次,GitLabä¸æœƒé‡è©¦ã€‚當å•é¡Œè§£æ±ºæ™‚é‡ç½®å˜å„²ä¿¡æ¯ã€‚" @@ -115,24 +127,81 @@ msgstr "æ·»åŠ è¨±å¯è‰" msgid "Add new directory" msgstr "æ·»åŠ æ–°ç›®éŒ„" +msgid "Add todo" +msgstr "" + +msgid "AdminArea|Stop all jobs" +msgstr "" + +msgid "AdminArea|Stop all jobs?" +msgstr "" + +msgid "AdminArea|Stop jobs" +msgstr "" + +msgid "AdminArea|Stopping jobs failed" +msgstr "" + +msgid "AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running." +msgstr "" + msgid "AdminHealthPageLink|health page" msgstr "" +msgid "Advanced" +msgstr "" + msgid "Advanced settings" msgstr "" msgid "All" msgstr "全部" +msgid "All changes are committed" +msgstr "" + +msgid "Allows you to add and manage Kubernetes clusters." +msgstr "" + +msgid "An error occurred previewing the blob" +msgstr "" + msgid "An error occurred when toggling the notification subscription" msgstr "" msgid "An error occurred when updating the issue weight" msgstr "" +msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." +msgstr "" + +msgid "An error occurred while fetching markdown preview" +msgstr "" + msgid "An error occurred while fetching sidebar data" msgstr "" +msgid "An error occurred while getting projects" +msgstr "" + +msgid "An error occurred while loading filenames" +msgstr "" + +msgid "An error occurred while rendering KaTeX" +msgstr "" + +msgid "An error occurred while rendering preview broadcast message" +msgstr "" + +msgid "An error occurred while retrieving calendar activity" +msgstr "" + +msgid "An error occurred while retrieving diff" +msgstr "" + +msgid "An error occurred while validating username" +msgstr "" + msgid "An error occurred. Please try again." msgstr "" @@ -157,9 +226,6 @@ msgstr "確定è¦åˆªé™¤æ¤æµæ°´ç·šè¨ˆåŠƒå—Žï¼Ÿ" msgid "Are you sure you want to discard your changes?" msgstr "確定è¦æ”¾æ£„修改嗎?" -msgid "Are you sure you want to leave this group?" -msgstr "" - msgid "Are you sure you want to reset registration token?" msgstr "確定è¦é‡ç½®è¨»å†Šä»¤ç‰Œå—Žï¼Ÿ" @@ -172,6 +238,21 @@ msgstr "確定嗎?" msgid "Artifacts" msgstr "" +msgid "Assign custom color like #FF0000" +msgstr "" + +msgid "Assign labels" +msgstr "" + +msgid "Assign milestone" +msgstr "" + +msgid "Assign to" +msgstr "" + +msgid "Assignee" +msgstr "" + msgid "Attach a file by drag & drop or %{upload_link}" msgstr "拖放文件到æ¤è™•æˆ–者 %{upload_link}" @@ -187,13 +268,16 @@ msgstr "" msgid "Author" msgstr "" -msgid "Auto Review Apps and Auto Deploy need a domain name and the %{kubernetes} to work correctly." +msgid "Authors: %{authors}" msgstr "" -msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly." +msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." msgstr "" -msgid "Auto Review Apps and Auto Deploy need the %{kubernetes} to work correctly." +msgid "Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly." +msgstr "" + +msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly." msgstr "" msgid "AutoDevOps|Auto DevOps (Beta)" @@ -217,6 +301,12 @@ msgstr "" msgid "Available" msgstr "" +msgid "Avatar will be removed. Are you sure?" +msgstr "" + +msgid "Average per day: %{average}" +msgstr "" + msgid "Billing" msgstr "" @@ -271,6 +361,9 @@ msgstr "" msgid "BillingPlans|per user" msgstr "" +msgid "Begin with the selected commit" +msgstr "" + msgid "Branch" msgid_plural "Branches" msgstr[0] "分支" @@ -398,8 +491,8 @@ msgstr "作者:" msgid "CI / CD" msgstr "" -msgid "CI configuration" -msgstr "CI é…ç½®" +msgid "CI/CD configuration" +msgstr "" msgid "CICD|Jobs" msgstr "" @@ -410,6 +503,9 @@ msgstr "å–消" msgid "Cancel edit" msgstr "å–消编辑" +msgid "Cannot modify managed Kubernetes cluster" +msgstr "" + msgid "Change Weight" msgstr "" @@ -425,15 +521,24 @@ msgstr "優é¸" msgid "ChangeTypeAction|Revert" msgstr "還原" +msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes." +msgstr "" + msgid "Changelog" msgstr "更新日誌" +msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision." +msgstr "" + msgid "Charts" msgstr "統計圖" msgid "Chat" msgstr "" +msgid "Check interval" +msgstr "" + msgid "Checking %{text} availability…" msgstr "" @@ -446,7 +551,19 @@ msgstr "優é¸æ¤æ交" msgid "Cherry-pick this merge request" msgstr "優é¸æ¤åˆä½µè«‹æ±‚" -msgid "Choose which groups you wish to replicate to this secondary node. Leave blank to replicate all." +msgid "Choose File ..." +msgstr "" + +msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request." +msgstr "" + +msgid "Choose file..." +msgstr "" + +msgid "Choose which groups you wish to synchronize to this secondary node." +msgstr "" + +msgid "Choose which shards you wish to synchronize to this secondary node." msgstr "" msgid "CiStatusLabel|canceled" @@ -503,79 +620,91 @@ msgstr "已跳éŽ" msgid "CiStatus|running" msgstr "é‹è¡Œä¸" -msgid "CircuitBreakerApiLink|circuitbreaker api" +msgid "CiVariables|Input variable key" msgstr "" -msgid "Clone repository" +msgid "CiVariables|Input variable value" msgstr "" -msgid "Close" +msgid "CiVariables|Remove variable row" msgstr "" -msgid "Cluster" +msgid "CiVariable|* (All environments)" msgstr "" -msgid "ClusterIntegration|%{appList} was successfully installed on your cluster" +msgid "CiVariable|All environments" msgstr "" -msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which incur additional costs. See %{pricingLink}" +msgid "CiVariable|Create wildcard" msgstr "" -msgid "ClusterIntegration|API URL" +msgid "CiVariable|Error occured while saving variables" msgstr "" -msgid "ClusterIntegration|Active" +msgid "CiVariable|New environment" msgstr "" -msgid "ClusterIntegration|Add an existing cluster" +msgid "CiVariable|Protected" msgstr "" -msgid "ClusterIntegration|Add cluster" +msgid "CiVariable|Search environments" msgstr "" -msgid "ClusterIntegration|All" +msgid "CiVariable|Toggle protected" msgstr "" -msgid "ClusterIntegration|Applications" +msgid "CiVariable|Validation failed" msgstr "" -msgid "ClusterIntegration|CA Certificate" +msgid "CircuitBreakerApiLink|circuitbreaker api" msgstr "" -msgid "ClusterIntegration|Certificate Authority bundle (PEM format)" +msgid "Click to expand text" +msgstr "" + +msgid "Clone repository" +msgstr "" + +msgid "Close" msgstr "" -msgid "ClusterIntegration|Choose how to set up cluster integration" +msgid "Closed" msgstr "" -msgid "ClusterIntegration|Cluster" +msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Cluster details" +msgid "ClusterIntegration|API URL" msgstr "" -msgid "ClusterIntegration|Cluster integration" +msgid "ClusterIntegration|Add Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Cluster integration is disabled for this project." +msgid "ClusterIntegration|Add an existing Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Cluster integration is enabled for this project." +msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration" msgstr "" -msgid "ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab's connection to it." +msgid "ClusterIntegration|Applications" msgstr "" -msgid "ClusterIntegration|Cluster is being created on Google Kubernetes Engine..." +msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster." msgstr "" -msgid "ClusterIntegration|Cluster name" +msgid "ClusterIntegration|CA Certificate" msgstr "" -msgid "ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine. Refresh the page to see cluster's details" +msgid "ClusterIntegration|Certificate Authority bundle (PEM format)" msgstr "" -msgid "ClusterIntegration|Clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" +msgid "ClusterIntegration|Choose how to set up Kubernetes cluster integration" +msgstr "" + +msgid "ClusterIntegration|Choose which of your project's environments will use this Kubernetes cluster." +msgstr "" + +msgid "ClusterIntegration|Control how your Kubernetes cluster integrates with GitLab" msgstr "" msgid "ClusterIntegration|Copy API URL" @@ -584,37 +713,34 @@ msgstr "" msgid "ClusterIntegration|Copy CA Certificate" msgstr "" -msgid "ClusterIntegration|Copy Token" +msgid "ClusterIntegration|Copy Kubernetes cluster name" msgstr "" -msgid "ClusterIntegration|Copy cluster name" +msgid "ClusterIntegration|Copy Token" msgstr "" -msgid "ClusterIntegration|Create a new cluster on Google Engine right from GitLab" +msgid "ClusterIntegration|Create Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Create cluster" +msgid "ClusterIntegration|Create Kubernetes cluster on Google Kubernetes Engine" msgstr "" -msgid "ClusterIntegration|Create cluster on Google Kubernetes Engine" +msgid "ClusterIntegration|Create a new Kubernetes cluster on Google Kubernetes Engine right from GitLab" msgstr "" msgid "ClusterIntegration|Create on GKE" msgstr "" -msgid "ClusterIntegration|Enable cluster integration" -msgstr "" - msgid "ClusterIntegration|Enter the details for an existing Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Enter the details for your cluster" +msgid "ClusterIntegration|Enter the details for your Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Environment pattern" +msgid "ClusterIntegration|Environment scope" msgstr "" -msgid "ClusterIntegration|GKE pricing" +msgid "ClusterIntegration|GitLab Integration" msgstr "" msgid "ClusterIntegration|GitLab Runner" @@ -632,64 +758,94 @@ msgstr "" msgid "ClusterIntegration|Helm Tiller" msgstr "" -msgid "ClusterIntegration|Inactive" -msgstr "" - msgid "ClusterIntegration|Ingress" msgstr "" msgid "ClusterIntegration|Install" msgstr "" -msgid "ClusterIntegration|Install applications on your cluster. Read more about %{helpLink}" -msgstr "" - msgid "ClusterIntegration|Installed" msgstr "" msgid "ClusterIntegration|Installing" msgstr "" -msgid "ClusterIntegration|Integrate cluster automation" +msgid "ClusterIntegration|Integrate Kubernetes cluster automation" +msgstr "" + +msgid "ClusterIntegration|Integration status" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster details" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is disabled for this project." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project. Disabling this integration will not affect your Kubernetes cluster, it will only temporarily turn off GitLab's connection to it." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster name" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project" msgstr "" msgid "ClusterIntegration|Learn more about %{link_to_documentation}" msgstr "" -msgid "ClusterIntegration|Learn more about Clusters" +msgid "ClusterIntegration|Learn more about Kubernetes" msgstr "" -msgid "ClusterIntegration|Machine type" +msgid "ClusterIntegration|Learn more about environments" msgstr "" -msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create clusters" +msgid "ClusterIntegration|Machine type" msgstr "" -msgid "ClusterIntegration|Manage cluster integration on your GitLab project" +msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters" msgstr "" -msgid "ClusterIntegration|Manage your cluster by visiting %{link_gke}" +msgid "ClusterIntegration|Manage" msgstr "" -msgid "ClusterIntegration|Multiple clusters are available in GitLab Entreprise Edition Premium and Ultimate" +msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}" msgstr "" -msgid "ClusterIntegration|Note:" +msgid "ClusterIntegration|More information" msgstr "" -msgid "ClusterIntegration|Number of nodes" +msgid "ClusterIntegration|Multiple Kubernetes clusters are available in GitLab Enterprise Edition Premium and Ultimate" msgstr "" -msgid "ClusterIntegration|Please enter access information for your cluster. If you need help, you can read our %{link_to_help_page} on clusters" +msgid "ClusterIntegration|Note:" msgstr "" -msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:" +msgid "ClusterIntegration|Number of nodes" msgstr "" -msgid "ClusterIntegration|Problem setting up the cluster" +msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes" msgstr "" -msgid "ClusterIntegration|Problem setting up the clusters list" +msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:" msgstr "" msgid "ClusterIntegration|Project ID" @@ -701,16 +857,19 @@ msgstr "" msgid "ClusterIntegration|Project namespace (optional, unique)" msgstr "" -msgid "ClusterIntegration|Read our %{link_to_help_page} on cluster integration." +msgid "ClusterIntegration|Prometheus" msgstr "" -msgid "ClusterIntegration|Remove cluster integration" +msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration." +msgstr "" + +msgid "ClusterIntegration|Remove Kubernetes cluster integration" msgstr "" msgid "ClusterIntegration|Remove integration" msgstr "" -msgid "ClusterIntegration|Removing cluster integration will remove the cluster configuration you have added to this project. It will not delete your cluster on Google Kubernetes Engine." +msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster." msgstr "" msgid "ClusterIntegration|Request to begin installing failed" @@ -719,7 +878,7 @@ msgstr "" msgid "ClusterIntegration|Save changes" msgstr "" -msgid "ClusterIntegration|See and edit the details for your cluster" +msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster" msgstr "" msgid "ClusterIntegration|See machine types" @@ -740,25 +899,25 @@ msgstr "" msgid "ClusterIntegration|Something went wrong on our end." msgstr "" -msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine" +msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine" msgstr "" msgid "ClusterIntegration|Something went wrong while installing %{title}" msgstr "" -msgid "ClusterIntegration|There are no clusters to show" +msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below" msgstr "" -msgid "ClusterIntegration|This account must have permissions to create a cluster in the %{link_to_container_project} specified below" +msgid "ClusterIntegration|Toggle Kubernetes Cluster" msgstr "" -msgid "ClusterIntegration|Toggle Cluster" +msgid "ClusterIntegration|Toggle Kubernetes cluster" msgstr "" msgid "ClusterIntegration|Token" msgstr "" -msgid "ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." +msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." msgstr "" msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}" @@ -770,7 +929,7 @@ msgstr "" msgid "ClusterIntegration|access to Google Kubernetes Engine" msgstr "" -msgid "ClusterIntegration|cluster" +msgid "ClusterIntegration|check the pricing here" msgstr "" msgid "ClusterIntegration|documentation" @@ -788,6 +947,9 @@ msgstr "" msgid "ClusterIntegration|properly configured" msgstr "" +msgid "Collapse" +msgstr "" + msgid "Comments" msgstr "è©•è«–" @@ -804,6 +966,9 @@ msgstr "最近30次æ交花費的時間(分é˜ï¼‰" msgid "Commit message" msgstr "æ交信æ¯" +msgid "Commit statistics for %{ref} %{start_time} - %{end_time}" +msgstr "" + msgid "CommitBoxTitle|Commit" msgstr "æ交" @@ -816,15 +981,57 @@ msgstr "æ交" msgid "Commits feed" msgstr "æ交動態" +msgid "Commits per day hour (UTC)" +msgstr "" + +msgid "Commits per day of month" +msgstr "" + +msgid "Commits per weekday" +msgstr "" + +msgid "Commits|An error occurred while fetching merge requests data." +msgstr "" + +msgid "Commits|Commit: %{commitText}" +msgstr "" + msgid "Commits|History" msgstr "æ·å²" +msgid "Commits|No related merge requests found" +msgstr "" + msgid "Committed by" msgstr "æ交者:" msgid "Compare" msgstr "比較" +msgid "Compare Git revisions" +msgstr "" + +msgid "Compare Revisions" +msgstr "" + +msgid "CompareBranches|%{source_branch} and %{target_branch} are the same." +msgstr "" + +msgid "CompareBranches|Compare" +msgstr "" + +msgid "CompareBranches|Source" +msgstr "" + +msgid "CompareBranches|Target" +msgstr "" + +msgid "CompareBranches|There isn't anything to compare." +msgstr "" + +msgid "Confidentiality" +msgstr "" + msgid "Container Registry" msgstr "" @@ -876,6 +1083,9 @@ msgstr "è²¢ç»æŒ‡å—" msgid "Contributors" msgstr "è²¢ç»è€…" +msgid "ContributorsPage|%{startDate} – %{endDate}" +msgstr "" + msgid "ContributorsPage|Building repository graph." msgstr "" @@ -897,9 +1107,18 @@ msgstr "" msgid "Copy URL to clipboard" msgstr "複製URL到剪貼æ¿" +msgid "Copy branch name to clipboard" +msgstr "" + msgid "Copy commit SHA to clipboard" msgstr "複製æ交 SHA 到剪貼æ¿" +msgid "Copy reference to clipboard" +msgstr "" + +msgid "Create" +msgstr "" + msgid "Create New Directory" msgstr "創建新目錄" @@ -918,6 +1137,9 @@ msgstr "" msgid "Create file" msgstr "" +msgid "Create lists from labels. Issues with that label appear in that list." +msgstr "" + msgid "Create merge request" msgstr "創建åˆä½µè«‹æ±‚" @@ -930,6 +1152,9 @@ msgstr "" msgid "Create new file" msgstr "" +msgid "Create new label" +msgstr "" + msgid "Create new..." msgstr "創建..." @@ -951,6 +1176,9 @@ msgstr "Cron 時å€" msgid "Cron syntax" msgstr "Cron 語法" +msgid "Current node" +msgstr "" + msgid "Custom notification events" msgstr "自定義通知事件" @@ -960,9 +1188,6 @@ msgstr "自定義通知級別繼承自åƒèˆ‡ç´šåˆ¥ã€‚使用自定義通知級別 msgid "Cycle Analytics" msgstr "週期分æž" -msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project." -msgstr "週期分æžæ¦‚è¿°äº†é …ç›®å¾žæƒ³æ³•åˆ°ç”¢å“實ç¾çš„å„階段所需的時間。" - msgid "CycleAnalyticsStage|Code" msgstr "編碼" @@ -1018,12 +1243,21 @@ msgstr "" msgid "Details" msgstr "詳情" +msgid "Diffs|No file name available" +msgstr "" + msgid "Directory name" msgstr "目錄å稱" +msgid "Disable" +msgstr "" + msgid "Discard changes" msgstr "放棄更改" +msgid "Discover GitLab Geo." +msgstr "" + msgid "Dismiss Cycle Analytics introduction box" msgstr "" @@ -1060,15 +1294,24 @@ msgstr "差異文件" msgid "DownloadSource|Download" msgstr "下載" +msgid "Due date" +msgstr "" + msgid "Edit" msgstr "編輯" msgid "Edit Pipeline Schedule %{id}" msgstr "編輯 %{id} æµæ°´ç·šè¨ˆåŠƒ" +msgid "Edit files in the editor and commit changes here" +msgstr "" + msgid "Emails" msgstr "" +msgid "Enable" +msgstr "" + msgid "Environments|An error occurred while fetching the environments." msgstr "" @@ -1087,9 +1330,6 @@ msgstr "" msgid "Environments|Environments" msgstr "" -msgid "Environments|Environments are places where code gets deployed, such as staging or production." -msgstr "" - msgid "Environments|Job" msgstr "" @@ -1132,9 +1372,33 @@ msgstr "" msgid "Error creating epic" msgstr "" +msgid "Error fetching contributors data." +msgstr "" + +msgid "Error fetching labels." +msgstr "" + +msgid "Error fetching network graph." +msgstr "" + +msgid "Error fetching refs" +msgstr "" + +msgid "Error fetching usage ping data." +msgstr "" + msgid "Error occurred when toggling the notification subscription" msgstr "" +msgid "Error saving label update." +msgstr "" + +msgid "Error updating status for all todos." +msgstr "" + +msgid "Error updating todo status." +msgstr "" + msgid "EventFilterBy|Filter by all" msgstr "全部" @@ -1162,6 +1426,9 @@ msgstr "æ¯æœˆåŸ·è¡Œï¼ˆæ¯æœˆ 1 日淩晨 4 點)" msgid "Every week (Sundays at 4:00am)" msgstr "æ¯é€±åŸ·è¡Œï¼ˆå‘¨æ—¥æ·©æ™¨ 4 點)" +msgid "Expand" +msgstr "" + msgid "Explore projects" msgstr "" @@ -1180,6 +1447,9 @@ msgstr "" msgid "February" msgstr "" +msgid "Fields on this page are now uneditable, you can configure" +msgstr "" + msgid "File name" msgstr "" @@ -1223,10 +1493,10 @@ msgstr "從åˆä½µè«‹æ±‚çš„åˆä½µåˆ°éƒ¨ç½²è‡³ç”Ÿç”¢ç’°å¢ƒ" msgid "GPG Keys" msgstr "" -msgid "Geo Nodes" +msgid "Generate a default set of labels" msgstr "" -msgid "GeoNodeSyncStatus|Failed" +msgid "Geo Nodes" msgstr "" msgid "GeoNodeSyncStatus|Node is failing or broken." @@ -1235,16 +1505,100 @@ msgstr "" msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage." msgstr "" -msgid "GeoNodeSyncStatus|Out of sync" +msgid "GeoNodes|Database replication lag:" +msgstr "" + +msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?" +msgstr "" + +msgid "GeoNodes|Does not match the primary storage configuration" +msgstr "" + +msgid "GeoNodes|Failed" +msgstr "" + +msgid "GeoNodes|Full" +msgstr "" + +msgid "GeoNodes|GitLab version does not match the primary node version" +msgstr "" + +msgid "GeoNodes|GitLab version:" +msgstr "" + +msgid "GeoNodes|Health status:" +msgstr "" + +msgid "GeoNodes|Last event ID processed by cursor:" +msgstr "" + +msgid "GeoNodes|Last event ID seen from primary:" +msgstr "" + +msgid "GeoNodes|Loading nodes" +msgstr "" + +msgid "GeoNodes|Local Attachments:" +msgstr "" + +msgid "GeoNodes|Local LFS objects:" +msgstr "" + +msgid "GeoNodes|Local job artifacts:" +msgstr "" + +msgid "GeoNodes|New node" +msgstr "" + +msgid "GeoNodes|Out of sync" +msgstr "" + +msgid "GeoNodes|Replication slot WAL:" +msgstr "" + +msgid "GeoNodes|Replication slots:" +msgstr "" + +msgid "GeoNodes|Repositories:" +msgstr "" + +msgid "GeoNodes|Selective" +msgstr "" + +msgid "GeoNodes|Storage config:" +msgstr "" + +msgid "GeoNodes|Sync settings:" msgstr "" -msgid "GeoNodeSyncStatus|Synced" +msgid "GeoNodes|Synced" +msgstr "" + +msgid "GeoNodes|Unused slots" +msgstr "" + +msgid "GeoNodes|Used slots" +msgstr "" + +msgid "GeoNodes|Wikis:" +msgstr "" + +msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS." +msgstr "" + +msgid "Geo|All projects" msgstr "" msgid "Geo|File sync capacity" msgstr "" -msgid "Geo|Groups to replicate" +msgid "Geo|Groups to synchronize" +msgstr "" + +msgid "Geo|Projects in certain groups" +msgstr "" + +msgid "Geo|Projects in certain storage shards" msgstr "" msgid "Geo|Repository sync capacity" @@ -1253,12 +1607,24 @@ msgstr "" msgid "Geo|Select groups to replicate." msgstr "" +msgid "Geo|Shards to synchronize" +msgstr "" + +msgid "Git revision" +msgstr "" + msgid "Git storage health information has been reset" msgstr "Git å˜å„²å¥åº·ä¿¡æ¯å·²é‡ç½®" +msgid "Git version" +msgstr "" + msgid "GitLab Runner section" msgstr "GitLab Runner 介紹" +msgid "Gitaly Servers" +msgstr "" + msgid "Go to your fork" msgstr "è·³è½‰åˆ°æ´¾ç”Ÿé …ç›®" @@ -1268,6 +1634,9 @@ msgstr "è·³è½‰åˆ°æ´¾ç”Ÿé …ç›®" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it!" +msgstr "" + msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" msgstr "" @@ -1304,7 +1673,7 @@ msgstr "" msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group." msgstr "" -msgid "GroupsTree|Are you sure you want to leave the \"${this.group.fullName}\" group?" +msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?" msgstr "" msgid "GroupsTree|Create a project in this group." @@ -1355,6 +1724,10 @@ msgstr "沒有檢測到å¥åº·å•é¡Œ" msgid "HealthCheck|Unhealthy" msgstr "ä¸è‰¯" +msgid "Hide value" +msgid_plural "Hide values" +msgstr[0] "" + msgid "History" msgstr "" @@ -1380,6 +1753,12 @@ msgid "Instance" msgid_plural "Instances" msgstr[0] "" +msgid "Instance does not support multiple Kubernetes clusters" +msgstr "" + +msgid "Interested parties can even contribute by pushing commits if they want to." +msgstr "" + msgid "Internal - The group and any internal projects can be viewed by any logged in user." msgstr "" @@ -1407,6 +1786,9 @@ msgstr "" msgid "Issues" msgstr "" +msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable." +msgstr "" + msgid "Jan" msgstr "" @@ -1425,6 +1807,27 @@ msgstr "" msgid "June" msgstr "" +msgid "Kubernetes" +msgstr "" + +msgid "Kubernetes Cluster" +msgstr "" + +msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}" +msgstr "" + +msgid "Kubernetes cluster integration was not removed." +msgstr "" + +msgid "Kubernetes cluster integration was successfully removed." +msgstr "" + +msgid "Kubernetes cluster was successfully updated." +msgstr "" + +msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page" +msgstr "" + msgid "LFSStatus|Disabled" msgstr "åœç”¨" @@ -1434,6 +1837,9 @@ msgstr "啟用" msgid "Labels" msgstr "" +msgid "Labels can be applied to issues and merge requests to categorize them." +msgstr "" + msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "最近 %d 天" @@ -1462,6 +1868,9 @@ msgstr "您推é€äº†" msgid "LastPushEvent|at" msgstr "在" +msgid "Learn more" +msgstr "" + msgid "Learn more in the" msgstr "了解更多" @@ -1480,13 +1889,18 @@ msgstr "é€€å‡ºé …ç›®" msgid "License" msgstr "" -msgid "Limited to showing %d event at most" -msgid_plural "Limited to showing %d events at most" -msgstr[0] "最多顯示 %d 個事件" +msgid "Loading the GitLab IDE..." +msgstr "" msgid "Lock" msgstr "" +msgid "Lock %{issuableDisplayName}" +msgstr "" + +msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment." +msgstr "" + msgid "Locked" msgstr "" @@ -1496,12 +1910,21 @@ msgstr "" msgid "Login" msgstr "" +msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos." +msgstr "" + +msgid "Manage labels" +msgstr "" + msgid "Mar" msgstr "" msgid "March" msgstr "" +msgid "Mark done" +msgstr "" + msgid "Maximum git storage failures" msgstr "" @@ -1514,6 +1937,9 @@ msgstr "ä¸ä½æ•¸" msgid "Members" msgstr "" +msgid "Merge Request" +msgstr "" + msgid "Merge Requests" msgstr "" @@ -1523,9 +1949,30 @@ msgstr "åˆä½µäº‹ä»¶ (merge event)" msgid "Merge request" msgstr "" +msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others" +msgstr "" + +msgid "Merged" +msgstr "" + msgid "Messages" msgstr "" +msgid "Milestone" +msgstr "" + +msgid "Milestones|Delete milestone" +msgstr "" + +msgid "Milestones|Delete milestone %{milestoneTitle}?" +msgstr "" + +msgid "Milestones|Failed to delete milestone %{milestoneTitle}" +msgstr "" + +msgid "Milestones|Milestone %{milestoneTitle} was not found" +msgstr "" + msgid "MissingSSHKeyWarningLink|add an SSH key" msgstr "æ·»åŠ å£¹å€‹ SSH 公鑰" @@ -1535,16 +1982,28 @@ msgstr "" msgid "More information is available|here" msgstr "幫助文檔" +msgid "Move" +msgstr "" + +msgid "Move issue" +msgstr "" + msgid "Multiple issue boards" msgstr "" -msgid "New Cluster" +msgid "Name new label" msgstr "" msgid "New Issue" msgid_plural "New Issues" msgstr[0] "新建è°é¡Œ" +msgid "New Kubernetes Cluster" +msgstr "" + +msgid "New Kubernetes cluster" +msgstr "" + msgid "New Pipeline Schedule" msgstr "創建æµæ°´ç·šè¨ˆåŠƒ" @@ -1569,6 +2028,9 @@ msgstr "" msgid "New issue" msgstr "æ–°è°é¡Œ" +msgid "New label" +msgstr "" + msgid "New merge request" msgstr "新增åˆä½µè«‹æ±‚" @@ -1587,7 +2049,22 @@ msgstr "" msgid "New tag" msgstr "新增標籤" -msgid "No container images stored for this project. Add one by following the instructions above." +msgid "No assignee" +msgstr "" + +msgid "No changes" +msgstr "" + +msgid "No connection could be made to a Gitaly Server, please check your logs!" +msgstr "" + +msgid "No due date" +msgstr "" + +msgid "No estimate or time spent" +msgstr "" + +msgid "No file chosen" msgstr "" msgid "No repository" @@ -1602,9 +2079,15 @@ msgstr "" msgid "None" msgstr "" +msgid "Not allowed to merge" +msgstr "" + msgid "Not available" msgstr "ä¸å¯ç”¨" +msgid "Not confidential" +msgstr "" + msgid "Not enough data" msgstr "數據ä¸è¶³" @@ -1665,6 +2148,12 @@ msgstr "關注" msgid "Notifications" msgstr "" +msgid "Notifications off" +msgstr "" + +msgid "Notifications on" +msgstr "" + msgid "Nov" msgstr "" @@ -1674,7 +2163,7 @@ msgstr "" msgid "Number of access attempts" msgstr "" -msgid "Number of failures before backing off" +msgid "OK" msgstr "" msgid "Oct" @@ -1689,6 +2178,9 @@ msgstr "篩é¸" msgid "Only project members can comment." msgstr "" +msgid "Open" +msgstr "" + msgid "Opened" msgstr "" @@ -1722,9 +2214,6 @@ msgstr "" msgid "Password" msgstr "" -msgid "People without permission will never get a notification and won\\'t be able to comment." -msgstr "" - msgid "Pipeline" msgstr "æµæ°´ç·š" @@ -1767,12 +2256,6 @@ msgstr "所有" msgid "PipelineSchedules|Inactive" msgstr "未啟用" -msgid "PipelineSchedules|Input variable key" -msgstr "輸入變é‡å" - -msgid "PipelineSchedules|Input variable value" -msgstr "輸入變é‡å€¼" - msgid "PipelineSchedules|Next Run" msgstr "下次é‹è¡Œæ™‚é–“" @@ -1782,9 +2265,6 @@ msgstr "ç„¡" msgid "PipelineSchedules|Provide a short description for this pipeline" msgstr "為æ¤æµæ°´ç·šæ供簡çŸæè¿°" -msgid "PipelineSchedules|Remove variable row" -msgstr "刪除變é‡" - msgid "PipelineSchedules|Take ownership" msgstr "å–得所有權" @@ -1812,6 +2292,12 @@ msgstr "" msgid "Pipelines for last year" msgstr "" +msgid "Pipelines|Build with confidence" +msgstr "" + +msgid "Pipelines|Get started with Pipelines" +msgstr "" + msgid "Pipeline|all" msgstr "所有" @@ -1824,12 +2310,21 @@ msgstr "於階段" msgid "Pipeline|with stages" msgstr "於階段" +msgid "Play" +msgstr "" + +msgid "Please <a href=%{link_to_billing} target=\"_blank\" rel=\"noopener noreferrer\">enable billing for one of your projects to be able to create a Kubernetes cluster</a>, then try again." +msgstr "" + msgid "Please solve the reCAPTCHA" msgstr "" msgid "Preferences" msgstr "" +msgid "Primary" +msgstr "" + msgid "Private - Project access must be granted explicitly to each user." msgstr "" @@ -1875,6 +2370,9 @@ msgstr "" msgid "Profiles|your account" msgstr "" +msgid "Programming languages used in this repository" +msgstr "" + msgid "Project '%{project_name}' is in the process of being deleted." msgstr "" @@ -1890,6 +2388,15 @@ msgstr "é …ç›® '%{project_name}' 已更新完æˆã€‚" msgid "Project access must be granted explicitly to each user." msgstr "é …ç›®è¨ªå•æ¬Šé™å¿…é ˆæ˜Žç¢ºæŽˆæ¬Šçµ¦æ¯å€‹ç”¨æˆ¶ã€‚" +msgid "Project avatar" +msgstr "" + +msgid "Project avatar in repository: %{link}" +msgstr "" + +msgid "Project cache successfully reset." +msgstr "" + msgid "Project details" msgstr "專案詳情" @@ -1908,6 +2415,21 @@ msgstr "é …ç›®å°Žå‡ºå·²é–‹å§‹ã€‚ä¸‹è¼‰éˆæŽ¥å°‡é€šéŽé›»å郵件發é€ã€‚" msgid "ProjectActivityRSS|Subscribe" msgstr "訂閱" +msgid "ProjectCreationLevel|Allowed to create projects" +msgstr "" + +msgid "ProjectCreationLevel|Default project creation protection" +msgstr "" + +msgid "ProjectCreationLevel|Developers + Masters" +msgstr "" + +msgid "ProjectCreationLevel|Masters" +msgstr "" + +msgid "ProjectCreationLevel|No one" +msgstr "" + msgid "ProjectFeature|Disabled" msgstr "åœç”¨" @@ -1932,15 +2454,9 @@ msgstr "分支圖" msgid "ProjectSettings|Contact an admin to change this setting." msgstr "" -msgid "ProjectSettings|Immediately run a pipeline on the default branch" -msgstr "" - msgid "ProjectSettings|Only signed commits can be pushed to this repository." msgstr "" -msgid "ProjectSettings|Problem setting up the CI/CD settings JavaScript" -msgstr "" - msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin." msgstr "" @@ -2004,12 +2520,15 @@ msgstr "" msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/" msgstr "" -msgid "PrometheusService|Prometheus monitoring" +msgid "PrometheusService|Time-series monitoring service" msgstr "" msgid "PrometheusService|View environments" msgstr "" +msgid "Protip:" +msgstr "" + msgid "Public - The group and any public projects can be viewed without any authentication." msgstr "" @@ -2025,6 +2544,9 @@ msgstr "推é€äº‹ä»¶ (push event) " msgid "PushRule|Committer restriction" msgstr "" +msgid "Quick actions can be used in the issues description and comment boxes." +msgstr "" + msgid "Read more" msgstr "了解更多" @@ -2037,6 +2559,12 @@ msgstr "分支" msgid "RefSwitcher|Tags" msgstr "標籤" +msgid "Reference:" +msgstr "" + +msgid "Register / Sign In" +msgstr "" + msgid "Registry" msgstr "" @@ -2061,9 +2589,18 @@ msgstr "相關已åˆä½µçš„åˆä½µè«‹æ±‚" msgid "Remind later" msgstr "ç¨å¾Œæ醒" +msgid "Remove" +msgstr "" + +msgid "Remove avatar" +msgstr "" + msgid "Remove project" msgstr "åˆªé™¤é …ç›®" +msgid "Repair authentication" +msgstr "" + msgid "Repository" msgstr "å˜å„²åº«" @@ -2079,6 +2616,10 @@ msgstr "é‡ç½®å¥åº·æª¢æŸ¥è¨ªå•ä»¤ç‰Œ" msgid "Reset runners registration token" msgstr "é‡ç½® Runner 註冊令牌" +msgid "Reveal value" +msgid_plural "Reveal values" +msgstr[0] "" + msgid "Revert this commit" msgstr "還原æ¤æ交" @@ -2088,15 +2629,15 @@ msgstr "還原æ¤åˆä½µè«‹æ±‚" msgid "SSH Keys" msgstr "" -msgid "Save" -msgstr "" - msgid "Save changes" msgstr "" msgid "Save pipeline schedule" msgstr "ä¿å˜æµæ°´ç·šè¨ˆåŠƒ" +msgid "Save variables" +msgstr "" + msgid "Schedule a new pipeline" msgstr "新建æµæ°´ç·šè¨ˆåŠƒ" @@ -2112,38 +2653,59 @@ msgstr "" msgid "Search branches and tags" msgstr "æœç´¢åˆ†æ”¯å’Œæ¨™ç±¤" -msgid "Seconds before reseting failure information" +msgid "Search milestones" msgstr "" -msgid "Seconds to wait after a storage failure" +msgid "Search project" +msgstr "" + +msgid "Search users" +msgstr "" + +msgid "Seconds before reseting failure information" msgstr "" msgid "Seconds to wait for a storage access attempt" msgstr "" +msgid "Secret variables" +msgstr "" + msgid "Select Archive Format" msgstr "é¸æ“‡ä¸‹è¼‰æ ¼å¼" msgid "Select a timezone" msgstr "é¸æ“‡æ™‚å€" +msgid "Select assignee" +msgstr "" + +msgid "Select branch/tag" +msgstr "" + msgid "Select target branch" msgstr "é¸æ“‡ç›®æ¨™åˆ†æ”¯" +msgid "Selective synchronization" +msgstr "" + msgid "Sep" msgstr "" msgid "September" msgstr "" +msgid "Server version" +msgstr "" + msgid "Service Templates" msgstr "" msgid "Set a password on your account to pull or push via %{protocol}." msgstr "ç‚ºè³¬è™Ÿæ·»åŠ å£¹å€‹ç”¨æ–¼æŽ¨é€æˆ–拉å–çš„ %{protocol} 密碼。" -msgid "Set up CI" -msgstr "è¨ç½® CI" +msgid "Set up CI/CD" +msgstr "" msgid "Set up Koding" msgstr "è¨ç½® Koding" @@ -2157,6 +2719,15 @@ msgstr "è¨ç½®å¯†ç¢¼" msgid "Settings" msgstr "" +msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero." +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset pipeline minutes" +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes" +msgstr "" + msgid "Show parent pages" msgstr "" @@ -2170,9 +2741,6 @@ msgstr[0] "顯示 %d 個事件" msgid "Sidebar|Change weight" msgstr "" -msgid "Sidebar|Edit" -msgstr "" - msgid "Sidebar|No" msgstr "" @@ -2185,18 +2753,30 @@ msgstr "" msgid "Snippets" msgstr "" +msgid "Something went wrong on our end" +msgstr "" + msgid "Something went wrong on our end." msgstr "" +msgid "Something went wrong trying to change the confidentiality of this issue" +msgstr "" + msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}" msgstr "" +msgid "Something went wrong when toggling the button" +msgstr "" + msgid "Something went wrong while fetching the projects." msgstr "" msgid "Something went wrong while fetching the registry list." msgstr "" +msgid "Something went wrong. Please try again." +msgstr "" + msgid "Sort by" msgstr "" @@ -2326,10 +2906,10 @@ msgstr "é‹ä½œ Runner!" msgid "Stopped" msgstr "" -msgid "Subgroups" +msgid "Storage" msgstr "" -msgid "Subscribe" +msgid "Subgroups" msgstr "" msgid "Switch branch/tag" @@ -2426,7 +3006,10 @@ msgstr "" msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project." msgstr "" -msgid "The circuitbreaker backoff threshold should be lower than the failure count threshold" +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project" +msgstr "" + +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project." msgstr "" msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request." @@ -2441,10 +3024,10 @@ msgstr "派生關係已被刪除。" msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage." msgstr "è°é¡ŒéšŽæ®µæ¦‚述了從創建è°é¡Œåˆ°å°‡è°é¡Œæ·»åŠ 到è£ç¨‹ç¢‘或è°é¡Œçœ‹æ¿æ‰€èŠ±è²»çš„時間。創建第壹個è°é¡Œå¾Œï¼Œæ•¸æ“šå°‡è‡ªå‹•æ·»åŠ 到æ¤è™•.。" -msgid "The number of attempts GitLab will make to access a storage." +msgid "The maximum file size allowed is 200KB." msgstr "" -msgid "The number of failures after which GitLab will start temporarily disabling access to a storage shard on a host" +msgid "The number of attempts GitLab will make to access a storage." msgstr "" msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}." @@ -2453,9 +3036,6 @@ msgstr "" msgid "The phase of the development lifecycle." msgstr "é …ç›®ç”Ÿå‘½é€±æœŸä¸çš„å„個階段。" -msgid "The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user." -msgstr "æµæ°´ç·šè¨ˆåŠƒæœƒé€±æœŸæ€§é‡è¤‡é‹è¡ŒæŒ‡å®šåˆ†æ”¯æˆ–標籤的æµæ°´ç·šã€‚這些æµæ°´ç·šå°‡æ ¹æ“šå…¶é—œè¯ç”¨æˆ¶ç¹¼æ‰¿æœ‰é™çš„é …ç›®è¨ªå•æ¬Šé™ã€‚" - msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit." msgstr "計劃階段概述了從è°é¡Œæ·»åŠ 到日程到推é€é¦–次æ交的時間。當首次推é€æäº¤å¾Œï¼Œæ•¸æ“šå°‡è‡ªå‹•æ·»åŠ åˆ°æ¤è™•ã€‚" @@ -2486,19 +3066,46 @@ msgstr "" msgid "The time in seconds GitLab will try to access storage. After this time a timeout error will be raised." msgstr "" +msgid "The time in seconds between storage checks. When a previous check did complete yet, GitLab will skip a check." +msgstr "" + msgid "The time taken by each data entry gathered by that stage." msgstr "該階段æ¯æ¢æ•¸æ“šæ‰€èŠ±çš„時間" msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6." msgstr "ä¸ä½æ•¸æ˜¯å£¹å€‹æ•¸åˆ—ä¸æœ€ä¸é–“的值。例如在 3ã€5ã€9 之間,ä¸ä½æ•¸æ˜¯ 5。在 3ã€5ã€7ã€8 之間,ä¸ä½æ•¸æ˜¯ (5 + 7)/ 2 = 6。" +msgid "There are no issues to show" +msgstr "" + +msgid "There are no merge requests to show" +msgstr "" + msgid "There are problems accessing Git storage: " msgstr "è¨ªå• Git å˜å„²æ™‚出ç¾å•é¡Œï¼š" +msgid "There was an error loading users activity calendar." +msgstr "" + +msgid "There was an error saving your notification settings." +msgstr "" + +msgid "There was an error subscribing to this label." +msgstr "" + +msgid "There was an error when reseting email token." +msgstr "" + +msgid "There was an error when subscribing to this label." +msgstr "" + +msgid "There was an error when unsubscribing from this label." +msgstr "" + msgid "This board\\'s scope is reduced" msgstr "" -msgid "This branch has changed since you started editing. Would you like to create a new branch?" +msgid "This directory" msgstr "" msgid "This is a confidential issue." @@ -2507,18 +3114,45 @@ msgstr "" msgid "This is the author's first Merge Request to this project." msgstr "" +msgid "This issue is confidential" +msgstr "" + msgid "This issue is confidential and locked." msgstr "" msgid "This issue is locked." msgstr "" +msgid "This job depends on a user to trigger its process. Often they are used to deploy code to production environments" +msgstr "" + +msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered" +msgstr "" + +msgid "This job has not been triggered yet" +msgstr "" + +msgid "This job has not started yet" +msgstr "" + +msgid "This job is in pending state and is waiting to be picked by a runner" +msgstr "" + +msgid "This job requires a manual action" +msgstr "" + msgid "This means you can not push code until you create an empty repository or import existing one." msgstr "在創建壹個空的å˜å„²åº«æˆ–å°Žå…¥ç¾æœ‰å˜å„²åº«ä¹‹å‰ï¼Œæ‚¨å°‡ç„¡æ³•æŽ¨é€ä»£ç¢¼ã€‚" msgid "This merge request is locked." msgstr "" +msgid "This project" +msgstr "" + +msgid "This repository" +msgstr "" + msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here." msgstr "" @@ -2531,9 +3165,21 @@ msgstr "開始進行編碼å‰çš„時間" msgid "Time between merge request creation and merge/close" msgstr "從創建åˆä½µè«‹æ±‚到被åˆä½µæˆ–關閉的時間" +msgid "Time tracking" +msgstr "" + msgid "Time until first merge request" msgstr "創建第壹個åˆä½µè«‹æ±‚之å‰çš„時間" +msgid "TimeTrackingEstimated|Est" +msgstr "" + +msgid "TimeTracking|Estimated:" +msgstr "" + +msgid "TimeTracking|Spent" +msgstr "" + msgid "Timeago|%s days ago" msgstr " %s 天å‰" @@ -2671,6 +3317,18 @@ msgstr "秒" msgid "Title" msgstr "" +msgid "Todo" +msgstr "" + +msgid "Toggle sidebar" +msgstr "" + +msgid "ToggleButton|Toggle Status: OFF" +msgstr "" + +msgid "ToggleButton|Toggle Status: ON" +msgstr "" + msgid "Total Time" msgstr "總時間" @@ -2686,19 +3344,40 @@ msgstr "" msgid "Track groups of issues that share a theme, across projects and milestones" msgstr "" +msgid "Total: %{total}" +msgstr "" + +msgid "Track time with quick actions" +msgstr "" + +msgid "Trigger this manual action" +msgstr "" + msgid "Turn on Service Desk" msgstr "" +msgid "Type %{value} to confirm:" +msgstr "" + +msgid "Unable to reset project cache." +msgstr "" + +msgid "Unknown" +msgstr "" + msgid "Unlock" msgstr "" +msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment." +msgstr "" + msgid "Unlocked" msgstr "" msgid "Unstar" msgstr "å–消星標" -msgid "Unsubscribe" +msgid "Up to date" msgstr "" msgid "Upgrade your plan to activate Advanced Global Search." @@ -2722,6 +3401,9 @@ msgstr "上傳新文件" msgid "Upload file" msgstr "上傳文件" +msgid "Upload new avatar" +msgstr "" + msgid "UploadLink|click to upload" msgstr "點擊上傳" @@ -2734,9 +3416,15 @@ msgstr "在安è£éŽç¨‹ä¸ä½¿ç”¨ä»¥ä¸‹è¨»å†Šä»¤ç‰Œï¼š" msgid "Use your global notification setting" msgstr "使用全局通知è¨ç½®" +msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." +msgstr "" + msgid "View file @ " msgstr "" +msgid "View labels" +msgstr "" + msgid "View open merge request" msgstr "查看開啟的åˆä¸¦è«‹æ±‚" @@ -2758,6 +3446,9 @@ msgstr "未知" msgid "Want to see the data? Please ask an administrator for access." msgstr "權é™ä¸è¶³ã€‚如需查看相關數據,請å‘管ç†å“¡ç”³è«‹æ¬Šé™ã€‚" +msgid "We could not verify that one of your projects on GCP has billing enabled. Please try again." +msgstr "" + msgid "We don't have enough data to show this stage." msgstr "該階段的數據ä¸è¶³ï¼Œç„¡æ³•é¡¯ç¤ºã€‚" @@ -2770,9 +3461,6 @@ msgstr "" msgid "Weight" msgstr "" -msgid "When access to a storage fails. GitLab will prevent access to the storage for the time specified here. This allows the filesystem to recover. Repositories on failing shards are temporarly unavailable" -msgstr "" - msgid "Wiki" msgstr "" @@ -2791,6 +3479,12 @@ msgstr "" msgid "WikiClone|Start Gollum and edit locally" msgstr "" +msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title." +msgstr "" + +msgid "WikiEdit|There is already a page with the same title in that path." +msgstr "" + msgid "WikiEmptyPageError|You are not allowed to create wiki pages" msgstr "" @@ -2893,9 +3587,21 @@ msgstr "å³å°‡åˆªé™¤èˆ‡æºé …ç›® %{forked_from_project} 的派生關系。確定 msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" msgstr "å³å°‡ %{project_name_with_namespace} 轉義給å¦å£¹å€‹æ‰€æœ‰è€…。確定繼續嗎?" +msgid "You can also star a label to make it a priority label." +msgstr "" + +msgid "You can move around the graph by using the arrow keys." +msgstr "" + +msgid "You can move around the graph by using the arrow keys." +msgstr "" + msgid "You can only add files when you are on a branch" msgstr "åªèƒ½åœ¨åˆ†æ”¯ä¸Šæ·»åŠ 文件" +msgid "You can only edit files when you are on a branch" +msgstr "" + msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead." msgstr "" @@ -2935,6 +3641,12 @@ msgstr "åœ¨è³¬è™Ÿä¸ %{add_ssh_key_link} 之å‰å°‡ç„¡æ³•é€šéŽ SSH 拉å–或推é msgid "You won't be able to pull or push project code via SSH until you add an SSH key to your profile" msgstr "" +msgid "You'll need to use different branch names to get a valid comparison." +msgstr "" + +msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure" +msgstr "" + msgid "Your comment will not be visible to the public." msgstr "" @@ -2947,25 +3659,218 @@ msgstr "您的åå—" msgid "Your projects" msgstr "" +msgid "assign yourself" +msgstr "" + msgid "branch name" msgstr "" msgid "by" msgstr "" +msgid "ciReport|Code quality" +msgstr "" + +msgid "ciReport|DAST detected no alerts by analyzing the review app" +msgstr "" + +msgid "ciReport|Failed to load ${type} report" +msgstr "" + +msgid "ciReport|Fixed:" +msgstr "" + +msgid "ciReport|Instances" +msgstr "" + +msgid "ciReport|Learn more about whitelisting" +msgstr "" + +msgid "ciReport|Loading ${type} report" +msgstr "" + +msgid "ciReport|No changes to code quality" +msgstr "" + +msgid "ciReport|No changes to performance metrics" +msgstr "" + +msgid "ciReport|Performance metrics" +msgstr "" + +msgid "ciReport|SAST" +msgstr "" + +msgid "ciReport|SAST detected no security vulnerabilities" +msgstr "" + +msgid "ciReport|SAST:container no vulnerabilities were found" +msgstr "" + +msgid "ciReport|Show complete code vulnerabilities report" +msgstr "" + +msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}" +msgstr "" + msgid "commit" msgstr "" +msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue." +msgstr "" + +msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue." +msgstr "" + msgid "day" msgid_plural "days" msgstr[0] "天" +msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command." +msgstr "" + +msgid "merge request" +msgid_plural "merge requests" +msgstr[0] "" + +msgid "mrWidget|Cancel automatic merge" +msgstr "" + +msgid "mrWidget|Check out branch" +msgstr "" + +msgid "mrWidget|Checking ability to merge automatically" +msgstr "" + +msgid "mrWidget|Cherry-pick" +msgstr "" + +msgid "mrWidget|Cherry-pick this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Closed" +msgstr "" + +msgid "mrWidget|Closed by" +msgstr "" + +msgid "mrWidget|Closes" +msgstr "" + +msgid "mrWidget|Did not close" +msgstr "" + +msgid "mrWidget|Email patches" +msgstr "" + +msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|Mentions" +msgstr "" + +msgid "mrWidget|Merge" +msgstr "" + +msgid "mrWidget|Merge failed." +msgstr "" + +msgid "mrWidget|Merge locally" +msgstr "" + +msgid "mrWidget|Merged by" +msgstr "" + +msgid "mrWidget|Plain diff" +msgstr "" + +msgid "mrWidget|Refresh" +msgstr "" + +msgid "mrWidget|Refresh now" +msgstr "" + +msgid "mrWidget|Refreshing now" +msgstr "" + +msgid "mrWidget|Remove Source Branch" +msgstr "" + +msgid "mrWidget|Remove source branch" +msgstr "" + +msgid "mrWidget|Request to merge" +msgstr "" + +msgid "mrWidget|Resolve conflicts" +msgstr "" + +msgid "mrWidget|Revert" +msgstr "" + +msgid "mrWidget|Revert this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Set by" +msgstr "" + +msgid "mrWidget|The changes were merged into" +msgstr "" + +msgid "mrWidget|The changes were not merged into" +msgstr "" + +msgid "mrWidget|The changes will be merged into" +msgstr "" + +msgid "mrWidget|The source branch has been removed" +msgstr "" + +msgid "mrWidget|The source branch is being removed" +msgstr "" + +msgid "mrWidget|The source branch will be removed" +msgstr "" + +msgid "mrWidget|The source branch will not be removed" +msgstr "" + +msgid "mrWidget|There are merge conflicts" +msgstr "" + +msgid "mrWidget|This merge request failed to be merged automatically" +msgstr "" + +msgid "mrWidget|This merge request is in the process of being merged" +msgstr "" + +msgid "mrWidget|This project is archived, write access has been disabled" +msgstr "" + +msgid "mrWidget|You can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|You can remove source branch now" +msgstr "" + +msgid "mrWidget|command line" +msgstr "" + +msgid "mrWidget|into" +msgstr "" + +msgid "mrWidget|to be merged automatically when the pipeline succeeds" +msgstr "" + msgid "new merge request" msgstr "新建åˆä½µè«‹æ±‚" msgid "notification emails" msgstr "通知郵件" +msgid "or" +msgstr "" + msgid "parent" msgid_plural "parents" msgstr[0] "父級" @@ -2976,12 +3881,21 @@ msgstr "" msgid "personal access token" msgstr "" +msgid "remove due date" +msgstr "" + msgid "source" msgstr "" +msgid "spendCommand|%{slash_command} will update the sum of the time spent." +msgstr "" + msgid "to help your contributors communicate effectively!" msgstr "" msgid "username" msgstr "" +msgid "uses Kubernetes clusters to deploy your code!" +msgstr "" + diff --git a/locale/zh_TW/gitlab.po b/locale/zh_TW/gitlab.po index 76c1e598433..635f5c6c449 100644 --- a/locale/zh_TW/gitlab.po +++ b/locale/zh_TW/gitlab.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab-ee\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-12-12 18:31+0000\n" -"PO-Revision-Date: 2018-01-05 04:42-0500\n" +"POT-Creation-Date: 2018-02-07 11:38-0600\n" +"PO-Revision-Date: 2018-02-12 03:58-0500\n" "Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n" "Language-Team: Chinese Traditional\n" "Language: zh_TW\n" @@ -16,20 +16,35 @@ msgstr "" "X-Crowdin-Language: zh-TW\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" +msgid " and" +msgstr "" + msgid "%d commit" msgid_plural "%d commits" msgstr[0] "%d 個更動 (commit)" +msgid "%d commit behind" +msgid_plural "%d commits behind" +msgstr[0] "" + +msgid "%d issue" +msgid_plural "%d issues" +msgstr[0] "" + msgid "%d layer" msgid_plural "%d layers" msgstr[0] "%d 個圖層" +msgid "%d merge request" +msgid_plural "%d merge requests" +msgstr[0] "" + msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "%s additional commits have been omitted to prevent performance issues." msgstr[0] "å› æ•ˆèƒ½è€ƒé‡ï¼Œå·²éš±è— %s 個更動 (commit)。" -msgid "%{commit_author_link} committed %{commit_timeago}" -msgstr "%{commit_author_link} 在 %{commit_timeago} é€äº¤" +msgid "%{commit_author_link} authored %{commit_timeago}" +msgstr "" msgid "%{count} participant" msgid_plural "%{count} participants" @@ -41,9 +56,6 @@ msgstr "" msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will allow access on the next attempt." msgstr "ç›®å‰å·²å¤±æ•— %{number_of_failures} 次。GitLab å…許在 %{maximum_failures} 次之內å¯å†å˜—è©¦è®€å– ã€‚" -msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will block access for %{number_of_seconds} seconds." -msgstr "已失敗 %{number_of_failures} 次,在失敗 %{maximum_failures} æ¬¡å‰ GitLab 會在 %{number_of_seconds} 秒後é‡è©¦ã€‚" - msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved." msgstr "已失敗 %{number_of_failures} / %{maximum_failures} 次,GitLab å°‡ä¸å†è‡ªå‹•é‡è©¦ã€‚請在確èªå•é¡Œè§£æ±ºå¾Œæ‰‹å‹•é‡ç½®å„²å˜ç©ºé–“資訊。" @@ -115,24 +127,81 @@ msgstr "新增授權æ¢æ¬¾" msgid "Add new directory" msgstr "新增目錄" +msgid "Add todo" +msgstr "" + +msgid "AdminArea|Stop all jobs" +msgstr "" + +msgid "AdminArea|Stop all jobs?" +msgstr "" + +msgid "AdminArea|Stop jobs" +msgstr "" + +msgid "AdminArea|Stopping jobs failed" +msgstr "" + +msgid "AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running." +msgstr "" + msgid "AdminHealthPageLink|health page" msgstr "系統狀態" +msgid "Advanced" +msgstr "" + msgid "Advanced settings" msgstr "進階è¨å®š" msgid "All" msgstr "全部" +msgid "All changes are committed" +msgstr "" + +msgid "Allows you to add and manage Kubernetes clusters." +msgstr "" + +msgid "An error occurred previewing the blob" +msgstr "" + msgid "An error occurred when toggling the notification subscription" msgstr "" msgid "An error occurred when updating the issue weight" msgstr "" +msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." +msgstr "" + +msgid "An error occurred while fetching markdown preview" +msgstr "" + msgid "An error occurred while fetching sidebar data" msgstr "" +msgid "An error occurred while getting projects" +msgstr "" + +msgid "An error occurred while loading filenames" +msgstr "" + +msgid "An error occurred while rendering KaTeX" +msgstr "" + +msgid "An error occurred while rendering preview broadcast message" +msgstr "" + +msgid "An error occurred while retrieving calendar activity" +msgstr "" + +msgid "An error occurred while retrieving diff" +msgstr "" + +msgid "An error occurred while validating username" +msgstr "" + msgid "An error occurred. Please try again." msgstr "發生錯誤,請å†è©¦ä¸€æ¬¡ã€‚" @@ -157,9 +226,6 @@ msgstr "確定è¦åˆªé™¤æ¤æµæ°´ç·š (pipeline) 排程嗎?" msgid "Are you sure you want to discard your changes?" msgstr "確定è¦æ”¾æ£„修改嗎?" -msgid "Are you sure you want to leave this group?" -msgstr "確定è¦é›¢é–‹é€™å€‹ç¾¤çµ„嗎?" - msgid "Are you sure you want to reset registration token?" msgstr "確定è¦é‡ç½®è¨»å†Šæ†‘è‰ (registration token) 嗎?" @@ -172,6 +238,21 @@ msgstr "確定嗎?" msgid "Artifacts" msgstr "" +msgid "Assign custom color like #FF0000" +msgstr "" + +msgid "Assign labels" +msgstr "" + +msgid "Assign milestone" +msgstr "" + +msgid "Assign to" +msgstr "" + +msgid "Assignee" +msgstr "" + msgid "Attach a file by drag & drop or %{upload_link}" msgstr "拖放檔案到æ¤è™•æˆ–者 %{upload_link}" @@ -187,15 +268,18 @@ msgstr "登入紀錄" msgid "Author" msgstr "作者" -msgid "Auto Review Apps and Auto Deploy need a domain name and the %{kubernetes} to work correctly." -msgstr "自動複閱應用 (review apps) 與自動部署需è¦ç¶²åŸŸå’Œ %{kubernetes} æ‰èƒ½é‹ä½œã€‚" +msgid "Authors: %{authors}" +msgstr "" + +msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." +msgstr "" + +msgid "Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly." +msgstr "" msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly." msgstr "自動複閱應用 (review apps) 與自動部署需è¦ç¶²åŸŸæ‰èƒ½é‹ä½œã€‚" -msgid "Auto Review Apps and Auto Deploy need the %{kubernetes} to work correctly." -msgstr "自動複閱應用 (review apps) èˆ‡è‡ªå‹•éƒ¨ç½²éœ€è¦ %{kubernetes} æ‰èƒ½é‹ä½œã€‚" - msgid "AutoDevOps|Auto DevOps (Beta)" msgstr "DevOps 自動化(beta)" @@ -217,6 +301,12 @@ msgstr "ä½ å¯ä»¥ç‚ºæ¤å°ˆæ¡ˆå•Ÿå‹• %{link_to_settings}" msgid "Available" msgstr "" +msgid "Avatar will be removed. Are you sure?" +msgstr "" + +msgid "Average per day: %{average}" +msgstr "" + msgid "Billing" msgstr "" @@ -271,6 +361,9 @@ msgstr "" msgid "BillingPlans|per user" msgstr "" +msgid "Begin with the selected commit" +msgstr "" + msgid "Branch" msgid_plural "Branches" msgstr[0] "分支 (branch) " @@ -398,8 +491,8 @@ msgstr "作者:" msgid "CI / CD" msgstr "CI / CD" -msgid "CI configuration" -msgstr "CI 組態" +msgid "CI/CD configuration" +msgstr "" msgid "CICD|Jobs" msgstr "作æ¥" @@ -410,6 +503,9 @@ msgstr "å–消" msgid "Cancel edit" msgstr "å–消編輯" +msgid "Cannot modify managed Kubernetes cluster" +msgstr "" + msgid "Change Weight" msgstr "" @@ -425,15 +521,24 @@ msgstr "挑é¸" msgid "ChangeTypeAction|Revert" msgstr "還原" +msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes." +msgstr "" + msgid "Changelog" msgstr "更新日誌" +msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision." +msgstr "" + msgid "Charts" msgstr "統計圖" msgid "Chat" msgstr "å³æ™‚通訊" +msgid "Check interval" +msgstr "" + msgid "Checking %{text} availability…" msgstr "" @@ -446,7 +551,19 @@ msgstr "挑é¸æ¤æ›´å‹•è¨˜éŒ„ (commit) " msgid "Cherry-pick this merge request" msgstr "挑é¸æ¤åˆä½µè«‹æ±‚ (merge request) " -msgid "Choose which groups you wish to replicate to this secondary node. Leave blank to replicate all." +msgid "Choose File ..." +msgstr "" + +msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request." +msgstr "" + +msgid "Choose file..." +msgstr "" + +msgid "Choose which groups you wish to synchronize to this secondary node." +msgstr "" + +msgid "Choose which shards you wish to synchronize to this secondary node." msgstr "" msgid "CiStatusLabel|canceled" @@ -503,79 +620,91 @@ msgstr "已略éŽ" msgid "CiStatus|running" msgstr "執行ä¸" -msgid "CircuitBreakerApiLink|circuitbreaker api" -msgstr "斷路器 (circuitbreaker) API" +msgid "CiVariables|Input variable key" +msgstr "" -msgid "Clone repository" -msgstr "複製(clone)檔案庫(repository)" +msgid "CiVariables|Input variable value" +msgstr "" -msgid "Close" +msgid "CiVariables|Remove variable row" msgstr "" -msgid "Cluster" -msgstr "å¢é›†" +msgid "CiVariable|* (All environments)" +msgstr "" -msgid "ClusterIntegration|%{appList} was successfully installed on your cluster" +msgid "CiVariable|All environments" msgstr "" -msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which incur additional costs. See %{pricingLink}" +msgid "CiVariable|Create wildcard" msgstr "" -msgid "ClusterIntegration|API URL" +msgid "CiVariable|Error occured while saving variables" msgstr "" -msgid "ClusterIntegration|Active" +msgid "CiVariable|New environment" msgstr "" -msgid "ClusterIntegration|Add an existing cluster" +msgid "CiVariable|Protected" msgstr "" -msgid "ClusterIntegration|Add cluster" +msgid "CiVariable|Search environments" msgstr "" -msgid "ClusterIntegration|All" +msgid "CiVariable|Toggle protected" msgstr "" -msgid "ClusterIntegration|Applications" +msgid "CiVariable|Validation failed" msgstr "" -msgid "ClusterIntegration|CA Certificate" +msgid "CircuitBreakerApiLink|circuitbreaker api" +msgstr "斷路器 (circuitbreaker) API" + +msgid "Click to expand text" msgstr "" -msgid "ClusterIntegration|Certificate Authority bundle (PEM format)" +msgid "Clone repository" +msgstr "複製(clone)檔案庫(repository)" + +msgid "Close" msgstr "" -msgid "ClusterIntegration|Choose how to set up cluster integration" +msgid "Closed" msgstr "" -msgid "ClusterIntegration|Cluster" +msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Cluster details" -msgstr "å¢é›†è©³æƒ…" +msgid "ClusterIntegration|API URL" +msgstr "" -msgid "ClusterIntegration|Cluster integration" -msgstr "å¢é›†æ•´åˆ" +msgid "ClusterIntegration|Add Kubernetes cluster" +msgstr "" -msgid "ClusterIntegration|Cluster integration is disabled for this project." -msgstr "æ¤å°ˆæ¡ˆå·²ç¶“ç¦ç”¨å¢é›†æ•´åˆ" +msgid "ClusterIntegration|Add an existing Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration" +msgstr "" + +msgid "ClusterIntegration|Applications" +msgstr "" -msgid "ClusterIntegration|Cluster integration is enabled for this project." -msgstr "æ¤å°ˆæ¡ˆå·²ç¶“啟用å¢é›†æ•´åˆ" +msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster." +msgstr "" -msgid "ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab's connection to it." -msgstr "æ¤å°ˆæ¡ˆå·²å•Ÿç”¨å¢é›†æ•´åˆã€‚ç¦æ¢å¢é›†æ•´åˆä¸æœƒå½±éŸ¿æ‚¨çš„å¢é›†ï¼Œå®ƒåªæ˜¯æš«æ™‚關閉 GitLab 的連接。" +msgid "ClusterIntegration|CA Certificate" +msgstr "" -msgid "ClusterIntegration|Cluster is being created on Google Kubernetes Engine..." +msgid "ClusterIntegration|Certificate Authority bundle (PEM format)" msgstr "" -msgid "ClusterIntegration|Cluster name" -msgstr "å¢é›†å稱" +msgid "ClusterIntegration|Choose how to set up Kubernetes cluster integration" +msgstr "" -msgid "ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine. Refresh the page to see cluster's details" +msgid "ClusterIntegration|Choose which of your project's environments will use this Kubernetes cluster." msgstr "" -msgid "ClusterIntegration|Clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" +msgid "ClusterIntegration|Control how your Kubernetes cluster integrates with GitLab" msgstr "" msgid "ClusterIntegration|Copy API URL" @@ -584,37 +713,34 @@ msgstr "" msgid "ClusterIntegration|Copy CA Certificate" msgstr "" -msgid "ClusterIntegration|Copy Token" +msgid "ClusterIntegration|Copy Kubernetes cluster name" msgstr "" -msgid "ClusterIntegration|Copy cluster name" -msgstr "複製å¢é›†å稱" +msgid "ClusterIntegration|Copy Token" +msgstr "" -msgid "ClusterIntegration|Create a new cluster on Google Engine right from GitLab" +msgid "ClusterIntegration|Create Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Create cluster" -msgstr "建立å¢é›†" +msgid "ClusterIntegration|Create Kubernetes cluster on Google Kubernetes Engine" +msgstr "" -msgid "ClusterIntegration|Create cluster on Google Kubernetes Engine" +msgid "ClusterIntegration|Create a new Kubernetes cluster on Google Kubernetes Engine right from GitLab" msgstr "" msgid "ClusterIntegration|Create on GKE" msgstr "" -msgid "ClusterIntegration|Enable cluster integration" -msgstr "å•Ÿå‹•å¢é›†æ•´åˆ" - msgid "ClusterIntegration|Enter the details for an existing Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Enter the details for your cluster" +msgid "ClusterIntegration|Enter the details for your Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Environment pattern" +msgid "ClusterIntegration|Environment scope" msgstr "" -msgid "ClusterIntegration|GKE pricing" +msgid "ClusterIntegration|GitLab Integration" msgstr "" msgid "ClusterIntegration|GitLab Runner" @@ -632,46 +758,82 @@ msgstr "" msgid "ClusterIntegration|Helm Tiller" msgstr "" -msgid "ClusterIntegration|Inactive" -msgstr "" - msgid "ClusterIntegration|Ingress" msgstr "" msgid "ClusterIntegration|Install" msgstr "" -msgid "ClusterIntegration|Install applications on your cluster. Read more about %{helpLink}" -msgstr "" - msgid "ClusterIntegration|Installed" msgstr "" msgid "ClusterIntegration|Installing" msgstr "" -msgid "ClusterIntegration|Integrate cluster automation" +msgid "ClusterIntegration|Integrate Kubernetes cluster automation" +msgstr "" + +msgid "ClusterIntegration|Integration status" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster details" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is disabled for this project." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project. Disabling this integration will not affect your Kubernetes cluster, it will only temporarily turn off GitLab's connection to it." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster name" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project" msgstr "" msgid "ClusterIntegration|Learn more about %{link_to_documentation}" msgstr "å¸ç¿’更多有關於%{link_to_documentation}" -msgid "ClusterIntegration|Learn more about Clusters" +msgid "ClusterIntegration|Learn more about Kubernetes" +msgstr "" + +msgid "ClusterIntegration|Learn more about environments" msgstr "" msgid "ClusterIntegration|Machine type" msgstr "機器型別" -msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create clusters" -msgstr "請確èªæ‚¨çš„帳戶ä¸%{link_to_requirements} 是å¦å»ºç«‹å¢é›†" +msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters" +msgstr "" -msgid "ClusterIntegration|Manage cluster integration on your GitLab project" +msgid "ClusterIntegration|Manage" msgstr "" -msgid "ClusterIntegration|Manage your cluster by visiting %{link_gke}" -msgstr "請至 %{link_gke} 管ç†ä½ çš„å¢é›†" +msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}" +msgstr "" -msgid "ClusterIntegration|Multiple clusters are available in GitLab Entreprise Edition Premium and Ultimate" +msgid "ClusterIntegration|More information" +msgstr "" + +msgid "ClusterIntegration|Multiple Kubernetes clusters are available in GitLab Enterprise Edition Premium and Ultimate" msgstr "" msgid "ClusterIntegration|Note:" @@ -680,18 +842,12 @@ msgstr "" msgid "ClusterIntegration|Number of nodes" msgstr "所有的端點數é‡" -msgid "ClusterIntegration|Please enter access information for your cluster. If you need help, you can read our %{link_to_help_page} on clusters" +msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes" msgstr "" msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:" msgstr "請確èªä½ çš„ Google 帳號是å¦ç¬¦åˆé€™äº›æ¢ä»¶" -msgid "ClusterIntegration|Problem setting up the cluster" -msgstr "" - -msgid "ClusterIntegration|Problem setting up the clusters list" -msgstr "" - msgid "ClusterIntegration|Project ID" msgstr "" @@ -701,16 +857,19 @@ msgstr "" msgid "ClusterIntegration|Project namespace (optional, unique)" msgstr "專案命å空間(é¸å¡«ï¼Œä¸å¯é‡è¤‡ï¼‰" -msgid "ClusterIntegration|Read our %{link_to_help_page} on cluster integration." +msgid "ClusterIntegration|Prometheus" msgstr "" -msgid "ClusterIntegration|Remove cluster integration" -msgstr "刪除å¢é›†æ•´åˆ" +msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration." +msgstr "" + +msgid "ClusterIntegration|Remove Kubernetes cluster integration" +msgstr "" msgid "ClusterIntegration|Remove integration" msgstr "刪除整åˆ" -msgid "ClusterIntegration|Removing cluster integration will remove the cluster configuration you have added to this project. It will not delete your cluster on Google Kubernetes Engine." +msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster." msgstr "" msgid "ClusterIntegration|Request to begin installing failed" @@ -719,8 +878,8 @@ msgstr "" msgid "ClusterIntegration|Save changes" msgstr "" -msgid "ClusterIntegration|See and edit the details for your cluster" -msgstr "æŸ¥çœ‹èˆ‡ç·¨è¼¯ä½ çš„å¢é›†å…§å®¹" +msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster" +msgstr "" msgid "ClusterIntegration|See machine types" msgstr "查看機器型別" @@ -740,26 +899,26 @@ msgstr "" msgid "ClusterIntegration|Something went wrong on our end." msgstr "內部發生了錯誤" -msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine" +msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine" msgstr "" msgid "ClusterIntegration|Something went wrong while installing %{title}" msgstr "" -msgid "ClusterIntegration|There are no clusters to show" +msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below" msgstr "" -msgid "ClusterIntegration|This account must have permissions to create a cluster in the %{link_to_container_project} specified below" +msgid "ClusterIntegration|Toggle Kubernetes Cluster" msgstr "" -msgid "ClusterIntegration|Toggle Cluster" -msgstr "å¢é›†é–‹é—œ" +msgid "ClusterIntegration|Toggle Kubernetes cluster" +msgstr "" msgid "ClusterIntegration|Token" msgstr "" -msgid "ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." -msgstr "當å¢é›†é€£çµåˆ°æ¤å°ˆæ¡ˆï¼Œä½ å¯ä»¥ä½¿ç”¨è¤‡é–±æ‡‰ç”¨ (review apps)ï¼Œéƒ¨ç½²ä½ çš„æ‡‰ç”¨ç¨‹å¼ï¼ŒåŸ·è¡Œä½ çš„æµæ°´ç·š (pipelines),還有更多容易上手的方å¼å¯ä»¥ä½¿ç”¨ã€‚" +msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." +msgstr "" msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}" msgstr "" @@ -770,8 +929,8 @@ msgstr "å€åŸŸ" msgid "ClusterIntegration|access to Google Kubernetes Engine" msgstr "" -msgid "ClusterIntegration|cluster" -msgstr "å¢é›†" +msgid "ClusterIntegration|check the pricing here" +msgstr "" msgid "ClusterIntegration|documentation" msgstr "" @@ -788,6 +947,9 @@ msgstr "符åˆéœ€æ±‚" msgid "ClusterIntegration|properly configured" msgstr "è¨å®šæ£ç¢º" +msgid "Collapse" +msgstr "" + msgid "Comments" msgstr "留言" @@ -804,6 +966,9 @@ msgstr "最近 30 次更動所花費的時間(分é˜ï¼‰" msgid "Commit message" msgstr "更動說明 (commit) " +msgid "Commit statistics for %{ref} %{start_time} - %{end_time}" +msgstr "" + msgid "CommitBoxTitle|Commit" msgstr "é€äº¤" @@ -816,15 +981,57 @@ msgstr "更動記錄 (commit) " msgid "Commits feed" msgstr "æ›´å‹•æ‘˜è¦ (commit feed)" +msgid "Commits per day hour (UTC)" +msgstr "" + +msgid "Commits per day of month" +msgstr "" + +msgid "Commits per weekday" +msgstr "" + +msgid "Commits|An error occurred while fetching merge requests data." +msgstr "" + +msgid "Commits|Commit: %{commitText}" +msgstr "" + msgid "Commits|History" msgstr "更動紀錄 (commit)" +msgid "Commits|No related merge requests found" +msgstr "" + msgid "Committed by" msgstr "é€äº¤è€…為 " msgid "Compare" msgstr "比較" +msgid "Compare Git revisions" +msgstr "" + +msgid "Compare Revisions" +msgstr "" + +msgid "CompareBranches|%{source_branch} and %{target_branch} are the same." +msgstr "" + +msgid "CompareBranches|Compare" +msgstr "" + +msgid "CompareBranches|Source" +msgstr "" + +msgid "CompareBranches|Target" +msgstr "" + +msgid "CompareBranches|There isn't anything to compare." +msgstr "" + +msgid "Confidentiality" +msgstr "" + msgid "Container Registry" msgstr "Container Registry" @@ -876,6 +1083,9 @@ msgstr "å”作指å—" msgid "Contributors" msgstr "å”作者" +msgid "ContributorsPage|%{startDate} – %{endDate}" +msgstr "" + msgid "ContributorsPage|Building repository graph." msgstr "" @@ -897,9 +1107,18 @@ msgstr "" msgid "Copy URL to clipboard" msgstr "複製網å€åˆ°å‰ªè²¼ç°¿" +msgid "Copy branch name to clipboard" +msgstr "" + msgid "Copy commit SHA to clipboard" msgstr "複製更動記錄 (commit) çš„ SHA 值到剪貼簿" +msgid "Copy reference to clipboard" +msgstr "" + +msgid "Create" +msgstr "" + msgid "Create New Directory" msgstr "建立新目錄" @@ -918,6 +1137,9 @@ msgstr "" msgid "Create file" msgstr "新增檔案" +msgid "Create lists from labels. Issues with that label appear in that list." +msgstr "" + msgid "Create merge request" msgstr "發出åˆä½µè«‹æ±‚ (merge request) " @@ -930,6 +1152,9 @@ msgstr "新增資料夾" msgid "Create new file" msgstr "新增檔案" +msgid "Create new label" +msgstr "" + msgid "Create new..." msgstr "建立..." @@ -951,6 +1176,9 @@ msgstr "Cron 時å€" msgid "Cron syntax" msgstr "Cron 語法" +msgid "Current node" +msgstr "" + msgid "Custom notification events" msgstr "自訂事件通知" @@ -960,9 +1188,6 @@ msgstr "自訂通知的ç‰ç´šèˆ‡åƒèˆ‡åº¦è¨å®šç›¸åŒã€‚ä½¿ç”¨è‡ªè¨‚é€šçŸ¥è®“ä½ msgid "Cycle Analytics" msgstr "週期分æž" -msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project." -msgstr "週期分æžè®“ä½ å¯ä»¥æœ‰æ•ˆåœ°é‡æ¸…專案從發想到產å“推出所花費的時間。" - msgid "CycleAnalyticsStage|Code" msgstr "程å¼é–‹ç™¼" @@ -1018,12 +1243,21 @@ msgstr "" msgid "Details" msgstr "細節" +msgid "Diffs|No file name available" +msgstr "" + msgid "Directory name" msgstr "目錄å稱" +msgid "Disable" +msgstr "" + msgid "Discard changes" msgstr "放棄修改" +msgid "Discover GitLab Geo." +msgstr "" + msgid "Dismiss Cycle Analytics introduction box" msgstr "關閉循環分æžä»‹ç´¹è¦–窗" @@ -1060,15 +1294,24 @@ msgstr "差異檔 (diff)" msgid "DownloadSource|Download" msgstr "下載原始碼" +msgid "Due date" +msgstr "" + msgid "Edit" msgstr "編輯" msgid "Edit Pipeline Schedule %{id}" msgstr "編輯 %{id} æµæ°´ç·š (pipeline) 排程" +msgid "Edit files in the editor and commit changes here" +msgstr "" + msgid "Emails" msgstr "é›»å郵件" +msgid "Enable" +msgstr "" + msgid "Environments|An error occurred while fetching the environments." msgstr "" @@ -1087,9 +1330,6 @@ msgstr "" msgid "Environments|Environments" msgstr "" -msgid "Environments|Environments are places where code gets deployed, such as staging or production." -msgstr "" - msgid "Environments|Job" msgstr "" @@ -1132,9 +1372,33 @@ msgstr "" msgid "Error creating epic" msgstr "" +msgid "Error fetching contributors data." +msgstr "" + +msgid "Error fetching labels." +msgstr "" + +msgid "Error fetching network graph." +msgstr "" + +msgid "Error fetching refs" +msgstr "" + +msgid "Error fetching usage ping data." +msgstr "" + msgid "Error occurred when toggling the notification subscription" msgstr "" +msgid "Error saving label update." +msgstr "" + +msgid "Error updating status for all todos." +msgstr "" + +msgid "Error updating todo status." +msgstr "" + msgid "EventFilterBy|Filter by all" msgstr "顯示全部" @@ -1162,6 +1426,9 @@ msgstr "æ¯æœˆåŸ·è¡Œï¼ˆæ¯æœˆä¸€æ—¥æ·©æ™¨å››é»žï¼‰" msgid "Every week (Sundays at 4:00am)" msgstr "æ¯é€±åŸ·è¡Œï¼ˆé€±æ—¥æ·©æ™¨ 四點)" +msgid "Expand" +msgstr "" + msgid "Explore projects" msgstr "ç€è¦½å°ˆæ¡ˆ" @@ -1180,6 +1447,9 @@ msgstr "" msgid "February" msgstr "" +msgid "Fields on this page are now uneditable, you can configure" +msgstr "" + msgid "File name" msgstr "檔案å稱" @@ -1223,10 +1493,10 @@ msgstr "從請求被åˆä½µå¾Œ (merge request merged) 直到部署至營é‹ç’°å¢ƒ msgid "GPG Keys" msgstr "GPG 金鑰" -msgid "Geo Nodes" +msgid "Generate a default set of labels" msgstr "" -msgid "GeoNodeSyncStatus|Failed" +msgid "Geo Nodes" msgstr "" msgid "GeoNodeSyncStatus|Node is failing or broken." @@ -1235,16 +1505,100 @@ msgstr "" msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage." msgstr "" -msgid "GeoNodeSyncStatus|Out of sync" +msgid "GeoNodes|Database replication lag:" +msgstr "" + +msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?" +msgstr "" + +msgid "GeoNodes|Does not match the primary storage configuration" +msgstr "" + +msgid "GeoNodes|Failed" +msgstr "" + +msgid "GeoNodes|Full" +msgstr "" + +msgid "GeoNodes|GitLab version does not match the primary node version" +msgstr "" + +msgid "GeoNodes|GitLab version:" +msgstr "" + +msgid "GeoNodes|Health status:" +msgstr "" + +msgid "GeoNodes|Last event ID processed by cursor:" +msgstr "" + +msgid "GeoNodes|Last event ID seen from primary:" +msgstr "" + +msgid "GeoNodes|Loading nodes" +msgstr "" + +msgid "GeoNodes|Local Attachments:" msgstr "" -msgid "GeoNodeSyncStatus|Synced" +msgid "GeoNodes|Local LFS objects:" +msgstr "" + +msgid "GeoNodes|Local job artifacts:" +msgstr "" + +msgid "GeoNodes|New node" +msgstr "" + +msgid "GeoNodes|Out of sync" +msgstr "" + +msgid "GeoNodes|Replication slot WAL:" +msgstr "" + +msgid "GeoNodes|Replication slots:" +msgstr "" + +msgid "GeoNodes|Repositories:" +msgstr "" + +msgid "GeoNodes|Selective" +msgstr "" + +msgid "GeoNodes|Storage config:" +msgstr "" + +msgid "GeoNodes|Sync settings:" +msgstr "" + +msgid "GeoNodes|Synced" +msgstr "" + +msgid "GeoNodes|Unused slots" +msgstr "" + +msgid "GeoNodes|Used slots" +msgstr "" + +msgid "GeoNodes|Wikis:" +msgstr "" + +msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS." +msgstr "" + +msgid "Geo|All projects" msgstr "" msgid "Geo|File sync capacity" msgstr "" -msgid "Geo|Groups to replicate" +msgid "Geo|Groups to synchronize" +msgstr "" + +msgid "Geo|Projects in certain groups" +msgstr "" + +msgid "Geo|Projects in certain storage shards" msgstr "" msgid "Geo|Repository sync capacity" @@ -1253,12 +1607,24 @@ msgstr "" msgid "Geo|Select groups to replicate." msgstr "" +msgid "Geo|Shards to synchronize" +msgstr "" + +msgid "Git revision" +msgstr "" + msgid "Git storage health information has been reset" msgstr "Git 儲å˜ç©ºé–“å¥åº·æŒ‡æ•¸å·²é‡ç½®" +msgid "Git version" +msgstr "" + msgid "GitLab Runner section" msgstr "GitLab Runner" +msgid "Gitaly Servers" +msgstr "" + msgid "Go to your fork" msgstr "å‰å¾€æ‚¨çš„分支 (fork) " @@ -1268,6 +1634,9 @@ msgstr "å‰å¾€æ‚¨çš„分支 (fork) " msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "Google 身份驗è‰ä¸æ˜¯ %{link_to_documentation}。如果您想使用æ¤æœå‹™ï¼Œè«‹è«®è©¢ç®¡ç†å“¡ã€‚" +msgid "Got it!" +msgstr "" + msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" msgstr "ç¦æ¢èˆ‡å…¶ä»–群組共享 %{group} ä¸çš„專案" @@ -1304,8 +1673,8 @@ msgstr "找ä¸åˆ°ç¾¤çµ„" msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group." msgstr "ä½ å¯ä»¥ç®¡ç†ç¾¤çµ„內所有æˆå“¡çš„æ¯å€‹å°ˆæ¡ˆçš„å˜å–權é™" -msgid "GroupsTree|Are you sure you want to leave the \"${this.group.fullName}\" group?" -msgstr "ä½ ç¢ºå®šè¦é›¢é–‹ç¾¤çµ„ \"${this.group.fullName}\" 嗎?" +msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?" +msgstr "" msgid "GroupsTree|Create a project in this group." msgstr "在æ¤ç¾¤çµ„建立新的專案" @@ -1355,6 +1724,10 @@ msgstr "沒有檢測到å¥åº·å•é¡Œ" msgid "HealthCheck|Unhealthy" msgstr "ä¸è‰¯" +msgid "Hide value" +msgid_plural "Hide values" +msgstr[0] "" + msgid "History" msgstr "æ·å²" @@ -1380,6 +1753,12 @@ msgid "Instance" msgid_plural "Instances" msgstr[0] "" +msgid "Instance does not support multiple Kubernetes clusters" +msgstr "" + +msgid "Interested parties can even contribute by pushing commits if they want to." +msgstr "" + msgid "Internal - The group and any internal projects can be viewed by any logged in user." msgstr "內部 - 任何登入的使用者都å¯ä»¥æŸ¥çœ‹è©²ç¾¤çµ„åŠå…¶å°ˆæ¡ˆ" @@ -1407,6 +1786,9 @@ msgstr "" msgid "Issues" msgstr "è°é¡Œ" +msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable." +msgstr "" + msgid "Jan" msgstr "" @@ -1425,6 +1807,27 @@ msgstr "" msgid "June" msgstr "" +msgid "Kubernetes" +msgstr "" + +msgid "Kubernetes Cluster" +msgstr "" + +msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}" +msgstr "" + +msgid "Kubernetes cluster integration was not removed." +msgstr "" + +msgid "Kubernetes cluster integration was successfully removed." +msgstr "" + +msgid "Kubernetes cluster was successfully updated." +msgstr "" + +msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page" +msgstr "" + msgid "LFSStatus|Disabled" msgstr "åœç”¨" @@ -1434,6 +1837,9 @@ msgstr "啟用" msgid "Labels" msgstr "標籤" +msgid "Labels can be applied to issues and merge requests to categorize them." +msgstr "" + msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "最近 %d 天" @@ -1462,6 +1868,9 @@ msgstr "您上傳 (push) 了" msgid "LastPushEvent|at" msgstr "æ–¼" +msgid "Learn more" +msgstr "" + msgid "Learn more in the" msgstr "了解更多" @@ -1480,13 +1889,18 @@ msgstr "退出專案" msgid "License" msgstr "" -msgid "Limited to showing %d event at most" -msgid_plural "Limited to showing %d events at most" -msgstr[0] "é™åˆ¶æœ€å¤šé¡¯ç¤º %d 個事件" +msgid "Loading the GitLab IDE..." +msgstr "" msgid "Lock" msgstr "鎖定" +msgid "Lock %{issuableDisplayName}" +msgstr "" + +msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment." +msgstr "" + msgid "Locked" msgstr "鎖定" @@ -1496,12 +1910,21 @@ msgstr "" msgid "Login" msgstr "登入" +msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos." +msgstr "" + +msgid "Manage labels" +msgstr "" + msgid "Mar" msgstr "" msgid "March" msgstr "" +msgid "Mark done" +msgstr "" + msgid "Maximum git storage failures" msgstr "最大 git 儲å˜å¤±æ•—" @@ -1514,6 +1937,9 @@ msgstr "ä¸ä½æ•¸" msgid "Members" msgstr "æˆå“¡" +msgid "Merge Request" +msgstr "" + msgid "Merge Requests" msgstr "åˆä½µè«‹æ±‚ (merge request)" @@ -1523,9 +1949,30 @@ msgstr "åˆä½µ (merge) 事件" msgid "Merge request" msgstr "åˆä½µè«‹æ±‚" +msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others" +msgstr "" + +msgid "Merged" +msgstr "" + msgid "Messages" msgstr "公告" +msgid "Milestone" +msgstr "" + +msgid "Milestones|Delete milestone" +msgstr "" + +msgid "Milestones|Delete milestone %{milestoneTitle}?" +msgstr "" + +msgid "Milestones|Failed to delete milestone %{milestoneTitle}" +msgstr "" + +msgid "Milestones|Milestone %{milestoneTitle} was not found" +msgstr "" + msgid "MissingSSHKeyWarningLink|add an SSH key" msgstr "新增 SSH 金鑰" @@ -1535,16 +1982,28 @@ msgstr "監控" msgid "More information is available|here" msgstr "å¥åº·æª¢æŸ¥" +msgid "Move" +msgstr "" + +msgid "Move issue" +msgstr "" + msgid "Multiple issue boards" msgstr "" -msgid "New Cluster" -msgstr "æ–°å¢é›†" +msgid "Name new label" +msgstr "" msgid "New Issue" msgid_plural "New Issues" msgstr[0] "建立è°é¡Œ (issue) " +msgid "New Kubernetes Cluster" +msgstr "" + +msgid "New Kubernetes cluster" +msgstr "" + msgid "New Pipeline Schedule" msgstr "建立æµæ°´ç·š (pipeline) 排程" @@ -1569,6 +2028,9 @@ msgstr "新群組" msgid "New issue" msgstr "新增è°é¡Œ (issue) " +msgid "New label" +msgstr "" + msgid "New merge request" msgstr "新增åˆä½µè«‹æ±‚ (merge request) " @@ -1587,7 +2049,22 @@ msgstr "æ–°å群組" msgid "New tag" msgstr "新增標籤" -msgid "No container images stored for this project. Add one by following the instructions above." +msgid "No assignee" +msgstr "" + +msgid "No changes" +msgstr "" + +msgid "No connection could be made to a Gitaly Server, please check your logs!" +msgstr "" + +msgid "No due date" +msgstr "" + +msgid "No estimate or time spent" +msgstr "" + +msgid "No file chosen" msgstr "" msgid "No repository" @@ -1602,9 +2079,15 @@ msgstr "" msgid "None" msgstr "ç„¡" +msgid "Not allowed to merge" +msgstr "" + msgid "Not available" msgstr "無法使用" +msgid "Not confidential" +msgstr "" + msgid "Not enough data" msgstr "資料ä¸è¶³" @@ -1665,6 +2148,12 @@ msgstr "關注" msgid "Notifications" msgstr "通知" +msgid "Notifications off" +msgstr "" + +msgid "Notifications on" +msgstr "" + msgid "Nov" msgstr "" @@ -1674,7 +2163,7 @@ msgstr "" msgid "Number of access attempts" msgstr "嘗試å˜å–的次數" -msgid "Number of failures before backing off" +msgid "OK" msgstr "" msgid "Oct" @@ -1689,6 +2178,9 @@ msgstr "篩é¸" msgid "Only project members can comment." msgstr "åªæœ‰ç¾¤çµ„æˆå“¡æ‰èƒ½ç•™è¨€ã€‚" +msgid "Open" +msgstr "" + msgid "Opened" msgstr "" @@ -1722,9 +2214,6 @@ msgstr "« 第一é " msgid "Password" msgstr "密碼" -msgid "People without permission will never get a notification and won\\'t be able to comment." -msgstr "沒有權é™çš„使用者將ä¸æœƒæ”¶åˆ°é€šçŸ¥ï¼Œä¹Ÿç„¡æ³•ç•™è¨€ã€‚" - msgid "Pipeline" msgstr "æµæ°´ç·š (pipeline) " @@ -1767,12 +2256,6 @@ msgstr "所有" msgid "PipelineSchedules|Inactive" msgstr "未啟用" -msgid "PipelineSchedules|Input variable key" -msgstr "變數å稱" - -msgid "PipelineSchedules|Input variable value" -msgstr "變數值" - msgid "PipelineSchedules|Next Run" msgstr "下次執行時間" @@ -1782,9 +2265,6 @@ msgstr "ç„¡" msgid "PipelineSchedules|Provide a short description for this pipeline" msgstr "請簡單說明æ¤æµæ°´ç·š (pipeline) " -msgid "PipelineSchedules|Remove variable row" -msgstr "刪除變數" - msgid "PipelineSchedules|Take ownership" msgstr "å–得所有權" @@ -1812,6 +2292,12 @@ msgstr "上週的æµæ°´ç·š" msgid "Pipelines for last year" msgstr "去年的æµæ°´ç·š" +msgid "Pipelines|Build with confidence" +msgstr "" + +msgid "Pipelines|Get started with Pipelines" +msgstr "" + msgid "Pipeline|all" msgstr "所有" @@ -1824,12 +2310,21 @@ msgstr "於階段" msgid "Pipeline|with stages" msgstr "於階段" +msgid "Play" +msgstr "" + +msgid "Please <a href=%{link_to_billing} target=\"_blank\" rel=\"noopener noreferrer\">enable billing for one of your projects to be able to create a Kubernetes cluster</a>, then try again." +msgstr "" + msgid "Please solve the reCAPTCHA" msgstr "" msgid "Preferences" msgstr "å好è¨å®š" +msgid "Primary" +msgstr "" + msgid "Private - Project access must be granted explicitly to each user." msgstr "ç§æœ‰ - 專案權é™å¿…é ˆä¸€ä¸€æŒ‡æ´¾çµ¦æ¯å€‹ä½¿ç”¨è€…" @@ -1875,6 +2370,9 @@ msgstr "ä½ çš„å¸³è™Ÿç›®å‰æ“有這些群組:" msgid "Profiles|your account" msgstr "ä½ çš„å¸³è™Ÿ" +msgid "Programming languages used in this repository" +msgstr "" + msgid "Project '%{project_name}' is in the process of being deleted." msgstr "專案 \"%{project_name}\" æ£åœ¨è¢«åˆªé™¤ã€‚" @@ -1890,6 +2388,15 @@ msgstr "專案 '%{project_name}' 更新完æˆã€‚" msgid "Project access must be granted explicitly to each user." msgstr "專案權é™å¿…é ˆä¸€ä¸€æŒ‡æ´¾çµ¦æ¯å€‹ä½¿ç”¨è€…。" +msgid "Project avatar" +msgstr "" + +msgid "Project avatar in repository: %{link}" +msgstr "" + +msgid "Project cache successfully reset." +msgstr "" + msgid "Project details" msgstr "專案細節" @@ -1908,6 +2415,21 @@ msgstr "專案導出已開始。完æˆå¾Œä¸‹è¼‰é€£çµæœƒé€åˆ°æ‚¨çš„信箱。" msgid "ProjectActivityRSS|Subscribe" msgstr "訂閱" +msgid "ProjectCreationLevel|Allowed to create projects" +msgstr "" + +msgid "ProjectCreationLevel|Default project creation protection" +msgstr "" + +msgid "ProjectCreationLevel|Developers + Masters" +msgstr "" + +msgid "ProjectCreationLevel|Masters" +msgstr "" + +msgid "ProjectCreationLevel|No one" +msgstr "" + msgid "ProjectFeature|Disabled" msgstr "åœç”¨" @@ -1932,15 +2454,9 @@ msgstr "分支圖" msgid "ProjectSettings|Contact an admin to change this setting." msgstr "" -msgid "ProjectSettings|Immediately run a pipeline on the default branch" -msgstr "" - msgid "ProjectSettings|Only signed commits can be pushed to this repository." msgstr "" -msgid "ProjectSettings|Problem setting up the CI/CD settings JavaScript" -msgstr "" - msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin." msgstr "" @@ -2004,12 +2520,15 @@ msgstr "" msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/" msgstr "" -msgid "PrometheusService|Prometheus monitoring" +msgid "PrometheusService|Time-series monitoring service" msgstr "" msgid "PrometheusService|View environments" msgstr "" +msgid "Protip:" +msgstr "" + msgid "Public - The group and any public projects can be viewed without any authentication." msgstr "公開 - 未登入的情æ³ä¸‹ä¾ç„¶å¯ä»¥æŸ¥çœ‹ä»»ä½•å…¬é–‹å°ˆæ¡ˆ" @@ -2025,6 +2544,9 @@ msgstr "æŽ¨é€ (push) 事件" msgid "PushRule|Committer restriction" msgstr "" +msgid "Quick actions can be used in the issues description and comment boxes." +msgstr "" + msgid "Read more" msgstr "çžè§£æ›´å¤š" @@ -2037,6 +2559,12 @@ msgstr "分支 (branch) " msgid "RefSwitcher|Tags" msgstr "標籤" +msgid "Reference:" +msgstr "" + +msgid "Register / Sign In" +msgstr "" + msgid "Registry" msgstr "" @@ -2061,9 +2589,18 @@ msgstr "相關已åˆä½µçš„請求" msgid "Remind later" msgstr "ç¨å¾Œæ醒" +msgid "Remove" +msgstr "" + +msgid "Remove avatar" +msgstr "" + msgid "Remove project" msgstr "刪除專案" +msgid "Repair authentication" +msgstr "" + msgid "Repository" msgstr "檔案庫 (repository)" @@ -2079,6 +2616,10 @@ msgstr "é‡ç½®å¥åº·æª¢æŸ¥å˜å–æ†‘è‰ (access token)" msgid "Reset runners registration token" msgstr "é‡ç½® Runner è¨»å†Šæ†‘è‰ (registration token)" +msgid "Reveal value" +msgid_plural "Reveal values" +msgstr[0] "" + msgid "Revert this commit" msgstr "還原æ¤æ›´å‹•è¨˜éŒ„ (commit)" @@ -2088,15 +2629,15 @@ msgstr "還原æ¤åˆä½µè«‹æ±‚ (merge request) " msgid "SSH Keys" msgstr "SSH 金鑰" -msgid "Save" -msgstr "儲å˜" - msgid "Save changes" msgstr "儲å˜è®Šæ›´" msgid "Save pipeline schedule" msgstr "儲å˜æµæ°´ç·š (pipeline) 排程" +msgid "Save variables" +msgstr "" + msgid "Schedule a new pipeline" msgstr "建立æµæ°´ç·š (pipeline) 排程" @@ -2112,38 +2653,59 @@ msgstr "" msgid "Search branches and tags" msgstr "æœå°‹åˆ†æ”¯ (branch) 和標籤" +msgid "Search milestones" +msgstr "" + +msgid "Search project" +msgstr "" + +msgid "Search users" +msgstr "" + msgid "Seconds before reseting failure information" msgstr "é‡ç½®å¤±æ•—訊æ¯ç‰å¾…時間(秒)" -msgid "Seconds to wait after a storage failure" -msgstr "儲å˜å¤±æ•—後ç‰å¾…時間(秒)" - msgid "Seconds to wait for a storage access attempt" msgstr "ç‰å¾…å˜å–儲å˜ç©ºé–“的嘗試時間(秒)" +msgid "Secret variables" +msgstr "" + msgid "Select Archive Format" msgstr "é¸æ“‡ä¸‹è¼‰æ ¼å¼" msgid "Select a timezone" msgstr "é¸æ“‡æ™‚å€" +msgid "Select assignee" +msgstr "" + +msgid "Select branch/tag" +msgstr "" + msgid "Select target branch" msgstr "é¸æ“‡ç›®æ¨™åˆ†æ”¯ (branch) " +msgid "Selective synchronization" +msgstr "" + msgid "Sep" msgstr "" msgid "September" msgstr "" +msgid "Server version" +msgstr "" + msgid "Service Templates" msgstr "æœå‹™ç¯„本" msgid "Set a password on your account to pull or push via %{protocol}." msgstr "è«‹å…ˆè¨å®šå¯†ç¢¼ï¼Œæ‰èƒ½ä½¿ç”¨ %{protocol} 來上傳 (push) 或下載 (pull) 。" -msgid "Set up CI" -msgstr "è¨å®š CI" +msgid "Set up CI/CD" +msgstr "" msgid "Set up Koding" msgstr "è¨å®š Koding" @@ -2157,6 +2719,15 @@ msgstr "è¨å®šå¯†ç¢¼" msgid "Settings" msgstr "è¨å®š" +msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero." +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset pipeline minutes" +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes" +msgstr "" + msgid "Show parent pages" msgstr "顯示上層é é¢" @@ -2170,9 +2741,6 @@ msgstr[0] "顯示 %d 個事件" msgid "Sidebar|Change weight" msgstr "" -msgid "Sidebar|Edit" -msgstr "" - msgid "Sidebar|No" msgstr "" @@ -2185,18 +2753,30 @@ msgstr "" msgid "Snippets" msgstr "æ–‡å—片段" +msgid "Something went wrong on our end" +msgstr "" + msgid "Something went wrong on our end." msgstr "發生了錯誤。" +msgid "Something went wrong trying to change the confidentiality of this issue" +msgstr "" + msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}" msgstr "" +msgid "Something went wrong when toggling the button" +msgstr "" + msgid "Something went wrong while fetching the projects." msgstr "讀å–專案時發生錯誤。" msgid "Something went wrong while fetching the registry list." msgstr "讀å–註冊列表時發生錯誤。" +msgid "Something went wrong. Please try again." +msgstr "" + msgid "Sort by" msgstr "排åº" @@ -2326,12 +2906,12 @@ msgstr "å•Ÿå‹• Runner!" msgid "Stopped" msgstr "" +msgid "Storage" +msgstr "" + msgid "Subgroups" msgstr "å群組" -msgid "Subscribe" -msgstr "訂閱" - msgid "Switch branch/tag" msgstr "切æ›åˆ†æ”¯ (branch) 或標籤" @@ -2426,8 +3006,11 @@ msgstr "" msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project." msgstr "" -msgid "The circuitbreaker backoff threshold should be lower than the failure count threshold" -msgstr "é™æµé˜»æ–·å…ƒä»¶çš„觸發門檻應低於計數錯誤門檻" +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project" +msgstr "" + +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project." +msgstr "" msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request." msgstr "程å¼é–‹ç™¼éšŽæ®µé¡¯ç¤ºå¾žç¬¬ä¸€æ¬¡æ›´å‹•è¨˜éŒ„ (commit) 到建立åˆä½µè«‹æ±‚ (merge request) 的時間。建立第一個åˆä½µè«‹æ±‚後,資料將自動填入。" @@ -2441,21 +3024,18 @@ msgstr "åˆ†æ”¯èˆ‡ä¸»å¹¹é–“çš„é—œè¯ (fork relationship) 已被刪除。" msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage." msgstr "è°é¡Œ (issue) 階段顯示從è°é¡Œå»ºç«‹åˆ°è¨å®šé‡Œç¨‹ç¢‘所花的時間,或是è°é¡Œè¢«åˆ†é¡žåˆ°è°é¡Œçœ‹æ¿ (issue board) ä¸æ‰€èŠ±çš„時間。建立第一個è°é¡Œå¾Œï¼Œè³‡æ–™å°‡è‡ªå‹•å¡«å…¥ã€‚" +msgid "The maximum file size allowed is 200KB." +msgstr "" + msgid "The number of attempts GitLab will make to access a storage." msgstr "GitLab å˜å–儲å˜ç©ºé–“的嘗試次數。" -msgid "The number of failures after which GitLab will start temporarily disabling access to a storage shard on a host" -msgstr "" - msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}." msgstr "GitLab 將阻擋å˜å–失敗的次數。在管ç†è€…介é¢ä¸å¯ä»¥é‡ç½®å¤±æ•—次數: %{link_to_health_page} 或使用 %{api_documentation_link}。" msgid "The phase of the development lifecycle." msgstr "專案開發週期的å„個階段。" -msgid "The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user." -msgstr "在指定了特定分支 (branch) 或標籤後,æ¤è™•çš„æµæ°´ç·š (pipeline) 排程會ä¸æ–·åœ°é‡è¤‡åŸ·è¡Œã€‚æµæ°´ç·šæŽ’程的å˜å–權é™èˆ‡å°ˆæ¡ˆæœ¬èº«ç›¸åŒã€‚" - msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit." msgstr "計劃階段顯示從更動記錄 (commit) 被排程至第一個推é€çš„時間。第一次推é€ä¹‹å¾Œï¼Œè³‡æ–™å°‡è‡ªå‹•å¡«å…¥ã€‚" @@ -2486,20 +3066,47 @@ msgstr "GitLab ä¿å˜å¤±æ•—訊æ¯çš„時間(秒)。在æ¤æ™‚間內若沒有發生 msgid "The time in seconds GitLab will try to access storage. After this time a timeout error will be raised." msgstr "GitLab 嘗試å˜å–檔案庫 (repository) 的時間 (秒)。超éŽæ¤æ™‚間將會引發逾時錯誤。" +msgid "The time in seconds between storage checks. When a previous check did complete yet, GitLab will skip a check." +msgstr "" + msgid "The time taken by each data entry gathered by that stage." msgstr "該階段ä¸æ¯ä¸€å€‹è³‡æ–™é …目所花的時間。" msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6." msgstr "ä¸ä½æ•¸æ˜¯ä¸€å€‹æ•¸åˆ—ä¸æœ€ä¸é–“的值。例如在 3ã€5ã€9 之間,ä¸ä½æ•¸æ˜¯ 5。在 3ã€5ã€7ã€8 之間,ä¸ä½æ•¸æ˜¯ (5 + 7)/ 2 = 6。" +msgid "There are no issues to show" +msgstr "" + +msgid "There are no merge requests to show" +msgstr "" + msgid "There are problems accessing Git storage: " msgstr "å˜å– Git 儲å˜ç©ºé–“時出ç¾å•é¡Œï¼š" +msgid "There was an error loading users activity calendar." +msgstr "" + +msgid "There was an error saving your notification settings." +msgstr "" + +msgid "There was an error subscribing to this label." +msgstr "" + +msgid "There was an error when reseting email token." +msgstr "" + +msgid "There was an error when subscribing to this label." +msgstr "" + +msgid "There was an error when unsubscribing from this label." +msgstr "" + msgid "This board\\'s scope is reduced" msgstr "" -msgid "This branch has changed since you started editing. Would you like to create a new branch?" -msgstr "在您編輯後,æ¤åˆ†æ”¯å·²è¢«æ›´æ”¹ï¼Œæ‚¨æƒ³è¦å»ºç«‹ä¸€å€‹æ–°çš„分支嗎?" +msgid "This directory" +msgstr "" msgid "This is a confidential issue." msgstr "這是個隱密å•é¡Œã€‚" @@ -2507,18 +3114,45 @@ msgstr "這是個隱密å•é¡Œã€‚" msgid "This is the author's first Merge Request to this project." msgstr "這是作者第一次åˆä½µè«‹æ±‚至本專案。" +msgid "This issue is confidential" +msgstr "" + msgid "This issue is confidential and locked." msgstr "這個å•é¡Œæ˜¯ä¿å¯†ä¸”鎖定的。" msgid "This issue is locked." msgstr "這個å•é¡Œå·²è¢«éŽ–定。" +msgid "This job depends on a user to trigger its process. Often they are used to deploy code to production environments" +msgstr "" + +msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered" +msgstr "" + +msgid "This job has not been triggered yet" +msgstr "" + +msgid "This job has not started yet" +msgstr "" + +msgid "This job is in pending state and is waiting to be picked by a runner" +msgstr "" + +msgid "This job requires a manual action" +msgstr "" + msgid "This means you can not push code until you create an empty repository or import existing one." msgstr "這代表在您建立一個空的檔案庫 (repository) 或是匯入一個ç¾å˜çš„檔案庫之å‰ï¼Œæ‚¨å°‡ç„¡æ³•ä¸Šå‚³æ›´æ–° (push) 。" msgid "This merge request is locked." msgstr "這個åˆä½µè«‹æ±‚已被鎖定。" +msgid "This project" +msgstr "" + +msgid "This repository" +msgstr "" + msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here." msgstr "" @@ -2531,9 +3165,21 @@ msgstr "è°é¡Œ (issue) ç‰å¾…開始實作的時間" msgid "Time between merge request creation and merge/close" msgstr "åˆä½µè«‹æ±‚ (merge request) 從建立到被åˆä½µæˆ–是關閉的時間" +msgid "Time tracking" +msgstr "" + msgid "Time until first merge request" msgstr "第一個åˆä½µè«‹æ±‚ (merge request) 被建立å‰çš„時間" +msgid "TimeTrackingEstimated|Est" +msgstr "" + +msgid "TimeTracking|Estimated:" +msgstr "" + +msgid "TimeTracking|Spent" +msgstr "" + msgid "Timeago|%s days ago" msgstr " %s 天å‰" @@ -2671,6 +3317,18 @@ msgstr "秒" msgid "Title" msgstr "" +msgid "Todo" +msgstr "" + +msgid "Toggle sidebar" +msgstr "" + +msgid "ToggleButton|Toggle Status: OFF" +msgstr "" + +msgid "ToggleButton|Toggle Status: ON" +msgstr "" + msgid "Total Time" msgstr "總時間" @@ -2686,20 +3344,41 @@ msgstr "" msgid "Track groups of issues that share a theme, across projects and milestones" msgstr "" +msgid "Total: %{total}" +msgstr "" + +msgid "Track time with quick actions" +msgstr "" + +msgid "Trigger this manual action" +msgstr "" + msgid "Turn on Service Desk" msgstr "" +msgid "Type %{value} to confirm:" +msgstr "" + +msgid "Unable to reset project cache." +msgstr "" + +msgid "Unknown" +msgstr "" + msgid "Unlock" msgstr "解鎖" +msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment." +msgstr "" + msgid "Unlocked" msgstr "已解鎖" msgid "Unstar" msgstr "å–消收è—" -msgid "Unsubscribe" -msgstr "å–消訂閱" +msgid "Up to date" +msgstr "" msgid "Upgrade your plan to activate Advanced Global Search." msgstr "" @@ -2722,6 +3401,9 @@ msgstr "上傳新檔案" msgid "Upload file" msgstr "上傳檔案" +msgid "Upload new avatar" +msgstr "" + msgid "UploadLink|click to upload" msgstr "點擊上傳" @@ -2734,9 +3416,15 @@ msgstr "在安è£éŽç¨‹ä¸ä½¿ç”¨æ¤è¨»å†Šæ†‘è‰ (registration token):" msgid "Use your global notification setting" msgstr "使用全域通知è¨å®š" +msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." +msgstr "" + msgid "View file @ " msgstr "ç€è¦½æª”案 @ " +msgid "View labels" +msgstr "" + msgid "View open merge request" msgstr "查看æ¤åˆ†æ”¯çš„åˆä½µè«‹æ±‚ (merge request)" @@ -2758,6 +3446,9 @@ msgstr "ä¸æ˜Ž" msgid "Want to see the data? Please ask an administrator for access." msgstr "權é™ä¸è¶³ã€‚如需查看相關資料,請å‘管ç†å“¡ç”³è«‹æ¬Šé™ã€‚" +msgid "We could not verify that one of your projects on GCP has billing enabled. Please try again." +msgstr "" + msgid "We don't have enough data to show this stage." msgstr "å› è©²éšŽæ®µçš„è³‡æ–™ä¸è¶³è€Œç„¡æ³•é¡¯ç¤ºç›¸é—œè³‡è¨Š" @@ -2770,9 +3461,6 @@ msgstr "" msgid "Weight" msgstr "" -msgid "When access to a storage fails. GitLab will prevent access to the storage for the time specified here. This allows the filesystem to recover. Repositories on failing shards are temporarly unavailable" -msgstr "當å˜å–檔案庫 (repository) 失敗時, GitLab 將在æ¤è™•æŒ‡å®šçš„時間內防æ¢æª”案庫的å˜å–,以æ¤ç‰å¾…檔案系統æ¢å¾©ã€‚å¤±æ•—çš„æª”æ¡ˆåº«åˆ†æµ (shard) 會暫時無法使用。" - msgid "Wiki" msgstr "Wiki" @@ -2791,6 +3479,12 @@ msgstr "å®ƒè¢«æŽ¨è–¦å®‰è£ %{markdown} 所以那 GFM 功能在本機呈ç¾ï¼š" msgid "WikiClone|Start Gollum and edit locally" msgstr "é–‹å§‹ä½ çš„ Gollum 並在本機編輯。" +msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title." +msgstr "" + +msgid "WikiEdit|There is already a page with the same title in that path." +msgstr "" + msgid "WikiEmptyPageError|You are not allowed to create wiki pages" msgstr "ä½ æ²’æœ‰æ¬Šé™å»ºç«‹ Wiki é é¢" @@ -2893,9 +3587,21 @@ msgstr "å°‡è¦åˆªé™¤æœ¬åˆ†æ”¯å°ˆæ¡ˆèˆ‡ä¸»å¹¹ %{forked_from_project} 的所有關 msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" msgstr "å°‡è¦æŠŠ %{project_name_with_namespace} 的所有權轉移給å¦ä¸€å€‹äººã€‚真的「確定ã€è¦é€™éº¼åšå—Žï¼Ÿ" +msgid "You can also star a label to make it a priority label." +msgstr "" + +msgid "You can move around the graph by using the arrow keys." +msgstr "" + +msgid "You can move around the graph by using the arrow keys." +msgstr "" + msgid "You can only add files when you are on a branch" msgstr "åªèƒ½åœ¨åˆ†æ”¯ (branch) 上建立檔案" +msgid "You can only edit files when you are on a branch" +msgstr "" + msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead." msgstr "" @@ -2935,6 +3641,12 @@ msgstr "åœ¨å€‹äººå¸³è™Ÿä¸ %{add_ssh_key_link} 之å‰ï¼Œ 將無法使用 SSH 上 msgid "You won't be able to pull or push project code via SSH until you add an SSH key to your profile" msgstr "" +msgid "You'll need to use different branch names to get a valid comparison." +msgstr "" + +msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure" +msgstr "" + msgid "Your comment will not be visible to the public." msgstr "ä½ çš„ç•™è¨€å°‡ä¸æœƒè¢«å…¬é–‹ã€‚" @@ -2947,25 +3659,218 @@ msgstr "您的åå—" msgid "Your projects" msgstr "ä½ çš„è¨ˆåŠƒ" +msgid "assign yourself" +msgstr "" + msgid "branch name" msgstr "" msgid "by" msgstr "" +msgid "ciReport|Code quality" +msgstr "" + +msgid "ciReport|DAST detected no alerts by analyzing the review app" +msgstr "" + +msgid "ciReport|Failed to load ${type} report" +msgstr "" + +msgid "ciReport|Fixed:" +msgstr "" + +msgid "ciReport|Instances" +msgstr "" + +msgid "ciReport|Learn more about whitelisting" +msgstr "" + +msgid "ciReport|Loading ${type} report" +msgstr "" + +msgid "ciReport|No changes to code quality" +msgstr "" + +msgid "ciReport|No changes to performance metrics" +msgstr "" + +msgid "ciReport|Performance metrics" +msgstr "" + +msgid "ciReport|SAST" +msgstr "" + +msgid "ciReport|SAST detected no security vulnerabilities" +msgstr "" + +msgid "ciReport|SAST:container no vulnerabilities were found" +msgstr "" + +msgid "ciReport|Show complete code vulnerabilities report" +msgstr "" + +msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}" +msgstr "" + msgid "commit" msgstr "" +msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue." +msgstr "" + +msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue." +msgstr "" + msgid "day" msgid_plural "days" msgstr[0] "天" +msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command." +msgstr "" + +msgid "merge request" +msgid_plural "merge requests" +msgstr[0] "" + +msgid "mrWidget|Cancel automatic merge" +msgstr "" + +msgid "mrWidget|Check out branch" +msgstr "" + +msgid "mrWidget|Checking ability to merge automatically" +msgstr "" + +msgid "mrWidget|Cherry-pick" +msgstr "" + +msgid "mrWidget|Cherry-pick this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Closed" +msgstr "" + +msgid "mrWidget|Closed by" +msgstr "" + +msgid "mrWidget|Closes" +msgstr "" + +msgid "mrWidget|Did not close" +msgstr "" + +msgid "mrWidget|Email patches" +msgstr "" + +msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|Mentions" +msgstr "" + +msgid "mrWidget|Merge" +msgstr "" + +msgid "mrWidget|Merge failed." +msgstr "" + +msgid "mrWidget|Merge locally" +msgstr "" + +msgid "mrWidget|Merged by" +msgstr "" + +msgid "mrWidget|Plain diff" +msgstr "" + +msgid "mrWidget|Refresh" +msgstr "" + +msgid "mrWidget|Refresh now" +msgstr "" + +msgid "mrWidget|Refreshing now" +msgstr "" + +msgid "mrWidget|Remove Source Branch" +msgstr "" + +msgid "mrWidget|Remove source branch" +msgstr "" + +msgid "mrWidget|Request to merge" +msgstr "" + +msgid "mrWidget|Resolve conflicts" +msgstr "" + +msgid "mrWidget|Revert" +msgstr "" + +msgid "mrWidget|Revert this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Set by" +msgstr "" + +msgid "mrWidget|The changes were merged into" +msgstr "" + +msgid "mrWidget|The changes were not merged into" +msgstr "" + +msgid "mrWidget|The changes will be merged into" +msgstr "" + +msgid "mrWidget|The source branch has been removed" +msgstr "" + +msgid "mrWidget|The source branch is being removed" +msgstr "" + +msgid "mrWidget|The source branch will be removed" +msgstr "" + +msgid "mrWidget|The source branch will not be removed" +msgstr "" + +msgid "mrWidget|There are merge conflicts" +msgstr "" + +msgid "mrWidget|This merge request failed to be merged automatically" +msgstr "" + +msgid "mrWidget|This merge request is in the process of being merged" +msgstr "" + +msgid "mrWidget|This project is archived, write access has been disabled" +msgstr "" + +msgid "mrWidget|You can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|You can remove source branch now" +msgstr "" + +msgid "mrWidget|command line" +msgstr "" + +msgid "mrWidget|into" +msgstr "" + +msgid "mrWidget|to be merged automatically when the pipeline succeeds" +msgstr "" + msgid "new merge request" msgstr "建立åˆä½µè«‹æ±‚" msgid "notification emails" msgstr "通知信" +msgid "or" +msgstr "" + msgid "parent" msgid_plural "parents" msgstr[0] "上層" @@ -2976,12 +3881,21 @@ msgstr "密碼" msgid "personal access token" msgstr "ç§äººå˜å–æ†‘è‰ (access token)" +msgid "remove due date" +msgstr "" + msgid "source" msgstr "" +msgid "spendCommand|%{slash_command} will update the sum of the time spent." +msgstr "" + msgid "to help your contributors communicate effectively!" msgstr "" msgid "username" msgstr "使用者å稱" +msgid "uses Kubernetes clusters to deploy your code!" +msgstr "" + diff --git a/package.json b/package.json index 750c6f63de6..7498bb486dc 100644 --- a/package.json +++ b/package.json @@ -53,9 +53,11 @@ "jed": "^1.1.1", "jquery": "^2.2.4", "jquery-ujs": "1.2.2", + "jquery.waitforimages": "^2.2.0", "js-cookie": "^2.1.3", "jszip": "^3.1.3", "jszip-utils": "^0.0.2", + "katex": "^0.8.3", "marked": "^0.3.12", "monaco-editor": "0.10.0", "mousetrap": "^1.4.6", @@ -69,6 +71,7 @@ "sanitize-html": "^1.16.1", "select2": "3.5.2-browserify", "sql.js": "^0.4.0", + "style-loader": "^0.19.1", "svg4everybody": "2.1.9", "three": "^0.84.0", "three-orbit-controls": "^82.1.0", diff --git a/qa/README.md b/qa/README.md index b937dc4c7a0..3a99a30d379 100644 --- a/qa/README.md +++ b/qa/README.md @@ -34,9 +34,6 @@ You can use GitLab QA to exercise tests on any live instance! For example, the following call would login to a local [GDK] instance and run all specs in `qa/specs/features`: -First, `cd` into the `$gdk/gitlab/qa` directory. -The `bin/qa` script expects you to be in the `qa` folder of the app. - ``` bin/qa Test::Instance http://localhost:3000 ``` @@ -73,6 +70,30 @@ If you need to authenticate as a different user, you can provide the GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password bin/qa Test::Instance https://gitlab.example.com ``` +If your user doesn't have permission to default sandbox group +`gitlab-qa-sandbox`, you could also use another sandbox group by giving +`GITLAB_SANDBOX_NAME`: + +``` +GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password GITLAB_SANDBOX_NAME=jsmith-qa-sandbox bin/qa Test::Instance https://gitlab.example.com +``` + +In addition, the `GITLAB_USER_TYPE` can be set to "ldap" to sign in as an LDAP user: + +``` +GITLAB_USER_TYPE=ldap GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password GITLAB_SANDBOX_NAME=jsmith-qa-sandbox bin/qa Test::Instance https://gitlab.example.com +``` + All [supported environment variables are here](https://gitlab.com/gitlab-org/gitlab-qa#supported-environment-variables). +### Building a Docker image to test + +Once you have made changes to the CE/EE repositories, you may want to build a +Docker image to test locally instead of waiting for the `gitlab-ce-qa` or +`gitlab-ee-qa` nightly builds. To do that, you can run from this directory: + +```sh +docker build -t gitlab/gitlab-ce-qa:nightly . +``` + [GDK]: https://gitlab.com/gitlab-org/gitlab-development-kit/ @@ -64,6 +64,7 @@ module QA autoload :Instance, 'qa/scenario/test/instance' module Integration + autoload :LDAP, 'qa/scenario/test/integration/ldap' autoload :Mattermost, 'qa/scenario/test/integration/mattermost' end @@ -116,6 +117,10 @@ module QA autoload :Show, 'qa/page/project/pipeline/show' end + module Job + autoload :Show, 'qa/page/project/job/show' + end + module Settings autoload :Common, 'qa/page/project/settings/common' autoload :Advanced, 'qa/page/project/settings/advanced' @@ -164,6 +169,7 @@ module QA # module Git autoload :Repository, 'qa/git/repository' + autoload :Location, 'qa/git/location' end ## diff --git a/qa/qa/factory/resource/runner.rb b/qa/qa/factory/resource/runner.rb index 5f37f8ac2e9..03b69eb1bdf 100644 --- a/qa/qa/factory/resource/runner.rb +++ b/qa/qa/factory/resource/runner.rb @@ -4,7 +4,7 @@ module QA module Factory module Resource class Runner < Factory::Base - attr_writer :name, :tags + attr_writer :name, :tags, :image dependency Factory::Resource::Project, as: :project do |project| project.name = 'project-with-ci-cd' @@ -19,6 +19,10 @@ module QA @tags || %w[qa e2e] end + def image + @image || 'gitlab/gitlab-runner:alpine' + end + def fabricate! project.visit! @@ -31,6 +35,7 @@ module QA runner.token = runners.registration_token runner.address = runners.coordinator_address runner.tags = tags + runner.image = image runner.register! end end diff --git a/qa/qa/factory/resource/secret_variable.rb b/qa/qa/factory/resource/secret_variable.rb index 54ef4d8d964..af0fa8af2df 100644 --- a/qa/qa/factory/resource/secret_variable.rb +++ b/qa/qa/factory/resource/secret_variable.rb @@ -31,7 +31,7 @@ module QA page.fill_variable_key(key) page.fill_variable_value(value) - page.add_variable + page.save_variables end end end diff --git a/qa/qa/git/location.rb b/qa/qa/git/location.rb new file mode 100644 index 00000000000..30538388530 --- /dev/null +++ b/qa/qa/git/location.rb @@ -0,0 +1,32 @@ +require 'uri' +require 'forwardable' + +module QA + module Git + class Location + extend Forwardable + + attr_reader :git_uri, :uri + def_delegators :@uri, :user, :host, :path + + # See: config/initializers/1_settings.rb + # Settings#build_gitlab_shell_ssh_path_prefix + def initialize(git_uri) + @git_uri = git_uri + @uri = + if git_uri.start_with?('ssh://') + URI.parse(git_uri) + else + *rest, path = git_uri.split(':') + # Host cannot have : so we'll need to escape it + user_host = rest.join('%3A').sub(/\A\[(.+)\]\z/, '\1') + URI.parse("ssh://#{user_host}/#{path}") + end + end + + def port + uri.port || 22 + end + end + end +end diff --git a/qa/qa/git/repository.rb b/qa/qa/git/repository.rb index 8f999511d58..4c4ef3ef477 100644 --- a/qa/qa/git/repository.rb +++ b/qa/qa/git/repository.rb @@ -1,3 +1,4 @@ +require 'cgi' require 'uri' module QA @@ -32,7 +33,7 @@ module QA end def clone(opts = '') - `git clone #{opts} #{@uri.to_s} ./` + `git clone #{opts} #{@uri.to_s} ./ #{suppress_output}` end def shallow_clone @@ -60,12 +61,22 @@ module QA end def push_changes(branch = 'master') - `git push #{@uri.to_s} #{branch}` + `git push #{@uri.to_s} #{branch} #{suppress_output}` end def commits `git log --oneline`.split("\n") end + + private + + def suppress_output + # If we're running as the default user, it's probably a temporary + # instance and output can be useful for debugging + return if @username == Runtime::User.default_name + + "&> #{File::NULL}" + end end end end diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index 5c3af4b9115..7924479e2a1 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -17,7 +17,8 @@ module QA start = Time.now while Time.now - start < max - return true if yield + result = yield + return result if result sleep(time) diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb index 95880475ffa..596205fe540 100644 --- a/qa/qa/page/main/login.rb +++ b/qa/qa/page/main/login.rb @@ -14,19 +14,56 @@ module QA element :sign_in_button, 'submit "Sign in"' end + view 'app/views/devise/sessions/_new_ldap.html.haml' do + element :username_field, 'text_field_tag :username' + element :password_field, 'password_field_tag :password' + element :sign_in_button, 'submit_tag "Sign in"' + end + + view 'app/views/devise/shared/_tabs_ldap.html.haml' do + element :ldap_tab, "link_to server['label']" + element :standard_tab, "link_to 'Standard'" + end + def initialize wait(max: 500) do page.has_css?('.application') end end + def set_initial_password_if_present + if page.has_content?('Change your password') + fill_in :user_password, with: Runtime::User.password + fill_in :user_password_confirmation, with: Runtime::User.password + click_button 'Change your password' + end + end + def sign_in_using_credentials + if Runtime::User.ldap_user? + sign_in_using_ldap_credentials + else + sign_in_using_gitlab_credentials + end + end + + def sign_in_using_ldap_credentials using_wait_time 0 do - if page.has_content?('Change your password') - fill_in :user_password, with: Runtime::User.password - fill_in :user_password_confirmation, with: Runtime::User.password - click_button 'Change your password' - end + set_initial_password_if_present + + click_link 'LDAP' + + fill_in :username, with: Runtime::User.ldap_username + fill_in :password, with: Runtime::User.ldap_password + click_button 'Sign in' + end + end + + def sign_in_using_gitlab_credentials + using_wait_time 0 do + set_initial_password_if_present + + click_link 'Standard' if page.has_content?('LDAP') fill_in :user_login, with: Runtime::User.name fill_in :user_password, with: Runtime::User.password diff --git a/qa/qa/page/project/job/show.rb b/qa/qa/page/project/job/show.rb new file mode 100644 index 00000000000..21bda74efb2 --- /dev/null +++ b/qa/qa/page/project/job/show.rb @@ -0,0 +1,19 @@ +module QA::Page + module Project::Job + class Show < QA::Page::Base + view 'app/views/projects/jobs/show.html.haml' do + element :build_output, '.js-build-output' + end + + def output + css = '.js-build-output' + + wait(reload: false) do + has_css?(css) + end + + find(css).text + end + end + end +end diff --git a/qa/qa/page/project/pipeline/index.rb b/qa/qa/page/project/pipeline/index.rb index 32c108393b9..ce430a2a6ee 100644 --- a/qa/qa/page/project/pipeline/index.rb +++ b/qa/qa/page/project/pipeline/index.rb @@ -6,7 +6,13 @@ module QA::Page end def go_to_latest_pipeline - first('.js-pipeline-url-link').click + css = '.js-pipeline-url-link' + + link = wait(reload: false) do + first(css) + end + + link.click end end end diff --git a/qa/qa/page/project/pipeline/show.rb b/qa/qa/page/project/pipeline/show.rb index 0835173f1cd..b183552d46c 100644 --- a/qa/qa/page/project/pipeline/show.rb +++ b/qa/qa/page/project/pipeline/show.rb @@ -11,6 +11,7 @@ module QA::Page view 'app/assets/javascripts/pipelines/components/graph/job_component.vue' do element :job_component, /class.*ci-job-component.*/ + element :job_link, /class.*js-pipeline-graph-job-link.*/ end view 'app/assets/javascripts/vue_shared/components/ci_icon.vue' do @@ -30,6 +31,16 @@ module QA::Page end end end + + def go_to_first_job + css = '.js-pipeline-graph-job-link' + + wait(reload: false) do + has_css?(css) + end + + first(css).click + end end end end diff --git a/qa/qa/page/project/settings/secret_variables.rb b/qa/qa/page/project/settings/secret_variables.rb index e3bfbfcf080..fea4acb389a 100644 --- a/qa/qa/page/project/settings/secret_variables.rb +++ b/qa/qa/page/project/settings/secret_variables.rb @@ -5,49 +5,40 @@ module QA class SecretVariables < Page::Base include Common - view 'app/views/ci/variables/_table.html.haml' do - element :variable_key, '.variable-key' - element :variable_value, '.variable-value' + view 'app/views/ci/variables/_variable_row.html.haml' do + element :variable_key, '.js-ci-variable-input-key' + element :variable_value, '.js-ci-variable-input-value' end view 'app/views/ci/variables/_index.html.haml' do - element :add_new_variable, 'btn_text: "Add new variable"' - end - - view 'app/assets/javascripts/behaviors/secret_values.js' do - element :reveal_value, 'Reveal value' - element :hide_value, 'Hide value' + element :save_variables, '.js-secret-variables-save-button' end def fill_variable_key(key) - fill_in 'variable_key', with: key + page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + page.find('.js-ci-variable-input-key').set(key) + end end def fill_variable_value(value) - fill_in 'variable_value', with: value + page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + page.find('.js-ci-variable-input-value').set(value) + end end - def add_variable - click_on 'Add new variable' + def save_variables + click_button('Save variables') end def variable_key - page.find('.variable-key').text - end - - def variable_value - reveal_value do - page.find('.variable-value').text + page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + page.find('.js-ci-variable-input-key').value end end - private - - def reveal_value - click_button('Reveal value') - - yield.tap do - click_button('Hide value') + def variable_value + page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + page.find('.js-ci-variable-input-value').value end end end diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb index 9d2a84ea644..0c7ad46d36b 100644 --- a/qa/qa/page/project/show.rb +++ b/qa/qa/page/project/show.rb @@ -22,27 +22,33 @@ module QA end def choose_repository_clone_http - wait(reload: false) do - click_element :clone_dropdown - - page.within('.clone-options-dropdown') do - click_link('HTTP') - end + choose_repository_clone('HTTP', 'http') + end - # Ensure git clone textbox was updated to http URI - repository_location.include?('http') - end + def choose_repository_clone_ssh + # It's not always beginning with ssh:// so detecting with @ + # would be more reliable because ssh would always contain it. + # We can't use .git because HTTP also contain that part. + choose_repository_clone('SSH', '@') end def repository_location find('#project_clone').value end + def repository_location_uri + Git::Location.new(repository_location) + end + def project_name find('.qa-project-name').text end def new_merge_request + wait(reload: true) do + has_css?(element_selector_css(:create_merge_request)) + end + click_element :create_merge_request end @@ -56,6 +62,21 @@ module QA click_link 'New issue' end + + private + + def choose_repository_clone(kind, detect_text) + wait(reload: false) do + click_element :clone_dropdown + + page.within('.clone-options-dropdown') do + click_link(kind) + end + + # Ensure git clone textbox was updated + repository_location.include?(detect_text) + end + end end end end diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb index 56944e8b641..fe432edfa2a 100644 --- a/qa/qa/runtime/env.rb +++ b/qa/qa/runtime/env.rb @@ -16,6 +16,36 @@ module QA def personal_access_token ENV['PERSONAL_ACCESS_TOKEN'] end + + # By default, "standard" denotes a standard GitLab user login. + # Set this to "ldap" if the user should be logged in via LDAP. + def user_type + (ENV['GITLAB_USER_TYPE'] || 'standard').tap do |type| + unless %w(ldap standard).include?(type) + raise ArgumentError.new("Invalid user type '#{type}': must be 'ldap' or 'standard'") + end + end + end + + def user_username + ENV['GITLAB_USERNAME'] + end + + def user_password + ENV['GITLAB_PASSWORD'] + end + + def ldap_username + ENV['GITLAB_LDAP_USERNAME'] + end + + def ldap_password + ENV['GITLAB_LDAP_PASSWORD'] + end + + def sandbox_name + ENV['GITLAB_SANDBOX_NAME'] + end end end end diff --git a/qa/qa/runtime/namespace.rb b/qa/qa/runtime/namespace.rb index a72c2d21898..8d05b387416 100644 --- a/qa/qa/runtime/namespace.rb +++ b/qa/qa/runtime/namespace.rb @@ -16,7 +16,7 @@ module QA end def sandbox_name - 'gitlab-qa-sandbox' + Runtime::Env.sandbox_name || 'gitlab-qa-sandbox' end end end diff --git a/qa/qa/runtime/rsa_key.rb b/qa/qa/runtime/rsa_key.rb index d456062bce7..fcd7dcc4f02 100644 --- a/qa/qa/runtime/rsa_key.rb +++ b/qa/qa/runtime/rsa_key.rb @@ -7,7 +7,7 @@ module QA extend Forwardable attr_reader :key - def_delegators :@key, :fingerprint + def_delegators :@key, :fingerprint, :to_pem def initialize(bits = 4096) @key = OpenSSL::PKey::RSA.new(bits) diff --git a/qa/qa/runtime/user.rb b/qa/qa/runtime/user.rb index 60027c89ab1..c80ee6d4d96 100644 --- a/qa/qa/runtime/user.rb +++ b/qa/qa/runtime/user.rb @@ -3,12 +3,28 @@ module QA module User extend self + def default_name + 'root' + end + def name - ENV['GITLAB_USERNAME'] || 'root' + Runtime::Env.user_username || default_name end def password - ENV['GITLAB_PASSWORD'] || '5iveL!fe' + Runtime::Env.user_password || '5iveL!fe' + end + + def ldap_user? + Runtime::Env.user_type == 'ldap' + end + + def ldap_username + Runtime::Env.ldap_username || name + end + + def ldap_password + Runtime::Env.ldap_password || password end end end diff --git a/qa/qa/scenario/test/instance.rb b/qa/qa/scenario/test/instance.rb index 993bbd723a3..0af9afd1ea4 100644 --- a/qa/qa/scenario/test/instance.rb +++ b/qa/qa/scenario/test/instance.rb @@ -22,7 +22,12 @@ module QA Specs::Runner.perform do |specs| specs.tty = true specs.tags = self.class.focus - specs.files = files.any? ? files : 'qa/specs/features' + specs.files = + if files.any? + files + else + File.expand_path('../../specs/features', __dir__) + end end end end diff --git a/qa/qa/scenario/test/integration/ldap.rb b/qa/qa/scenario/test/integration/ldap.rb new file mode 100644 index 00000000000..257ed81d9e1 --- /dev/null +++ b/qa/qa/scenario/test/integration/ldap.rb @@ -0,0 +1,11 @@ +module QA + module Scenario + module Test + module Integration + class LDAP < Test::Instance + tags :ldap + end + end + end + end +end diff --git a/qa/qa/service/runner.rb b/qa/qa/service/runner.rb index d0ee33c69f2..c0352e0467a 100644 --- a/qa/qa/service/runner.rb +++ b/qa/qa/service/runner.rb @@ -15,6 +15,14 @@ module QA @tags = %w[qa test] end + def network + shell "docker network inspect #{@network}" + rescue CommandError + 'bridge' + else + @network + end + def pull shell "docker pull #{@image}" end @@ -22,7 +30,7 @@ module QA def register! shell <<~CMD.tr("\n", ' ') docker run -d --rm --entrypoint=/bin/sh - --network #{@network} --name #{@name} + --network #{network} --name #{@name} -e CI_SERVER_URL=#{@address} -e REGISTER_NON_INTERACTIVE=true -e REGISTRATION_TOKEN=#{@token} diff --git a/qa/qa/service/shellout.rb b/qa/qa/service/shellout.rb index 898febde63c..76fb2af6319 100644 --- a/qa/qa/service/shellout.rb +++ b/qa/qa/service/shellout.rb @@ -3,6 +3,8 @@ require 'open3' module QA module Service module Shellout + CommandError = Class.new(StandardError) + ## # TODO, make it possible to use generic QA framework classes # as a library - gitlab-org/gitlab-qa#94 @@ -14,7 +16,7 @@ module QA out.each { |line| puts line } if wait.value.exited? && wait.value.exitstatus.nonzero? - raise "Command `#{command}` failed!" + raise CommandError, "Command `#{command}` failed!" end end end diff --git a/qa/qa/specs/features/api/users_spec.rb b/qa/qa/specs/features/api/users_spec.rb index 9d039590a0e..d4ff4ebbc9a 100644 --- a/qa/qa/specs/features/api/users_spec.rb +++ b/qa/qa/specs/features/api/users_spec.rb @@ -14,7 +14,7 @@ module QA end scenario 'submit request with a valid user name' do - get request.url, { params: { username: 'root' } } + get request.url, { params: { username: Runtime::User.name } } expect_status(200) expect(json_body).to be_an Array @@ -23,7 +23,7 @@ module QA end scenario 'submit request with an invalid user name' do - get request.url, { params: { username: 'invalid' } } + get request.url, { params: { username: SecureRandom.hex(10) } } expect_status(200) expect(json_body).to be_an Array diff --git a/qa/qa/specs/features/login/ldap_spec.rb b/qa/qa/specs/features/login/ldap_spec.rb new file mode 100644 index 00000000000..ac2bd5a3c39 --- /dev/null +++ b/qa/qa/specs/features/login/ldap_spec.rb @@ -0,0 +1,15 @@ +module QA + feature 'LDAP user login', :ldap do + scenario 'user logs in using LDAP credentials' do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.act { sign_in_using_ldap_credentials } + + # TODO, since `Signed in successfully` message was removed + # this is the only way to tell if user is signed in correctly. + # + Page::Menu::Main.perform do |menu| + expect(menu).to have_personal_area + end + end + end +end diff --git a/qa/qa/specs/features/project/deploy_key_clone_spec.rb b/qa/qa/specs/features/project/deploy_key_clone_spec.rb new file mode 100644 index 00000000000..19d3c83758a --- /dev/null +++ b/qa/qa/specs/features/project/deploy_key_clone_spec.rb @@ -0,0 +1,81 @@ +require 'digest/sha1' + +module QA + feature 'cloning code using a deploy key', :core, :docker do + let(:runner_name) { "qa-runner-#{Time.now.to_i}" } + let(:key) { Runtime::RSAKey.new } + + given(:project) do + Factory::Resource::Project.fabricate! do |resource| + resource.name = 'deploy-key-clone-project' + end + end + + after do + Service::Runner.new(runner_name).remove! + end + + scenario 'user sets up a deploy key to clone code using pipelines' do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.act { sign_in_using_credentials } + + Factory::Resource::Runner.fabricate! do |resource| + resource.project = project + resource.name = runner_name + resource.tags = %w[qa docker] + resource.image = 'gitlab/gitlab-runner:ubuntu' + end + + Factory::Resource::DeployKey.fabricate! do |resource| + resource.project = project + resource.title = 'deploy key title' + resource.key = key.public_key + end + + Factory::Resource::SecretVariable.fabricate! do |resource| + resource.project = project + resource.key = 'DEPLOY_KEY' + resource.value = key.to_pem + end + + project.visit! + + repository_uri = Page::Project::Show.act do + choose_repository_clone_ssh + repository_location_uri + end + + gitlab_ci = <<~YAML + cat-config: + script: + - mkdir -p ~/.ssh + - ssh-keyscan -p #{repository_uri.port} #{repository_uri.host} >> ~/.ssh/known_hosts + - eval $(ssh-agent -s) + - echo "$DEPLOY_KEY" | ssh-add - + - git clone #{repository_uri.git_uri} + - sha1sum #{project.name}/.gitlab-ci.yml + tags: + - qa + - docker + YAML + + Factory::Repository::Push.fabricate! do |resource| + resource.project = project + resource.file_name = '.gitlab-ci.yml' + resource.commit_message = 'Add .gitlab-ci.yml' + resource.file_content = gitlab_ci + end + + sha1sum = Digest::SHA1.hexdigest(gitlab_ci) + + Page::Project::Show.act { wait_for_push } + Page::Menu::Side.act { click_ci_cd_pipelines } + Page::Project::Pipeline::Index.act { go_to_latest_pipeline } + Page::Project::Pipeline::Show.act { go_to_first_job } + + Page::Project::Job::Show.perform do |job| + expect(job.output).to include(sha1sum) + end + end + end +end diff --git a/qa/qa/specs/features/project/pipelines_spec.rb b/qa/qa/specs/features/project/pipelines_spec.rb index 1bb7730e06c..74f6474443d 100644 --- a/qa/qa/specs/features/project/pipelines_spec.rb +++ b/qa/qa/specs/features/project/pipelines_spec.rb @@ -69,7 +69,7 @@ module QA tags: - qa - test - script: echo "CONTENTS" > my-artifacts/artifact.txt + script: mkdir my-artifacts; echo "CONTENTS" > my-artifacts/artifact.txt artifacts: paths: - my-artifacts/ @@ -95,7 +95,7 @@ module QA expect(pipeline).to have_build('test-success', status: :success) expect(pipeline).to have_build('test-failure', status: :failed) expect(pipeline).to have_build('test-tags', status: :pending) - expect(pipeline).to have_build('test-artifacts', status: :failed) + expect(pipeline).to have_build('test-artifacts', status: :success) end end end diff --git a/qa/qa/specs/runner.rb b/qa/qa/specs/runner.rb index 3f7b75df986..752e3e60b8c 100644 --- a/qa/qa/specs/runner.rb +++ b/qa/qa/specs/runner.rb @@ -8,7 +8,7 @@ module QA def initialize @tty = false @tags = [] - @files = ['qa/specs/features'] + @files = [File.expand_path('./features', __dir__)] end def perform diff --git a/qa/spec/git/location_spec.rb b/qa/spec/git/location_spec.rb new file mode 100644 index 00000000000..aef906ee836 --- /dev/null +++ b/qa/spec/git/location_spec.rb @@ -0,0 +1,55 @@ +describe QA::Git::Location do + describe '.new' do + context 'when URI starts with ssh://' do + context 'when URI has port' do + it 'parses correctly' do + uri = described_class + .new('ssh://git@qa.test:2222/sandbox/qa/repo.git') + + expect(uri.user).to eq('git') + expect(uri.host).to eq('qa.test') + expect(uri.port).to eq(2222) + expect(uri.path).to eq('/sandbox/qa/repo.git') + end + end + + context 'when URI does not have port' do + it 'parses correctly' do + uri = described_class + .new('ssh://git@qa.test/sandbox/qa/repo.git') + + expect(uri.user).to eq('git') + expect(uri.host).to eq('qa.test') + expect(uri.port).to eq(22) + expect(uri.path).to eq('/sandbox/qa/repo.git') + end + end + end + + context 'when URI does not start with ssh://' do + context 'when host does not have colons' do + it 'parses correctly' do + uri = described_class + .new('git@qa.test:sandbox/qa/repo.git') + + expect(uri.user).to eq('git') + expect(uri.host).to eq('qa.test') + expect(uri.port).to eq(22) + expect(uri.path).to eq('/sandbox/qa/repo.git') + end + end + + context 'when host has a colon' do + it 'parses correctly' do + uri = described_class + .new('[git@qa:test]:sandbox/qa/repo.git') + + expect(uri.user).to eq('git') + expect(uri.host).to eq('qa%3Atest') + expect(uri.port).to eq(22) + expect(uri.path).to eq('/sandbox/qa/repo.git') + end + end + end + end +end diff --git a/qa/spec/runtime/env_spec.rb b/qa/spec/runtime/env_spec.rb index 103573db6be..2b6365dbc41 100644 --- a/qa/spec/runtime/env_spec.rb +++ b/qa/spec/runtime/env_spec.rb @@ -55,4 +55,25 @@ describe QA::Runtime::Env do end end end + + describe '.user_type' do + it 'returns standard if not defined' do + expect(described_class.user_type).to eq('standard') + end + + it 'returns standard as defined' do + stub_env('GITLAB_USER_TYPE', 'standard') + expect(described_class.user_type).to eq('standard') + end + + it 'returns ldap as defined' do + stub_env('GITLAB_USER_TYPE', 'ldap') + expect(described_class.user_type).to eq('ldap') + end + + it 'returns an error if invalid user type' do + stub_env('GITLAB_USER_TYPE', 'foobar') + expect { described_class.user_type }.to raise_error(ArgumentError) + end + end end diff --git a/qa/spec/scenario/test/instance_spec.rb b/qa/spec/scenario/test/instance_spec.rb index 1824db54c9b..bd09c28e924 100644 --- a/qa/spec/scenario/test/instance_spec.rb +++ b/qa/spec/scenario/test/instance_spec.rb @@ -29,7 +29,8 @@ describe QA::Scenario::Test::Instance do it 'should call runner with default arguments' do subject.perform("test") - expect(runner).to have_received(:files=).with('qa/specs/features') + expect(runner).to have_received(:files=) + .with(File.expand_path('../../../qa/specs/features', __dir__)) end end diff --git a/scripts/security-harness b/scripts/security-harness new file mode 100755 index 00000000000..d454f44dff7 --- /dev/null +++ b/scripts/security-harness @@ -0,0 +1,55 @@ +#!/usr/bin/env ruby + +require 'digest' +require 'fileutils' + +harness_path = File.expand_path('../.git/security_harness', __dir__) +hook_path = File.expand_path("../.git/hooks/pre-push", __dir__) + +if File.exist?(hook_path) + # Deal with a pre-existing hook + source_sum = Digest::SHA256.hexdigest(DATA.read) + dest_sum = Digest::SHA256.file(hook_path).hexdigest + + if source_sum != dest_sum + puts "#{hook_path} exists and is different from our hook!" + puts "Remove it and re-run this script to continue." + + exit 1 + end +else + File.open(hook_path, 'w') do |file| + IO.copy_stream(DATA, file) + end +end + +# Toggle the harness on or off +if File.exist?(harness_path) + FileUtils.rm(harness_path) + + puts "Security harness removed -- you can now push to all remotes." +else + FileUtils.touch(harness_path) + + puts "Security harness installed -- you will only be able to push to dev.gitlab.org!" +end + +__END__ +#!/bin/sh + +set -e + +url="$2" +harness=`dirname "$0"`/../security_harness + +if [ -e "$harness" ] +then + if [[ "$url" != *"dev.gitlab.org"* ]] + then + echo "Pushing to remotes other than dev.gitlab.org has been disabled!" + echo "Run scripts/security-harness to disable this check." + echo + + exit 1 + fi +fi diff --git a/scripts/static-analysis b/scripts/static-analysis index bdb88f3cb57..0e67eabfec1 100755 --- a/scripts/static-analysis +++ b/scripts/static-analysis @@ -7,7 +7,7 @@ require_relative '../lib/gitlab/popen/runner' def emit_warnings(static_analysis) static_analysis.warned_results.each do |result| puts - puts "**** #{result.cmd.join(' ')} had the following warnings:" + puts "**** #{result.cmd.join(' ')} had the following warning(s):" puts puts result.stderr puts @@ -17,7 +17,7 @@ end def emit_errors(static_analysis) static_analysis.failed_results.each do |result| puts - puts "**** #{result.cmd.join(' ')} failed with the following error:" + puts "**** #{result.cmd.join(' ')} failed with the following error(s):" puts puts result.stdout puts result.stderr @@ -26,15 +26,10 @@ def emit_errors(static_analysis) end tasks = [ - %w[bundle exec rake config_lint], - %w[bundle exec rake flay], - %w[bundle exec rake haml_lint], - %w[bundle exec rake scss_lint], + %w[bin/rake lint:all], %w[bundle exec license_finder], %w[yarn run eslint], %w[bundle exec rubocop --parallel], - %w[bundle exec rake gettext:lint], - %w[bundle exec rake lint:static_verification], %w[scripts/lint-conflicts.sh], %w[scripts/lint-rugged] ] diff --git a/spec/controllers/groups/variables_controller_spec.rb b/spec/controllers/groups/variables_controller_spec.rb index 8ea98cd9e8f..39a36b92bb4 100644 --- a/spec/controllers/groups/variables_controller_spec.rb +++ b/spec/controllers/groups/variables_controller_spec.rb @@ -9,48 +9,27 @@ describe Groups::VariablesController do group.add_master(user) end - describe 'POST #create' do - context 'variable is valid' do - it 'shows a success flash message' do - post :create, group_id: group, variable: { key: "one", value: "two" } - - expect(flash[:notice]).to include 'Variable was successfully created.' - expect(response).to redirect_to(group_settings_ci_cd_path(group)) - end - end - - context 'variable is invalid' do - it 'renders show' do - post :create, group_id: group, variable: { key: "..one", value: "two" } + describe 'GET #show' do + let!(:variable) { create(:ci_group_variable, group: group) } - expect(response).to render_template("groups/variables/show") - end + subject do + get :show, group_id: group, format: :json end - end - - describe 'POST #update' do - let(:variable) { create(:ci_group_variable) } - context 'updating a variable with valid characters' do - before do - group.variables << variable - end - - it 'shows a success flash message' do - post :update, group_id: group, - id: variable.id, variable: { key: variable.key, value: 'two' } - - expect(flash[:notice]).to include 'Variable was successfully updated.' - expect(response).to redirect_to(group_variables_path(group)) - end + include_examples 'GET #show lists all variables' + end - it 'renders the action #show if the variable key is invalid' do - post :update, group_id: group, - id: variable.id, variable: { key: '?', value: variable.value } + describe 'PATCH #update' do + let!(:variable) { create(:ci_group_variable, group: group) } + let(:owner) { group } - expect(response).to have_gitlab_http_status(200) - expect(response).to render_template :show - end + subject do + patch :update, + group_id: group, + variables_attributes: variables_attributes, + format: :json end + + include_examples 'PATCH #update updates variables' end end diff --git a/spec/controllers/help_controller_spec.rb b/spec/controllers/help_controller_spec.rb index f75048f422c..21d59c62613 100644 --- a/spec/controllers/help_controller_spec.rb +++ b/spec/controllers/help_controller_spec.rb @@ -68,7 +68,7 @@ describe HelpController do context 'when requested file exists' do it 'renders the raw file' do get :show, - path: 'user/project/img/labels_filter', + path: 'user/project/img/labels_default', format: :png expect(response).to be_success expect(response.content_type).to eq 'image/png' diff --git a/spec/controllers/import/bitbucket_controller_spec.rb b/spec/controllers/import/bitbucket_controller_spec.rb index e8707760a5a..2be46049aab 100644 --- a/spec/controllers/import/bitbucket_controller_spec.rb +++ b/spec/controllers/import/bitbucket_controller_spec.rb @@ -84,20 +84,42 @@ describe Import::BitbucketController do double(slug: "vim", owner: bitbucket_username, name: 'vim') end + let(:project) { create(:project) } + before do allow_any_instance_of(Bitbucket::Client).to receive(:repo).and_return(bitbucket_repo) allow_any_instance_of(Bitbucket::Client).to receive(:user).and_return(bitbucket_user) assign_session_tokens end + it 'returns 200 response when the project is imported successfully' do + allow(Gitlab::BitbucketImport::ProjectCreator) + .to receive(:new).with(bitbucket_repo, bitbucket_repo.name, user.namespace, user, access_params) + .and_return(double(execute: project)) + + post :create, format: :json + + expect(response).to have_gitlab_http_status(200) + end + + it 'returns 422 response when the project could not be imported' do + allow(Gitlab::BitbucketImport::ProjectCreator) + .to receive(:new).with(bitbucket_repo, bitbucket_repo.name, user.namespace, user, access_params) + .and_return(double(execute: build(:project))) + + post :create, format: :json + + expect(response).to have_gitlab_http_status(422) + end + context "when the repository owner is the Bitbucket user" do context "when the Bitbucket user and GitLab user's usernames match" do it "takes the current user's namespace" do expect(Gitlab::BitbucketImport::ProjectCreator) .to receive(:new).with(bitbucket_repo, bitbucket_repo.name, user.namespace, user, access_params) - .and_return(double(execute: true)) + .and_return(double(execute: project)) - post :create, format: :js + post :create, format: :json end end @@ -107,9 +129,9 @@ describe Import::BitbucketController do it "takes the current user's namespace" do expect(Gitlab::BitbucketImport::ProjectCreator) .to receive(:new).with(bitbucket_repo, bitbucket_repo.name, user.namespace, user, access_params) - .and_return(double(execute: true)) + .and_return(double(execute: project)) - post :create, format: :js + post :create, format: :json end end @@ -120,7 +142,7 @@ describe Import::BitbucketController do allow(controller).to receive(:current_user).and_return(user) allow(user).to receive(:can?).and_return(false) - post :create, format: :js + post :create, format: :json end end end @@ -143,9 +165,9 @@ describe Import::BitbucketController do it "takes the existing namespace" do expect(Gitlab::BitbucketImport::ProjectCreator) .to receive(:new).with(bitbucket_repo, bitbucket_repo.name, existing_namespace, user, access_params) - .and_return(double(execute: true)) + .and_return(double(execute: project)) - post :create, format: :js + post :create, format: :json end end @@ -154,7 +176,7 @@ describe Import::BitbucketController do expect(Gitlab::BitbucketImport::ProjectCreator) .not_to receive(:new) - post :create, format: :js + post :create, format: :json end end end @@ -163,17 +185,17 @@ describe Import::BitbucketController do context "when current user can create namespaces" do it "creates the namespace" do expect(Gitlab::BitbucketImport::ProjectCreator) - .to receive(:new).and_return(double(execute: true)) + .to receive(:new).and_return(double(execute: project)) - expect { post :create, format: :js }.to change(Namespace, :count).by(1) + expect { post :create, format: :json }.to change(Namespace, :count).by(1) end it "takes the new namespace" do expect(Gitlab::BitbucketImport::ProjectCreator) .to receive(:new).with(bitbucket_repo, bitbucket_repo.name, an_instance_of(Group), user, access_params) - .and_return(double(execute: true)) + .and_return(double(execute: project)) - post :create, format: :js + post :create, format: :json end end @@ -184,23 +206,23 @@ describe Import::BitbucketController do it "doesn't create the namespace" do expect(Gitlab::BitbucketImport::ProjectCreator) - .to receive(:new).and_return(double(execute: true)) + .to receive(:new).and_return(double(execute: project)) - expect { post :create, format: :js }.not_to change(Namespace, :count) + expect { post :create, format: :json }.not_to change(Namespace, :count) end it "takes the current user's namespace" do expect(Gitlab::BitbucketImport::ProjectCreator) .to receive(:new).with(bitbucket_repo, bitbucket_repo.name, user.namespace, user, access_params) - .and_return(double(execute: true)) + .and_return(double(execute: project)) - post :create, format: :js + post :create, format: :json end end end end - context 'user has chosen an existing nested namespace and name for the project' do + context 'user has chosen an existing nested namespace and name for the project', :postgresql do let(:parent_namespace) { create(:group, name: 'foo', owner: user) } let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) } let(:test_name) { 'test_name' } @@ -212,63 +234,77 @@ describe Import::BitbucketController do it 'takes the selected namespace and name' do expect(Gitlab::BitbucketImport::ProjectCreator) .to receive(:new).with(bitbucket_repo, test_name, nested_namespace, user, access_params) - .and_return(double(execute: true)) + .and_return(double(execute: project)) - post :create, { target_namespace: nested_namespace.full_path, new_name: test_name, format: :js } + post :create, { target_namespace: nested_namespace.full_path, new_name: test_name, format: :json } end end - context 'user has chosen a non-existent nested namespaces and name for the project' do + context 'user has chosen a non-existent nested namespaces and name for the project', :postgresql do let(:test_name) { 'test_name' } it 'takes the selected namespace and name' do expect(Gitlab::BitbucketImport::ProjectCreator) .to receive(:new).with(bitbucket_repo, test_name, kind_of(Namespace), user, access_params) - .and_return(double(execute: true)) + .and_return(double(execute: project)) - post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :js } + post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :json } end it 'creates the namespaces' do allow(Gitlab::BitbucketImport::ProjectCreator) .to receive(:new).with(bitbucket_repo, test_name, kind_of(Namespace), user, access_params) - .and_return(double(execute: true)) + .and_return(double(execute: project)) - expect { post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :js } } + expect { post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :json } } .to change { Namespace.count }.by(2) end it 'new namespace has the right parent' do allow(Gitlab::BitbucketImport::ProjectCreator) .to receive(:new).with(bitbucket_repo, test_name, kind_of(Namespace), user, access_params) - .and_return(double(execute: true)) + .and_return(double(execute: project)) - post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :js } + post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :json } expect(Namespace.find_by_path_or_name('bar').parent.path).to eq('foo') end end - context 'user has chosen existent and non-existent nested namespaces and name for the project' do + context 'user has chosen existent and non-existent nested namespaces and name for the project', :postgresql do let(:test_name) { 'test_name' } let!(:parent_namespace) { create(:group, name: 'foo', owner: user) } + before do + parent_namespace.add_owner(user) + end + it 'takes the selected namespace and name' do expect(Gitlab::BitbucketImport::ProjectCreator) .to receive(:new).with(bitbucket_repo, test_name, kind_of(Namespace), user, access_params) - .and_return(double(execute: true)) + .and_return(double(execute: project)) - post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :js } + post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :json } end it 'creates the namespaces' do allow(Gitlab::BitbucketImport::ProjectCreator) .to receive(:new).with(bitbucket_repo, test_name, kind_of(Namespace), user, access_params) - .and_return(double(execute: true)) + .and_return(double(execute: project)) - expect { post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :js } } + expect { post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :json } } .to change { Namespace.count }.by(2) end end + + context 'when user can not create projects in the chosen namespace' do + it 'returns 422 response' do + other_namespace = create(:group, name: 'other_namespace') + + post :create, { target_namespace: other_namespace.name, format: :json } + + expect(response).to have_gitlab_http_status(422) + end + end end end diff --git a/spec/controllers/import/gitlab_controller_spec.rb b/spec/controllers/import/gitlab_controller_spec.rb index faf1e6f63ea..e958be077c2 100644 --- a/spec/controllers/import/gitlab_controller_spec.rb +++ b/spec/controllers/import/gitlab_controller_spec.rb @@ -57,6 +57,7 @@ describe Import::GitlabController do end describe "POST create" do + let(:project) { create(:project) } let(:gitlab_username) { user.username } let(:gitlab_user) do { username: gitlab_username }.with_indifferent_access @@ -75,14 +76,34 @@ describe Import::GitlabController do assign_session_token end + it 'returns 200 response when the project is imported successfully' do + allow(Gitlab::GitlabImport::ProjectCreator) + .to receive(:new).with(gitlab_repo, user.namespace, user, access_params) + .and_return(double(execute: project)) + + post :create, format: :json + + expect(response).to have_gitlab_http_status(200) + end + + it 'returns 422 response when the project could not be imported' do + allow(Gitlab::GitlabImport::ProjectCreator) + .to receive(:new).with(gitlab_repo, user.namespace, user, access_params) + .and_return(double(execute: build(:project))) + + post :create, format: :json + + expect(response).to have_gitlab_http_status(422) + end + context "when the repository owner is the GitLab.com user" do context "when the GitLab.com user and GitLab server user's usernames match" do it "takes the current user's namespace" do expect(Gitlab::GitlabImport::ProjectCreator) .to receive(:new).with(gitlab_repo, user.namespace, user, access_params) - .and_return(double(execute: true)) + .and_return(double(execute: project)) - post :create, format: :js + post :create, format: :json end end @@ -92,9 +113,9 @@ describe Import::GitlabController do it "takes the current user's namespace" do expect(Gitlab::GitlabImport::ProjectCreator) .to receive(:new).with(gitlab_repo, user.namespace, user, access_params) - .and_return(double(execute: true)) + .and_return(double(execute: project)) - post :create, format: :js + post :create, format: :json end end end @@ -118,9 +139,9 @@ describe Import::GitlabController do it "takes the existing namespace" do expect(Gitlab::GitlabImport::ProjectCreator) .to receive(:new).with(gitlab_repo, existing_namespace, user, access_params) - .and_return(double(execute: true)) + .and_return(double(execute: project)) - post :create, format: :js + post :create, format: :json end end @@ -129,7 +150,7 @@ describe Import::GitlabController do expect(Gitlab::GitlabImport::ProjectCreator) .not_to receive(:new) - post :create, format: :js + post :create, format: :json end end end @@ -138,17 +159,17 @@ describe Import::GitlabController do context "when current user can create namespaces" do it "creates the namespace" do expect(Gitlab::GitlabImport::ProjectCreator) - .to receive(:new).and_return(double(execute: true)) + .to receive(:new).and_return(double(execute: project)) - expect { post :create, format: :js }.to change(Namespace, :count).by(1) + expect { post :create, format: :json }.to change(Namespace, :count).by(1) end it "takes the new namespace" do expect(Gitlab::GitlabImport::ProjectCreator) .to receive(:new).with(gitlab_repo, an_instance_of(Group), user, access_params) - .and_return(double(execute: true)) + .and_return(double(execute: project)) - post :create, format: :js + post :create, format: :json end end @@ -159,22 +180,22 @@ describe Import::GitlabController do it "doesn't create the namespace" do expect(Gitlab::GitlabImport::ProjectCreator) - .to receive(:new).and_return(double(execute: true)) + .to receive(:new).and_return(double(execute: project)) - expect { post :create, format: :js }.not_to change(Namespace, :count) + expect { post :create, format: :json }.not_to change(Namespace, :count) end it "takes the current user's namespace" do expect(Gitlab::GitlabImport::ProjectCreator) .to receive(:new).with(gitlab_repo, user.namespace, user, access_params) - .and_return(double(execute: true)) + .and_return(double(execute: project)) - post :create, format: :js + post :create, format: :json end end end - context 'user has chosen an existing nested namespace for the project' do + context 'user has chosen an existing nested namespace for the project', :postgresql do let(:parent_namespace) { create(:group, name: 'foo', owner: user) } let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) } @@ -185,64 +206,78 @@ describe Import::GitlabController do it 'takes the selected namespace and name' do expect(Gitlab::GitlabImport::ProjectCreator) .to receive(:new).with(gitlab_repo, nested_namespace, user, access_params) - .and_return(double(execute: true)) + .and_return(double(execute: project)) - post :create, { target_namespace: nested_namespace.full_path, format: :js } + post :create, { target_namespace: nested_namespace.full_path, format: :json } end end - context 'user has chosen a non-existent nested namespaces for the project' do + context 'user has chosen a non-existent nested namespaces for the project', :postgresql do let(:test_name) { 'test_name' } it 'takes the selected namespace and name' do expect(Gitlab::GitlabImport::ProjectCreator) .to receive(:new).with(gitlab_repo, kind_of(Namespace), user, access_params) - .and_return(double(execute: true)) + .and_return(double(execute: project)) - post :create, { target_namespace: 'foo/bar', format: :js } + post :create, { target_namespace: 'foo/bar', format: :json } end it 'creates the namespaces' do allow(Gitlab::GitlabImport::ProjectCreator) .to receive(:new).with(gitlab_repo, kind_of(Namespace), user, access_params) - .and_return(double(execute: true)) + .and_return(double(execute: project)) - expect { post :create, { target_namespace: 'foo/bar', format: :js } } + expect { post :create, { target_namespace: 'foo/bar', format: :json } } .to change { Namespace.count }.by(2) end it 'new namespace has the right parent' do allow(Gitlab::GitlabImport::ProjectCreator) .to receive(:new).with(gitlab_repo, kind_of(Namespace), user, access_params) - .and_return(double(execute: true)) + .and_return(double(execute: project)) - post :create, { target_namespace: 'foo/bar', format: :js } + post :create, { target_namespace: 'foo/bar', format: :json } expect(Namespace.find_by_path_or_name('bar').parent.path).to eq('foo') end end - context 'user has chosen existent and non-existent nested namespaces and name for the project' do + context 'user has chosen existent and non-existent nested namespaces and name for the project', :postgresql do let(:test_name) { 'test_name' } let!(:parent_namespace) { create(:group, name: 'foo', owner: user) } + before do + parent_namespace.add_owner(user) + end + it 'takes the selected namespace and name' do expect(Gitlab::GitlabImport::ProjectCreator) .to receive(:new).with(gitlab_repo, kind_of(Namespace), user, access_params) - .and_return(double(execute: true)) + .and_return(double(execute: project)) - post :create, { target_namespace: 'foo/foobar/bar', format: :js } + post :create, { target_namespace: 'foo/foobar/bar', format: :json } end it 'creates the namespaces' do allow(Gitlab::GitlabImport::ProjectCreator) .to receive(:new).with(gitlab_repo, kind_of(Namespace), user, access_params) - .and_return(double(execute: true)) + .and_return(double(execute: project)) - expect { post :create, { target_namespace: 'foo/foobar/bar', format: :js } } + expect { post :create, { target_namespace: 'foo/foobar/bar', format: :json } } .to change { Namespace.count }.by(2) end end + + context 'when user can not create projects in the chosen namespace' do + it 'returns 422 response' do + other_namespace = create(:group, name: 'other_namespace') + + post :create, { target_namespace: other_namespace.name, format: :json } + + expect(response).to have_gitlab_http_status(422) + end + end end end end diff --git a/spec/controllers/profiles_controller_spec.rb b/spec/controllers/profiles_controller_spec.rb index d380978b86e..03cbbb21e62 100644 --- a/spec/controllers/profiles_controller_spec.rb +++ b/spec/controllers/profiles_controller_spec.rb @@ -69,9 +69,8 @@ describe ProfilesController, :request_store do describe 'PUT update_username' do let(:namespace) { user.namespace } - let(:project) { create(:project_empty_repo, namespace: namespace) } let(:gitlab_shell) { Gitlab::Shell.new } - let(:new_username) { 'renamedtosomethingelse' } + let(:new_username) { generate(:username) } it 'allows username change' do sign_in(user) @@ -85,16 +84,39 @@ describe ProfilesController, :request_store do expect(user.username).to eq(new_username) end - it 'moves dependent projects to new namespace' do - sign_in(user) + context 'with legacy storage' do + it 'moves dependent projects to new namespace' do + project = create(:project_empty_repo, :legacy_storage, namespace: namespace) - put :update_username, - user: { username: new_username } + sign_in(user) - user.reload + put :update_username, + user: { username: new_username } - expect(response.status).to eq(302) - expect(gitlab_shell.exists?(project.repository_storage_path, "#{new_username}/#{project.path}.git")).to be_truthy + user.reload + + expect(response.status).to eq(302) + expect(gitlab_shell.exists?(project.repository_storage_path, "#{new_username}/#{project.path}.git")).to be_truthy + end + end + + context 'with hashed storage' do + it 'keeps repository location unchanged on disk' do + project = create(:project_empty_repo, namespace: namespace) + + before_disk_path = project.disk_path + + sign_in(user) + + put :update_username, + user: { username: new_username } + + user.reload + + expect(response.status).to eq(302) + expect(gitlab_shell.exists?(project.repository_storage_path, "#{project.disk_path}.git")).to be_truthy + expect(before_disk_path).to eq(project.disk_path) + end end end end diff --git a/spec/controllers/projects/variables_controller_spec.rb b/spec/controllers/projects/variables_controller_spec.rb index 9fde6544215..68019743be0 100644 --- a/spec/controllers/projects/variables_controller_spec.rb +++ b/spec/controllers/projects/variables_controller_spec.rb @@ -9,50 +9,28 @@ describe Projects::VariablesController do project.add_master(user) end - describe 'POST #create' do - context 'variable is valid' do - it 'shows a success flash message' do - post :create, namespace_id: project.namespace.to_param, project_id: project, - variable: { key: "one", value: "two" } - - expect(flash[:notice]).to include 'Variable was successfully created.' - expect(response).to redirect_to(project_settings_ci_cd_path(project)) - end - end - - context 'variable is invalid' do - it 'renders show' do - post :create, namespace_id: project.namespace.to_param, project_id: project, - variable: { key: "..one", value: "two" } + describe 'GET #show' do + let!(:variable) { create(:ci_variable, project: project) } - expect(response).to render_template("projects/variables/show") - end + subject do + get :show, namespace_id: project.namespace.to_param, project_id: project, format: :json end - end - - describe 'POST #update' do - let(:variable) { create(:ci_variable) } - context 'updating a variable with valid characters' do - before do - project.variables << variable - end - - it 'shows a success flash message' do - post :update, namespace_id: project.namespace.to_param, project_id: project, - id: variable.id, variable: { key: variable.key, value: 'two' } - - expect(flash[:notice]).to include 'Variable was successfully updated.' - expect(response).to redirect_to(project_variables_path(project)) - end + include_examples 'GET #show lists all variables' + end - it 'renders the action #show if the variable key is invalid' do - post :update, namespace_id: project.namespace.to_param, project_id: project, - id: variable.id, variable: { key: '?', value: variable.value } + describe 'PATCH #update' do + let!(:variable) { create(:ci_variable, project: project) } + let(:owner) { project } - expect(response).to have_gitlab_http_status(200) - expect(response).to render_template :show - end + subject do + patch :update, + namespace_id: project.namespace.to_param, + project_id: project, + variables_attributes: variables_attributes, + format: :json end + + include_examples 'PATCH #update updates variables' end end diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 5202ffdd8bb..994da3cd159 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -288,62 +288,82 @@ describe ProjectsController do render_views let(:admin) { create(:admin) } - let(:project) { create(:project, :repository) } before do sign_in(admin) end - context 'when only renaming a project path' do - it "sets the repository to the right path after a rename" do - expect { update_project path: 'renamed_path' } - .to change { project.reload.path } + shared_examples_for 'updating a project' do + context 'when only renaming a project path' do + it "sets the repository to the right path after a rename" do + original_repository_path = project.repository.path - expect(project.path).to include 'renamed_path' - expect(assigns(:repository).path).to include project.path - expect(response).to have_gitlab_http_status(302) - end - end + expect { update_project path: 'renamed_path' } + .to change { project.reload.path } + expect(project.path).to include 'renamed_path' - context 'when project has container repositories with tags' do - before do - stub_container_registry_config(enabled: true) - stub_container_registry_tags(repository: /image/, tags: %w[rc1]) - create(:container_repository, project: project, name: :image) + if project.hashed_storage?(:repository) + expect(assigns(:repository).path).to eq(original_repository_path) + else + expect(assigns(:repository).path).to include(project.path) + end + + expect(response).to have_gitlab_http_status(302) + end end - it 'does not allow to rename the project' do - expect { update_project path: 'renamed_path' } - .not_to change { project.reload.path } + context 'when project has container repositories with tags' do + before do + stub_container_registry_config(enabled: true) + stub_container_registry_tags(repository: /image/, tags: %w[rc1]) + create(:container_repository, project: project, name: :image) + end - expect(controller).to set_flash[:alert].to(/container registry tags/) - expect(response).to have_gitlab_http_status(200) + it 'does not allow to rename the project' do + expect { update_project path: 'renamed_path' } + .not_to change { project.reload.path } + + expect(controller).to set_flash[:alert].to(/container registry tags/) + expect(response).to have_gitlab_http_status(200) + end end - end - it 'updates Fast Forward Merge attributes' do - controller.instance_variable_set(:@project, project) + it 'updates Fast Forward Merge attributes' do + controller.instance_variable_set(:@project, project) - params = { - merge_method: :ff - } + params = { + merge_method: :ff + } - put :update, - namespace_id: project.namespace, - id: project.id, - project: params + put :update, + namespace_id: project.namespace, + id: project.id, + project: params - expect(response).to have_gitlab_http_status(302) - params.each do |param, value| - expect(project.public_send(param)).to eq(value) + expect(response).to have_gitlab_http_status(302) + params.each do |param, value| + expect(project.public_send(param)).to eq(value) + end + end + + def update_project(**parameters) + put :update, + namespace_id: project.namespace.path, + id: project.path, + project: parameters end end - def update_project(**parameters) - put :update, - namespace_id: project.namespace.path, - id: project.path, - project: parameters + context 'hashed storage' do + let(:project) { create(:project, :repository) } + + it_behaves_like 'updating a project' + end + + context 'legacy storage' do + let(:project) { create(:project, :repository, :legacy_storage) } + + it_behaves_like 'updating a project' end end diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index 6ba599cdf83..f6ba3a581ca 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -180,8 +180,8 @@ FactoryBot.define do trait :artifacts do after(:create) do |build| - create(:ci_job_artifact, :archive, job: build) - create(:ci_job_artifact, :metadata, job: build) + create(:ci_job_artifact, :archive, job: build, expire_at: build.artifacts_expire_at) + create(:ci_job_artifact, :metadata, job: build, expire_at: build.artifacts_expire_at) build.reload end end diff --git a/spec/factories/keys.rb b/spec/factories/keys.rb index 23a98a899f1..f0c43f3d6f5 100644 --- a/spec/factories/keys.rb +++ b/spec/factories/keys.rb @@ -5,10 +5,6 @@ FactoryBot.define do title key { Spec::Support::Helpers::KeyGeneratorHelper.new(1024).generate + ' dummy@gitlab.com' } - factory :key_without_comment do - key { Spec::Support::Helpers::KeyGeneratorHelper.new(1024).generate } - end - factory :deploy_key, class: 'DeployKey' factory :personal_key do diff --git a/spec/factories/lfs_file_locks.rb b/spec/factories/lfs_file_locks.rb new file mode 100644 index 00000000000..b9d24f82b65 --- /dev/null +++ b/spec/factories/lfs_file_locks.rb @@ -0,0 +1,7 @@ +FactoryBot.define do + factory :lfs_file_lock do + user + project + path 'README.md' + end +end diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 20976977f21..1761b6e2a3b 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -81,8 +81,10 @@ FactoryBot.define do archived true end - trait :hashed do - storage_version Project::LATEST_STORAGE_VERSION + storage_version Project::LATEST_STORAGE_VERSION + + trait :legacy_storage do + storage_version nil end trait :access_requestable do @@ -249,7 +251,8 @@ FactoryBot.define do project.create_prometheus_service( active: true, properties: { - api_url: 'https://prometheus.example.com' + api_url: 'https://prometheus.example.com/', + manual_configuration: true } ) end diff --git a/spec/factories/services.rb b/spec/factories/services.rb index 110ef33c6f7..0d4fd49bf3a 100644 --- a/spec/factories/services.rb +++ b/spec/factories/services.rb @@ -30,7 +30,8 @@ FactoryBot.define do project active true properties({ - api_url: 'https://prometheus.example.com/' + api_url: 'https://prometheus.example.com/', + manual_configuration: true }) end diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index cc0849d1cc6..39b213988f0 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -47,6 +47,16 @@ feature 'Admin updates settings' do expect(page).to have_content "Application settings saved successfully" end + scenario 'Change AutoDevOps settings' do + check 'Enabled Auto DevOps (Beta) for projects by default' + fill_in 'Auto devops domain', with: 'domain.com' + click_button 'Save' + + expect(Gitlab::CurrentSettings.auto_devops_enabled?).to be true + expect(Gitlab::CurrentSettings.auto_devops_domain).to eq('domain.com') + expect(page).to have_content "Application settings saved successfully" + end + scenario 'Change Slack Notifications Service template settings' do first(:link, 'Service Templates').click click_link 'Slack notifications' diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb index a69b428d117..2307ba5985e 100644 --- a/spec/features/admin/admin_users_spec.rb +++ b/spec/features/admin/admin_users_spec.rb @@ -26,8 +26,8 @@ describe "Admin::Users" do expect(page).to have_content(user.email) expect(page).to have_content(user.name) expect(page).to have_link('Block', href: block_admin_user_path(user)) - expect(page).to have_link('Remove user', href: admin_user_path(user)) - expect(page).to have_link('Remove user and contributions', href: admin_user_path(user, hard_delete: true)) + expect(page).to have_button('Delete user') + expect(page).to have_button('Delete user and contributions') end describe 'Two-factor Authentication filters' do @@ -122,8 +122,8 @@ describe "Admin::Users" do expect(page).to have_content(user.email) expect(page).to have_content(user.name) expect(page).to have_link('Block user', href: block_admin_user_path(user)) - expect(page).to have_link('Remove user', href: admin_user_path(user)) - expect(page).to have_link('Remove user and contributions', href: admin_user_path(user, hard_delete: true)) + expect(page).to have_button('Delete user') + expect(page).to have_button('Delete user and contributions') end describe 'Impersonation' do diff --git a/spec/features/ci_lint_spec.rb b/spec/features/ci_lint_spec.rb index 9bc23baf6cf..b1dceec9da8 100644 --- a/spec/features/ci_lint_spec.rb +++ b/spec/features/ci_lint_spec.rb @@ -3,16 +3,19 @@ require 'spec_helper' describe 'CI Lint', :js do before do sign_in(create(:user)) + + visit ci_lint_path + find('#ci-editor') + execute_script("ace.edit('ci-editor').setValue(#{yaml_content.to_json});") + + # Ace editor updates a hidden textarea and it happens asynchronously + wait_for('YAML content') do + find('.ace_content').text.present? + end end describe 'YAML parsing' do before do - visit ci_lint_path - # Ace editor updates a hidden textarea and it happens asynchronously - # `sleep 0.1` is actually needed here because of this - find('#ci-editor') - execute_script("ace.edit('ci-editor').setValue(" + yaml_content.to_json + ");") - sleep 0.1 click_on 'Validate' end @@ -32,11 +35,10 @@ describe 'CI Lint', :js do end context 'YAML is incorrect' do - let(:yaml_content) { '' } + let(:yaml_content) { 'value: cannot have :' } it 'displays information about an error' do expect(page).to have_content('Status: syntax is incorrect') - expect(page).to have_content('Error: Please provide content of .gitlab-ci.yml') end end @@ -48,4 +50,20 @@ describe 'CI Lint', :js do end end end + + describe 'YAML clearing' do + before do + click_on 'Clear' + end + + context 'YAML is present' do + let(:yaml_content) do + File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) + end + + it 'YAML content is cleared' do + expect(page).to have_field('content', with: '', visible: false, type: 'textarea') + end + end + end end diff --git a/spec/features/group_variables_spec.rb b/spec/features/group_variables_spec.rb index e9b375f4c94..f7863807572 100644 --- a/spec/features/group_variables_spec.rb +++ b/spec/features/group_variables_spec.rb @@ -3,76 +3,15 @@ require 'spec_helper' feature 'Group variables', :js do let(:user) { create(:user) } let(:group) { create(:group) } + let!(:variable) { create(:ci_group_variable, key: 'test_key', value: 'test value', group: group) } + let(:page_path) { group_settings_ci_cd_path(group) } background do group.add_master(user) gitlab_sign_in(user) - end - - context 'when user creates a new variable' do - background do - visit group_settings_ci_cd_path(group) - fill_in 'variable_key', with: 'AAA' - fill_in 'variable_value', with: 'AAA123' - find(:css, "#variable_protected").set(true) - click_on 'Add new variable' - end - - scenario 'user sees the created variable' do - page.within('.variables-table') do - expect(find(".variable-key")).to have_content('AAA') - expect(find(".variable-value")).to have_content('******') - expect(find(".variable-protected")).to have_content('Yes') - end - click_on 'Reveal value' - page.within('.variables-table') do - expect(find(".variable-value")).to have_content('AAA123') - end - end - end - - context 'when user edits a variable' do - background do - create(:ci_group_variable, key: 'AAA', value: 'AAA123', protected: true, - group: group) - - visit group_settings_ci_cd_path(group) - page.within('.variable-menu') do - click_on 'Update' - end - - fill_in 'variable_key', with: 'BBB' - fill_in 'variable_value', with: 'BBB123' - find(:css, "#variable_protected").set(false) - click_on 'Save variable' - end - - scenario 'user sees the updated variable' do - page.within('.variables-table') do - expect(find(".variable-key")).to have_content('BBB') - expect(find(".variable-value")).to have_content('******') - expect(find(".variable-protected")).to have_content('No') - end - end + visit page_path end - context 'when user deletes a variable' do - background do - create(:ci_group_variable, key: 'BBB', value: 'BBB123', protected: false, - group: group) - - visit group_settings_ci_cd_path(group) - - page.within('.variable-menu') do - page.accept_alert 'Are you sure?' do - click_on 'Remove' - end - end - end - - scenario 'user does not see the deleted variable' do - expect(page).to have_no_css('.variables-table') - end - end + it_behaves_like 'variable list' end diff --git a/spec/features/groups/members/search_members_spec.rb b/spec/features/groups/members/search_members_spec.rb new file mode 100644 index 00000000000..31fbbcf562c --- /dev/null +++ b/spec/features/groups/members/search_members_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe 'Search group member' do + let(:user) { create :user } + let(:member) { create :user } + + let!(:guest_group) do + create(:group) do |group| + group.add_guest(user) + group.add_guest(member) + end + end + + before do + sign_in(user) + visit group_group_members_path(guest_group) + end + + it 'renders member users' do + page.within '.member-search-form' do + fill_in 'search', with: member.name + find('.member-search-btn').click + end + + group_members_list = find(".panel .content-list") + expect(group_members_list).to have_content(member.name) + expect(group_members_list).not_to have_content(user.name) + end +end diff --git a/spec/features/groups/milestone_spec.rb b/spec/features/groups/milestone_spec.rb index 1b41b3842c8..20337f1d3b0 100644 --- a/spec/features/groups/milestone_spec.rb +++ b/spec/features/groups/milestone_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -feature 'Group milestones', :js do +feature 'Group milestones' do let(:group) { create(:group) } let!(:project) { create(:project_empty_repo, group: group) } let(:user) { create(:group_member, :master, user: create(:user), group: group ).user } @@ -13,7 +13,7 @@ feature 'Group milestones', :js do sign_in(user) end - context 'create a milestone' do + context 'create a milestone', :js do before do visit new_group_milestone_path(group) end @@ -61,55 +61,132 @@ feature 'Group milestones', :js do end context 'milestones list' do - let!(:other_project) { create(:project_empty_repo, group: group) } - - let!(:active_project_milestone1) { create(:milestone, project: project, state: 'active', title: 'v1.0') } - let!(:active_project_milestone2) { create(:milestone, project: other_project, state: 'active', title: 'v1.0') } - let!(:closed_project_milestone1) { create(:milestone, project: project, state: 'closed', title: 'v2.0') } - let!(:closed_project_milestone2) { create(:milestone, project: other_project, state: 'closed', title: 'v2.0') } - let!(:active_group_milestone) { create(:milestone, group: group, state: 'active') } - let!(:closed_group_milestone) { create(:milestone, group: group, state: 'closed') } - - before do - visit group_milestones_path(group) + context 'when no milestones' do + it 'renders no milestones text' do + visit group_milestones_path(group) + expect(page).to have_content('No milestones to show') + end end - it 'counts milestones correctly' do - expect(find('.top-area .active .badge').text).to eq("2") - expect(find('.top-area .closed .badge').text).to eq("2") - expect(find('.top-area .all .badge').text).to eq("4") - end + context 'when milestones exists' do + let!(:other_project) { create(:project_empty_repo, group: group) } + + let!(:active_project_milestone1) do + create( + :milestone, + project: project, + state: 'active', + title: 'v1.0', + due_date: '2114-08-20', + description: 'Lorem Ipsum is simply dummy text' + ) + end + let!(:active_project_milestone2) { create(:milestone, project: other_project, state: 'active', title: 'v1.0') } + let!(:closed_project_milestone1) { create(:milestone, project: project, state: 'closed', title: 'v2.0') } + let!(:closed_project_milestone2) { create(:milestone, project: other_project, state: 'closed', title: 'v2.0') } + let!(:active_group_milestone) { create(:milestone, group: group, state: 'active', title: 'GL-113') } + let!(:closed_group_milestone) { create(:milestone, group: group, state: 'closed') } + let!(:issue) do + create :issue, project: project, assignees: [user], author: user, milestone: active_project_milestone1 + end - it 'lists legacy group milestones and group milestones' do - legacy_milestone = GroupMilestone.build_collection(group, group.projects, { state: 'active' }).first + before do + visit group_milestones_path(group) + end - expect(page).to have_selector("#milestone_#{active_group_milestone.id}", count: 1) - expect(page).to have_selector("#milestone_#{legacy_milestone.milestones.first.id}", count: 1) - end + it 'counts milestones correctly' do + expect(find('.top-area .active .badge').text).to eq("2") + expect(find('.top-area .closed .badge').text).to eq("2") + expect(find('.top-area .all .badge').text).to eq("4") + end - it 'updates milestone' do - page.within(".milestones #milestone_#{active_group_milestone.id}") do - click_link('Edit') + it 'lists legacy group milestones and group milestones' do + legacy_milestone = GroupMilestone.build_collection(group, group.projects, { state: 'active' }).first + + expect(page).to have_selector("#milestone_#{active_group_milestone.id}", count: 1) + expect(page).to have_selector("#milestone_#{legacy_milestone.milestones.first.id}", count: 1) end - page.within('.milestone-form') do - fill_in 'milestone_title', with: 'new title' - click_button('Update milestone') + it 'updates milestone' do + page.within(".milestones #milestone_#{active_group_milestone.id}") do + click_link('Edit') + end + + page.within('.milestone-form') do + fill_in 'milestone_title', with: 'new title' + click_button('Update milestone') + end + + expect(find('#content-body h2')).to have_content('new title') end - expect(find('#content-body h2')).to have_content('new title') - end + it 'shows milestone detail and supports its edit' do + page.within(".milestones #milestone_#{active_group_milestone.id}") do + click_link(active_group_milestone.title) + end + + page.within('.detail-page-header') do + click_link('Edit') + end - it 'shows milestone detail and supports its edit' do - page.within(".milestones #milestone_#{active_group_milestone.id}") do - click_link(active_group_milestone.title) + expect(page).to have_selector('.milestone-form') end - page.within('.detail-page-header') do - click_link('Edit') + it 'renders milestones' do + expect(page).to have_content('v1.0') + expect(page).to have_content('GL-113') + expect(page).to have_link( + '1 Issue', + href: issues_group_path(group, milestone_title: 'v1.0') + ) + expect(page).to have_link( + '0 Merge Requests', + href: merge_requests_group_path(group, milestone_title: 'v1.0') + ) end - expect(page).to have_selector('.milestone-form') + it 'renders group milestone details' do + click_link 'v1.0' + + expect(page).to have_content('expires on Aug 20, 2114') + expect(page).to have_content('v1.0') + expect(page).to have_content('Issues 1 Open: 1 Closed: 0') + expect(page).to have_link(issue.title, href: project_issue_path(issue.project, issue)) + end + + describe 'labels' do + before do + create(:label, project: project, title: 'bug') do |label| + issue.labels << label + end + + create(:label, project: project, title: 'feature') do |label| + issue.labels << label + end + end + + it 'renders labels' do + click_link 'v1.0' + + page.within('#tab-issues') do + expect(page).to have_content 'bug' + expect(page).to have_content 'feature' + end + end + + it 'renders labels list', :js do + click_link 'v1.0' + + page.within('.content .nav-links') do + page.find(:xpath, "//a[@href='#tab-labels']").click + end + + page.within('#tab-labels') do + expect(page).to have_content 'bug' + expect(page).to have_content 'feature' + end + end + end end end end diff --git a/spec/features/copy_as_gfm_spec.rb b/spec/features/markdown/copy_as_gfm_spec.rb index f82ed6300cc..f82ed6300cc 100644 --- a/spec/features/copy_as_gfm_spec.rb +++ b/spec/features/markdown/copy_as_gfm_spec.rb diff --git a/spec/features/gitlab_flavored_markdown_spec.rb b/spec/features/markdown/gitlab_flavored_markdown_spec.rb index 3c2186b3598..3c2186b3598 100644 --- a/spec/features/gitlab_flavored_markdown_spec.rb +++ b/spec/features/markdown/gitlab_flavored_markdown_spec.rb diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown/markdown_spec.rb index f13d78d24e3..f13d78d24e3 100644 --- a/spec/features/markdown_spec.rb +++ b/spec/features/markdown/markdown_spec.rb diff --git a/spec/features/markdown/math_spec.rb b/spec/features/markdown/math_spec.rb new file mode 100644 index 00000000000..6a23d6b78ab --- /dev/null +++ b/spec/features/markdown/math_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe 'Math rendering', :js do + it 'renders inline and display math correctly' do + description = <<~MATH + This math is inline $`a^2+b^2=c^2`$. + + This is on a separate line + ```math + a^2+b^2=c^2 + ``` + MATH + + project = create(:project, :public) + issue = create(:issue, project: project, description: description) + + visit project_issue_path(project, issue) + + expect(page).to have_selector('.katex .mord.mathit', text: 'b') + expect(page).to have_selector('.katex-display .mord.mathit', text: 'b') + end +end diff --git a/spec/features/markdown/mermaid_spec.rb b/spec/features/markdown/mermaid_spec.rb new file mode 100644 index 00000000000..a25d701ee35 --- /dev/null +++ b/spec/features/markdown/mermaid_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe 'Mermaid rendering', :js do + it 'renders Mermaid diagrams correctly' do + description = <<~MERMAID + ```mermaid + graph TD; + A-->B; + A-->C; + B-->D; + C-->D; + ``` + MERMAID + + project = create(:project, :public) + issue = create(:issue, project: project, description: description) + + visit project_issue_path(project, issue) + + %w[A B C D].each do |label| + expect(page).to have_selector('svg foreignObject', text: label) + end + end +end diff --git a/spec/features/profiles/password_spec.rb b/spec/features/profiles/password_spec.rb index 4665626f114..1d7700b6767 100644 --- a/spec/features/profiles/password_spec.rb +++ b/spec/features/profiles/password_spec.rb @@ -1,6 +1,15 @@ require 'spec_helper' describe 'Profile > Password' do + let(:user) { create(:user) } + + def fill_passwords(password, confirmation) + fill_in 'New password', with: password + fill_in 'Password confirmation', with: confirmation + + click_button 'Save password' + end + context 'Password authentication enabled' do let(:user) { create(:user, password_automatically_set: true) } @@ -9,13 +18,6 @@ describe 'Profile > Password' do visit edit_profile_password_path end - def fill_passwords(password, confirmation) - fill_in 'New password', with: password - fill_in 'Password confirmation', with: confirmation - - click_button 'Save password' - end - context 'User with password automatically set' do describe 'User puts different passwords in the field and in the confirmation' do it 'shows an error message' do @@ -73,4 +75,64 @@ describe 'Profile > Password' do end end end + + context 'Change passowrd' do + before do + sign_in(user) + visit(edit_profile_password_path) + end + + it 'does not change user passowrd without old one' do + page.within '.update-password' do + fill_passwords('22233344', '22233344') + end + + page.within '.flash-container' do + expect(page).to have_content 'You must provide a valid current password' + end + end + + it 'does not change password with invalid old password' do + page.within '.update-password' do + fill_in 'user_current_password', with: 'invalid' + fill_passwords('password', 'confirmation') + end + + page.within '.flash-container' do + expect(page).to have_content 'You must provide a valid current password' + end + end + + it 'changes user password' do + page.within '.update-password' do + fill_in "user_current_password", with: user.password + fill_passwords('22233344', '22233344') + end + + expect(current_path).to eq new_user_session_path + end + end + + context 'when password is expired' do + before do + sign_in(user) + + user.update_attributes(password_expires_at: 1.hour.ago) + user.identities.delete + expect(user.ldap_user?).to eq false + end + + it 'needs change user password' do + visit edit_profile_password_path + + expect(current_path).to eq new_profile_password_path + + fill_in :user_current_password, with: user.password + fill_in :user_password, with: '12345678' + fill_in :user_password_confirmation, with: '12345678' + click_button 'Set new password' + + expect(current_path).to eq new_user_session_path + end + end end diff --git a/spec/features/profiles/user_edit_profile_spec.rb b/spec/features/profiles/user_edit_profile_spec.rb new file mode 100644 index 00000000000..0b5eacbe916 --- /dev/null +++ b/spec/features/profiles/user_edit_profile_spec.rb @@ -0,0 +1,58 @@ +require 'spec_helper' + +describe 'User edit profile' do + let(:user) { create(:user) } + + before do + sign_in(user) + visit(profile_path) + end + + it 'changes user profile' do + fill_in 'user_skype', with: 'testskype' + fill_in 'user_linkedin', with: 'testlinkedin' + fill_in 'user_twitter', with: 'testtwitter' + fill_in 'user_website_url', with: 'testurl' + fill_in 'user_location', with: 'Ukraine' + fill_in 'user_bio', with: 'I <3 GitLab' + fill_in 'user_organization', with: 'GitLab' + click_button 'Update profile settings' + + expect(user.reload).to have_attributes( + skype: 'testskype', + linkedin: 'testlinkedin', + twitter: 'testtwitter', + website_url: 'testurl', + bio: 'I <3 GitLab', + organization: 'GitLab' + ) + + expect(find('#user_location').value).to eq 'Ukraine' + expect(page).to have_content('Profile was successfully updated') + end + + context 'user avatar' do + before do + attach_file(:user_avatar, Rails.root.join('spec', 'fixtures', 'banana_sample.gif')) + click_button 'Update profile settings' + end + + it 'changes user avatar' do + expect(page).to have_link('Remove avatar') + + user.reload + expect(user.avatar).to be_instance_of AvatarUploader + expect(user.avatar.url).to eq "/uploads/-/system/user/avatar/#{user.id}/banana_sample.gif" + end + + it 'removes user avatar' do + click_link 'Remove avatar' + + user.reload + + expect(user.avatar?).to eq false + expect(page).not_to have_link('Remove avatar') + expect(page).to have_link('gravatar.com') + end + end +end diff --git a/spec/features/profiles/user_manages_applications_spec.rb b/spec/features/profiles/user_manages_applications_spec.rb new file mode 100644 index 00000000000..387584fef62 --- /dev/null +++ b/spec/features/profiles/user_manages_applications_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' + +describe 'User manages applications' do + let(:user) { create(:user) } + + before do + sign_in(user) + visit applications_profile_path + end + + it 'manages applications' do + expect(page).to have_content 'Add new application' + + fill_in :doorkeeper_application_name, with: 'test' + fill_in :doorkeeper_application_redirect_uri, with: 'https://test.com' + click_on 'Save application' + + expect(page).to have_content 'Application: test' + expect(page).to have_content 'Application Id' + expect(page).to have_content 'Secret' + + click_on 'Edit' + + expect(page).to have_content 'Edit application' + fill_in :doorkeeper_application_name, with: 'test_changed' + click_on 'Save application' + + expect(page).to have_content 'test_changed' + expect(page).to have_content 'Application Id' + expect(page).to have_content 'Secret' + + visit applications_profile_path + + page.within '.oauth-applications' do + click_on 'Destroy' + end + expect(page.find('.oauth-applications')).not_to have_content 'test_changed' + end +end diff --git a/spec/features/profiles/user_visits_profile_authentication_log_spec.rb b/spec/features/profiles/user_visits_profile_authentication_log_spec.rb index a50ebb29e01..0f419c3c2c0 100644 --- a/spec/features/profiles/user_visits_profile_authentication_log_spec.rb +++ b/spec/features/profiles/user_visits_profile_authentication_log_spec.rb @@ -3,13 +3,28 @@ require 'spec_helper' describe 'User visits the authentication log' do let(:user) { create(:user) } - before do - sign_in(user) + context 'when user signed in' do + before do + sign_in(user) + end - visit(audit_log_profile_path) + it 'shows correct menu item' do + visit(audit_log_profile_path) + + expect(page).to have_active_navigation('Authentication log') + end end - it 'shows correct menu item' do - expect(page).to have_active_navigation('Authentication log') + context 'when user has activity' do + before do + create(:closed_issue_event, author: user) + gitlab_sign_in(user) + end + + it 'shows user activity' do + visit(audit_log_profile_path) + + expect(page).to have_content 'Signed in with standard authentication' + end end end diff --git a/spec/features/profiles/user_visits_profile_spec.rb b/spec/features/profiles/user_visits_profile_spec.rb index 6601d3039ed..713112477c8 100644 --- a/spec/features/profiles/user_visits_profile_spec.rb +++ b/spec/features/profiles/user_visits_profile_spec.rb @@ -5,11 +5,58 @@ describe 'User visits their profile' do before do sign_in(user) - - visit(profile_path) end it 'shows correct menu item' do + visit(profile_path) + expect(page).to have_active_navigation('Profile') end + + it 'shows profile info' do + visit(profile_path) + + expect(page).to have_content "This information will appear on your profile" + end + + context 'when user has groups' do + let(:group) do + create :group do |group| + group.add_owner(user) + end + end + + let!(:project) do + create(:project, :repository, namespace: group) do |project| + create(:closed_issue_event, project: project) + project.add_master(user) + end + end + + def click_on_profile_picture + find(:css, '.header-user-dropdown-toggle').click + + page.within ".header-user" do + click_link "Profile" + end + end + + it 'shows user groups', :js do + visit(profile_path) + click_on_profile_picture + + page.within ".cover-block" do + expect(page).to have_content user.name + expect(page).to have_content user.username + end + + page.within ".content" do + click_link "Groups" + end + + page.within "#groups" do + expect(page).to have_content group.name + end + end + end end diff --git a/spec/features/project_variables_spec.rb b/spec/features/project_variables_spec.rb new file mode 100644 index 00000000000..0ba2224359a --- /dev/null +++ b/spec/features/project_variables_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +describe 'Project variables', :js do + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:variable) { create(:ci_variable, key: 'test_key', value: 'test value') } + let(:page_path) { project_settings_ci_cd_path(project) } + + before do + sign_in(user) + project.add_master(user) + project.variables << variable + + visit page_path + end + + it_behaves_like 'variable list' +end diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb index 02dbd3380b3..4d47cdb500c 100644 --- a/spec/features/projects/clusters/gcp_spec.rb +++ b/spec/features/projects/clusters/gcp_spec.rb @@ -25,7 +25,7 @@ feature 'Gcp Cluster', :js do context 'when user has a GCP project with billing enabled' do before do allow_any_instance_of(Projects::Clusters::GcpController).to receive(:authorize_google_project_billing) - allow_any_instance_of(Projects::Clusters::GcpController).to receive(:google_project_billing_status).and_return('true') + allow_any_instance_of(Projects::Clusters::GcpController).to receive(:google_project_billing_status).and_return(true) end context 'when user does not have a cluster and visits cluster index page' do @@ -134,7 +134,7 @@ feature 'Gcp Cluster', :js do context 'when user does not have a GCP project with billing enabled' do before do allow_any_instance_of(Projects::Clusters::GcpController).to receive(:authorize_google_project_billing) - allow_any_instance_of(Projects::Clusters::GcpController).to receive(:google_project_billing_status).and_return('false') + allow_any_instance_of(Projects::Clusters::GcpController).to receive(:google_project_billing_status).and_return(false) visit project_clusters_path(project) diff --git a/spec/features/projects/import_export/namespace_export_file_spec.rb b/spec/features/projects/import_export/namespace_export_file_spec.rb index c1fccf4a40b..7d056b0c140 100644 --- a/spec/features/projects/import_export/namespace_export_file_spec.rb +++ b/spec/features/projects/import_export/namespace_export_file_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Import/Export - Namespace export file cleanup', :js do +describe 'Import/Export - Namespace export file cleanup', :js do let(:export_path) { Dir.mktmpdir('namespace_export_file_spec') } before do @@ -42,13 +42,13 @@ feature 'Import/Export - Namespace export file cleanup', :js do end describe 'legacy storage' do - let(:project) { create(:project) } + let(:project) { create(:project, :legacy_storage) } it_behaves_like 'handling project exports on namespace change' end describe 'hashed storage' do - let(:project) { create(:project, :hashed) } + let(:project) { create(:project) } it_behaves_like 'handling project exports on namespace change' end diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index 592c99fc64a..37a06b65481 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -109,7 +109,8 @@ describe 'Pipelines', :js do context 'when canceling' do before do - accept_confirm { find('.js-pipelines-cancel-button').click } + find('.js-pipelines-cancel-button').click + find('.js-primary-button').click wait_for_requests end @@ -140,6 +141,7 @@ describe 'Pipelines', :js do context 'when retrying' do before do find('.js-pipelines-retry-button').click + find('.js-primary-button').click wait_for_requests end @@ -238,7 +240,8 @@ describe 'Pipelines', :js do context 'when canceling' do before do - accept_alert { find('.js-pipelines-cancel-button').click } + find('.js-pipelines-cancel-button').click + find('.js-primary-button').click end it 'indicates that pipeline was canceled' do diff --git a/spec/features/projects/services/user_activates_issue_tracker_spec.rb b/spec/features/projects/services/user_activates_issue_tracker_spec.rb new file mode 100644 index 00000000000..e9502178bd7 --- /dev/null +++ b/spec/features/projects/services/user_activates_issue_tracker_spec.rb @@ -0,0 +1,92 @@ +require 'spec_helper' + +describe 'User activates issue tracker', :js do + let(:user) { create(:user) } + let(:project) { create(:project) } + + let(:url) { 'http://tracker.example.com' } + + def fill_form(active = true) + check 'Active' if active + + fill_in 'service_project_url', with: url + fill_in 'service_issues_url', with: "#{url}/:id" + fill_in 'service_new_issue_url', with: url + end + + before do + project.add_master(user) + sign_in(user) + + visit project_settings_integrations_path(project) + end + + shared_examples 'external issue tracker activation' do |tracker:| + describe 'user sets and activates the Service' do + context 'when the connection test succeeds' do + before do + stub_request(:head, url).to_return(headers: { 'Content-Type' => 'application/json' }) + + click_link(tracker) + fill_form + click_button('Test settings and save changes') + wait_for_requests + end + + it 'activates the service' do + expect(page).to have_content("#{tracker} activated.") + expect(current_path).to eq(project_settings_integrations_path(project)) + end + + it 'shows the link in the menu' do + page.within('.nav-sidebar') do + expect(page).to have_link(tracker, href: url) + end + end + end + + context 'when the connection test fails' do + it 'activates the service' do + stub_request(:head, url).to_raise(HTTParty::Error) + + click_link(tracker) + fill_form + click_button('Test settings and save changes') + wait_for_requests + + expect(find('.flash-container-page')).to have_content 'Test failed.' + expect(find('.flash-container-page')).to have_content 'Save anyway' + + find('.flash-alert .flash-action').click + wait_for_requests + + expect(page).to have_content("#{tracker} activated.") + expect(current_path).to eq(project_settings_integrations_path(project)) + end + end + end + + describe 'user sets the service but keeps it disabled' do + before do + click_link(tracker) + fill_form(false) + click_button('Save changes') + end + + it 'saves but does not activate the service' do + expect(page).to have_content("#{tracker} settings saved, but not activated.") + expect(current_path).to eq(project_settings_integrations_path(project)) + end + + it 'does not show the external tracker link in the menu' do + page.within('.nav-sidebar') do + expect(page).not_to have_link(tracker, href: url) + end + end + end + end + + it_behaves_like 'external issue tracker activation', tracker: 'Redmine' + it_behaves_like 'external issue tracker activation', tracker: 'Bugzilla' + it_behaves_like 'external issue tracker activation', tracker: 'Custom Issue Tracker' +end diff --git a/spec/features/projects/services/user_activates_jira_spec.rb b/spec/features/projects/services/user_activates_jira_spec.rb index 028669eeaf2..429128ec096 100644 --- a/spec/features/projects/services/user_activates_jira_spec.rb +++ b/spec/features/projects/services/user_activates_jira_spec.rb @@ -3,7 +3,6 @@ require 'spec_helper' describe 'User activates Jira', :js do let(:user) { create(:user) } let(:project) { create(:project) } - let(:service) { project.create_jira_service } let(:url) { 'http://jira.example.com' } let(:test_url) { 'http://jira.example.com/rest/api/2/serverInfo' } @@ -26,7 +25,7 @@ describe 'User activates Jira', :js do describe 'user sets and activates Jira Service' do context 'when Jira connection test succeeds' do - it 'activates the JIRA service' do + before do server_info = { key: 'value' }.to_json WebMock.stub_request(:get, test_url).with(basic_auth: %w(username password)).to_return(body: server_info) @@ -34,10 +33,18 @@ describe 'User activates Jira', :js do fill_form click_button('Test settings and save changes') wait_for_requests + end + it 'activates the JIRA service' do expect(page).to have_content('JIRA activated.') expect(current_path).to eq(project_settings_integrations_path(project)) end + + it 'shows the JIRA link in the menu' do + page.within('.nav-sidebar') do + expect(page).to have_link('JIRA', href: url) + end + end end context 'when Jira connection test fails' do @@ -75,14 +82,20 @@ describe 'User activates Jira', :js do end describe 'user sets Jira Service but keeps it disabled' do - context 'when Jira connection test succeeds' do - it 'activates the JIRA service' do - click_link('JIRA') - fill_form(false) - click_button('Save changes') + before do + click_link('JIRA') + fill_form(false) + click_button('Save changes') + end - expect(page).to have_content('JIRA settings saved, but not activated.') - expect(current_path).to eq(project_settings_integrations_path(project)) + it 'saves but does not activate the JIRA service' do + expect(page).to have_content('JIRA settings saved, but not activated.') + expect(current_path).to eq(project_settings_integrations_path(project)) + end + + it 'does not show the JIRA link in the menu' do + page.within('.nav-sidebar') do + expect(page).not_to have_link('JIRA', href: url) end end end diff --git a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb index 4d2a08afecc..ef1bb712846 100644 --- a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb +++ b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb @@ -1,226 +1,234 @@ require 'spec_helper' -# Remove skip_gitaly_mock flag when gitaly_update_page implements moving pages -describe 'User updates wiki page', :skip_gitaly_mock do - let(:user) { create(:user) } - - before do - project.add_master(user) - sign_in(user) - end - - context 'when wiki is empty' do +describe 'User updates wiki page' do + shared_examples 'wiki page user update' do + let(:user) { create(:user) } before do - visit(project_wikis_path(project)) + project.add_master(user) + sign_in(user) end - context 'in a user namespace' do - let(:project) { create(:project, namespace: user.namespace) } - - it 'redirects back to the home edit page' do - page.within(:css, '.wiki-form .form-actions') do - click_on('Cancel') - end - - expect(current_path).to eq project_wiki_path(project, :home) + context 'when wiki is empty' do + before do + visit(project_wikis_path(project)) end - it 'updates a page that has a path', :js do - click_on('New page') + context 'in a user namespace' do + let(:project) { create(:project, namespace: user.namespace) } - page.within('#modal-new-wiki') do - fill_in(:new_wiki_path, with: 'one/two/three-test') - click_on('Create page') - end + it 'redirects back to the home edit page' do + page.within(:css, '.wiki-form .form-actions') do + click_on('Cancel') + end - page.within '.wiki-form' do - fill_in(:wiki_content, with: 'wiki content') - click_on('Create page') + expect(current_path).to eq project_wiki_path(project, :home) end - expect(current_path).to include('one/two/three-test') - expect(find('.wiki-pages')).to have_content('Three') + it 'updates a page that has a path', :js do + click_on('New page') - first(:link, text: 'Three').click + page.within('#modal-new-wiki') do + fill_in(:new_wiki_path, with: 'one/two/three-test') + click_on('Create page') + end - expect(find('.nav-text')).to have_content('Three') + page.within '.wiki-form' do + fill_in(:wiki_content, with: 'wiki content') + click_on('Create page') + end - click_on('Edit') + expect(current_path).to include('one/two/three-test') + expect(find('.wiki-pages')).to have_content('Three') - expect(current_path).to include('one/two/three-test') - expect(page).to have_content('Edit Page') + first(:link, text: 'Three').click - fill_in('Content', with: 'Updated Wiki Content') - click_on('Save changes') + expect(find('.nav-text')).to have_content('Three') - expect(page).to have_content('Updated Wiki Content') - end - end - end + click_on('Edit') - context 'when wiki is not empty' do - let(:project_wiki) { create(:project_wiki, project: project, user: project.creator) } - let!(:wiki_page) { create(:wiki_page, wiki: project_wiki, attrs: { title: 'home', content: 'Home page' }) } + expect(current_path).to include('one/two/three-test') + expect(page).to have_content('Edit Page') - before do - visit(project_wikis_path(project)) + fill_in('Content', with: 'Updated Wiki Content') + click_on('Save changes') + + expect(page).to have_content('Updated Wiki Content') + end + end end - context 'in a user namespace' do - let(:project) { create(:project, namespace: user.namespace) } + context 'when wiki is not empty' do + let(:project_wiki) { create(:project_wiki, project: project, user: project.creator) } + let!(:wiki_page) { create(:wiki_page, wiki: project_wiki, attrs: { title: 'home', content: 'Home page' }) } - it 'updates a page' do - click_link('Edit') + before do + visit(project_wikis_path(project)) + end - # Commit message field should have correct value. - expect(page).to have_field('wiki[message]', with: 'Update home') + context 'in a user namespace' do + let(:project) { create(:project, namespace: user.namespace) } - fill_in(:wiki_content, with: 'My awesome wiki!') - click_button('Save changes') + it 'updates a page' do + click_link('Edit') - expect(page).to have_content('Home') - expect(page).to have_content("Last edited by #{user.name}") - expect(page).to have_content('My awesome wiki!') - end + # Commit message field should have correct value. + expect(page).to have_field('wiki[message]', with: 'Update home') - it 'shows a validation error message' do - click_link('Edit') + fill_in(:wiki_content, with: 'My awesome wiki!') + click_button('Save changes') - fill_in(:wiki_content, with: '') - click_button('Save changes') + expect(page).to have_content('Home') + expect(page).to have_content("Last edited by #{user.name}") + expect(page).to have_content('My awesome wiki!') + end - expect(page).to have_selector('.wiki-form') - expect(page).to have_content('Edit Page') - expect(page).to have_content('The form contains the following error:') - expect(page).to have_content("Content can't be blank") - expect(find('textarea#wiki_content').value).to eq('') - end + it 'shows a validation error message' do + click_link('Edit') - it 'shows the autocompletion dropdown', :js do - click_link('Edit') + fill_in(:wiki_content, with: '') + click_button('Save changes') - find('#wiki_content').native.send_keys('') - fill_in(:wiki_content, with: '@') + expect(page).to have_selector('.wiki-form') + expect(page).to have_content('Edit Page') + expect(page).to have_content('The form contains the following error:') + expect(page).to have_content("Content can't be blank") + expect(find('textarea#wiki_content').value).to eq('') + end - expect(page).to have_selector('.atwho-view') - end + it 'shows the autocompletion dropdown', :js do + click_link('Edit') - it 'shows the error message' do - click_link('Edit') + find('#wiki_content').native.send_keys('') + fill_in(:wiki_content, with: '@') - wiki_page.update(content: 'Update') + expect(page).to have_selector('.atwho-view') + end - click_button('Save changes') + it 'shows the error message' do + click_link('Edit') - expect(page).to have_content('Someone edited the page the same time you did.') - end + wiki_page.update(content: 'Update') - it 'updates a page' do - click_on('Edit') - fill_in('Content', with: 'Updated Wiki Content') - click_on('Save changes') + click_button('Save changes') - expect(page).to have_content('Updated Wiki Content') - end + expect(page).to have_content('Someone edited the page the same time you did.') + end - it 'cancels edititng of a page' do - click_on('Edit') + it 'updates a page' do + click_on('Edit') + fill_in('Content', with: 'Updated Wiki Content') + click_on('Save changes') - page.within(:css, '.wiki-form .form-actions') do - click_on('Cancel') + expect(page).to have_content('Updated Wiki Content') end - expect(current_path).to eq(project_wiki_path(project, wiki_page)) + it 'cancels edititng of a page' do + click_on('Edit') + + page.within(:css, '.wiki-form .form-actions') do + click_on('Cancel') + end + + expect(current_path).to eq(project_wiki_path(project, wiki_page)) + end end - end - context 'in a group namespace' do - let(:project) { create(:project, namespace: create(:group, :public)) } + context 'in a group namespace' do + let(:project) { create(:project, namespace: create(:group, :public)) } - it 'updates a page' do - click_link('Edit') + it 'updates a page' do + click_link('Edit') - # Commit message field should have correct value. - expect(page).to have_field('wiki[message]', with: 'Update home') + # Commit message field should have correct value. + expect(page).to have_field('wiki[message]', with: 'Update home') - fill_in(:wiki_content, with: 'My awesome wiki!') + fill_in(:wiki_content, with: 'My awesome wiki!') - click_button('Save changes') + click_button('Save changes') - expect(page).to have_content('Home') - expect(page).to have_content("Last edited by #{user.name}") - expect(page).to have_content('My awesome wiki!') + expect(page).to have_content('Home') + expect(page).to have_content("Last edited by #{user.name}") + expect(page).to have_content('My awesome wiki!') + end end end - end - context 'when the page is in a subdir' do - let!(:project) { create(:project, namespace: user.namespace) } - let(:project_wiki) { create(:project_wiki, project: project, user: project.creator) } - let(:page_name) { 'page_name' } - let(:page_dir) { "foo/bar/#{page_name}" } - let!(:wiki_page) { create(:wiki_page, wiki: project_wiki, attrs: { title: page_dir, content: 'Home page' }) } + context 'when the page is in a subdir' do + let!(:project) { create(:project, namespace: user.namespace) } + let(:project_wiki) { create(:project_wiki, project: project, user: project.creator) } + let(:page_name) { 'page_name' } + let(:page_dir) { "foo/bar/#{page_name}" } + let!(:wiki_page) { create(:wiki_page, wiki: project_wiki, attrs: { title: page_dir, content: 'Home page' }) } - before do - visit(project_wiki_edit_path(project, wiki_page)) - end + before do + visit(project_wiki_edit_path(project, wiki_page)) + end - it 'moves the page to the root folder' do - fill_in(:wiki_title, with: "/#{page_name}") + it 'moves the page to the root folder', :skip_gitaly_mock do + fill_in(:wiki_title, with: "/#{page_name}") - click_button('Save changes') + click_button('Save changes') - expect(current_path).to eq(project_wiki_path(project, page_name)) - end + expect(current_path).to eq(project_wiki_path(project, page_name)) + end - it 'moves the page to other dir' do - new_page_dir = "foo1/bar1/#{page_name}" + it 'moves the page to other dir' do + new_page_dir = "foo1/bar1/#{page_name}" - fill_in(:wiki_title, with: new_page_dir) + fill_in(:wiki_title, with: new_page_dir) - click_button('Save changes') + click_button('Save changes') - expect(current_path).to eq(project_wiki_path(project, new_page_dir)) - end + expect(current_path).to eq(project_wiki_path(project, new_page_dir)) + end - it 'remains in the same place if title has not changed' do - original_path = project_wiki_path(project, wiki_page) + it 'remains in the same place if title has not changed' do + original_path = project_wiki_path(project, wiki_page) - fill_in(:wiki_title, with: page_name) + fill_in(:wiki_title, with: page_name) - click_button('Save changes') + click_button('Save changes') - expect(current_path).to eq(original_path) - end + expect(current_path).to eq(original_path) + end - it 'can be moved to a different dir with a different name' do - new_page_dir = "foo1/bar1/new_page_name" + it 'can be moved to a different dir with a different name' do + new_page_dir = "foo1/bar1/new_page_name" - fill_in(:wiki_title, with: new_page_dir) + fill_in(:wiki_title, with: new_page_dir) - click_button('Save changes') + click_button('Save changes') - expect(current_path).to eq(project_wiki_path(project, new_page_dir)) - end + expect(current_path).to eq(project_wiki_path(project, new_page_dir)) + end - it 'can be renamed and moved to the root folder' do - new_name = 'new_page_name' + it 'can be renamed and moved to the root folder' do + new_name = 'new_page_name' - fill_in(:wiki_title, with: "/#{new_name}") + fill_in(:wiki_title, with: "/#{new_name}") - click_button('Save changes') + click_button('Save changes') - expect(current_path).to eq(project_wiki_path(project, new_name)) - end + expect(current_path).to eq(project_wiki_path(project, new_name)) + end - it 'squishes the title before creating the page' do - new_page_dir = " foo1 / bar1 / #{page_name} " + it 'squishes the title before creating the page' do + new_page_dir = " foo1 / bar1 / #{page_name} " - fill_in(:wiki_title, with: new_page_dir) + fill_in(:wiki_title, with: new_page_dir) - click_button('Save changes') + click_button('Save changes') - expect(current_path).to eq(project_wiki_path(project, "foo1/bar1/#{page_name}")) + expect(current_path).to eq(project_wiki_path(project, "foo1/bar1/#{page_name}")) + end end end + + context 'when Gitaly is enabled' do + it_behaves_like 'wiki page user update' + end + + context 'when Gitaly is disabled', :skip_gitaly_mock do + it_behaves_like 'wiki page user update' + end end diff --git a/spec/features/projects/wiki/user_views_wiki_page_spec.rb b/spec/features/projects/wiki/user_views_wiki_page_spec.rb index e37436838fd..306e382119a 100644 --- a/spec/features/projects/wiki/user_views_wiki_page_spec.rb +++ b/spec/features/projects/wiki/user_views_wiki_page_spec.rb @@ -1,146 +1,155 @@ require 'spec_helper' -# Remove skip_gitaly_mock flag when gitaly_update_page implements moving pages -describe 'User views a wiki page', :skip_gitaly_mock do - let(:user) { create(:user) } - let(:project) { create(:project, namespace: user.namespace) } - let(:wiki_page) do - create(:wiki_page, - wiki: project.wiki, - attrs: { title: 'home', content: 'Look at this [image](image.jpg)\n\n ![alt text](image.jpg)' }) - end - - before do - project.add_master(user) - sign_in(user) - end +describe 'User views a wiki page' do + shared_examples 'wiki page user view' do + let(:user) { create(:user) } + let(:project) { create(:project, namespace: user.namespace) } + let(:wiki_page) do + create(:wiki_page, + wiki: project.wiki, + attrs: { title: 'home', content: 'Look at this [image](image.jpg)\n\n ![alt text](image.jpg)' }) + end - context 'when wiki is empty' do before do - visit(project_wikis_path(project)) + project.add_master(user) + sign_in(user) + end - click_on('New page') + context 'when wiki is empty' do + before do + visit(project_wikis_path(project)) - page.within('#modal-new-wiki') do - fill_in(:new_wiki_path, with: 'one/two/three-test') - click_on('Create page') - end + click_on('New page') - page.within('.wiki-form') do - fill_in(:wiki_content, with: 'wiki content') - click_on('Create page') + page.within('#modal-new-wiki') do + fill_in(:new_wiki_path, with: 'one/two/three-test') + click_on('Create page') + end + + page.within('.wiki-form') do + fill_in(:wiki_content, with: 'wiki content') + click_on('Create page') + end end - end - it 'shows the history of a page that has a path', :js do - expect(current_path).to include('one/two/three-test') + it 'shows the history of a page that has a path', :js do + expect(current_path).to include('one/two/three-test') - first(:link, text: 'Three').click - click_on('Page history') + first(:link, text: 'Three').click + click_on('Page history') - expect(current_path).to include('one/two/three-test') + expect(current_path).to include('one/two/three-test') - page.within(:css, '.nav-text') do - expect(page).to have_content('History') + page.within(:css, '.nav-text') do + expect(page).to have_content('History') + end end - end - it 'shows an old version of a page', :js do - expect(current_path).to include('one/two/three-test') - expect(find('.wiki-pages')).to have_content('Three') + it 'shows an old version of a page', :js do + expect(current_path).to include('one/two/three-test') + expect(find('.wiki-pages')).to have_content('Three') - first(:link, text: 'Three').click + first(:link, text: 'Three').click - expect(find('.nav-text')).to have_content('Three') + expect(find('.nav-text')).to have_content('Three') - click_on('Edit') + click_on('Edit') - expect(current_path).to include('one/two/three-test') - expect(page).to have_content('Edit Page') + expect(current_path).to include('one/two/three-test') + expect(page).to have_content('Edit Page') - fill_in('Content', with: 'Updated Wiki Content') + fill_in('Content', with: 'Updated Wiki Content') - click_on('Save changes') - click_on('Page history') + click_on('Save changes') + click_on('Page history') - page.within(:css, '.nav-text') do - expect(page).to have_content('History') - end + page.within(:css, '.nav-text') do + expect(page).to have_content('History') + end - find('a[href*="?version_id"]') + find('a[href*="?version_id"]') + end end - end - context 'when a page does not have history' do - before do - visit(project_wiki_path(project, wiki_page)) - end + context 'when a page does not have history' do + before do + visit(project_wiki_path(project, wiki_page)) + end - it 'shows all the pages' do - expect(page).to have_content(user.name) - expect(find('.wiki-pages')).to have_content(wiki_page.title.capitalize) - end + it 'shows all the pages' do + expect(page).to have_content(user.name) + expect(find('.wiki-pages')).to have_content(wiki_page.title.capitalize) + end - it 'shows a file stored in a page' do - gollum_file_double = double('Gollum::File', - mime_type: 'image/jpeg', - name: 'images/image.jpg', - path: 'images/image.jpg', - raw_data: '') - wiki_file = Gitlab::Git::WikiFile.new(gollum_file_double) + it 'shows a file stored in a page' do + gollum_file_double = double('Gollum::File', + mime_type: 'image/jpeg', + name: 'images/image.jpg', + path: 'images/image.jpg', + raw_data: '') + wiki_file = Gitlab::Git::WikiFile.new(gollum_file_double) - allow(wiki_file).to receive(:mime_type).and_return('image/jpeg') - allow_any_instance_of(ProjectWiki).to receive(:find_file).with('image.jpg', nil).and_return(wiki_file) + allow(wiki_file).to receive(:mime_type).and_return('image/jpeg') + allow_any_instance_of(ProjectWiki).to receive(:find_file).with('image.jpg', nil).and_return(wiki_file) - expect(page).to have_xpath('//img[@data-src="image.jpg"]') - expect(page).to have_link('image', href: "#{project.wiki.wiki_base_path}/image.jpg") + expect(page).to have_xpath('//img[@data-src="image.jpg"]') + expect(page).to have_link('image', href: "#{project.wiki.wiki_base_path}/image.jpg") - click_on('image') + click_on('image') - expect(current_path).to match('wikis/image.jpg') - expect(page).not_to have_xpath('/html') # Page should render the image which means there is no html involved - end + expect(current_path).to match('wikis/image.jpg') + expect(page).not_to have_xpath('/html') # Page should render the image which means there is no html involved + end - it 'shows the creation page if file does not exist' do - expect(page).to have_link('image', href: "#{project.wiki.wiki_base_path}/image.jpg") + it 'shows the creation page if file does not exist' do + expect(page).to have_link('image', href: "#{project.wiki.wiki_base_path}/image.jpg") - click_on('image') + click_on('image') - expect(current_path).to match('wikis/image.jpg') - expect(page).to have_content('New Wiki Page') - expect(page).to have_content('Create page') + expect(current_path).to match('wikis/image.jpg') + expect(page).to have_content('New Wiki Page') + expect(page).to have_content('Create page') + end end - end - context 'when a page has history' do - before do - wiki_page.update(message: 'updated home', content: 'updated [some link](other-page)') - end + context 'when a page has history' do + before do + wiki_page.update(message: 'updated home', content: 'updated [some link](other-page)') + end - it 'shows the page history' do - visit(project_wiki_path(project, wiki_page)) + it 'shows the page history' do + visit(project_wiki_path(project, wiki_page)) - expect(page).to have_selector('a.btn', text: 'Edit') + expect(page).to have_selector('a.btn', text: 'Edit') - click_on('Page history') + click_on('Page history') - expect(page).to have_content(user.name) - expect(page).to have_content("#{user.username} created page: home") - expect(page).to have_content('updated home') + expect(page).to have_content(user.name) + expect(page).to have_content("#{user.username} created page: home") + expect(page).to have_content('updated home') + end + + it 'does not show the "Edit" button' do + visit(project_wiki_path(project, wiki_page, version_id: wiki_page.versions.last.id)) + + expect(page).not_to have_selector('a.btn', text: 'Edit') + end end - it 'does not show the "Edit" button' do - visit(project_wiki_path(project, wiki_page, version_id: wiki_page.versions.last.id)) + it 'opens a default wiki page', :js do + visit(project_path(project)) - expect(page).not_to have_selector('a.btn', text: 'Edit') + find('.shortcuts-wiki').click + + expect(page).to have_content('Home · Create Page') end end - it 'opens a default wiki page', :js do - visit(project_path(project)) - - find('.shortcuts-wiki').click + context 'when Gitaly is enabled' do + it_behaves_like 'wiki page user view' + end - expect(page).to have_content('Home · Create Page') + context 'when Gitaly is disabled', :skip_gitaly_mock do + it_behaves_like 'wiki page user view' end end diff --git a/spec/features/variables_spec.rb b/spec/features/variables_spec.rb deleted file mode 100644 index 79ca2b4bb4a..00000000000 --- a/spec/features/variables_spec.rb +++ /dev/null @@ -1,145 +0,0 @@ -require 'spec_helper' - -describe 'Project variables', :js do - let(:user) { create(:user) } - let(:project) { create(:project) } - let(:variable) { create(:ci_variable, key: 'test_key', value: 'test value') } - - before do - sign_in(user) - project.add_master(user) - project.variables << variable - - visit project_settings_ci_cd_path(project) - end - - it 'shows list of variables' do - page.within('.variables-table') do - expect(page).to have_content(variable.key) - end - end - - it 'adds new secret variable' do - fill_in('variable_key', with: 'key') - fill_in('variable_value', with: 'key value') - click_button('Add new variable') - - expect(page).to have_content('Variable was successfully created.') - page.within('.variables-table') do - expect(page).to have_content('key') - expect(page).to have_content('No') - end - end - - it 'adds empty variable' do - fill_in('variable_key', with: 'new_key') - fill_in('variable_value', with: '') - click_button('Add new variable') - - expect(page).to have_content('Variable was successfully created.') - page.within('.variables-table') do - expect(page).to have_content('new_key') - end - end - - it 'adds new protected variable' do - fill_in('variable_key', with: 'key') - fill_in('variable_value', with: 'value') - check('Protected') - click_button('Add new variable') - - expect(page).to have_content('Variable was successfully created.') - page.within('.variables-table') do - expect(page).to have_content('key') - expect(page).to have_content('Yes') - end - end - - it 'reveals and hides new variable' do - fill_in('variable_key', with: 'key') - fill_in('variable_value', with: 'key value') - click_button('Add new variable') - - page.within('.variables-table') do - expect(page).to have_content('key') - expect(page).to have_content('******') - end - - click_button('Reveal values') - - page.within('.variables-table') do - expect(page).to have_content('key') - expect(page).to have_content('key value') - end - - click_button('Hide values') - - page.within('.variables-table') do - expect(page).to have_content('key') - expect(page).to have_content('******') - end - end - - it 'deletes variable' do - page.within('.variables-table') do - accept_confirm { click_on 'Remove' } - end - - expect(page).not_to have_selector('variables-table') - end - - it 'edits variable' do - page.within('.variables-table') do - click_on 'Update' - end - - expect(page).to have_content('Update variable') - fill_in('variable_key', with: 'key') - fill_in('variable_value', with: 'key value') - click_button('Save variable') - - expect(page).to have_content('Variable was successfully updated.') - expect(project.variables(true).first.value).to eq('key value') - end - - it 'edits variable with empty value' do - page.within('.variables-table') do - click_on 'Update' - end - - expect(page).to have_content('Update variable') - fill_in('variable_value', with: '') - click_button('Save variable') - - expect(page).to have_content('Variable was successfully updated.') - expect(project.variables(true).first.value).to eq('') - end - - it 'edits variable to be protected' do - page.within('.variables-table') do - click_on 'Update' - end - - expect(page).to have_content('Update variable') - check('Protected') - click_button('Save variable') - - expect(page).to have_content('Variable was successfully updated.') - expect(project.variables(true).first).to be_protected - end - - it 'edits variable to be unprotected' do - project.variables.first.update(protected: true) - - page.within('.variables-table') do - click_on 'Update' - end - - expect(page).to have_content('Update variable') - uncheck('Protected') - click_button('Save variable') - - expect(page).to have_content('Variable was successfully updated.') - expect(project.variables(true).first).not_to be_protected - end -end diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb index 0a018d2b417..54a07eccaba 100644 --- a/spec/finders/snippets_finder_spec.rb +++ b/spec/finders/snippets_finder_spec.rb @@ -1,57 +1,8 @@ require 'spec_helper' describe SnippetsFinder do - let(:user) { create :user } - let(:user1) { create :user } - let(:group) { create :group, :public } - - let(:project1) { create(:project, :public, group: group) } - let(:project2) { create(:project, :private, group: group) } - - context 'all snippets visible to a user' do - let!(:snippet1) { create(:personal_snippet, :private) } - let!(:snippet2) { create(:personal_snippet, :internal) } - let!(:snippet3) { create(:personal_snippet, :public) } - let!(:project_snippet1) { create(:project_snippet, :private) } - let!(:project_snippet2) { create(:project_snippet, :internal) } - let!(:project_snippet3) { create(:project_snippet, :public) } - - it "returns all private and internal snippets" do - snippets = described_class.new(user, scope: :all).execute - expect(snippets).to include(snippet2, snippet3, project_snippet2, project_snippet3) - expect(snippets).not_to include(snippet1, project_snippet1) - end - - it "returns all public snippets" do - snippets = described_class.new(nil, scope: :all).execute - expect(snippets).to include(snippet3, project_snippet3) - expect(snippets).not_to include(snippet1, snippet2, project_snippet1, project_snippet2) - end - - it "returns all public and internal snippets for normal user" do - snippets = described_class.new(user).execute - - expect(snippets).to include(snippet2, snippet3, project_snippet2, project_snippet3) - expect(snippets).not_to include(snippet1, project_snippet1) - end - - it "returns all public snippets for non authorized user" do - snippets = described_class.new(nil).execute - - expect(snippets).to include(snippet3, project_snippet3) - expect(snippets).not_to include(snippet1, snippet2, project_snippet1, project_snippet2) - end - - it "returns all public and authored snippets for external user" do - external_user = create(:user, :external) - authored_snippet = create(:personal_snippet, :internal, author: external_user) - - snippets = described_class.new(external_user).execute - - expect(snippets).to include(snippet3, project_snippet3, authored_snippet) - expect(snippets).not_to include(snippet1, snippet2, project_snippet1, project_snippet2) - end - end + include Gitlab::Allowable + using RSpec::Parameterized::TableSyntax context 'filter by visibility' do let!(:snippet1) { create(:personal_snippet, :private) } @@ -67,6 +18,7 @@ describe SnippetsFinder do end context 'filter by scope' do + let(:user) { create :user } let!(:snippet1) { create(:personal_snippet, :private, author: user) } let!(:snippet2) { create(:personal_snippet, :internal, author: user) } let!(:snippet3) { create(:personal_snippet, :public, author: user) } @@ -84,7 +36,7 @@ describe SnippetsFinder do expect(snippets).not_to include(snippet2, snippet3) end - it "returns all snippets for 'are_interna;' scope" do + it "returns all snippets for 'are_internal' scope" do snippets = described_class.new(user, scope: :are_internal).execute expect(snippets).to include(snippet2) @@ -100,6 +52,8 @@ describe SnippetsFinder do end context 'filter by author' do + let(:user) { create :user } + let(:user1) { create :user } let!(:snippet1) { create(:personal_snippet, :private, author: user) } let!(:snippet2) { create(:personal_snippet, :internal, author: user) } let!(:snippet3) { create(:personal_snippet, :public, author: user) } @@ -147,6 +101,10 @@ describe SnippetsFinder do end context 'filter by project' do + let(:user) { create :user } + let(:group) { create :group, :public } + let(:project1) { create(:project, :public, group: group) } + before do @snippet1 = create(:project_snippet, :private, project: project1) @snippet2 = create(:project_snippet, :internal, project: project1) @@ -203,4 +161,9 @@ describe SnippetsFinder do expect(snippets).to include(@snippet1) end end + + describe "#execute" do + # Snippet visibility scenarios are included in more details in spec/support/snippet_visibility.rb + include_examples 'snippet visibility', described_class + end end diff --git a/spec/fixtures/api/schemas/deployment.json b/spec/fixtures/api/schemas/deployment.json new file mode 100644 index 00000000000..536e6475c23 --- /dev/null +++ b/spec/fixtures/api/schemas/deployment.json @@ -0,0 +1,45 @@ +{ + "additionalProperties": false, + "properties": { + "created_at": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "iid": { + "type": "integer" + }, + "last?": { + "type": "boolean" + }, + "ref": { + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "sha": { + "type": "string" + }, + "tag": { + "type": "boolean" + } + }, + "required": [ + "sha", + "created_at", + "iid", + "tag", + "last?", + "ref", + "id" + ], + "type": "object" +} diff --git a/spec/fixtures/api/schemas/deployments.json b/spec/fixtures/api/schemas/deployments.json index 1112f23aab2..7bf50e4f859 100644 --- a/spec/fixtures/api/schemas/deployments.json +++ b/spec/fixtures/api/schemas/deployments.json @@ -3,49 +3,7 @@ "properties": { "deployments": { "items": { - "additionalProperties": false, - "properties": { - "created_at": { - "type": "string" - }, - "id": { - "type": "integer" - }, - "iid": { - "type": "integer" - }, - "last?": { - "type": "boolean" - }, - "ref": { - "additionalProperties": false, - "properties": { - "name": { - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object" - }, - "sha": { - "type": "string" - }, - "tag": { - "type": "boolean" - } - }, - "required": [ - "sha", - "created_at", - "iid", - "tag", - "last?", - "ref", - "id" - ], - "type": "object" + "$ref": "deployment.json" }, "minItems": 1, "type": "array" diff --git a/spec/fixtures/api/schemas/public_api/v4/blobs.json b/spec/fixtures/api/schemas/public_api/v4/blobs.json new file mode 100644 index 00000000000..a812815838f --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/blobs.json @@ -0,0 +1,19 @@ +{ + "type": "array", + "items": { + "type": "object", + "properties" : { + "basename": { "type": "string" }, + "data": { "type": "string" }, + "filename": { "type": ["string"] }, + "id": { "type": ["string", "null"] }, + "project_id": { "type": "integer" }, + "ref": { "type": "string" }, + "startline": { "type": "integer" } + }, + "required": [ + "basename", "data", "filename", "id", "ref", "startline", "project_id" + ], + "additionalProperties": false + } +} diff --git a/spec/fixtures/api/schemas/public_api/v4/commit/detail.json b/spec/fixtures/api/schemas/public_api/v4/commit/detail.json index 88a3cad62f6..477e776a804 100644 --- a/spec/fixtures/api/schemas/public_api/v4/commit/detail.json +++ b/spec/fixtures/api/schemas/public_api/v4/commit/detail.json @@ -4,9 +4,9 @@ { "$ref": "basic.json" }, { "required" : [ - "stats", "status", - "last_pipeline" + "last_pipeline", + "project_id" ], "properties": { "stats": { "$ref": "../commit_stats.json" }, @@ -16,7 +16,8 @@ { "type": "null" }, { "$ref": "../pipeline/basic.json" } ] - } + }, + "project_id": { "type": "integer" } } } ] diff --git a/spec/fixtures/api/schemas/public_api/v4/commits_details.json b/spec/fixtures/api/schemas/public_api/v4/commits_details.json new file mode 100644 index 00000000000..1f5b1ad86ef --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/commits_details.json @@ -0,0 +1,4 @@ +{ + "type": "array", + "items": { "$ref": "commit/detail.json" } +} diff --git a/spec/fixtures/api/schemas/public_api/v4/issue.json b/spec/fixtures/api/schemas/public_api/v4/issue.json new file mode 100644 index 00000000000..147f53239e0 --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/issue.json @@ -0,0 +1,96 @@ +{ + "type": "object", + "properties" : { + "id": { "type": "integer" }, + "iid": { "type": "integer" }, + "project_id": { "type": "integer" }, + "title": { "type": "string" }, + "description": { "type": ["string", "null"] }, + "state": { "type": "string" }, + "discussion_locked": { "type": ["boolean", "null"] }, + "closed_at": { "type": "date" }, + "created_at": { "type": "date" }, + "updated_at": { "type": "date" }, + "labels": { + "type": "array", + "items": { + "type": "string" + } + }, + "milestone": { + "type": ["object", "null"], + "properties": { + "id": { "type": "integer" }, + "iid": { "type": "integer" }, + "project_id": { "type": ["integer", "null"] }, + "group_id": { "type": ["integer", "null"] }, + "title": { "type": "string" }, + "description": { "type": ["string", "null"] }, + "state": { "type": "string" }, + "created_at": { "type": "date" }, + "updated_at": { "type": "date" }, + "due_date": { "type": "date" }, + "start_date": { "type": "date" } + }, + "additionalProperties": false + }, + "assignees": { + "type": "array", + "items": { + "type": ["object", "null"], + "properties": { + "name": { "type": "string" }, + "username": { "type": "string" }, + "id": { "type": "integer" }, + "state": { "type": "string" }, + "avatar_url": { "type": "uri" }, + "web_url": { "type": "uri" } + }, + "additionalProperties": false + } + }, + "assignee": { + "type": ["object", "null"], + "properties": { + "name": { "type": "string" }, + "username": { "type": "string" }, + "id": { "type": "integer" }, + "state": { "type": "string" }, + "avatar_url": { "type": "uri" }, + "web_url": { "type": "uri" } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "username": { "type": "string" }, + "id": { "type": "integer" }, + "state": { "type": "string" }, + "avatar_url": { "type": "uri" }, + "web_url": { "type": "uri" } + }, + "additionalProperties": false + }, + "user_notes_count": { "type": "integer" }, + "upvotes": { "type": "integer" }, + "downvotes": { "type": "integer" }, + "due_date": { "type": ["date", "null"] }, + "confidential": { "type": "boolean" }, + "web_url": { "type": "uri" }, + "time_stats": { + "time_estimate": { "type": "integer" }, + "total_time_spent": { "type": "integer" }, + "human_time_estimate": { "type": ["string", "null"] }, + "human_total_time_spent": { "type": ["string", "null"] } + } + }, + "required": [ + "id", "iid", "project_id", "title", "description", + "state", "created_at", "updated_at", "labels", + "milestone", "assignees", "author", "user_notes_count", + "upvotes", "downvotes", "due_date", "confidential", + "web_url" + ] +} diff --git a/spec/fixtures/api/schemas/public_api/v4/issues.json b/spec/fixtures/api/schemas/public_api/v4/issues.json index 5c08dbc3b96..c76806705e8 100644 --- a/spec/fixtures/api/schemas/public_api/v4/issues.json +++ b/spec/fixtures/api/schemas/public_api/v4/issues.json @@ -3,98 +3,7 @@ "items": { "type": "object", "properties" : { - "id": { "type": "integer" }, - "iid": { "type": "integer" }, - "project_id": { "type": "integer" }, - "title": { "type": "string" }, - "description": { "type": ["string", "null"] }, - "state": { "type": "string" }, - "discussion_locked": { "type": ["boolean", "null"] }, - "closed_at": { "type": "date" }, - "created_at": { "type": "date" }, - "updated_at": { "type": "date" }, - "labels": { - "type": "array", - "items": { - "type": "string" - } - }, - "milestone": { - "type": "object", - "properties": { - "id": { "type": "integer" }, - "iid": { "type": "integer" }, - "project_id": { "type": ["integer", "null"] }, - "group_id": { "type": ["integer", "null"] }, - "title": { "type": "string" }, - "description": { "type": ["string", "null"] }, - "state": { "type": "string" }, - "created_at": { "type": "date" }, - "updated_at": { "type": "date" }, - "due_date": { "type": "date" }, - "start_date": { "type": "date" } - }, - "additionalProperties": false - }, - "assignees": { - "type": "array", - "items": { - "type": ["object", "null"], - "properties": { - "name": { "type": "string" }, - "username": { "type": "string" }, - "id": { "type": "integer" }, - "state": { "type": "string" }, - "avatar_url": { "type": "uri" }, - "web_url": { "type": "uri" } - }, - "additionalProperties": false - } - }, - "assignee": { - "type": ["object", "null"], - "properties": { - "name": { "type": "string" }, - "username": { "type": "string" }, - "id": { "type": "integer" }, - "state": { "type": "string" }, - "avatar_url": { "type": "uri" }, - "web_url": { "type": "uri" } - }, - "additionalProperties": false - }, - "author": { - "type": "object", - "properties": { - "name": { "type": "string" }, - "username": { "type": "string" }, - "id": { "type": "integer" }, - "state": { "type": "string" }, - "avatar_url": { "type": "uri" }, - "web_url": { "type": "uri" } - }, - "additionalProperties": false - }, - "user_notes_count": { "type": "integer" }, - "upvotes": { "type": "integer" }, - "downvotes": { "type": "integer" }, - "due_date": { "type": ["date", "null"] }, - "confidential": { "type": "boolean" }, - "web_url": { "type": "uri" }, - "time_stats": { - "time_estimate": { "type": "integer" }, - "total_time_spent": { "type": "integer" }, - "human_time_estimate": { "type": ["string", "null"] }, - "human_total_time_spent": { "type": ["string", "null"] } - } - }, - "required": [ - "id", "iid", "project_id", "title", "description", - "state", "created_at", "updated_at", "labels", - "milestone", "assignees", "author", "user_notes_count", - "upvotes", "downvotes", "due_date", "confidential", - "web_url" - ], - "additionalProperties": false + "$ref": "./issue.json" + } } } diff --git a/spec/fixtures/api/schemas/public_api/v4/merge_requests.json b/spec/fixtures/api/schemas/public_api/v4/merge_requests.json index 034509091a5..e86176e5316 100644 --- a/spec/fixtures/api/schemas/public_api/v4/merge_requests.json +++ b/spec/fixtures/api/schemas/public_api/v4/merge_requests.json @@ -28,7 +28,7 @@ "additionalProperties": false }, "assignee": { - "type": "object", + "type": ["object", "null"], "properties": { "name": { "type": "string" }, "username": { "type": "string" }, diff --git a/spec/fixtures/api/schemas/public_api/v4/milestones.json b/spec/fixtures/api/schemas/public_api/v4/milestones.json new file mode 100644 index 00000000000..c3c42b6ee60 --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/milestones.json @@ -0,0 +1,24 @@ +{ + "type": "array", + "items": { + "type": "object", + "properties" : { + "id": { "type": "integer" }, + "iid": { "type": "integer" }, + "project_id": { "type": ["integer", "null"] }, + "group_id": { "type": ["integer", "null"] }, + "title": { "type": "string" }, + "description": { "type": ["string", "null"] }, + "state": { "type": "string" }, + "created_at": { "type": "date" }, + "updated_at": { "type": "date" }, + "start_date": { "type": "date" }, + "due_date": { "type": "date" } + }, + "required": [ + "id", "iid", "title", "description", "state", + "state", "created_at", "updated_at", "start_date", "due_date" + ], + "additionalProperties": false + } +} diff --git a/spec/fixtures/api/schemas/public_api/v4/notes.json b/spec/fixtures/api/schemas/public_api/v4/notes.json new file mode 100644 index 00000000000..6525f7c2c80 --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/notes.json @@ -0,0 +1,34 @@ +{ + "type": "array", + "items": { + "type": "object", + "properties" : { + "id": { "type": "integer" }, + "body": { "type": "string" }, + "attachment": { "type": ["string", "null"] }, + "author": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "username": { "type": "string" }, + "id": { "type": "integer" }, + "state": { "type": "string" }, + "avatar_url": { "type": "uri" }, + "web_url": { "type": "uri" } + }, + "additionalProperties": false + }, + "created_at": { "type": "date" }, + "updated_at": { "type": "date" }, + "system": { "type": "boolean" }, + "noteable_id": { "type": "integer" }, + "noteable_iid": { "type": "integer" }, + "noteable_type": { "type": "string" } + }, + "required": [ + "id", "body", "attachment", "author", "created_at", "updated_at", + "system", "noteable_id", "noteable_type" + ], + "additionalProperties": false + } +} diff --git a/spec/fixtures/api/schemas/public_api/v4/projects.json b/spec/fixtures/api/schemas/public_api/v4/projects.json new file mode 100644 index 00000000000..d89eeea89a5 --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/projects.json @@ -0,0 +1,36 @@ +{ + "type": "array", + "items": { + "type": "object", + "properties" : { + "id": { "type": "integer" }, + "name": { "type": "string" }, + "name_with_namespace": { "type": "string" }, + "description": { "type": ["string", "null"] }, + "path": { "type": "string" }, + "path_with_namespace": { "type": "string" }, + "created_at": { "type": "date" }, + "default_branch": { "type": ["string", "null"] }, + "tag_list": { + "type": "array", + "items": { + "type": "string" + } + }, + "ssh_url_to_repo": { "type": "string" }, + "http_url_to_repo": { "type": "string" }, + "web_url": { "type": "string" }, + "avatar_url": { "type": ["string", "null"] }, + "star_count": { "type": "integer" }, + "forks_count": { "type": "integer" }, + "last_activity_at": { "type": "date" } + }, + "required": [ + "id", "name", "name_with_namespace", "description", "path", + "path_with_namespace", "created_at", "default_branch", "tag_list", + "ssh_url_to_repo", "http_url_to_repo", "web_url", "avatar_url", + "star_count", "last_activity_at" + ], + "additionalProperties": false + } +} diff --git a/spec/fixtures/api/schemas/public_api/v4/snippets.json b/spec/fixtures/api/schemas/public_api/v4/snippets.json new file mode 100644 index 00000000000..e37e9704649 --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/snippets.json @@ -0,0 +1,33 @@ +{ + "type": "array", + "items": { + "type": "object", + "properties" : { + "id": { "type": "integer" }, + "project_id": { "type": ["integer", "null"] }, + "title": { "type": "string" }, + "file_name": { "type": ["string", "null"] }, + "description": { "type": ["string", "null"] }, + "web_url": { "type": "string" }, + "created_at": { "type": "date" }, + "updated_at": { "type": "date" }, + "author": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "username": { "type": "string" }, + "id": { "type": "integer" }, + "state": { "type": "string" }, + "avatar_url": { "type": "uri" }, + "web_url": { "type": "uri" } + }, + "additionalProperties": false + } + }, + "required": [ + "id", "title", "file_name", "description", "web_url", + "created_at", "updated_at", "author" + ], + "additionalProperties": false + } +} diff --git a/spec/fixtures/api/schemas/variable.json b/spec/fixtures/api/schemas/variable.json new file mode 100644 index 00000000000..6f6b044115b --- /dev/null +++ b/spec/fixtures/api/schemas/variable.json @@ -0,0 +1,17 @@ +{ + "type": "object", + "required": [ + "id", + "key", + "value", + "protected" + ], + "properties": { + "id": { "type": "integer" }, + "key": { "type": "string" }, + "value": { "type": "string" }, + "protected": { "type": "boolean" }, + "environment_scope": { "type": "string", "optional": true } + }, + "additionalProperties": false +} diff --git a/spec/fixtures/api/schemas/variables.json b/spec/fixtures/api/schemas/variables.json new file mode 100644 index 00000000000..8002f39a7b8 --- /dev/null +++ b/spec/fixtures/api/schemas/variables.json @@ -0,0 +1,11 @@ +{ + "type": "object", + "required": ["variables"], + "properties": { + "variables": { + "type": "array", + "items": { "$ref": "variable.json" } + } + }, + "additionalProperties": false +} diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index f7a4a7afced..43cb0dfe163 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -63,13 +63,29 @@ describe ApplicationHelper do end end - describe 'avatar_icon' do + describe 'avatar_icon_for' do + let!(:user) { create(:user, avatar: File.open(uploaded_image_temp_path), email: 'bar@example.com') } + let(:email) { 'foo@example.com' } + let!(:another_user) { create(:user, avatar: File.open(uploaded_image_temp_path), email: email) } + + it 'prefers the user to retrieve the avatar_url' do + expect(helper.avatar_icon_for(user, email).to_s) + .to eq(user.avatar.url) + end + + it 'falls back to email lookup if no user given' do + expect(helper.avatar_icon_for(nil, email).to_s) + .to eq(another_user.avatar.url) + end + end + + describe 'avatar_icon_for_email' do let(:user) { create(:user, avatar: File.open(uploaded_image_temp_path)) } context 'using an email' do context 'when there is a matching user' do it 'returns a relative URL for the avatar' do - expect(helper.avatar_icon(user.email).to_s) + expect(helper.avatar_icon_for_email(user.email).to_s) .to eq(user.avatar.url) end end @@ -78,17 +94,37 @@ describe ApplicationHelper do it 'calls gravatar_icon' do expect(helper).to receive(:gravatar_icon).with('foo@example.com', 20, 2) - helper.avatar_icon('foo@example.com', 20, 2) + helper.avatar_icon_for_email('foo@example.com', 20, 2) + end + end + + context 'without an email passed' do + it 'calls gravatar_icon' do + expect(helper).to receive(:gravatar_icon).with(nil, 20, 2) + + helper.avatar_icon_for_email(nil, 20, 2) end end end + end + + describe 'avatar_icon_for_user' do + let(:user) { create(:user, avatar: File.open(uploaded_image_temp_path)) } - describe 'using a user' do + context 'with a user object passed' do it 'returns a relative URL for the avatar' do - expect(helper.avatar_icon(user).to_s) + expect(helper.avatar_icon_for_user(user).to_s) .to eq(user.avatar.url) end end + + context 'without a user object passed' do + it 'calls gravatar_icon' do + expect(helper).to receive(:gravatar_icon).with(nil, 20, 2) + + helper.avatar_icon_for_user(nil, 20, 2) + end + end end describe 'gravatar_icon' do diff --git a/spec/helpers/auto_devops_helper_spec.rb b/spec/helpers/auto_devops_helper_spec.rb index 5e272af6073..1950c2b129b 100644 --- a/spec/helpers/auto_devops_helper_spec.rb +++ b/spec/helpers/auto_devops_helper_spec.rb @@ -82,4 +82,39 @@ describe AutoDevopsHelper do it { is_expected.to eq(false) } end end + + describe '.auto_devops_warning_message' do + subject { helper.auto_devops_warning_message(project) } + + context 'when the service is missing' do + before do + allow(helper).to receive(:missing_auto_devops_service?).and_return(true) + end + + context 'when the domain is missing' do + before do + allow(helper).to receive(:missing_auto_devops_domain?).and_return(true) + end + + it { is_expected.to match(/Auto Review Apps and Auto Deploy need a domain name and a .* to work correctly./) } + end + + context 'when the domain is not missing' do + before do + allow(helper).to receive(:missing_auto_devops_domain?).and_return(false) + end + + it { is_expected.to match(/Auto Review Apps and Auto Deploy need a .* to work correctly./) } + end + end + + context 'when the domain is missing' do + before do + allow(helper).to receive(:missing_auto_devops_service?).and_return(false) + allow(helper).to receive(:missing_auto_devops_domain?).and_return(true) + end + + it { is_expected.to eq('Auto Review Apps and Auto Deploy need a domain name to work correctly.') } + end + end end diff --git a/spec/helpers/avatars_helper_spec.rb b/spec/helpers/avatars_helper_spec.rb index f44e7ef6843..04c6d259135 100644 --- a/spec/helpers/avatars_helper_spec.rb +++ b/spec/helpers/avatars_helper_spec.rb @@ -29,7 +29,7 @@ describe AvatarsHelper do is_expected.to eq tag( :img, alt: "#{user.name}'s avatar", - src: avatar_icon(user, 16), + src: avatar_icon_for_user(user, 16), data: { container: 'body' }, class: 'avatar s16 has-tooltip', title: user.name @@ -43,7 +43,7 @@ describe AvatarsHelper do is_expected.to eq tag( :img, alt: "#{user.name}'s avatar", - src: avatar_icon(user, 16), + src: avatar_icon_for_user(user, 16), data: { container: 'body' }, class: "avatar s16 #{options[:css_class]} has-tooltip", title: user.name @@ -58,7 +58,7 @@ describe AvatarsHelper do is_expected.to eq tag( :img, alt: "#{user.name}'s avatar", - src: avatar_icon(user, options[:size]), + src: avatar_icon_for_user(user, options[:size]), data: { container: 'body' }, class: "avatar s#{options[:size]} has-tooltip", title: user.name @@ -89,7 +89,7 @@ describe AvatarsHelper do :img, alt: "#{user.name}'s avatar", src: LazyImageTagHelper.placeholder_image, - data: { container: 'body', src: avatar_icon(user, 16) }, + data: { container: 'body', src: avatar_icon_for_user(user, 16) }, class: "avatar s16 has-tooltip lazy", title: user.name ) @@ -104,7 +104,7 @@ describe AvatarsHelper do is_expected.to eq tag( :img, alt: "#{user.name}'s avatar", - src: avatar_icon(user, 16), + src: avatar_icon_for_user(user, 16), data: { container: 'body' }, class: "avatar s16 has-tooltip", title: user.name @@ -119,7 +119,7 @@ describe AvatarsHelper do is_expected.to eq tag( :img, alt: "#{user.name}'s avatar", - src: avatar_icon(user, 16), + src: avatar_icon_for_user(user, 16), class: "avatar s16", title: user.name ) @@ -137,7 +137,7 @@ describe AvatarsHelper do is_expected.to eq tag( :img, alt: "#{user.name}'s avatar", - src: avatar_icon(user, 16), + src: avatar_icon_for_user(user, 16), data: { container: 'body' }, class: "avatar s16 has-tooltip", title: user.name @@ -149,7 +149,7 @@ describe AvatarsHelper do is_expected.to eq tag( :img, alt: "#{options[:user_name]}'s avatar", - src: avatar_icon(options[:user_email], 16), + src: avatar_icon_for_email(options[:user_email], 16), data: { container: 'body' }, class: "avatar s16 has-tooltip", title: options[:user_name] diff --git a/spec/helpers/events_helper_spec.rb b/spec/helpers/events_helper_spec.rb index 8a80b88da5d..fccde8b7eba 100644 --- a/spec/helpers/events_helper_spec.rb +++ b/spec/helpers/events_helper_spec.rb @@ -20,5 +20,9 @@ describe EventsHelper do it 'handles nil values' do expect(helper.event_commit_title(nil)).to eq('') end + + it 'does not escape HTML entities' do + expect(helper.event_commit_title("foo & bar")).to eq("foo & bar") + end end end diff --git a/spec/helpers/graph_helper_spec.rb b/spec/helpers/graph_helper_spec.rb index 400635abdde..1f8a38dc697 100644 --- a/spec/helpers/graph_helper_spec.rb +++ b/spec/helpers/graph_helper_spec.rb @@ -7,10 +7,10 @@ describe GraphHelper do let(:graph) { Network::Graph.new(project, 'master', commit, '') } it 'filters our refs used by GitLab' do - allow(commit).to receive(:ref_names).and_return(['refs/merge-requests/abc', 'master', 'refs/tmp/xyz']) self.instance_variable_set(:@graph, graph) - refs = get_refs(project.repository, commit) - expect(refs).to eq('master') + refs = refs(project.repository, commit) + + expect(refs).to match('master') end end end diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index c0251bf7dc0..b67fee2fcc0 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -215,7 +215,7 @@ describe ProjectsHelper do let(:expected) { double } before do - expect(helper).to receive(:avatar_icon).with(user, 16).and_return(expected) + expect(helper).to receive(:avatar_icon_for_user).with(user, 16).and_return(expected) end it 'returns image tag for member avatar' do diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js index 268b5b83b73..1c1b3f3ced3 100644 --- a/spec/javascripts/awards_handler_spec.js +++ b/spec/javascripts/awards_handler_spec.js @@ -79,7 +79,7 @@ import '~/lib/utils/common_utils'; return expect($emojiMenu.length).toBe(1); }); }); - return it('should remove emoji menu when body is clicked', function(done) { + it('should remove emoji menu when body is clicked', function(done) { $('.js-add-award').eq(0).click(); return lazyAssert(done, function() { var $emojiMenu; @@ -90,6 +90,17 @@ import '~/lib/utils/common_utils'; return expect($('.js-awards-block.current').length).toBe(0); }); }); + it('should not remove emoji menu when search is clicked', function(done) { + $('.js-add-award').eq(0).click(); + return lazyAssert(done, function() { + var $emojiMenu; + $emojiMenu = $('.emoji-menu'); + $('.emoji-search').click(); + expect($emojiMenu.length).toBe(1); + expect($emojiMenu.hasClass('is-visible')).toBe(true); + return expect($('.js-awards-block.current').length).toBe(1); + }); + }); }); describe('::addAwardToEmojiBar', function() { it('should add emoji to votes block', function() { diff --git a/spec/javascripts/boards/board_list_spec.js b/spec/javascripts/boards/board_list_spec.js index a5fcb10b9dd..03df6c06691 100644 --- a/spec/javascripts/boards/board_list_spec.js +++ b/spec/javascripts/boards/board_list_spec.js @@ -5,7 +5,7 @@ import Vue from 'vue'; import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; import Sortable from 'vendor/Sortable'; -import BoardList from '~/boards/components/board_list'; +import BoardList from '~/boards/components/board_list.vue'; import eventHub from '~/boards/eventhub'; import '~/boards/mixins/sortable_default_options'; import '~/boards/models/issue'; diff --git a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js new file mode 100644 index 00000000000..ee457a9c48c --- /dev/null +++ b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js @@ -0,0 +1,213 @@ +import $ from 'jquery'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import AjaxFormVariableList from '~/ci_variable_list/ajax_variable_list'; + +const VARIABLE_PATCH_ENDPOINT = 'http://test.host/frontend-fixtures/builds-project/variables'; +const HIDE_CLASS = 'hide'; + +describe('AjaxFormVariableList', () => { + preloadFixtures('projects/ci_cd_settings.html.raw'); + preloadFixtures('projects/ci_cd_settings_with_variables.html.raw'); + + let container; + let saveButton; + let errorBox; + + let mock; + let ajaxVariableList; + + beforeEach(() => { + loadFixtures('projects/ci_cd_settings.html.raw'); + container = document.querySelector('.js-ci-variable-list-section'); + + mock = new MockAdapter(axios); + + const ajaxVariableListEl = document.querySelector('.js-ci-variable-list-section'); + saveButton = ajaxVariableListEl.querySelector('.js-secret-variables-save-button'); + errorBox = container.querySelector('.js-ci-variable-error-box'); + ajaxVariableList = new AjaxFormVariableList({ + container, + formField: 'variables', + saveButton, + errorBox, + saveEndpoint: container.dataset.saveEndpoint, + }); + + spyOn(ajaxVariableList, 'updateRowsWithPersistedVariables').and.callThrough(); + spyOn(ajaxVariableList.variableList, 'toggleEnableRow').and.callThrough(); + }); + + afterEach(() => { + mock.restore(); + }); + + describe('onSaveClicked', () => { + it('shows loading spinner while waiting for the request', (done) => { + const loadingIcon = saveButton.querySelector('.js-secret-variables-save-loading-icon'); + + mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(() => { + expect(loadingIcon.classList.contains(HIDE_CLASS)).toEqual(false); + + return [200, {}]; + }); + + expect(loadingIcon.classList.contains(HIDE_CLASS)).toEqual(true); + + ajaxVariableList.onSaveClicked() + .then(() => { + expect(loadingIcon.classList.contains(HIDE_CLASS)).toEqual(true); + }) + .then(done) + .catch(done.fail); + }); + + it('calls `updateRowsWithPersistedVariables` with the persisted variables', (done) => { + const variablesResponse = [{ id: 1, key: 'foo', value: 'bar' }]; + mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(200, { + variables: variablesResponse, + }); + + ajaxVariableList.onSaveClicked() + .then(() => { + expect(ajaxVariableList.updateRowsWithPersistedVariables) + .toHaveBeenCalledWith(variablesResponse); + }) + .then(done) + .catch(done.fail); + }); + + it('hides any previous error box', (done) => { + mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(200); + + expect(errorBox.classList.contains(HIDE_CLASS)).toEqual(true); + + ajaxVariableList.onSaveClicked() + .then(() => { + expect(errorBox.classList.contains(HIDE_CLASS)).toEqual(true); + }) + .then(done) + .catch(done.fail); + }); + + it('disables remove buttons while waiting for the request', (done) => { + mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(() => { + expect(ajaxVariableList.variableList.toggleEnableRow).toHaveBeenCalledWith(false); + + return [200, {}]; + }); + + ajaxVariableList.onSaveClicked() + .then(() => { + expect(ajaxVariableList.variableList.toggleEnableRow).toHaveBeenCalledWith(true); + }) + .then(done) + .catch(done.fail); + }); + + it('hides secret values', (done) => { + mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(200, {}); + + const row = container.querySelector('.js-row:first-child'); + const valueInput = row.querySelector('.js-ci-variable-input-value'); + const valuePlaceholder = row.querySelector('.js-secret-value-placeholder'); + + valueInput.value = 'bar'; + $(valueInput).trigger('input'); + + expect(valuePlaceholder.classList.contains(HIDE_CLASS)).toBe(true); + expect(valueInput.classList.contains(HIDE_CLASS)).toBe(false); + + ajaxVariableList.onSaveClicked() + .then(() => { + expect(valuePlaceholder.classList.contains(HIDE_CLASS)).toBe(false); + expect(valueInput.classList.contains(HIDE_CLASS)).toBe(true); + }) + .then(done) + .catch(done.fail); + }); + + it('shows error box with validation errors', (done) => { + const validationError = 'some validation error'; + mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(400, [ + validationError, + ]); + + expect(errorBox.classList.contains(HIDE_CLASS)).toEqual(true); + + ajaxVariableList.onSaveClicked() + .then(() => { + expect(errorBox.classList.contains(HIDE_CLASS)).toEqual(false); + expect(errorBox.textContent.trim().replace(/\n+\s+/m, ' ')).toEqual(`Validation failed ${validationError}`); + }) + .then(done) + .catch(done.fail); + }); + + it('shows flash message when request fails', (done) => { + mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(500); + + expect(errorBox.classList.contains(HIDE_CLASS)).toEqual(true); + + ajaxVariableList.onSaveClicked() + .then(() => { + expect(errorBox.classList.contains(HIDE_CLASS)).toEqual(true); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('updateRowsWithPersistedVariables', () => { + beforeEach(() => { + loadFixtures('projects/ci_cd_settings_with_variables.html.raw'); + container = document.querySelector('.js-ci-variable-list-section'); + + const ajaxVariableListEl = document.querySelector('.js-ci-variable-list-section'); + saveButton = ajaxVariableListEl.querySelector('.js-secret-variables-save-button'); + errorBox = container.querySelector('.js-ci-variable-error-box'); + ajaxVariableList = new AjaxFormVariableList({ + container, + formField: 'variables', + saveButton, + errorBox, + saveEndpoint: container.dataset.saveEndpoint, + }); + }); + + it('removes variable that was removed', () => { + expect(container.querySelectorAll('.js-row').length).toBe(3); + + container.querySelector('.js-row-remove-button').click(); + + expect(container.querySelectorAll('.js-row').length).toBe(3); + + ajaxVariableList.updateRowsWithPersistedVariables([]); + + expect(container.querySelectorAll('.js-row').length).toBe(2); + }); + + it('updates new variable row with persisted ID', () => { + const row = container.querySelector('.js-row:last-child'); + const idInput = row.querySelector('.js-ci-variable-input-id'); + const keyInput = row.querySelector('.js-ci-variable-input-key'); + const valueInput = row.querySelector('.js-ci-variable-input-value'); + + keyInput.value = 'foo'; + $(keyInput).trigger('input'); + valueInput.value = 'bar'; + $(valueInput).trigger('input'); + + expect(idInput.value).toEqual(''); + + ajaxVariableList.updateRowsWithPersistedVariables([{ + id: 3, + key: 'foo', + value: 'bar', + }]); + + expect(idInput.value).toEqual('3'); + expect(row.dataset.isPersisted).toEqual('true'); + }); + }); +}); diff --git a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js index 0170ab458d4..cac785fd3c6 100644 --- a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js +++ b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js @@ -1,9 +1,12 @@ import VariableList from '~/ci_variable_list/ci_variable_list'; import getSetTimeoutPromise from '../helpers/set_timeout_promise_helper'; +const HIDE_CLASS = 'hide'; + describe('VariableList', () => { preloadFixtures('pipeline_schedules/edit.html.raw'); preloadFixtures('pipeline_schedules/edit_with_variables.html.raw'); + preloadFixtures('projects/ci_cd_settings.html.raw'); let $wrapper; let variableList; @@ -91,51 +94,22 @@ describe('VariableList', () => { const $inputValue = $row.find('.js-ci-variable-input-value'); const $placeholder = $row.find('.js-secret-value-placeholder'); - expect($placeholder.hasClass('hide')).toBe(false); - expect($inputValue.hasClass('hide')).toBe(true); + expect($placeholder.hasClass(HIDE_CLASS)).toBe(false); + expect($inputValue.hasClass(HIDE_CLASS)).toBe(true); // Reveal values $wrapper.find('.js-secret-value-reveal-button').click(); - expect($placeholder.hasClass('hide')).toBe(true); - expect($inputValue.hasClass('hide')).toBe(false); + expect($placeholder.hasClass(HIDE_CLASS)).toBe(true); + expect($inputValue.hasClass(HIDE_CLASS)).toBe(false); }); }); }); describe('with all inputs(key, value, protected)', () => { beforeEach(() => { - // This markup will be replaced with a fixture when we can render the - // CI/CD settings page with the new dynamic variable list in https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4110 - $wrapper = $(`<form class="js-variable-list"> - <ul> - <li class="js-row"> - <div class="ci-variable-body-item"> - <input class="js-ci-variable-input-key" name="variables[variables_attributes][][key]"> - </div> - - <div class="ci-variable-body-item"> - <textarea class="js-ci-variable-input-value" name="variables[variables_attributes][][value]"></textarea> - </div> - - <div class="ci-variable-body-item ci-variable-protected-item"> - <button type="button" class="js-project-feature-toggle project-feature-toggle"> - <input - type="hidden" - class="js-ci-variable-input-protected js-project-feature-toggle-input" - name="variables[variables_attributes][][protected]" - value="true" - /> - </button> - </div> - - <button type="button" class="js-row-remove-button"></button> - </li> - </ul> - <button type="button" class="js-secret-value-reveal-button"> - Reveal values - </button> - </form>`); + loadFixtures('projects/ci_cd_settings.html.raw'); + $wrapper = $('.js-ci-variable-list-section'); variableList = new VariableList({ container: $wrapper, @@ -154,10 +128,88 @@ describe('VariableList', () => { // Check for the correct default in the new row const $protectedInput = $wrapper.find('.js-row:last-child').find('.js-ci-variable-input-protected'); - expect($protectedInput.val()).toBe('true'); + expect($protectedInput.val()).toBe('false'); }) .then(done) .catch(done.fail); }); }); + + describe('toggleEnableRow method', () => { + beforeEach(() => { + loadFixtures('pipeline_schedules/edit_with_variables.html.raw'); + $wrapper = $('.js-ci-variable-list-section'); + + variableList = new VariableList({ + container: $wrapper, + formField: 'variables', + }); + variableList.init(); + }); + + it('should disable all key inputs', () => { + expect($wrapper.find('.js-ci-variable-input-key:not([disabled])').length).toBe(3); + + variableList.toggleEnableRow(false); + + expect($wrapper.find('.js-ci-variable-input-key[disabled]').length).toBe(3); + }); + + it('should disable all remove buttons', () => { + expect($wrapper.find('.js-row-remove-button:not([disabled])').length).toBe(3); + + variableList.toggleEnableRow(false); + + expect($wrapper.find('.js-row-remove-button[disabled]').length).toBe(3); + }); + + it('should enable all remove buttons', () => { + variableList.toggleEnableRow(false); + expect($wrapper.find('.js-row-remove-button[disabled]').length).toBe(3); + + variableList.toggleEnableRow(true); + + expect($wrapper.find('.js-row-remove-button:not([disabled])').length).toBe(3); + }); + + it('should enable all key inputs', () => { + variableList.toggleEnableRow(false); + expect($wrapper.find('.js-ci-variable-input-key[disabled]').length).toBe(3); + + variableList.toggleEnableRow(true); + + expect($wrapper.find('.js-ci-variable-input-key:not([disabled])').length).toBe(3); + }); + }); + + describe('hideValues', () => { + beforeEach(() => { + loadFixtures('projects/ci_cd_settings.html.raw'); + $wrapper = $('.js-ci-variable-list-section'); + + variableList = new VariableList({ + container: $wrapper, + formField: 'variables', + }); + variableList.init(); + }); + + it('should hide value input and show placeholder stars', () => { + const $row = $wrapper.find('.js-row'); + const $inputValue = $row.find('.js-ci-variable-input-value'); + const $placeholder = $row.find('.js-secret-value-placeholder'); + + $row.find('.js-ci-variable-input-value') + .val('foo') + .trigger('input'); + + expect($placeholder.hasClass(HIDE_CLASS)).toBe(true); + expect($inputValue.hasClass(HIDE_CLASS)).toBe(false); + + variableList.hideValues(); + + expect($placeholder.hasClass(HIDE_CLASS)).toBe(false); + expect($inputValue.hasClass(HIDE_CLASS)).toBe(true); + }); + }); }); diff --git a/spec/javascripts/commits_spec.js b/spec/javascripts/commits_spec.js index 44ec9e4eabf..1daccc8dd02 100644 --- a/spec/javascripts/commits_spec.js +++ b/spec/javascripts/commits_spec.js @@ -4,6 +4,8 @@ import axios from '~/lib/utils/axios_utils'; import CommitsList from '~/commits'; describe('Commits List', () => { + let commitsList; + beforeEach(() => { setFixtures(` <form class="commits-search-form" action="/h5bp/html5-boilerplate/commits/master"> @@ -11,6 +13,7 @@ describe('Commits List', () => { </form> <ol id="commits-list"></ol> `); + commitsList = new CommitsList(25); }); it('should be defined', () => { @@ -19,7 +22,7 @@ describe('Commits List', () => { describe('processCommits', () => { it('should join commit headers', () => { - CommitsList.$contentList = $(` + commitsList.$contentList = $(` <div> <li class="commit-header" data-day="2016-09-20"> <span class="day">20 Sep, 2016</span> @@ -39,7 +42,7 @@ describe('Commits List', () => { // The last commit header should be removed // since the previous one has the same data-day value. - expect(CommitsList.processCommits(data).find('li.commit-header').length).toBe(0); + expect(commitsList.processCommits(data).find('li.commit-header').length).toBe(0); }); }); @@ -48,8 +51,7 @@ describe('Commits List', () => { let mock; beforeEach(() => { - CommitsList.init(25); - CommitsList.searchField.val(''); + commitsList.searchField.val(''); spyOn(history, 'replaceState').and.stub(); mock = new MockAdapter(axios); @@ -66,11 +68,11 @@ describe('Commits List', () => { }); it('should save the last search string', (done) => { - CommitsList.searchField.val('GitLab'); - CommitsList.filterResults() + commitsList.searchField.val('GitLab'); + commitsList.filterResults() .then(() => { expect(ajaxSpy).toHaveBeenCalled(); - expect(CommitsList.lastSearch).toEqual('GitLab'); + expect(commitsList.lastSearch).toEqual('GitLab'); done(); }) @@ -78,10 +80,10 @@ describe('Commits List', () => { }); it('should not make ajax call if the input does not change', (done) => { - CommitsList.filterResults() + commitsList.filterResults() .then(() => { expect(ajaxSpy).not.toHaveBeenCalled(); - expect(CommitsList.lastSearch).toEqual(''); + expect(commitsList.lastSearch).toEqual(''); done(); }) diff --git a/spec/javascripts/datetime_utility_spec.js b/spec/javascripts/datetime_utility_spec.js index 2e5b65f5610..a8d09202154 100644 --- a/spec/javascripts/datetime_utility_spec.js +++ b/spec/javascripts/datetime_utility_spec.js @@ -105,4 +105,68 @@ describe('dateInWords', () => { it('should return abbreviated month name', () => { expect(datetimeUtility.dateInWords(date, true)).toEqual('Jul 1, 2016'); }); + + it('should return date in words without year', () => { + expect(datetimeUtility.dateInWords(date, true, true)).toEqual('Jul 1'); + }); +}); + +describe('monthInWords', () => { + const date = new Date('2017-01-20'); + + it('returns month name from provided date', () => { + expect(datetimeUtility.monthInWords(date)).toBe('January'); + }); + + it('returns abbreviated month name from provided date', () => { + expect(datetimeUtility.monthInWords(date, true)).toBe('Jan'); + }); +}); + +describe('totalDaysInMonth', () => { + it('returns number of days in a month for given date', () => { + // 1st Feb, 2016 (leap year) + expect(datetimeUtility.totalDaysInMonth(new Date(2016, 1, 1))).toBe(29); + + // 1st Feb, 2017 + expect(datetimeUtility.totalDaysInMonth(new Date(2017, 1, 1))).toBe(28); + + // 1st Jan, 2017 + expect(datetimeUtility.totalDaysInMonth(new Date(2017, 0, 1))).toBe(31); + }); +}); + +describe('getSundays', () => { + it('returns array of dates representing all Sundays of the month', () => { + // December, 2017 (it has 5 Sundays) + const dateOfSundays = [3, 10, 17, 24, 31]; + const sundays = datetimeUtility.getSundays(new Date(2017, 11, 1)); + + expect(sundays.length).toBe(5); + sundays.forEach((sunday, index) => { + expect(sunday.getDate()).toBe(dateOfSundays[index]); + }); + }); +}); + +describe('getTimeframeWindow', () => { + it('returns array of dates representing a timeframe based on provided length and date', () => { + const date = new Date(2018, 0, 1); + const mockTimeframe = [ + new Date(2017, 9, 1), + new Date(2017, 10, 1), + new Date(2017, 11, 1), + new Date(2018, 0, 1), + new Date(2018, 1, 1), + new Date(2018, 2, 31), + ]; + const timeframe = datetimeUtility.getTimeframeWindow(6, date); + + expect(timeframe.length).toBe(6); + timeframe.forEach((timeframeItem, index) => { + expect(timeframeItem.getFullYear() === mockTimeframe[index].getFullYear()).toBeTruthy(); + expect(timeframeItem.getMonth() === mockTimeframe[index].getMonth()).toBeTruthy(); + expect(timeframeItem.getDate() === mockTimeframe[index].getDate()).toBeTruthy(); + }); + }); }); diff --git a/spec/javascripts/droplab/drop_down_spec.js b/spec/javascripts/droplab/drop_down_spec.js index 1225fe2cb66..896a04a1a07 100644 --- a/spec/javascripts/droplab/drop_down_spec.js +++ b/spec/javascripts/droplab/drop_down_spec.js @@ -1,8 +1,8 @@ import DropDown from '~/droplab/drop_down'; import utils from '~/droplab/utils'; -import { SELECTED_CLASS, IGNORE_CLASS } from '~/droplab/constants'; +import { SELECTED_CLASS } from '~/droplab/constants'; -describe('DropDown', function () { +describe('DropLab DropDown', function () { describe('class constructor', function () { beforeEach(function () { spyOn(DropDown.prototype, 'getItems'); @@ -128,93 +128,131 @@ describe('DropDown', function () { beforeEach(function () { this.classList = jasmine.createSpyObj('classList', ['contains']); this.list = { dispatchEvent: () => {} }; - this.dropdown = { hide: () => {}, list: this.list, addSelectedClass: () => {} }; - this.event = { preventDefault: () => {}, target: { classList: this.classList } }; + this.dropdown = { + hideOnClick: true, + hide: () => {}, + list: this.list, + addSelectedClass: () => {}, + }; + this.event = { + preventDefault: () => {}, + target: { + classList: this.classList, + closest: () => null, + }, + }; this.customEvent = {}; - this.closestElement = {}; + this.dummyListItem = document.createElement('li'); + spyOn(this.event.target, 'closest').and.callFake((selector) => { + if (selector === 'li') { + return this.dummyListItem; + } + + return null; + }); spyOn(this.dropdown, 'hide'); spyOn(this.dropdown, 'addSelectedClass'); spyOn(this.list, 'dispatchEvent'); spyOn(this.event, 'preventDefault'); spyOn(window, 'CustomEvent').and.returnValue(this.customEvent); - spyOn(utils, 'closest').and.returnValues(this.closestElement, undefined); this.classList.contains.and.returnValue(false); + }); + it('should call event.target.closest', function () { DropDown.prototype.clickEvent.call(this.dropdown, this.event); - }); - it('should call utils.closest', function () { - expect(utils.closest).toHaveBeenCalledWith(this.event.target, 'LI'); + expect(this.event.target.closest).toHaveBeenCalledWith('.droplab-item-ignore'); + expect(this.event.target.closest).toHaveBeenCalledWith('li'); }); it('should call addSelectedClass', function () { - expect(this.dropdown.addSelectedClass).toHaveBeenCalledWith(this.closestElement); + DropDown.prototype.clickEvent.call(this.dropdown, this.event); + + expect(this.dropdown.addSelectedClass).toHaveBeenCalledWith(this.dummyListItem); }); it('should call .preventDefault', function () { + DropDown.prototype.clickEvent.call(this.dropdown, this.event); + expect(this.event.preventDefault).toHaveBeenCalled(); }); it('should call .hide', function () { + DropDown.prototype.clickEvent.call(this.dropdown, this.event); + expect(this.dropdown.hide).toHaveBeenCalled(); }); it('should construct CustomEvent', function () { - expect(window.CustomEvent).toHaveBeenCalledWith('click.dl', jasmine.any(Object)); - }); + DropDown.prototype.clickEvent.call(this.dropdown, this.event); - it('should call .classList.contains checking for IGNORE_CLASS', function () { - expect(this.classList.contains).toHaveBeenCalledWith(IGNORE_CLASS); + expect(window.CustomEvent).toHaveBeenCalledWith('click.dl', jasmine.any(Object)); }); it('should call .dispatchEvent with the customEvent', function () { + DropDown.prototype.clickEvent.call(this.dropdown, this.event); + expect(this.list.dispatchEvent).toHaveBeenCalledWith(this.customEvent); }); describe('if the target is a UL element', function () { beforeEach(function () { - this.event = { preventDefault: () => {}, target: { tagName: 'UL', classList: this.classList } }; - - spyOn(this.event, 'preventDefault'); - utils.closest.calls.reset(); + this.event.target = document.createElement('ul'); - DropDown.prototype.clickEvent.call(this.dropdown, this.event); + spyOn(this.event.target, 'closest'); }); it('should return immediately', function () { - expect(utils.closest).not.toHaveBeenCalled(); + DropDown.prototype.clickEvent.call(this.dropdown, this.event); + + expect(this.event.target.closest).not.toHaveBeenCalled(); + expect(this.dropdown.addSelectedClass).not.toHaveBeenCalled(); }); }); - describe('if the target has the IGNORE_CLASS class', function () { + describe('if the target has the droplab-item-ignore class', function () { beforeEach(function () { - this.event = { preventDefault: () => {}, target: { tagName: 'LI', classList: this.classList } }; + this.ignoredButton = document.createElement('button'); + this.ignoredButton.classList.add('droplab-item-ignore'); + this.event.target = this.ignoredButton; - spyOn(this.event, 'preventDefault'); - this.classList.contains.and.returnValue(true); - utils.closest.calls.reset(); + spyOn(this.ignoredButton, 'closest').and.callThrough(); + }); + it('does not select element', function () { DropDown.prototype.clickEvent.call(this.dropdown, this.event); - }); - it('should return immediately', function () { - expect(utils.closest).not.toHaveBeenCalled(); + expect(this.ignoredButton.closest.calls.count()).toBe(1); + expect(this.ignoredButton.closest).toHaveBeenCalledWith('.droplab-item-ignore'); + expect(this.dropdown.addSelectedClass).not.toHaveBeenCalled(); }); }); describe('if no selected element exists', function () { beforeEach(function () { this.event.preventDefault.calls.reset(); - this.clickEvent = DropDown.prototype.clickEvent.call(this.dropdown, this.event); - }); - - it('should return undefined', function () { - expect(this.clickEvent).toBe(undefined); + this.dummyListItem = null; }); it('should return before .preventDefault is called', function () { + DropDown.prototype.clickEvent.call(this.dropdown, this.event); + expect(this.event.preventDefault).not.toHaveBeenCalled(); + expect(this.dropdown.addSelectedClass).not.toHaveBeenCalled(); + }); + }); + + describe('if hideOnClick is false', () => { + beforeEach(function () { + this.dropdown.hideOnClick = false; + this.dropdown.hide.calls.reset(); + }); + + it('should not call .hide', function () { + DropDown.prototype.clickEvent.call(this.dropdown, this.event); + + expect(this.dropdown.hide).not.toHaveBeenCalled(); }); }); }); @@ -278,20 +316,23 @@ describe('DropDown', function () { describe('addEvents', function () { beforeEach(function () { - this.list = { addEventListener: () => {} }; + this.list = { + addEventListener: () => {}, + querySelectorAll: () => [], + }; this.dropdown = { list: this.list, clickEvent: () => {}, closeDropdown: () => {}, eventWrapper: {}, }; + }); + it('should call .addEventListener', function () { spyOn(this.list, 'addEventListener'); DropDown.prototype.addEvents.call(this.dropdown); - }); - it('should call .addEventListener', function () { expect(this.list.addEventListener).toHaveBeenCalledWith('click', jasmine.any(Function)); expect(this.list.addEventListener).toHaveBeenCalledWith('keyup', jasmine.any(Function)); }); diff --git a/spec/javascripts/filtered_search/components/recent_searches_dropdown_content_spec.js b/spec/javascripts/filtered_search/components/recent_searches_dropdown_content_spec.js index 79447787fc9..4a516c517ef 100644 --- a/spec/javascripts/filtered_search/components/recent_searches_dropdown_content_spec.js +++ b/spec/javascripts/filtered_search/components/recent_searches_dropdown_content_spec.js @@ -2,7 +2,7 @@ import Vue from 'vue'; import eventHub from '~/filtered_search/event_hub'; import RecentSearchesDropdownContent from '~/filtered_search/components/recent_searches_dropdown_content'; -import '~/filtered_search/filtered_search_token_keys'; +import FilteredSearchTokenKeys from '~/filtered_search/filtered_search_token_keys'; const createComponent = (propsData) => { const Component = Vue.extend(RecentSearchesDropdownContent); @@ -19,14 +19,14 @@ const trimMarkupWhitespace = text => text.replace(/(\n|\s)+/gm, ' ').trim(); describe('RecentSearchesDropdownContent', () => { const propsDataWithoutItems = { items: [], - allowedKeys: gl.FilteredSearchTokenKeys.getKeys(), + allowedKeys: FilteredSearchTokenKeys.getKeys(), }; const propsDataWithItems = { items: [ 'foo', 'author:@root label:~foo bar', ], - allowedKeys: gl.FilteredSearchTokenKeys.getKeys(), + allowedKeys: FilteredSearchTokenKeys.getKeys(), }; let vm; diff --git a/spec/javascripts/filtered_search/dropdown_user_spec.js b/spec/javascripts/filtered_search/dropdown_user_spec.js index 02415485d19..f1e6119253e 100644 --- a/spec/javascripts/filtered_search/dropdown_user_spec.js +++ b/spec/javascripts/filtered_search/dropdown_user_spec.js @@ -3,6 +3,8 @@ import '~/filtered_search/filtered_search_tokenizer'; import '~/filtered_search/filtered_search_dropdown'; import '~/filtered_search/dropdown_user'; +import FilteredSearchTokenKeys from '~/filtered_search/filtered_search_token_keys'; + describe('Dropdown User', () => { describe('getSearchInput', () => { let dropdownUser; @@ -14,7 +16,7 @@ describe('Dropdown User', () => { spyOn(gl.DropdownUtils, 'getSearchInput').and.callFake(() => {}); dropdownUser = new gl.DropdownUser({ - tokenKeys: gl.FilteredSearchTokenKeys, + tokenKeys: FilteredSearchTokenKeys, }); }); diff --git a/spec/javascripts/filtered_search/dropdown_utils_spec.js b/spec/javascripts/filtered_search/dropdown_utils_spec.js index b1b3d43f241..d6e1af105f1 100644 --- a/spec/javascripts/filtered_search/dropdown_utils_spec.js +++ b/spec/javascripts/filtered_search/dropdown_utils_spec.js @@ -1,6 +1,7 @@ import '~/filtered_search/dropdown_utils'; import '~/filtered_search/filtered_search_tokenizer'; import '~/filtered_search/filtered_search_dropdown_manager'; +import FilteredSearchTokenKeys from '~/filtered_search/filtered_search_token_keys'; import FilteredSearchSpecHelper from '../helpers/filtered_search_spec_helper'; describe('Dropdown Utils', () => { @@ -137,7 +138,7 @@ describe('Dropdown Utils', () => { `); input = document.getElementById('test'); - allowedKeys = gl.FilteredSearchTokenKeys.getKeys(); + allowedKeys = FilteredSearchTokenKeys.getKeys(); }); function config() { diff --git a/spec/javascripts/filtered_search/filtered_search_manager_spec.js b/spec/javascripts/filtered_search/filtered_search_manager_spec.js index b8890e4cda1..0ed9a587dc1 100644 --- a/spec/javascripts/filtered_search/filtered_search_manager_spec.js +++ b/spec/javascripts/filtered_search/filtered_search_manager_spec.js @@ -3,8 +3,8 @@ import * as recentSearchesStoreSrc from '~/filtered_search/stores/recent_searche import RecentSearchesService from '~/filtered_search/services/recent_searches_service'; import RecentSearchesServiceError from '~/filtered_search/services/recent_searches_service_error'; import RecentSearchesRoot from '~/filtered_search/recent_searches_root'; +import FilteredSearchTokenKeys from '~/filtered_search/filtered_search_token_keys'; import '~/lib/utils/common_utils'; -import '~/filtered_search/filtered_search_token_keys'; import '~/filtered_search/filtered_search_tokenizer'; import '~/filtered_search/filtered_search_dropdown_manager'; import '~/filtered_search/filtered_search_manager'; @@ -14,6 +14,7 @@ describe('Filtered Search Manager', () => { let input; let manager; let tokensContainer; + const page = 'issues'; const placeholder = 'Search or filter results...'; function dispatchBackspaceEvent(element, eventType) { @@ -62,7 +63,7 @@ describe('Filtered Search Manager', () => { input = document.querySelector('.filtered-search'); tokensContainer = document.querySelector('.tokens-container'); - manager = new gl.FilteredSearchManager(); + manager = new gl.FilteredSearchManager({ page }); manager.setup(); }; @@ -80,19 +81,19 @@ describe('Filtered Search Manager', () => { }); it('should instantiate RecentSearchesStore with isLocalStorageAvailable', () => { - manager = new gl.FilteredSearchManager(); + manager = new gl.FilteredSearchManager({ page }); expect(RecentSearchesService.isAvailable).toHaveBeenCalled(); expect(recentSearchesStoreSrc.default).toHaveBeenCalledWith({ isLocalStorageAvailable, - allowedKeys: gl.FilteredSearchTokenKeys.getKeys(), + allowedKeys: FilteredSearchTokenKeys.getKeys(), }); }); }); describe('setup', () => { beforeEach(() => { - manager = new gl.FilteredSearchManager(); + manager = new gl.FilteredSearchManager({ page }); }); it('should not instantiate Flash if an RecentSearchesServiceError is caught', () => { diff --git a/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js b/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js index 69b424c3af5..fbc3926d332 100644 --- a/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js +++ b/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js @@ -1,11 +1,11 @@ -import '~/filtered_search/filtered_search_token_keys'; +import FilteredSearchTokenKeys from '~/filtered_search/filtered_search_token_keys'; describe('Filtered Search Token Keys', () => { describe('get', () => { let tokenKeys; beforeEach(() => { - tokenKeys = gl.FilteredSearchTokenKeys.get(); + tokenKeys = FilteredSearchTokenKeys.get(); }); it('should return tokenKeys', () => { @@ -21,7 +21,7 @@ describe('Filtered Search Token Keys', () => { let conditions; beforeEach(() => { - conditions = gl.FilteredSearchTokenKeys.getConditions(); + conditions = FilteredSearchTokenKeys.getConditions(); }); it('should return conditions', () => { @@ -35,71 +35,71 @@ describe('Filtered Search Token Keys', () => { describe('searchByKey', () => { it('should return null when key not found', () => { - const tokenKey = gl.FilteredSearchTokenKeys.searchByKey('notakey'); + const tokenKey = FilteredSearchTokenKeys.searchByKey('notakey'); expect(tokenKey === null).toBe(true); }); it('should return tokenKey when found by key', () => { - const tokenKeys = gl.FilteredSearchTokenKeys.get(); - const result = gl.FilteredSearchTokenKeys.searchByKey(tokenKeys[0].key); + const tokenKeys = FilteredSearchTokenKeys.get(); + const result = FilteredSearchTokenKeys.searchByKey(tokenKeys[0].key); expect(result).toEqual(tokenKeys[0]); }); }); describe('searchBySymbol', () => { it('should return null when symbol not found', () => { - const tokenKey = gl.FilteredSearchTokenKeys.searchBySymbol('notasymbol'); + const tokenKey = FilteredSearchTokenKeys.searchBySymbol('notasymbol'); expect(tokenKey === null).toBe(true); }); it('should return tokenKey when found by symbol', () => { - const tokenKeys = gl.FilteredSearchTokenKeys.get(); - const result = gl.FilteredSearchTokenKeys.searchBySymbol(tokenKeys[0].symbol); + const tokenKeys = FilteredSearchTokenKeys.get(); + const result = FilteredSearchTokenKeys.searchBySymbol(tokenKeys[0].symbol); expect(result).toEqual(tokenKeys[0]); }); }); describe('searchByKeyParam', () => { it('should return null when key param not found', () => { - const tokenKey = gl.FilteredSearchTokenKeys.searchByKeyParam('notakeyparam'); + const tokenKey = FilteredSearchTokenKeys.searchByKeyParam('notakeyparam'); expect(tokenKey === null).toBe(true); }); it('should return tokenKey when found by key param', () => { - const tokenKeys = gl.FilteredSearchTokenKeys.get(); - const result = gl.FilteredSearchTokenKeys.searchByKeyParam(`${tokenKeys[0].key}_${tokenKeys[0].param}`); + const tokenKeys = FilteredSearchTokenKeys.get(); + const result = FilteredSearchTokenKeys.searchByKeyParam(`${tokenKeys[0].key}_${tokenKeys[0].param}`); expect(result).toEqual(tokenKeys[0]); }); it('should return alternative tokenKey when found by key param', () => { - const tokenKeys = gl.FilteredSearchTokenKeys.getAlternatives(); - const result = gl.FilteredSearchTokenKeys.searchByKeyParam(`${tokenKeys[0].key}_${tokenKeys[0].param}`); + const tokenKeys = FilteredSearchTokenKeys.getAlternatives(); + const result = FilteredSearchTokenKeys.searchByKeyParam(`${tokenKeys[0].key}_${tokenKeys[0].param}`); expect(result).toEqual(tokenKeys[0]); }); }); describe('searchByConditionUrl', () => { it('should return null when condition url not found', () => { - const condition = gl.FilteredSearchTokenKeys.searchByConditionUrl(null); + const condition = FilteredSearchTokenKeys.searchByConditionUrl(null); expect(condition === null).toBe(true); }); it('should return condition when found by url', () => { - const conditions = gl.FilteredSearchTokenKeys.getConditions(); - const result = gl.FilteredSearchTokenKeys.searchByConditionUrl(conditions[0].url); + const conditions = FilteredSearchTokenKeys.getConditions(); + const result = FilteredSearchTokenKeys.searchByConditionUrl(conditions[0].url); expect(result).toBe(conditions[0]); }); }); describe('searchByConditionKeyValue', () => { it('should return null when condition tokenKey and value not found', () => { - const condition = gl.FilteredSearchTokenKeys.searchByConditionKeyValue(null, null); + const condition = FilteredSearchTokenKeys.searchByConditionKeyValue(null, null); expect(condition === null).toBe(true); }); it('should return condition when found by tokenKey and value', () => { - const conditions = gl.FilteredSearchTokenKeys.getConditions(); - const result = gl.FilteredSearchTokenKeys + const conditions = FilteredSearchTokenKeys.getConditions(); + const result = FilteredSearchTokenKeys .searchByConditionKeyValue(conditions[0].tokenKey, conditions[0].value); expect(result).toEqual(conditions[0]); }); diff --git a/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js b/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js index 585bea9b499..bf8b66f1110 100644 --- a/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js +++ b/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js @@ -1,8 +1,8 @@ -import '~/filtered_search/filtered_search_token_keys'; +import FilteredSearchTokenKeys from '~/filtered_search/filtered_search_token_keys'; import '~/filtered_search/filtered_search_tokenizer'; describe('Filtered Search Tokenizer', () => { - const allowedKeys = gl.FilteredSearchTokenKeys.getKeys(); + const allowedKeys = FilteredSearchTokenKeys.getKeys(); describe('processTokens', () => { it('returns for input containing only search value', () => { diff --git a/spec/javascripts/fixtures/groups.rb b/spec/javascripts/fixtures/groups.rb new file mode 100644 index 00000000000..35be52fbf97 --- /dev/null +++ b/spec/javascripts/fixtures/groups.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe 'Groups (JavaScript fixtures)', type: :controller do + include JavaScriptFixturesHelpers + + let(:admin) { create(:admin) } + let(:group) { create(:group, name: 'frontend-fixtures-group' )} + + render_views + + before(:all) do + clean_frontend_fixtures('groups/') + end + + before do + group.add_master(admin) + sign_in(admin) + end + + describe Groups::Settings::CiCdController, '(JavaScript fixtures)', type: :controller do + it 'groups/ci_cd_settings.html.raw' do |example| + get :show, + group_id: group + + expect(response).to be_success + store_frontend_fixture(response, example.description) + end + end +end diff --git a/spec/javascripts/fixtures/projects.rb b/spec/javascripts/fixtures/projects.rb index 2a100e7fab5..b344b389241 100644 --- a/spec/javascripts/fixtures/projects.rb +++ b/spec/javascripts/fixtures/projects.rb @@ -1,11 +1,14 @@ require 'spec_helper' -describe ProjectsController, '(JavaScript fixtures)', type: :controller do +describe 'Projects (JavaScript fixtures)', type: :controller do include JavaScriptFixturesHelpers let(:admin) { create(:admin) } let(:namespace) { create(:namespace, name: 'frontend-fixtures' )} let(:project) { create(:project, namespace: namespace, path: 'builds-project') } + let(:project_variable_populated) { create(:project, namespace: namespace, path: 'builds-project2') } + let!(:variable1) { create(:ci_variable, project: project_variable_populated) } + let!(:variable2) { create(:ci_variable, project: project_variable_populated) } render_views @@ -14,6 +17,9 @@ describe ProjectsController, '(JavaScript fixtures)', type: :controller do end before do + # EE-specific start + # EE specific end + project.add_master(admin) sign_in(admin) end @@ -21,12 +27,43 @@ describe ProjectsController, '(JavaScript fixtures)', type: :controller do remove_repository(project) end - it 'projects/dashboard.html.raw' do |example| - get :show, - namespace_id: project.namespace.to_param, - id: project + describe ProjectsController, '(JavaScript fixtures)', type: :controller do + it 'projects/dashboard.html.raw' do |example| + get :show, + namespace_id: project.namespace.to_param, + id: project - expect(response).to be_success - store_frontend_fixture(response, example.description) + expect(response).to be_success + store_frontend_fixture(response, example.description) + end + + it 'projects/edit.html.raw' do |example| + get :edit, + namespace_id: project.namespace.to_param, + id: project + + expect(response).to be_success + store_frontend_fixture(response, example.description) + end + end + + describe Projects::Settings::CiCdController, '(JavaScript fixtures)', type: :controller do + it 'projects/ci_cd_settings.html.raw' do |example| + get :show, + namespace_id: project.namespace.to_param, + project_id: project + + expect(response).to be_success + store_frontend_fixture(response, example.description) + end + + it 'projects/ci_cd_settings_with_variables.html.raw' do |example| + get :show, + namespace_id: project_variable_populated.namespace.to_param, + project_id: project_variable_populated + + expect(response).to be_success + store_frontend_fixture(response, example.description) + end end end diff --git a/spec/javascripts/gl_form_spec.js b/spec/javascripts/gl_form_spec.js index 5a8009e57fd..9c1fc0fda9e 100644 --- a/spec/javascripts/gl_form_spec.js +++ b/spec/javascripts/gl_form_spec.js @@ -1,10 +1,8 @@ -import Autosize from 'autosize'; +import autosize from 'autosize'; import GLForm from '~/gl_form'; import '~/lib/utils/text_utility'; import '~/lib/utils/common_utils'; -window.autosize = Autosize; - describe('GLForm', () => { describe('when instantiated', function () { beforeEach((done) => { @@ -13,14 +11,12 @@ describe('GLForm', () => { spyOn($.prototype, 'off').and.returnValue(this.textarea); spyOn($.prototype, 'on').and.returnValue(this.textarea); spyOn($.prototype, 'css'); - spyOn(window, 'autosize'); - this.glForm = new GLForm(this.form); + this.glForm = new GLForm(this.form, false); setTimeout(() => { $.prototype.off.calls.reset(); $.prototype.on.calls.reset(); $.prototype.css.calls.reset(); - window.autosize.calls.reset(); done(); }); }); @@ -43,10 +39,6 @@ describe('GLForm', () => { expect($.prototype.on).toHaveBeenCalledWith('mouseup.autosize', jasmine.any(Function)); }); - it('should autosize the textarea', () => { - expect(window.autosize).toHaveBeenCalledWith(jasmine.any(Object)); - }); - it('should set the resize css property to vertical', () => { expect($.prototype.css).toHaveBeenCalledWith('resize', 'vertical'); }); @@ -74,7 +66,7 @@ describe('GLForm', () => { spyOn($.prototype, 'data'); spyOn($.prototype, 'outerHeight').and.returnValue(200); spyOn(window, 'outerHeight').and.returnValue(400); - spyOn(window.autosize, 'destroy'); + spyOn(autosize, 'destroy'); this.glForm.destroyAutosize(); }); @@ -88,7 +80,7 @@ describe('GLForm', () => { }); it('should call autosize destroy', () => { - expect(window.autosize.destroy).toHaveBeenCalledWith(this.textarea); + expect(autosize.destroy).toHaveBeenCalledWith(this.textarea); }); it('should set the data-height attribute', () => { @@ -107,9 +99,9 @@ describe('GLForm', () => { it('should return undefined if the data-height equals the outerHeight', () => { spyOn($.prototype, 'outerHeight').and.returnValue(200); spyOn($.prototype, 'data').and.returnValue(200); - spyOn(window.autosize, 'destroy'); + spyOn(autosize, 'destroy'); expect(this.glForm.destroyAutosize()).toBeUndefined(); - expect(window.autosize.destroy).not.toHaveBeenCalled(); + expect(autosize.destroy).not.toHaveBeenCalled(); }); }); }); diff --git a/spec/javascripts/gpg_badges_spec.js b/spec/javascripts/gpg_badges_spec.js index 7a826487bf9..5decb5e6bbd 100644 --- a/spec/javascripts/gpg_badges_spec.js +++ b/spec/javascripts/gpg_badges_spec.js @@ -1,6 +1,9 @@ +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; import GpgBadges from '~/gpg_badges'; describe('GpgBadges', () => { + let mock; const dummyCommitSha = 'n0m0rec0ffee'; const dummyBadgeHtml = 'dummy html'; const dummyResponse = { @@ -11,38 +14,43 @@ describe('GpgBadges', () => { }; beforeEach(() => { + mock = new MockAdapter(axios); setFixtures(` + <form + class="commits-search-form" data-signatures-path="/hello" action="/hello" + method="get"> + <input name="utf8" type="hidden" value="✓"> + <input type="search" name="search" id="commits-search"class="form-control search-text-input input-short"> + </form> <div class="parent-container"> <div class="js-loading-gpg-badge" data-commit-sha="${dummyCommitSha}"></div> </div> `); }); - it('displays a loading spinner', () => { - spyOn($, 'get').and.returnValue({ - done() { - // intentionally left blank - }, - }); + afterEach(() => { + mock.restore(); + }); - GpgBadges.fetch(); + it('displays a loading spinner', (done) => { + mock.onGet('/hello').reply(200); - expect(document.querySelector('.js-loading-gpg-badge:empty')).toBe(null); - const spinners = document.querySelectorAll('.js-loading-gpg-badge i.fa.fa-spinner.fa-spin'); - expect(spinners.length).toBe(1); + GpgBadges.fetch().then(() => { + expect(document.querySelector('.js-loading-gpg-badge:empty')).toBe(null); + const spinners = document.querySelectorAll('.js-loading-gpg-badge i.fa.fa-spinner.fa-spin'); + expect(spinners.length).toBe(1); + done(); + }).catch(done.fail); }); - it('replaces the loading spinner', () => { - spyOn($, 'get').and.returnValue({ - done(callback) { - callback(dummyResponse); - }, - }); - - GpgBadges.fetch(); + it('replaces the loading spinner', (done) => { + mock.onGet('/hello').reply(200, dummyResponse); - expect(document.querySelector('.js-loading-gpg-badge')).toBe(null); - const parentContainer = document.querySelector('.parent-container'); - expect(parentContainer.innerHTML.trim()).toEqual(dummyBadgeHtml); + GpgBadges.fetch().then(() => { + expect(document.querySelector('.js-loading-gpg-badge')).toBe(null); + const parentContainer = document.querySelector('.parent-container'); + expect(parentContainer.innerHTML.trim()).toEqual(dummyBadgeHtml); + done(); + }).catch(done.fail); }); }); diff --git a/spec/javascripts/groups/components/app_spec.js b/spec/javascripts/groups/components/app_spec.js index 8338efe915b..3adc29262f3 100644 --- a/spec/javascripts/groups/components/app_spec.js +++ b/spec/javascripts/groups/components/app_spec.js @@ -268,10 +268,10 @@ describe('AppComponent', () => { it('updates props which show modal confirmation dialog', () => { const group = Object.assign({}, mockParentGroupItem); - expect(vm.showModal).toBeFalsy(); + expect(vm.updateModal).toBeFalsy(); expect(vm.groupLeaveConfirmationMessage).toBe(''); vm.showLeaveGroupModal(group, mockParentGroupItem); - expect(vm.showModal).toBeTruthy(); + expect(vm.updateModal).toBeTruthy(); expect(vm.groupLeaveConfirmationMessage).toBe(`Are you sure you want to leave the "${group.fullName}" group?`); }); }); @@ -280,9 +280,9 @@ describe('AppComponent', () => { it('hides modal confirmation which is shown before leaving the group', () => { const group = Object.assign({}, mockParentGroupItem); vm.showLeaveGroupModal(group, mockParentGroupItem); - expect(vm.showModal).toBeTruthy(); + expect(vm.updateModal).toBeTruthy(); vm.hideLeaveGroupModal(); - expect(vm.showModal).toBeFalsy(); + expect(vm.updateModal).toBeFalsy(); }); }); @@ -307,7 +307,7 @@ describe('AppComponent', () => { spyOn($, 'scrollTo'); vm.leaveGroup(); - expect(vm.showModal).toBeFalsy(); + expect(vm.updateModal).toBeFalsy(); expect(vm.targetGroup.isBeingRemoved).toBeTruthy(); expect(vm.service.leaveGroup).toHaveBeenCalledWith(vm.targetGroup.leavePath); setTimeout(() => { @@ -475,7 +475,7 @@ describe('AppComponent', () => { it('renders modal confirmation dialog', () => { vm.groupLeaveConfirmationMessage = 'Are you sure you want to leave the "foo" group?'; - vm.showModal = true; + vm.updateModal = true; const modalDialogEl = vm.$el.querySelector('.modal'); expect(modalDialogEl).not.toBe(null); expect(modalDialogEl.querySelector('.modal-title').innerText.trim()).toBe('Are you sure?'); diff --git a/spec/javascripts/importer_status_spec.js b/spec/javascripts/importer_status_spec.js new file mode 100644 index 00000000000..71a2cd51f63 --- /dev/null +++ b/spec/javascripts/importer_status_spec.js @@ -0,0 +1,107 @@ +import { ImporterStatus } from '~/importer_status'; +import axios from '~/lib/utils/axios_utils'; +import MockAdapter from 'axios-mock-adapter'; + +describe('Importer Status', () => { + let instance; + let mock; + + beforeEach(() => { + mock = new MockAdapter(axios); + }); + + afterEach(() => { + mock.restore(); + }); + + describe('addToImport', () => { + const importUrl = '/import_url'; + + beforeEach(() => { + setFixtures(` + <tr id="repo_123"> + <td class="import-target"></td> + <td class="import-actions job-status"> + <button name="button" type="submit" class="btn btn-import js-add-to-import"> + </button> + </td> + </tr> + `); + spyOn(ImporterStatus.prototype, 'initStatusPage').and.callFake(() => {}); + spyOn(ImporterStatus.prototype, 'setAutoUpdate').and.callFake(() => {}); + instance = new ImporterStatus('', importUrl); + }); + + it('sets table row to active after post request', (done) => { + mock.onPost(importUrl).reply(200, { + id: 1, + full_path: '/full_path', + }); + + instance.addToImport({ + currentTarget: document.querySelector('.js-add-to-import'), + }) + .then(() => { + expect(document.querySelector('tr').classList.contains('active')).toEqual(true); + done(); + }) + .catch(done.fail); + }); + }); + + describe('autoUpdate', () => { + const jobsUrl = '/jobs_url'; + + beforeEach(() => { + const div = document.createElement('div'); + div.innerHTML = ` + <div id="project_1"> + <div class="job-status"> + </div> + </div> + `; + + document.body.appendChild(div); + + spyOn(ImporterStatus.prototype, 'initStatusPage').and.callFake(() => {}); + spyOn(ImporterStatus.prototype, 'setAutoUpdate').and.callFake(() => {}); + instance = new ImporterStatus(jobsUrl); + }); + + function setupMock(importStatus) { + mock.onGet(jobsUrl).reply(200, [{ + id: 1, + import_status: importStatus, + }]); + } + + function expectJobStatus(done, status) { + instance.autoUpdate() + .then(() => { + expect(document.querySelector('#project_1').innerText.trim()).toEqual(status); + done(); + }) + .catch(done.fail); + } + + it('sets the job status to done', (done) => { + setupMock('finished'); + expectJobStatus(done, 'done'); + }); + + it('sets the job status to scheduled', (done) => { + setupMock('scheduled'); + expectJobStatus(done, 'scheduled'); + }); + + it('sets the job status to started', (done) => { + setupMock('started'); + expectJobStatus(done, 'started'); + }); + + it('sets the job status to custom status', (done) => { + setupMock('custom status'); + expectJobStatus(done, 'custom status'); + }); + }); +}); diff --git a/spec/javascripts/issuable_time_tracker_spec.js b/spec/javascripts/issuable_time_tracker_spec.js index 8ff93c4f918..365e9fe6a4b 100644 --- a/spec/javascripts/issuable_time_tracker_spec.js +++ b/spec/javascripts/issuable_time_tracker_spec.js @@ -2,7 +2,7 @@ import Vue from 'vue'; -import timeTracker from '~/sidebar/components/time_tracking/time_tracker'; +import timeTracker from '~/sidebar/components/time_tracking/time_tracker.vue'; function initTimeTrackingComponent(opts) { setFixtures(` diff --git a/spec/javascripts/job_spec.js b/spec/javascripts/job_spec.js index 03b58e9c1d0..b4599688c6d 100644 --- a/spec/javascripts/job_spec.js +++ b/spec/javascripts/job_spec.js @@ -10,6 +10,7 @@ describe('Job', () => { const JOB_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/-/jobs/1`; let mock; let response; + let job; function waitForPromise() { return new Promise(resolve => requestAnimationFrame(resolve)); @@ -22,6 +23,8 @@ describe('Job', () => { spyOn(urlUtils, 'visitUrl'); + response = {}; + mock = new MockAdapter(axios); mock.onGet(new RegExp(`${JOB_URL}/trace.json?(.*)`)).reply(() => [200, response]); @@ -30,7 +33,7 @@ describe('Job', () => { afterEach(() => { mock.restore(); - response = {}; + clearTimeout(job.timeout); }); describe('class constructor', () => { @@ -43,15 +46,19 @@ describe('Job', () => { }); describe('setup', () => { - beforeEach(function () { - this.job = new Job(); + beforeEach(function (done) { + job = new Job(); + + waitForPromise() + .then(done) + .catch(done.fail); }); it('copies build options', function () { - expect(this.job.pagePath).toBe(JOB_URL); - expect(this.job.buildStatus).toBe('success'); - expect(this.job.buildStage).toBe('test'); - expect(this.job.state).toBe(''); + expect(job.pagePath).toBe(JOB_URL); + expect(job.buildStatus).toBe('success'); + expect(job.buildStage).toBe('test'); + expect(job.state).toBe(''); }); it('only shows the jobs matching the current stage', () => { @@ -84,12 +91,12 @@ describe('Job', () => { complete: false, }; - this.job = new Job(); + job = new Job(); waitForPromise() .then(() => { expect($('#build-trace .js-build-output').text()).toMatch(/Update/); - expect(this.job.state).toBe('newstate'); + expect(job.state).toBe('newstate'); response = { html: '<span>More</span>', @@ -103,7 +110,7 @@ describe('Job', () => { .then(waitForPromise) .then(() => { expect($('#build-trace .js-build-output').text()).toMatch(/UpdateMore/); - expect(this.job.state).toBe('finalstate'); + expect(job.state).toBe('finalstate'); }) .then(done) .catch(done.fail); @@ -117,7 +124,7 @@ describe('Job', () => { complete: false, }; - this.job = new Job(); + job = new Job(); waitForPromise() .then(() => { @@ -151,7 +158,7 @@ describe('Job', () => { total: 100, }; - this.job = new Job(); + job = new Job(); waitForPromise() .then(() => { @@ -172,7 +179,7 @@ describe('Job', () => { total: 100, }; - this.job = new Job(); + job = new Job(); waitForPromise() .then(() => { @@ -191,9 +198,10 @@ describe('Job', () => { append: false, size: 50, total: 100, + complete: false, }; - this.job = new Job(); + job = new Job(); waitForPromise() .then(() => { @@ -207,6 +215,7 @@ describe('Job', () => { append: true, size: 10, total: 100, + complete: true, }; }) .then(() => jasmine.clock().tick(4001)) @@ -229,7 +238,7 @@ describe('Job', () => { total: 100, }; - this.job = new Job(); + job = new Job(); expect( document.querySelector('.js-raw-link').textContent.trim(), @@ -247,7 +256,7 @@ describe('Job', () => { total: 100, }; - this.job = new Job(); + job = new Job(); waitForPromise() .then(() => { @@ -269,7 +278,7 @@ describe('Job', () => { total: 100, }; - this.job = new Job(); + job = new Job(); waitForPromise() .then(done) @@ -296,7 +305,7 @@ describe('Job', () => { it('should request build trace with state parameter', (done) => { spyOn(axios, 'get').and.callThrough(); // eslint-disable-next-line no-new - new Job(); + job = new Job(); setTimeout(() => { expect(axios.get).toHaveBeenCalledWith( diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js index 80430011aed..49799c31995 100644 --- a/spec/javascripts/lib/utils/common_utils_spec.js +++ b/spec/javascripts/lib/utils/common_utils_spec.js @@ -480,4 +480,33 @@ describe('common_utils', () => { expect(commonUtils.spriteIcon('test', 'fa fa-test')).toEqual('<svg class="fa fa-test"><use xlink:href="icons.svg#test" /></svg>'); }); }); + + describe('convertObjectPropsToCamelCase', () => { + it('returns new object with camelCase property names by converting object with snake_case names', () => { + const snakeRegEx = /(_\w)/g; + const mockObj = { + id: 1, + group_name: 'GitLab.org', + absolute_web_url: 'https://gitlab.com/gitlab-org/', + }; + const mappings = { + id: 'id', + groupName: 'group_name', + absoluteWebUrl: 'absolute_web_url', + }; + + const convertedObj = commonUtils.convertObjectPropsToCamelCase(mockObj); + + Object.keys(convertedObj).forEach((prop) => { + expect(snakeRegEx.test(prop)).toBeFalsy(); + expect(convertedObj[prop]).toBe(mockObj[mappings[prop]]); + }); + }); + + it('return empty object if method is called with null or undefined', () => { + expect(Object.keys(commonUtils.convertObjectPropsToCamelCase(null)).length).toBe(0); + expect(Object.keys(commonUtils.convertObjectPropsToCamelCase()).length).toBe(0); + expect(Object.keys(commonUtils.convertObjectPropsToCamelCase({})).length).toBe(0); + }); + }); }); diff --git a/spec/javascripts/lib/utils/text_utility_spec.js b/spec/javascripts/lib/utils/text_utility_spec.js index 69a23d7b2f3..e57a55fa71a 100644 --- a/spec/javascripts/lib/utils/text_utility_spec.js +++ b/spec/javascripts/lib/utils/text_utility_spec.js @@ -72,4 +72,10 @@ describe('text_utility', () => { expect(textUtils.stripHtml('This is a text with <p>html</p>.', ' ')).toEqual('This is a text with html .'); }); }); + + describe('convertToCamelCase', () => { + it('converts snake_case string to camelCase string', () => { + expect(textUtils.convertToCamelCase('snake_case')).toBe('snakeCase'); + }); + }); }); diff --git a/spec/javascripts/monitoring/dashboard_state_spec.js b/spec/javascripts/monitoring/dashboard_state_spec.js index 3319eeb3f31..df3198dd3e2 100644 --- a/spec/javascripts/monitoring/dashboard_state_spec.js +++ b/spec/javascripts/monitoring/dashboard_state_spec.js @@ -29,34 +29,6 @@ describe('EmptyState', () => { expect(component.currentState).toBe(component.states.gettingStarted); }); - it('buttonPath returns settings path for the state "gettingStarted"', () => { - const component = createComponent({ - selectedState: 'gettingStarted', - settingsPath: statePaths.settingsPath, - documentationPath: statePaths.documentationPath, - emptyGettingStartedSvgPath: 'foo', - emptyLoadingSvgPath: 'foo', - emptyUnableToConnectSvgPath: 'foo', - }); - - expect(component.buttonPath).toEqual(statePaths.settingsPath); - expect(component.buttonPath).not.toEqual(statePaths.documentationPath); - }); - - it('buttonPath returns documentation path for any of the other states', () => { - const component = createComponent({ - selectedState: 'loading', - settingsPath: statePaths.settingsPath, - documentationPath: statePaths.documentationPath, - emptyGettingStartedSvgPath: 'foo', - emptyLoadingSvgPath: 'foo', - emptyUnableToConnectSvgPath: 'foo', - }); - - expect(component.buttonPath).toEqual(statePaths.documentationPath); - expect(component.buttonPath).not.toEqual(statePaths.settingsPath); - }); - it('showButtonDescription returns a description with a link for the unableToConnect state', () => { const component = createComponent({ selectedState: 'unableToConnect', @@ -88,6 +60,7 @@ describe('EmptyState', () => { const component = createComponent({ selectedState: 'gettingStarted', settingsPath: statePaths.settingsPath, + clustersPath: statePaths.clustersPath, documentationPath: statePaths.documentationPath, emptyGettingStartedSvgPath: 'foo', emptyLoadingSvgPath: 'foo', diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js index 2bbe963e393..f30208b27b6 100644 --- a/spec/javascripts/monitoring/mock_data.js +++ b/spec/javascripts/monitoring/mock_data.js @@ -2471,6 +2471,7 @@ export const deploymentData = [ export const statePaths = { settingsPath: '/root/hello-prometheus/services/prometheus/edit', + clustersPath: '/root/hello-prometheus/clusters', documentationPath: '/help/administration/monitoring/prometheus/index.md', }; diff --git a/spec/javascripts/notebook/cells/markdown_spec.js b/spec/javascripts/notebook/cells/markdown_spec.js index 02304bf5d7d..8f8ba231ae8 100644 --- a/spec/javascripts/notebook/cells/markdown_spec.js +++ b/spec/javascripts/notebook/cells/markdown_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; import MarkdownComponent from '~/notebook/cells/markdown.vue'; -import katex from 'vendor/katex'; +import katex from 'katex'; const Component = Vue.extend(MarkdownComponent); diff --git a/spec/javascripts/notes/components/note_app_spec.js b/spec/javascripts/notes/components/note_app_spec.js index 36c56cd3862..12d180137a0 100644 --- a/spec/javascripts/notes/components/note_app_spec.js +++ b/spec/javascripts/notes/components/note_app_spec.js @@ -2,14 +2,29 @@ import _ from 'underscore'; import Vue from 'vue'; import notesApp from '~/notes/components/notes_app.vue'; import service from '~/notes/services/notes_service'; +import '~/render_gfm'; import * as mockData from '../mock_data'; -import getSetTimeoutPromise from '../../helpers/set_timeout_promise_helper'; + +const vueMatchers = { + toIncludeElement() { + return { + compare(vm, selector) { + const result = { + pass: vm.$el.querySelector(selector) !== null, + }; + return result; + }, + }; + }, +}; describe('note_app', () => { let mountComponent; let vm; beforeEach(() => { + jasmine.addMatchers(vueMatchers); + const IssueNotesApp = Vue.extend(notesApp); mountComponent = (data) => { @@ -105,7 +120,7 @@ describe('note_app', () => { }); it('should render loading icon', () => { - expect(vm.$el.querySelector('.js-loading')).toBeDefined(); + expect(vm).toIncludeElement('.js-loading'); }); it('should render form', () => { @@ -118,10 +133,14 @@ describe('note_app', () => { describe('update note', () => { describe('individual note', () => { - beforeEach(() => { + beforeEach((done) => { Vue.http.interceptors.push(mockData.individualNoteInterceptor); spyOn(service, 'updateNote').and.callThrough(); vm = mountComponent(); + setTimeout(() => { + vm.$el.querySelector('.js-note-edit').click(); + Vue.nextTick(done); + }, 0); }); afterEach(() => { @@ -131,40 +150,32 @@ describe('note_app', () => { ); }); - it('renders edit form', (done) => { - setTimeout(() => { - vm.$el.querySelector('.js-note-edit').click(); - Vue.nextTick(() => { - expect(vm.$el.querySelector('.js-vue-issue-note-form')).toBeDefined(); - done(); - }); - }, 0); + it('renders edit form', () => { + expect(vm).toIncludeElement('.js-vue-issue-note-form'); }); it('calls the service to update the note', (done) => { - getSetTimeoutPromise() - .then(() => { - vm.$el.querySelector('.js-note-edit').click(); - }) - .then(Vue.nextTick) - .then(() => { - vm.$el.querySelector('.js-vue-issue-note-form').value = 'this is a note'; - vm.$el.querySelector('.js-vue-issue-save').click(); - - expect(service.updateNote).toHaveBeenCalled(); - }) - // Wait for the requests to finish before destroying - .then(Vue.nextTick) + vm.$el.querySelector('.js-vue-issue-note-form').value = 'this is a note'; + vm.$el.querySelector('.js-vue-issue-save').click(); + + expect(service.updateNote).toHaveBeenCalled(); + // Wait for the requests to finish before destroying + Vue.nextTick() .then(done) .catch(done.fail); }); }); - describe('dicussion note', () => { - beforeEach(() => { + describe('discussion note', () => { + beforeEach((done) => { Vue.http.interceptors.push(mockData.discussionNoteInterceptor); spyOn(service, 'updateNote').and.callThrough(); vm = mountComponent(); + + setTimeout(() => { + vm.$el.querySelector('.js-note-edit').click(); + Vue.nextTick(done); + }, 0); }); afterEach(() => { @@ -174,30 +185,17 @@ describe('note_app', () => { ); }); - it('renders edit form', (done) => { - setTimeout(() => { - vm.$el.querySelector('.js-note-edit').click(); - Vue.nextTick(() => { - expect(vm.$el.querySelector('.js-vue-issue-note-form')).toBeDefined(); - done(); - }); - }, 0); + it('renders edit form', () => { + expect(vm).toIncludeElement('.js-vue-issue-note-form'); }); it('updates the note and resets the edit form', (done) => { - getSetTimeoutPromise() - .then(() => { - vm.$el.querySelector('.js-note-edit').click(); - }) - .then(Vue.nextTick) - .then(() => { - vm.$el.querySelector('.js-vue-issue-note-form').value = 'this is a note'; - vm.$el.querySelector('.js-vue-issue-save').click(); - - expect(service.updateNote).toHaveBeenCalled(); - }) - // Wait for the requests to finish before destroying - .then(Vue.nextTick) + vm.$el.querySelector('.js-vue-issue-note-form').value = 'this is a note'; + vm.$el.querySelector('.js-vue-issue-save').click(); + + expect(service.updateNote).toHaveBeenCalled(); + // Wait for the requests to finish before destroying + Vue.nextTick() .then(done) .catch(done.fail); }); diff --git a/spec/javascripts/notes/components/noteable_note_spec.js b/spec/javascripts/notes/components/noteable_note_spec.js index cb63b64724d..88a7ffb0b9c 100644 --- a/spec/javascripts/notes/components/noteable_note_spec.js +++ b/spec/javascripts/notes/components/noteable_note_spec.js @@ -56,4 +56,25 @@ describe('issue_note', () => { done(); }, 0); }); + + describe('cancel edit', () => { + it('restores content of updated note', (done) => { + const noteBody = 'updated note text'; + vm.updateNote = () => Promise.resolve(); + + vm.formUpdateHandler(noteBody, null, $.noop); + + setTimeout(() => { + expect(vm.note.note_html).toEqual(noteBody); + + vm.formCancelHandler(); + + setTimeout(() => { + expect(vm.note.note_html).toEqual(noteBody); + + done(); + }); + }); + }); + }); }); diff --git a/spec/javascripts/notes/helpers.js b/spec/javascripts/notes/helpers.js new file mode 100644 index 00000000000..a7663710a56 --- /dev/null +++ b/spec/javascripts/notes/helpers.js @@ -0,0 +1,12 @@ +// eslint-disable-next-line import/prefer-default-export +export const resetStore = (store) => { + store.replaceState({ + notes: [], + targetNoteHash: null, + lastFetchedAt: null, + + notesData: {}, + userData: {}, + noteableData: {}, + }); +}; diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js index f0c800c759d..ccf4bd070c2 100644 --- a/spec/javascripts/notes/mock_data.js +++ b/spec/javascripts/notes/mock_data.js @@ -7,6 +7,8 @@ export const notesDataMock = { notesPath: '/gitlab-org/gitlab-ce/noteable/issue/98/notes', quickActionsDocsPath: '/help/user/project/quick_actions', registerPath: '/users/sign_in?redirect_to_referer=yes#register-pane', + closeIssuePath: '/twitter/flight/issues/9.json?issue%5Bstate_event%5D=close', + reopenIssuePath: '/twitter/flight/issues/9.json?issue%5Bstate_event%5D=reopen', }; export const userDataMock = { diff --git a/spec/javascripts/notes/stores/actions_spec.js b/spec/javascripts/notes/stores/actions_spec.js index e092320f9a3..ab80ed7bbfb 100644 --- a/spec/javascripts/notes/stores/actions_spec.js +++ b/spec/javascripts/notes/stores/actions_spec.js @@ -1,8 +1,16 @@ +import Vue from 'vue'; +import _ from 'underscore'; import * as actions from '~/notes/stores/actions'; +import store from '~/notes/stores'; import testAction from '../../helpers/vuex_action_helper'; +import { resetStore } from '../helpers'; import { discussionMock, notesDataMock, userDataMock, noteableDataMock, individualNote } from '../mock_data'; describe('Actions Notes Store', () => { + afterEach(() => { + resetStore(store); + }); + describe('setNotesData', () => { it('should set received notes data', (done) => { testAction(actions.setNotesData, null, { notesData: {} }, [ @@ -58,4 +66,67 @@ describe('Actions Notes Store', () => { ], done); }); }); + + describe('async methods', () => { + const interceptor = (request, next) => { + next(request.respondWith(JSON.stringify({}), { + status: 200, + })); + }; + + beforeEach(() => { + Vue.http.interceptors.push(interceptor); + }); + + afterEach(() => { + Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor); + }); + + describe('closeIssue', () => { + it('sets state as closed', (done) => { + store.dispatch('closeIssue', { notesData: { closeIssuePath: '' } }) + .then(() => { + expect(store.state.noteableData.state).toEqual('closed'); + done(); + }) + .catch(done.fail); + }); + }); + + describe('reopenIssue', () => { + it('sets state as reopened', (done) => { + store.dispatch('reopenIssue', { notesData: { reopenIssuePath: '' } }) + .then(() => { + expect(store.state.noteableData.state).toEqual('reopened'); + done(); + }) + .catch(done.fail); + }); + }); + }); + + describe('emitStateChangedEvent', () => { + it('emits an event on the document', () => { + document.addEventListener('issuable_vue_app:change', (event) => { + expect(event.detail.data).toEqual({ id: '1', state: 'closed' }); + expect(event.detail.isClosed).toEqual(false); + }); + + store.dispatch('emitStateChangedEvent', { id: '1', state: 'closed' }); + }); + }); + + describe('toggleIssueLocalState', () => { + it('sets issue state as closed', (done) => { + testAction(actions.toggleIssueLocalState, 'closed', {}, [ + { type: 'CLOSE_ISSUE', payload: 'closed' }, + ], done); + }); + + it('sets issue state as reopened', (done) => { + testAction(actions.toggleIssueLocalState, 'reopened', {}, [ + { type: 'REOPEN_ISSUE', payload: 'reopened' }, + ], done); + }); + }); }); diff --git a/spec/javascripts/notes/stores/getters_spec.js b/spec/javascripts/notes/stores/getters_spec.js index c5a84b71788..919ffbfdef0 100644 --- a/spec/javascripts/notes/stores/getters_spec.js +++ b/spec/javascripts/notes/stores/getters_spec.js @@ -55,4 +55,10 @@ describe('Getters Notes Store', () => { expect(getters.getCurrentUserLastNote(state)).toEqual(individualNote.notes[0]); }); }); + + describe('issueState', () => { + it('should return the issue state', () => { + expect(getters.issueState(state)).toEqual(noteableDataMock.state); + }); + }); }); diff --git a/spec/javascripts/pipeline_schedules/interval_pattern_input_spec.js b/spec/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input_spec.js index 040d14efed2..4655e29eed0 100644 --- a/spec/javascripts/pipeline_schedules/interval_pattern_input_spec.js +++ b/spec/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; import Translate from '~/vue_shared/translate'; -import IntervalPatternInput from '~/pipeline_schedules/components/interval_pattern_input.vue'; +import IntervalPatternInput from '~/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue'; Vue.use(Translate); diff --git a/spec/javascripts/pipeline_schedules/pipeline_schedule_callout_spec.js b/spec/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedule_callout_spec.js index ed481cb60a1..f95a7cef18a 100644 --- a/spec/javascripts/pipeline_schedules/pipeline_schedule_callout_spec.js +++ b/spec/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedule_callout_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; import Cookies from 'js-cookie'; -import PipelineSchedulesCallout from '~/pipeline_schedules/components/pipeline_schedules_callout.vue'; +import PipelineSchedulesCallout from '~/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue'; const PipelineSchedulesCalloutComponent = Vue.extend(PipelineSchedulesCallout); const cookieKey = 'pipeline_schedules_callout_dismissed'; diff --git a/spec/javascripts/pipelines/async_button_spec.js b/spec/javascripts/pipelines/async_button_spec.js index d010d897642..8ce33d410a7 100644 --- a/spec/javascripts/pipelines/async_button_spec.js +++ b/spec/javascripts/pipelines/async_button_spec.js @@ -15,6 +15,7 @@ describe('Pipelines Async Button', () => { title: 'Foo', icon: 'repeat', cssClass: 'bar', + id: 123, }, }).$mount(); }); @@ -38,9 +39,8 @@ describe('Pipelines Async Button', () => { describe('With confirm dialog', () => { it('should call the service when confimation is positive', () => { - spyOn(window, 'confirm').and.returnValue(true); - eventHub.$on('postAction', (endpoint) => { - expect(endpoint).toEqual('/foo'); + eventHub.$on('actionConfirmationModal', (data) => { + expect(data.id).toEqual(123); }); component = new AsyncButtonComponent({ @@ -49,7 +49,7 @@ describe('Pipelines Async Button', () => { title: 'Foo', icon: 'fa fa-foo', cssClass: 'bar', - confirmActionMessage: 'bar', + id: 123, }, }).$mount(); diff --git a/spec/javascripts/settings_panels_spec.js b/spec/javascripts/settings_panels_spec.js new file mode 100644 index 00000000000..d433f8c3e07 --- /dev/null +++ b/spec/javascripts/settings_panels_spec.js @@ -0,0 +1,29 @@ +import initSettingsPanels from '~/settings_panels'; + +describe('Settings Panels', () => { + preloadFixtures('projects/ci_cd_settings.html.raw'); + + beforeEach(() => { + loadFixtures('projects/ci_cd_settings.html.raw'); + }); + + describe('initSettingsPane', () => { + afterEach(() => { + location.hash = ''; + }); + + it('should expand linked hash fragment panel', () => { + location.hash = '#js-general-pipeline-settings'; + + const pipelineSettingsPanel = document.querySelector('#js-general-pipeline-settings'); + // Our test environment automatically expands everything so we need to clear that out first + pipelineSettingsPanel.classList.remove('expanded'); + + expect(pipelineSettingsPanel.classList.contains('expanded')).toBe(false); + + initSettingsPanels(); + + expect(pipelineSettingsPanel.classList.contains('expanded')).toBe(true); + }); + }); +}); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js index 720effb5c1c..3d7f4abd420 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js @@ -1,38 +1,22 @@ import Vue from 'vue'; -import missingBranchComponent from '~/vue_merge_request_widget/components/states/mr_widget_missing_branch'; - -const createComponent = () => { - const Component = Vue.extend(missingBranchComponent); - const mr = { - sourceBranchRemoved: true, - }; - - return new Component({ - el: document.createElement('div'), - propsData: { mr }, - }); -}; +import missingBranchComponent from '~/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue'; +import mountComponent from '../../../helpers/vue_mount_component_helper'; describe('MRWidgetMissingBranch', () => { - describe('props', () => { - it('should have props', () => { - const mrProp = missingBranchComponent.props.mr; + let vm; - expect(mrProp.type instanceof Object).toBeTruthy(); - expect(mrProp.required).toBeTruthy(); - }); + beforeEach(() => { + const Component = Vue.extend(missingBranchComponent); + vm = mountComponent(Component, { mr: { sourceBranchRemoved: true } }); }); - describe('components', () => { - it('should have components added', () => { - expect(missingBranchComponent.components['mr-widget-merge-help']).toBeDefined(); - }); + afterEach(() => { + vm.$destroy(); }); describe('computed', () => { describe('missingBranchName', () => { it('should return proper branch name', () => { - const vm = createComponent(); expect(vm.missingBranchName).toEqual('source'); vm.mr.sourceBranchRemoved = false; @@ -43,7 +27,7 @@ describe('MRWidgetMissingBranch', () => { describe('template', () => { it('should have correct elements', () => { - const el = createComponent().$el; + const el = vm.$el; const content = el.textContent.replace(/\n(\s)+/g, ' ').trim(); expect(el.classList.contains('mr-widget-body')).toBeTruthy(); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_not_allowed_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_not_allowed_spec.js index 33f20ab132d..c89e863d904 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_not_allowed_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_not_allowed_spec.js @@ -1,17 +1,24 @@ import Vue from 'vue'; -import notAllowedComponent from '~/vue_merge_request_widget/components/states/mr_widget_not_allowed'; +import notAllowedComponent from '~/vue_merge_request_widget/components/states/mr_widget_not_allowed.vue'; +import mountComponent from '../../../helpers/vue_mount_component_helper'; describe('MRWidgetNotAllowed', () => { - describe('template', () => { + let vm; + beforeEach(() => { const Component = Vue.extend(notAllowedComponent); - const vm = new Component({ - el: document.createElement('div'), - }); - it('should have correct elements', () => { - expect(vm.$el.classList.contains('mr-widget-body')).toBeTruthy(); - expect(vm.$el.querySelector('button').getAttribute('disabled')).toBeTruthy(); - expect(vm.$el.innerText).toContain('Ready to be merged automatically.'); - expect(vm.$el.innerText).toContain('Ask someone with write access to this repository to merge this request'); - }); + vm = mountComponent(Component); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('renders success icon', () => { + expect(vm.$el.querySelector('.ci-status-icon-success')).not.toBe(null); + }); + + it('renders informative text', () => { + expect(vm.$el.innerText).toContain('Ready to be merged automatically.'); + expect(vm.$el.innerText).toContain('Ask someone with write access to this repository to merge this request'); }); }); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_blocked_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_blocked_spec.js index d0702f9f503..edab26286bc 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_blocked_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_blocked_spec.js @@ -1,16 +1,23 @@ import Vue from 'vue'; -import pipelineBlockedComponent from '~/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked'; +import pipelineBlockedComponent from '~/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue'; +import mountComponent from '../../../helpers/vue_mount_component_helper'; describe('MRWidgetPipelineBlocked', () => { - describe('template', () => { + let vm; + beforeEach(() => { const Component = Vue.extend(pipelineBlockedComponent); - const vm = new Component({ - el: document.createElement('div'), - }); - it('should have correct elements', () => { - expect(vm.$el.classList.contains('mr-widget-body')).toBeTruthy(); - expect(vm.$el.querySelector('button').getAttribute('disabled')).toBeTruthy(); - expect(vm.$el.innerText).toContain('Pipeline blocked. The pipeline for this merge request requires a manual action to proceed'); - }); + vm = mountComponent(Component); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('renders warning icon', () => { + expect(vm.$el.querySelector('.ci-status-icon-warning')).not.toBe(null); + }); + + it('renders information text', () => { + expect(vm.$el.textContent.trim().replace(/[\r\n]+/g, ' ')).toContain('Pipeline blocked. The pipeline for this merge request requires a manual action to proceed'); }); }); diff --git a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js index cd00d0a39a3..45035effe81 100644 --- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js +++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js @@ -295,6 +295,15 @@ describe('mrWidgetOptions', () => { expect(notify.notifyMe).not.toHaveBeenCalled(); }); + + it('should not notify if no pipeline provided', () => { + vm.handleNotification({ + ...data, + pipeline: undefined, + }); + + expect(notify.notifyMe).not.toHaveBeenCalled(); + }); }); describe('resumePolling', () => { diff --git a/spec/javascripts/vue_shared/components/confirmation_input_spec.js b/spec/javascripts/vue_shared/components/confirmation_input_spec.js deleted file mode 100644 index a6a12614e77..00000000000 --- a/spec/javascripts/vue_shared/components/confirmation_input_spec.js +++ /dev/null @@ -1,63 +0,0 @@ -import Vue from 'vue'; -import confirmationInput from '~/vue_shared/components/confirmation_input.vue'; -import mountComponent from '../../helpers/vue_mount_component_helper'; - -describe('Confirmation input component', () => { - const Component = Vue.extend(confirmationInput); - const props = { - inputId: 'dummy-id', - confirmationKey: 'confirmation-key', - confirmationValue: 'confirmation-value', - }; - let vm; - - afterEach(() => { - vm.$destroy(); - }); - - describe('props', () => { - beforeEach(() => { - vm = mountComponent(Component, props); - }); - - it('sets id of the input field to inputId', () => { - expect(vm.$refs.enteredValue.id).toBe(props.inputId); - }); - - it('sets name of the input field to confirmationKey', () => { - expect(vm.$refs.enteredValue.name).toBe(props.confirmationKey); - }); - }); - - describe('computed', () => { - describe('inputLabel', () => { - it('escapes confirmationValue by default', () => { - vm = mountComponent(Component, { ...props, confirmationValue: 'n<e></e>ds escap"ng' }); - expect(vm.inputLabel).toBe('Type <code>n<e></e>ds escap"ng</code> to confirm:'); - }); - - it('does not escape confirmationValue if escapeValue is false', () => { - vm = mountComponent(Component, { ...props, confirmationValue: 'n<e></e>ds escap"ng', shouldEscapeConfirmationValue: false }); - expect(vm.inputLabel).toBe('Type <code>n<e></e>ds escap"ng</code> to confirm:'); - }); - }); - }); - - describe('methods', () => { - describe('hasCorrectValue', () => { - beforeEach(() => { - vm = mountComponent(Component, props); - }); - - it('returns false if entered value is incorrect', () => { - vm.$refs.enteredValue.value = 'incorrect'; - expect(vm.hasCorrectValue()).toBe(false); - }); - - it('returns true if entered value is correct', () => { - vm.$refs.enteredValue.value = props.confirmationValue; - expect(vm.hasCorrectValue()).toBe(true); - }); - }); - }); -}); diff --git a/spec/javascripts/vue_shared/components/gl_modal_spec.js b/spec/javascripts/vue_shared/components/gl_modal_spec.js new file mode 100644 index 00000000000..d6148cb785b --- /dev/null +++ b/spec/javascripts/vue_shared/components/gl_modal_spec.js @@ -0,0 +1,192 @@ +import Vue from 'vue'; +import GlModal from '~/vue_shared/components/gl_modal.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; + +const modalComponent = Vue.extend(GlModal); + +describe('GlModal', () => { + let vm; + + afterEach(() => { + vm.$destroy(); + }); + + describe('props', () => { + describe('with id', () => { + const props = { + id: 'my-modal', + }; + + beforeEach(() => { + vm = mountComponent(modalComponent, props); + }); + + it('assigns the id to the modal', () => { + expect(vm.$el.id).toBe(props.id); + }); + }); + + describe('without id', () => { + beforeEach(() => { + vm = mountComponent(modalComponent, { }); + }); + + it('does not add an id attribute to the modal', () => { + expect(vm.$el.hasAttribute('id')).toBe(false); + }); + }); + + describe('with headerTitleText', () => { + const props = { + headerTitleText: 'my title text', + }; + + beforeEach(() => { + vm = mountComponent(modalComponent, props); + }); + + it('sets the modal title', () => { + const modalTitle = vm.$el.querySelector('.modal-title'); + expect(modalTitle.innerHTML.trim()).toBe(props.headerTitleText); + }); + }); + + describe('with footerPrimaryButtonVariant', () => { + const props = { + footerPrimaryButtonVariant: 'danger', + }; + + beforeEach(() => { + vm = mountComponent(modalComponent, props); + }); + + it('sets the primary button class', () => { + const primaryButton = vm.$el.querySelector('.modal-footer button:last-of-type'); + expect(primaryButton).toHaveClass(`btn-${props.footerPrimaryButtonVariant}`); + }); + }); + + describe('with footerPrimaryButtonText', () => { + const props = { + footerPrimaryButtonText: 'my button text', + }; + + beforeEach(() => { + vm = mountComponent(modalComponent, props); + }); + + it('sets the primary button text', () => { + const primaryButton = vm.$el.querySelector('.modal-footer button:last-of-type'); + expect(primaryButton.innerHTML.trim()).toBe(props.footerPrimaryButtonText); + }); + }); + }); + + it('works with data-toggle="modal"', (done) => { + setFixtures(` + <button id="modal-button" data-toggle="modal" data-target="#my-modal"></button> + <div id="modal-container"></div> + `); + + const modalContainer = document.getElementById('modal-container'); + const modalButton = document.getElementById('modal-button'); + vm = mountComponent(modalComponent, { + id: 'my-modal', + }, modalContainer); + $(vm.$el).on('shown.bs.modal', () => done()); + + modalButton.click(); + }); + + describe('methods', () => { + const dummyEvent = 'not really an event'; + + beforeEach(() => { + vm = mountComponent(modalComponent, { }); + spyOn(vm, '$emit'); + }); + + describe('emitCancel', () => { + it('emits a cancel event', () => { + vm.emitCancel(dummyEvent); + + expect(vm.$emit).toHaveBeenCalledWith('cancel', dummyEvent); + }); + }); + + describe('emitSubmit', () => { + it('emits a submit event', () => { + vm.emitSubmit(dummyEvent); + + expect(vm.$emit).toHaveBeenCalledWith('submit', dummyEvent); + }); + }); + }); + + describe('slots', () => { + const slotContent = 'this should go into the slot'; + const modalWithSlot = (slotName) => { + let template; + if (slotName) { + template = ` + <gl-modal> + <template slot="${slotName}">${slotContent}</template> + </gl-modal> + `; + } else { + template = `<gl-modal>${slotContent}</gl-modal>`; + } + + return Vue.extend({ + components: { + GlModal, + }, + template, + }); + }; + + describe('default slot', () => { + beforeEach(() => { + vm = mountComponent(modalWithSlot()); + }); + + it('sets the modal body', () => { + const modalBody = vm.$el.querySelector('.modal-body'); + expect(modalBody.innerHTML).toBe(slotContent); + }); + }); + + describe('header slot', () => { + beforeEach(() => { + vm = mountComponent(modalWithSlot('header')); + }); + + it('sets the modal header', () => { + const modalHeader = vm.$el.querySelector('.modal-header'); + expect(modalHeader.innerHTML).toBe(slotContent); + }); + }); + + describe('title slot', () => { + beforeEach(() => { + vm = mountComponent(modalWithSlot('title')); + }); + + it('sets the modal title', () => { + const modalTitle = vm.$el.querySelector('.modal-title'); + expect(modalTitle.innerHTML).toBe(slotContent); + }); + }); + + describe('footer slot', () => { + beforeEach(() => { + vm = mountComponent(modalWithSlot('footer')); + }); + + it('sets the modal footer', () => { + const modalFooter = vm.$el.querySelector('.modal-footer'); + expect(modalFooter.innerHTML).toBe(slotContent); + }); + }); + }); +}); diff --git a/spec/lib/backup/repository_spec.rb b/spec/lib/backup/repository_spec.rb index 6ee3d531d6e..f7b1a61f4f8 100644 --- a/spec/lib/backup/repository_spec.rb +++ b/spec/lib/backup/repository_spec.rb @@ -33,10 +33,22 @@ describe Backup::Repository do allow(Gitlab::Popen).to receive(:popen).and_return(['error', 1]) end - it 'shows the appropriate error' do - described_class.new.restore + context 'hashed storage' do + it 'shows the appropriate error' do + described_class.new.restore - expect(progress).to have_received(:puts).with("Ignoring error on #{project.full_path} - error") + expect(progress).to have_received(:puts).with("Ignoring error on #{project.full_path} (#{project.disk_path}) - error") + end + end + + context 'legacy storage' do + let!(:project) { create(:project, :legacy_storage) } + + it 'shows the appropriate error' do + described_class.new.restore + + expect(progress).to have_received(:puts).with("Ignoring error on #{project.full_path} - error") + end end end end diff --git a/spec/lib/banzai/filter/html_entity_filter_spec.rb b/spec/lib/banzai/filter/html_entity_filter_spec.rb index 91e18d876d5..1d98fc0d5db 100644 --- a/spec/lib/banzai/filter/html_entity_filter_spec.rb +++ b/spec/lib/banzai/filter/html_entity_filter_spec.rb @@ -3,17 +3,12 @@ require 'spec_helper' describe Banzai::Filter::HtmlEntityFilter do include FilterSpecHelper - let(:unescaped) { 'foo <strike attr="foo">&&&</strike>' } - let(:escaped) { 'foo <strike attr="foo">&&&</strike>' } + let(:unescaped) { 'foo <strike attr="foo">&&&</strike>' } + let(:escaped) { 'foo <strike attr="foo">&&amp;&</strike>' } it 'converts common entities to their HTML-escaped equivalents' do output = filter(unescaped) expect(output).to eq(escaped) end - - it 'does not double-escape' do - escaped = ERB::Util.html_escape("Merge branch 'blabla' into 'master'") - expect(filter(escaped)).to eq(escaped) - end end diff --git a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb index 9f2efa05a01..ef52c572898 100644 --- a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb +++ b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb @@ -3,35 +3,86 @@ require 'spec_helper' describe Banzai::Filter::SyntaxHighlightFilter do include FilterSpecHelper + shared_examples "XSS prevention" do |lang| + it "escapes HTML tags" do + # This is how a script tag inside a code block is presented to this filter + # after Markdown rendering. + result = filter(%{<pre lang="#{lang}"><code><script>alert(1)</script></code></pre>}) + + expect(result.to_html).not_to include("<script>alert(1)</script>") + expect(result.to_html).to include("alert(1)") + end + end + context "when no language is specified" do it "highlights as plaintext" do result = filter('<pre><code>def fun end</code></pre>') + expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">def fun end</span></code></pre>') end + + include_examples "XSS prevention", "" end context "when a valid language is specified" do it "highlights as that language" do result = filter('<pre><code lang="ruby">def fun end</code></pre>') + expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight ruby" lang="ruby" v-pre="true"><code><span id="LC1" class="line" lang="ruby"><span class="k">def</span> <span class="nf">fun</span> <span class="k">end</span></span></code></pre>') end + + include_examples "XSS prevention", "ruby" end context "when an invalid language is specified" do it "highlights as plaintext" do result = filter('<pre><code lang="gnuplot">This is a test</code></pre>') + expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">This is a test</span></code></pre>') end + + include_examples "XSS prevention", "gnuplot" end - context "when Rouge formatting fails" do + context "languages that should be passed through" do + %w(math mermaid plantuml).each do |lang| + context "when #{lang} is specified" do + it "highlights as plaintext but with the correct language attribute and class" do + result = filter(%{<pre><code lang="#{lang}">This is a test</code></pre>}) + + expect(result.to_html).to eq(%{<pre class="code highlight js-syntax-highlight #{lang}" lang="#{lang}" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre>}) + end + + include_examples "XSS prevention", lang + end + end + end + + context "when Rouge lexing fails" do before do - allow_any_instance_of(Rouge::Formatter).to receive(:format).and_raise(StandardError) + allow_any_instance_of(Rouge::Lexers::Ruby).to receive(:stream_tokens).and_raise(StandardError) end it "highlights as plaintext" do result = filter('<pre><code lang="ruby">This is a test</code></pre>') - expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight" lang="" v-pre="true"><code>This is a test</code></pre>') + + expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight" lang="" v-pre="true"><code><span id="LC1" class="line" lang="">This is a test</span></code></pre>') + end + + include_examples "XSS prevention", "ruby" + end + + context "when Rouge lexing fails after a retry" do + before do + allow_any_instance_of(Rouge::Lexers::PlainText).to receive(:stream_tokens).and_raise(StandardError) + end + + it "does not add highlighting classes" do + result = filter('<pre><code>This is a test</code></pre>') + + expect(result.to_html).to eq('<pre><code>This is a test</code></pre>') end + + include_examples "XSS prevention", "ruby" end end diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb index f668f78c2b8..2a0e19ae796 100644 --- a/spec/lib/gitlab/asciidoc_spec.rb +++ b/spec/lib/gitlab/asciidoc_spec.rb @@ -95,6 +95,14 @@ module Gitlab expect(render(input, context)).to include('<p><code data-math-style="inline" class="code math js-render-math">2+2</code> is 4</p>') end end + + context 'outfilesuffix' do + it 'defaults to adoc' do + output = render("Inter-document reference <<README.adoc#>>", context) + + expect(output).to include("a href=\"README.adoc\"") + end + end end def render(*args) diff --git a/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb b/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb index c8df6dd2118..007e93c1db6 100644 --- a/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb +++ b/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb @@ -15,10 +15,6 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :m .to receive(:commits_count=).and_return(nil) end - after do - [Project, MergeRequest, MergeRequestDiff].each(&:reset_column_information) - end - def diffs_to_hashes(diffs) diffs.as_json(only: Gitlab::Git::Diff::SERIALIZE_KEYS).map(&:with_indifferent_access) end diff --git a/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb b/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb index 2b69e718e08..e99257e3481 100644 --- a/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb +++ b/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb @@ -7,10 +7,6 @@ describe Gitlab::BackgroundMigration::PopulateMergeRequestMetricsWithEventsData, .to receive(:commits_count=).and_return(nil) end - after do - [Project, MergeRequest, MergeRequestDiff].each(&:reset_column_information) - end - describe '#perform' do let(:mr_with_event) { create(:merge_request) } let!(:merged_event) { create(:event, :merged, target: mr_with_event) } diff --git a/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb b/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb index be45c00dfe6..c8fa252439a 100644 --- a/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb +++ b/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb @@ -1,6 +1,11 @@ require 'spec_helper' -describe Gitlab::BackgroundMigration::PopulateUntrackedUploads, :sidekiq do +# This migration is using UploadService, which sets uploads.secret that is only +# added to the DB schema in 20180129193323. Since the test isn't isolated, we +# just use the latest schema when testing this migration. +# Ideally, the test should not use factories nor UploadService, and rely on the +# `table` helper instead. +describe Gitlab::BackgroundMigration::PopulateUntrackedUploads, :sidekiq, :migration, schema: 20180129193323 do include TrackUntrackedUploadsHelpers subject { described_class.new } @@ -9,22 +14,16 @@ describe Gitlab::BackgroundMigration::PopulateUntrackedUploads, :sidekiq do let!(:uploads) { described_class::Upload } before do - DatabaseCleaner.clean - drop_temp_table_if_exists ensure_temporary_tracking_table_exists uploads.delete_all end - after(:all) do - drop_temp_table_if_exists - end - context 'with untracked files and tracked files in untracked_files_for_uploads' do let!(:appearance) { create_or_update_appearance(logo: uploaded_file, header_logo: uploaded_file) } let!(:user1) { create(:user, :with_avatar) } let!(:user2) { create(:user, :with_avatar) } - let!(:project1) { create(:project, :with_avatar) } - let!(:project2) { create(:project, :with_avatar) } + let!(:project1) { create(:project, :legacy_storage, :with_avatar) } + let!(:project2) { create(:project, :legacy_storage, :with_avatar) } before do UploadService.new(project1, uploaded_file, FileUploader).execute # Markdown upload @@ -48,7 +47,7 @@ describe Gitlab::BackgroundMigration::PopulateUntrackedUploads, :sidekiq do it 'adds untracked files to the uploads table' do expect do - subject.perform(1, untracked_files_for_uploads.last.id) + subject.perform(1, untracked_files_for_uploads.reorder(:id).last.id) end.to change { uploads.count }.from(4).to(8) expect(user2.uploads.count).to eq(1) @@ -117,9 +116,9 @@ describe Gitlab::BackgroundMigration::PopulateUntrackedUploads, :sidekiq do end it 'drops the temporary tracking table after processing the batch, if there are no untracked rows left' do - subject.perform(1, untracked_files_for_uploads.last.id) + expect(subject).to receive(:drop_temp_table_if_finished) - expect(ActiveRecord::Base.connection.table_exists?(:untracked_files_for_uploads)).to be_falsey + subject.perform(1, untracked_files_for_uploads.last.id) end it 'does not block a whole batch because of one bad path' do @@ -213,13 +212,13 @@ describe Gitlab::BackgroundMigration::PopulateUntrackedUploads, :sidekiq do end context 'for a project avatar file path' do - let(:model) { create(:project, :with_avatar) } + let(:model) { create(:project, :legacy_storage, :with_avatar) } it_behaves_like 'non_markdown_file' end context 'for a project Markdown attachment (notes, issues, MR descriptions) file path' do - let(:model) { create(:project) } + let(:model) { create(:project, :legacy_storage) } before do # Upload the file @@ -255,10 +254,6 @@ describe Gitlab::BackgroundMigration::PopulateUntrackedUploads::UntrackedFile do ensure_temporary_tracking_table_exists end - after(:all) do - drop_temp_table_if_exists - end - describe '#upload_path' do def assert_upload_path(file_path, expected_upload_path) untracked_file = create_untracked_file(file_path) @@ -304,7 +299,7 @@ describe Gitlab::BackgroundMigration::PopulateUntrackedUploads::UntrackedFile do context 'for a project Markdown attachment (notes, issues, MR descriptions) file path' do it 'returns the file path relative to the project directory in uploads' do - project = create(:project) + project = create(:project, :legacy_storage) random_hex = SecureRandom.hex assert_upload_path("/#{project.full_path}/#{random_hex}/Some file.jpg", "#{random_hex}/Some file.jpg") @@ -357,7 +352,7 @@ describe Gitlab::BackgroundMigration::PopulateUntrackedUploads::UntrackedFile do context 'for a project Markdown attachment (notes, issues, MR descriptions) file path' do it 'returns FileUploader as a string' do - project = create(:project) + project = create(:project, :legacy_storage) assert_uploader("/#{project.full_path}/#{SecureRandom.hex}/Some file.jpg", 'FileUploader') end @@ -409,7 +404,7 @@ describe Gitlab::BackgroundMigration::PopulateUntrackedUploads::UntrackedFile do context 'for a project Markdown attachment (notes, issues, MR descriptions) file path' do it 'returns Project as a string' do - project = create(:project) + project = create(:project, :legacy_storage) assert_model_type("/#{project.full_path}/#{SecureRandom.hex}/Some file.jpg", 'Project') end @@ -461,7 +456,7 @@ describe Gitlab::BackgroundMigration::PopulateUntrackedUploads::UntrackedFile do context 'for a project Markdown attachment (notes, issues, MR descriptions) file path' do it 'returns the ID as a string' do - project = create(:project) + project = create(:project, :legacy_storage) assert_model_id("/#{project.full_path}/#{SecureRandom.hex}/Some file.jpg", project.id) end @@ -483,7 +478,7 @@ describe Gitlab::BackgroundMigration::PopulateUntrackedUploads::UntrackedFile do end context 'for a project avatar file path' do - let(:project) { create(:project, avatar: uploaded_file) } + let(:project) { create(:project, :legacy_storage, avatar: uploaded_file) } let(:untracked_file) { described_class.create!(path: project.uploads.first.path) } it 'returns the file size' do @@ -496,7 +491,7 @@ describe Gitlab::BackgroundMigration::PopulateUntrackedUploads::UntrackedFile do end context 'for a project Markdown attachment (notes, issues, MR descriptions) file path' do - let(:project) { create(:project) } + let(:project) { create(:project, :legacy_storage) } let(:untracked_file) { create_untracked_file("/#{project.full_path}/#{project.uploads.first.path}") } before do diff --git a/spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb b/spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb index 370c2490b97..ca77e64ae40 100644 --- a/spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb +++ b/spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb @@ -1,21 +1,11 @@ require 'spec_helper' -describe Gitlab::BackgroundMigration::PrepareUntrackedUploads, :sidekiq do +describe Gitlab::BackgroundMigration::PrepareUntrackedUploads, :sidekiq, :migration, schema: 20180129193323 do include TrackUntrackedUploadsHelpers include MigrationsHelpers let!(:untracked_files_for_uploads) { described_class::UntrackedFile } - before do - DatabaseCleaner.clean - - drop_temp_table_if_exists - end - - after do - drop_temp_table_if_exists - end - around do |example| # Especially important so the follow-up migration does not get run Sidekiq::Testing.fake! do @@ -23,61 +13,11 @@ describe Gitlab::BackgroundMigration::PrepareUntrackedUploads, :sidekiq do end end - # E.g. The installation is in use at the time of migration, and someone has - # just uploaded a file - shared_examples 'does not add files in /uploads/tmp' do - let(:tmp_file) { Rails.root.join(described_class::ABSOLUTE_UPLOAD_DIR, 'tmp', 'some_file.jpg') } - - before do - FileUtils.mkdir(File.dirname(tmp_file)) - FileUtils.touch(tmp_file) - end - - after do - FileUtils.rm(tmp_file) - end - - it 'does not add files from /uploads/tmp' do - described_class.new.perform - - expect(untracked_files_for_uploads.count).to eq(5) - end - end - - it 'ensures the untracked_files_for_uploads table exists' do - expect do - described_class.new.perform - end.to change { ActiveRecord::Base.connection.table_exists?(:untracked_files_for_uploads) }.from(false).to(true) - end - - it 'has a path field long enough for really long paths' do - described_class.new.perform - - component = 'a' * 255 - - long_path = [ - 'uploads', - component, # project.full_path - component # filename - ].flatten.join('/') - - record = untracked_files_for_uploads.create!(path: long_path) - expect(record.reload.path.size).to eq(519) - end - - context "test bulk insert with ON CONFLICT DO NOTHING or IGNORE" do - around do |example| - # If this is CI, we use Postgres 9.2 so this whole context should be - # skipped since we're unable to use ON CONFLICT DO NOTHING or IGNORE. - if described_class.new.send(:can_bulk_insert_and_ignore_duplicates?) - example.run - end - end - + shared_examples 'prepares the untracked_files_for_uploads table' do context 'when files were uploaded before and after hashed storage was enabled' do let!(:appearance) { create_or_update_appearance(logo: uploaded_file, header_logo: uploaded_file) } let!(:user) { create(:user, :with_avatar) } - let!(:project1) { create(:project, :with_avatar) } + let!(:project1) { create(:project, :with_avatar, :legacy_storage) } let(:project2) { create(:project) } # instantiate after enabling hashed_storage before do @@ -90,6 +30,21 @@ describe Gitlab::BackgroundMigration::PrepareUntrackedUploads, :sidekiq do UploadService.new(project2, uploaded_file, FileUploader).execute end + it 'has a path field long enough for really long paths' do + described_class.new.perform + + component = 'a' * 255 + + long_path = [ + 'uploads', + component, # project.full_path + component # filename + ].flatten.join('/') + + record = untracked_files_for_uploads.create!(path: long_path) + expect(record.reload.path.size).to eq(519) + end + it 'adds unhashed files to the untracked_files_for_uploads table' do described_class.new.perform @@ -113,7 +68,8 @@ describe Gitlab::BackgroundMigration::PrepareUntrackedUploads, :sidekiq do it 'correctly schedules the follow-up background migration jobs' do described_class.new.perform - expect(described_class::FOLLOW_UP_MIGRATION).to be_scheduled_migration(1, 5) + ids = described_class::UntrackedFile.all.order(:id).pluck(:id) + expect(described_class::FOLLOW_UP_MIGRATION).to be_scheduled_migration(ids.first, ids.last) expect(BackgroundMigrationWorker.jobs.size).to eq(1) end @@ -130,91 +86,68 @@ describe Gitlab::BackgroundMigration::PrepareUntrackedUploads, :sidekiq do end end + # E.g. The installation is in use at the time of migration, and someone has + # just uploaded a file context 'when there are files in /uploads/tmp' do - it_behaves_like 'does not add files in /uploads/tmp' - end - end - end - - context 'test bulk insert without ON CONFLICT DO NOTHING or IGNORE' do - before do - # If this is CI, we use Postgres 9.2 so this stub has no effect. - # - # If this is being run on Postgres 9.5+ or MySQL, then this stub allows us - # to test the bulk insert functionality without ON CONFLICT DO NOTHING or - # IGNORE. - allow_any_instance_of(described_class).to receive(:postgresql_pre_9_5?).and_return(true) - end - - context 'when files were uploaded before and after hashed storage was enabled' do - let!(:appearance) { create_or_update_appearance(logo: uploaded_file, header_logo: uploaded_file) } - let!(:user) { create(:user, :with_avatar) } - let!(:project1) { create(:project, :with_avatar) } - let(:project2) { create(:project) } # instantiate after enabling hashed_storage - - before do - # Markdown upload before enabling hashed_storage - UploadService.new(project1, uploaded_file, FileUploader).execute + let(:tmp_file) { Rails.root.join(described_class::ABSOLUTE_UPLOAD_DIR, 'tmp', 'some_file.jpg') } - stub_application_setting(hashed_storage_enabled: true) - - # Markdown upload after enabling hashed_storage - UploadService.new(project2, uploaded_file, FileUploader).execute - end - - it 'adds unhashed files to the untracked_files_for_uploads table' do - described_class.new.perform - - expect(untracked_files_for_uploads.count).to eq(5) - end - - it 'adds files with paths relative to CarrierWave.root' do - described_class.new.perform - untracked_files_for_uploads.all.each do |file| - expect(file.path.start_with?('uploads/')).to be_truthy + before do + FileUtils.mkdir(File.dirname(tmp_file)) + FileUtils.touch(tmp_file) end - end - - it 'does not add hashed files to the untracked_files_for_uploads table' do - described_class.new.perform - hashed_file_path = project2.uploads.where(uploader: 'FileUploader').first.path - expect(untracked_files_for_uploads.where("path like '%#{hashed_file_path}%'").exists?).to be_falsey - end + after do + FileUtils.rm(tmp_file) + end - it 'correctly schedules the follow-up background migration jobs' do - described_class.new.perform + it 'does not add files from /uploads/tmp' do + described_class.new.perform - expect(described_class::FOLLOW_UP_MIGRATION).to be_scheduled_migration(1, 5) - expect(BackgroundMigrationWorker.jobs.size).to eq(1) + expect(untracked_files_for_uploads.count).to eq(5) + end end - # E.g. from a previous failed run of this background migration - context 'when there is existing data in untracked_files_for_uploads' do - before do - described_class.new.perform - end + context 'when the last batch size exactly matches the max batch size' do + it 'does not raise error' do + stub_const("#{described_class}::FIND_BATCH_SIZE", 5) - it 'does not error or produce duplicates of existing data' do expect do described_class.new.perform - end.not_to change { untracked_files_for_uploads.count }.from(5) + end.not_to raise_error + + expect(untracked_files_for_uploads.count).to eq(5) end end + end + end - context 'when there are files in /uploads/tmp' do - it_behaves_like 'does not add files in /uploads/tmp' - end + # If running on Postgres 9.2 (like on CI), this whole context is skipped + # since we're unable to use ON CONFLICT DO NOTHING or IGNORE. + context "test bulk insert with ON CONFLICT DO NOTHING or IGNORE", if: described_class.new.send(:can_bulk_insert_and_ignore_duplicates?) do + it_behaves_like 'prepares the untracked_files_for_uploads table' + end + + # If running on Postgres 9.2 (like on CI), the stubbed method has no effect. + # + # If running on Postgres 9.5+ or MySQL, then this context effectively tests + # the bulk insert functionality without ON CONFLICT DO NOTHING or IGNORE. + context 'test bulk insert without ON CONFLICT DO NOTHING or IGNORE' do + before do + allow_any_instance_of(described_class).to receive(:postgresql_pre_9_5?).and_return(true) end + + it_behaves_like 'prepares the untracked_files_for_uploads table' end # Very new or lightly-used installations that are running this migration # may not have an upload directory because they have no uploads. context 'when no files were ever uploaded' do - it 'does not add to the untracked_files_for_uploads table (and does not raise error)' do - described_class.new.perform + it 'deletes the `untracked_files_for_uploads` table (and does not raise error)' do + background_migration = described_class.new + + expect(background_migration).to receive(:drop_temp_table) - expect(untracked_files_for_uploads.count).to eq(0) + background_migration.perform end end end diff --git a/spec/lib/gitlab/bare_repository_import/importer_spec.rb b/spec/lib/gitlab/bare_repository_import/importer_spec.rb index f302e412a6e..eb4b9d8b12f 100644 --- a/spec/lib/gitlab/bare_repository_import/importer_spec.rb +++ b/spec/lib/gitlab/bare_repository_import/importer_spec.rb @@ -8,11 +8,15 @@ describe Gitlab::BareRepositoryImport::Importer, repository: true do subject(:importer) { described_class.new(admin, bare_repository) } before do + @rainbow = Rainbow.enabled + Rainbow.enabled = false + allow(described_class).to receive(:log) end after do FileUtils.rm_rf(base_dir) + Rainbow.enabled = @rainbow end shared_examples 'importing a repository' do @@ -148,7 +152,7 @@ describe Gitlab::BareRepositoryImport::Importer, repository: true do # This is a quick way to get a valid repository instead of copying an # existing one. Since it's not persisted, the importer will try to # create the project. - project = build(:project, :repository) + project = build(:project, :legacy_storage, :repository) original_commit_count = project.repository.commit_count bare_repo = Gitlab::BareRepositoryImport::Repository.new(project.repository_storage_path, project.repository.path) diff --git a/spec/lib/gitlab/checks/change_access_spec.rb b/spec/lib/gitlab/checks/change_access_spec.rb index c2bca816aae..475b5c5cfb2 100644 --- a/spec/lib/gitlab/checks/change_access_spec.rb +++ b/spec/lib/gitlab/checks/change_access_spec.rb @@ -177,5 +177,44 @@ describe Gitlab::Checks::ChangeAccess do expect { subject.exec }.not_to raise_error end end + + context 'LFS file lock check' do + let(:owner) { create(:user) } + let!(:lock) { create(:lfs_file_lock, user: owner, project: project, path: 'README') } + + before do + allow(project.repository).to receive(:new_commits).and_return( + project.repository.commits_between('be93687618e4b132087f430a4d8fc3a609c9b77c', '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51') + ) + end + + context 'with LFS not enabled' do + it 'skips the validation' do + expect_any_instance_of(described_class).not_to receive(:lfs_file_locks_validation) + + subject.exec + end + end + + context 'with LFS enabled' do + before do + allow(project).to receive(:lfs_enabled?).and_return(true) + end + + context 'when change is sent by a different user' do + it 'raises an error if the user is not allowed to update the file' do + expect { subject.exec }.to raise_error(Gitlab::GitAccess::UnauthorizedError, "The path 'README' is locked in Git LFS by #{lock.user.name}") + end + end + + context 'when change is sent by the author od the lock' do + let(:user) { owner } + + it "doesn't raise any error" do + expect { subject.exec }.not_to raise_error + end + end + end + end end end diff --git a/spec/lib/gitlab/ci/config/loader_spec.rb b/spec/lib/gitlab/ci/config/loader_spec.rb index 2d44b1f60f1..590fc8904c1 100644 --- a/spec/lib/gitlab/ci/config/loader_spec.rb +++ b/spec/lib/gitlab/ci/config/loader_spec.rb @@ -38,6 +38,16 @@ describe Gitlab::Ci::Config::Loader do end end + context 'when there is an unknown alias' do + let(:yml) { 'steps: *bad_alias' } + + describe '#initialize' do + it 'raises FormatError' do + expect { loader }.to raise_error(Gitlab::Ci::Config::Loader::FormatError, 'Unknown alias: bad_alias') + end + end + end + context 'when yaml config is empty' do let(:yml) { '' } diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index 98880fe9f28..f83f932e61e 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -1394,11 +1394,15 @@ EOT describe "Error handling" do it "fails to parse YAML" do - expect {Gitlab::Ci::YamlProcessor.new("invalid: yaml: test")}.to raise_error(Psych::SyntaxError) + expect do + Gitlab::Ci::YamlProcessor.new("invalid: yaml: test") + end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError) end it "indicates that object is invalid" do - expect {Gitlab::Ci::YamlProcessor.new("invalid_yaml")}.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError) + expect do + Gitlab::Ci::YamlProcessor.new("invalid_yaml") + end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError) end it "returns errors if tags parameter is invalid" do @@ -1688,37 +1692,36 @@ EOT end describe "#validation_message" do + subject { Gitlab::Ci::YamlProcessor.validation_message(content) } + context "when the YAML could not be parsed" do - it "returns an error about invalid configutaion" do - content = YAML.dump("invalid: yaml: test") + let(:content) { YAML.dump("invalid: yaml: test") } - expect(Gitlab::Ci::YamlProcessor.validation_message(content)) - .to eq "Invalid configuration format" - end + it { is_expected.to eq "Invalid configuration format" } end context "when the tags parameter is invalid" do - it "returns an error about invalid tags" do - content = YAML.dump({ rspec: { script: "test", tags: "mysql" } }) + let(:content) { YAML.dump({ rspec: { script: "test", tags: "mysql" } }) } - expect(Gitlab::Ci::YamlProcessor.validation_message(content)) - .to eq "jobs:rspec tags should be an array of strings" - end + it { is_expected.to eq "jobs:rspec tags should be an array of strings" } end context "when YAML content is empty" do - it "returns an error about missing content" do - expect(Gitlab::Ci::YamlProcessor.validation_message('')) - .to eq "Please provide content of .gitlab-ci.yml" - end + let(:content) { '' } + + it { is_expected.to eq "Please provide content of .gitlab-ci.yml" } + end + + context 'when the YAML contains an unknown alias' do + let(:content) { 'steps: *bad_alias' } + + it { is_expected.to eq "Unknown alias: bad_alias" } end context "when the YAML is valid" do - it "does not return any errors" do - content = File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) + let(:content) { File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) } - expect(Gitlab::Ci::YamlProcessor.validation_message(content)).to be_nil - end + it { is_expected.to be_nil } end end diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb index f31475dbd71..b411aaa19da 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb @@ -94,7 +94,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, : describe '#move_repositories' do let(:namespace) { create(:group, name: 'hello-group') } it 'moves a project for a namespace' do - create(:project, :repository, namespace: namespace, path: 'hello-project') + create(:project, :repository, :legacy_storage, namespace: namespace, path: 'hello-project') expected_path = File.join(TestEnv.repos_path, 'bye-group', 'hello-project.git') subject.move_repositories(namespace, 'hello-group', 'bye-group') @@ -104,7 +104,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, : it 'moves a namespace in a subdirectory correctly' do child_namespace = create(:group, name: 'sub-group', parent: namespace) - create(:project, :repository, namespace: child_namespace, path: 'hello-project') + create(:project, :repository, :legacy_storage, namespace: child_namespace, path: 'hello-project') expected_path = File.join(TestEnv.repos_path, 'hello-group', 'renamed-sub-group', 'hello-project.git') @@ -115,7 +115,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, : it 'moves a parent namespace with subdirectories' do child_namespace = create(:group, name: 'sub-group', parent: namespace) - create(:project, :repository, namespace: child_namespace, path: 'hello-project') + create(:project, :repository, :legacy_storage, namespace: child_namespace, path: 'hello-project') expected_path = File.join(TestEnv.repos_path, 'renamed-group', 'sub-group', 'hello-project.git') subject.move_repositories(child_namespace, 'hello-group', 'renamed-group') @@ -166,7 +166,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, : describe '#rename_namespace_dependencies' do it "moves the the repository for a project in the namespace" do - create(:project, :repository, namespace: namespace, path: "the-path-project") + create(:project, :repository, :legacy_storage, namespace: namespace, path: "the-path-project") expected_repo = File.join(TestEnv.repos_path, "the-path0", "the-path-project.git") subject.rename_namespace_dependencies(namespace, 'the-path', 'the-path0') @@ -187,7 +187,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, : end it 'invalidates the markdown cache of related projects' do - project = create(:project, namespace: namespace, path: "the-path-project") + project = create(:project, :legacy_storage, namespace: namespace, path: "the-path-project") expect(subject).to receive(:remove_cached_html_for_projects).with([project.id]) @@ -243,7 +243,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, : describe '#revert_renames', :redis do it 'renames the routes back to the previous values' do - project = create(:project, :repository, path: 'a-project', namespace: namespace) + project = create(:project, :legacy_storage, :repository, path: 'a-project', namespace: namespace) subject.rename_namespace(namespace) expect(subject).to receive(:perform_rename) @@ -261,7 +261,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, : end it 'moves the repositories back to their original place' do - project = create(:project, :repository, path: 'a-project', namespace: namespace) + project = create(:project, :repository, :legacy_storage, path: 'a-project', namespace: namespace) project.create_repository subject.rename_namespace(namespace) diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb index 0958144643b..b4896d69077 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb @@ -5,6 +5,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :de let(:subject) { described_class.new(['the-path'], migration) } let(:project) do create(:project, + :legacy_storage, path: 'the-path', namespace: create(:namespace, path: 'known-parent' )) end @@ -17,7 +18,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :de describe '#projects_for_paths' do it 'searches using nested paths' do namespace = create(:namespace, path: 'hello') - project = create(:project, path: 'THE-path', namespace: namespace) + project = create(:project, :legacy_storage, path: 'THE-path', namespace: namespace) result_ids = described_class.new(['Hello/the-path'], migration) .projects_for_paths.map(&:id) @@ -26,8 +27,8 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :de end it 'includes the correct projects' do - project = create(:project, path: 'THE-path') - _other_project = create(:project) + project = create(:project, :legacy_storage, path: 'THE-path') + _other_project = create(:project, :legacy_storage) result_ids = subject.projects_for_paths.map(&:id) @@ -36,7 +37,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :de end describe '#rename_projects' do - let!(:projects) { create_list(:project, 2, path: 'the-path') } + let!(:projects) { create_list(:project, 2, :legacy_storage, path: 'the-path') } it 'renames each project' do expect(subject).to receive(:rename_project).twice @@ -120,7 +121,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :de describe '#move_repository' do let(:known_parent) { create(:namespace, path: 'known-parent') } - let(:project) { create(:project, :repository, path: 'the-path', namespace: known_parent) } + let(:project) { create(:project, :repository, :legacy_storage, path: 'the-path', namespace: known_parent) } it 'moves the repository for a project' do expected_path = File.join(TestEnv.repos_path, 'known-parent', 'new-repo.git') diff --git a/spec/lib/gitlab/email/attachment_uploader_spec.rb b/spec/lib/gitlab/email/attachment_uploader_spec.rb index f61dbc67ad1..45c690842bc 100644 --- a/spec/lib/gitlab/email/attachment_uploader_spec.rb +++ b/spec/lib/gitlab/email/attachment_uploader_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" describe Gitlab::Email::AttachmentUploader do describe "#execute" do - let(:project) { build(:project) } + let(:project) { create(:project) } let(:message_raw) { fixture_file("emails/attachment.eml") } let(:message) { Mail::Message.new(message_raw) } diff --git a/spec/lib/gitlab/encoding_helper_spec.rb b/spec/lib/gitlab/encoding_helper_spec.rb index 4e9367323cb..83d431a7458 100644 --- a/spec/lib/gitlab/encoding_helper_spec.rb +++ b/spec/lib/gitlab/encoding_helper_spec.rb @@ -24,6 +24,11 @@ describe Gitlab::EncodingHelper do 'removes invalid bytes from ASCII-8bit encoded multibyte string. This can occur when a git diff match line truncates in the middle of a multibyte character. This occurs after the second word in this example. The test string is as short as we can get while still triggering the error condition when not looking at `detect[:confidence]`.', "mu ns\xC3\n Lorem ipsum dolor sit amet, consectetur adipisicing ut\xC3\xA0y\xC3\xB9abcd\xC3\xB9efg kia elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non p\n {: .normal_pn}\n \n-Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in\n# *Lorem ipsum\xC3\xB9l\xC3\xB9l\xC3\xA0 dolor\xC3\xB9k\xC3\xB9 sit\xC3\xA8b\xC3\xA8 N\xC3\xA8 amet b\xC3\xA0d\xC3\xAC*\n+# *consectetur\xC3\xB9l\xC3\xB9l\xC3\xA0 adipisicing\xC3\xB9k\xC3\xB9 elit\xC3\xA8b\xC3\xA8 N\xC3\xA8 sed do\xC3\xA0d\xC3\xAC*{: .italic .smcaps}\n \n \xEF\x9B\xA1 eiusmod tempor incididunt, ut\xC3\xAAn\xC3\xB9 labore et dolore. Tw\xC4\x83nj\xC3\xAC magna aliqua. Ut enim ad minim veniam\n {: .normal}\n@@ -9,5 +9,5 @@ quis nostrud\xC3\xAAt\xC3\xB9 exercitiation ullamco laboris m\xC3\xB9s\xC3\xB9k\xC3\xB9abc\xC3\xB9 nisi ".force_encoding('ASCII-8BIT'), "mu ns\n Lorem ipsum dolor sit amet, consectetur adipisicing ut\xC3\xA0y\xC3\xB9abcd\xC3\xB9efg kia elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non p\n {: .normal_pn}\n \n-Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in\n# *Lorem ipsum\xC3\xB9l\xC3\xB9l\xC3\xA0 dolor\xC3\xB9k\xC3\xB9 sit\xC3\xA8b\xC3\xA8 N\xC3\xA8 amet b\xC3\xA0d\xC3\xAC*\n+# *consectetur\xC3\xB9l\xC3\xB9l\xC3\xA0 adipisicing\xC3\xB9k\xC3\xB9 elit\xC3\xA8b\xC3\xA8 N\xC3\xA8 sed do\xC3\xA0d\xC3\xAC*{: .italic .smcaps}\n \n \xEF\x9B\xA1 eiusmod tempor incididunt, ut\xC3\xAAn\xC3\xB9 labore et dolore. Tw\xC4\x83nj\xC3\xAC magna aliqua. Ut enim ad minim veniam\n {: .normal}\n@@ -9,5 +9,5 @@ quis nostrud\xC3\xAAt\xC3\xB9 exercitiation ullamco laboris m\xC3\xB9s\xC3\xB9k\xC3\xB9abc\xC3\xB9 nisi " + ], + [ + 'string with detected encoding that is not supported in Ruby', + "\xFFe,i\xFF,\xB8oi,'\xB8,\xFF,-", + "--broken encoding: IBM420_ltr" ] ].each do |description, test_string, xpect| it description do diff --git a/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb index 326ed2f2ecf..13df8531b63 100644 --- a/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb +++ b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb @@ -39,8 +39,8 @@ describe Gitlab::Gfm::UploadsRewriter do it 'copies files' do expect(new_files).to all(exist) expect(old_paths).not_to match_array new_paths - expect(old_paths).to all(include(old_project.full_path)) - expect(new_paths).to all(include(new_project.full_path)) + expect(old_paths).to all(include(old_project.disk_path)) + expect(new_paths).to all(include(new_project.disk_path)) end it 'does not remove old files' do diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb index 8ac960133c5..59e9e1cc94c 100644 --- a/spec/lib/gitlab/git/blob_spec.rb +++ b/spec/lib/gitlab/git/blob_spec.rb @@ -178,67 +178,77 @@ describe Gitlab::Git::Blob, seed_helper: true do end describe '.batch' do - let(:blob_references) do - [ - [SeedRepo::Commit::ID, "files/ruby/popen.rb"], - [SeedRepo::Commit::ID, 'six'] - ] - end + shared_examples 'loading blobs in batch' do + let(:blob_references) do + [ + [SeedRepo::Commit::ID, "files/ruby/popen.rb"], + [SeedRepo::Commit::ID, 'six'] + ] + end - subject { described_class.batch(repository, blob_references) } + subject { described_class.batch(repository, blob_references) } - it { expect(subject.size).to eq(blob_references.size) } + it { expect(subject.size).to eq(blob_references.size) } - context 'first blob' do - let(:blob) { subject[0] } + context 'first blob' do + let(:blob) { subject[0] } - it { expect(blob.id).to eq(SeedRepo::RubyBlob::ID) } - it { expect(blob.name).to eq(SeedRepo::RubyBlob::NAME) } - it { expect(blob.path).to eq("files/ruby/popen.rb") } - it { expect(blob.commit_id).to eq(SeedRepo::Commit::ID) } - it { expect(blob.data[0..10]).to eq(SeedRepo::RubyBlob::CONTENT[0..10]) } - it { expect(blob.size).to eq(669) } - it { expect(blob.mode).to eq("100644") } - end + it { expect(blob.id).to eq(SeedRepo::RubyBlob::ID) } + it { expect(blob.name).to eq(SeedRepo::RubyBlob::NAME) } + it { expect(blob.path).to eq("files/ruby/popen.rb") } + it { expect(blob.commit_id).to eq(SeedRepo::Commit::ID) } + it { expect(blob.data[0..10]).to eq(SeedRepo::RubyBlob::CONTENT[0..10]) } + it { expect(blob.size).to eq(669) } + it { expect(blob.mode).to eq("100644") } + end - context 'second blob' do - let(:blob) { subject[1] } + context 'second blob' do + let(:blob) { subject[1] } - it { expect(blob.id).to eq('409f37c4f05865e4fb208c771485f211a22c4c2d') } - it { expect(blob.data).to eq('') } - it 'does not mark the blob as binary' do - expect(blob).not_to be_binary + it { expect(blob.id).to eq('409f37c4f05865e4fb208c771485f211a22c4c2d') } + it { expect(blob.data).to eq('') } + it 'does not mark the blob as binary' do + expect(blob).not_to be_binary + end end - end - context 'limiting' do - subject { described_class.batch(repository, blob_references, blob_size_limit: blob_size_limit) } + context 'limiting' do + subject { described_class.batch(repository, blob_references, blob_size_limit: blob_size_limit) } - context 'positive' do - let(:blob_size_limit) { 10 } + context 'positive' do + let(:blob_size_limit) { 10 } - it { expect(subject.first.data.size).to eq(10) } - end + it { expect(subject.first.data.size).to eq(10) } + end - context 'zero' do - let(:blob_size_limit) { 0 } + context 'zero' do + let(:blob_size_limit) { 0 } - it 'only loads the metadata' do - expect(subject.first.size).not_to be(0) - expect(subject.first.data).to eq('') + it 'only loads the metadata' do + expect(subject.first.size).not_to be(0) + expect(subject.first.data).to eq('') + end end - end - context 'negative' do - let(:blob_size_limit) { -1 } + context 'negative' do + let(:blob_size_limit) { -1 } - it 'ignores MAX_DATA_DISPLAY_SIZE' do - stub_const('Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE', 100) + it 'ignores MAX_DATA_DISPLAY_SIZE' do + stub_const('Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE', 100) - expect(subject.first.data.size).to eq(669) + expect(subject.first.data.size).to eq(669) + end end end end + + context 'when Gitaly list_blobs_by_sha_path feature is enabled' do + it_behaves_like 'loading blobs in batch' + end + + context 'when Gitaly list_blobs_by_sha_path feature is disabled', :disable_gitaly do + it_behaves_like 'loading blobs in batch' + end end describe '.batch_lfs_pointers' do diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index ec1c7a96f92..edcf8889c27 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -600,12 +600,16 @@ describe Gitlab::Git::Repository, seed_helper: true do end describe "#refs_hash" do - let(:refs) { repository.refs_hash } + subject { repository.refs_hash } it "should have as many entries as branches and tags" do expected_refs = SeedRepo::Repo::BRANCHES + SeedRepo::Repo::TAGS # We flatten in case a commit is pointed at by more than one branch and/or tag - expect(refs.values.flatten.size).to eq(expected_refs.size) + expect(subject.values.flatten.size).to eq(expected_refs.size) + end + + it 'has valid commit ids as keys' do + expect(subject.keys).to all( match(Commit::COMMIT_SHA_PATTERN) ) end end diff --git a/spec/lib/gitlab/git/wiki_spec.rb b/spec/lib/gitlab/git/wiki_spec.rb index bd8dbf07fa7..761f7732036 100644 --- a/spec/lib/gitlab/git/wiki_spec.rb +++ b/spec/lib/gitlab/git/wiki_spec.rb @@ -3,34 +3,38 @@ require 'spec_helper' describe Gitlab::Git::Wiki do let(:project) { create(:project) } let(:user) { project.owner } - let(:wiki) { ProjectWiki.new(project, user) } - let(:gollum_wiki) { wiki.wiki } + let(:project_wiki) { ProjectWiki.new(project, user) } + subject { project_wiki.wiki } # Remove skip_gitaly_mock flag when gitaly_find_page when - # https://gitlab.com/gitlab-org/gitaly/merge_requests/539 gets merged + # https://gitlab.com/gitlab-org/gitlab-ce/issues/42039 is solved describe '#page', :skip_gitaly_mock do - it 'returns the right page' do + before do create_page('page1', 'content') - create_page('foo/page1', 'content') - - expect(gollum_wiki.page(title: 'page1', dir: '').url_path).to eq 'page1' - expect(gollum_wiki.page(title: 'page1', dir: 'foo').url_path).to eq 'foo/page1' + create_page('foo/page1', 'content foo/page1') + end + after do destroy_page('page1') destroy_page('page1', 'foo') end + + it 'returns the right page' do + expect(subject.page(title: 'page1', dir: '').url_path).to eq 'page1' + expect(subject.page(title: 'page1', dir: 'foo').url_path).to eq 'foo/page1' + end end def create_page(name, content) - gollum_wiki.write_page(name, :markdown, content, commit_details) + subject.write_page(name, :markdown, content, commit_details(name)) end - def commit_details - Gitlab::Git::Wiki::CommitDetails.new(user.name, user.email, "test commit") + def commit_details(name) + Gitlab::Git::Wiki::CommitDetails.new(user.name, user.email, "created page #{name}") end def destroy_page(title, dir = '') - page = gollum_wiki.page(title: title, dir: dir) - wiki.delete_page(page, "test commit") + page = subject.page(title: title, dir: dir) + project_wiki.delete_page(page, "test commit") end end diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 3c3697e7aa9..19d3f55501e 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -18,8 +18,9 @@ describe Gitlab::GitAccess do redirected_path: redirected_path) end - let(:push_access_check) { access.check('git-receive-pack', '_any') } - let(:pull_access_check) { access.check('git-upload-pack', '_any') } + let(:changes) { '_any' } + let(:push_access_check) { access.check('git-receive-pack', changes) } + let(:pull_access_check) { access.check('git-upload-pack', changes) } describe '#check with single protocols allowed' do def disable_protocol(protocol) @@ -646,6 +647,20 @@ describe Gitlab::GitAccess do end end + describe 'check LFS integrity' do + let(:changes) { ['6f6d7e7ed 570e7b2ab refs/heads/master', '6f6d7e7ed 570e7b2ab refs/heads/feature'] } + + before do + project.add_developer(user) + end + + it 'checks LFS integrity only for first change' do + expect_any_instance_of(Gitlab::Checks::LfsIntegrity).to receive(:objects_missing?).exactly(1).times + + push_access_check + end + end + describe '#check_push_access!' do before do merge_into_protected_branch diff --git a/spec/lib/gitlab/gitaly_client/blobs_stitcher_spec.rb b/spec/lib/gitlab/gitaly_client/blobs_stitcher_spec.rb new file mode 100644 index 00000000000..9db710e759e --- /dev/null +++ b/spec/lib/gitlab/gitaly_client/blobs_stitcher_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe Gitlab::GitalyClient::BlobsStitcher do + describe 'enumeration' do + it 'combines segregated blob messages together' do + messages = [ + OpenStruct.new(oid: 'abcdef1', path: 'path/to/file', size: 1642, revision: 'f00ba7', mode: 0100644, data: "first-line\n"), + OpenStruct.new(oid: '', data: 'second-line'), + OpenStruct.new(oid: '', data: '', revision: 'f00ba7', path: 'path/to/non-existent/file'), + OpenStruct.new(oid: 'abcdef2', path: 'path/to/another-file', size: 2461, revision: 'f00ba8', mode: 0100644, data: "GIF87a\x90\x01".b) + ] + + blobs = described_class.new(messages).to_a + + expect(blobs.size).to be(2) + + expect(blobs[0].id).to eq('abcdef1') + expect(blobs[0].mode).to eq('100644') + expect(blobs[0].name).to eq('file') + expect(blobs[0].path).to eq('path/to/file') + expect(blobs[0].size).to eq(1642) + expect(blobs[0].commit_id).to eq('f00ba7') + expect(blobs[0].data).to eq("first-line\nsecond-line") + expect(blobs[0].binary?).to be false + + expect(blobs[1].id).to eq('abcdef2') + expect(blobs[1].mode).to eq('100644') + expect(blobs[1].name).to eq('another-file') + expect(blobs[1].path).to eq('path/to/another-file') + expect(blobs[1].size).to eq(2461) + expect(blobs[1].commit_id).to eq('f00ba8') + expect(blobs[1].data).to eq("GIF87a\x90\x01".b) + expect(blobs[1].binary?).to be true + end + end +end diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb index 3722a91c050..001c4d3e10a 100644 --- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb @@ -166,6 +166,32 @@ describe Gitlab::GitalyClient::CommitService do described_class.new(repository).find_commit(revision) end + + describe 'caching', :request_store do + let(:commit_dbl) { double(id: 'f01b' * 10) } + + context 'when passed revision is a branch name' do + it 'calls Gitaly' do + expect_any_instance_of(Gitaly::CommitService::Stub).to receive(:find_commit).twice.and_return(double(commit: commit_dbl)) + + commit = nil + 2.times { commit = described_class.new(repository).find_commit('master') } + + expect(commit).to eq(commit_dbl) + end + end + + context 'when passed revision is a commit ID' do + it 'returns a cached commit' do + expect_any_instance_of(Gitaly::CommitService::Stub).to receive(:find_commit).once.and_return(double(commit: commit_dbl)) + + commit = nil + 2.times { commit = described_class.new(repository).find_commit('f01b' * 10) } + + expect(commit).to eq(commit_dbl) + end + end + end end describe '#patch' do diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 0ecb50f7110..41a55027f4d 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -276,6 +276,7 @@ project: - fork_network_member - fork_network - custom_attributes +- lfs_file_locks award_emoji: - awardable - user @@ -290,3 +291,5 @@ push_event_payload: issue_assignees: - issue - assignee +lfs_file_locks: +- user diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 5a33fa3fd53..feaab6673cd 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -530,3 +530,9 @@ ProjectCustomAttribute: - project_id - key - value +LfsFileLock: +- id +- path +- user_id +- project_id +- created_at diff --git a/spec/lib/gitlab/import_export/uploads_restorer_spec.rb b/spec/lib/gitlab/import_export/uploads_restorer_spec.rb index a685521cbf0..8a3a244be21 100644 --- a/spec/lib/gitlab/import_export/uploads_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/uploads_restorer_spec.rb @@ -16,7 +16,7 @@ describe Gitlab::ImportExport::UploadsRestorer do end describe 'legacy storage' do - let(:project) { create(:project) } + let(:project) { create(:project, :legacy_storage) } subject(:restorer) { described_class.new(project: project, shared: shared) } @@ -34,7 +34,7 @@ describe Gitlab::ImportExport::UploadsRestorer do end describe 'hashed storage' do - let(:project) { create(:project, :hashed) } + let(:project) { create(:project) } subject(:restorer) { described_class.new(project: project, shared: shared) } diff --git a/spec/lib/gitlab/import_export/uploads_saver_spec.rb b/spec/lib/gitlab/import_export/uploads_saver_spec.rb index 959779523f4..177036c109b 100644 --- a/spec/lib/gitlab/import_export/uploads_saver_spec.rb +++ b/spec/lib/gitlab/import_export/uploads_saver_spec.rb @@ -15,7 +15,7 @@ describe Gitlab::ImportExport::UploadsSaver do end describe 'legacy storage' do - let(:project) { create(:project) } + let(:project) { create(:project, :legacy_storage) } subject(:saver) { described_class.new(shared: shared, project: project) } @@ -37,7 +37,7 @@ describe Gitlab::ImportExport::UploadsSaver do end describe 'hashed storage' do - let(:project) { create(:project, :hashed) } + let(:project) { create(:project) } subject(:saver) { described_class.new(shared: shared, project: project) } diff --git a/spec/lib/gitlab/import_export/wiki_restorer_spec.rb b/spec/lib/gitlab/import_export/wiki_restorer_spec.rb new file mode 100644 index 00000000000..81b654e9c5f --- /dev/null +++ b/spec/lib/gitlab/import_export/wiki_restorer_spec.rb @@ -0,0 +1,45 @@ +require 'spec_helper' + +describe Gitlab::ImportExport::WikiRestorer do + describe 'restore a wiki Git repo' do + let!(:project_with_wiki) { create(:project, :wiki_repo) } + let!(:project_without_wiki) { create(:project) } + let!(:project) { create(:project) } + let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } + let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) } + let(:bundler) { Gitlab::ImportExport::WikiRepoSaver.new(project: project_with_wiki, shared: shared) } + let(:bundle_path) { File.join(shared.export_path, Gitlab::ImportExport.project_bundle_filename) } + let(:restorer) do + described_class.new(path_to_bundle: bundle_path, + shared: shared, + project: project.wiki, + wiki_enabled: true) + end + + before do + allow(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) + + bundler.save + end + + after do + FileUtils.rm_rf(export_path) + Gitlab::Shell.new.remove_repository(project_with_wiki.wiki.repository_storage_path, project_with_wiki.wiki.disk_path) + Gitlab::Shell.new.remove_repository(project.wiki.repository_storage_path, project.wiki.disk_path) + end + + it 'restores the wiki repo successfully' do + expect(restorer.restore).to be true + end + + describe "no wiki in the bundle" do + let(:bundler) { Gitlab::ImportExport::WikiRepoSaver.new(project: project_without_wiki, shared: shared) } + + it 'creates an empty wiki' do + expect(restorer.restore).to be true + + expect(project.wiki_repository_exists?).to be true + end + end + end +end diff --git a/spec/lib/gitlab/ldap/config_spec.rb b/spec/lib/gitlab/ldap/config_spec.rb index ca2213cd112..e10837578a8 100644 --- a/spec/lib/gitlab/ldap/config_spec.rb +++ b/spec/lib/gitlab/ldap/config_spec.rb @@ -5,6 +5,14 @@ describe Gitlab::LDAP::Config do let(:config) { described_class.new('ldapmain') } + describe '.servers' do + it 'returns empty array if no server information is available' do + allow(Gitlab.config).to receive(:ldap).and_return('enabled' => false) + + expect(described_class.servers).to eq [] + end + end + describe '#initialize' do it 'requires a provider' do expect { described_class.new }.to raise_error ArgumentError diff --git a/spec/lib/gitlab/ldap/person_spec.rb b/spec/lib/gitlab/ldap/person_spec.rb index b54d4000b53..05e1e394bb1 100644 --- a/spec/lib/gitlab/ldap/person_spec.rb +++ b/spec/lib/gitlab/ldap/person_spec.rb @@ -66,15 +66,6 @@ describe Gitlab::LDAP::Person do end end - describe '.validate_entry' do - it 'raises InvalidEntryError' do - entry['foo'] = 'bar' - - expect { described_class.new(entry, 'ldapmain') } - .to raise_error(Gitlab::LDAP::Person::InvalidEntryError) - end - end - describe '#name' do it 'uses the configured name attribute and handles values as an array' do name = 'John Doe' diff --git a/spec/lib/gitlab/middleware/multipart_spec.rb b/spec/lib/gitlab/middleware/multipart_spec.rb index 8d925460f01..a2ba91dae80 100644 --- a/spec/lib/gitlab/middleware/multipart_spec.rb +++ b/spec/lib/gitlab/middleware/multipart_spec.rb @@ -5,15 +5,17 @@ require 'tempfile' describe Gitlab::Middleware::Multipart do let(:app) { double(:app) } let(:middleware) { described_class.new(app) } + let(:original_filename) { 'filename' } it 'opens top-level files' do Tempfile.open('top-level') do |tempfile| - env = post_env({ 'file' => tempfile.path }, { 'file.name' => 'filename' }, Gitlab::Workhorse.secret, 'gitlab-workhorse') + env = post_env({ 'file' => tempfile.path }, { 'file.name' => original_filename }, Gitlab::Workhorse.secret, 'gitlab-workhorse') expect(app).to receive(:call) do |env| file = Rack::Request.new(env).params['file'] expect(file).to be_a(::UploadedFile) expect(file.path).to eq(tempfile.path) + expect(file.original_filename).to eq(original_filename) end middleware.call(env) @@ -34,13 +36,14 @@ describe Gitlab::Middleware::Multipart do it 'opens files one level deep' do Tempfile.open('one-level') do |tempfile| - in_params = { 'user' => { 'avatar' => { '.name' => 'filename' } } } + in_params = { 'user' => { 'avatar' => { '.name' => original_filename } } } env = post_env({ 'user[avatar]' => tempfile.path }, in_params, Gitlab::Workhorse.secret, 'gitlab-workhorse') expect(app).to receive(:call) do |env| file = Rack::Request.new(env).params['user']['avatar'] expect(file).to be_a(::UploadedFile) expect(file.path).to eq(tempfile.path) + expect(file.original_filename).to eq(original_filename) end middleware.call(env) @@ -49,13 +52,14 @@ describe Gitlab::Middleware::Multipart do it 'opens files two levels deep' do Tempfile.open('two-levels') do |tempfile| - in_params = { 'project' => { 'milestone' => { 'themesong' => { '.name' => 'filename' } } } } + in_params = { 'project' => { 'milestone' => { 'themesong' => { '.name' => original_filename } } } } env = post_env({ 'project[milestone][themesong]' => tempfile.path }, in_params, Gitlab::Workhorse.secret, 'gitlab-workhorse') expect(app).to receive(:call) do |env| file = Rack::Request.new(env).params['project']['milestone']['themesong'] expect(file).to be_a(::UploadedFile) expect(file.path).to eq(tempfile.path) + expect(file.original_filename).to eq(original_filename) end middleware.call(env) diff --git a/spec/lib/gitlab/o_auth/user_spec.rb b/spec/lib/gitlab/o_auth/user_spec.rb index 03e0a9e2a03..b8455403bdb 100644 --- a/spec/lib/gitlab/o_auth/user_spec.rb +++ b/spec/lib/gitlab/o_auth/user_spec.rb @@ -724,6 +724,10 @@ describe Gitlab::OAuth::User do it "does not update the user location" do expect(gl_user.location).not_to eq(info_hash[:address][:country]) end + + it 'does not create associated user synced attributes metadata' do + expect(gl_user.user_synced_attributes_metadata).to be_nil + end end end diff --git a/spec/lib/gitlab/profiler_spec.rb b/spec/lib/gitlab/profiler_spec.rb index 4a43dbb2371..f02b1cf55fb 100644 --- a/spec/lib/gitlab/profiler_spec.rb +++ b/spec/lib/gitlab/profiler_spec.rb @@ -53,6 +53,15 @@ describe Gitlab::Profiler do described_class.profile('/', user: user) end + context 'when providing a user without a personal access token' do + it 'raises an error' do + user = double(:user) + allow(user).to receive_message_chain(:personal_access_tokens, :active, :pluck).and_return([]) + + expect { described_class.profile('/', user: user) }.to raise_error('Your user must have a personal_access_token') + end + end + it 'uses the private_token for auth if both it and user are set' do user = double(:user) user_token = 'user' diff --git a/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb b/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb index c7169717fc1..0697cb2def6 100644 --- a/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb +++ b/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb @@ -7,7 +7,7 @@ describe Gitlab::Prometheus::Queries::AdditionalMetricsDeploymentQuery do include_examples 'additional metrics query' do let(:deployment) { create(:deployment, environment: environment) } - let(:query_params) { [deployment.id] } + let(:query_params) { [environment.id, deployment.id] } it 'queries using specific time' do expect(client).to receive(:query_range).with(anything, diff --git a/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb b/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb index ffe3ad85baa..84dc31d9732 100644 --- a/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb +++ b/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb @@ -31,7 +31,7 @@ describe Gitlab::Prometheus::Queries::DeploymentQuery do expect(client).to receive(:query).with('avg(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="environment-slug"}[30m])) * 100', time: stop_time) - expect(subject.query(deployment.id)).to eq(memory_values: nil, memory_before: nil, memory_after: nil, - cpu_values: nil, cpu_before: nil, cpu_after: nil) + expect(subject.query(environment.id, deployment.id)).to eq(memory_values: nil, memory_before: nil, memory_after: nil, + cpu_values: nil, cpu_before: nil, cpu_after: nil) end end diff --git a/spec/lib/gitlab/prometheus_client_spec.rb b/spec/lib/gitlab/prometheus_client_spec.rb index de625324092..5d86007f71f 100644 --- a/spec/lib/gitlab/prometheus_client_spec.rb +++ b/spec/lib/gitlab/prometheus_client_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::PrometheusClient do include PrometheusHelpers - subject { described_class.new(api_url: 'https://prometheus.example.com') } + subject { described_class.new(RestClient::Resource.new('https://prometheus.example.com')) } describe '#ping' do it 'issues a "query" request to the API endpoint' do @@ -47,16 +47,28 @@ describe Gitlab::PrometheusClient do expect(req_stub).to have_been_requested end end + + context 'when request returns non json data' do + it 'raises a Gitlab::PrometheusError error' do + req_stub = stub_prometheus_request(query_url, status: 200, body: 'not json') + + expect { execute_query } + .to raise_error(Gitlab::PrometheusError, 'Parsing response failed') + expect(req_stub).to have_been_requested + end + end end describe 'failure to reach a provided prometheus url' do let(:prometheus_url) {"https://prometheus.invalid.example.com"} + subject { described_class.new(RestClient::Resource.new(prometheus_url)) } + context 'exceptions are raised' do it 'raises a Gitlab::PrometheusError error when a SocketError is rescued' do req_stub = stub_prometheus_request_with_exception(prometheus_url, SocketError) - expect { subject.send(:get, prometheus_url) } + expect { subject.send(:get, '/', {}) } .to raise_error(Gitlab::PrometheusError, "Can't connect to #{prometheus_url}") expect(req_stub).to have_been_requested end @@ -64,15 +76,15 @@ describe Gitlab::PrometheusClient do it 'raises a Gitlab::PrometheusError error when a SSLError is rescued' do req_stub = stub_prometheus_request_with_exception(prometheus_url, OpenSSL::SSL::SSLError) - expect { subject.send(:get, prometheus_url) } + expect { subject.send(:get, '/', {}) } .to raise_error(Gitlab::PrometheusError, "#{prometheus_url} contains invalid SSL data") expect(req_stub).to have_been_requested end - it 'raises a Gitlab::PrometheusError error when a HTTParty::Error is rescued' do - req_stub = stub_prometheus_request_with_exception(prometheus_url, HTTParty::Error) + it 'raises a Gitlab::PrometheusError error when a RestClient::Exception is rescued' do + req_stub = stub_prometheus_request_with_exception(prometheus_url, RestClient::Exception) - expect { subject.send(:get, prometheus_url) } + expect { subject.send(:get, '/', {}) } .to raise_error(Gitlab::PrometheusError, "Network connection error") expect(req_stub).to have_been_requested end diff --git a/spec/lib/gitlab/query_limiting/transaction_spec.rb b/spec/lib/gitlab/query_limiting/transaction_spec.rb index b4231fcd0fa..b72b8574174 100644 --- a/spec/lib/gitlab/query_limiting/transaction_spec.rb +++ b/spec/lib/gitlab/query_limiting/transaction_spec.rb @@ -59,18 +59,6 @@ describe Gitlab::QueryLimiting::Transaction do expect { transaction.act_upon_results } .to raise_error(described_class::ThresholdExceededError) end - - it 'reports the error in Sentry if raising an error is disabled' do - expect(transaction) - .to receive(:raise_error?) - .and_return(false) - - expect(Raven) - .to receive(:capture_exception) - .with(an_instance_of(described_class::ThresholdExceededError)) - - transaction.act_upon_results - end end end diff --git a/spec/lib/gitlab/query_limiting_spec.rb b/spec/lib/gitlab/query_limiting_spec.rb index 2eddab0b8c3..42877b1e2dd 100644 --- a/spec/lib/gitlab/query_limiting_spec.rb +++ b/spec/lib/gitlab/query_limiting_spec.rb @@ -12,14 +12,16 @@ describe Gitlab::QueryLimiting do expect(described_class.enable?).to eq(true) end - it 'returns true on GitLab.com' do + it 'returns false on GitLab.com' do + expect(Rails.env).to receive(:development?).and_return(false) + expect(Rails.env).to receive(:test?).and_return(false) allow(Gitlab).to receive(:com?).and_return(true) - expect(described_class.enable?).to eq(true) + expect(described_class.enable?).to eq(false) end - it 'returns true in a non GitLab.com' do - expect(Gitlab).to receive(:com?).and_return(false) + it 'returns false in a non GitLab.com' do + allow(Gitlab).to receive(:com?).and_return(false) expect(Rails.env).to receive(:development?).and_return(false) expect(Rails.env).to receive(:test?).and_return(false) diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb index 8b54d72d6f7..a1079e54975 100644 --- a/spec/lib/gitlab/regex_spec.rb +++ b/spec/lib/gitlab/regex_spec.rb @@ -18,6 +18,7 @@ describe Gitlab::Regex do subject { described_class.environment_name_regex } it { is_expected.to match('foo') } + it { is_expected.to match('a') } it { is_expected.to match('foo-1') } it { is_expected.to match('FOO') } it { is_expected.to match('foo/1') } @@ -25,6 +26,10 @@ describe Gitlab::Regex do it { is_expected.not_to match('9&foo') } it { is_expected.not_to match('foo-^') } it { is_expected.not_to match('!!()()') } + it { is_expected.not_to match('/foo') } + it { is_expected.not_to match('foo/') } + it { is_expected.not_to match('/foo/') } + it { is_expected.not_to match('/') } end describe '.environment_slug_regex' do diff --git a/spec/lib/gitlab/repo_path_spec.rb b/spec/lib/gitlab/repo_path_spec.rb index 1a925a15e0c..b67bcc77bd4 100644 --- a/spec/lib/gitlab/repo_path_spec.rb +++ b/spec/lib/gitlab/repo_path_spec.rb @@ -6,11 +6,11 @@ describe ::Gitlab::RepoPath do context 'a repository storage path' do it 'parses a full repository path' do - expect(described_class.parse(project.repository.path)).to eq([project, false, nil]) + expect(described_class.parse(project.repository.full_path)).to eq([project, false, nil]) end it 'parses a full wiki path' do - expect(described_class.parse(project.wiki.repository.path)).to eq([project, true, nil]) + expect(described_class.parse(project.wiki.repository.full_path)).to eq([project, true, nil]) end end diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb index 17b48b3d062..9dbab95f70e 100644 --- a/spec/lib/gitlab/search_results_spec.rb +++ b/spec/lib/gitlab/search_results_spec.rb @@ -20,9 +20,13 @@ describe Gitlab::SearchResults do end describe '#objects' do - it 'returns without_page collection by default' do + it 'returns without_counts collection by default' do expect(results.objects('projects')).to be_kind_of(Kaminari::PaginatableWithoutCount) end + + it 'returns with counts collection when requested' do + expect(results.objects('projects', 1, false)).not_to be_kind_of(Kaminari::PaginatableWithoutCount) + end end describe '#projects_count' do diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb index 2b61ce38418..4506cbc3982 100644 --- a/spec/lib/gitlab/shell_spec.rb +++ b/spec/lib/gitlab/shell_spec.rb @@ -443,7 +443,7 @@ describe Gitlab::Shell do end describe '#remove_repository' do - let!(:project) { create(:project, :repository) } + let!(:project) { create(:project, :repository, :legacy_storage) } let(:disk_path) { "#{project.disk_path}.git" } it 'returns true when the command succeeds' do diff --git a/spec/lib/gitlab/ssh_public_key_spec.rb b/spec/lib/gitlab/ssh_public_key_spec.rb index c15e29774b6..93d538141ce 100644 --- a/spec/lib/gitlab/ssh_public_key_spec.rb +++ b/spec/lib/gitlab/ssh_public_key_spec.rb @@ -37,41 +37,6 @@ describe Gitlab::SSHPublicKey, lib: true do end end - describe '.sanitize(key_content)' do - let(:content) { build(:key).key } - - context 'when key has blank space characters' do - it 'removes the extra blank space characters' do - unsanitized = content.insert(100, "\n") - .insert(40, "\r\n") - .insert(30, ' ') - - sanitized = described_class.sanitize(unsanitized) - _, body = sanitized.split - - expect(sanitized).not_to eq(unsanitized) - expect(body).not_to match(/\s/) - end - end - - context "when key doesn't have blank space characters" do - it "doesn't modify the content" do - sanitized = described_class.sanitize(content) - - expect(sanitized).to eq(content) - end - end - - context "when key is invalid" do - it 'returns the original content' do - unsanitized = "ssh-foo any content==" - sanitized = described_class.sanitize(unsanitized) - - expect(sanitized).to eq(unsanitized) - end - end - end - describe '#valid?' do subject { public_key } diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index dc2bb5b9747..37a0bf1ad36 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -324,7 +324,7 @@ describe Gitlab::Workhorse do it 'includes a Repository param' do repo_param = { storage_name: 'default', - relative_path: project.full_path + '.git', + relative_path: project.disk_path + '.git', gl_repository: "project-#{project.id}" } diff --git a/spec/migrations/README.md b/spec/migrations/README.md index 45cf25b96de..49760fa62b8 100644 --- a/spec/migrations/README.md +++ b/spec/migrations/README.md @@ -89,5 +89,5 @@ end ## Best practices 1. Note that this type of tests do not run within the transaction, we use -a truncation database cleanup strategy. Do not depend on transaction being +a deletion database cleanup strategy. Do not depend on transaction being present. diff --git a/spec/migrations/convert_custom_notification_settings_to_columns_spec.rb b/spec/migrations/convert_custom_notification_settings_to_columns_spec.rb index 759e77ac9db..d1bf6bdf9d6 100644 --- a/spec/migrations/convert_custom_notification_settings_to_columns_spec.rb +++ b/spec/migrations/convert_custom_notification_settings_to_columns_spec.rb @@ -21,7 +21,7 @@ describe ConvertCustomNotificationSettingsToColumns, :migration do events[event] = true end - user = build(:user).becomes(user_class).tap(&:save!) + user = user_class.create!(email: "user-#{SecureRandom.hex}@example.org", username: "user-#{SecureRandom.hex}", encrypted_password: '12345678') create_params = { user_id: user.id, level: params[:level], events: events } notification_setting = described_class::NotificationSetting.create(create_params) @@ -37,7 +37,7 @@ describe ConvertCustomNotificationSettingsToColumns, :migration do events[event] = true end - user = build(:user).becomes(user_class).tap(&:save!) + user = user_class.create!(email: "user-#{SecureRandom.hex}@example.org", username: "user-#{SecureRandom.hex}", encrypted_password: '12345678') create_params = events.merge(user_id: user.id, level: params[:level]) notification_setting = described_class::NotificationSetting.create(create_params) diff --git a/spec/migrations/migrate_process_commit_worker_jobs_spec.rb b/spec/migrations/migrate_process_commit_worker_jobs_spec.rb index e5793a3c0ee..657113812bd 100644 --- a/spec/migrations/migrate_process_commit_worker_jobs_spec.rb +++ b/spec/migrations/migrate_process_commit_worker_jobs_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' require Rails.root.join('db', 'migrate', '20161124141322_migrate_process_commit_worker_jobs.rb') describe MigrateProcessCommitWorkerJobs do - let(:project) { create(:project, :repository) } + let(:project) { create(:project, :legacy_storage, :repository) } let(:user) { create(:user) } let(:commit) { project.commit.raw.rugged_commit } diff --git a/spec/migrations/remove_redundant_pipeline_stages_spec.rb b/spec/migrations/remove_redundant_pipeline_stages_spec.rb new file mode 100644 index 00000000000..8325f986594 --- /dev/null +++ b/spec/migrations/remove_redundant_pipeline_stages_spec.rb @@ -0,0 +1,59 @@ +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20180119121225_remove_redundant_pipeline_stages.rb') + +describe RemoveRedundantPipelineStages, :migration do + let(:projects) { table(:projects) } + let(:pipelines) { table(:ci_pipelines) } + let(:stages) { table(:ci_stages) } + let(:builds) { table(:ci_builds) } + + before do + projects.create!(id: 123, name: 'gitlab', path: 'gitlab-ce') + pipelines.create!(id: 234, project_id: 123, ref: 'master', sha: 'adf43c3a') + + stages.create!(id: 6, project_id: 123, pipeline_id: 234, name: 'build') + stages.create!(id: 10, project_id: 123, pipeline_id: 234, name: 'build') + stages.create!(id: 21, project_id: 123, pipeline_id: 234, name: 'build') + stages.create!(id: 41, project_id: 123, pipeline_id: 234, name: 'test') + stages.create!(id: 62, project_id: 123, pipeline_id: 234, name: 'test') + stages.create!(id: 102, project_id: 123, pipeline_id: 234, name: 'deploy') + + builds.create!(id: 1, commit_id: 234, project_id: 123, stage_id: 10) + builds.create!(id: 2, commit_id: 234, project_id: 123, stage_id: 21) + builds.create!(id: 3, commit_id: 234, project_id: 123, stage_id: 21) + builds.create!(id: 4, commit_id: 234, project_id: 123, stage_id: 41) + builds.create!(id: 5, commit_id: 234, project_id: 123, stage_id: 62) + builds.create!(id: 6, commit_id: 234, project_id: 123, stage_id: 102) + end + + it 'removes ambiguous stages and preserves builds' do + expect(stages.all.count).to eq 6 + expect(builds.all.count).to eq 6 + + migrate! + + expect(stages.all.count).to eq 1 + expect(builds.all.count).to eq 6 + expect(builds.all.pluck(:stage_id).compact).to eq [102] + end + + it 'retries when incorrectly added index exception is caught' do + allow_any_instance_of(described_class) + .to receive(:remove_redundant_pipeline_stages!) + + expect_any_instance_of(described_class) + .to receive(:remove_outdated_index!) + .exactly(100).times.and_call_original + + expect { migrate! } + .to raise_error StandardError, /Failed to add an unique index/ + end + + it 'does not retry when unknown exception is being raised' do + allow(subject).to receive(:remove_outdated_index!) + expect(subject).to receive(:remove_redundant_pipeline_stages!).once + allow(subject).to receive(:add_unique_index!).and_raise(StandardError) + + expect { subject.up(attempts: 3) }.to raise_error StandardError + end +end diff --git a/spec/migrations/rename_reserved_project_names_spec.rb b/spec/migrations/rename_reserved_project_names_spec.rb index e6555b1fe6b..34336d705b1 100644 --- a/spec/migrations/rename_reserved_project_names_spec.rb +++ b/spec/migrations/rename_reserved_project_names_spec.rb @@ -3,10 +3,14 @@ require 'spec_helper' require Rails.root.join('db', 'post_migrate', '20161221153951_rename_reserved_project_names.rb') -# This migration uses multiple threads, and thus different transactions. This -# means data created in this spec may not be visible to some threads. To work -# around this we use the DELETE cleaning strategy. -describe RenameReservedProjectNames, :delete do +# This migration is using factories, which set fields that don't actually +# exist in the DB schema previous to 20161221153951. Thus we just use the +# latest schema when testing this migration. +# This is ok-ish because: +# 1. This migration is a data migration +# 2. It only relies on very stable DB fields: routes.id, routes.path, namespaces.id, projects.namespace_id +# Ideally, the test should not use factories and rely on the `table` helper instead. +describe RenameReservedProjectNames, :migration, schema: :latest do let(:migration) { described_class.new } let!(:project) { create(:project) } diff --git a/spec/migrations/turn_nested_groups_into_regular_groups_for_mysql_spec.rb b/spec/migrations/turn_nested_groups_into_regular_groups_for_mysql_spec.rb index 6f7a730edff..528dc54781d 100644 --- a/spec/migrations/turn_nested_groups_into_regular_groups_for_mysql_spec.rb +++ b/spec/migrations/turn_nested_groups_into_regular_groups_for_mysql_spec.rb @@ -4,7 +4,7 @@ require Rails.root.join('db', 'migrate', '20170503140202_turn_nested_groups_into describe TurnNestedGroupsIntoRegularGroupsForMysql do let!(:parent_group) { create(:group) } let!(:child_group) { create(:group, parent: parent_group) } - let!(:project) { create(:project, :empty_repo, namespace: child_group) } + let!(:project) { create(:project, :legacy_storage, :empty_repo, namespace: child_group) } let!(:member) { create(:user) } let(:migration) { described_class.new } diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index ef480e7a80a..ae2d34750a7 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -114,6 +114,40 @@ describe ApplicationSetting do it { expect(setting.repository_storages).to eq(['default']) } end + context 'auto_devops_domain setting' do + context 'when auto_devops_enabled? is true' do + before do + setting.update(auto_devops_enabled: true) + end + + it 'can be blank' do + setting.update(auto_devops_domain: '') + + expect(setting).to be_valid + end + + context 'with a valid value' do + before do + setting.update(auto_devops_domain: 'domain.com') + end + + it 'is valid' do + expect(setting).to be_valid + end + end + + context 'with an invalid value' do + before do + setting.update(auto_devops_domain: 'definitelynotahostname') + end + + it 'is invalid' do + expect(setting).to be_invalid + end + end + end + end + context 'circuitbreaker settings' do [:circuitbreaker_failure_count_threshold, :circuitbreaker_check_interval, diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 0b3d5c6a0bd..2b6b6a61182 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1413,6 +1413,7 @@ describe Ci::Build do [ { key: 'CI', value: 'true', public: true }, { key: 'GITLAB_CI', value: 'true', public: true }, + { key: 'GITLAB_FEATURES', value: project.namespace.features.join(','), public: true }, { key: 'CI_SERVER_NAME', value: 'GitLab', public: true }, { key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true }, { key: 'CI_SERVER_REVISION', value: Gitlab::REVISION, public: true }, @@ -1589,7 +1590,7 @@ describe Ci::Build do context 'when the branch is protected' do before do - create(:protected_branch, project: build.project, name: build.ref) + allow(build.project).to receive(:protected_for?).with(build.ref).and_return(true) end it { is_expected.to include(protected_variable) } @@ -1597,7 +1598,7 @@ describe Ci::Build do context 'when the tag is protected' do before do - create(:protected_tag, project: build.project, name: build.ref) + allow(build.project).to receive(:protected_for?).with(build.ref).and_return(true) end it { is_expected.to include(protected_variable) } @@ -1634,7 +1635,7 @@ describe Ci::Build do context 'when the branch is protected' do before do - create(:protected_branch, project: build.project, name: build.ref) + allow(build.project).to receive(:protected_for?).with(build.ref).and_return(true) end it { is_expected.to include(protected_variable) } @@ -1642,7 +1643,7 @@ describe Ci::Build do context 'when the tag is protected' do before do - create(:protected_tag, project: build.project, name: build.ref) + allow(build.project).to receive(:protected_for?).with(build.ref).and_return(true) end it { is_expected.to include(protected_variable) } diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index b2b64e6ff48..ab170e6351c 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -95,28 +95,68 @@ describe Ci::Runner do subject { runner.online? } - context 'never contacted' do + before do + allow_any_instance_of(described_class).to receive(:cached_attribute).and_call_original + allow_any_instance_of(described_class).to receive(:cached_attribute) + .with(:platform).and_return("darwin") + end + + context 'no cache value' do before do - runner.contacted_at = nil + stub_redis_runner_contacted_at(nil) end - it { is_expected.to be_falsey } - end + context 'never contacted' do + before do + runner.contacted_at = nil + end - context 'contacted long time ago time' do - before do - runner.contacted_at = 1.year.ago + it { is_expected.to be_falsey } + end + + context 'contacted long time ago time' do + before do + runner.contacted_at = 1.year.ago + end + + it { is_expected.to be_falsey } end - it { is_expected.to be_falsey } + context 'contacted 1s ago' do + before do + runner.contacted_at = 1.second.ago + end + + it { is_expected.to be_truthy } + end end - context 'contacted 1s ago' do - before do - runner.contacted_at = 1.second.ago + context 'with cache value' do + context 'contacted long time ago time' do + before do + runner.contacted_at = 1.year.ago + stub_redis_runner_contacted_at(1.year.ago.to_s) + end + + it { is_expected.to be_falsey } + end + + context 'contacted 1s ago' do + before do + runner.contacted_at = 50.minutes.ago + stub_redis_runner_contacted_at(1.second.ago.to_s) + end + + it { is_expected.to be_truthy } end + end - it { is_expected.to be_truthy } + def stub_redis_runner_contacted_at(value) + Gitlab::Redis::SharedState.with do |redis| + cache_key = runner.send(:cache_attribute_key) + expect(redis).to receive(:get).with(cache_key) + .and_return({ contacted_at: value }.to_json).at_least(:once) + end end end @@ -361,6 +401,50 @@ describe Ci::Runner do end end + describe '#update_cached_info' do + let(:runner) { create(:ci_runner) } + + subject { runner.update_cached_info(architecture: '18-bit') } + + context 'when database was updated recently' do + before do + runner.contacted_at = Time.now + end + + it 'updates cache' do + expect_redis_update + + subject + end + end + + context 'when database was not updated recently' do + before do + runner.contacted_at = 2.hours.ago + end + + it 'updates database' do + expect_redis_update + + expect { subject }.to change { runner.reload.read_attribute(:contacted_at) } + .and change { runner.reload.read_attribute(:architecture) } + end + + it 'updates cache' do + expect_redis_update + + subject + end + end + + def expect_redis_update + Gitlab::Redis::SharedState.with do |redis| + redis_key = runner.send(:cache_attribute_key) + expect(redis).to receive(:set).with(redis_key, anything, any_args) + end + end + end + describe '#destroy' do let(:runner) { create(:ci_runner) } diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb index 696099f7cf7..01037919530 100644 --- a/spec/models/clusters/applications/prometheus_spec.rb +++ b/spec/models/clusters/applications/prometheus_spec.rb @@ -6,6 +6,24 @@ describe Clusters::Applications::Prometheus do include_examples 'cluster application specs', described_class + describe 'transition to installed' do + let(:project) { create(:project) } + let(:cluster) { create(:cluster, projects: [project]) } + let(:prometheus_service) { double('prometheus_service') } + + subject { create(:clusters_applications_prometheus, :installing, cluster: cluster) } + + before do + allow(project).to receive(:find_or_initialize_service).with('prometheus').and_return prometheus_service + end + + it 'ensures Prometheus service is activated' do + expect(prometheus_service).to receive(:update).with(active: true) + + subject.make_installed + end + end + describe "#chart_values_file" do subject { create(:clusters_applications_prometheus).chart_values_file } @@ -13,4 +31,58 @@ describe Clusters::Applications::Prometheus do expect(subject).to eq("#{Rails.root}/vendor/prometheus/values.yaml") end end + + describe '#proxy_client' do + context 'cluster is nil' do + it 'returns nil' do + expect(subject.cluster).to be_nil + expect(subject.proxy_client).to be_nil + end + end + + context "cluster doesn't have kubeclient" do + let(:cluster) { create(:cluster) } + subject { create(:clusters_applications_prometheus, cluster: cluster) } + + it 'returns nil' do + expect(subject.proxy_client).to be_nil + end + end + + context 'cluster has kubeclient' do + let(:kubernetes_url) { 'http://example.com' } + let(:k8s_discover_response) do + { + resources: [ + { + name: 'service', + kind: 'Service' + } + ] + } + end + + let(:kube_client) { Kubeclient::Client.new(kubernetes_url) } + + let(:cluster) { create(:cluster) } + subject { create(:clusters_applications_prometheus, cluster: cluster) } + + before do + allow(kube_client.rest_client).to receive(:get).and_return(k8s_discover_response.to_json) + allow(subject.cluster).to receive(:kubeclient).and_return(kube_client) + end + + it 'creates proxy prometheus rest client' do + expect(subject.proxy_client).to be_instance_of(RestClient::Resource) + end + + it 'creates proper url' do + expect(subject.proxy_client.url).to eq('http://example.com/api/v1/proxy/namespaces/gitlab-managed-apps/service/prometheus-prometheus-server:80') + end + + it 'copies options and headers from kube client to proxy client' do + expect(subject.proxy_client.options).to eq(kube_client.rest_client.options.merge(headers: kube_client.headers)) + end + end + end end diff --git a/spec/models/concerns/redis_cacheable_spec.rb b/spec/models/concerns/redis_cacheable_spec.rb new file mode 100644 index 00000000000..3d7963120b6 --- /dev/null +++ b/spec/models/concerns/redis_cacheable_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' + +describe RedisCacheable do + let(:model) { double } + + before do + model.extend(described_class) + allow(model).to receive(:cache_attribute_key).and_return('key') + end + + describe '#cached_attribute' do + let(:payload) { { attribute: 'value' } } + + subject { model.cached_attribute(payload.keys.first) } + + it 'gets the cache attribute' do + Gitlab::Redis::SharedState.with do |redis| + expect(redis).to receive(:get).with('key') + .and_return(payload.to_json) + end + + expect(subject).to eq(payload.values.first) + end + end + + describe '#cache_attributes' do + let(:values) { { name: 'new_name' } } + + subject { model.cache_attributes(values) } + + it 'sets the cache attributes' do + Gitlab::Redis::SharedState.with do |redis| + expect(redis).to receive(:set).with('key', values.to_json, anything) + end + + subject + end + end +end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 338fb314ee9..4f16b73ef38 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -549,7 +549,7 @@ describe Group do context 'when the ref is a protected branch' do before do - create(:protected_branch, name: 'ref', project: project) + allow(project).to receive(:protected_for?).with('ref').and_return(true) end it_behaves_like 'ref is protected' @@ -557,7 +557,7 @@ describe Group do context 'when the ref is a protected tag' do before do - create(:protected_tag, name: 'ref', project: project) + allow(project).to receive(:protected_for?).with('ref').and_return(true) end it_behaves_like 'ref is protected' @@ -571,6 +571,10 @@ describe Group do let(:variable_child_2) { create(:ci_group_variable, group: group_child_2) } let(:variable_child_3) { create(:ci_group_variable, group: group_child_3) } + before do + allow(project).to receive(:protected_for?).with('ref').and_return(true) + end + it 'returns all variables belong to the group and parent groups' do expected_array1 = [protected_variable, secret_variable] expected_array2 = [variable_child, variable_child_2, variable_child_3] diff --git a/spec/models/identity_spec.rb b/spec/models/identity_spec.rb index 7c66c98231b..a5ce245c21d 100644 --- a/spec/models/identity_spec.rb +++ b/spec/models/identity_spec.rb @@ -70,5 +70,38 @@ describe Identity do end end end + + context 'after_destroy' do + let!(:user) { create(:user) } + let(:ldap_identity) { create(:identity, provider: 'ldapmain', extern_uid: 'uid=john smith,ou=people,dc=example,dc=com', user: user) } + let(:ldap_user_synced_attributes) { { provider: 'ldapmain', name_synced: true, email_synced: true } } + let(:other_provider_user_synced_attributes) { { provider: 'other', name_synced: true, email_synced: true } } + + describe 'if user synced attributes metadada provider' do + context 'matches the identity provider ' do + it 'removes the user synced attributes' do + user.create_user_synced_attributes_metadata(ldap_user_synced_attributes) + + expect(user.user_synced_attributes_metadata.provider).to eq 'ldapmain' + + ldap_identity.destroy + + expect(user.reload.user_synced_attributes_metadata).to be_nil + end + end + + context 'does not matche the identity provider' do + it 'does not remove the user synced attributes' do + user.create_user_synced_attributes_metadata(other_provider_user_synced_attributes) + + expect(user.user_synced_attributes_metadata.provider).to eq 'other' + + ldap_identity.destroy + + expect(user.reload.user_synced_attributes_metadata.provider).to eq 'other' + end + end + end + end end end diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb index bf5703ac986..7398fd25aa8 100644 --- a/spec/models/key_spec.rb +++ b/spec/models/key_spec.rb @@ -72,52 +72,15 @@ describe Key, :mailer do expect(build(:key)).to be_valid end - it 'rejects the unfingerprintable key (not a key)' do - expect(build(:key, key: 'ssh-rsa an-invalid-key==')).not_to be_valid - end - - where(:factory, :chars, :expected_sections) do - [ - [:key, ["\n", "\r\n"], 3], - [:key, [' ', ' '], 3], - [:key_without_comment, [' ', ' '], 2] - ] - end - - with_them do - let!(:key) { create(factory) } - let!(:original_fingerprint) { key.fingerprint } - - it 'accepts a key with blank space characters after stripping them' do - modified_key = key.key.insert(100, chars.first).insert(40, chars.last) - _, content = modified_key.split - - key.update!(key: modified_key) - - expect(key).to be_valid - expect(key.key.split.size).to eq(expected_sections) - - expect(content).not_to match(/\s/) - expect(original_fingerprint).to eq(key.fingerprint) - end - end - end - - context 'validate size' do - where(:key_content, :result) do - [ - [Spec::Support::Helpers::KeyGeneratorHelper.new(512).generate, false], - [Spec::Support::Helpers::KeyGeneratorHelper.new(8192).generate, false], - [Spec::Support::Helpers::KeyGeneratorHelper.new(1024).generate, true] - ] + it 'accepts a key with newline charecters after stripping them' do + key = build(:key) + key.key = key.key.insert(100, "\n") + key.key = key.key.insert(40, "\r\n") + expect(key).to be_valid end - with_them do - it 'validates the size of the key' do - key = build(:key, key: key_content) - - expect(key.valid?).to eq(result) - end + it 'rejects the unfingerprintable key (not a key)' do + expect(build(:key, key: 'ssh-rsa an-invalid-key==')).not_to be_valid end end diff --git a/spec/models/lfs_file_lock_spec.rb b/spec/models/lfs_file_lock_spec.rb new file mode 100644 index 00000000000..ce87b01b49c --- /dev/null +++ b/spec/models/lfs_file_lock_spec.rb @@ -0,0 +1,57 @@ +require 'rails_helper' + +describe LfsFileLock do + set(:lfs_file_lock) { create(:lfs_file_lock) } + subject { lfs_file_lock } + + it { is_expected.to belong_to(:project) } + it { is_expected.to belong_to(:user) } + + it { is_expected.to validate_presence_of(:project_id) } + it { is_expected.to validate_presence_of(:user_id) } + it { is_expected.to validate_presence_of(:path) } + + describe '#can_be_unlocked_by?' do + let(:developer) { create(:user) } + let(:master) { create(:user) } + + before do + project = lfs_file_lock.project + + project.add_developer(developer) + project.add_master(master) + end + + context "when it's forced" do + it 'can be unlocked by the author' do + user = lfs_file_lock.user + + expect(lfs_file_lock.can_be_unlocked_by?(user, true)).to eq(true) + end + + it 'can be unlocked by a master' do + expect(lfs_file_lock.can_be_unlocked_by?(master, true)).to eq(true) + end + + it "can't be unlocked by other user" do + expect(lfs_file_lock.can_be_unlocked_by?(developer, true)).to eq(false) + end + end + + context "when it isn't forced" do + it 'can be unlocked by the author' do + user = lfs_file_lock.user + + expect(lfs_file_lock.can_be_unlocked_by?(user)).to eq(true) + end + + it "can't be unlocked by a master" do + expect(lfs_file_lock.can_be_unlocked_by?(master)).to eq(false) + end + + it "can't be unlocked by other user" do + expect(lfs_file_lock.can_be_unlocked_by?(developer)).to eq(false) + end + end + end +end diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 191b60e4383..e626efd054d 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -168,84 +168,105 @@ describe Namespace do end describe '#move_dir', :request_store do - let(:namespace) { create(:namespace) } - let!(:project) { create(:project_empty_repo, namespace: namespace) } + shared_examples "namespace restrictions" do + context "when any project has container images" do + let(:container_repository) { create(:container_repository) } - it "raises error when directory exists" do - expect { namespace.move_dir }.to raise_error("namespace directory cannot be moved") - end + before do + stub_container_registry_config(enabled: true) + stub_container_registry_tags(repository: :any, tags: ['tag']) - it "moves dir if path changed" do - namespace.update_attributes(path: namespace.full_path + '_new') + create(:project, namespace: namespace, container_repositories: [container_repository]) - expect(gitlab_shell.exists?(project.repository_storage_path, "#{namespace.path}/#{project.path}.git")).to be_truthy - end + allow(namespace).to receive(:path_was).and_return(namespace.path) + allow(namespace).to receive(:path).and_return('new_path') + end - context "when any project has container images" do - let(:container_repository) { create(:container_repository) } + it 'raises an error about not movable project' do + expect { namespace.move_dir }.to raise_error(/Namespace cannot be moved/) + end + end + end - before do - stub_container_registry_config(enabled: true) - stub_container_registry_tags(repository: :any, tags: ['tag']) + context 'legacy storage' do + let(:namespace) { create(:namespace) } + let!(:project) { create(:project_empty_repo, :legacy_storage, namespace: namespace) } - create(:project, namespace: namespace, container_repositories: [container_repository]) + it_behaves_like 'namespace restrictions' - allow(namespace).to receive(:path_was).and_return(namespace.path) - allow(namespace).to receive(:path).and_return('new_path') + it "raises error when directory exists" do + expect { namespace.move_dir }.to raise_error("namespace directory cannot be moved") end - it 'raises an error about not movable project' do - expect { namespace.move_dir }.to raise_error(/Namespace cannot be moved/) + it "moves dir if path changed" do + namespace.update_attributes(path: namespace.full_path + '_new') + + expect(gitlab_shell.exists?(project.repository_storage_path, "#{namespace.path}/#{project.path}.git")).to be_truthy end - end - context 'with subgroups' do - let(:parent) { create(:group, name: 'parent', path: 'parent') } - let(:child) { create(:group, name: 'child', path: 'child', parent: parent) } - let!(:project) { create(:project_empty_repo, path: 'the-project', namespace: child, skip_disk_validation: true) } - let(:uploads_dir) { FileUploader.root } - let(:pages_dir) { File.join(TestEnv.pages_path) } + context 'with subgroups' do + let(:parent) { create(:group, name: 'parent', path: 'parent') } + let(:child) { create(:group, name: 'child', path: 'child', parent: parent) } + let!(:project) { create(:project_empty_repo, :legacy_storage, path: 'the-project', namespace: child, skip_disk_validation: true) } + let(:uploads_dir) { FileUploader.root } + let(:pages_dir) { File.join(TestEnv.pages_path) } - before do - FileUtils.mkdir_p(File.join(uploads_dir, 'parent', 'child', 'the-project')) - FileUtils.mkdir_p(File.join(pages_dir, 'parent', 'child', 'the-project')) - end + before do + FileUtils.mkdir_p(File.join(uploads_dir, project.full_path)) + FileUtils.mkdir_p(File.join(pages_dir, project.full_path)) + end + + context 'renaming child' do + it 'correctly moves the repository, uploads and pages' do + expected_repository_path = File.join(TestEnv.repos_path, 'parent', 'renamed', 'the-project.git') + expected_upload_path = File.join(uploads_dir, 'parent', 'renamed', 'the-project') + expected_pages_path = File.join(pages_dir, 'parent', 'renamed', 'the-project') - context 'renaming child' do - it 'correctly moves the repository, uploads and pages' do - expected_repository_path = File.join(TestEnv.repos_path, 'parent', 'renamed', 'the-project.git') - expected_upload_path = File.join(uploads_dir, 'parent', 'renamed', 'the-project') - expected_pages_path = File.join(pages_dir, 'parent', 'renamed', 'the-project') + child.update_attributes!(path: 'renamed') - child.update_attributes!(path: 'renamed') + expect(File.directory?(expected_repository_path)).to be(true) + expect(File.directory?(expected_upload_path)).to be(true) + expect(File.directory?(expected_pages_path)).to be(true) + end + end + + context 'renaming parent' do + it 'correctly moves the repository, uploads and pages' do + expected_repository_path = File.join(TestEnv.repos_path, 'renamed', 'child', 'the-project.git') + expected_upload_path = File.join(uploads_dir, 'renamed', 'child', 'the-project') + expected_pages_path = File.join(pages_dir, 'renamed', 'child', 'the-project') - expect(File.directory?(expected_repository_path)).to be(true) - expect(File.directory?(expected_upload_path)).to be(true) - expect(File.directory?(expected_pages_path)).to be(true) + parent.update_attributes!(path: 'renamed') + + expect(File.directory?(expected_repository_path)).to be(true) + expect(File.directory?(expected_upload_path)).to be(true) + expect(File.directory?(expected_pages_path)).to be(true) + end end end + end - context 'renaming parent' do - it 'correctly moves the repository, uploads and pages' do - expected_repository_path = File.join(TestEnv.repos_path, 'renamed', 'child', 'the-project.git') - expected_upload_path = File.join(uploads_dir, 'renamed', 'child', 'the-project') - expected_pages_path = File.join(pages_dir, 'renamed', 'child', 'the-project') + context 'hashed storage' do + let(:namespace) { create(:namespace) } + let!(:project) { create(:project_empty_repo, namespace: namespace) } - parent.update_attributes!(path: 'renamed') + it_behaves_like 'namespace restrictions' - expect(File.directory?(expected_repository_path)).to be(true) - expect(File.directory?(expected_upload_path)).to be(true) - expect(File.directory?(expected_pages_path)).to be(true) - end + it "repository directory remains unchanged if path changed" do + before_disk_path = project.disk_path + namespace.update_attributes(path: namespace.full_path + '_new') + + expect(before_disk_path).to eq(project.disk_path) + expect(gitlab_shell.exists?(project.repository_storage_path, "#{project.disk_path}.git")).to be_truthy end end it 'updates project full path in .git/config for each project inside namespace' do parent = create(:group, name: 'mygroup', path: 'mygroup') subgroup = create(:group, name: 'mysubgroup', path: 'mysubgroup', parent: parent) - project_in_parent_group = create(:project, :repository, namespace: parent, name: 'foo1') - hashed_project_in_subgroup = create(:project, :repository, :hashed, namespace: subgroup, name: 'foo2') - legacy_project_in_subgroup = create(:project, :repository, namespace: subgroup, name: 'foo3') + project_in_parent_group = create(:project, :legacy_storage, :repository, namespace: parent, name: 'foo1') + hashed_project_in_subgroup = create(:project, :repository, namespace: subgroup, name: 'foo2') + legacy_project_in_subgroup = create(:project, :legacy_storage, :repository, namespace: subgroup, name: 'foo3') parent.update(path: 'mygroup_new') @@ -260,38 +281,18 @@ describe Namespace do end describe '#rm_dir', 'callback' do - let!(:project) { create(:project_empty_repo, namespace: namespace) } let(:repository_storage_path) { Gitlab.config.repositories.storages.default['path'] } let(:path_in_dir) { File.join(repository_storage_path, namespace.full_path) } let(:deleted_path) { namespace.full_path.gsub(namespace.path, "#{namespace.full_path}+#{namespace.id}+deleted") } let(:deleted_path_in_dir) { File.join(repository_storage_path, deleted_path) } - it 'renames its dirs when deleted' do - allow(GitlabShellWorker).to receive(:perform_in) - - namespace.destroy - - expect(File.exist?(deleted_path_in_dir)).to be(true) - end - - it 'schedules the namespace for deletion' do - expect(GitlabShellWorker).to receive(:perform_in).with(5.minutes, :rm_namespace, repository_storage_path, deleted_path) - - namespace.destroy - end - - context 'in sub-groups' do - let(:parent) { create(:group, path: 'parent') } - let(:child) { create(:group, parent: parent, path: 'child') } - let!(:project) { create(:project_empty_repo, namespace: child) } - let(:path_in_dir) { File.join(repository_storage_path, 'parent', 'child') } - let(:deleted_path) { File.join('parent', "child+#{child.id}+deleted") } - let(:deleted_path_in_dir) { File.join(repository_storage_path, deleted_path) } + context 'legacy storage' do + let!(:project) { create(:project_empty_repo, :legacy_storage, namespace: namespace) } it 'renames its dirs when deleted' do allow(GitlabShellWorker).to receive(:perform_in) - child.destroy + namespace.destroy expect(File.exist?(deleted_path_in_dir)).to be(true) end @@ -299,14 +300,57 @@ describe Namespace do it 'schedules the namespace for deletion' do expect(GitlabShellWorker).to receive(:perform_in).with(5.minutes, :rm_namespace, repository_storage_path, deleted_path) - child.destroy + namespace.destroy + end + + context 'in sub-groups' do + let(:parent) { create(:group, path: 'parent') } + let(:child) { create(:group, parent: parent, path: 'child') } + let!(:project) { create(:project_empty_repo, :legacy_storage, namespace: child) } + let(:path_in_dir) { File.join(repository_storage_path, 'parent', 'child') } + let(:deleted_path) { File.join('parent', "child+#{child.id}+deleted") } + let(:deleted_path_in_dir) { File.join(repository_storage_path, deleted_path) } + + it 'renames its dirs when deleted' do + allow(GitlabShellWorker).to receive(:perform_in) + + child.destroy + + expect(File.exist?(deleted_path_in_dir)).to be(true) + end + + it 'schedules the namespace for deletion' do + expect(GitlabShellWorker).to receive(:perform_in).with(5.minutes, :rm_namespace, repository_storage_path, deleted_path) + + child.destroy + end + end + + it 'removes the exports folder' do + expect(namespace).to receive(:remove_exports!) + + namespace.destroy end end - it 'removes the exports folder' do - expect(namespace).to receive(:remove_exports!) + context 'hashed storage' do + let!(:project) { create(:project_empty_repo, namespace: namespace) } + + it 'has no repositories base directories to remove' do + allow(GitlabShellWorker).to receive(:perform_in) + + expect(File.exist?(path_in_dir)).to be(false) - namespace.destroy + namespace.destroy + + expect(File.exist?(deleted_path_in_dir)).to be(false) + end + + it 'removes the exports folder' do + expect(namespace).to receive(:remove_exports!) + + namespace.destroy + end end end @@ -567,8 +611,8 @@ describe Namespace do end describe '#remove_exports' do - let(:legacy_project) { create(:project, :with_export, namespace: namespace) } - let(:hashed_project) { create(:project, :with_export, :hashed, namespace: namespace) } + let(:legacy_project) { create(:project, :with_export, :legacy_storage, namespace: namespace) } + let(:hashed_project) { create(:project, :with_export, namespace: namespace) } let(:export_path) { Dir.mktmpdir('namespace_remove_exports_spec') } let(:legacy_export) { legacy_project.export_project_path } let(:hashed_export) { hashed_project.export_project_path } diff --git a/spec/models/project_auto_devops_spec.rb b/spec/models/project_auto_devops_spec.rb index 12069575866..296b91a771c 100644 --- a/spec/models/project_auto_devops_spec.rb +++ b/spec/models/project_auto_devops_spec.rb @@ -18,7 +18,21 @@ describe ProjectAutoDevops do context 'when domain is empty' do let(:auto_devops) { build_stubbed(:project_auto_devops, project: project, domain: '') } - it { expect(auto_devops).not_to have_domain } + context 'when there is an instance domain specified' do + before do + allow(Gitlab::CurrentSettings).to receive(:auto_devops_domain).and_return('example.com') + end + + it { expect(auto_devops).to have_domain } + end + + context 'when there is no instance domain specified' do + before do + allow(Gitlab::CurrentSettings).to receive(:auto_devops_domain).and_return(nil) + end + + it { expect(auto_devops).not_to have_domain } + end end end @@ -29,9 +43,32 @@ describe ProjectAutoDevops do let(:domain) { 'example.com' } it 'returns AUTO_DEVOPS_DOMAIN' do - expect(auto_devops.variables).to include( - { key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true }) + expect(auto_devops.variables).to include(domain_variable) end end + + context 'when domain is not defined' do + let(:domain) { nil } + + context 'when there is an instance domain specified' do + before do + allow(Gitlab::CurrentSettings).to receive(:auto_devops_domain).and_return('example.com') + end + + it { expect(auto_devops.variables).to include(domain_variable) } + end + + context 'when there is no instance domain specified' do + before do + allow(Gitlab::CurrentSettings).to receive(:auto_devops_domain).and_return(nil) + end + + it { expect(auto_devops.variables).not_to include(domain_variable) } + end + end + + def domain_variable + { key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true } + end end end diff --git a/spec/models/project_services/prometheus_service_spec.rb b/spec/models/project_services/prometheus_service_spec.rb index bf39e8d7a39..ed17e019d42 100644 --- a/spec/models/project_services/prometheus_service_spec.rb +++ b/spec/models/project_services/prometheus_service_spec.rb @@ -13,17 +13,17 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do end describe 'Validations' do - context 'when service is active' do + context 'when manual_configuration is enabled' do before do - subject.active = true + subject.manual_configuration = true end it { is_expected.to validate_presence_of(:api_url) } end - context 'when service is inactive' do + context 'when manual configuration is disabled' do before do - subject.active = false + subject.manual_configuration = false end it { is_expected.not_to validate_presence_of(:api_url) } @@ -31,12 +31,17 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do end describe '#test' do + before do + service.manual_configuration = true + end + let!(:req_stub) { stub_prometheus_request(prometheus_query_url('1'), body: prometheus_value_body('vector')) } context 'success' do it 'reads the discovery endpoint' do + expect(service.test[:result]).to eq('Checked API endpoint') expect(service.test[:success]).to be_truthy - expect(req_stub).to have_been_requested + expect(req_stub).to have_been_requested.twice end end @@ -70,6 +75,25 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do end end + describe '#matched_metrics' do + let(:matched_metrics_query) { Gitlab::Prometheus::Queries::MatchedMetricsQuery } + let(:client) { double(:client, label_values: nil) } + + context 'with valid data' do + subject { service.matched_metrics } + + before do + allow(service).to receive(:client).and_return(client) + synchronous_reactive_cache(service) + end + + it 'returns reactive data' do + expect(subject[:success]).to be_truthy + expect(subject[:data]).to eq([]) + end + end + end + describe '#deployment_metrics' do let(:deployment) { build_stubbed(:deployment) } let(:deployment_query) { Gitlab::Prometheus::Queries::DeploymentQuery } @@ -83,7 +107,7 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do let(:fake_deployment_time) { 10 } before do - stub_reactive_cache(service, prometheus_data, deployment_query, deployment.id) + stub_reactive_cache(service, prometheus_data, deployment_query, deployment.environment.id, deployment.id) end it 'returns reactive data' do @@ -96,13 +120,17 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do describe '#calculate_reactive_cache' do let(:environment) { create(:environment, slug: 'env-slug') } - - around do |example| - Timecop.freeze { example.run } + before do + service.manual_configuration = true + service.active = true end subject do - service.calculate_reactive_cache(environment_query.to_s, environment.id) + service.calculate_reactive_cache(environment_query.name, environment.id) + end + + around do |example| + Timecop.freeze { example.run } end context 'when service is inactive' do @@ -132,4 +160,193 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do end end end + + describe '#client' do + context 'manual configuration is enabled' do + let(:api_url) { 'http://some_url' } + before do + subject.manual_configuration = true + subject.api_url = api_url + end + + it 'returns simple rest client from api_url' do + expect(subject.client).to be_instance_of(Gitlab::PrometheusClient) + expect(subject.client.rest_client.url).to eq(api_url) + end + end + + context 'manual configuration is disabled' do + let!(:cluster_for_all) { create(:cluster, environment_scope: '*', projects: [project]) } + let!(:cluster_for_dev) { create(:cluster, environment_scope: 'dev', projects: [project]) } + + let!(:prometheus_for_dev) { create(:clusters_applications_prometheus, :installed, cluster: cluster_for_dev) } + let(:proxy_client) { double('proxy_client') } + + before do + service.manual_configuration = false + end + + context 'with cluster for all environments with prometheus installed' do + let!(:prometheus_for_all) { create(:clusters_applications_prometheus, :installed, cluster: cluster_for_all) } + + context 'without environment supplied' do + it 'returns client handling all environments' do + expect(service).to receive(:client_from_cluster).with(cluster_for_all).and_return(proxy_client).twice + + expect(service.client).to be_instance_of(Gitlab::PrometheusClient) + expect(service.client.rest_client).to eq(proxy_client) + end + end + + context 'with dev environment supplied' do + let!(:environment) { create(:environment, project: project, name: 'dev') } + + it 'returns dev cluster client' do + expect(service).to receive(:client_from_cluster).with(cluster_for_dev).and_return(proxy_client).twice + + expect(service.client(environment.id)).to be_instance_of(Gitlab::PrometheusClient) + expect(service.client(environment.id).rest_client).to eq(proxy_client) + end + end + + context 'with prod environment supplied' do + let!(:environment) { create(:environment, project: project, name: 'prod') } + + it 'returns dev cluster client' do + expect(service).to receive(:client_from_cluster).with(cluster_for_all).and_return(proxy_client).twice + + expect(service.client(environment.id)).to be_instance_of(Gitlab::PrometheusClient) + expect(service.client(environment.id).rest_client).to eq(proxy_client) + end + end + end + + context 'with cluster for all environments without prometheus installed' do + context 'without environment supplied' do + it 'raises PrometheusError because cluster was not found' do + expect { service.client }.to raise_error(Gitlab::PrometheusError, /couldn't find cluster with Prometheus installed/) + end + end + + context 'with dev environment supplied' do + let!(:environment) { create(:environment, project: project, name: 'dev') } + + it 'returns dev cluster client' do + expect(service).to receive(:client_from_cluster).with(cluster_for_dev).and_return(proxy_client).twice + + expect(service.client(environment.id)).to be_instance_of(Gitlab::PrometheusClient) + expect(service.client(environment.id).rest_client).to eq(proxy_client) + end + end + + context 'with prod environment supplied' do + let!(:environment) { create(:environment, project: project, name: 'prod') } + + it 'raises PrometheusError because cluster was not found' do + expect { service.client }.to raise_error(Gitlab::PrometheusError, /couldn't find cluster with Prometheus installed/) + end + end + end + end + end + + describe '#prometheus_installed?' do + context 'clusters with installed prometheus' do + let!(:cluster) { create(:cluster, projects: [project]) } + let!(:prometheus) { create(:clusters_applications_prometheus, :installed, cluster: cluster) } + + it 'returns true' do + expect(service.prometheus_installed?).to be(true) + end + end + + context 'clusters without prometheus installed' do + let(:cluster) { create(:cluster, projects: [project]) } + let!(:prometheus) { create(:clusters_applications_prometheus, cluster: cluster) } + + it 'returns false' do + expect(service.prometheus_installed?).to be(false) + end + end + + context 'clusters without prometheus' do + let(:cluster) { create(:cluster, projects: [project]) } + + it 'returns false' do + expect(service.prometheus_installed?).to be(false) + end + end + + context 'no clusters' do + it 'returns false' do + expect(service.prometheus_installed?).to be(false) + end + end + end + + describe '#synchronize_service_state! before_save callback' do + context 'no clusters with prometheus are installed' do + context 'when service is inactive' do + before do + service.active = false + end + + it 'activates service when manual_configuration is enabled' do + expect { service.update!(manual_configuration: true) }.to change { service.active }.from(false).to(true) + end + + it 'keeps service inactive when manual_configuration is disabled' do + expect { service.update!(manual_configuration: false) }.not_to change { service.active }.from(false) + end + end + + context 'when service is active' do + before do + service.active = true + end + + it 'keeps the service active when manual_configuration is enabled' do + expect { service.update!(manual_configuration: true) }.not_to change { service.active }.from(true) + end + + it 'inactivates the service when manual_configuration is disabled' do + expect { service.update!(manual_configuration: false) }.to change { service.active }.from(true).to(false) + end + end + end + + context 'with prometheus installed in the cluster' do + before do + allow(service).to receive(:prometheus_installed?).and_return(true) + end + + context 'when service is inactive' do + before do + service.active = false + end + + it 'activates service when manual_configuration is enabled' do + expect { service.update!(manual_configuration: true) }.to change { service.active }.from(false).to(true) + end + + it 'activates service when manual_configuration is disabled' do + expect { service.update!(manual_configuration: false) }.to change { service.active }.from(false).to(true) + end + end + + context 'when service is active' do + before do + service.active = true + end + + it 'keeps service active when manual_configuration is enabled' do + expect { service.update!(manual_configuration: true) }.not_to change { service.active }.from(true) + end + + it 'keeps service active when manual_configuration is disabled' do + expect { service.update!(manual_configuration: false) }.not_to change { service.active }.from(true) + end + end + end + end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index a63f5d6d5a1..ee04d74d848 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -80,6 +80,7 @@ describe Project do it { is_expected.to have_many(:members_and_requesters) } it { is_expected.to have_many(:clusters) } it { is_expected.to have_many(:custom_attributes).class_name('ProjectCustomAttribute') } + it { is_expected.to have_many(:lfs_file_locks) } context 'after initialized' do it "has a project_feature" do @@ -2070,7 +2071,7 @@ describe Project do create(:ci_variable, :protected, value: 'protected', project: project) end - subject { project.secret_variables_for(ref: 'ref') } + subject { project.reload.secret_variables_for(ref: 'ref') } before do stub_application_setting( @@ -2091,7 +2092,7 @@ describe Project do context 'when the ref is a protected branch' do before do - create(:protected_branch, name: 'ref', project: project) + allow(project).to receive(:protected_for?).with('ref').and_return(true) end it_behaves_like 'ref is protected' @@ -2099,7 +2100,7 @@ describe Project do context 'when the ref is a protected tag' do before do - create(:protected_tag, name: 'ref', project: project) + allow(project).to receive(:protected_for?).with('ref').and_return(true) end it_behaves_like 'ref is protected' @@ -2124,6 +2125,8 @@ describe Project do context 'when the ref is a protected branch' do before do + allow(project).to receive(:repository).and_call_original + allow(project).to receive_message_chain(:repository, :branch_exists?).and_return(true) create(:protected_branch, name: 'ref', project: project) end @@ -2134,6 +2137,8 @@ describe Project do context 'when the ref is a protected tag' do before do + allow(project).to receive_message_chain(:repository, :branch_exists?).and_return(false) + allow(project).to receive_message_chain(:repository, :tag_exists?).and_return(true) create(:protected_tag, name: 'ref', project: project) end @@ -2503,6 +2508,7 @@ describe Project do end describe '#remove_exports' do + let(:legacy_project) { create(:project, :legacy_storage, :with_export) } let(:project) { create(:project, :with_export) } it 'removes the exports directory for the project' do @@ -2515,15 +2521,29 @@ describe Project do expect(File.exist?(project.export_path)).to be_falsy end - it 'is a no-op when there is no namespace' do + it 'is a no-op on legacy projects when there is no namespace' do + export_path = legacy_project.export_path + + legacy_project.update_column(:namespace_id, nil) + + expect(FileUtils).not_to receive(:rm_rf).with(export_path) + + legacy_project.remove_exports + + expect(File.exist?(export_path)).to be_truthy + end + + it 'runs on hashed storage projects when there is no namespace' do export_path = project.export_path + project.update_column(:namespace_id, nil) - expect(FileUtils).not_to receive(:rm_rf).with(export_path) + allow(FileUtils).to receive(:rm_rf).and_call_original + expect(FileUtils).to receive(:rm_rf).with(export_path).and_call_original project.remove_exports - expect(File.exist?(export_path)).to be_truthy + expect(File.exist?(export_path)).to be_falsy end it 'is run when the project is destroyed' do @@ -2544,7 +2564,7 @@ describe Project do end context 'legacy storage' do - let(:project) { create(:project, :repository) } + let(:project) { create(:project, :repository, :legacy_storage) } let(:gitlab_shell) { Gitlab::Shell.new } let(:project_storage) { project.send(:storage) } @@ -2718,6 +2738,8 @@ describe Project do let(:project) { create(:project, :repository, skip_disk_validation: true) } let(:gitlab_shell) { Gitlab::Shell.new } let(:hash) { Digest::SHA2.hexdigest(project.id.to_s) } + let(:hashed_prefix) { File.join('@hashed', hash[0..1], hash[2..3]) } + let(:hashed_path) { File.join(hashed_prefix, hash) } before do stub_application_setting(hashed_storage_enabled: true) @@ -2743,14 +2765,12 @@ describe Project do describe '#base_dir' do it 'returns base_dir based on hash of project id' do - expect(project.base_dir).to eq("@hashed/#{hash[0..1]}/#{hash[2..3]}") + expect(project.base_dir).to eq(hashed_prefix) end end describe '#disk_path' do it 'returns disk_path based on hash of project id' do - hashed_path = "@hashed/#{hash[0..1]}/#{hash[2..3]}/#{hash}" - expect(project.disk_path).to eq(hashed_path) end end @@ -2759,7 +2779,7 @@ describe Project do it 'delegates to gitlab_shell to ensure namespace is created' do allow(project).to receive(:gitlab_shell).and_return(gitlab_shell) - expect(gitlab_shell).to receive(:add_namespace).with(project.repository_storage_path, "@hashed/#{hash[0..1]}/#{hash[2..3]}") + expect(gitlab_shell).to receive(:add_namespace).with(project.repository_storage_path, hashed_prefix) project.ensure_storage_path_exists end @@ -3009,18 +3029,40 @@ describe Project do subject { project.auto_devops_variables } - context 'when enabled in settings' do + context 'when enabled in instance settings' do before do stub_application_setting(auto_devops_enabled: true) end context 'when domain is empty' do before do + stub_application_setting(auto_devops_domain: nil) + end + + it 'variables does not include AUTO_DEVOPS_DOMAIN' do + is_expected.not_to include(domain_variable) + end + end + + context 'when domain is configured' do + before do + stub_application_setting(auto_devops_domain: 'example.com') + end + + it 'variables includes AUTO_DEVOPS_DOMAIN' do + is_expected.to include(domain_variable) + end + end + end + + context 'when explicitely enabled' do + context 'when domain is empty' do + before do create(:project_auto_devops, project: project, domain: nil) end - it 'variables are empty' do - is_expected.to be_empty + it 'variables does not include AUTO_DEVOPS_DOMAIN' do + is_expected.not_to include(domain_variable) end end @@ -3029,11 +3071,15 @@ describe Project do create(:project_auto_devops, project: project, domain: 'example.com') end - it "variables are not empty" do - is_expected.not_to be_empty + it 'variables includes AUTO_DEVOPS_DOMAIN' do + is_expected.to include(domain_variable) end end end + + def domain_variable + { key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true } + end end describe '#latest_successful_builds_for' do diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 02a5ee54262..0bc07dc7a85 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -262,6 +262,28 @@ describe Repository do end end + describe '#new_commits' do + let(:new_refs) do + double(:git_rev_list, new_refs: %w[ + c1acaa58bbcbc3eafe538cb8274ba387047b69f8 + 5937ac0a7beb003549fc5fd26fc247adbce4a52e + ]) + end + + it 'delegates to Gitlab::Git::RevList' do + expect(Gitlab::Git::RevList).to receive(:new).with( + repository.raw, + newrev: 'aaaabbbbccccddddeeeeffffgggghhhhiiiijjjj').and_return(new_refs) + + commits = repository.new_commits('aaaabbbbccccddddeeeeffffgggghhhhiiiijjjj') + + expect(commits).to eq([ + repository.commit('c1acaa58bbcbc3eafe538cb8274ba387047b69f8'), + repository.commit('5937ac0a7beb003549fc5fd26fc247adbce4a52e') + ]) + end + end + describe '#commits_by' do set(:project) { create(:project, :repository) } @@ -851,6 +873,18 @@ describe Repository do expect(repository.license_key).to be_nil end + it 'returns nil when the commit SHA does not exist' do + allow(repository.head_commit).to receive(:sha).and_return('1' * 40) + + expect(repository.license_key).to be_nil + end + + it 'returns nil when master does not exist' do + repository.rm_branch(user, 'master') + + expect(repository.license_key).to be_nil + end + it 'returns the license key' do repository.create_file(user, 'LICENSE', Licensee::License.new('mit').content, diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index cb02d526a98..1815696a8a0 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -893,6 +893,14 @@ describe User do end end + describe '.find_for_database_authentication' do + it 'strips whitespace from login' do + user = create(:user) + + expect(described_class.find_for_database_authentication({ login: " #{user.username} " })).to eq user + end + end + describe '.find_by_any_email' do it 'finds by primary email' do user = create(:user, email: 'foo@example.com') @@ -1586,14 +1594,37 @@ describe User do describe '#authorized_groups' do let!(:user) { create(:user) } let!(:private_group) { create(:group) } + let!(:child_group) { create(:group, parent: private_group) } + + let!(:project_group) { create(:group) } + let!(:project) { create(:project, group: project_group) } before do private_group.add_user(user, Gitlab::Access::MASTER) + project.add_master(user) end subject { user.authorized_groups } - it { is_expected.to eq([private_group]) } + it { is_expected.to contain_exactly private_group, project_group } + end + + describe '#membership_groups' do + let!(:user) { create(:user) } + let!(:parent_group) { create(:group) } + let!(:child_group) { create(:group, parent: parent_group) } + + before do + parent_group.add_user(user, Gitlab::Access::MASTER) + end + + subject { user.membership_groups } + + if Group.supports_nested_groups? + it { is_expected.to contain_exactly parent_group, child_group } + else + it { is_expected.to contain_exactly parent_group } + end end describe '#authorized_projects', :delete do diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb index d53ba497ed1..b2b7721674c 100644 --- a/spec/models/wiki_page_spec.rb +++ b/spec/models/wiki_page_spec.rb @@ -188,162 +188,181 @@ describe WikiPage do end end - describe '#create', :skip_gitaly_mock do - context 'with valid attributes' do - it 'raises an error if a page with the same path already exists' do - create_page('New Page', 'content') - create_page('foo/bar', 'content') - expect { create_page('New Page', 'other content') }.to raise_error Gitlab::Git::Wiki::DuplicatePageError - expect { create_page('foo/bar', 'other content') }.to raise_error Gitlab::Git::Wiki::DuplicatePageError - - destroy_page('New Page') - destroy_page('bar', 'foo') - end + describe '#create' do + shared_examples 'create method' do + context 'with valid attributes' do + it 'raises an error if a page with the same path already exists' do + create_page('New Page', 'content') + create_page('foo/bar', 'content') + expect { create_page('New Page', 'other content') }.to raise_error Gitlab::Git::Wiki::DuplicatePageError + expect { create_page('foo/bar', 'other content') }.to raise_error Gitlab::Git::Wiki::DuplicatePageError + + destroy_page('New Page') + destroy_page('bar', 'foo') + end - it 'if the title is preceded by a / it is removed' do - create_page('/New Page', 'content') + it 'if the title is preceded by a / it is removed' do + create_page('/New Page', 'content') - expect(wiki.find_page('New Page')).not_to be_nil + expect(wiki.find_page('New Page')).not_to be_nil - destroy_page('New Page') + destroy_page('New Page') + end end end - end - # Remove skip_gitaly_mock flag when gitaly_update_page implements moving pages - describe "#update", :skip_gitaly_mock do - before do - create_page("Update", "content") - @page = wiki.find_page("Update") + context 'when Gitaly is enabled' do + it_behaves_like 'create method' end - after do - destroy_page(@page.title, @page.directory) + context 'when Gitaly is disabled', :skip_gitaly_mock do + it_behaves_like 'create method' end + end - context "with valid attributes" do - it "updates the content of the page" do - new_content = "new content" - - @page.update(content: new_content) + describe "#update" do + shared_examples 'update method' do + before do + create_page("Update", "content") @page = wiki.find_page("Update") + end - expect(@page.content).to eq("new content") + after do + destroy_page(@page.title, @page.directory) end - it "updates the title of the page" do - new_title = "Index v.1.2.4" + context "with valid attributes" do + it "updates the content of the page" do + new_content = "new content" - @page.update(title: new_title) - @page = wiki.find_page(new_title) + @page.update(content: new_content) + @page = wiki.find_page("Update") - expect(@page.title).to eq(new_title) - end + expect(@page.content).to eq("new content") + end - it "returns true" do - expect(@page.update(content: "more content")).to be_truthy + it "updates the title of the page" do + new_title = "Index v.1.2.4" + + @page.update(title: new_title) + @page = wiki.find_page(new_title) + + expect(@page.title).to eq(new_title) + end + + it "returns true" do + expect(@page.update(content: "more content")).to be_truthy + end end - end - context 'with same last commit sha' do - it 'returns true' do - expect(@page.update(content: 'more content', last_commit_sha: @page.last_commit_sha)).to be_truthy + context 'with same last commit sha' do + it 'returns true' do + expect(@page.update(content: 'more content', last_commit_sha: @page.last_commit_sha)).to be_truthy + end end - end - context 'with different last commit sha' do - it 'raises exception' do - expect { @page.update(content: 'more content', last_commit_sha: 'xxx') }.to raise_error(WikiPage::PageChangedError) + context 'with different last commit sha' do + it 'raises exception' do + expect { @page.update(content: 'more content', last_commit_sha: 'xxx') }.to raise_error(WikiPage::PageChangedError) + end end - end - context 'when renaming a page' do - it 'raises an error if the page already exists' do - create_page('Existing Page', 'content') + context 'when renaming a page' do + it 'raises an error if the page already exists' do + create_page('Existing Page', 'content') - expect { @page.update(title: 'Existing Page', content: 'new_content') }.to raise_error(WikiPage::PageRenameError) - expect(@page.title).to eq 'Update' - expect(@page.content).to eq 'new_content' + expect { @page.update(title: 'Existing Page', content: 'new_content') }.to raise_error(WikiPage::PageRenameError) + expect(@page.title).to eq 'Update' + expect(@page.content).to eq 'new_content' - destroy_page('Existing Page') - end + destroy_page('Existing Page') + end - it 'updates the content and rename the file' do - new_title = 'Renamed Page' - new_content = 'updated content' + it 'updates the content and rename the file' do + new_title = 'Renamed Page' + new_content = 'updated content' - expect(@page.update(title: new_title, content: new_content)).to be_truthy + expect(@page.update(title: new_title, content: new_content)).to be_truthy - @page = wiki.find_page(new_title) + @page = wiki.find_page(new_title) - expect(@page).not_to be_nil - expect(@page.content).to eq new_content + expect(@page).not_to be_nil + expect(@page.content).to eq new_content + end end - end - - context 'when moving a page' do - it 'raises an error if the page already exists' do - create_page('foo/Existing Page', 'content') - expect { @page.update(title: 'foo/Existing Page', content: 'new_content') }.to raise_error(WikiPage::PageRenameError) - expect(@page.title).to eq 'Update' - expect(@page.content).to eq 'new_content' + context 'when moving a page' do + it 'raises an error if the page already exists' do + create_page('foo/Existing Page', 'content') - destroy_page('Existing Page', 'foo') - end + expect { @page.update(title: 'foo/Existing Page', content: 'new_content') }.to raise_error(WikiPage::PageRenameError) + expect(@page.title).to eq 'Update' + expect(@page.content).to eq 'new_content' - it 'updates the content and moves the file' do - new_title = 'foo/Other Page' - new_content = 'new_content' + destroy_page('Existing Page', 'foo') + end - expect(@page.update(title: new_title, content: new_content)).to be_truthy + it 'updates the content and moves the file' do + new_title = 'foo/Other Page' + new_content = 'new_content' - page = wiki.find_page(new_title) + expect(@page.update(title: new_title, content: new_content)).to be_truthy - expect(page).not_to be_nil - expect(page.content).to eq new_content - end + page = wiki.find_page(new_title) - context 'in subdir' do - before do - create_page('foo/Existing Page', 'content') - @page = wiki.find_page('foo/Existing Page') + expect(page).not_to be_nil + expect(page.content).to eq new_content end - it 'moves the page to the root folder if the title is preceded by /' do - expect(@page.slug).to eq 'foo/Existing-Page' - expect(@page.update(title: '/Existing Page', content: 'new_content')).to be_truthy - expect(@page.slug).to eq 'Existing-Page' + context 'in subdir' do + before do + create_page('foo/Existing Page', 'content') + @page = wiki.find_page('foo/Existing Page') + end + + it 'moves the page to the root folder if the title is preceded by /', :skip_gitaly_mock do + expect(@page.slug).to eq 'foo/Existing-Page' + expect(@page.update(title: '/Existing Page', content: 'new_content')).to be_truthy + expect(@page.slug).to eq 'Existing-Page' + end + + it 'does nothing if it has the same title' do + original_path = @page.slug + + expect(@page.update(title: 'Existing Page', content: 'new_content')).to be_truthy + expect(@page.slug).to eq original_path + end end - it 'does nothing if it has the same title' do - original_path = @page.slug + context 'in root dir' do + it 'does nothing if the title is preceded by /' do + original_path = @page.slug - expect(@page.update(title: 'Existing Page', content: 'new_content')).to be_truthy - expect(@page.slug).to eq original_path + expect(@page.update(title: '/Update', content: 'new_content')).to be_truthy + expect(@page.slug).to eq original_path + end end end - context 'in root dir' do - it 'does nothing if the title is preceded by /' do - original_path = @page.slug + context "with invalid attributes" do + it 'aborts update if title blank' do + expect(@page.update(title: '', content: 'new_content')).to be_falsey + expect(@page.content).to eq 'new_content' - expect(@page.update(title: '/Update', content: 'new_content')).to be_truthy - expect(@page.slug).to eq original_path + page = wiki.find_page('Update') + expect(page.content).to eq 'content' + + @page.title = 'Update' end end end - context "with invalid attributes" do - it 'aborts update if title blank' do - expect(@page.update(title: '', content: 'new_content')).to be_falsey - expect(@page.content).to eq 'new_content' - - page = wiki.find_page('Update') - expect(page.content).to eq 'content' + context 'when Gitaly is enabled' do + it_behaves_like 'update method' + end - @page.title = 'Update' - end + context 'when Gitaly is disabled', :skip_gitaly_mock do + it_behaves_like 'update method' end end diff --git a/spec/policies/personal_snippet_policy_spec.rb b/spec/policies/personal_snippet_policy_spec.rb index b70c8646a3d..50bb0899eba 100644 --- a/spec/policies/personal_snippet_policy_spec.rb +++ b/spec/policies/personal_snippet_policy_spec.rb @@ -1,5 +1,6 @@ require 'spec_helper' +# Snippet visibility scenarios are included in more details in spec/support/snippet_visibility.rb describe PersonalSnippetPolicy do let(:regular_user) { create(:user) } let(:external_user) { create(:user, :external) } diff --git a/spec/policies/project_snippet_policy_spec.rb b/spec/policies/project_snippet_policy_spec.rb index cdba1b09fc1..4d32e06b553 100644 --- a/spec/policies/project_snippet_policy_spec.rb +++ b/spec/policies/project_snippet_policy_spec.rb @@ -1,5 +1,6 @@ require 'spec_helper' +# Snippet visibility scenarios are included in more details in spec/support/snippet_visibility.rb describe ProjectSnippetPolicy do let(:regular_user) { create(:user) } let(:external_user) { create(:user, :external) } diff --git a/spec/presenters/ci/group_variable_presenter_spec.rb b/spec/presenters/ci/group_variable_presenter_spec.rb index d404028405b..cb58a757564 100644 --- a/spec/presenters/ci/group_variable_presenter_spec.rb +++ b/spec/presenters/ci/group_variable_presenter_spec.rb @@ -35,29 +35,20 @@ describe Ci::GroupVariablePresenter do end describe '#form_path' do - context 'when variable is persisted' do - subject { described_class.new(variable).form_path } + subject { described_class.new(variable).form_path } - it { is_expected.to eq(group_variable_path(group, variable)) } - end - - context 'when variable is not persisted' do - let(:variable) { build(:ci_group_variable, group: group) } - subject { described_class.new(variable).form_path } - - it { is_expected.to eq(group_variables_path(group)) } - end + it { is_expected.to eq(group_settings_ci_cd_path(group)) } end describe '#edit_path' do subject { described_class.new(variable).edit_path } - it { is_expected.to eq(group_variable_path(group, variable)) } + it { is_expected.to eq(group_variables_path(group)) } end describe '#delete_path' do subject { described_class.new(variable).delete_path } - it { is_expected.to eq(group_variable_path(group, variable)) } + it { is_expected.to eq(group_variables_path(group)) } end end diff --git a/spec/presenters/ci/variable_presenter_spec.rb b/spec/presenters/ci/variable_presenter_spec.rb index db62f86edb0..e3ce88372ea 100644 --- a/spec/presenters/ci/variable_presenter_spec.rb +++ b/spec/presenters/ci/variable_presenter_spec.rb @@ -35,29 +35,20 @@ describe Ci::VariablePresenter do end describe '#form_path' do - context 'when variable is persisted' do - subject { described_class.new(variable).form_path } + subject { described_class.new(variable).form_path } - it { is_expected.to eq(project_variable_path(project, variable)) } - end - - context 'when variable is not persisted' do - let(:variable) { build(:ci_variable, project: project) } - subject { described_class.new(variable).form_path } - - it { is_expected.to eq(project_variables_path(project)) } - end + it { is_expected.to eq(project_settings_ci_cd_path(project)) } end describe '#edit_path' do subject { described_class.new(variable).edit_path } - it { is_expected.to eq(project_variable_path(project, variable)) } + it { is_expected.to eq(project_variables_path(project)) } end describe '#delete_path' do subject { described_class.new(variable).delete_path } - it { is_expected.to eq(project_variable_path(project, variable)) } + it { is_expected.to eq(project_variables_path(project)) } end end diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index ff5f207487b..31959d28fee 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -465,6 +465,72 @@ describe API::Commits do end end + describe 'GET /projects/:id/repository/commits/:sha/refs' do + let(:project) { create(:project, :public, :repository) } + let(:tag) { project.repository.find_tag('v1.1.0') } + let(:commit_id) { tag.dereferenced_target.id } + let(:route) { "/projects/#{project_id}/repository/commits/#{commit_id}/refs" } + + context 'when ref does not exist' do + let(:commit_id) { 'unknown' } + + it_behaves_like '404 response' do + let(:request) { get api(route, current_user) } + let(:message) { '404 Commit Not Found' } + end + end + + context 'when repository is disabled' do + include_context 'disabled repository' + + it_behaves_like '403 response' do + let(:request) { get api(route, current_user) } + end + end + + context 'for a valid commit' do + it 'returns all refs with no scope' do + get api(route, current_user), per_page: 100 + + refs = project.repository.branch_names_contains(commit_id).map {|name| ['branch', name]} + refs.concat(project.repository.tag_names_contains(commit_id).map {|name| ['tag', name]}) + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.map { |r| [r['type'], r['name']] }.compact).to eq(refs) + end + + it 'returns all refs' do + get api(route, current_user), type: 'all', per_page: 100 + + refs = project.repository.branch_names_contains(commit_id).map {|name| ['branch', name]} + refs.concat(project.repository.tag_names_contains(commit_id).map {|name| ['tag', name]}) + + expect(response).to have_gitlab_http_status(200) + expect(json_response.map { |r| [r['type'], r['name']] }.compact).to eq(refs) + end + + it 'returns the branch refs' do + get api(route, current_user), type: 'branch', per_page: 100 + + refs = project.repository.branch_names_contains(commit_id).map {|name| ['branch', name]} + + expect(response).to have_gitlab_http_status(200) + expect(json_response.map { |r| [r['type'], r['name']] }.compact).to eq(refs) + end + + it 'returns the tag refs' do + get api(route, current_user), type: 'tag', per_page: 100 + + refs = project.repository.tag_names_contains(commit_id).map {|name| ['tag', name]} + + expect(response).to have_gitlab_http_status(200) + expect(json_response.map { |r| [r['type'], r['name']] }.compact).to eq(refs) + end + end + end + describe 'GET /projects/:id/repository/commits/:sha' do let(:commit) { project.repository.commit } let(:commit_id) { commit.id } diff --git a/spec/requests/api/group_variables_spec.rb b/spec/requests/api/group_variables_spec.rb index a4f198eb5c9..64fa7dc824c 100644 --- a/spec/requests/api/group_variables_spec.rb +++ b/spec/requests/api/group_variables_spec.rb @@ -142,12 +142,12 @@ describe API::GroupVariables do end it 'updates variable data' do - initial_variable = group.variables.first + initial_variable = group.variables.reload.first value_before = initial_variable.value put api("/groups/#{group.id}/variables/#{variable.key}", user), value: 'VALUE_1_UP', protected: true - updated_variable = group.variables.first + updated_variable = group.variables.reload.first expect(response).to have_gitlab_http_status(200) expect(value_before).to eq(variable.value) diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index ea6b0a71849..c7df6251d74 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -366,20 +366,9 @@ describe API::Internal do end end - context 'project as /namespace/project' do - it do - push(key, project_with_repo_path('/' + project.full_path)) - - expect(response).to have_gitlab_http_status(200) - expect(json_response["status"]).to be_truthy - expect(json_response["repository_path"]).to eq(project.repository.path_to_repo) - expect(json_response["gl_repository"]).to eq("project-#{project.id}") - end - end - context 'project as namespace/project' do it do - push(key, project_with_repo_path(project.full_path)) + push(key, project) expect(response).to have_gitlab_http_status(200) expect(json_response["status"]).to be_truthy @@ -496,8 +485,10 @@ describe API::Internal do end context 'project does not exist' do - it do - pull(key, project_with_repo_path('gitlab/notexist')) + it 'returns a 200 response with status: false' do + project.destroy + + pull(key, project) expect(response).to have_gitlab_http_status(200) expect(json_response["status"]).to be_falsey @@ -569,6 +560,7 @@ describe API::Internal do end context 'the project path was changed' do + let(:project) { create(:project, :repository, :legacy_storage) } let!(:old_path_to_repo) { project.repository.path_to_repo } let!(:repository) { project.repository } @@ -858,9 +850,14 @@ describe API::Internal do end end - def project_with_repo_path(path) - double().tap do |fake_project| - allow(fake_project).to receive_message_chain('repository.path_to_repo' => path) + def gl_repository_for(project_or_wiki) + case project_or_wiki + when ProjectWiki + project_or_wiki.project.gl_repository(is_wiki: true) + when Project + project_or_wiki.gl_repository(is_wiki: false) + else + nil end end @@ -868,18 +865,8 @@ describe API::Internal do post( api("/internal/allowed"), key_id: key.id, - project: project.repository.path_to_repo, - action: 'git-upload-pack', - secret_token: secret_token, - protocol: protocol - ) - end - - def pull_with_path(key, path_to_repo, protocol = 'ssh') - post( - api("/internal/allowed"), - key_id: key.id, - project: path_to_repo, + project: project.full_path, + gl_repository: gl_repository_for(project), action: 'git-upload-pack', secret_token: secret_token, protocol: protocol @@ -891,20 +878,8 @@ describe API::Internal do api("/internal/allowed"), changes: 'd14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/master', key_id: key.id, - project: project.repository.path_to_repo, - action: 'git-receive-pack', - secret_token: secret_token, - protocol: protocol, - env: env - ) - end - - def push_with_path(key, path_to_repo, protocol = 'ssh', env: nil) - post( - api("/internal/allowed"), - changes: 'd14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/master', - key_id: key.id, - project: path_to_repo, + project: project.full_path, + gl_repository: gl_repository_for(project), action: 'git-receive-pack', secret_token: secret_token, protocol: protocol, @@ -917,7 +892,8 @@ describe API::Internal do api("/internal/allowed"), ref: 'master', key_id: key.id, - project: project.repository.path_to_repo, + project: project.full_path, + gl_repository: gl_repository_for(project), action: 'git-upload-archive', secret_token: secret_token, protocol: 'ssh' @@ -929,7 +905,7 @@ describe API::Internal do api("/internal/lfs_authenticate"), key_id: key_id, secret_token: secret_token, - project: project.repository.path_to_repo + project: project.full_path ) end end diff --git a/spec/requests/api/project_import_spec.rb b/spec/requests/api/project_import_spec.rb new file mode 100644 index 00000000000..987f6e26971 --- /dev/null +++ b/spec/requests/api/project_import_spec.rb @@ -0,0 +1,102 @@ +require 'spec_helper' + +describe API::ProjectImport do + let(:export_path) { "#{Dir.tmpdir}/project_export_spec" } + let(:user) { create(:user) } + let(:file) { File.join(Rails.root, 'spec', 'features', 'projects', 'import_export', 'test_project_export.tar.gz') } + let(:namespace) { create(:group) } + before do + allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) + + namespace.add_owner(user) + end + + after do + FileUtils.rm_rf(export_path, secure: true) + end + + describe 'POST /projects/import' do + it 'schedules an import using a namespace' do + stub_import(namespace) + + post api('/projects/import', user), path: 'test-import', file: fixture_file_upload(file), namespace: namespace.id + + expect(response).to have_gitlab_http_status(201) + end + + it 'schedules an import using the namespace path' do + stub_import(namespace) + + post api('/projects/import', user), path: 'test-import', file: fixture_file_upload(file), namespace: namespace.full_path + + expect(response).to have_gitlab_http_status(201) + end + + it 'schedules an import at the user namespace level' do + stub_import(user.namespace) + + post api('/projects/import', user), path: 'test-import2', file: fixture_file_upload(file) + + expect(response).to have_gitlab_http_status(201) + end + + it 'schedules an import at the user namespace level' do + expect_any_instance_of(Project).not_to receive(:import_schedule) + expect(::Projects::CreateService).not_to receive(:new) + + post api('/projects/import', user), namespace: 'nonexistent', path: 'test-import2', file: fixture_file_upload(file) + + expect(response).to have_gitlab_http_status(404) + expect(json_response['message']).to eq('404 Namespace Not Found') + end + + it 'does not schedule an import if the user has no permission to the namespace' do + expect_any_instance_of(Project).not_to receive(:import_schedule) + + post(api('/projects/import', create(:user)), + path: 'test-import3', + file: fixture_file_upload(file), + namespace: namespace.full_path) + + expect(response).to have_gitlab_http_status(404) + expect(json_response['message']).to eq('404 Namespace Not Found') + end + + it 'does not schedule an import if the user uploads no valid file' do + expect_any_instance_of(Project).not_to receive(:import_schedule) + + post api('/projects/import', user), path: 'test-import3', file: './random/test' + + expect(response).to have_gitlab_http_status(400) + expect(json_response['error']).to eq('file is invalid') + end + + def stub_import(namespace) + expect_any_instance_of(Project).to receive(:import_schedule) + expect(::Projects::CreateService).to receive(:new).with(user, hash_including(namespace_id: namespace.id)).and_call_original + end + end + + describe 'GET /projects/:id/import' do + it 'returns the import status' do + project = create(:project, import_status: 'started') + project.add_master(user) + + get api("/projects/#{project.id}/import", user) + + expect(response).to have_gitlab_http_status(200) + expect(json_response).to include('import_status' => 'started') + end + + it 'returns the import status and the error if failed' do + project = create(:project, import_status: 'failed', import_error: 'error') + project.add_master(user) + + get api("/projects/#{project.id}/import", user) + + expect(response).to have_gitlab_http_status(200) + expect(json_response).to include('import_status' => 'failed', + 'import_error' => 'error') + end + end +end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index f11cd638d96..00dd8897e6a 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -460,7 +460,7 @@ describe API::Projects do expect(response).to have_gitlab_http_status(201) project.each_pair do |k, v| - next if %i[has_external_issue_tracker issues_enabled merge_requests_enabled wiki_enabled].include?(k) + next if %i[has_external_issue_tracker issues_enabled merge_requests_enabled wiki_enabled storage_version].include?(k) expect(json_response[k.to_s]).to eq(v) end @@ -622,12 +622,8 @@ describe API::Projects do end describe 'POST /projects/user/:id' do - before do - expect(project).to be_persisted - end - it 'creates new project without path but with name and return 201' do - expect { post api("/projects/user/#{user.id}", admin), name: 'Foo Project' }.to change {Project.count}.by(1) + expect { post api("/projects/user/#{user.id}", admin), name: 'Foo Project' }.to change { Project.count }.by(1) expect(response).to have_gitlab_http_status(201) project = Project.last @@ -666,8 +662,9 @@ describe API::Projects do post api("/projects/user/#{user.id}", admin), project expect(response).to have_gitlab_http_status(201) + project.each_pair do |k, v| - next if %i[has_external_issue_tracker path].include?(k) + next if %i[has_external_issue_tracker path storage_version].include?(k) expect(json_response[k.to_s]).to eq(v) end diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index 0bd88748479..f10b6e43d09 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -8,6 +8,7 @@ describe API::Runner do before do stub_gitlab_calls stub_application_setting(runners_registration_token: registration_token) + allow_any_instance_of(Ci::Runner).to receive(:cache_attributes) end describe '/api/v4/runners' do @@ -408,7 +409,7 @@ describe API::Runner do expect { request_job }.to change { runner.reload.contacted_at } end - %w(name version revision platform architecture).each do |param| + %w(version revision platform architecture).each do |param| context "when info parameter '#{param}' is present" do let(:value) { "#{param}_value" } diff --git a/spec/requests/api/search_spec.rb b/spec/requests/api/search_spec.rb new file mode 100644 index 00000000000..9052a18c60b --- /dev/null +++ b/spec/requests/api/search_spec.rb @@ -0,0 +1,318 @@ +require 'spec_helper' + +describe API::Search do + set(:user) { create(:user) } + set(:group) { create(:group) } + set(:project) { create(:project, :public, name: 'awesome project', group: group) } + set(:repo_project) { create(:project, :public, :repository, group: group) } + + shared_examples 'response is correct' do |schema:, size: 1| + it { expect(response).to have_gitlab_http_status(200) } + it { expect(response).to match_response_schema(schema) } + it { expect(response).to include_limited_pagination_headers } + it { expect(json_response.size).to eq(size) } + end + + describe 'GET /search' do + context 'when user is not authenticated' do + it 'returns 401 error' do + get api('/search'), scope: 'projects', search: 'awesome' + + expect(response).to have_gitlab_http_status(401) + end + end + + context 'when scope is not supported' do + it 'returns 400 error' do + get api('/search', user), scope: 'unsupported', search: 'awesome' + + expect(response).to have_gitlab_http_status(400) + end + end + + context 'when scope is missing' do + it 'returns 400 error' do + get api('/search', user), search: 'awesome' + + expect(response).to have_gitlab_http_status(400) + end + end + + context 'with correct params' do + context 'for projects scope' do + before do + get api('/search', user), scope: 'projects', search: 'awesome' + end + + it_behaves_like 'response is correct', schema: 'public_api/v4/projects' + end + + context 'for issues scope' do + before do + create(:issue, project: project, title: 'awesome issue') + + get api('/search', user), scope: 'issues', search: 'awesome' + end + + it_behaves_like 'response is correct', schema: 'public_api/v4/issues' + end + + context 'for merge_requests scope' do + before do + create(:merge_request, source_project: repo_project, title: 'awesome mr') + + get api('/search', user), scope: 'merge_requests', search: 'awesome' + end + + it_behaves_like 'response is correct', schema: 'public_api/v4/merge_requests' + end + + context 'for milestones scope' do + before do + create(:milestone, project: project, title: 'awesome milestone') + + get api('/search', user), scope: 'milestones', search: 'awesome' + end + + it_behaves_like 'response is correct', schema: 'public_api/v4/milestones' + end + + context 'for snippet_titles scope' do + before do + create(:snippet, :public, title: 'awesome snippet', content: 'snippet content') + + get api('/search', user), scope: 'snippet_titles', search: 'awesome' + end + + it_behaves_like 'response is correct', schema: 'public_api/v4/snippets' + end + + context 'for snippet_blobs scope' do + before do + create(:snippet, :public, title: 'awesome snippet', content: 'snippet content') + + get api('/search', user), scope: 'snippet_blobs', search: 'content' + end + + it_behaves_like 'response is correct', schema: 'public_api/v4/snippets' + end + end + end + + describe "GET /groups/:id/-/search" do + context 'when user is not authenticated' do + it 'returns 401 error' do + get api("/groups/#{group.id}/-/search"), scope: 'projects', search: 'awesome' + + expect(response).to have_gitlab_http_status(401) + end + end + + context 'when scope is not supported' do + it 'returns 400 error' do + get api("/groups/#{group.id}/-/search", user), scope: 'unsupported', search: 'awesome' + + expect(response).to have_gitlab_http_status(400) + end + end + + context 'when scope is missing' do + it 'returns 400 error' do + get api("/groups/#{group.id}/-/search", user), search: 'awesome' + + expect(response).to have_gitlab_http_status(400) + end + end + + context 'when group does not exist' do + it 'returns 404 error' do + get api('/groups/9999/-/search', user), scope: 'issues', search: 'awesome' + + expect(response).to have_gitlab_http_status(404) + end + end + + context 'when user does can not see the group' do + it 'returns 404 error' do + private_group = create(:group, :private) + + get api("/groups/#{private_group.id}/-/search", user), scope: 'issues', search: 'awesome' + + expect(response).to have_gitlab_http_status(404) + end + end + + context 'with correct params' do + context 'for projects scope' do + before do + get api("/groups/#{group.id}/-/search", user), scope: 'projects', search: 'awesome' + end + + it_behaves_like 'response is correct', schema: 'public_api/v4/projects' + end + + context 'for issues scope' do + before do + create(:issue, project: project, title: 'awesome issue') + + get api("/groups/#{group.id}/-/search", user), scope: 'issues', search: 'awesome' + end + + it_behaves_like 'response is correct', schema: 'public_api/v4/issues' + end + + context 'for merge_requests scope' do + before do + create(:merge_request, source_project: repo_project, title: 'awesome mr') + + get api("/groups/#{group.id}/-/search", user), scope: 'merge_requests', search: 'awesome' + end + + it_behaves_like 'response is correct', schema: 'public_api/v4/merge_requests' + end + + context 'for milestones scope' do + before do + create(:milestone, project: project, title: 'awesome milestone') + + get api("/groups/#{group.id}/-/search", user), scope: 'milestones', search: 'awesome' + end + + it_behaves_like 'response is correct', schema: 'public_api/v4/milestones' + end + + context 'for milestones scope with group path as id' do + before do + another_project = create(:project, :public) + create(:milestone, project: project, title: 'awesome milestone') + create(:milestone, project: another_project, title: 'awesome milestone other project') + + get api("/groups/#{CGI.escape(group.full_path)}/-/search", user), scope: 'milestones', search: 'awesome' + end + + it_behaves_like 'response is correct', schema: 'public_api/v4/milestones' + end + end + end + + describe "GET /projects/:id/search" do + context 'when user is not authenticated' do + it 'returns 401 error' do + get api("/projects/#{project.id}/-/search"), scope: 'issues', search: 'awesome' + + expect(response).to have_gitlab_http_status(401) + end + end + + context 'when scope is not supported' do + it 'returns 400 error' do + get api("/projects/#{project.id}/-/search", user), scope: 'unsupported', search: 'awesome' + + expect(response).to have_gitlab_http_status(400) + end + end + + context 'when scope is missing' do + it 'returns 400 error' do + get api("/projects/#{project.id}/-/search", user), search: 'awesome' + + expect(response).to have_gitlab_http_status(400) + end + end + + context 'when project does not exist' do + it 'returns 404 error' do + get api('/projects/9999/-/search', user), scope: 'issues', search: 'awesome' + + expect(response).to have_gitlab_http_status(404) + end + end + + context 'when user does can not see the project' do + it 'returns 404 error' do + project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + + get api("/projects/#{project.id}/-/search", user), scope: 'issues', search: 'awesome' + + expect(response).to have_gitlab_http_status(404) + end + end + + context 'with correct params' do + context 'for issues scope' do + before do + create(:issue, project: project, title: 'awesome issue') + + get api("/projects/#{project.id}/-/search", user), scope: 'issues', search: 'awesome' + end + + it_behaves_like 'response is correct', schema: 'public_api/v4/issues' + end + + context 'for merge_requests scope' do + before do + create(:merge_request, source_project: repo_project, title: 'awesome mr') + + get api("/projects/#{repo_project.id}/-/search", user), scope: 'merge_requests', search: 'awesome' + end + + it_behaves_like 'response is correct', schema: 'public_api/v4/merge_requests' + end + + context 'for milestones scope' do + before do + create(:milestone, project: project, title: 'awesome milestone') + + get api("/projects/#{project.id}/-/search", user), scope: 'milestones', search: 'awesome' + end + + it_behaves_like 'response is correct', schema: 'public_api/v4/milestones' + end + + context 'for notes scope' do + before do + create(:note_on_merge_request, project: project, note: 'awesome note') + + get api("/projects/#{project.id}/-/search", user), scope: 'notes', search: 'awesome' + end + + it_behaves_like 'response is correct', schema: 'public_api/v4/notes' + end + + context 'for wiki_blobs scope' do + before do + wiki = create(:project_wiki, project: project) + create(:wiki_page, wiki: wiki, attrs: { title: 'home', content: "Awesome page" }) + + get api("/projects/#{project.id}/-/search", user), scope: 'wiki_blobs', search: 'awesome' + end + + it_behaves_like 'response is correct', schema: 'public_api/v4/blobs' + end + + context 'for commits scope' do + before do + get api("/projects/#{repo_project.id}/-/search", user), scope: 'commits', search: '498214de67004b1da3d820901307bed2a68a8ef6' + end + + it_behaves_like 'response is correct', schema: 'public_api/v4/commits_details' + end + + context 'for commits scope with project path as id' do + before do + get api("/projects/#{CGI.escape(repo_project.full_path)}/-/search", user), scope: 'commits', search: '498214de67004b1da3d820901307bed2a68a8ef6' + end + + it_behaves_like 'response is correct', schema: 'public_api/v4/commits_details' + end + + context 'for blobs scope' do + before do + get api("/projects/#{repo_project.id}/-/search", user), scope: 'blobs', search: 'monitors' + end + + it_behaves_like 'response is correct', schema: 'public_api/v4/blobs', size: 2 + end + end + end +end diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb index 74198c8eb4f..b3e253befc6 100644 --- a/spec/requests/api/snippets_spec.rb +++ b/spec/requests/api/snippets_spec.rb @@ -32,6 +32,27 @@ describe API::Snippets do expect(json_response).to be_an Array expect(json_response.size).to eq(0) end + + it 'returns 404 for non-authenticated' do + create(:personal_snippet, :internal) + + get api("/snippets/") + + expect(response).to have_gitlab_http_status(401) + end + + it 'does not return snippets related to a project with disable feature visibility' do + project = create(:project) + create(:project_member, project: project, user: user) + public_snippet = create(:personal_snippet, :public, author: user, project: project) + project.project_feature.update_attribute(:snippets_access_level, 0) + + get api("/snippets/", user) + + json_response.each do |snippet| + expect(snippet["id"]).not_to eq(public_snippet.id) + end + end end describe 'GET /snippets/public' do diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb index fb3a33cadff..2ee8d150dc8 100644 --- a/spec/requests/api/todos_spec.rb +++ b/spec/requests/api/todos_spec.rb @@ -129,6 +129,12 @@ describe API::Todos do post api("/todos/#{pending_1.id}/mark_as_done", john_doe) end + + it 'returns 404 if the todo does not belong to the current user' do + post api("/todos/#{pending_1.id}/mark_as_done", author_1) + + expect(response.status).to eq(404) + end end end diff --git a/spec/requests/api/v3/projects_spec.rb b/spec/requests/api/v3/projects_spec.rb index 5d99d9495f3..bf36d3e245a 100644 --- a/spec/requests/api/v3/projects_spec.rb +++ b/spec/requests/api/v3/projects_spec.rb @@ -401,7 +401,7 @@ describe API::V3::Projects do post v3_api('/projects', user), project project.each_pair do |k, v| - next if %i[has_external_issue_tracker issues_enabled merge_requests_enabled wiki_enabled].include?(k) + next if %i[storage_version has_external_issue_tracker issues_enabled merge_requests_enabled wiki_enabled].include?(k) expect(json_response[k.to_s]).to eq(v) end @@ -545,7 +545,7 @@ describe API::V3::Projects do expect(response).to have_gitlab_http_status(201) project.each_pair do |k, v| - next if %i[has_external_issue_tracker path].include?(k) + next if %i[storage_version has_external_issue_tracker path].include?(k) expect(json_response[k.to_s]).to eq(v) end diff --git a/spec/requests/api/v3/todos_spec.rb b/spec/requests/api/v3/todos_spec.rb index 53fd962272a..ea648e3917f 100644 --- a/spec/requests/api/v3/todos_spec.rb +++ b/spec/requests/api/v3/todos_spec.rb @@ -38,6 +38,12 @@ describe API::V3::Todos do delete v3_api("/todos/#{pending_1.id}", john_doe) end + + it 'returns 404 if the todo does not belong to the current user' do + delete v3_api("/todos/#{pending_1.id}", author_1) + + expect(response.status).to eq(404) + end end end diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb index 79ee6c126f6..62215ea3d7d 100644 --- a/spec/requests/api/variables_spec.rb +++ b/spec/requests/api/variables_spec.rb @@ -122,12 +122,12 @@ describe API::Variables do describe 'PUT /projects/:id/variables/:key' do context 'authorized user with proper permissions' do it 'updates variable data' do - initial_variable = project.variables.first + initial_variable = project.variables.reload.first value_before = initial_variable.value put api("/projects/#{project.id}/variables/#{variable.key}", user), value: 'VALUE_1_UP', protected: true - updated_variable = project.variables.first + updated_variable = project.variables.reload.first expect(response).to have_gitlab_http_status(200) expect(value_before).to eq(variable.value) diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 2e2dccdafad..942e5b2bb1b 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -163,7 +163,7 @@ describe 'Git HTTP requests' do download(path) do |response| json_body = ActiveSupport::JSON.decode(response.body) - expect(json_body['RepoPath']).to include(wiki.repository.full_path) + expect(json_body['RepoPath']).to include(wiki.repository.disk_path) end end end diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb index 930ef49b7f3..971b45c411d 100644 --- a/spec/requests/lfs_http_spec.rb +++ b/spec/requests/lfs_http_spec.rb @@ -1208,7 +1208,7 @@ describe 'Git LFS API and storage' do end def post_lfs_json(url, body = nil, headers = nil) - post(url, body.try(:to_json), (headers || {}).merge('Content-Type' => 'application/vnd.git-lfs+json')) + post(url, body.try(:to_json), (headers || {}).merge('Content-Type' => LfsRequest::CONTENT_TYPE)) end def json_response diff --git a/spec/requests/lfs_locks_api_spec.rb b/spec/requests/lfs_locks_api_spec.rb new file mode 100644 index 00000000000..e44a11a7232 --- /dev/null +++ b/spec/requests/lfs_locks_api_spec.rb @@ -0,0 +1,159 @@ +require 'spec_helper' + +describe 'Git LFS File Locking API' do + include WorkhorseHelpers + + let(:project) { create(:project) } + let(:master) { create(:user) } + let(:developer) { create(:user) } + let(:guest) { create(:user) } + let(:path) { 'README.md' } + let(:headers) do + { + 'Authorization' => authorization + }.compact + end + + shared_examples 'unauthorized request' do + context 'when user is not authorized' do + let(:authorization) { authorize_user(guest) } + + it 'returns a forbidden 403 response' do + post_lfs_json url, body, headers + + expect(response).to have_gitlab_http_status(403) + end + end + end + + before do + allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) + + project.add_developer(master) + project.add_developer(developer) + project.add_guest(guest) + end + + describe 'Create File Lock endpoint' do + let(:url) { "#{project.http_url_to_repo}/info/lfs/locks" } + let(:authorization) { authorize_user(developer) } + let(:body) { { path: path } } + + include_examples 'unauthorized request' + + context 'with an existent lock' do + before do + lock_file('README.md', developer) + end + + it 'return an error message' do + post_lfs_json url, body, headers + + expect(response).to have_gitlab_http_status(409) + + expect(json_response.keys).to match_array(%w(lock message documentation_url)) + expect(json_response['message']).to match(/already locked/) + end + + it 'returns the existen lock' do + post_lfs_json url, body, headers + + expect(json_response['lock']['path']).to eq('README.md') + end + end + + context 'without an existent lock' do + it 'creates the lock' do + post_lfs_json url, body, headers + + expect(response).to have_gitlab_http_status(201) + + expect(json_response['lock'].keys).to match_array(%w(id path locked_at owner)) + end + end + end + + describe 'Listing File Locks endpoint' do + let(:url) { "#{project.http_url_to_repo}/info/lfs/locks" } + let(:authorization) { authorize_user(developer) } + + include_examples 'unauthorized request' + + it 'returns the list of locked files' do + lock_file('README.md', developer) + lock_file('README', developer) + + do_get url, nil, headers + + expect(response).to have_gitlab_http_status(200) + + expect(json_response['locks'].size).to eq(2) + expect(json_response['locks'].first.keys).to match_array(%w(id path locked_at owner)) + end + end + + describe 'List File Locks for verification endpoint' do + let(:url) { "#{project.http_url_to_repo}/info/lfs/locks/verify" } + let(:authorization) { authorize_user(developer) } + + include_examples 'unauthorized request' + + it 'returns the list of locked files grouped by owner' do + lock_file('README.md', master) + lock_file('README', developer) + + post_lfs_json url, nil, headers + + expect(response).to have_gitlab_http_status(200) + + expect(json_response['ours'].size).to eq(1) + expect(json_response['ours'].first['path']).to eq('README') + expect(json_response['theirs'].size).to eq(1) + expect(json_response['theirs'].first['path']).to eq('README.md') + end + end + + describe 'Delete File Lock endpoint' do + let!(:lock) { lock_file('README.md', developer) } + let(:url) { "#{project.http_url_to_repo}/info/lfs/locks/#{lock[:id]}/unlock" } + let(:authorization) { authorize_user(developer) } + + include_examples 'unauthorized request' + + context 'with an existent lock' do + it 'deletes the lock' do + post_lfs_json url, nil, headers + + expect(response).to have_gitlab_http_status(200) + end + + it 'returns the deleted lock' do + post_lfs_json url, nil, headers + + expect(json_response['lock'].keys).to match_array(%w(id path locked_at owner)) + end + end + end + + def lock_file(path, author) + result = Lfs::LockFileService.new(project, author, { path: path }).execute + + result[:lock] + end + + def authorize_user(user) + ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password) + end + + def post_lfs_json(url, body = nil, headers = nil) + post(url, body.try(:to_json), (headers || {}).merge('Content-Type' => LfsRequest::CONTENT_TYPE)) + end + + def do_get(url, params = nil, headers = nil) + get(url, (params || {}), (headers || {}).merge('Content-Type' => LfsRequest::CONTENT_TYPE)) + end + + def json_response + @json_response ||= JSON.parse(response.body) + end +end diff --git a/spec/requests/openid_connect_spec.rb b/spec/requests/openid_connect_spec.rb index 1a5ad9b04e4..de829011e58 100644 --- a/spec/requests/openid_connect_spec.rb +++ b/spec/requests/openid_connect_spec.rb @@ -65,13 +65,23 @@ describe 'OpenID Connect requests' do ) end - let(:public_email) { build :email, email: 'public@example.com' } - let(:private_email) { build :email, email: 'private@example.com' } + let!(:public_email) { build :email, email: 'public@example.com' } + let!(:private_email) { build :email, email: 'private@example.com' } - it 'includes all user information' do + let!(:group1) { create :group, path: 'group1' } + let!(:group2) { create :group, path: 'group2' } + let!(:group3) { create :group, path: 'group3', parent: group2 } + let!(:group4) { create :group, path: 'group4', parent: group3 } + + before do + group1.add_user(user, GroupMember::OWNER) + group3.add_user(user, Gitlab::Access::DEVELOPER) + end + + it 'includes all user information and group memberships' do request_user_info - expect(json_response).to eq({ + expect(json_response).to match(a_hash_including({ 'sub' => hashed_subject, 'name' => 'Alice', 'nickname' => 'alice', @@ -79,8 +89,13 @@ describe 'OpenID Connect requests' do 'email_verified' => true, 'website' => 'https://example.com', 'profile' => 'http://localhost/alice', - 'picture' => "http://localhost/uploads/-/system/user/avatar/#{user.id}/dk.png" - }) + 'picture' => "http://localhost/uploads/-/system/user/avatar/#{user.id}/dk.png", + 'groups' => anything + })) + + expected_groups = %w[group1 group2/group3] + expected_groups << 'group2/group3/group4' if Group.supports_nested_groups? + expect(json_response['groups']).to match_array(expected_groups) end end diff --git a/spec/requests/rack_attack_global_spec.rb b/spec/requests/rack_attack_global_spec.rb index 0fec14d0cce..b18e922b063 100644 --- a/spec/requests/rack_attack_global_spec.rb +++ b/spec/requests/rack_attack_global_spec.rb @@ -22,6 +22,7 @@ describe 'Rack Attack global throttles' do let(:url_that_does_not_require_authentication) { '/users/sign_in' } let(:url_that_requires_authentication) { '/dashboard/snippets' } + let(:url_api_internal) { '/api/v4/internal/check' } let(:api_partial_url) { '/todos' } around do |example| @@ -172,6 +173,15 @@ describe 'Rack Attack global throttles' do get url_that_does_not_require_authentication expect(response).to have_http_status 200 end + + context 'when the request is to the api internal endpoints' do + it 'allows requests over the rate limit' do + (1 + requests_per_period).times do + get url_api_internal, secret_token: Gitlab::Shell.secret_token + expect(response).to have_http_status 200 + end + end + end end context 'when the throttle is disabled' do diff --git a/spec/serializers/group_variable_entity_spec.rb b/spec/serializers/group_variable_entity_spec.rb new file mode 100644 index 00000000000..f6de7d01f98 --- /dev/null +++ b/spec/serializers/group_variable_entity_spec.rb @@ -0,0 +1,14 @@ +require 'spec_helper' + +describe GroupVariableEntity do + let(:variable) { create(:ci_group_variable) } + let(:entity) { described_class.new(variable) } + + describe '#as_json' do + subject { entity.as_json } + + it 'contains required fields' do + expect(subject).to include(:id, :key, :value, :protected) + end + end +end diff --git a/spec/serializers/lfs_file_lock_entity_spec.rb b/spec/serializers/lfs_file_lock_entity_spec.rb new file mode 100644 index 00000000000..5919f473a90 --- /dev/null +++ b/spec/serializers/lfs_file_lock_entity_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe LfsFileLockEntity do + let(:user) { create(:user) } + let(:resource) { create(:lfs_file_lock, user: user) } + + let(:request) { double('request', current_user: user) } + + subject { described_class.new(resource, request: request).as_json } + + it 'exposes basic attrs of the lock' do + expect(subject).to include(:id, :path, :locked_at) + end + + it 'exposes the owner info' do + expect(subject).to include(:owner) + expect(subject[:owner][:name]).to eq(user.name) + end +end diff --git a/spec/serializers/variable_entity_spec.rb b/spec/serializers/variable_entity_spec.rb new file mode 100644 index 00000000000..effc0022633 --- /dev/null +++ b/spec/serializers/variable_entity_spec.rb @@ -0,0 +1,14 @@ +require 'spec_helper' + +describe VariableEntity do + let(:variable) { create(:ci_variable) } + let(:entity) { described_class.new(variable) } + + describe '#as_json' do + subject { entity.as_json } + + it 'contains required fields' do + expect(subject).to include(:id, :key, :value, :protected) + end + end +end diff --git a/spec/services/ci/ensure_stage_service_spec.rb b/spec/services/ci/ensure_stage_service_spec.rb new file mode 100644 index 00000000000..d17e30763d7 --- /dev/null +++ b/spec/services/ci/ensure_stage_service_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +describe Ci::EnsureStageService, '#execute' do + set(:project) { create(:project) } + set(:user) { create(:user) } + + let(:stage) { create(:ci_stage_entity) } + let(:job) { build(:ci_build) } + + let(:service) { described_class.new(project, user) } + + context 'when build has a stage assigned' do + it 'does not create a new stage' do + job.assign_attributes(stage_id: stage.id) + + expect { service.execute(job) }.not_to change { Ci::Stage.count } + end + end + + context 'when build does not have a stage assigned' do + it 'creates a new stage' do + job.assign_attributes(stage_id: nil, stage: 'test') + + expect { service.execute(job) }.to change { Ci::Stage.count }.by(1) + end + end + + context 'when build is invalid' do + it 'does not create a new stage' do + job.assign_attributes(stage_id: nil, ref: nil) + + expect { service.execute(job) }.not_to change { Ci::Stage.count } + end + end + + context 'when new stage can not be created because of an exception' do + before do + allow(Ci::Stage).to receive(:create!) + .and_raise(ActiveRecord::RecordNotUnique.new('Duplicates!')) + end + + it 'retries up to two times' do + job.assign_attributes(stage_id: nil) + + expect(service).to receive(:find_stage).exactly(2).times + + expect { service.execute(job) } + .to raise_error(Ci::EnsureStageService::EnsureStageError) + end + end +end diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb index 2c2f48e323d..db9c216d3f4 100644 --- a/spec/services/ci/retry_build_service_spec.rb +++ b/spec/services/ci/retry_build_service_spec.rb @@ -5,7 +5,11 @@ describe Ci::RetryBuildService do set(:project) { create(:project) } set(:pipeline) { create(:ci_pipeline, project: project) } - let(:build) { create(:ci_build, pipeline: pipeline) } + let(:stage) do + Ci::Stage.create!(project: project, pipeline: pipeline, name: 'test') + end + + let(:build) { create(:ci_build, pipeline: pipeline, stage_id: stage.id) } let(:service) do described_class.new(project, user) @@ -27,29 +31,27 @@ describe Ci::RetryBuildService do user_id auto_canceled_by_id retried failure_reason].freeze shared_examples 'build duplication' do - let(:stage) do - # TODO, we still do not have factory for new stages, we will need to - # switch existing factory to persist stages, instead of using LegacyStage - # - Ci::Stage.create!(project: project, pipeline: pipeline, name: 'test') - end + let(:another_pipeline) { create(:ci_empty_pipeline, project: project) } let(:build) do create(:ci_build, :failed, :artifacts, :expired, :erased, :queued, :coverage, :tags, :allowed_to_fail, :on_tag, :triggered, :trace_artifact, :teardown_environment, - description: 'my-job', stage: 'test', pipeline: pipeline, - auto_canceled_by: create(:ci_empty_pipeline, project: project)) do |build| - ## - # TODO, workaround for FactoryBot limitation when having both - # stage (text) and stage_id (integer) columns in the table. - build.stage_id = stage.id - end + description: 'my-job', stage: 'test', stage_id: stage.id, + pipeline: pipeline, auto_canceled_by: another_pipeline) + end + + before do + # Make sure that build has both `stage_id` and `stage` because FactoryBot + # can reset one of the fields when assigning another. We plan to deprecate + # and remove legacy `stage` column in the future. + build.update_attributes(stage: 'test', stage_id: stage.id) end describe 'clone accessors' do CLONE_ACCESSORS.each do |attribute| it "clones #{attribute} build attribute" do + expect(build.send(attribute)).not_to be_nil expect(new_build.send(attribute)).not_to be_nil expect(new_build.send(attribute)).to eq build.send(attribute) end @@ -122,10 +124,12 @@ describe Ci::RetryBuildService do context 'when there are subsequent builds that are skipped' do let!(:subsequent_build) do - create(:ci_build, :skipped, stage_idx: 1, pipeline: pipeline) + create(:ci_build, :skipped, stage_idx: 2, + pipeline: pipeline, + stage: 'deploy') end - it 'resumes pipeline processing in subsequent stages' do + it 'resumes pipeline processing in a subsequent stage' do service.execute(build) expect(subsequent_build.reload).to be_created diff --git a/spec/services/groups/destroy_service_spec.rb b/spec/services/groups/destroy_service_spec.rb index ac4b9c02ba7..e8216abb08b 100644 --- a/spec/services/groups/destroy_service_spec.rb +++ b/spec/services/groups/destroy_service_spec.rb @@ -6,7 +6,7 @@ describe Groups::DestroyService do let!(:user) { create(:user) } let!(:group) { create(:group) } let!(:nested_group) { create(:group, parent: group) } - let!(:project) { create(:project, namespace: group) } + let!(:project) { create(:project, :legacy_storage, namespace: group) } let!(:notification_setting) { create(:notification_setting, source: group)} let(:gitlab_shell) { Gitlab::Shell.new } let(:remove_path) { group.path + "+#{group.id}+deleted" } @@ -141,7 +141,7 @@ describe Groups::DestroyService do end context 'legacy storage' do - let!(:project) { create(:project, :empty_repo, namespace: group) } + let!(:project) { create(:project, :legacy_storage, :empty_repo, namespace: group) } it 'removes repository' do expect(gitlab_shell.exists?(project.repository_storage_path, "#{project.disk_path}.git")).to be_falsey @@ -149,7 +149,7 @@ describe Groups::DestroyService do end context 'hashed storage' do - let!(:project) { create(:project, :hashed, :empty_repo, namespace: group) } + let!(:project) { create(:project, :empty_repo, namespace: group) } it 'removes repository' do expect(gitlab_shell.exists?(project.repository_storage_path, "#{project.disk_path}.git")).to be_falsey diff --git a/spec/services/issues/fetch_referenced_merge_requests_service_spec.rb b/spec/services/issues/fetch_referenced_merge_requests_service_spec.rb new file mode 100644 index 00000000000..4e58179f45f --- /dev/null +++ b/spec/services/issues/fetch_referenced_merge_requests_service_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper.rb' + +describe Issues::FetchReferencedMergeRequestsService do + let(:project) { create(:project) } + let(:issue) { create(:issue, project: project) } + let(:other_project) { create(:project) } + + let(:mr) { create(:merge_request, source_project: project, target_project: project, id: 2)} + let(:other_mr) { create(:merge_request, source_project: other_project, target_project: other_project, id: 1)} + + let(:user) { create(:user) } + let(:service) { described_class.new(project, user) } + + context 'with mentioned merge requests' do + it 'returns a list of sorted merge requests' do + allow(issue).to receive(:referenced_merge_requests).with(user).and_return([other_mr, mr]) + + mrs, closed_by_mrs = service.execute(issue) + + expect(mrs).to match_array([mr, other_mr]) + expect(closed_by_mrs).to match_array([]) + end + end + + context 'with closed-by merge requests' do + it 'returns a list of sorted merge requests' do + allow(issue).to receive(:closed_by_merge_requests).with(user).and_return([other_mr, mr]) + + mrs, closed_by_mrs = service.execute(issue) + + expect(mrs).to match_array([]) + expect(closed_by_mrs).to match_array([mr, other_mr]) + end + end +end diff --git a/spec/services/lfs/lock_file_service_spec.rb b/spec/services/lfs/lock_file_service_spec.rb new file mode 100644 index 00000000000..3e58eea2501 --- /dev/null +++ b/spec/services/lfs/lock_file_service_spec.rb @@ -0,0 +1,62 @@ +require 'spec_helper' + +describe Lfs::LockFileService do + let(:project) { create(:project) } + let(:current_user) { create(:user) } + + subject { described_class.new(project, current_user, params) } + + describe '#execute' do + let(:params) { { path: 'README.md' } } + + context 'when not authorized' do + it "doesn't succeed" do + result = subject.execute + + expect(result[:status]).to eq(:error) + expect(result[:http_status]).to eq(403) + expect(result[:message]).to eq('You have no permissions') + end + end + + context 'when authorized' do + before do + project.add_developer(current_user) + end + + context 'with an existent lock' do + let!(:lock) { create(:lfs_file_lock, project: project) } + + it "doesn't succeed" do + expect(subject.execute[:status]).to eq(:error) + end + + it "doesn't create the Lock" do + expect do + subject.execute + end.not_to change { LfsFileLock.count } + end + end + + context 'without an existent lock' do + it "succeeds" do + expect(subject.execute[:status]).to eq(:success) + end + + it "creates the Lock" do + expect do + subject.execute + end.to change { LfsFileLock.count }.by(1) + end + end + + context 'when an error is raised' do + it "doesn't succeed" do + allow_any_instance_of(described_class).to receive(:create_lock!).and_raise(StandardError) + + expect(subject.execute[:status]).to eq(:error) + end + end + end + end +end diff --git a/spec/services/lfs/locks_finder_service_spec.rb b/spec/services/lfs/locks_finder_service_spec.rb new file mode 100644 index 00000000000..e409b77babf --- /dev/null +++ b/spec/services/lfs/locks_finder_service_spec.rb @@ -0,0 +1,101 @@ +require 'spec_helper' + +describe Lfs::LocksFinderService do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:params) { {} } + + subject { described_class.new(project, user, params) } + + shared_examples 'no results' do + it 'returns an empty list' do + result = subject.execute + + expect(result[:status]).to eq(:success) + expect(result[:locks]).to be_blank + end + end + + describe '#execute' do + let!(:lock_1) { create(:lfs_file_lock, project: project) } + let!(:lock_2) { create(:lfs_file_lock, project: project, path: 'README') } + + context 'find by id' do + context 'with results' do + let(:params) do + { id: lock_1.id } + end + + it 'returns the record' do + result = subject.execute + + expect(result[:status]).to eq(:success) + expect(result[:locks].size).to eq(1) + expect(result[:locks].first).to eq(lock_1) + end + end + + context 'without results' do + let(:params) do + { id: 123 } + end + + include_examples 'no results' + end + end + + context 'find by path' do + context 'with results' do + let(:params) do + { path: lock_1.path } + end + + it 'returns the record' do + result = subject.execute + + expect(result[:status]).to eq(:success) + expect(result[:locks].size).to eq(1) + expect(result[:locks].first).to eq(lock_1) + end + end + + context 'without results' do + let(:params) do + { path: 'not-found' } + end + + include_examples 'no results' + end + end + + context 'find all' do + context 'with results' do + it 'returns all the records' do + result = subject.execute + + expect(result[:status]).to eq(:success) + expect(result[:locks].size).to eq(2) + end + end + + context 'without results' do + before do + LfsFileLock.delete_all + end + + include_examples 'no results' + end + end + + context 'when an error is raised' do + it "doesn't succeed" do + allow_any_instance_of(described_class).to receive(:find_locks).and_raise(StandardError) + + result = subject.execute + + expect(result[:status]).to eq(:error) + expect(result[:locks]).to be_blank + end + end + end +end diff --git a/spec/services/lfs/unlock_file_service_spec.rb b/spec/services/lfs/unlock_file_service_spec.rb new file mode 100644 index 00000000000..4bea112b9c6 --- /dev/null +++ b/spec/services/lfs/unlock_file_service_spec.rb @@ -0,0 +1,105 @@ +require 'spec_helper' + +describe Lfs::UnlockFileService do + let(:project) { create(:project) } + let(:current_user) { create(:user) } + let(:lock_author) { create(:user) } + let!(:lock) { create(:lfs_file_lock, user: lock_author, project: project) } + let(:params) { {} } + + subject { described_class.new(project, current_user, params) } + + describe '#execute' do + context 'when not authorized' do + it "doesn't succeed" do + result = subject.execute + + expect(result[:status]).to eq(:error) + expect(result[:http_status]).to eq(403) + expect(result[:message]).to eq('You have no permissions') + end + end + + context 'when authorized' do + before do + project.add_developer(current_user) + end + + context 'when lock does not exists' do + let(:params) { { id: 123 } } + it "doesn't succeed" do + result = subject.execute + + expect(result[:status]).to eq(:error) + expect(result[:http_status]).to eq(404) + end + end + + context 'when unlocked by the author' do + let(:current_user) { lock_author } + let(:params) { { id: lock.id } } + + it "succeeds" do + result = subject.execute + + expect(result[:status]).to eq(:success) + expect(result[:lock]).to be_present + end + end + + context 'when unlocked by a different user' do + let(:current_user) { create(:user) } + let(:params) { { id: lock.id } } + + it "doesn't succeed" do + result = subject.execute + + expect(result[:status]).to eq(:error) + expect(result[:message]).to match(/is locked by GitLab User #{lock_author.id}/) + expect(result[:http_status]).to eq(403) + end + end + + context 'when forced' do + let(:developer) { create(:user) } + let(:master) { create(:user) } + + before do + project.add_developer(developer) + project.add_master(master) + end + + context 'by a regular user' do + let(:current_user) { developer } + let(:params) do + { id: lock.id, + force: true } + end + + it "doesn't succeed" do + result = subject.execute + + expect(result[:status]).to eq(:error) + expect(result[:message]).to match(/You must have master access/) + expect(result[:http_status]).to eq(403) + end + end + + context 'by a master user' do + let(:current_user) { master } + let(:params) do + { id: lock.id, + force: true } + end + + it "succeeds" do + result = subject.execute + + expect(result[:status]).to eq(:success) + expect(result[:lock]).to be_present + end + end + end + end + end +end diff --git a/spec/services/members/authorized_destroy_service_spec.rb b/spec/services/members/authorized_destroy_service_spec.rb index 757c45708b9..9cf6f64a078 100644 --- a/spec/services/members/authorized_destroy_service_spec.rb +++ b/spec/services/members/authorized_destroy_service_spec.rb @@ -21,6 +21,15 @@ describe Members::AuthorizedDestroyService do .to change { Member.count }.from(3).to(2) end + it "doesn't destroy invited project member notification_settings" do + project.add_developer(member_user) + + member = create :project_member, :invited, project: project + + expect { described_class.new(member, member_user).execute } + .not_to change { NotificationSetting.count } + end + it 'destroys invited group member' do group.add_developer(member_user) @@ -29,38 +38,73 @@ describe Members::AuthorizedDestroyService do expect { described_class.new(member, member_user).execute } .to change { Member.count }.from(2).to(1) end + + it "doesn't destroy invited group member notification_settings" do + group.add_developer(member_user) + + member = create :group_member, :invited, group: group + + expect { described_class.new(member, member_user).execute } + .not_to change { NotificationSetting.count } + end + end + + context 'Requested user' do + it "doesn't destroy member notification_settings" do + member = create(:project_member, user: member_user, requested_at: Time.now) + + expect { described_class.new(member, member_user).execute } + .not_to change { NotificationSetting.count } + end end context 'Group member' do - it "unassigns issues and merge requests" do + let(:member) { group.members.find_by(user_id: member_user.id) } + + before do group.add_developer(member_user) + end + it "unassigns issues and merge requests" do issue = create :issue, project: group_project, assignees: [member_user] create :issue, assignees: [member_user] merge_request = create :merge_request, target_project: group_project, source_project: group_project, assignee: member_user create :merge_request, target_project: project, source_project: project, assignee: member_user - member = group.members.find_by(user_id: member_user.id) - expect { described_class.new(member, member_user).execute } .to change { number_of_assigned_issuables(member_user) }.from(4).to(2) expect(issue.reload.assignee_ids).to be_empty expect(merge_request.reload.assignee_id).to be_nil end + + it 'destroys member notification_settings' do + group.add_developer(member_user) + member = group.members.find_by(user_id: member_user.id) + + expect { described_class.new(member, member_user).execute } + .to change { member_user.notification_settings.count }.by(-1) + end end context 'Project member' do - it "unassigns issues and merge requests" do + let(:member) { project.members.find_by(user_id: member_user.id) } + + before do project.add_developer(member_user) + end + it "unassigns issues and merge requests" do create :issue, project: project, assignees: [member_user] create :merge_request, target_project: project, source_project: project, assignee: member_user - member = project.members.find_by(user_id: member_user.id) - expect { described_class.new(member, member_user).execute } .to change { number_of_assigned_issuables(member_user) }.from(2).to(0) end + + it 'destroys member notification_settings' do + expect { described_class.new(member, member_user).execute } + .to change { member_user.notification_settings.count }.by(-1) + end end end diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb index e56d335a7d6..3a935d98540 100644 --- a/spec/services/merge_requests/build_service_spec.rb +++ b/spec/services/merge_requests/build_service_spec.rb @@ -1,6 +1,7 @@ require 'spec_helper' describe MergeRequests::BuildService do + using RSpec::Parameterized::TableSyntax include RepoHelpers let(:project) { create(:project, :repository) } @@ -111,6 +112,7 @@ describe MergeRequests::BuildService do context 'one commit in the diff' do let(:commits) { Commit.decorate([commit_1], project) } + let(:commit_description) { commit_1.safe_message.split(/\n+/, 2).last } before do stub_compare @@ -125,7 +127,7 @@ describe MergeRequests::BuildService do end it 'uses the description of the commit as the description of the merge request' do - expect(merge_request.description).to eq(commit_1.safe_message.split(/\n+/, 2).last) + expect(merge_request.description).to eq(commit_description) end context 'merge request already has a description set' do @@ -148,68 +150,32 @@ describe MergeRequests::BuildService do end end - context 'branch starts with issue IID followed by a hyphen' do - let(:source_branch) { "#{issue.iid}-fix-issue" } - - it 'appends "Closes #$issue-iid" to the description' do - expect(merge_request.description).to eq("#{commit_1.safe_message.split(/\n+/, 2).last}\n\nCloses ##{issue.iid}") + context 'when the source branch matches an issue' do + where(:issue_tracker, :source_branch, :closing_message) do + :jira | 'FOO-123-fix-issue' | 'Closes FOO-123' + :jira | 'fix-issue' | nil + :custom_issue_tracker | '123-fix-issue' | 'Closes #123' + :custom_issue_tracker | 'fix-issue' | nil + :internal | '123-fix-issue' | 'Closes #123' + :internal | 'fix-issue' | nil end - context 'merge request already has a description set' do - let(:description) { 'Merge request description' } - - it 'appends "Closes #$issue-iid" to the description' do - expect(merge_request.description).to eq("#{description}\n\nCloses ##{issue.iid}") + with_them do + before do + if issue_tracker == :internal + issue.update!(iid: 123) + else + create(:"#{issue_tracker}_service", project: project) + end end - end - context 'commit has no description' do - let(:commits) { Commit.decorate([commit_2], project) } + it 'appends the closing description' do + expected_description = [commit_description, closing_message].compact.join("\n\n") - it 'sets the description to "Closes #$issue-iid"' do - expect(merge_request.description).to eq("Closes ##{issue.iid}") + expect(merge_request.description).to eq(expected_description) end end end - - context 'branch starts with numeric characters followed by a hyphen with no issue tracker' do - let(:source_branch) { '12345-fix-issue' } - - before do - allow(project).to receive(:external_issue_tracker).and_return(false) - allow(project).to receive(:issues_enabled?).and_return(false) - end - - it 'uses the title of the commit as the title of the merge request' do - expect(merge_request.title).to eq(commit_1.safe_message.split("\n").first) - end - - it 'uses the description of the commit as the description of the merge request' do - commit_description = commit_1.safe_message.split(/\n+/, 2).last - - expect(merge_request.description).to eq("#{commit_description}") - end - end - - context 'branch starts with JIRA-formatted external issue IID followed by a hyphen' do - let(:source_branch) { 'EXMPL-12345-fix-issue' } - - before do - allow(project).to receive(:external_issue_tracker).and_return(true) - allow(project).to receive(:issues_enabled?).and_return(false) - allow(project).to receive(:external_issue_reference_pattern).and_return(IssueTrackerService.reference_pattern) - end - - it 'uses the title of the commit as the title of the merge request' do - expect(merge_request.title).to eq(commit_1.safe_message.split("\n").first) - end - - it 'uses the description of the commit as the description of the merge request and appends the closes text' do - commit_description = commit_1.safe_message.split(/\n+/, 2).last - - expect(merge_request.description).to eq("#{commit_description}\n\nCloses EXMPL-12345") - end - end end context 'more than one commit in the diff' do @@ -239,80 +205,62 @@ describe MergeRequests::BuildService do end end - context 'branch starts with GitLab issue IID followed by a hyphen' do - let(:source_branch) { "#{issue.iid}-fix-issue" } - - it 'sets the title to: Resolves "$issue-title"' do - expect(merge_request.title).to eq("Resolve \"#{issue.title}\"") + context 'when the source branch matches an issue' do + where(:issue_tracker, :source_branch, :title, :closing_message) do + :jira | 'FOO-123-fix-issue' | 'Resolve FOO-123 "Fix issue"' | 'Closes FOO-123' + :jira | 'fix-issue' | 'Fix issue' | nil + :custom_issue_tracker | '123-fix-issue' | 'Resolve #123 "Fix issue"' | 'Closes #123' + :custom_issue_tracker | 'fix-issue' | 'Fix issue' | nil + :internal | '123-fix-issue' | 'Resolve "A bug"' | 'Closes #123' + :internal | 'fix-issue' | 'Fix issue' | nil + :internal | '124-fix-issue' | '124 fix issue' | nil end - context 'when issue is not accessible to user' do + with_them do before do - project.team.truncate - end - - it 'uses branch title as the merge request title' do - expect(merge_request.title).to eq("#{issue.iid} fix issue") + if issue_tracker == :internal + issue.update!(iid: 123) + else + create(:"#{issue_tracker}_service", project: project) + end end - end - - context 'issue does not exist' do - let(:source_branch) { "#{issue.iid.succ}-fix-issue" } - it 'uses the title of the branch as the merge request title' do - expect(merge_request.title).to eq("#{issue.iid.succ} fix issue") + it 'sets the correct title' do + expect(merge_request.title).to eq(title) end - end - - context 'issue is confidential' do - let(:issue_confidential) { true } - it 'uses the title of the branch as the merge request title' do - expect(merge_request.title).to eq("#{issue.iid} fix issue") + it 'sets the closing description' do + expect(merge_request.description).to eq(closing_message) end end end - context 'branch starts with numeric characters followed by a hyphen with no issue tracker' do - let(:source_branch) { '12345-fix-issue' } + context 'when the issue is not accessible to user' do + let(:source_branch) { "#{issue.iid}-fix-issue" } before do - allow(project).to receive(:external_issue_tracker).and_return(false) - allow(project).to receive(:issues_enabled?).and_return(false) + project.team.truncate end - it 'sets the title to the humanized branch title' do - expect(merge_request.title).to eq('12345 fix issue') + it 'uses branch title as the merge request title' do + expect(merge_request.title).to eq("#{issue.iid} fix issue") end - end - - context 'branch starts with JIRA-formatted external issue IID' do - let(:source_branch) { 'EXMPL-12345' } - before do - allow(project).to receive(:external_issue_tracker).and_return(true) - allow(project).to receive(:issues_enabled?).and_return(false) - allow(project).to receive(:external_issue_reference_pattern).and_return(IssueTrackerService.reference_pattern) + it 'does not set a description' do + expect(merge_request.description).to be_nil end + end - it 'sets the title to the humanized branch title' do - expect(merge_request.title).to eq('Resolve EXMPL-12345') - end + context 'when the issue is confidential' do + let(:source_branch) { "#{issue.iid}-fix-issue" } + let(:issue_confidential) { true } - it 'appends the closes text' do - expect(merge_request.description).to eq('Closes EXMPL-12345') + it 'uses the title of the branch as the merge request title' do + expect(merge_request.title).to eq("#{issue.iid} fix issue") end - context 'followed by hyphenated text' do - let(:source_branch) { 'EXMPL-12345-fix-issue' } - - it 'sets the title to the humanized branch title' do - expect(merge_request.title).to eq('Resolve EXMPL-12345 "Fix issue"') - end - - it 'appends the closes text' do - expect(merge_request.description).to eq('Closes EXMPL-12345') - end + it 'does not set a description' do + expect(merge_request.description).to be_nil end end end diff --git a/spec/services/projects/create_from_template_service_spec.rb b/spec/services/projects/create_from_template_service_spec.rb index 9919ec254c6..609d678caea 100644 --- a/spec/services/projects/create_from_template_service_spec.rb +++ b/spec/services/projects/create_from_template_service_spec.rb @@ -4,8 +4,10 @@ describe Projects::CreateFromTemplateService do let(:user) { create(:user) } let(:project_params) do { - path: user.to_param, - template_name: 'rails' + path: user.to_param, + template_name: 'rails', + description: 'project description', + visibility_level: Gitlab::VisibilityLevel::PRIVATE } end @@ -22,5 +24,7 @@ describe Projects::CreateFromTemplateService do expect(project).to be_saved expect(project.scheduled?).to be(true) + expect(project.description).to match('project description') + expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE) end end diff --git a/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb b/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb index 15699574b3a..fb6d7171ac3 100644 --- a/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb +++ b/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Projects::HashedStorage::MigrateAttachmentsService do subject(:service) { described_class.new(project) } - let(:project) { create(:project) } + let(:project) { create(:project, :legacy_storage) } let(:legacy_storage) { Storage::LegacyProject.new(project) } let(:hashed_storage) { Storage::HashedProject.new(project) } diff --git a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb index 7b536cc05cb..747bd4529a0 100644 --- a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb +++ b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Projects::HashedStorage::MigrateRepositoryService do let(:gitlab_shell) { Gitlab::Shell.new } - let(:project) { create(:project, :repository, :wiki_repo) } + let(:project) { create(:project, :legacy_storage, :repository, :wiki_repo) } let(:service) { described_class.new(project) } let(:legacy_storage) { Storage::LegacyProject.new(project) } let(:hashed_storage) { Storage::HashedProject.new(project) } diff --git a/spec/services/projects/hashed_storage_migration_service_spec.rb b/spec/services/projects/hashed_storage_migration_service_spec.rb index 466f0b5d7c2..e8e18bb3ac0 100644 --- a/spec/services/projects/hashed_storage_migration_service_spec.rb +++ b/spec/services/projects/hashed_storage_migration_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::HashedStorageMigrationService do - let(:project) { create(:project, :empty_repo, :wiki_repo) } + let(:project) { create(:project, :empty_repo, :wiki_repo, :legacy_storage) } subject(:service) { described_class.new(project) } describe '#execute' do diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index ef68742a463..ae0e22e3dc0 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -4,7 +4,7 @@ describe Projects::TransferService do let(:gitlab_shell) { Gitlab::Shell.new } let(:user) { create(:user) } let(:group) { create(:group) } - let(:project) { create(:project, :repository, namespace: user.namespace) } + let(:project) { create(:project, :repository, :legacy_storage, namespace: user.namespace) } context 'namespace -> namespace' do before do @@ -214,7 +214,7 @@ describe Projects::TransferService do end context 'when hashed storage in use' do - let(:hashed_project) { create(:project, :repository, :hashed, namespace: user.namespace) } + let(:hashed_project) { create(:project, :repository, namespace: user.namespace) } before do group.add_owner(user) diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index fc6aa713d6f..a0b97ceead9 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -150,6 +150,8 @@ describe Projects::UpdateService do let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage]['path'] } context 'with legacy storage' do + let(:project) { create(:project, :legacy_storage, :repository, creator: user, namespace: user.namespace) } + before do gitlab_shell.add_repository(repository_storage, "#{user.namespace.full_path}/existing") end diff --git a/spec/services/search/snippet_service_spec.rb b/spec/services/search/snippet_service_spec.rb index bc7885b03d9..8ad162ad66e 100644 --- a/spec/services/search/snippet_service_spec.rb +++ b/spec/services/search/snippet_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Search::SnippetService do let(:author) { create(:author) } - let(:project) { create(:project) } + let(:project) { create(:project, :public) } let!(:public_snippet) { create(:snippet, :public, content: 'password: XXX') } let!(:internal_snippet) { create(:snippet, :internal, content: 'password: XXX') } diff --git a/spec/services/users/destroy_service_spec.rb b/spec/services/users/destroy_service_spec.rb index bb3d73edf8e..11c75ddfcf8 100644 --- a/spec/services/users/destroy_service_spec.rb +++ b/spec/services/users/destroy_service_spec.rb @@ -173,7 +173,7 @@ describe Users::DestroyService do end context 'legacy storage' do - let!(:project) { create(:project, :empty_repo, namespace: user.namespace) } + let!(:project) { create(:project, :empty_repo, :legacy_storage, namespace: user.namespace) } it 'removes repository' do expect(gitlab_shell.exists?(project.repository_storage_path, "#{project.disk_path}.git")).to be_falsey @@ -181,7 +181,7 @@ describe Users::DestroyService do end context 'hashed storage' do - let!(:project) { create(:project, :empty_repo, :hashed, namespace: user.namespace) } + let!(:project) { create(:project, :empty_repo, namespace: user.namespace) } it 'removes repository' do expect(gitlab_shell.exists?(project.repository_storage_path, "#{project.disk_path}.git")).to be_falsey diff --git a/spec/support/controllers/githubish_import_controller_shared_examples.rb b/spec/support/controllers/githubish_import_controller_shared_examples.rb index a0839eefe6c..3321f920666 100644 --- a/spec/support/controllers/githubish_import_controller_shared_examples.rb +++ b/spec/support/controllers/githubish_import_controller_shared_examples.rb @@ -92,6 +92,7 @@ end shared_examples 'a GitHub-ish import controller: POST create' do let(:user) { create(:user) } + let(:project) { create(:project) } let(:provider_username) { user.username } let(:provider_user) { OpenStruct.new(login: provider_username) } let(:provider_repo) do @@ -107,14 +108,34 @@ shared_examples 'a GitHub-ish import controller: POST create' do assign_session_token(provider) end + it 'returns 200 response when the project is imported successfully' do + allow(Gitlab::LegacyGithubImport::ProjectCreator) + .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider) + .and_return(double(execute: project)) + + post :create, format: :json + + expect(response).to have_gitlab_http_status(200) + end + + it 'returns 422 response when the project could not be imported' do + allow(Gitlab::LegacyGithubImport::ProjectCreator) + .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider) + .and_return(double(execute: build(:project))) + + post :create, format: :json + + expect(response).to have_gitlab_http_status(422) + end + context "when the repository owner is the provider user" do context "when the provider user and GitLab user's usernames match" do it "takes the current user's namespace" do expect(Gitlab::LegacyGithubImport::ProjectCreator) .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider) - .and_return(double(execute: true)) + .and_return(double(execute: project)) - post :create, format: :js + post :create, format: :json end end @@ -124,9 +145,9 @@ shared_examples 'a GitHub-ish import controller: POST create' do it "takes the current user's namespace" do expect(Gitlab::LegacyGithubImport::ProjectCreator) .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider) - .and_return(double(execute: true)) + .and_return(double(execute: project)) - post :create, format: :js + post :create, format: :json end end end @@ -151,9 +172,9 @@ shared_examples 'a GitHub-ish import controller: POST create' do it "takes the existing namespace" do expect(Gitlab::LegacyGithubImport::ProjectCreator) .to receive(:new).with(provider_repo, provider_repo.name, existing_namespace, user, access_params, type: provider) - .and_return(double(execute: true)) + .and_return(double(execute: project)) - post :create, format: :js + post :create, format: :json end end @@ -163,9 +184,9 @@ shared_examples 'a GitHub-ish import controller: POST create' do expect(Gitlab::LegacyGithubImport::ProjectCreator) .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider) - .and_return(double(execute: true)) + .and_return(double(execute: project)) - post :create, format: :js + post :create, format: :json end end end @@ -174,17 +195,17 @@ shared_examples 'a GitHub-ish import controller: POST create' do context "when current user can create namespaces" do it "creates the namespace" do expect(Gitlab::LegacyGithubImport::ProjectCreator) - .to receive(:new).and_return(double(execute: true)) + .to receive(:new).and_return(double(execute: project)) - expect { post :create, target_namespace: provider_repo.name, format: :js }.to change(Namespace, :count).by(1) + expect { post :create, target_namespace: provider_repo.name, format: :json }.to change(Namespace, :count).by(1) end it "takes the new namespace" do expect(Gitlab::LegacyGithubImport::ProjectCreator) .to receive(:new).with(provider_repo, provider_repo.name, an_instance_of(Group), user, access_params, type: provider) - .and_return(double(execute: true)) + .and_return(double(execute: project)) - post :create, target_namespace: provider_repo.name, format: :js + post :create, target_namespace: provider_repo.name, format: :json end end @@ -195,17 +216,17 @@ shared_examples 'a GitHub-ish import controller: POST create' do it "doesn't create the namespace" do expect(Gitlab::LegacyGithubImport::ProjectCreator) - .to receive(:new).and_return(double(execute: true)) + .to receive(:new).and_return(double(execute: project)) - expect { post :create, format: :js }.not_to change(Namespace, :count) + expect { post :create, format: :json }.not_to change(Namespace, :count) end it "takes the current user's namespace" do expect(Gitlab::LegacyGithubImport::ProjectCreator) .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider) - .and_return(double(execute: true)) + .and_return(double(execute: project)) - post :create, format: :js + post :create, format: :json end end end @@ -221,21 +242,21 @@ shared_examples 'a GitHub-ish import controller: POST create' do it 'takes the selected namespace and name' do expect(Gitlab::LegacyGithubImport::ProjectCreator) .to receive(:new).with(provider_repo, test_name, test_namespace, user, access_params, type: provider) - .and_return(double(execute: true)) + .and_return(double(execute: project)) - post :create, { target_namespace: test_namespace.name, new_name: test_name, format: :js } + post :create, { target_namespace: test_namespace.name, new_name: test_name, format: :json } end it 'takes the selected name and default namespace' do expect(Gitlab::LegacyGithubImport::ProjectCreator) .to receive(:new).with(provider_repo, test_name, user.namespace, user, access_params, type: provider) - .and_return(double(execute: true)) + .and_return(double(execute: project)) - post :create, { new_name: test_name, format: :js } + post :create, { new_name: test_name, format: :json } end end - context 'user has chosen an existing nested namespace and name for the project' do + context 'user has chosen an existing nested namespace and name for the project', :postgresql do let(:parent_namespace) { create(:group, name: 'foo', owner: user) } let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) } let(:test_name) { 'test_name' } @@ -247,63 +268,124 @@ shared_examples 'a GitHub-ish import controller: POST create' do it 'takes the selected namespace and name' do expect(Gitlab::LegacyGithubImport::ProjectCreator) .to receive(:new).with(provider_repo, test_name, nested_namespace, user, access_params, type: provider) - .and_return(double(execute: true)) + .and_return(double(execute: project)) - post :create, { target_namespace: nested_namespace.full_path, new_name: test_name, format: :js } + post :create, { target_namespace: nested_namespace.full_path, new_name: test_name, format: :json } end end - context 'user has chosen a non-existent nested namespaces and name for the project' do + context 'user has chosen a non-existent nested namespaces and name for the project', :postgresql do let(:test_name) { 'test_name' } it 'takes the selected namespace and name' do expect(Gitlab::LegacyGithubImport::ProjectCreator) .to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider) - .and_return(double(execute: true)) + .and_return(double(execute: project)) - post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :js } + post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :json } end it 'creates the namespaces' do allow(Gitlab::LegacyGithubImport::ProjectCreator) .to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider) - .and_return(double(execute: true)) + .and_return(double(execute: project)) - expect { post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :js } } + expect { post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :json } } .to change { Namespace.count }.by(2) end it 'new namespace has the right parent' do allow(Gitlab::LegacyGithubImport::ProjectCreator) .to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider) - .and_return(double(execute: true)) + .and_return(double(execute: project)) - post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :js } + post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :json } expect(Namespace.find_by_path_or_name('bar').parent.path).to eq('foo') end end - context 'user has chosen existent and non-existent nested namespaces and name for the project' do + context 'user has chosen existent and non-existent nested namespaces and name for the project', :postgresql do let(:test_name) { 'test_name' } let!(:parent_namespace) { create(:group, name: 'foo', owner: user) } + before do + parent_namespace.add_owner(user) + end + it 'takes the selected namespace and name' do expect(Gitlab::LegacyGithubImport::ProjectCreator) .to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider) - .and_return(double(execute: true)) + .and_return(double(execute: project)) - post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :js } + post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :json } end it 'creates the namespaces' do allow(Gitlab::LegacyGithubImport::ProjectCreator) .to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider) - .and_return(double(execute: true)) + .and_return(double(execute: project)) - expect { post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :js } } + expect { post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :json } } .to change { Namespace.count }.by(2) end + + it 'does not create a new namespace under the user namespace' do + expect(Gitlab::LegacyGithubImport::ProjectCreator) + .to receive(:new).with(provider_repo, test_name, user.namespace, user, access_params, type: provider) + .and_return(double(execute: build_stubbed(:project))) + + expect { post :create, { target_namespace: "#{user.namespace_path}/test_group", new_name: test_name, format: :js } } + .not_to change { Namespace.count } + end + end + + context 'user cannot create a subgroup inside a group is not a member of' do + let(:test_name) { 'test_name' } + let!(:parent_namespace) { create(:group, name: 'foo') } + + it 'does not take the selected namespace and name' do + expect(Gitlab::LegacyGithubImport::ProjectCreator) + .to receive(:new).with(provider_repo, test_name, user.namespace, user, access_params, type: provider) + .and_return(double(execute: build_stubbed(:project))) + + post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :js } + end + + it 'does not create the namespaces' do + allow(Gitlab::LegacyGithubImport::ProjectCreator) + .to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider) + .and_return(double(execute: build_stubbed(:project))) + + expect { post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :js } } + .not_to change { Namespace.count } + end + end + + context 'user can use a group without having permissions to create a group' do + let(:test_name) { 'test_name' } + let!(:group) { create(:group, name: 'foo') } + + it 'takes the selected namespace and name' do + group.add_owner(user) + user.update!(can_create_group: false) + + expect(Gitlab::LegacyGithubImport::ProjectCreator) + .to receive(:new).with(provider_repo, test_name, group, user, access_params, type: provider) + .and_return(double(execute: build_stubbed(:project))) + + post :create, { target_namespace: 'foo', new_name: test_name, format: :js } + end + end + + context 'when user can not create projects in the chosen namespace' do + it 'returns 422 response' do + other_namespace = create(:group, name: 'other_namespace') + + post :create, { target_namespace: other_namespace.name, format: :json } + + expect(response).to have_gitlab_http_status(422) + end end end end diff --git a/spec/support/factory_girl.rb b/spec/support/factory_bot.rb index c7890e49c66..c7890e49c66 100644 --- a/spec/support/factory_girl.rb +++ b/spec/support/factory_bot.rb diff --git a/spec/support/features/variable_list_shared_examples.rb b/spec/support/features/variable_list_shared_examples.rb new file mode 100644 index 00000000000..0d8f7a7aae6 --- /dev/null +++ b/spec/support/features/variable_list_shared_examples.rb @@ -0,0 +1,269 @@ +shared_examples 'variable list' do + it 'shows list of variables' do + page.within('.js-ci-variable-list-section') do + expect(first('.js-ci-variable-input-key').value).to eq(variable.key) + end + end + + it 'adds new secret variable' do + page.within('.js-ci-variable-list-section .js-row:last-child') do + find('.js-ci-variable-input-key').set('key') + find('.js-ci-variable-input-value').set('key value') + end + + click_button('Save variables') + wait_for_requests + + visit page_path + + # We check the first row because it re-sorts to alphabetical order on refresh + page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + expect(find('.js-ci-variable-input-key').value).to eq('key') + expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key value') + end + end + + it 'adds empty variable' do + page.within('.js-ci-variable-list-section .js-row:last-child') do + find('.js-ci-variable-input-key').set('key') + find('.js-ci-variable-input-value').set('') + end + + click_button('Save variables') + wait_for_requests + + visit page_path + + # We check the first row because it re-sorts to alphabetical order on refresh + page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + expect(find('.js-ci-variable-input-key').value).to eq('key') + expect(find('.js-ci-variable-input-value', visible: false).value).to eq('') + end + end + + it 'adds new protected variable' do + page.within('.js-ci-variable-list-section .js-row:last-child') do + find('.js-ci-variable-input-key').set('key') + find('.js-ci-variable-input-value').set('key value') + find('.ci-variable-protected-item .js-project-feature-toggle').click + + expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true') + end + + click_button('Save variables') + wait_for_requests + + visit page_path + + # We check the first row because it re-sorts to alphabetical order on refresh + page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + expect(find('.js-ci-variable-input-key').value).to eq('key') + expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key value') + expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true') + end + end + + it 'reveals and hides variables' do + page.within('.js-ci-variable-list-section') do + expect(first('.js-ci-variable-input-key').value).to eq(variable.key) + expect(first('.js-ci-variable-input-value', visible: false).value).to eq(variable.value) + expect(page).to have_content('*' * 20) + + click_button('Reveal value') + + expect(first('.js-ci-variable-input-key').value).to eq(variable.key) + expect(first('.js-ci-variable-input-value').value).to eq(variable.value) + expect(page).not_to have_content('*' * 20) + + click_button('Hide value') + + expect(first('.js-ci-variable-input-key').value).to eq(variable.key) + expect(first('.js-ci-variable-input-value', visible: false).value).to eq(variable.value) + expect(page).to have_content('*' * 20) + end + end + + it 'deletes variable' do + page.within('.js-ci-variable-list-section') do + expect(page).to have_selector('.js-row', count: 2) + + first('.js-row-remove-button').click + + click_button('Save variables') + wait_for_requests + + expect(page).to have_selector('.js-row', count: 1) + end + end + + it 'edits variable' do + page.within('.js-ci-variable-list-section') do + click_button('Reveal value') + + page.within('.js-row:nth-child(1)') do + find('.js-ci-variable-input-key').set('new_key') + find('.js-ci-variable-input-value').set('new_value') + end + + click_button('Save variables') + wait_for_requests + + visit page_path + + page.within('.js-row:nth-child(1)') do + expect(find('.js-ci-variable-input-key').value).to eq('new_key') + expect(find('.js-ci-variable-input-value', visible: false).value).to eq('new_value') + end + end + end + + it 'edits variable with empty value' do + page.within('.js-ci-variable-list-section') do + click_button('Reveal value') + + page.within('.js-row:nth-child(1)') do + find('.js-ci-variable-input-key').set('new_key') + find('.js-ci-variable-input-value').set('') + end + + click_button('Save variables') + wait_for_requests + + visit page_path + + page.within('.js-row:nth-child(1)') do + expect(find('.js-ci-variable-input-key').value).to eq('new_key') + expect(find('.js-ci-variable-input-value', visible: false).value).to eq('') + end + end + end + + it 'edits variable to be protected' do + # Create the unprotected variable + page.within('.js-ci-variable-list-section .js-row:last-child') do + find('.js-ci-variable-input-key').set('unprotected_key') + find('.js-ci-variable-input-value').set('unprotected_value') + + expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('false') + end + + click_button('Save variables') + wait_for_requests + + visit page_path + + # We check the first row because it re-sorts to alphabetical order on refresh + page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do + find('.ci-variable-protected-item .js-project-feature-toggle').click + + expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true') + end + + click_button('Save variables') + wait_for_requests + + visit page_path + + # We check the first row because it re-sorts to alphabetical order on refresh + page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do + expect(find('.js-ci-variable-input-key').value).to eq('unprotected_key') + expect(find('.js-ci-variable-input-value', visible: false).value).to eq('unprotected_value') + expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true') + end + end + + it 'edits variable to be unprotected' do + # Create the protected variable + page.within('.js-ci-variable-list-section .js-row:last-child') do + find('.js-ci-variable-input-key').set('protected_key') + find('.js-ci-variable-input-value').set('protected_value') + find('.ci-variable-protected-item .js-project-feature-toggle').click + + expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true') + end + + click_button('Save variables') + wait_for_requests + + visit page_path + + page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + find('.ci-variable-protected-item .js-project-feature-toggle').click + + expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('false') + end + + click_button('Save variables') + wait_for_requests + + visit page_path + + page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + expect(find('.js-ci-variable-input-key').value).to eq('protected_key') + expect(find('.js-ci-variable-input-value', visible: false).value).to eq('protected_value') + expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('false') + end + end + + it 'handles multiple edits and deletion in the middle' do + page.within('.js-ci-variable-list-section') do + # Create 2 variables + page.within('.js-row:last-child') do + find('.js-ci-variable-input-key').set('akey') + find('.js-ci-variable-input-value').set('akeyvalue') + end + page.within('.js-row:last-child') do + find('.js-ci-variable-input-key').set('zkey') + find('.js-ci-variable-input-value').set('zkeyvalue') + end + + click_button('Save variables') + wait_for_requests + + expect(page).to have_selector('.js-row', count: 4) + + # Remove the `akey` variable + page.within('.js-row:nth-child(2)') do + first('.js-row-remove-button').click + end + + # Add another variable + page.within('.js-row:last-child') do + find('.js-ci-variable-input-key').set('ckey') + find('.js-ci-variable-input-value').set('ckeyvalue') + end + + click_button('Save variables') + wait_for_requests + + visit page_path + + # Expect to find 3 variables(4 rows) in alphbetical order + expect(page).to have_selector('.js-row', count: 4) + row_keys = all('.js-ci-variable-input-key') + expect(row_keys[0].value).to eq('ckey') + expect(row_keys[1].value).to eq('test_key') + expect(row_keys[2].value).to eq('zkey') + expect(row_keys[3].value).to eq('') + end + end + + it 'shows validation error box about duplicate keys' do + page.within('.js-ci-variable-list-section .js-row:last-child') do + find('.js-ci-variable-input-key').set('samekey') + find('.js-ci-variable-input-value').set('value1') + end + page.within('.js-ci-variable-list-section .js-row:last-child') do + find('.js-ci-variable-input-key').set('samekey') + find('.js-ci-variable-input-value').set('value2') + end + + click_button('Save variables') + wait_for_requests + + # We check the first row because it re-sorts to alphabetical order on refresh + page.within('.js-ci-variable-list-section') do + expect(find('.js-ci-variable-error-box')).to have_content(/Validation failed Variables have duplicate values \(.+\)/) + end + end +end diff --git a/spec/support/fixture_helpers.rb b/spec/support/fixture_helpers.rb index 128aaaf25fe..8854382dc6b 100644 --- a/spec/support/fixture_helpers.rb +++ b/spec/support/fixture_helpers.rb @@ -1,12 +1,12 @@ module FixtureHelpers - def fixture_file(filename) + def fixture_file(filename, dir: '') return '' if filename.blank? - File.read(expand_fixture_path(filename)) + File.read(expand_fixture_path(filename, dir: dir)) end - def expand_fixture_path(filename) - File.expand_path(Rails.root.join('spec/fixtures/', filename)) + def expand_fixture_path(filename, dir: '') + File.expand_path(Rails.root.join(dir, 'spec', 'fixtures', filename)) end end diff --git a/spec/support/matchers/pagination_matcher.rb b/spec/support/matchers/pagination_matcher.rb index 60f5e8239a7..9a7697e2bfc 100644 --- a/spec/support/matchers/pagination_matcher.rb +++ b/spec/support/matchers/pagination_matcher.rb @@ -3,3 +3,9 @@ RSpec::Matchers.define :include_pagination_headers do |expected| expect(actual.headers).to include('X-Total', 'X-Total-Pages', 'X-Per-Page', 'X-Page', 'X-Next-Page', 'X-Prev-Page', 'Link') end end + +RSpec::Matchers.define :include_limited_pagination_headers do |expected| + match do |actual| + expect(actual.headers).to include('X-Per-Page', 'X-Page', 'X-Next-Page', 'X-Prev-Page', 'Link') + end +end diff --git a/spec/support/migrations_helpers.rb b/spec/support/migrations_helpers.rb index 6522d74ba89..6bf976a2cf9 100644 --- a/spec/support/migrations_helpers.rb +++ b/spec/support/migrations_helpers.rb @@ -15,18 +15,27 @@ module MigrationsHelpers ActiveRecord::Migrator.migrations(migrations_paths) end - def reset_column_in_migration_models + def clear_schema_cache! ActiveRecord::Base.connection_pool.connections.each do |conn| conn.schema_cache.clear! end + end - described_class.constants.sort.each do |name| - const = described_class.const_get(name) + def reset_column_in_all_models + clear_schema_cache! - if const.is_a?(Class) && const < ActiveRecord::Base - const.reset_column_information - end - end + # Reset column information for the most offending classes **after** we + # migrated the schema up, otherwise, column information could be + # outdated. We have a separate method for this so we can override it in EE. + ActiveRecord::Base.descendants.each(&method(:reset_column_information)) + + # Without that, we get errors because of missing attributes, e.g. + # super: no superclass method `elasticsearch_indexing' for #<ApplicationSetting:0x00007f85628508d8> + ApplicationSetting.define_attribute_methods + end + + def reset_column_information(klass) + klass.reset_column_information end def previous_migration @@ -36,7 +45,13 @@ module MigrationsHelpers end def migration_schema_version - self.class.metadata[:schema] || previous_migration.version + metadata_schema = self.class.metadata[:schema] + + if metadata_schema == :latest + migrations.last.version + else + metadata_schema || previous_migration.version + end end def schema_migrate_down! @@ -45,15 +60,17 @@ module MigrationsHelpers migration_schema_version) end - reset_column_in_migration_models + reset_column_in_all_models end def schema_migrate_up! + reset_column_in_all_models + disable_migrations_output do ActiveRecord::Migrator.migrate(migrations_paths) end - reset_column_in_migration_models + reset_column_in_all_models end def disable_migrations_output diff --git a/spec/support/reactive_caching_helpers.rb b/spec/support/reactive_caching_helpers.rb index 34124f02133..e22dd974c6a 100644 --- a/spec/support/reactive_caching_helpers.rb +++ b/spec/support/reactive_caching_helpers.rb @@ -13,6 +13,12 @@ module ReactiveCachingHelpers write_reactive_cache(subject, data, *qualifiers) if data end + def synchronous_reactive_cache(subject) + allow(service).to receive(:with_reactive_cache) do |*args, &block| + block.call(service.calculate_reactive_cache(*args)) + end + end + def read_reactive_cache(subject, *qualifiers) Rails.cache.read(reactive_cache_key(subject, *qualifiers)) end diff --git a/spec/support/shared_examples/controllers/variables_shared_examples.rb b/spec/support/shared_examples/controllers/variables_shared_examples.rb new file mode 100644 index 00000000000..d7acf8c0032 --- /dev/null +++ b/spec/support/shared_examples/controllers/variables_shared_examples.rb @@ -0,0 +1,123 @@ +shared_examples 'GET #show lists all variables' do + it 'renders the variables as json' do + subject + + expect(response).to match_response_schema('variables') + end + + it 'has only one variable' do + subject + + expect(json_response['variables'].count).to eq(1) + end +end + +shared_examples 'PATCH #update updates variables' do + let(:variable_attributes) do + { id: variable.id, + key: variable.key, + value: variable.value, + protected: variable.protected?.to_s } + end + let(:new_variable_attributes) do + { key: 'new_key', + value: 'dummy_value', + protected: 'false' } + end + + context 'with invalid new variable parameters' do + let(:variables_attributes) do + [ + variable_attributes.merge(value: 'other_value'), + new_variable_attributes.merge(key: '...?') + ] + end + + it 'does not update the existing variable' do + expect { subject }.not_to change { variable.reload.value } + end + + it 'does not create the new variable' do + expect { subject }.not_to change { owner.variables.count } + end + + it 'returns a bad request response' do + subject + + expect(response).to have_gitlab_http_status(:bad_request) + end + end + + context 'with duplicate new variable parameters' do + let(:variables_attributes) do + [ + new_variable_attributes, + new_variable_attributes.merge(value: 'other_value') + ] + end + + it 'does not update the existing variable' do + expect { subject }.not_to change { variable.reload.value } + end + + it 'does not create the new variable' do + expect { subject }.not_to change { owner.variables.count } + end + + it 'returns a bad request response' do + subject + + expect(response).to have_gitlab_http_status(:bad_request) + end + end + + context 'with valid new variable parameters' do + let(:variables_attributes) do + [ + variable_attributes.merge(value: 'other_value'), + new_variable_attributes + ] + end + + it 'updates the existing variable' do + expect { subject }.to change { variable.reload.value }.to('other_value') + end + + it 'creates the new variable' do + expect { subject }.to change { owner.variables.count }.by(1) + end + + it 'returns a successful response' do + subject + + expect(response).to have_gitlab_http_status(:ok) + end + + it 'has all variables in response' do + subject + + expect(response).to match_response_schema('variables') + end + end + + context 'with a deleted variable' do + let(:variables_attributes) { [variable_attributes.merge(_destroy: 'true')] } + + it 'destroys the variable' do + expect { subject }.to change { owner.variables.count }.by(-1) + expect { variable.reload }.to raise_error ActiveRecord::RecordNotFound + end + + it 'returns a successful response' do + subject + + expect(response).to have_gitlab_http_status(:ok) + end + + it 'has all variables in response' do + subject + + expect(response).to match_response_schema('variables') + end + end +end diff --git a/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb b/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb index 4e18804b937..9fc2fbef449 100644 --- a/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb @@ -17,12 +17,88 @@ shared_examples 'custom attributes endpoints' do |attributable_name| end end - it 'filters by custom attributes' do - get api("/#{attributable_name}", admin), custom_attributes: { foo: 'foo', bar: 'bar' } + context 'with an authorized user' do + it 'filters by custom attributes' do + get api("/#{attributable_name}", admin), custom_attributes: { foo: 'foo', bar: 'bar' } - expect(response).to have_gitlab_http_status(200) - expect(json_response.size).to be 1 - expect(json_response.first['id']).to eq attributable.id + expect(response).to have_gitlab_http_status(200) + expect(json_response.size).to be 1 + expect(json_response.first['id']).to eq attributable.id + end + end + end + + describe "GET /#{attributable_name} with custom attributes" do + before do + other_attributable + end + + context 'with an unauthorized user' do + it 'does not include custom attributes' do + get api("/#{attributable_name}", user), with_custom_attributes: true + + expect(response).to have_gitlab_http_status(200) + expect(json_response.size).to be 2 + expect(json_response.first).not_to include 'custom_attributes' + end + end + + context 'with an authorized user' do + it 'does not include custom attributes by default' do + get api("/#{attributable_name}", admin) + + expect(response).to have_gitlab_http_status(200) + expect(json_response.size).to be 2 + expect(json_response.first).not_to include 'custom_attributes' + expect(json_response.second).not_to include 'custom_attributes' + end + + it 'includes custom attributes if requested' do + get api("/#{attributable_name}", admin), with_custom_attributes: true + + expect(response).to have_gitlab_http_status(200) + expect(json_response.size).to be 2 + + attributable_response = json_response.find { |r| r['id'] == attributable.id } + other_attributable_response = json_response.find { |r| r['id'] == other_attributable.id } + + expect(attributable_response['custom_attributes']).to contain_exactly( + { 'key' => 'foo', 'value' => 'foo' }, + { 'key' => 'bar', 'value' => 'bar' } + ) + + expect(other_attributable_response['custom_attributes']).to eq [] + end + end + end + + describe "GET /#{attributable_name}/:id with custom attributes" do + context 'with an unauthorized user' do + it 'does not include custom attributes' do + get api("/#{attributable_name}/#{attributable.id}", user), with_custom_attributes: true + + expect(response).to have_gitlab_http_status(200) + expect(json_response).not_to include 'custom_attributes' + end + end + + context 'with an authorized user' do + it 'does not include custom attributes by default' do + get api("/#{attributable_name}/#{attributable.id}", admin) + + expect(response).to have_gitlab_http_status(200) + expect(json_response).not_to include 'custom_attributes' + end + + it 'includes custom attributes if requested' do + get api("/#{attributable_name}/#{attributable.id}", admin), with_custom_attributes: true + + expect(response).to have_gitlab_http_status(200) + expect(json_response['custom_attributes']).to contain_exactly( + { 'key' => 'foo', 'value' => 'foo' }, + { 'key' => 'bar', 'value' => 'bar' } + ) + end end end @@ -33,14 +109,16 @@ shared_examples 'custom attributes endpoints' do |attributable_name| it_behaves_like 'an unauthorized API user' end - it 'returns all custom attributes' do - get api("/#{attributable_name}/#{attributable.id}/custom_attributes", admin) + context 'with an authorized user' do + it 'returns all custom attributes' do + get api("/#{attributable_name}/#{attributable.id}/custom_attributes", admin) - expect(response).to have_gitlab_http_status(200) - expect(json_response).to contain_exactly( - { 'key' => 'foo', 'value' => 'foo' }, - { 'key' => 'bar', 'value' => 'bar' } - ) + expect(response).to have_gitlab_http_status(200) + expect(json_response).to contain_exactly( + { 'key' => 'foo', 'value' => 'foo' }, + { 'key' => 'bar', 'value' => 'bar' } + ) + end end end @@ -51,11 +129,13 @@ shared_examples 'custom attributes endpoints' do |attributable_name| it_behaves_like 'an unauthorized API user' end - it 'returns a single custom attribute' do - get api("/#{attributable_name}/#{attributable.id}/custom_attributes/foo", admin) + context 'with an authorized user' do + it'returns a single custom attribute' do + get api("/#{attributable_name}/#{attributable.id}/custom_attributes/foo", admin) - expect(response).to have_gitlab_http_status(200) - expect(json_response).to eq({ 'key' => 'foo', 'value' => 'foo' }) + expect(response).to have_gitlab_http_status(200) + expect(json_response).to eq({ 'key' => 'foo', 'value' => 'foo' }) + end end end @@ -66,24 +146,26 @@ shared_examples 'custom attributes endpoints' do |attributable_name| it_behaves_like 'an unauthorized API user' end - it 'creates a new custom attribute' do - expect do - put api("/#{attributable_name}/#{attributable.id}/custom_attributes/new", admin), value: 'new' - end.to change { attributable.custom_attributes.count }.by(1) + context 'with an authorized user' do + it 'creates a new custom attribute' do + expect do + put api("/#{attributable_name}/#{attributable.id}/custom_attributes/new", admin), value: 'new' + end.to change { attributable.custom_attributes.count }.by(1) - expect(response).to have_gitlab_http_status(200) - expect(json_response).to eq({ 'key' => 'new', 'value' => 'new' }) - expect(attributable.custom_attributes.find_by(key: 'new').value).to eq 'new' - end + expect(response).to have_gitlab_http_status(200) + expect(json_response).to eq({ 'key' => 'new', 'value' => 'new' }) + expect(attributable.custom_attributes.find_by(key: 'new').value).to eq 'new' + end - it 'updates an existing custom attribute' do - expect do - put api("/#{attributable_name}/#{attributable.id}/custom_attributes/foo", admin), value: 'new' - end.not_to change { attributable.custom_attributes.count } + it 'updates an existing custom attribute' do + expect do + put api("/#{attributable_name}/#{attributable.id}/custom_attributes/foo", admin), value: 'new' + end.not_to change { attributable.custom_attributes.count } - expect(response).to have_gitlab_http_status(200) - expect(json_response).to eq({ 'key' => 'foo', 'value' => 'new' }) - expect(custom_attribute1.reload.value).to eq 'new' + expect(response).to have_gitlab_http_status(200) + expect(json_response).to eq({ 'key' => 'foo', 'value' => 'new' }) + expect(custom_attribute1.reload.value).to eq 'new' + end end end @@ -94,13 +176,15 @@ shared_examples 'custom attributes endpoints' do |attributable_name| it_behaves_like 'an unauthorized API user' end - it 'deletes an existing custom attribute' do - expect do - delete api("/#{attributable_name}/#{attributable.id}/custom_attributes/foo", admin) - end.to change { attributable.custom_attributes.count }.by(-1) + context 'with an authorized user' do + it 'deletes an existing custom attribute' do + expect do + delete api("/#{attributable_name}/#{attributable.id}/custom_attributes/foo", admin) + end.to change { attributable.custom_attributes.count }.by(-1) - expect(response).to have_gitlab_http_status(204) - expect(attributable.custom_attributes.find_by(key: 'foo')).to be_nil + expect(response).to have_gitlab_http_status(204) + expect(attributable.custom_attributes.find_by(key: 'foo')).to be_nil + end end end end diff --git a/spec/support/snippet_visibility.rb b/spec/support/snippet_visibility.rb new file mode 100644 index 00000000000..1cb904823d2 --- /dev/null +++ b/spec/support/snippet_visibility.rb @@ -0,0 +1,304 @@ +RSpec.shared_examples 'snippet visibility' do + let!(:author) { create(:user) } + let!(:member) { create(:user) } + let!(:external) { create(:user, :external) } + + let!(:snippet_type_visibilities) do + { + public: Snippet::PUBLIC, + internal: Snippet::INTERNAL, + private: Snippet::PRIVATE + } + end + + context "For project snippets" do + let!(:users) do + { + unauthenticated: nil, + external: external, + non_member: create(:user), + member: member, + author: author + } + end + + let!(:project_type_visibilities) do + { + public: Gitlab::VisibilityLevel::PUBLIC, + internal: Gitlab::VisibilityLevel::INTERNAL, + private: Gitlab::VisibilityLevel::PRIVATE + } + end + + let(:project_feature_visibilities) do + { + enabled: ProjectFeature::ENABLED, + private: ProjectFeature::PRIVATE, + disabled: ProjectFeature::DISABLED + } + end + + where(:project_type, :feature_visibility, :user_type, :snippet_type, :outcome) do + [ + # Public projects + [:public, :enabled, :unauthenticated, :public, true], + [:public, :enabled, :unauthenticated, :internal, false], + [:public, :enabled, :unauthenticated, :private, false], + + [:public, :enabled, :external, :public, true], + [:public, :enabled, :external, :internal, false], + [:public, :enabled, :external, :private, false], + + [:public, :enabled, :non_member, :public, true], + [:public, :enabled, :non_member, :internal, true], + [:public, :enabled, :non_member, :private, false], + + [:public, :enabled, :member, :public, true], + [:public, :enabled, :member, :internal, true], + [:public, :enabled, :member, :private, true], + + [:public, :enabled, :author, :public, true], + [:public, :enabled, :author, :internal, true], + [:public, :enabled, :author, :private, true], + + [:public, :private, :unauthenticated, :public, false], + [:public, :private, :unauthenticated, :internal, false], + [:public, :private, :unauthenticated, :private, false], + + [:public, :private, :external, :public, false], + [:public, :private, :external, :internal, false], + [:public, :private, :external, :private, false], + + [:public, :private, :non_member, :public, false], + [:public, :private, :non_member, :internal, false], + [:public, :private, :non_member, :private, false], + + [:public, :private, :member, :public, true], + [:public, :private, :member, :internal, true], + [:public, :private, :member, :private, true], + + [:public, :private, :author, :public, true], + [:public, :private, :author, :internal, true], + [:public, :private, :author, :private, true], + + [:public, :disabled, :unauthenticated, :public, false], + [:public, :disabled, :unauthenticated, :internal, false], + [:public, :disabled, :unauthenticated, :private, false], + + [:public, :disabled, :external, :public, false], + [:public, :disabled, :external, :internal, false], + [:public, :disabled, :external, :private, false], + + [:public, :disabled, :non_member, :public, false], + [:public, :disabled, :non_member, :internal, false], + [:public, :disabled, :non_member, :private, false], + + [:public, :disabled, :member, :public, false], + [:public, :disabled, :member, :internal, false], + [:public, :disabled, :member, :private, false], + + [:public, :disabled, :author, :public, false], + [:public, :disabled, :author, :internal, false], + [:public, :disabled, :author, :private, false], + + # Internal projects + [:internal, :enabled, :unauthenticated, :public, false], + [:internal, :enabled, :unauthenticated, :internal, false], + [:internal, :enabled, :unauthenticated, :private, false], + + [:internal, :enabled, :external, :public, false], + [:internal, :enabled, :external, :internal, false], + [:internal, :enabled, :external, :private, false], + + [:internal, :enabled, :non_member, :public, true], + [:internal, :enabled, :non_member, :internal, true], + [:internal, :enabled, :non_member, :private, false], + + [:internal, :enabled, :member, :public, true], + [:internal, :enabled, :member, :internal, true], + [:internal, :enabled, :member, :private, true], + + [:internal, :enabled, :author, :public, true], + [:internal, :enabled, :author, :internal, true], + [:internal, :enabled, :author, :private, true], + + [:internal, :private, :unauthenticated, :public, false], + [:internal, :private, :unauthenticated, :internal, false], + [:internal, :private, :unauthenticated, :private, false], + + [:internal, :private, :external, :public, false], + [:internal, :private, :external, :internal, false], + [:internal, :private, :external, :private, false], + + [:internal, :private, :non_member, :public, false], + [:internal, :private, :non_member, :internal, false], + [:internal, :private, :non_member, :private, false], + + [:internal, :private, :member, :public, true], + [:internal, :private, :member, :internal, true], + [:internal, :private, :member, :private, true], + + [:internal, :private, :author, :public, true], + [:internal, :private, :author, :internal, true], + [:internal, :private, :author, :private, true], + + [:internal, :disabled, :unauthenticated, :public, false], + [:internal, :disabled, :unauthenticated, :internal, false], + [:internal, :disabled, :unauthenticated, :private, false], + + [:internal, :disabled, :external, :public, false], + [:internal, :disabled, :external, :internal, false], + [:internal, :disabled, :external, :private, false], + + [:internal, :disabled, :non_member, :public, false], + [:internal, :disabled, :non_member, :internal, false], + [:internal, :disabled, :non_member, :private, false], + + [:internal, :disabled, :member, :public, false], + [:internal, :disabled, :member, :internal, false], + [:internal, :disabled, :member, :private, false], + + [:internal, :disabled, :author, :public, false], + [:internal, :disabled, :author, :internal, false], + [:internal, :disabled, :author, :private, false], + + # Private projects + [:private, :enabled, :unauthenticated, :public, false], + [:private, :enabled, :unauthenticated, :internal, false], + [:private, :enabled, :unauthenticated, :private, false], + + [:private, :enabled, :external, :public, true], + [:private, :enabled, :external, :internal, true], + [:private, :enabled, :external, :private, true], + + [:private, :enabled, :non_member, :public, false], + [:private, :enabled, :non_member, :internal, false], + [:private, :enabled, :non_member, :private, false], + + [:private, :enabled, :member, :public, true], + [:private, :enabled, :member, :internal, true], + [:private, :enabled, :member, :private, true], + + [:private, :enabled, :author, :public, true], + [:private, :enabled, :author, :internal, true], + [:private, :enabled, :author, :private, true], + + [:private, :private, :unauthenticated, :public, false], + [:private, :private, :unauthenticated, :internal, false], + [:private, :private, :unauthenticated, :private, false], + + [:private, :private, :external, :public, true], + [:private, :private, :external, :internal, true], + [:private, :private, :external, :private, true], + + [:private, :private, :non_member, :public, false], + [:private, :private, :non_member, :internal, false], + [:private, :private, :non_member, :private, false], + + [:private, :private, :member, :public, true], + [:private, :private, :member, :internal, true], + [:private, :private, :member, :private, true], + + [:private, :private, :author, :public, true], + [:private, :private, :author, :internal, true], + [:private, :private, :author, :private, true], + + [:private, :disabled, :unauthenticated, :public, false], + [:private, :disabled, :unauthenticated, :internal, false], + [:private, :disabled, :unauthenticated, :private, false], + + [:private, :disabled, :external, :public, false], + [:private, :disabled, :external, :internal, false], + [:private, :disabled, :external, :private, false], + + [:private, :disabled, :non_member, :public, false], + [:private, :disabled, :non_member, :internal, false], + [:private, :disabled, :non_member, :private, false], + + [:private, :disabled, :member, :public, false], + [:private, :disabled, :member, :internal, false], + [:private, :disabled, :member, :private, false], + + [:private, :disabled, :author, :public, false], + [:private, :disabled, :author, :internal, false], + [:private, :disabled, :author, :private, false] + ] + end + + with_them do + let!(:project) { create(:project, visibility_level: project_type_visibilities[project_type]) } + let!(:project_feature) { project.project_feature.update_column(:snippets_access_level, project_feature_visibilities[feature_visibility]) } + let!(:user) { users[user_type] } + let!(:snippet) { create(:project_snippet, visibility_level: snippet_type_visibilities[snippet_type], project: project, author: author) } + let!(:members) do + project.add_developer(author) + project.add_developer(member) + project.add_developer(external) if project.private? + end + + context "For #{params[:project_type]} project and #{params[:user_type]} users" do + it 'should agree with the read_project_snippet policy' do + expect(can?(user, :read_project_snippet, snippet)).to eq(outcome) + end + + it 'should return proper outcome' do + results = described_class.new(user, project: project).execute + expect(results.include?(snippet)).to eq(outcome) + end + end + + context "Without a given project and #{params[:user_type]} users" do + it 'should return proper outcome' do + results = described_class.new(user).execute + expect(results.include?(snippet)).to eq(outcome) + end + end + end + end + + context 'For personal snippets' do + let!(:users) do + { + unauthenticated: nil, + external: external, + non_member: create(:user), + author: author + } + end + + where(:snippet_visibility, :user_type, :outcome) do + [ + [:public, :unauthenticated, true], + [:public, :external, true], + [:public, :non_member, true], + [:public, :author, true], + + [:internal, :unauthenticated, false], + [:internal, :external, false], + [:internal, :non_member, true], + [:internal, :author, true], + + [:private, :unauthenticated, false], + [:private, :external, false], + [:private, :non_member, false], + [:private, :author, true] + ] + end + + with_them do + let!(:user) { users[user_type] } + let!(:snippet) { create(:personal_snippet, visibility_level: snippet_type_visibilities[snippet_visibility], author: author) } + + context "For personal and #{params[:snippet_visibility]} snippets with #{params[:user_type]} user" do + it 'should agree with read_personal_snippet policy' do + expect(can?(user, :read_personal_snippet, snippet)).to eq(outcome) + end + + it 'should return proper outcome' do + results = described_class.new(user).execute + expect(results.include?(snippet)).to eq(outcome) + end + end + end + end +end diff --git a/spec/support/track_untracked_uploads_helpers.rb b/spec/support/track_untracked_uploads_helpers.rb index 5752078d2a0..a8b3ed1f41c 100644 --- a/spec/support/track_untracked_uploads_helpers.rb +++ b/spec/support/track_untracked_uploads_helpers.rb @@ -8,10 +8,6 @@ module TrackUntrackedUploadsHelpers Gitlab::BackgroundMigration::PrepareUntrackedUploads.new.send(:ensure_temporary_tracking_table_exists) end - def drop_temp_table_if_exists - ActiveRecord::Base.connection.drop_table(:untracked_files_for_uploads) if ActiveRecord::Base.connection.table_exists?(:untracked_files_for_uploads) - end - def create_or_update_appearance(attrs) a = Appearance.first_or_initialize(title: 'foo', description: 'bar') a.update!(attrs) diff --git a/spec/uploaders/file_uploader_spec.rb b/spec/uploaders/file_uploader_spec.rb index 6a92e7fae51..b42ce982b27 100644 --- a/spec/uploaders/file_uploader_spec.rb +++ b/spec/uploaders/file_uploader_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe FileUploader do let(:group) { create(:group, name: 'awesome') } - let(:project) { create(:project, namespace: group, name: 'project') } + let(:project) { create(:project, :legacy_storage, namespace: group, name: 'project') } let(:uploader) { described_class.new(project) } let(:upload) { double(model: project, path: 'secret/foo.jpg') } @@ -16,12 +16,12 @@ describe FileUploader do shared_examples 'uses hashed storage' do context 'when rolled out attachments' do + let(:project) { build_stubbed(:project, namespace: group, name: 'project') } + before do allow(project).to receive(:disk_path).and_return('ca/fe/fe/ed') end - let(:project) { build_stubbed(:project, :hashed, namespace: group, name: 'project') } - it_behaves_like 'builds correct paths', store_dir: %r{ca/fe/fe/ed/\h+}, absolute_path: %r{#{described_class.root}/ca/fe/fe/ed/secret/foo.jpg} diff --git a/spec/validators/variable_duplicates_validator_spec.rb b/spec/validators/variable_duplicates_validator_spec.rb new file mode 100644 index 00000000000..0b71a67f94d --- /dev/null +++ b/spec/validators/variable_duplicates_validator_spec.rb @@ -0,0 +1,67 @@ +require 'spec_helper' + +describe VariableDuplicatesValidator do + let(:validator) { described_class.new(attributes: [:variables], **options) } + + describe '#validate_each' do + let(:project) { build(:project) } + + subject { validator.validate_each(project, :variables, project.variables) } + + context 'with no scope' do + let(:options) { {} } + let(:variables) { build_list(:ci_variable, 2, project: project) } + + before do + project.variables << variables + end + + it 'does not have any errors' do + subject + + expect(project.errors.empty?).to be true + end + + context 'with duplicates' do + before do + project.variables.build(key: variables.first.key, value: 'dummy_value') + end + + it 'has a duplicate key error' do + subject + + expect(project.errors).to have_key(:variables) + end + end + end + + context 'with a scope attribute' do + let(:options) { { scope: :environment_scope } } + let(:first_variable) { build(:ci_variable, key: 'test_key', environment_scope: '*', project: project) } + let(:second_variable) { build(:ci_variable, key: 'test_key', environment_scope: 'prod', project: project) } + + before do + project.variables << first_variable + project.variables << second_variable + end + + it 'does not have any errors' do + subject + + expect(project.errors.empty?).to be true + end + + context 'with duplicates' do + before do + project.variables.build(key: second_variable.key, value: 'dummy_value', environment_scope: second_variable.environment_scope) + end + + it 'has a duplicate key error' do + subject + + expect(project.errors).to have_key(:variables) + end + end + end + end +end diff --git a/spec/workers/check_gcp_project_billing_worker_spec.rb b/spec/workers/check_gcp_project_billing_worker_spec.rb index 7b7a7c1bc44..526ecf75921 100644 --- a/spec/workers/check_gcp_project_billing_worker_spec.rb +++ b/spec/workers/check_gcp_project_billing_worker_spec.rb @@ -6,6 +6,11 @@ describe CheckGcpProjectBillingWorker do subject { described_class.new.perform('token_key') } + before do + allow(described_class).to receive(:get_billing_state) + allow_any_instance_of(described_class).to receive(:update_billing_change_counter) + end + context 'when there is a token in redis' do before do allow(described_class).to receive(:get_session_token).and_return(token) @@ -23,11 +28,8 @@ describe CheckGcpProjectBillingWorker do end it 'stores billing status in redis' do - redis_double = double - expect(CheckGcpProjectBillingService).to receive_message_chain(:new, :execute).and_return([double]) - expect(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis_double) - expect(redis_double).to receive(:set).with(described_class.redis_shared_state_key_for(token), anything, anything) + expect(described_class).to receive(:set_billing_state).with(token, true) subject end @@ -48,7 +50,7 @@ describe CheckGcpProjectBillingWorker do context 'when there is no token in redis' do before do - allow_any_instance_of(described_class).to receive(:get_session_token).and_return(nil) + allow(described_class).to receive(:get_session_token).and_return(nil) end it 'does not call the service' do @@ -58,4 +60,57 @@ describe CheckGcpProjectBillingWorker do end end end + + describe 'billing change counter' do + subject { described_class.new.perform('token_key') } + + before do + allow(described_class).to receive(:get_session_token).and_return('bogustoken') + allow_any_instance_of(described_class).to receive(:try_obtain_lease_for).and_return('randomuuid') + allow(described_class).to receive(:set_billing_state) + end + + context 'when previous state was false' do + before do + expect(described_class).to receive(:get_billing_state).and_return(false) + end + + context 'when the current state is false' do + before do + expect(CheckGcpProjectBillingService).to receive_message_chain(:new, :execute).and_return([]) + end + + it 'increments the billing change counter' do + expect_any_instance_of(described_class).to receive_message_chain(:billing_changed_counter, :increment) + + subject + end + end + + context 'when the current state is true' do + before do + expect(CheckGcpProjectBillingService).to receive_message_chain(:new, :execute).and_return([double]) + end + + it 'increments the billing change counter' do + expect_any_instance_of(described_class).to receive_message_chain(:billing_changed_counter, :increment) + + subject + end + end + end + + context 'when previous state was true' do + before do + expect(described_class).to receive(:get_billing_state).and_return(true) + expect(CheckGcpProjectBillingService).to receive_message_chain(:new, :execute).and_return([double]) + end + + it 'increment the billing change counter' do + expect_any_instance_of(described_class).to receive_message_chain(:billing_changed_counter, :increment) + + subject + end + end + end end diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb index 4912baa348c..6c66658d8c3 100644 --- a/spec/workers/repository_fork_worker_spec.rb +++ b/spec/workers/repository_fork_worker_spec.rb @@ -68,7 +68,7 @@ describe RepositoryForkWorker do end it "handles bad fork" do - error_message = "Unable to fork project #{fork_project.id} for repository #{project.full_path} -> #{fork_project.full_path}" + error_message = "Unable to fork project #{fork_project.id} for repository #{project.disk_path} -> #{fork_project.disk_path}" expect_fork_repository.and_return(false) diff --git a/spec/workers/storage_migrator_worker_spec.rb b/spec/workers/storage_migrator_worker_spec.rb index 8619ff2f7da..ff625164142 100644 --- a/spec/workers/storage_migrator_worker_spec.rb +++ b/spec/workers/storage_migrator_worker_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe StorageMigratorWorker do subject(:worker) { described_class.new } - let(:projects) { create_list(:project, 2) } + let(:projects) { create_list(:project, 2, :legacy_storage) } describe '#perform' do let(:ids) { projects.map(&:id) } diff --git a/vendor/assets/fonts/KaTeX_AMS-Regular.eot b/vendor/assets/fonts/KaTeX_AMS-Regular.eot Binary files differdeleted file mode 100644 index 784276a3cbf..00000000000 --- a/vendor/assets/fonts/KaTeX_AMS-Regular.eot +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_AMS-Regular.ttf b/vendor/assets/fonts/KaTeX_AMS-Regular.ttf Binary files differdeleted file mode 100644 index 6f1e0be2028..00000000000 --- a/vendor/assets/fonts/KaTeX_AMS-Regular.ttf +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_AMS-Regular.woff b/vendor/assets/fonts/KaTeX_AMS-Regular.woff Binary files differdeleted file mode 100644 index 4dded4733b3..00000000000 --- a/vendor/assets/fonts/KaTeX_AMS-Regular.woff +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_AMS-Regular.woff2 b/vendor/assets/fonts/KaTeX_AMS-Regular.woff2 Binary files differdeleted file mode 100644 index ea81079c4e2..00000000000 --- a/vendor/assets/fonts/KaTeX_AMS-Regular.woff2 +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Caligraphic-Bold.eot b/vendor/assets/fonts/KaTeX_Caligraphic-Bold.eot Binary files differdeleted file mode 100644 index 1a0db0c568e..00000000000 --- a/vendor/assets/fonts/KaTeX_Caligraphic-Bold.eot +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Caligraphic-Bold.ttf b/vendor/assets/fonts/KaTeX_Caligraphic-Bold.ttf Binary files differdeleted file mode 100644 index b94907dad11..00000000000 --- a/vendor/assets/fonts/KaTeX_Caligraphic-Bold.ttf +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Caligraphic-Bold.woff b/vendor/assets/fonts/KaTeX_Caligraphic-Bold.woff Binary files differdeleted file mode 100644 index 799fa8122ca..00000000000 --- a/vendor/assets/fonts/KaTeX_Caligraphic-Bold.woff +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Caligraphic-Bold.woff2 b/vendor/assets/fonts/KaTeX_Caligraphic-Bold.woff2 Binary files differdeleted file mode 100644 index 73bb5422878..00000000000 --- a/vendor/assets/fonts/KaTeX_Caligraphic-Bold.woff2 +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Caligraphic-Regular.eot b/vendor/assets/fonts/KaTeX_Caligraphic-Regular.eot Binary files differdeleted file mode 100644 index 6cc83d0922c..00000000000 --- a/vendor/assets/fonts/KaTeX_Caligraphic-Regular.eot +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Caligraphic-Regular.ttf b/vendor/assets/fonts/KaTeX_Caligraphic-Regular.ttf Binary files differdeleted file mode 100644 index cf51e2021e4..00000000000 --- a/vendor/assets/fonts/KaTeX_Caligraphic-Regular.ttf +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Caligraphic-Regular.woff b/vendor/assets/fonts/KaTeX_Caligraphic-Regular.woff Binary files differdeleted file mode 100644 index f5e5c623577..00000000000 --- a/vendor/assets/fonts/KaTeX_Caligraphic-Regular.woff +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Caligraphic-Regular.woff2 b/vendor/assets/fonts/KaTeX_Caligraphic-Regular.woff2 Binary files differdeleted file mode 100644 index dd76d3488d5..00000000000 --- a/vendor/assets/fonts/KaTeX_Caligraphic-Regular.woff2 +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Fraktur-Bold.eot b/vendor/assets/fonts/KaTeX_Fraktur-Bold.eot Binary files differdeleted file mode 100644 index 1960b106656..00000000000 --- a/vendor/assets/fonts/KaTeX_Fraktur-Bold.eot +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Fraktur-Bold.ttf b/vendor/assets/fonts/KaTeX_Fraktur-Bold.ttf Binary files differdeleted file mode 100644 index 7b0790f1ae8..00000000000 --- a/vendor/assets/fonts/KaTeX_Fraktur-Bold.ttf +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Fraktur-Bold.woff b/vendor/assets/fonts/KaTeX_Fraktur-Bold.woff Binary files differdeleted file mode 100644 index dc325713291..00000000000 --- a/vendor/assets/fonts/KaTeX_Fraktur-Bold.woff +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Fraktur-Bold.woff2 b/vendor/assets/fonts/KaTeX_Fraktur-Bold.woff2 Binary files differdeleted file mode 100644 index fdc429227ad..00000000000 --- a/vendor/assets/fonts/KaTeX_Fraktur-Bold.woff2 +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Fraktur-Regular.eot b/vendor/assets/fonts/KaTeX_Fraktur-Regular.eot Binary files differdeleted file mode 100644 index e4e73796aea..00000000000 --- a/vendor/assets/fonts/KaTeX_Fraktur-Regular.eot +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Fraktur-Regular.ttf b/vendor/assets/fonts/KaTeX_Fraktur-Regular.ttf Binary files differdeleted file mode 100644 index 063bc0263eb..00000000000 --- a/vendor/assets/fonts/KaTeX_Fraktur-Regular.ttf +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Fraktur-Regular.woff b/vendor/assets/fonts/KaTeX_Fraktur-Regular.woff Binary files differdeleted file mode 100644 index c4b18d863f3..00000000000 --- a/vendor/assets/fonts/KaTeX_Fraktur-Regular.woff +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Fraktur-Regular.woff2 b/vendor/assets/fonts/KaTeX_Fraktur-Regular.woff2 Binary files differdeleted file mode 100644 index 4318d938e26..00000000000 --- a/vendor/assets/fonts/KaTeX_Fraktur-Regular.woff2 +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Main-Bold.eot b/vendor/assets/fonts/KaTeX_Main-Bold.eot Binary files differdeleted file mode 100644 index 80fbd022363..00000000000 --- a/vendor/assets/fonts/KaTeX_Main-Bold.eot +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Main-Bold.ttf b/vendor/assets/fonts/KaTeX_Main-Bold.ttf Binary files differdeleted file mode 100644 index 8e10722afae..00000000000 --- a/vendor/assets/fonts/KaTeX_Main-Bold.ttf +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Main-Bold.woff b/vendor/assets/fonts/KaTeX_Main-Bold.woff Binary files differdeleted file mode 100644 index 43b361a6005..00000000000 --- a/vendor/assets/fonts/KaTeX_Main-Bold.woff +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Main-Bold.woff2 b/vendor/assets/fonts/KaTeX_Main-Bold.woff2 Binary files differdeleted file mode 100644 index af57a96c148..00000000000 --- a/vendor/assets/fonts/KaTeX_Main-Bold.woff2 +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Main-Italic.eot b/vendor/assets/fonts/KaTeX_Main-Italic.eot Binary files differdeleted file mode 100644 index fc770166b5e..00000000000 --- a/vendor/assets/fonts/KaTeX_Main-Italic.eot +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Main-Italic.ttf b/vendor/assets/fonts/KaTeX_Main-Italic.ttf Binary files differdeleted file mode 100644 index d124495d7b6..00000000000 --- a/vendor/assets/fonts/KaTeX_Main-Italic.ttf +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Main-Italic.woff b/vendor/assets/fonts/KaTeX_Main-Italic.woff Binary files differdeleted file mode 100644 index e623236bc44..00000000000 --- a/vendor/assets/fonts/KaTeX_Main-Italic.woff +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Main-Italic.woff2 b/vendor/assets/fonts/KaTeX_Main-Italic.woff2 Binary files differdeleted file mode 100644 index 944e9740bdf..00000000000 --- a/vendor/assets/fonts/KaTeX_Main-Italic.woff2 +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Main-Regular.eot b/vendor/assets/fonts/KaTeX_Main-Regular.eot Binary files differdeleted file mode 100644 index dc60c090c7a..00000000000 --- a/vendor/assets/fonts/KaTeX_Main-Regular.eot +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Main-Regular.ttf b/vendor/assets/fonts/KaTeX_Main-Regular.ttf Binary files differdeleted file mode 100644 index da5797ffcce..00000000000 --- a/vendor/assets/fonts/KaTeX_Main-Regular.ttf +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Main-Regular.woff b/vendor/assets/fonts/KaTeX_Main-Regular.woff Binary files differdeleted file mode 100644 index 37db672e821..00000000000 --- a/vendor/assets/fonts/KaTeX_Main-Regular.woff +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Main-Regular.woff2 b/vendor/assets/fonts/KaTeX_Main-Regular.woff2 Binary files differdeleted file mode 100644 index 48820424893..00000000000 --- a/vendor/assets/fonts/KaTeX_Main-Regular.woff2 +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Math-BoldItalic.eot b/vendor/assets/fonts/KaTeX_Math-BoldItalic.eot Binary files differdeleted file mode 100644 index 52c8b8c6b40..00000000000 --- a/vendor/assets/fonts/KaTeX_Math-BoldItalic.eot +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Math-BoldItalic.ttf b/vendor/assets/fonts/KaTeX_Math-BoldItalic.ttf Binary files differdeleted file mode 100644 index a8b527c7ef6..00000000000 --- a/vendor/assets/fonts/KaTeX_Math-BoldItalic.ttf +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Math-BoldItalic.woff b/vendor/assets/fonts/KaTeX_Math-BoldItalic.woff Binary files differdeleted file mode 100644 index 8940e0b5801..00000000000 --- a/vendor/assets/fonts/KaTeX_Math-BoldItalic.woff +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Math-BoldItalic.woff2 b/vendor/assets/fonts/KaTeX_Math-BoldItalic.woff2 Binary files differdeleted file mode 100644 index 15cf56d3408..00000000000 --- a/vendor/assets/fonts/KaTeX_Math-BoldItalic.woff2 +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Math-Italic.eot b/vendor/assets/fonts/KaTeX_Math-Italic.eot Binary files differdeleted file mode 100644 index 64c8992c477..00000000000 --- a/vendor/assets/fonts/KaTeX_Math-Italic.eot +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Math-Italic.ttf b/vendor/assets/fonts/KaTeX_Math-Italic.ttf Binary files differdeleted file mode 100644 index 06f39d3a299..00000000000 --- a/vendor/assets/fonts/KaTeX_Math-Italic.ttf +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Math-Italic.woff b/vendor/assets/fonts/KaTeX_Math-Italic.woff Binary files differdeleted file mode 100644 index cf3b4b79e5b..00000000000 --- a/vendor/assets/fonts/KaTeX_Math-Italic.woff +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Math-Italic.woff2 b/vendor/assets/fonts/KaTeX_Math-Italic.woff2 Binary files differdeleted file mode 100644 index 5f8c4bfa455..00000000000 --- a/vendor/assets/fonts/KaTeX_Math-Italic.woff2 +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Math-Regular.eot b/vendor/assets/fonts/KaTeX_Math-Regular.eot Binary files differdeleted file mode 100644 index 5521e6a564d..00000000000 --- a/vendor/assets/fonts/KaTeX_Math-Regular.eot +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Math-Regular.ttf b/vendor/assets/fonts/KaTeX_Math-Regular.ttf Binary files differdeleted file mode 100644 index 73127082370..00000000000 --- a/vendor/assets/fonts/KaTeX_Math-Regular.ttf +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Math-Regular.woff b/vendor/assets/fonts/KaTeX_Math-Regular.woff Binary files differdeleted file mode 100644 index 0e2ebdf18af..00000000000 --- a/vendor/assets/fonts/KaTeX_Math-Regular.woff +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Math-Regular.woff2 b/vendor/assets/fonts/KaTeX_Math-Regular.woff2 Binary files differdeleted file mode 100644 index ebe3d028a34..00000000000 --- a/vendor/assets/fonts/KaTeX_Math-Regular.woff2 +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_SansSerif-Bold.eot b/vendor/assets/fonts/KaTeX_SansSerif-Bold.eot Binary files differdeleted file mode 100644 index 1660e76a2b6..00000000000 --- a/vendor/assets/fonts/KaTeX_SansSerif-Bold.eot +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_SansSerif-Bold.ttf b/vendor/assets/fonts/KaTeX_SansSerif-Bold.ttf Binary files differdeleted file mode 100644 index dbeb7b92ab5..00000000000 --- a/vendor/assets/fonts/KaTeX_SansSerif-Bold.ttf +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_SansSerif-Bold.woff b/vendor/assets/fonts/KaTeX_SansSerif-Bold.woff Binary files differdeleted file mode 100644 index 8f144a8bb31..00000000000 --- a/vendor/assets/fonts/KaTeX_SansSerif-Bold.woff +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_SansSerif-Bold.woff2 b/vendor/assets/fonts/KaTeX_SansSerif-Bold.woff2 Binary files differdeleted file mode 100644 index 329e85557fa..00000000000 --- a/vendor/assets/fonts/KaTeX_SansSerif-Bold.woff2 +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_SansSerif-Italic.eot b/vendor/assets/fonts/KaTeX_SansSerif-Italic.eot Binary files differdeleted file mode 100644 index 289ae3ff8b7..00000000000 --- a/vendor/assets/fonts/KaTeX_SansSerif-Italic.eot +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_SansSerif-Italic.ttf b/vendor/assets/fonts/KaTeX_SansSerif-Italic.ttf Binary files differdeleted file mode 100644 index b3a2f38f224..00000000000 --- a/vendor/assets/fonts/KaTeX_SansSerif-Italic.ttf +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_SansSerif-Italic.woff b/vendor/assets/fonts/KaTeX_SansSerif-Italic.woff Binary files differdeleted file mode 100644 index bddf7ea6579..00000000000 --- a/vendor/assets/fonts/KaTeX_SansSerif-Italic.woff +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_SansSerif-Italic.woff2 b/vendor/assets/fonts/KaTeX_SansSerif-Italic.woff2 Binary files differdeleted file mode 100644 index 5fa767bddd6..00000000000 --- a/vendor/assets/fonts/KaTeX_SansSerif-Italic.woff2 +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_SansSerif-Regular.eot b/vendor/assets/fonts/KaTeX_SansSerif-Regular.eot Binary files differdeleted file mode 100644 index 1b38b98a180..00000000000 --- a/vendor/assets/fonts/KaTeX_SansSerif-Regular.eot +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_SansSerif-Regular.ttf b/vendor/assets/fonts/KaTeX_SansSerif-Regular.ttf Binary files differdeleted file mode 100644 index e4712f84775..00000000000 --- a/vendor/assets/fonts/KaTeX_SansSerif-Regular.ttf +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_SansSerif-Regular.woff b/vendor/assets/fonts/KaTeX_SansSerif-Regular.woff Binary files differdeleted file mode 100644 index 33be368048f..00000000000 --- a/vendor/assets/fonts/KaTeX_SansSerif-Regular.woff +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_SansSerif-Regular.woff2 b/vendor/assets/fonts/KaTeX_SansSerif-Regular.woff2 Binary files differdeleted file mode 100644 index 4fcb2e29a05..00000000000 --- a/vendor/assets/fonts/KaTeX_SansSerif-Regular.woff2 +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Script-Regular.eot b/vendor/assets/fonts/KaTeX_Script-Regular.eot Binary files differdeleted file mode 100644 index 7870d7f319b..00000000000 --- a/vendor/assets/fonts/KaTeX_Script-Regular.eot +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Script-Regular.ttf b/vendor/assets/fonts/KaTeX_Script-Regular.ttf Binary files differdeleted file mode 100644 index da4d11308ae..00000000000 --- a/vendor/assets/fonts/KaTeX_Script-Regular.ttf +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Script-Regular.woff b/vendor/assets/fonts/KaTeX_Script-Regular.woff Binary files differdeleted file mode 100644 index d6ae79f998a..00000000000 --- a/vendor/assets/fonts/KaTeX_Script-Regular.woff +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Script-Regular.woff2 b/vendor/assets/fonts/KaTeX_Script-Regular.woff2 Binary files differdeleted file mode 100644 index 1b43deb45a8..00000000000 --- a/vendor/assets/fonts/KaTeX_Script-Regular.woff2 +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Size1-Regular.eot b/vendor/assets/fonts/KaTeX_Size1-Regular.eot Binary files differdeleted file mode 100644 index 29950f95ff6..00000000000 --- a/vendor/assets/fonts/KaTeX_Size1-Regular.eot +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Size1-Regular.ttf b/vendor/assets/fonts/KaTeX_Size1-Regular.ttf Binary files differdeleted file mode 100644 index 194466a655d..00000000000 --- a/vendor/assets/fonts/KaTeX_Size1-Regular.ttf +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Size1-Regular.woff b/vendor/assets/fonts/KaTeX_Size1-Regular.woff Binary files differdeleted file mode 100644 index 237f271edd1..00000000000 --- a/vendor/assets/fonts/KaTeX_Size1-Regular.woff +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Size1-Regular.woff2 b/vendor/assets/fonts/KaTeX_Size1-Regular.woff2 Binary files differdeleted file mode 100644 index 39b6f8f746c..00000000000 --- a/vendor/assets/fonts/KaTeX_Size1-Regular.woff2 +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Size2-Regular.eot b/vendor/assets/fonts/KaTeX_Size2-Regular.eot Binary files differdeleted file mode 100644 index b8b0536f967..00000000000 --- a/vendor/assets/fonts/KaTeX_Size2-Regular.eot +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Size2-Regular.ttf b/vendor/assets/fonts/KaTeX_Size2-Regular.ttf Binary files differdeleted file mode 100644 index b41b66a638f..00000000000 --- a/vendor/assets/fonts/KaTeX_Size2-Regular.ttf +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Size2-Regular.woff b/vendor/assets/fonts/KaTeX_Size2-Regular.woff Binary files differdeleted file mode 100644 index 4a3055854ed..00000000000 --- a/vendor/assets/fonts/KaTeX_Size2-Regular.woff +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Size2-Regular.woff2 b/vendor/assets/fonts/KaTeX_Size2-Regular.woff2 Binary files differdeleted file mode 100644 index 3facec1ab89..00000000000 --- a/vendor/assets/fonts/KaTeX_Size2-Regular.woff2 +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Size3-Regular.eot b/vendor/assets/fonts/KaTeX_Size3-Regular.eot Binary files differdeleted file mode 100644 index 576b864fae6..00000000000 --- a/vendor/assets/fonts/KaTeX_Size3-Regular.eot +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Size3-Regular.ttf b/vendor/assets/fonts/KaTeX_Size3-Regular.ttf Binary files differdeleted file mode 100644 index 790ddbbc55f..00000000000 --- a/vendor/assets/fonts/KaTeX_Size3-Regular.ttf +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Size3-Regular.woff b/vendor/assets/fonts/KaTeX_Size3-Regular.woff Binary files differdeleted file mode 100644 index 3a6d062e660..00000000000 --- a/vendor/assets/fonts/KaTeX_Size3-Regular.woff +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Size3-Regular.woff2 b/vendor/assets/fonts/KaTeX_Size3-Regular.woff2 Binary files differdeleted file mode 100644 index 2cffafe5018..00000000000 --- a/vendor/assets/fonts/KaTeX_Size3-Regular.woff2 +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Size4-Regular.eot b/vendor/assets/fonts/KaTeX_Size4-Regular.eot Binary files differdeleted file mode 100644 index c2b045fc3db..00000000000 --- a/vendor/assets/fonts/KaTeX_Size4-Regular.eot +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Size4-Regular.ttf b/vendor/assets/fonts/KaTeX_Size4-Regular.ttf Binary files differdeleted file mode 100644 index ce660aa7ff9..00000000000 --- a/vendor/assets/fonts/KaTeX_Size4-Regular.ttf +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Size4-Regular.woff b/vendor/assets/fonts/KaTeX_Size4-Regular.woff Binary files differdeleted file mode 100644 index 7826c6c97a1..00000000000 --- a/vendor/assets/fonts/KaTeX_Size4-Regular.woff +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Size4-Regular.woff2 b/vendor/assets/fonts/KaTeX_Size4-Regular.woff2 Binary files differdeleted file mode 100644 index c92189812d9..00000000000 --- a/vendor/assets/fonts/KaTeX_Size4-Regular.woff2 +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Typewriter-Regular.eot b/vendor/assets/fonts/KaTeX_Typewriter-Regular.eot Binary files differdeleted file mode 100644 index 4c178f484a8..00000000000 --- a/vendor/assets/fonts/KaTeX_Typewriter-Regular.eot +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Typewriter-Regular.ttf b/vendor/assets/fonts/KaTeX_Typewriter-Regular.ttf Binary files differdeleted file mode 100644 index b0427ad0a56..00000000000 --- a/vendor/assets/fonts/KaTeX_Typewriter-Regular.ttf +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Typewriter-Regular.woff b/vendor/assets/fonts/KaTeX_Typewriter-Regular.woff Binary files differdeleted file mode 100644 index 78e990488a9..00000000000 --- a/vendor/assets/fonts/KaTeX_Typewriter-Regular.woff +++ /dev/null diff --git a/vendor/assets/fonts/KaTeX_Typewriter-Regular.woff2 b/vendor/assets/fonts/KaTeX_Typewriter-Regular.woff2 Binary files differdeleted file mode 100644 index 618de99d480..00000000000 --- a/vendor/assets/fonts/KaTeX_Typewriter-Regular.woff2 +++ /dev/null diff --git a/vendor/assets/javascripts/jquery.waitforimages.js b/vendor/assets/javascripts/jquery.waitforimages.js deleted file mode 100644 index 95b39c2e074..00000000000 --- a/vendor/assets/javascripts/jquery.waitforimages.js +++ /dev/null @@ -1,144 +0,0 @@ -/* - * waitForImages 1.4 - * ----------------- - * Provides a callback when all images have loaded in your given selector. - * http://www.alexanderdickson.com/ - * - * - * Copyright (c) 2011 Alex Dickson - * Licensed under the MIT licenses. - * See website for more info. - * - */ - -;(function($) { - // Namespace all events. - var eventNamespace = 'waitForImages'; - - // CSS properties which contain references to images. - $.waitForImages = { - hasImageProperties: [ - 'backgroundImage', - 'listStyleImage', - 'borderImage', - 'borderCornerImage' - ] - }; - - // Custom selector to find `img` elements that have a valid `src` attribute and have not already loaded. - $.expr[':'].uncached = function(obj) { - // Ensure we are dealing with an `img` element with a valid `src` attribute. - if ( ! $(obj).is('img[src!=""]')) { - return false; - } - - // Firefox's `complete` property will always be`true` even if the image has not been downloaded. - // Doing it this way works in Firefox. - var img = document.createElement('img'); - img.src = obj.src; - return ! img.complete; - }; - - $.fn.waitForImages = function(finishedCallback, eachCallback, waitForAll) { - - // Handle options object. - if ($.isPlainObject(arguments[0])) { - eachCallback = finishedCallback.each; - waitForAll = finishedCallback.waitForAll; - finishedCallback = finishedCallback.finished; - } - - // Handle missing callbacks. - finishedCallback = finishedCallback || $.noop; - eachCallback = eachCallback || $.noop; - - // Convert waitForAll to Boolean - waitForAll = !! waitForAll; - - // Ensure callbacks are functions. - if (!$.isFunction(finishedCallback) || !$.isFunction(eachCallback)) { - throw new TypeError('An invalid callback was supplied.'); - }; - - return this.each(function() { - // Build a list of all imgs, dependent on what images will be considered. - var obj = $(this), - allImgs = []; - - if (waitForAll) { - // CSS properties which may contain an image. - var hasImgProperties = $.waitForImages.hasImageProperties || [], - matchUrl = /url\((['"]?)(.*?)\1\)/g; - - // Get all elements, as any one of them could have a background image. - obj.find('*').each(function() { - var element = $(this); - - // If an `img` element, add it. But keep iterating in case it has a background image too. - if (element.is('img:uncached')) { - allImgs.push({ - src: element.attr('src'), - element: element[0] - }); - } - - $.each(hasImgProperties, function(i, property) { - var propertyValue = element.css(property); - // If it doesn't contain this property, skip. - if ( ! propertyValue) { - return true; - } - - // Get all url() of this element. - var match; - while (match = matchUrl.exec(propertyValue)) { - allImgs.push({ - src: match[2], - element: element[0] - }); - }; - }); - }); - } else { - // For images only, the task is simpler. - obj - .find('img:uncached') - .each(function() { - allImgs.push({ - src: this.src, - element: this - }); - }); - }; - - var allImgsLength = allImgs.length, - allImgsLoaded = 0; - - // If no images found, don't bother. - if (allImgsLength == 0) { - finishedCallback.call(obj[0]); - }; - - $.each(allImgs, function(i, img) { - - var image = new Image; - - // Handle the image loading and error with the same callback. - $(image).bind('load.' + eventNamespace + ' error.' + eventNamespace, function(event) { - allImgsLoaded++; - - // If an error occurred with loading the image, set the third argument accordingly. - eachCallback.call(img.element, allImgsLoaded, allImgsLength, event.type == 'load'); - - if (allImgsLoaded == allImgsLength) { - finishedCallback.call(obj[0]); - return false; - }; - - }); - - image.src = img.src; - }); - }); - }; -})(jQuery); diff --git a/vendor/assets/javascripts/katex.js b/vendor/assets/javascripts/katex.js deleted file mode 100644 index 6b59a3477a7..00000000000 --- a/vendor/assets/javascripts/katex.js +++ /dev/null @@ -1,8685 +0,0 @@ -/* - The MIT License (MIT) - - Copyright (c) 2015 Khan Academy - - This software also uses portions of the underscore.js project, which is - MIT licensed with the following copyright: - - Copyright (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative - Reporters & Editors - - 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: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - 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. - */ - -/* - Here is how to build a version of KaTeX that works with gitlab. - - The problem is that the standard procedure for changing font location doesn't work for the empty string. - - 1. Clone KaTeX. Anything later than 4fb9445a9 (is merged into master) will do. - 2. make (requires node) - 3. sed -e 's,fonts/,,' -e 's/url\(([^)]*)\)/url(font-path\1)/g' build/katex.css > build/katex.scss - 4. Copy build/katex.js to gitlab/vendor/assets/javascripts/katex.js, - build/katex.scss to gitlab/vendor/assets/stylesheets/katex.scss and - fonts/* to gitlab/vendor/assets/fonts/. -*/ - -(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.katex = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ -/* eslint no-console:0 */ -/** - * This is the main entry point for KaTeX. Here, we expose functions for - * rendering expressions either to DOM nodes or to markup strings. - * - * We also expose the ParseError class to check if errors thrown from KaTeX are - * errors in the expression, or errors in javascript handling. - */ - -var ParseError = require("./src/ParseError"); -var Settings = require("./src/Settings"); - -var buildTree = require("./src/buildTree"); -var parseTree = require("./src/parseTree"); -var utils = require("./src/utils"); - -/** - * Parse and build an expression, and place that expression in the DOM node - * given. - */ -var render = function(expression, baseNode, options) { - utils.clearNode(baseNode); - - var settings = new Settings(options); - - var tree = parseTree(expression, settings); - var node = buildTree(tree, expression, settings).toNode(); - - baseNode.appendChild(node); -}; - -// KaTeX's styles don't work properly in quirks mode. Print out an error, and -// disable rendering. -if (typeof document !== "undefined") { - if (document.compatMode !== "CSS1Compat") { - typeof console !== "undefined" && console.warn( - "Warning: KaTeX doesn't work in quirks mode. Make sure your " + - "website has a suitable doctype."); - - render = function() { - throw new ParseError("KaTeX doesn't work in quirks mode."); - }; - } -} - -/** - * Parse and build an expression, and return the markup for that. - */ -var renderToString = function(expression, options) { - var settings = new Settings(options); - - var tree = parseTree(expression, settings); - return buildTree(tree, expression, settings).toMarkup(); -}; - -/** - * Parse an expression and return the parse tree. - */ -var generateParseTree = function(expression, options) { - var settings = new Settings(options); - return parseTree(expression, settings); -}; - -module.exports = { - render: render, - renderToString: renderToString, - /** - * NOTE: This method is not currently recommended for public use. - * The internal tree representation is unstable and is very likely - * to change. Use at your own risk. - */ - __parse: generateParseTree, - ParseError: ParseError, -}; - -},{"./src/ParseError":6,"./src/Settings":8,"./src/buildTree":13,"./src/parseTree":22,"./src/utils":25}],2:[function(require,module,exports){ -/** @flow */ - -"use strict"; - -function getRelocatable(re) { - // In the future, this could use a WeakMap instead of an expando. - if (!re.__matchAtRelocatable) { - // Disjunctions are the lowest-precedence operator, so we can make any - // pattern match the empty string by appending `|()` to it: - // https://people.mozilla.org/~jorendorff/es6-draft.html#sec-patterns - var source = re.source + "|()"; - - // We always make the new regex global. - var flags = "g" + (re.ignoreCase ? "i" : "") + (re.multiline ? "m" : "") + (re.unicode ? "u" : "") - // sticky (/.../y) doesn't make sense in conjunction with our relocation - // logic, so we ignore it here. - ; - - re.__matchAtRelocatable = new RegExp(source, flags); - } - return re.__matchAtRelocatable; -} - -function matchAt(re, str, pos) { - if (re.global || re.sticky) { - throw new Error("matchAt(...): Only non-global regexes are supported"); - } - var reloc = getRelocatable(re); - reloc.lastIndex = pos; - var match = reloc.exec(str); - // Last capturing group is our sentinel that indicates whether the regex - // matched at the given location. - if (match[match.length - 1] == null) { - // Original regex matched. - match.length = match.length - 1; - return match; - } else { - return null; - } -} - -module.exports = matchAt; -},{}],3:[function(require,module,exports){ -/** - * The Lexer class handles tokenizing the input in various ways. Since our - * parser expects us to be able to backtrack, the lexer allows lexing from any - * given starting point. - * - * Its main exposed function is the `lex` function, which takes a position to - * lex from and a type of token to lex. It defers to the appropriate `_innerLex` - * function. - * - * The various `_innerLex` functions perform the actual lexing of different - * kinds. - */ - -var matchAt = require("match-at"); - -var ParseError = require("./ParseError"); - -// The main lexer class -function Lexer(input) { - this.input = input; - this.pos = 0; -} - -/** - * The resulting token returned from `lex`. - * - * It consists of the token text plus some position information. - * The position information is essentially a range in an input string, - * but instead of referencing the bare input string, we refer to the lexer. - * That way it is possible to attach extra metadata to the input string, - * like for example a file name or similar. - * - * The position information (all three parameters) is optional, - * so it is OK to construct synthetic tokens if appropriate. - * Not providing available position information may lead to - * degraded error reporting, though. - * - * @param {string} text the text of this token - * @param {number=} start the start offset, zero-based inclusive - * @param {number=} end the end offset, zero-based exclusive - * @param {Lexer=} lexer the lexer which in turn holds the input string - */ -function Token(text, start, end, lexer) { - this.text = text; - this.start = start; - this.end = end; - this.lexer = lexer; -} - -/** - * Given a pair of tokens (this and endToken), compute a “Token†encompassing - * the whole input range enclosed by these two. - * - * @param {Token} endToken last token of the range, inclusive - * @param {string} text the text of the newly constructed token - */ -Token.prototype.range = function(endToken, text) { - if (endToken.lexer !== this.lexer) { - return new Token(text); // sorry, no position information available - } - return new Token(text, this.start, endToken.end, this.lexer); -}; - -/* The following tokenRegex - * - matches typical whitespace (but not NBSP etc.) using its first group - * - does not match any control character \x00-\x1f except whitespace - * - does not match a bare backslash - * - matches any ASCII character except those just mentioned - * - does not match the BMP private use area \uE000-\uF8FF - * - does not match bare surrogate code units - * - matches any BMP character except for those just described - * - matches any valid Unicode surrogate pair - * - matches a backslash followed by one or more letters - * - matches a backslash followed by any BMP character, including newline - * Just because the Lexer matches something doesn't mean it's valid input: - * If there is no matching function or symbol definition, the Parser will - * still reject the input. - */ -var tokenRegex = new RegExp( - "([ \r\n\t]+)|" + // whitespace - "([!-\\[\\]-\u2027\u202A-\uD7FF\uF900-\uFFFF]" + // single codepoint - "|[\uD800-\uDBFF][\uDC00-\uDFFF]" + // surrogate pair - "|\\\\(?:[a-zA-Z]+|[^\uD800-\uDFFF])" + // function name - ")" -); - -/** - * This function lexes a single token. - */ -Lexer.prototype.lex = function() { - var input = this.input; - var pos = this.pos; - if (pos === input.length) { - return new Token("EOF", pos, pos, this); - } - var match = matchAt(tokenRegex, input, pos); - if (match === null) { - throw new ParseError( - "Unexpected character: '" + input[pos] + "'", - new Token(input[pos], pos, pos + 1, this)); - } - var text = match[2] || " "; - var start = this.pos; - this.pos += match[0].length; - var end = this.pos; - return new Token(text, start, end, this); -}; - -module.exports = Lexer; - -},{"./ParseError":6,"match-at":2}],4:[function(require,module,exports){ -/** - * This file contains the “gullet†where macros are expanded - * until only non-macro tokens remain. - */ - -var Lexer = require("./Lexer"); - -function MacroExpander(input, macros) { - this.lexer = new Lexer(input); - this.macros = macros; - this.stack = []; // contains tokens in REVERSE order - this.discardedWhiteSpace = []; -} - -/** - * Recursively expand first token, then return first non-expandable token. - */ -MacroExpander.prototype.nextToken = function() { - for (;;) { - if (this.stack.length === 0) { - this.stack.push(this.lexer.lex()); - } - var topToken = this.stack.pop(); - var name = topToken.text; - if (!(name.charAt(0) === "\\" && this.macros.hasOwnProperty(name))) { - return topToken; - } - var expansion = this.macros[name]; - if (typeof expansion === "string") { - var bodyLexer = new Lexer(expansion); - expansion = []; - var tok = bodyLexer.lex(); - while (tok.text !== "EOF") { - expansion.push(tok); - tok = bodyLexer.lex(); - } - expansion.reverse(); // to fit in with stack using push and pop - this.macros[name] = expansion; - } - this.stack = this.stack.concat(expansion); - } -}; - -MacroExpander.prototype.get = function(ignoreSpace) { - this.discardedWhiteSpace = []; - var token = this.nextToken(); - if (ignoreSpace) { - while (token.text === " ") { - this.discardedWhiteSpace.push(token); - token = this.nextToken(); - } - } - return token; -}; - -/** - * Undo the effect of the preceding call to the get method. - * A call to this method MUST be immediately preceded and immediately followed - * by a call to get. Only used during mode switching, i.e. after one token - * was got in the old mode but should get got again in a new mode - * with possibly different whitespace handling. - */ -MacroExpander.prototype.unget = function(token) { - this.stack.push(token); - while (this.discardedWhiteSpace.length !== 0) { - this.stack.push(this.discardedWhiteSpace.pop()); - } -}; - -module.exports = MacroExpander; - -},{"./Lexer":3}],5:[function(require,module,exports){ -/** - * This file contains information about the options that the Parser carries - * around with it while parsing. Data is held in an `Options` object, and when - * recursing, a new `Options` object can be created with the `.with*` and - * `.reset` functions. - */ - -/** - * This is the main options class. It contains the style, size, color, and font - * of the current parse level. It also contains the style and size of the parent - * parse level, so size changes can be handled efficiently. - * - * Each of the `.with*` and `.reset` functions passes its current style and size - * as the parentStyle and parentSize of the new options class, so parent - * handling is taken care of automatically. - */ -function Options(data) { - this.style = data.style; - this.color = data.color; - this.size = data.size; - this.phantom = data.phantom; - this.font = data.font; - - if (data.parentStyle === undefined) { - this.parentStyle = data.style; - } else { - this.parentStyle = data.parentStyle; - } - - if (data.parentSize === undefined) { - this.parentSize = data.size; - } else { - this.parentSize = data.parentSize; - } -} - -/** - * Returns a new options object with the same properties as "this". Properties - * from "extension" will be copied to the new options object. - */ -Options.prototype.extend = function(extension) { - var data = { - style: this.style, - size: this.size, - color: this.color, - parentStyle: this.style, - parentSize: this.size, - phantom: this.phantom, - font: this.font, - }; - - for (var key in extension) { - if (extension.hasOwnProperty(key)) { - data[key] = extension[key]; - } - } - - return new Options(data); -}; - -/** - * Create a new options object with the given style. - */ -Options.prototype.withStyle = function(style) { - return this.extend({ - style: style, - }); -}; - -/** - * Create a new options object with the given size. - */ -Options.prototype.withSize = function(size) { - return this.extend({ - size: size, - }); -}; - -/** - * Create a new options object with the given color. - */ -Options.prototype.withColor = function(color) { - return this.extend({ - color: color, - }); -}; - -/** - * Create a new options object with "phantom" set to true. - */ -Options.prototype.withPhantom = function() { - return this.extend({ - phantom: true, - }); -}; - -/** - * Create a new options objects with the give font. - */ -Options.prototype.withFont = function(font) { - return this.extend({ - font: font, - }); -}; - -/** - * Create a new options object with the same style, size, and color. This is - * used so that parent style and size changes are handled correctly. - */ -Options.prototype.reset = function() { - return this.extend({}); -}; - -/** - * A map of color names to CSS colors. - * TODO(emily): Remove this when we have real macros - */ -var colorMap = { - "katex-blue": "#6495ed", - "katex-orange": "#ffa500", - "katex-pink": "#ff00af", - "katex-red": "#df0030", - "katex-green": "#28ae7b", - "katex-gray": "gray", - "katex-purple": "#9d38bd", - "katex-blueA": "#ccfaff", - "katex-blueB": "#80f6ff", - "katex-blueC": "#63d9ea", - "katex-blueD": "#11accd", - "katex-blueE": "#0c7f99", - "katex-tealA": "#94fff5", - "katex-tealB": "#26edd5", - "katex-tealC": "#01d1c1", - "katex-tealD": "#01a995", - "katex-tealE": "#208170", - "katex-greenA": "#b6ffb0", - "katex-greenB": "#8af281", - "katex-greenC": "#74cf70", - "katex-greenD": "#1fab54", - "katex-greenE": "#0d923f", - "katex-goldA": "#ffd0a9", - "katex-goldB": "#ffbb71", - "katex-goldC": "#ff9c39", - "katex-goldD": "#e07d10", - "katex-goldE": "#a75a05", - "katex-redA": "#fca9a9", - "katex-redB": "#ff8482", - "katex-redC": "#f9685d", - "katex-redD": "#e84d39", - "katex-redE": "#bc2612", - "katex-maroonA": "#ffbde0", - "katex-maroonB": "#ff92c6", - "katex-maroonC": "#ed5fa6", - "katex-maroonD": "#ca337c", - "katex-maroonE": "#9e034e", - "katex-purpleA": "#ddd7ff", - "katex-purpleB": "#c6b9fc", - "katex-purpleC": "#aa87ff", - "katex-purpleD": "#7854ab", - "katex-purpleE": "#543b78", - "katex-mintA": "#f5f9e8", - "katex-mintB": "#edf2df", - "katex-mintC": "#e0e5cc", - "katex-grayA": "#f6f7f7", - "katex-grayB": "#f0f1f2", - "katex-grayC": "#e3e5e6", - "katex-grayD": "#d6d8da", - "katex-grayE": "#babec2", - "katex-grayF": "#888d93", - "katex-grayG": "#626569", - "katex-grayH": "#3b3e40", - "katex-grayI": "#21242c", - "katex-kaBlue": "#314453", - "katex-kaGreen": "#71B307", -}; - -/** - * Gets the CSS color of the current options object, accounting for the - * `colorMap`. - */ -Options.prototype.getColor = function() { - if (this.phantom) { - return "transparent"; - } else { - return colorMap[this.color] || this.color; - } -}; - -module.exports = Options; - -},{}],6:[function(require,module,exports){ -/** - * This is the ParseError class, which is the main error thrown by KaTeX - * functions when something has gone wrong. This is used to distinguish internal - * errors from errors in the expression that the user provided. - * - * If possible, a caller should provide a Token or ParseNode with information - * about where in the source string the problem occurred. - * - * @param {string} message The error message - * @param {(Token|ParseNode)=} token An object providing position information - */ -function ParseError(message, token) { - var error = "KaTeX parse error: " + message; - var start; - var end; - - if (token && token.lexer && token.start <= token.end) { - // If we have the input and a position, make the error a bit fancier - - // Get the input - var input = token.lexer.input; - - // Prepend some information - start = token.start; - end = token.end; - if (start === input.length) { - error += " at end of input: "; - } else { - error += " at position " + (start + 1) + ": "; - } - - // Underline token in question using combining underscores - var underlined = input.slice(start, end).replace(/[^]/g, "$&\u0332"); - - // Extract some context from the input and add it to the error - var left; - if (start > 15) { - left = "…" + input.slice(start - 15, start); - } else { - left = input.slice(0, start); - } - var right; - if (end + 15 < input.length) { - right = input.slice(end, end + 15) + "…"; - } else { - right = input.slice(end); - } - error += left + underlined + right; - } - - // Some hackery to make ParseError a prototype of Error - // See http://stackoverflow.com/a/8460753 - var self = new Error(error); - self.name = "ParseError"; - self.__proto__ = ParseError.prototype; - - self.position = start; - return self; -} - -// More hackery -ParseError.prototype.__proto__ = Error.prototype; - -module.exports = ParseError; - -},{}],7:[function(require,module,exports){ -/* eslint no-constant-condition:0 */ -var functions = require("./functions"); -var environments = require("./environments"); -var MacroExpander = require("./MacroExpander"); -var symbols = require("./symbols"); -var utils = require("./utils"); -var cjkRegex = require("./unicodeRegexes").cjkRegex; - -var parseData = require("./parseData"); -var ParseError = require("./ParseError"); - -/** - * This file contains the parser used to parse out a TeX expression from the - * input. Since TeX isn't context-free, standard parsers don't work particularly - * well. - * - * The strategy of this parser is as such: - * - * The main functions (the `.parse...` ones) take a position in the current - * parse string to parse tokens from. The lexer (found in Lexer.js, stored at - * this.lexer) also supports pulling out tokens at arbitrary places. When - * individual tokens are needed at a position, the lexer is called to pull out a - * token, which is then used. - * - * The parser has a property called "mode" indicating the mode that - * the parser is currently in. Currently it has to be one of "math" or - * "text", which denotes whether the current environment is a math-y - * one or a text-y one (e.g. inside \text). Currently, this serves to - * limit the functions which can be used in text mode. - * - * The main functions then return an object which contains the useful data that - * was parsed at its given point, and a new position at the end of the parsed - * data. The main functions can call each other and continue the parsing by - * using the returned position as a new starting point. - * - * There are also extra `.handle...` functions, which pull out some reused - * functionality into self-contained functions. - * - * The earlier functions return ParseNodes. - * The later functions (which are called deeper in the parse) sometimes return - * ParseFuncOrArgument, which contain a ParseNode as well as some data about - * whether the parsed object is a function which is missing some arguments, or a - * standalone object which can be used as an argument to another function. - */ - -/** - * Main Parser class - */ -function Parser(input, settings) { - // Create a new macro expander (gullet) and (indirectly via that) also a - // new lexer (mouth) for this parser (stomach, in the language of TeX) - this.gullet = new MacroExpander(input, settings.macros); - // Store the settings for use in parsing - this.settings = settings; -} - -var ParseNode = parseData.ParseNode; - -/** - * An initial function (without its arguments), or an argument to a function. - * The `result` argument should be a ParseNode. - */ -function ParseFuncOrArgument(result, isFunction, token) { - this.result = result; - // Is this a function (i.e. is it something defined in functions.js)? - this.isFunction = isFunction; - this.token = token; -} - -/** - * Checks a result to make sure it has the right type, and throws an - * appropriate error otherwise. - * - * @param {boolean=} consume whether to consume the expected token, - * defaults to true - */ -Parser.prototype.expect = function(text, consume) { - if (this.nextToken.text !== text) { - throw new ParseError( - "Expected '" + text + "', got '" + this.nextToken.text + "'", - this.nextToken - ); - } - if (consume !== false) { - this.consume(); - } -}; - -/** - * Considers the current look ahead token as consumed, - * and fetches the one after that as the new look ahead. - */ -Parser.prototype.consume = function() { - this.nextToken = this.gullet.get(this.mode === "math"); -}; - -Parser.prototype.switchMode = function(newMode) { - this.gullet.unget(this.nextToken); - this.mode = newMode; - this.consume(); -}; - -/** - * Main parsing function, which parses an entire input. - * - * @return {?Array.<ParseNode>} - */ -Parser.prototype.parse = function() { - // Try to parse the input - this.mode = "math"; - this.consume(); - var parse = this.parseInput(); - return parse; -}; - -/** - * Parses an entire input tree. - */ -Parser.prototype.parseInput = function() { - // Parse an expression - var expression = this.parseExpression(false); - // If we succeeded, make sure there's an EOF at the end - this.expect("EOF", false); - return expression; -}; - -var endOfExpression = ["}", "\\end", "\\right", "&", "\\\\", "\\cr"]; - -/** - * Parses an "expression", which is a list of atoms. - * - * @param {boolean} breakOnInfix Should the parsing stop when we hit infix - * nodes? This happens when functions have higher precendence - * than infix nodes in implicit parses. - * - * @param {?string} breakOnTokenText The text of the token that the expression - * should end with, or `null` if something else should end the - * expression. - * - * @return {ParseNode} - */ -Parser.prototype.parseExpression = function(breakOnInfix, breakOnTokenText) { - var body = []; - // Keep adding atoms to the body until we can't parse any more atoms (either - // we reached the end, a }, or a \right) - while (true) { - var lex = this.nextToken; - if (endOfExpression.indexOf(lex.text) !== -1) { - break; - } - if (breakOnTokenText && lex.text === breakOnTokenText) { - break; - } - if (breakOnInfix && functions[lex.text] && functions[lex.text].infix) { - break; - } - var atom = this.parseAtom(); - if (!atom) { - if (!this.settings.throwOnError && lex.text[0] === "\\") { - var errorNode = this.handleUnsupportedCmd(); - body.push(errorNode); - continue; - } - - break; - } - body.push(atom); - } - return this.handleInfixNodes(body); -}; - -/** - * Rewrites infix operators such as \over with corresponding commands such - * as \frac. - * - * There can only be one infix operator per group. If there's more than one - * then the expression is ambiguous. This can be resolved by adding {}. - * - * @returns {Array} - */ -Parser.prototype.handleInfixNodes = function(body) { - var overIndex = -1; - var funcName; - - for (var i = 0; i < body.length; i++) { - var node = body[i]; - if (node.type === "infix") { - if (overIndex !== -1) { - throw new ParseError( - "only one infix operator per group", - node.value.token); - } - overIndex = i; - funcName = node.value.replaceWith; - } - } - - if (overIndex !== -1) { - var numerNode; - var denomNode; - - var numerBody = body.slice(0, overIndex); - var denomBody = body.slice(overIndex + 1); - - if (numerBody.length === 1 && numerBody[0].type === "ordgroup") { - numerNode = numerBody[0]; - } else { - numerNode = new ParseNode("ordgroup", numerBody, this.mode); - } - - if (denomBody.length === 1 && denomBody[0].type === "ordgroup") { - denomNode = denomBody[0]; - } else { - denomNode = new ParseNode("ordgroup", denomBody, this.mode); - } - - var value = this.callFunction( - funcName, [numerNode, denomNode], null); - return [new ParseNode(value.type, value, this.mode)]; - } else { - return body; - } -}; - -// The greediness of a superscript or subscript -var SUPSUB_GREEDINESS = 1; - -/** - * Handle a subscript or superscript with nice errors. - */ -Parser.prototype.handleSupSubscript = function(name) { - var symbolToken = this.nextToken; - var symbol = symbolToken.text; - this.consume(); - var group = this.parseGroup(); - - if (!group) { - if (!this.settings.throwOnError && this.nextToken.text[0] === "\\") { - return this.handleUnsupportedCmd(); - } else { - throw new ParseError( - "Expected group after '" + symbol + "'", - symbolToken - ); - } - } else if (group.isFunction) { - // ^ and _ have a greediness, so handle interactions with functions' - // greediness - var funcGreediness = functions[group.result].greediness; - if (funcGreediness > SUPSUB_GREEDINESS) { - return this.parseFunction(group); - } else { - throw new ParseError( - "Got function '" + group.result + "' with no arguments " + - "as " + name, symbolToken); - } - } else { - return group.result; - } -}; - -/** - * Converts the textual input of an unsupported command into a text node - * contained within a color node whose color is determined by errorColor - */ -Parser.prototype.handleUnsupportedCmd = function() { - var text = this.nextToken.text; - var textordArray = []; - - for (var i = 0; i < text.length; i++) { - textordArray.push(new ParseNode("textord", text[i], "text")); - } - - var textNode = new ParseNode( - "text", - { - body: textordArray, - type: "text", - }, - this.mode); - - var colorNode = new ParseNode( - "color", - { - color: this.settings.errorColor, - value: [textNode], - type: "color", - }, - this.mode); - - this.consume(); - return colorNode; -}; - -/** - * Parses a group with optional super/subscripts. - * - * @return {?ParseNode} - */ -Parser.prototype.parseAtom = function() { - // The body of an atom is an implicit group, so that things like - // \left(x\right)^2 work correctly. - var base = this.parseImplicitGroup(); - - // In text mode, we don't have superscripts or subscripts - if (this.mode === "text") { - return base; - } - - // Note that base may be empty (i.e. null) at this point. - - var superscript; - var subscript; - while (true) { - // Lex the first token - var lex = this.nextToken; - - if (lex.text === "\\limits" || lex.text === "\\nolimits") { - // We got a limit control - if (!base || base.type !== "op") { - throw new ParseError( - "Limit controls must follow a math operator", - lex); - } else { - var limits = lex.text === "\\limits"; - base.value.limits = limits; - base.value.alwaysHandleSupSub = true; - } - this.consume(); - } else if (lex.text === "^") { - // We got a superscript start - if (superscript) { - throw new ParseError("Double superscript", lex); - } - superscript = this.handleSupSubscript("superscript"); - } else if (lex.text === "_") { - // We got a subscript start - if (subscript) { - throw new ParseError("Double subscript", lex); - } - subscript = this.handleSupSubscript("subscript"); - } else if (lex.text === "'") { - // We got a prime - var prime = new ParseNode("textord", "\\prime", this.mode); - - // Many primes can be grouped together, so we handle this here - var primes = [prime]; - this.consume(); - // Keep lexing tokens until we get something that's not a prime - while (this.nextToken.text === "'") { - // For each one, add another prime to the list - primes.push(prime); - this.consume(); - } - // Put them into an ordgroup as the superscript - superscript = new ParseNode("ordgroup", primes, this.mode); - } else { - // If it wasn't ^, _, or ', stop parsing super/subscripts - break; - } - } - - if (superscript || subscript) { - // If we got either a superscript or subscript, create a supsub - return new ParseNode("supsub", { - base: base, - sup: superscript, - sub: subscript, - }, this.mode); - } else { - // Otherwise return the original body - return base; - } -}; - -// A list of the size-changing functions, for use in parseImplicitGroup -var sizeFuncs = [ - "\\tiny", "\\scriptsize", "\\footnotesize", "\\small", "\\normalsize", - "\\large", "\\Large", "\\LARGE", "\\huge", "\\Huge", -]; - -// A list of the style-changing functions, for use in parseImplicitGroup -var styleFuncs = [ - "\\displaystyle", "\\textstyle", "\\scriptstyle", "\\scriptscriptstyle", -]; - -/** - * Parses an implicit group, which is a group that starts at the end of a - * specified, and ends right before a higher explicit group ends, or at EOL. It - * is used for functions that appear to affect the current style, like \Large or - * \textrm, where instead of keeping a style we just pretend that there is an - * implicit grouping after it until the end of the group. E.g. - * small text {\Large large text} small text again - * It is also used for \left and \right to get the correct grouping. - * - * @return {?ParseNode} - */ -Parser.prototype.parseImplicitGroup = function() { - var start = this.parseSymbol(); - - if (start == null) { - // If we didn't get anything we handle, fall back to parseFunction - return this.parseFunction(); - } - - var func = start.result; - var body; - - if (func === "\\left") { - // If we see a left: - // Parse the entire left function (including the delimiter) - var left = this.parseFunction(start); - // Parse out the implicit body - body = this.parseExpression(false); - // Check the next token - this.expect("\\right", false); - var right = this.parseFunction(); - return new ParseNode("leftright", { - body: body, - left: left.value.value, - right: right.value.value, - }, this.mode); - } else if (func === "\\begin") { - // begin...end is similar to left...right - var begin = this.parseFunction(start); - var envName = begin.value.name; - if (!environments.hasOwnProperty(envName)) { - throw new ParseError( - "No such environment: " + envName, begin.value.nameGroup); - } - // Build the environment object. Arguments and other information will - // be made available to the begin and end methods using properties. - var env = environments[envName]; - var args = this.parseArguments("\\begin{" + envName + "}", env); - var context = { - mode: this.mode, - envName: envName, - parser: this, - positions: args.pop(), - }; - var result = env.handler(context, args); - this.expect("\\end", false); - var endNameToken = this.nextToken; - var end = this.parseFunction(); - if (end.value.name !== envName) { - throw new ParseError( - "Mismatch: \\begin{" + envName + "} matched " + - "by \\end{" + end.value.name + "}", - endNameToken); - } - result.position = end.position; - return result; - } else if (utils.contains(sizeFuncs, func)) { - // If we see a sizing function, parse out the implict body - body = this.parseExpression(false); - return new ParseNode("sizing", { - // Figure out what size to use based on the list of functions above - size: "size" + (utils.indexOf(sizeFuncs, func) + 1), - value: body, - }, this.mode); - } else if (utils.contains(styleFuncs, func)) { - // If we see a styling function, parse out the implict body - body = this.parseExpression(true); - return new ParseNode("styling", { - // Figure out what style to use by pulling out the style from - // the function name - style: func.slice(1, func.length - 5), - value: body, - }, this.mode); - } else { - // Defer to parseFunction if it's not a function we handle - return this.parseFunction(start); - } -}; - -/** - * Parses an entire function, including its base and all of its arguments. - * The base might either have been parsed already, in which case - * it is provided as an argument, or it's the next group in the input. - * - * @param {ParseFuncOrArgument=} baseGroup optional as described above - * @return {?ParseNode} - */ -Parser.prototype.parseFunction = function(baseGroup) { - if (!baseGroup) { - baseGroup = this.parseGroup(); - } - - if (baseGroup) { - if (baseGroup.isFunction) { - var func = baseGroup.result; - var funcData = functions[func]; - if (this.mode === "text" && !funcData.allowedInText) { - throw new ParseError( - "Can't use function '" + func + "' in text mode", - baseGroup.token); - } - - var args = this.parseArguments(func, funcData); - var token = baseGroup.token; - var result = this.callFunction(func, args, args.pop(), token); - return new ParseNode(result.type, result, this.mode); - } else { - return baseGroup.result; - } - } else { - return null; - } -}; - -/** - * Call a function handler with a suitable context and arguments. - */ -Parser.prototype.callFunction = function(name, args, positions, token) { - var context = { - funcName: name, - parser: this, - positions: positions, - token: token, - }; - return functions[name].handler(context, args); -}; - -/** - * Parses the arguments of a function or environment - * - * @param {string} func "\name" or "\begin{name}" - * @param {{numArgs:number,numOptionalArgs:number|undefined}} funcData - * @return the array of arguments, with the list of positions as last element - */ -Parser.prototype.parseArguments = function(func, funcData) { - var totalArgs = funcData.numArgs + funcData.numOptionalArgs; - if (totalArgs === 0) { - return [[this.pos]]; - } - - var baseGreediness = funcData.greediness; - var positions = [this.pos]; - var args = []; - - for (var i = 0; i < totalArgs; i++) { - var nextToken = this.nextToken; - var argType = funcData.argTypes && funcData.argTypes[i]; - var arg; - if (i < funcData.numOptionalArgs) { - if (argType) { - arg = this.parseGroupOfType(argType, true); - } else { - arg = this.parseGroup(true); - } - if (!arg) { - args.push(null); - positions.push(this.pos); - continue; - } - } else { - if (argType) { - arg = this.parseGroupOfType(argType); - } else { - arg = this.parseGroup(); - } - if (!arg) { - if (!this.settings.throwOnError && - this.nextToken.text[0] === "\\") { - arg = new ParseFuncOrArgument( - this.handleUnsupportedCmd(this.nextToken.text), - false); - } else { - throw new ParseError( - "Expected group after '" + func + "'", nextToken); - } - } - } - var argNode; - if (arg.isFunction) { - var argGreediness = - functions[arg.result].greediness; - if (argGreediness > baseGreediness) { - argNode = this.parseFunction(arg); - } else { - throw new ParseError( - "Got function '" + arg.result + "' as " + - "argument to '" + func + "'", nextToken); - } - } else { - argNode = arg.result; - } - args.push(argNode); - positions.push(this.pos); - } - - args.push(positions); - - return args; -}; - - -/** - * Parses a group when the mode is changing. - * - * @return {?ParseFuncOrArgument} - */ -Parser.prototype.parseGroupOfType = function(innerMode, optional) { - var outerMode = this.mode; - // Handle `original` argTypes - if (innerMode === "original") { - innerMode = outerMode; - } - - if (innerMode === "color") { - return this.parseColorGroup(optional); - } - if (innerMode === "size") { - return this.parseSizeGroup(optional); - } - - this.switchMode(innerMode); - if (innerMode === "text") { - // text mode is special because it should ignore the whitespace before - // it - while (this.nextToken.text === " ") { - this.consume(); - } - } - // By the time we get here, innerMode is one of "text" or "math". - // We switch the mode of the parser, recurse, then restore the old mode. - var res = this.parseGroup(optional); - this.switchMode(outerMode); - return res; -}; - -/** - * Parses a group, essentially returning the string formed by the - * brace-enclosed tokens plus some position information. - * - * @param {string} modeName Used to describe the mode in error messages - * @param {boolean=} optional Whether the group is optional or required - */ -Parser.prototype.parseStringGroup = function(modeName, optional) { - if (optional && this.nextToken.text !== "[") { - return null; - } - var outerMode = this.mode; - this.mode = "text"; - this.expect(optional ? "[" : "{"); - var str = ""; - var firstToken = this.nextToken; - var lastToken = firstToken; - while (this.nextToken.text !== (optional ? "]" : "}")) { - if (this.nextToken.text === "EOF") { - throw new ParseError( - "Unexpected end of input in " + modeName, - firstToken.range(this.nextToken, str)); - } - lastToken = this.nextToken; - str += lastToken.text; - this.consume(); - } - this.mode = outerMode; - this.expect(optional ? "]" : "}"); - return firstToken.range(lastToken, str); -}; - -/** - * Parses a color description. - */ -Parser.prototype.parseColorGroup = function(optional) { - var res = this.parseStringGroup("color", optional); - if (!res) { - return null; - } - var match = (/^(#[a-z0-9]+|[a-z]+)$/i).exec(res.text); - if (!match) { - throw new ParseError("Invalid color: '" + res.text + "'", res); - } - return new ParseFuncOrArgument( - new ParseNode("color", match[0], this.mode), - false); -}; - -/** - * Parses a size specification, consisting of magnitude and unit. - */ -Parser.prototype.parseSizeGroup = function(optional) { - var res = this.parseStringGroup("size", optional); - if (!res) { - return null; - } - var match = (/(-?) *(\d+(?:\.\d*)?|\.\d+) *([a-z]{2})/).exec(res.text); - if (!match) { - throw new ParseError("Invalid size: '" + res.text + "'", res); - } - var data = { - number: +(match[1] + match[2]), // sign + magnitude, cast to number - unit: match[3], - }; - if (data.unit !== "em" && data.unit !== "ex") { - throw new ParseError("Invalid unit: '" + data.unit + "'", res); - } - return new ParseFuncOrArgument( - new ParseNode("color", data, this.mode), - false); -}; - -/** - * If the argument is false or absent, this parses an ordinary group, - * which is either a single nucleus (like "x") or an expression - * in braces (like "{x+y}"). - * If the argument is true, it parses either a bracket-delimited expression - * (like "[x+y]") or returns null to indicate the absence of a - * bracket-enclosed group. - * - * @param {boolean=} optional Whether the group is optional or required - * @return {?ParseFuncOrArgument} - */ -Parser.prototype.parseGroup = function(optional) { - var firstToken = this.nextToken; - // Try to parse an open brace - if (this.nextToken.text === (optional ? "[" : "{")) { - // If we get a brace, parse an expression - this.consume(); - var expression = this.parseExpression(false, optional ? "]" : null); - var lastToken = this.nextToken; - // Make sure we get a close brace - this.expect(optional ? "]" : "}"); - if (this.mode === "text") { - this.formLigatures(expression); - } - return new ParseFuncOrArgument( - new ParseNode("ordgroup", expression, this.mode, - firstToken, lastToken), - false); - } else { - // Otherwise, just return a nucleus, or nothing for an optional group - return optional ? null : this.parseSymbol(); - } -}; - -/** - * Form ligature-like combinations of characters for text mode. - * This includes inputs like "--", "---", "``" and "''". - * The result will simply replace multiple textord nodes with a single - * character in each value by a single textord node having multiple - * characters in its value. The representation is still ASCII source. - * - * @param {Array.<ParseNode>} group the nodes of this group, - * list will be moified in place - */ -Parser.prototype.formLigatures = function(group) { - var i; - var n = group.length - 1; - for (i = 0; i < n; ++i) { - var a = group[i]; - var v = a.value; - if (v === "-" && group[i + 1].value === "-") { - if (i + 1 < n && group[i + 2].value === "-") { - group.splice(i, 3, new ParseNode( - "textord", "---", "text", a, group[i + 2])); - n -= 2; - } else { - group.splice(i, 2, new ParseNode( - "textord", "--", "text", a, group[i + 1])); - n -= 1; - } - } - if ((v === "'" || v === "`") && group[i + 1].value === v) { - group.splice(i, 2, new ParseNode( - "textord", v + v, "text", a, group[i + 1])); - n -= 1; - } - } -}; - -/** - * Parse a single symbol out of the string. Here, we handle both the functions - * we have defined, as well as the single character symbols - * - * @return {?ParseFuncOrArgument} - */ -Parser.prototype.parseSymbol = function() { - var nucleus = this.nextToken; - - if (functions[nucleus.text]) { - this.consume(); - // If there exists a function with this name, we return the function and - // say that it is a function. - return new ParseFuncOrArgument( - nucleus.text, - true, nucleus); - } else if (symbols[this.mode][nucleus.text]) { - this.consume(); - // Otherwise if this is a no-argument function, find the type it - // corresponds to in the symbols map - return new ParseFuncOrArgument( - new ParseNode(symbols[this.mode][nucleus.text].group, - nucleus.text, this.mode, nucleus), - false, nucleus); - } else if (this.mode === "text" && cjkRegex.test(nucleus.text)) { - this.consume(); - return new ParseFuncOrArgument( - new ParseNode("textord", nucleus.text, this.mode, nucleus), - false, nucleus); - } else { - return null; - } -}; - -Parser.prototype.ParseNode = ParseNode; - -module.exports = Parser; - -},{"./MacroExpander":4,"./ParseError":6,"./environments":16,"./functions":19,"./parseData":21,"./symbols":23,"./unicodeRegexes":24,"./utils":25}],8:[function(require,module,exports){ -/** - * This is a module for storing settings passed into KaTeX. It correctly handles - * default settings. - */ - -/** - * Helper function for getting a default value if the value is undefined - */ -function get(option, defaultValue) { - return option === undefined ? defaultValue : option; -} - -/** - * The main Settings object - * - * The current options stored are: - * - displayMode: Whether the expression should be typeset by default in - * textstyle or displaystyle (default false) - */ -function Settings(options) { - // allow null options - options = options || {}; - this.displayMode = get(options.displayMode, false); - this.throwOnError = get(options.throwOnError, true); - this.errorColor = get(options.errorColor, "#cc0000"); - this.macros = options.macros || {}; -} - -module.exports = Settings; - -},{}],9:[function(require,module,exports){ -/** - * This file contains information and classes for the various kinds of styles - * used in TeX. It provides a generic `Style` class, which holds information - * about a specific style. It then provides instances of all the different kinds - * of styles possible, and provides functions to move between them and get - * information about them. - */ - -/** - * The main style class. Contains a unique id for the style, a size (which is - * the same for cramped and uncramped version of a style), a cramped flag, and a - * size multiplier, which gives the size difference between a style and - * textstyle. - */ -function Style(id, size, multiplier, cramped) { - this.id = id; - this.size = size; - this.cramped = cramped; - this.sizeMultiplier = multiplier; -} - -/** - * Get the style of a superscript given a base in the current style. - */ -Style.prototype.sup = function() { - return styles[sup[this.id]]; -}; - -/** - * Get the style of a subscript given a base in the current style. - */ -Style.prototype.sub = function() { - return styles[sub[this.id]]; -}; - -/** - * Get the style of a fraction numerator given the fraction in the current - * style. - */ -Style.prototype.fracNum = function() { - return styles[fracNum[this.id]]; -}; - -/** - * Get the style of a fraction denominator given the fraction in the current - * style. - */ -Style.prototype.fracDen = function() { - return styles[fracDen[this.id]]; -}; - -/** - * Get the cramped version of a style (in particular, cramping a cramped style - * doesn't change the style). - */ -Style.prototype.cramp = function() { - return styles[cramp[this.id]]; -}; - -/** - * HTML class name, like "displaystyle cramped" - */ -Style.prototype.cls = function() { - return sizeNames[this.size] + (this.cramped ? " cramped" : " uncramped"); -}; - -/** - * HTML Reset class name, like "reset-textstyle" - */ -Style.prototype.reset = function() { - return resetNames[this.size]; -}; - -// IDs of the different styles -var D = 0; -var Dc = 1; -var T = 2; -var Tc = 3; -var S = 4; -var Sc = 5; -var SS = 6; -var SSc = 7; - -// String names for the different sizes -var sizeNames = [ - "displaystyle textstyle", - "textstyle", - "scriptstyle", - "scriptscriptstyle", -]; - -// Reset names for the different sizes -var resetNames = [ - "reset-textstyle", - "reset-textstyle", - "reset-scriptstyle", - "reset-scriptscriptstyle", -]; - -// Instances of the different styles -var styles = [ - new Style(D, 0, 1.0, false), - new Style(Dc, 0, 1.0, true), - new Style(T, 1, 1.0, false), - new Style(Tc, 1, 1.0, true), - new Style(S, 2, 0.7, false), - new Style(Sc, 2, 0.7, true), - new Style(SS, 3, 0.5, false), - new Style(SSc, 3, 0.5, true), -]; - -// Lookup tables for switching from one style to another -var sup = [S, Sc, S, Sc, SS, SSc, SS, SSc]; -var sub = [Sc, Sc, Sc, Sc, SSc, SSc, SSc, SSc]; -var fracNum = [T, Tc, S, Sc, SS, SSc, SS, SSc]; -var fracDen = [Tc, Tc, Sc, Sc, SSc, SSc, SSc, SSc]; -var cramp = [Dc, Dc, Tc, Tc, Sc, Sc, SSc, SSc]; - -// We only export some of the styles. Also, we don't export the `Style` class so -// no more styles can be generated. -module.exports = { - DISPLAY: styles[D], - TEXT: styles[T], - SCRIPT: styles[S], - SCRIPTSCRIPT: styles[SS], -}; - -},{}],10:[function(require,module,exports){ -/* eslint no-console:0 */ -/** - * This module contains general functions that can be used for building - * different kinds of domTree nodes in a consistent manner. - */ - -var domTree = require("./domTree"); -var fontMetrics = require("./fontMetrics"); -var symbols = require("./symbols"); -var utils = require("./utils"); - -var greekCapitals = [ - "\\Gamma", - "\\Delta", - "\\Theta", - "\\Lambda", - "\\Xi", - "\\Pi", - "\\Sigma", - "\\Upsilon", - "\\Phi", - "\\Psi", - "\\Omega", -]; - -// The following have to be loaded from Main-Italic font, using class mainit -var mainitLetters = [ - "\u0131", // dotless i, \imath - "\u0237", // dotless j, \jmath - "\u00a3", // \pounds -]; - -/** - * Makes a symbolNode after translation via the list of symbols in symbols.js. - * Correctly pulls out metrics for the character, and optionally takes a list of - * classes to be attached to the node. - */ -var makeSymbol = function(value, style, mode, color, classes) { - // Replace the value with its replaced value from symbol.js - if (symbols[mode][value] && symbols[mode][value].replace) { - value = symbols[mode][value].replace; - } - - var metrics = fontMetrics.getCharacterMetrics(value, style); - - var symbolNode; - if (metrics) { - symbolNode = new domTree.symbolNode( - value, metrics.height, metrics.depth, metrics.italic, metrics.skew, - classes); - } else { - // TODO(emily): Figure out a good way to only print this in development - typeof console !== "undefined" && console.warn( - "No character metrics for '" + value + "' in style '" + - style + "'"); - symbolNode = new domTree.symbolNode(value, 0, 0, 0, 0, classes); - } - - if (color) { - symbolNode.style.color = color; - } - - return symbolNode; -}; - -/** - * Makes a symbol in Main-Regular or AMS-Regular. - * Used for rel, bin, open, close, inner, and punct. - */ -var mathsym = function(value, mode, color, classes) { - // Decide what font to render the symbol in by its entry in the symbols - // table. - // Have a special case for when the value = \ because the \ is used as a - // textord in unsupported command errors but cannot be parsed as a regular - // text ordinal and is therefore not present as a symbol in the symbols - // table for text - if (value === "\\" || symbols[mode][value].font === "main") { - return makeSymbol(value, "Main-Regular", mode, color, classes); - } else { - return makeSymbol( - value, "AMS-Regular", mode, color, classes.concat(["amsrm"])); - } -}; - -/** - * Makes a symbol in the default font for mathords and textords. - */ -var mathDefault = function(value, mode, color, classes, type) { - if (type === "mathord") { - return mathit(value, mode, color, classes); - } else if (type === "textord") { - return makeSymbol( - value, "Main-Regular", mode, color, classes.concat(["mathrm"])); - } else { - throw new Error("unexpected type: " + type + " in mathDefault"); - } -}; - -/** - * Makes a symbol in the italic math font. - */ -var mathit = function(value, mode, color, classes) { - if (/[0-9]/.test(value.charAt(0)) || - // glyphs for \imath and \jmath do not exist in Math-Italic so we - // need to use Main-Italic instead - utils.contains(mainitLetters, value) || - utils.contains(greekCapitals, value)) { - return makeSymbol( - value, "Main-Italic", mode, color, classes.concat(["mainit"])); - } else { - return makeSymbol( - value, "Math-Italic", mode, color, classes.concat(["mathit"])); - } -}; - -/** - * Makes either a mathord or textord in the correct font and color. - */ -var makeOrd = function(group, options, type) { - var mode = group.mode; - var value = group.value; - if (symbols[mode][value] && symbols[mode][value].replace) { - value = symbols[mode][value].replace; - } - - var classes = ["mord"]; - var color = options.getColor(); - - var font = options.font; - if (font) { - if (font === "mathit" || utils.contains(mainitLetters, value)) { - return mathit(value, mode, color, classes); - } else { - var fontName = fontMap[font].fontName; - if (fontMetrics.getCharacterMetrics(value, fontName)) { - return makeSymbol( - value, fontName, mode, color, classes.concat([font])); - } else { - return mathDefault(value, mode, color, classes, type); - } - } - } else { - return mathDefault(value, mode, color, classes, type); - } -}; - -/** - * Calculate the height, depth, and maxFontSize of an element based on its - * children. - */ -var sizeElementFromChildren = function(elem) { - var height = 0; - var depth = 0; - var maxFontSize = 0; - - if (elem.children) { - for (var i = 0; i < elem.children.length; i++) { - if (elem.children[i].height > height) { - height = elem.children[i].height; - } - if (elem.children[i].depth > depth) { - depth = elem.children[i].depth; - } - if (elem.children[i].maxFontSize > maxFontSize) { - maxFontSize = elem.children[i].maxFontSize; - } - } - } - - elem.height = height; - elem.depth = depth; - elem.maxFontSize = maxFontSize; -}; - -/** - * Makes a span with the given list of classes, list of children, and color. - */ -var makeSpan = function(classes, children, color) { - var span = new domTree.span(classes, children); - - sizeElementFromChildren(span); - - if (color) { - span.style.color = color; - } - - return span; -}; - -/** - * Makes a document fragment with the given list of children. - */ -var makeFragment = function(children) { - var fragment = new domTree.documentFragment(children); - - sizeElementFromChildren(fragment); - - return fragment; -}; - -/** - * Makes an element placed in each of the vlist elements to ensure that each - * element has the same max font size. To do this, we create a zero-width space - * with the correct font size. - */ -var makeFontSizer = function(options, fontSize) { - var fontSizeInner = makeSpan([], [new domTree.symbolNode("\u200b")]); - fontSizeInner.style.fontSize = - (fontSize / options.style.sizeMultiplier) + "em"; - - var fontSizer = makeSpan( - ["fontsize-ensurer", "reset-" + options.size, "size5"], - [fontSizeInner]); - - return fontSizer; -}; - -/** - * Makes a vertical list by stacking elements and kerns on top of each other. - * Allows for many different ways of specifying the positioning method. - * - * Arguments: - * - children: A list of child or kern nodes to be stacked on top of each other - * (i.e. the first element will be at the bottom, and the last at - * the top). Element nodes are specified as - * {type: "elem", elem: node} - * while kern nodes are specified as - * {type: "kern", size: size} - * - positionType: The method by which the vlist should be positioned. Valid - * values are: - * - "individualShift": The children list only contains elem - * nodes, and each node contains an extra - * "shift" value of how much it should be - * shifted (note that shifting is always - * moving downwards). positionData is - * ignored. - * - "top": The positionData specifies the topmost point of - * the vlist (note this is expected to be a height, - * so positive values move up) - * - "bottom": The positionData specifies the bottommost point - * of the vlist (note this is expected to be a - * depth, so positive values move down - * - "shift": The vlist will be positioned such that its - * baseline is positionData away from the baseline - * of the first child. Positive values move - * downwards. - * - "firstBaseline": The vlist will be positioned such that - * its baseline is aligned with the - * baseline of the first child. - * positionData is ignored. (this is - * equivalent to "shift" with - * positionData=0) - * - positionData: Data used in different ways depending on positionType - * - options: An Options object - * - */ -var makeVList = function(children, positionType, positionData, options) { - var depth; - var currPos; - var i; - if (positionType === "individualShift") { - var oldChildren = children; - children = [oldChildren[0]]; - - // Add in kerns to the list of children to get each element to be - // shifted to the correct specified shift - depth = -oldChildren[0].shift - oldChildren[0].elem.depth; - currPos = depth; - for (i = 1; i < oldChildren.length; i++) { - var diff = -oldChildren[i].shift - currPos - - oldChildren[i].elem.depth; - var size = diff - - (oldChildren[i - 1].elem.height + - oldChildren[i - 1].elem.depth); - - currPos = currPos + diff; - - children.push({type: "kern", size: size}); - children.push(oldChildren[i]); - } - } else if (positionType === "top") { - // We always start at the bottom, so calculate the bottom by adding up - // all the sizes - var bottom = positionData; - for (i = 0; i < children.length; i++) { - if (children[i].type === "kern") { - bottom -= children[i].size; - } else { - bottom -= children[i].elem.height + children[i].elem.depth; - } - } - depth = bottom; - } else if (positionType === "bottom") { - depth = -positionData; - } else if (positionType === "shift") { - depth = -children[0].elem.depth - positionData; - } else if (positionType === "firstBaseline") { - depth = -children[0].elem.depth; - } else { - depth = 0; - } - - // Make the fontSizer - var maxFontSize = 0; - for (i = 0; i < children.length; i++) { - if (children[i].type === "elem") { - maxFontSize = Math.max(maxFontSize, children[i].elem.maxFontSize); - } - } - var fontSizer = makeFontSizer(options, maxFontSize); - - // Create a new list of actual children at the correct offsets - var realChildren = []; - currPos = depth; - for (i = 0; i < children.length; i++) { - if (children[i].type === "kern") { - currPos += children[i].size; - } else { - var child = children[i].elem; - - var shift = -child.depth - currPos; - currPos += child.height + child.depth; - - var childWrap = makeSpan([], [fontSizer, child]); - childWrap.height -= shift; - childWrap.depth += shift; - childWrap.style.top = shift + "em"; - - realChildren.push(childWrap); - } - } - - // Add in an element at the end with no offset to fix the calculation of - // baselines in some browsers (namely IE, sometimes safari) - var baselineFix = makeSpan( - ["baseline-fix"], [fontSizer, new domTree.symbolNode("\u200b")]); - realChildren.push(baselineFix); - - var vlist = makeSpan(["vlist"], realChildren); - // Fix the final height and depth, in case there were kerns at the ends - // since the makeSpan calculation won't take that in to account. - vlist.height = Math.max(currPos, vlist.height); - vlist.depth = Math.max(-depth, vlist.depth); - return vlist; -}; - -// A table of size -> font size for the different sizing functions -var sizingMultiplier = { - size1: 0.5, - size2: 0.7, - size3: 0.8, - size4: 0.9, - size5: 1.0, - size6: 1.2, - size7: 1.44, - size8: 1.73, - size9: 2.07, - size10: 2.49, -}; - -// A map of spacing functions to their attributes, like size and corresponding -// CSS class -var spacingFunctions = { - "\\qquad": { - size: "2em", - className: "qquad", - }, - "\\quad": { - size: "1em", - className: "quad", - }, - "\\enspace": { - size: "0.5em", - className: "enspace", - }, - "\\;": { - size: "0.277778em", - className: "thickspace", - }, - "\\:": { - size: "0.22222em", - className: "mediumspace", - }, - "\\,": { - size: "0.16667em", - className: "thinspace", - }, - "\\!": { - size: "-0.16667em", - className: "negativethinspace", - }, -}; - -/** - * Maps TeX font commands to objects containing: - * - variant: string used for "mathvariant" attribute in buildMathML.js - * - fontName: the "style" parameter to fontMetrics.getCharacterMetrics - */ -// A map between tex font commands an MathML mathvariant attribute values -var fontMap = { - // styles - "mathbf": { - variant: "bold", - fontName: "Main-Bold", - }, - "mathrm": { - variant: "normal", - fontName: "Main-Regular", - }, - - // "mathit" is missing because it requires the use of two fonts: Main-Italic - // and Math-Italic. This is handled by a special case in makeOrd which ends - // up calling mathit. - - // families - "mathbb": { - variant: "double-struck", - fontName: "AMS-Regular", - }, - "mathcal": { - variant: "script", - fontName: "Caligraphic-Regular", - }, - "mathfrak": { - variant: "fraktur", - fontName: "Fraktur-Regular", - }, - "mathscr": { - variant: "script", - fontName: "Script-Regular", - }, - "mathsf": { - variant: "sans-serif", - fontName: "SansSerif-Regular", - }, - "mathtt": { - variant: "monospace", - fontName: "Typewriter-Regular", - }, -}; - -module.exports = { - fontMap: fontMap, - makeSymbol: makeSymbol, - mathsym: mathsym, - makeSpan: makeSpan, - makeFragment: makeFragment, - makeVList: makeVList, - makeOrd: makeOrd, - sizingMultiplier: sizingMultiplier, - spacingFunctions: spacingFunctions, -}; - -},{"./domTree":15,"./fontMetrics":17,"./symbols":23,"./utils":25}],11:[function(require,module,exports){ -/* eslint no-console:0 */ -/** - * This file does the main work of building a domTree structure from a parse - * tree. The entry point is the `buildHTML` function, which takes a parse tree. - * Then, the buildExpression, buildGroup, and various groupTypes functions are - * called, to produce a final HTML tree. - */ - -var ParseError = require("./ParseError"); -var Style = require("./Style"); - -var buildCommon = require("./buildCommon"); -var delimiter = require("./delimiter"); -var domTree = require("./domTree"); -var fontMetrics = require("./fontMetrics"); -var utils = require("./utils"); - -var makeSpan = buildCommon.makeSpan; - -/** - * Take a list of nodes, build them in order, and return a list of the built - * nodes. This function handles the `prev` node correctly, and passes the - * previous element from the list as the prev of the next element. - */ -var buildExpression = function(expression, options, prev) { - var groups = []; - for (var i = 0; i < expression.length; i++) { - var group = expression[i]; - groups.push(buildGroup(group, options, prev)); - prev = group; - } - return groups; -}; - -// List of types used by getTypeOfGroup, -// see https://github.com/Khan/KaTeX/wiki/Examining-TeX#group-types -var groupToType = { - mathord: "mord", - textord: "mord", - bin: "mbin", - rel: "mrel", - text: "mord", - open: "mopen", - close: "mclose", - inner: "minner", - genfrac: "mord", - array: "mord", - spacing: "mord", - punct: "mpunct", - ordgroup: "mord", - op: "mop", - katex: "mord", - overline: "mord", - underline: "mord", - rule: "mord", - leftright: "minner", - sqrt: "mord", - accent: "mord", -}; - -/** - * Gets the final math type of an expression, given its group type. This type is - * used to determine spacing between elements, and affects bin elements by - * causing them to change depending on what types are around them. This type - * must be attached to the outermost node of an element as a CSS class so that - * spacing with its surrounding elements works correctly. - * - * Some elements can be mapped one-to-one from group type to math type, and - * those are listed in the `groupToType` table. - * - * Others (usually elements that wrap around other elements) often have - * recursive definitions, and thus call `getTypeOfGroup` on their inner - * elements. - */ -var getTypeOfGroup = function(group) { - if (group == null) { - // Like when typesetting $^3$ - return groupToType.mathord; - } else if (group.type === "supsub") { - return getTypeOfGroup(group.value.base); - } else if (group.type === "llap" || group.type === "rlap") { - return getTypeOfGroup(group.value); - } else if (group.type === "color") { - return getTypeOfGroup(group.value.value); - } else if (group.type === "sizing") { - return getTypeOfGroup(group.value.value); - } else if (group.type === "styling") { - return getTypeOfGroup(group.value.value); - } else if (group.type === "delimsizing") { - return groupToType[group.value.delimType]; - } else { - return groupToType[group.type]; - } -}; - -/** - * Sometimes, groups perform special rules when they have superscripts or - * subscripts attached to them. This function lets the `supsub` group know that - * its inner element should handle the superscripts and subscripts instead of - * handling them itself. - */ -var shouldHandleSupSub = function(group, options) { - if (!group) { - return false; - } else if (group.type === "op") { - // Operators handle supsubs differently when they have limits - // (e.g. `\displaystyle\sum_2^3`) - return group.value.limits && - (options.style.size === Style.DISPLAY.size || - group.value.alwaysHandleSupSub); - } else if (group.type === "accent") { - return isCharacterBox(group.value.base); - } else { - return null; - } -}; - -/** - * Sometimes we want to pull out the innermost element of a group. In most - * cases, this will just be the group itself, but when ordgroups and colors have - * a single element, we want to pull that out. - */ -var getBaseElem = function(group) { - if (!group) { - return false; - } else if (group.type === "ordgroup") { - if (group.value.length === 1) { - return getBaseElem(group.value[0]); - } else { - return group; - } - } else if (group.type === "color") { - if (group.value.value.length === 1) { - return getBaseElem(group.value.value[0]); - } else { - return group; - } - } else if (group.type === "font") { - return getBaseElem(group.value.body); - } else { - return group; - } -}; - -/** - * TeXbook algorithms often reference "character boxes", which are simply groups - * with a single character in them. To decide if something is a character box, - * we find its innermost group, and see if it is a single character. - */ -var isCharacterBox = function(group) { - var baseElem = getBaseElem(group); - - // These are all they types of groups which hold single characters - return baseElem.type === "mathord" || - baseElem.type === "textord" || - baseElem.type === "bin" || - baseElem.type === "rel" || - baseElem.type === "inner" || - baseElem.type === "open" || - baseElem.type === "close" || - baseElem.type === "punct"; -}; - -var makeNullDelimiter = function(options) { - return makeSpan([ - "sizing", "reset-" + options.size, "size5", - options.style.reset(), Style.TEXT.cls(), - "nulldelimiter", - ]); -}; - -/** - * This is a map of group types to the function used to handle that type. - * Simpler types come at the beginning, while complicated types come afterwards. - */ -var groupTypes = {}; - -groupTypes.mathord = function(group, options, prev) { - return buildCommon.makeOrd(group, options, "mathord"); -}; - -groupTypes.textord = function(group, options, prev) { - return buildCommon.makeOrd(group, options, "textord"); -}; - -groupTypes.bin = function(group, options, prev) { - var className = "mbin"; - // Pull out the most recent element. Do some special handling to find - // things at the end of a \color group. Note that we don't use the same - // logic for ordgroups (which count as ords). - var prevAtom = prev; - while (prevAtom && prevAtom.type === "color") { - var atoms = prevAtom.value.value; - prevAtom = atoms[atoms.length - 1]; - } - // See TeXbook pg. 442-446, Rules 5 and 6, and the text before Rule 19. - // Here, we determine whether the bin should turn into an ord. We - // currently only apply Rule 5. - if (!prev || utils.contains(["mbin", "mopen", "mrel", "mop", "mpunct"], - getTypeOfGroup(prevAtom))) { - group.type = "textord"; - className = "mord"; - } - - return buildCommon.mathsym( - group.value, group.mode, options.getColor(), [className]); -}; - -groupTypes.rel = function(group, options, prev) { - return buildCommon.mathsym( - group.value, group.mode, options.getColor(), ["mrel"]); -}; - -groupTypes.open = function(group, options, prev) { - return buildCommon.mathsym( - group.value, group.mode, options.getColor(), ["mopen"]); -}; - -groupTypes.close = function(group, options, prev) { - return buildCommon.mathsym( - group.value, group.mode, options.getColor(), ["mclose"]); -}; - -groupTypes.inner = function(group, options, prev) { - return buildCommon.mathsym( - group.value, group.mode, options.getColor(), ["minner"]); -}; - -groupTypes.punct = function(group, options, prev) { - return buildCommon.mathsym( - group.value, group.mode, options.getColor(), ["mpunct"]); -}; - -groupTypes.ordgroup = function(group, options, prev) { - return makeSpan( - ["mord", options.style.cls()], - buildExpression(group.value, options.reset()) - ); -}; - -groupTypes.text = function(group, options, prev) { - return makeSpan(["text", "mord", options.style.cls()], - buildExpression(group.value.body, options.reset())); -}; - -groupTypes.color = function(group, options, prev) { - var elements = buildExpression( - group.value.value, - options.withColor(group.value.color), - prev - ); - - // \color isn't supposed to affect the type of the elements it contains. - // To accomplish this, we wrap the results in a fragment, so the inner - // elements will be able to directly interact with their neighbors. For - // example, `\color{red}{2 +} 3` has the same spacing as `2 + 3` - return new buildCommon.makeFragment(elements); -}; - -groupTypes.supsub = function(group, options, prev) { - // Superscript and subscripts are handled in the TeXbook on page - // 445-446, rules 18(a-f). - - // Here is where we defer to the inner group if it should handle - // superscripts and subscripts itself. - if (shouldHandleSupSub(group.value.base, options)) { - return groupTypes[group.value.base.type](group, options, prev); - } - - var base = buildGroup(group.value.base, options.reset()); - var supmid; - var submid; - var sup; - var sub; - - if (group.value.sup) { - sup = buildGroup(group.value.sup, - options.withStyle(options.style.sup())); - supmid = makeSpan( - [options.style.reset(), options.style.sup().cls()], [sup]); - } - - if (group.value.sub) { - sub = buildGroup(group.value.sub, - options.withStyle(options.style.sub())); - submid = makeSpan( - [options.style.reset(), options.style.sub().cls()], [sub]); - } - - // Rule 18a - var supShift; - var subShift; - if (isCharacterBox(group.value.base)) { - supShift = 0; - subShift = 0; - } else { - supShift = base.height - fontMetrics.metrics.supDrop; - subShift = base.depth + fontMetrics.metrics.subDrop; - } - - // Rule 18c - var minSupShift; - if (options.style === Style.DISPLAY) { - minSupShift = fontMetrics.metrics.sup1; - } else if (options.style.cramped) { - minSupShift = fontMetrics.metrics.sup3; - } else { - minSupShift = fontMetrics.metrics.sup2; - } - - // scriptspace is a font-size-independent size, so scale it - // appropriately - var multiplier = Style.TEXT.sizeMultiplier * - options.style.sizeMultiplier; - var scriptspace = - (0.5 / fontMetrics.metrics.ptPerEm) / multiplier + "em"; - - var supsub; - if (!group.value.sup) { - // Rule 18b - subShift = Math.max( - subShift, fontMetrics.metrics.sub1, - sub.height - 0.8 * fontMetrics.metrics.xHeight); - - supsub = buildCommon.makeVList([ - {type: "elem", elem: submid}, - ], "shift", subShift, options); - - supsub.children[0].style.marginRight = scriptspace; - - // Subscripts shouldn't be shifted by the base's italic correction. - // Account for that by shifting the subscript back the appropriate - // amount. Note we only do this when the base is a single symbol. - if (base instanceof domTree.symbolNode) { - supsub.children[0].style.marginLeft = -base.italic + "em"; - } - } else if (!group.value.sub) { - // Rule 18c, d - supShift = Math.max(supShift, minSupShift, - sup.depth + 0.25 * fontMetrics.metrics.xHeight); - - supsub = buildCommon.makeVList([ - {type: "elem", elem: supmid}, - ], "shift", -supShift, options); - - supsub.children[0].style.marginRight = scriptspace; - } else { - supShift = Math.max( - supShift, minSupShift, - sup.depth + 0.25 * fontMetrics.metrics.xHeight); - subShift = Math.max(subShift, fontMetrics.metrics.sub2); - - var ruleWidth = fontMetrics.metrics.defaultRuleThickness; - - // Rule 18e - if ((supShift - sup.depth) - (sub.height - subShift) < - 4 * ruleWidth) { - subShift = 4 * ruleWidth - (supShift - sup.depth) + sub.height; - var psi = 0.8 * fontMetrics.metrics.xHeight - - (supShift - sup.depth); - if (psi > 0) { - supShift += psi; - subShift -= psi; - } - } - - supsub = buildCommon.makeVList([ - {type: "elem", elem: submid, shift: subShift}, - {type: "elem", elem: supmid, shift: -supShift}, - ], "individualShift", null, options); - - // See comment above about subscripts not being shifted - if (base instanceof domTree.symbolNode) { - supsub.children[0].style.marginLeft = -base.italic + "em"; - } - - supsub.children[0].style.marginRight = scriptspace; - supsub.children[1].style.marginRight = scriptspace; - } - - // We ensure to wrap the supsub vlist in a span.msupsub to reset text-align - return makeSpan([getTypeOfGroup(group.value.base)], - [base, makeSpan(["msupsub"], [supsub])]); -}; - -groupTypes.genfrac = function(group, options, prev) { - // Fractions are handled in the TeXbook on pages 444-445, rules 15(a-e). - // Figure out what style this fraction should be in based on the - // function used - var fstyle = options.style; - if (group.value.size === "display") { - fstyle = Style.DISPLAY; - } else if (group.value.size === "text") { - fstyle = Style.TEXT; - } - - var nstyle = fstyle.fracNum(); - var dstyle = fstyle.fracDen(); - - var numer = buildGroup(group.value.numer, options.withStyle(nstyle)); - var numerreset = makeSpan([fstyle.reset(), nstyle.cls()], [numer]); - - var denom = buildGroup(group.value.denom, options.withStyle(dstyle)); - var denomreset = makeSpan([fstyle.reset(), dstyle.cls()], [denom]); - - var ruleWidth; - if (group.value.hasBarLine) { - ruleWidth = fontMetrics.metrics.defaultRuleThickness / - options.style.sizeMultiplier; - } else { - ruleWidth = 0; - } - - // Rule 15b - var numShift; - var clearance; - var denomShift; - if (fstyle.size === Style.DISPLAY.size) { - numShift = fontMetrics.metrics.num1; - if (ruleWidth > 0) { - clearance = 3 * ruleWidth; - } else { - clearance = 7 * fontMetrics.metrics.defaultRuleThickness; - } - denomShift = fontMetrics.metrics.denom1; - } else { - if (ruleWidth > 0) { - numShift = fontMetrics.metrics.num2; - clearance = ruleWidth; - } else { - numShift = fontMetrics.metrics.num3; - clearance = 3 * fontMetrics.metrics.defaultRuleThickness; - } - denomShift = fontMetrics.metrics.denom2; - } - - var frac; - if (ruleWidth === 0) { - // Rule 15c - var candiateClearance = - (numShift - numer.depth) - (denom.height - denomShift); - if (candiateClearance < clearance) { - numShift += 0.5 * (clearance - candiateClearance); - denomShift += 0.5 * (clearance - candiateClearance); - } - - frac = buildCommon.makeVList([ - {type: "elem", elem: denomreset, shift: denomShift}, - {type: "elem", elem: numerreset, shift: -numShift}, - ], "individualShift", null, options); - } else { - // Rule 15d - var axisHeight = fontMetrics.metrics.axisHeight; - - if ((numShift - numer.depth) - (axisHeight + 0.5 * ruleWidth) < - clearance) { - numShift += - clearance - ((numShift - numer.depth) - - (axisHeight + 0.5 * ruleWidth)); - } - - if ((axisHeight - 0.5 * ruleWidth) - (denom.height - denomShift) < - clearance) { - denomShift += - clearance - ((axisHeight - 0.5 * ruleWidth) - - (denom.height - denomShift)); - } - - var mid = makeSpan( - [options.style.reset(), Style.TEXT.cls(), "frac-line"]); - // Manually set the height of the line because its height is - // created in CSS - mid.height = ruleWidth; - - var midShift = -(axisHeight - 0.5 * ruleWidth); - - frac = buildCommon.makeVList([ - {type: "elem", elem: denomreset, shift: denomShift}, - {type: "elem", elem: mid, shift: midShift}, - {type: "elem", elem: numerreset, shift: -numShift}, - ], "individualShift", null, options); - } - - // Since we manually change the style sometimes (with \dfrac or \tfrac), - // account for the possible size change here. - frac.height *= fstyle.sizeMultiplier / options.style.sizeMultiplier; - frac.depth *= fstyle.sizeMultiplier / options.style.sizeMultiplier; - - // Rule 15e - var delimSize; - if (fstyle.size === Style.DISPLAY.size) { - delimSize = fontMetrics.metrics.delim1; - } else { - delimSize = fontMetrics.metrics.getDelim2(fstyle); - } - - var leftDelim; - var rightDelim; - if (group.value.leftDelim == null) { - leftDelim = makeNullDelimiter(options); - } else { - leftDelim = delimiter.customSizedDelim( - group.value.leftDelim, delimSize, true, - options.withStyle(fstyle), group.mode); - } - if (group.value.rightDelim == null) { - rightDelim = makeNullDelimiter(options); - } else { - rightDelim = delimiter.customSizedDelim( - group.value.rightDelim, delimSize, true, - options.withStyle(fstyle), group.mode); - } - - return makeSpan( - ["mord", options.style.reset(), fstyle.cls()], - [leftDelim, makeSpan(["mfrac"], [frac]), rightDelim], - options.getColor()); -}; - -groupTypes.array = function(group, options, prev) { - var r; - var c; - var nr = group.value.body.length; - var nc = 0; - var body = new Array(nr); - - // Horizontal spacing - var pt = 1 / fontMetrics.metrics.ptPerEm; - var arraycolsep = 5 * pt; // \arraycolsep in article.cls - - // Vertical spacing - var baselineskip = 12 * pt; // see size10.clo - // Default \arraystretch from lttab.dtx - // TODO(gagern): may get redefined once we have user-defined macros - var arraystretch = utils.deflt(group.value.arraystretch, 1); - var arrayskip = arraystretch * baselineskip; - var arstrutHeight = 0.7 * arrayskip; // \strutbox in ltfsstrc.dtx and - var arstrutDepth = 0.3 * arrayskip; // \@arstrutbox in lttab.dtx - - var totalHeight = 0; - for (r = 0; r < group.value.body.length; ++r) { - var inrow = group.value.body[r]; - var height = arstrutHeight; // \@array adds an \@arstrut - var depth = arstrutDepth; // to each tow (via the template) - - if (nc < inrow.length) { - nc = inrow.length; - } - - var outrow = new Array(inrow.length); - for (c = 0; c < inrow.length; ++c) { - var elt = buildGroup(inrow[c], options); - if (depth < elt.depth) { - depth = elt.depth; - } - if (height < elt.height) { - height = elt.height; - } - outrow[c] = elt; - } - - var gap = 0; - if (group.value.rowGaps[r]) { - gap = group.value.rowGaps[r].value; - switch (gap.unit) { - case "em": - gap = gap.number; - break; - case "ex": - gap = gap.number * fontMetrics.metrics.emPerEx; - break; - default: - console.error("Can't handle unit " + gap.unit); - gap = 0; - } - if (gap > 0) { // \@argarraycr - gap += arstrutDepth; - if (depth < gap) { - depth = gap; // \@xargarraycr - } - gap = 0; - } - } - - outrow.height = height; - outrow.depth = depth; - totalHeight += height; - outrow.pos = totalHeight; - totalHeight += depth + gap; // \@yargarraycr - body[r] = outrow; - } - - var offset = totalHeight / 2 + fontMetrics.metrics.axisHeight; - var colDescriptions = group.value.cols || []; - var cols = []; - var colSep; - var colDescrNum; - for (c = 0, colDescrNum = 0; - // Continue while either there are more columns or more column - // descriptions, so trailing separators don't get lost. - c < nc || colDescrNum < colDescriptions.length; - ++c, ++colDescrNum) { - - var colDescr = colDescriptions[colDescrNum] || {}; - - var firstSeparator = true; - while (colDescr.type === "separator") { - // If there is more than one separator in a row, add a space - // between them. - if (!firstSeparator) { - colSep = makeSpan(["arraycolsep"], []); - colSep.style.width = - fontMetrics.metrics.doubleRuleSep + "em"; - cols.push(colSep); - } - - if (colDescr.separator === "|") { - var separator = makeSpan( - ["vertical-separator"], - []); - separator.style.height = totalHeight + "em"; - separator.style.verticalAlign = - -(totalHeight - offset) + "em"; - - cols.push(separator); - } else { - throw new ParseError( - "Invalid separator type: " + colDescr.separator); - } - - colDescrNum++; - colDescr = colDescriptions[colDescrNum] || {}; - firstSeparator = false; - } - - if (c >= nc) { - continue; - } - - var sepwidth; - if (c > 0 || group.value.hskipBeforeAndAfter) { - sepwidth = utils.deflt(colDescr.pregap, arraycolsep); - if (sepwidth !== 0) { - colSep = makeSpan(["arraycolsep"], []); - colSep.style.width = sepwidth + "em"; - cols.push(colSep); - } - } - - var col = []; - for (r = 0; r < nr; ++r) { - var row = body[r]; - var elem = row[c]; - if (!elem) { - continue; - } - var shift = row.pos - offset; - elem.depth = row.depth; - elem.height = row.height; - col.push({type: "elem", elem: elem, shift: shift}); - } - - col = buildCommon.makeVList(col, "individualShift", null, options); - col = makeSpan( - ["col-align-" + (colDescr.align || "c")], - [col]); - cols.push(col); - - if (c < nc - 1 || group.value.hskipBeforeAndAfter) { - sepwidth = utils.deflt(colDescr.postgap, arraycolsep); - if (sepwidth !== 0) { - colSep = makeSpan(["arraycolsep"], []); - colSep.style.width = sepwidth + "em"; - cols.push(colSep); - } - } - } - body = makeSpan(["mtable"], cols); - return makeSpan(["mord"], [body], options.getColor()); -}; - -groupTypes.spacing = function(group, options, prev) { - if (group.value === "\\ " || group.value === "\\space" || - group.value === " " || group.value === "~") { - // Spaces are generated by adding an actual space. Each of these - // things has an entry in the symbols table, so these will be turned - // into appropriate outputs. - return makeSpan( - ["mord", "mspace"], - [buildCommon.mathsym(group.value, group.mode)] - ); - } else { - // Other kinds of spaces are of arbitrary width. We use CSS to - // generate these. - return makeSpan( - ["mord", "mspace", - buildCommon.spacingFunctions[group.value].className]); - } -}; - -groupTypes.llap = function(group, options, prev) { - var inner = makeSpan( - ["inner"], [buildGroup(group.value.body, options.reset())]); - var fix = makeSpan(["fix"], []); - return makeSpan( - ["llap", options.style.cls()], [inner, fix]); -}; - -groupTypes.rlap = function(group, options, prev) { - var inner = makeSpan( - ["inner"], [buildGroup(group.value.body, options.reset())]); - var fix = makeSpan(["fix"], []); - return makeSpan( - ["rlap", options.style.cls()], [inner, fix]); -}; - -groupTypes.op = function(group, options, prev) { - // Operators are handled in the TeXbook pg. 443-444, rule 13(a). - var supGroup; - var subGroup; - var hasLimits = false; - if (group.type === "supsub" ) { - // If we have limits, supsub will pass us its group to handle. Pull - // out the superscript and subscript and set the group to the op in - // its base. - supGroup = group.value.sup; - subGroup = group.value.sub; - group = group.value.base; - hasLimits = true; - } - - // Most operators have a large successor symbol, but these don't. - var noSuccessor = [ - "\\smallint", - ]; - - var large = false; - if (options.style.size === Style.DISPLAY.size && - group.value.symbol && - !utils.contains(noSuccessor, group.value.body)) { - - // Most symbol operators get larger in displaystyle (rule 13) - large = true; - } - - var base; - var baseShift = 0; - var slant = 0; - if (group.value.symbol) { - // If this is a symbol, create the symbol. - var style = large ? "Size2-Regular" : "Size1-Regular"; - base = buildCommon.makeSymbol( - group.value.body, style, "math", options.getColor(), - ["op-symbol", large ? "large-op" : "small-op", "mop"]); - - // Shift the symbol so its center lies on the axis (rule 13). It - // appears that our fonts have the centers of the symbols already - // almost on the axis, so these numbers are very small. Note we - // don't actually apply this here, but instead it is used either in - // the vlist creation or separately when there are no limits. - baseShift = (base.height - base.depth) / 2 - - fontMetrics.metrics.axisHeight * - options.style.sizeMultiplier; - - // The slant of the symbol is just its italic correction. - slant = base.italic; - } else { - // Otherwise, this is a text operator. Build the text from the - // operator's name. - // TODO(emily): Add a space in the middle of some of these - // operators, like \limsup - var output = []; - for (var i = 1; i < group.value.body.length; i++) { - output.push(buildCommon.mathsym(group.value.body[i], group.mode)); - } - base = makeSpan(["mop"], output, options.getColor()); - } - - if (hasLimits) { - // IE 8 clips \int if it is in a display: inline-block. We wrap it - // in a new span so it is an inline, and works. - base = makeSpan([], [base]); - - var supmid; - var supKern; - var submid; - var subKern; - // We manually have to handle the superscripts and subscripts. This, - // aside from the kern calculations, is copied from supsub. - if (supGroup) { - var sup = buildGroup( - supGroup, options.withStyle(options.style.sup())); - supmid = makeSpan( - [options.style.reset(), options.style.sup().cls()], [sup]); - - supKern = Math.max( - fontMetrics.metrics.bigOpSpacing1, - fontMetrics.metrics.bigOpSpacing3 - sup.depth); - } - - if (subGroup) { - var sub = buildGroup( - subGroup, options.withStyle(options.style.sub())); - submid = makeSpan( - [options.style.reset(), options.style.sub().cls()], - [sub]); - - subKern = Math.max( - fontMetrics.metrics.bigOpSpacing2, - fontMetrics.metrics.bigOpSpacing4 - sub.height); - } - - // Build the final group as a vlist of the possible subscript, base, - // and possible superscript. - var finalGroup; - var top; - var bottom; - if (!supGroup) { - top = base.height - baseShift; - - finalGroup = buildCommon.makeVList([ - {type: "kern", size: fontMetrics.metrics.bigOpSpacing5}, - {type: "elem", elem: submid}, - {type: "kern", size: subKern}, - {type: "elem", elem: base}, - ], "top", top, options); - - // Here, we shift the limits by the slant of the symbol. Note - // that we are supposed to shift the limits by 1/2 of the slant, - // but since we are centering the limits adding a full slant of - // margin will shift by 1/2 that. - finalGroup.children[0].style.marginLeft = -slant + "em"; - } else if (!subGroup) { - bottom = base.depth + baseShift; - - finalGroup = buildCommon.makeVList([ - {type: "elem", elem: base}, - {type: "kern", size: supKern}, - {type: "elem", elem: supmid}, - {type: "kern", size: fontMetrics.metrics.bigOpSpacing5}, - ], "bottom", bottom, options); - - // See comment above about slants - finalGroup.children[1].style.marginLeft = slant + "em"; - } else if (!supGroup && !subGroup) { - // This case probably shouldn't occur (this would mean the - // supsub was sending us a group with no superscript or - // subscript) but be safe. - return base; - } else { - bottom = fontMetrics.metrics.bigOpSpacing5 + - submid.height + submid.depth + - subKern + - base.depth + baseShift; - - finalGroup = buildCommon.makeVList([ - {type: "kern", size: fontMetrics.metrics.bigOpSpacing5}, - {type: "elem", elem: submid}, - {type: "kern", size: subKern}, - {type: "elem", elem: base}, - {type: "kern", size: supKern}, - {type: "elem", elem: supmid}, - {type: "kern", size: fontMetrics.metrics.bigOpSpacing5}, - ], "bottom", bottom, options); - - // See comment above about slants - finalGroup.children[0].style.marginLeft = -slant + "em"; - finalGroup.children[2].style.marginLeft = slant + "em"; - } - - return makeSpan(["mop", "op-limits"], [finalGroup]); - } else { - if (group.value.symbol) { - base.style.top = baseShift + "em"; - } - - return base; - } -}; - -groupTypes.katex = function(group, options, prev) { - // The KaTeX logo. The offsets for the K and a were chosen to look - // good, but the offsets for the T, E, and X were taken from the - // definition of \TeX in TeX (see TeXbook pg. 356) - var k = makeSpan( - ["k"], [buildCommon.mathsym("K", group.mode)]); - var a = makeSpan( - ["a"], [buildCommon.mathsym("A", group.mode)]); - - a.height = (a.height + 0.2) * 0.75; - a.depth = (a.height - 0.2) * 0.75; - - var t = makeSpan( - ["t"], [buildCommon.mathsym("T", group.mode)]); - var e = makeSpan( - ["e"], [buildCommon.mathsym("E", group.mode)]); - - e.height = (e.height - 0.2155); - e.depth = (e.depth + 0.2155); - - var x = makeSpan( - ["x"], [buildCommon.mathsym("X", group.mode)]); - - return makeSpan( - ["katex-logo", "mord"], [k, a, t, e, x], options.getColor()); -}; - -groupTypes.overline = function(group, options, prev) { - // Overlines are handled in the TeXbook pg 443, Rule 9. - - // Build the inner group in the cramped style. - var innerGroup = buildGroup(group.value.body, - options.withStyle(options.style.cramp())); - - var ruleWidth = fontMetrics.metrics.defaultRuleThickness / - options.style.sizeMultiplier; - - // Create the line above the body - var line = makeSpan( - [options.style.reset(), Style.TEXT.cls(), "overline-line"]); - line.height = ruleWidth; - line.maxFontSize = 1.0; - - // Generate the vlist, with the appropriate kerns - var vlist = buildCommon.makeVList([ - {type: "elem", elem: innerGroup}, - {type: "kern", size: 3 * ruleWidth}, - {type: "elem", elem: line}, - {type: "kern", size: ruleWidth}, - ], "firstBaseline", null, options); - - return makeSpan(["overline", "mord"], [vlist], options.getColor()); -}; - -groupTypes.underline = function(group, options, prev) { - // Underlines are handled in the TeXbook pg 443, Rule 10. - - // Build the inner group. - var innerGroup = buildGroup(group.value.body, options); - - var ruleWidth = fontMetrics.metrics.defaultRuleThickness / - options.style.sizeMultiplier; - - // Create the line above the body - var line = makeSpan( - [options.style.reset(), Style.TEXT.cls(), "underline-line"]); - line.height = ruleWidth; - line.maxFontSize = 1.0; - - // Generate the vlist, with the appropriate kerns - var vlist = buildCommon.makeVList([ - {type: "kern", size: ruleWidth}, - {type: "elem", elem: line}, - {type: "kern", size: 3 * ruleWidth}, - {type: "elem", elem: innerGroup}, - ], "top", innerGroup.height, options); - - return makeSpan(["underline", "mord"], [vlist], options.getColor()); -}; - -groupTypes.sqrt = function(group, options, prev) { - // Square roots are handled in the TeXbook pg. 443, Rule 11. - - // First, we do the same steps as in overline to build the inner group - // and line - var inner = buildGroup(group.value.body, - options.withStyle(options.style.cramp())); - - var ruleWidth = fontMetrics.metrics.defaultRuleThickness / - options.style.sizeMultiplier; - - var line = makeSpan( - [options.style.reset(), Style.TEXT.cls(), "sqrt-line"], [], - options.getColor()); - line.height = ruleWidth; - line.maxFontSize = 1.0; - - var phi = ruleWidth; - if (options.style.id < Style.TEXT.id) { - phi = fontMetrics.metrics.xHeight; - } - - // Calculate the clearance between the body and line - var lineClearance = ruleWidth + phi / 4; - - var innerHeight = - (inner.height + inner.depth) * options.style.sizeMultiplier; - var minDelimiterHeight = innerHeight + lineClearance + ruleWidth; - - // Create a \surd delimiter of the required minimum size - var delim = makeSpan(["sqrt-sign"], [ - delimiter.customSizedDelim("\\surd", minDelimiterHeight, - false, options, group.mode)], - options.getColor()); - - var delimDepth = (delim.height + delim.depth) - ruleWidth; - - // Adjust the clearance based on the delimiter size - if (delimDepth > inner.height + inner.depth + lineClearance) { - lineClearance = - (lineClearance + delimDepth - inner.height - inner.depth) / 2; - } - - // Shift the delimiter so that its top lines up with the top of the line - var delimShift = -(inner.height + lineClearance + ruleWidth) + delim.height; - delim.style.top = delimShift + "em"; - delim.height -= delimShift; - delim.depth += delimShift; - - // We add a special case here, because even when `inner` is empty, we - // still get a line. So, we use a simple heuristic to decide if we - // should omit the body entirely. (note this doesn't work for something - // like `\sqrt{\rlap{x}}`, but if someone is doing that they deserve for - // it not to work. - var body; - if (inner.height === 0 && inner.depth === 0) { - body = makeSpan(); - } else { - body = buildCommon.makeVList([ - {type: "elem", elem: inner}, - {type: "kern", size: lineClearance}, - {type: "elem", elem: line}, - {type: "kern", size: ruleWidth}, - ], "firstBaseline", null, options); - } - - if (!group.value.index) { - return makeSpan(["sqrt", "mord"], [delim, body]); - } else { - // Handle the optional root index - - // The index is always in scriptscript style - var root = buildGroup( - group.value.index, - options.withStyle(Style.SCRIPTSCRIPT)); - var rootWrap = makeSpan( - [options.style.reset(), Style.SCRIPTSCRIPT.cls()], - [root]); - - // Figure out the height and depth of the inner part - var innerRootHeight = Math.max(delim.height, body.height); - var innerRootDepth = Math.max(delim.depth, body.depth); - - // The amount the index is shifted by. This is taken from the TeX - // source, in the definition of `\r@@t`. - var toShift = 0.6 * (innerRootHeight - innerRootDepth); - - // Build a VList with the superscript shifted up correctly - var rootVList = buildCommon.makeVList( - [{type: "elem", elem: rootWrap}], - "shift", -toShift, options); - // Add a class surrounding it so we can add on the appropriate - // kerning - var rootVListWrap = makeSpan(["root"], [rootVList]); - - return makeSpan(["sqrt", "mord"], [rootVListWrap, delim, body]); - } -}; - -groupTypes.sizing = function(group, options, prev) { - // Handle sizing operators like \Huge. Real TeX doesn't actually allow - // these functions inside of math expressions, so we do some special - // handling. - var inner = buildExpression(group.value.value, - options.withSize(group.value.size), prev); - - var span = makeSpan(["mord"], - [makeSpan(["sizing", "reset-" + options.size, group.value.size, - options.style.cls()], - inner)]); - - // Calculate the correct maxFontSize manually - var fontSize = buildCommon.sizingMultiplier[group.value.size]; - span.maxFontSize = fontSize * options.style.sizeMultiplier; - - return span; -}; - -groupTypes.styling = function(group, options, prev) { - // Style changes are handled in the TeXbook on pg. 442, Rule 3. - - // Figure out what style we're changing to. - var style = { - "display": Style.DISPLAY, - "text": Style.TEXT, - "script": Style.SCRIPT, - "scriptscript": Style.SCRIPTSCRIPT, - }; - - var newStyle = style[group.value.style]; - - // Build the inner expression in the new style. - var inner = buildExpression( - group.value.value, options.withStyle(newStyle), prev); - - return makeSpan([options.style.reset(), newStyle.cls()], inner); -}; - -groupTypes.font = function(group, options, prev) { - var font = group.value.font; - return buildGroup(group.value.body, options.withFont(font), prev); -}; - -groupTypes.delimsizing = function(group, options, prev) { - var delim = group.value.value; - - if (delim === ".") { - // Empty delimiters still count as elements, even though they don't - // show anything. - return makeSpan([groupToType[group.value.delimType]]); - } - - // Use delimiter.sizedDelim to generate the delimiter. - return makeSpan( - [groupToType[group.value.delimType]], - [delimiter.sizedDelim( - delim, group.value.size, options, group.mode)]); -}; - -groupTypes.leftright = function(group, options, prev) { - // Build the inner expression - var inner = buildExpression(group.value.body, options.reset()); - - var innerHeight = 0; - var innerDepth = 0; - - // Calculate its height and depth - for (var i = 0; i < inner.length; i++) { - innerHeight = Math.max(inner[i].height, innerHeight); - innerDepth = Math.max(inner[i].depth, innerDepth); - } - - // The size of delimiters is the same, regardless of what style we are - // in. Thus, to correctly calculate the size of delimiter we need around - // a group, we scale down the inner size based on the size. - innerHeight *= options.style.sizeMultiplier; - innerDepth *= options.style.sizeMultiplier; - - var leftDelim; - if (group.value.left === ".") { - // Empty delimiters in \left and \right make null delimiter spaces. - leftDelim = makeNullDelimiter(options); - } else { - // Otherwise, use leftRightDelim to generate the correct sized - // delimiter. - leftDelim = delimiter.leftRightDelim( - group.value.left, innerHeight, innerDepth, options, - group.mode); - } - // Add it to the beginning of the expression - inner.unshift(leftDelim); - - var rightDelim; - // Same for the right delimiter - if (group.value.right === ".") { - rightDelim = makeNullDelimiter(options); - } else { - rightDelim = delimiter.leftRightDelim( - group.value.right, innerHeight, innerDepth, options, - group.mode); - } - // Add it to the end of the expression. - inner.push(rightDelim); - - return makeSpan( - ["minner", options.style.cls()], inner, options.getColor()); -}; - -groupTypes.rule = function(group, options, prev) { - // Make an empty span for the rule - var rule = makeSpan(["mord", "rule"], [], options.getColor()); - - // Calculate the shift, width, and height of the rule, and account for units - var shift = 0; - if (group.value.shift) { - shift = group.value.shift.number; - if (group.value.shift.unit === "ex") { - shift *= fontMetrics.metrics.xHeight; - } - } - - var width = group.value.width.number; - if (group.value.width.unit === "ex") { - width *= fontMetrics.metrics.xHeight; - } - - var height = group.value.height.number; - if (group.value.height.unit === "ex") { - height *= fontMetrics.metrics.xHeight; - } - - // The sizes of rules are absolute, so make it larger if we are in a - // smaller style. - shift /= options.style.sizeMultiplier; - width /= options.style.sizeMultiplier; - height /= options.style.sizeMultiplier; - - // Style the rule to the right size - rule.style.borderRightWidth = width + "em"; - rule.style.borderTopWidth = height + "em"; - rule.style.bottom = shift + "em"; - - // Record the height and width - rule.width = width; - rule.height = height + shift; - rule.depth = -shift; - - return rule; -}; - -groupTypes.kern = function(group, options, prev) { - // Make an empty span for the rule - var rule = makeSpan(["mord", "rule"], [], options.getColor()); - - var dimension = 0; - if (group.value.dimension) { - dimension = group.value.dimension.number; - if (group.value.dimension.unit === "ex") { - dimension *= fontMetrics.metrics.xHeight; - } - } - - dimension /= options.style.sizeMultiplier; - - rule.style.marginLeft = dimension + "em"; - - return rule; -}; - -groupTypes.accent = function(group, options, prev) { - // Accents are handled in the TeXbook pg. 443, rule 12. - var base = group.value.base; - - var supsubGroup; - if (group.type === "supsub") { - // If our base is a character box, and we have superscripts and - // subscripts, the supsub will defer to us. In particular, we want - // to attach the superscripts and subscripts to the inner body (so - // that the position of the superscripts and subscripts won't be - // affected by the height of the accent). We accomplish this by - // sticking the base of the accent into the base of the supsub, and - // rendering that, while keeping track of where the accent is. - - // The supsub group is the group that was passed in - var supsub = group; - // The real accent group is the base of the supsub group - group = supsub.value.base; - // The character box is the base of the accent group - base = group.value.base; - // Stick the character box into the base of the supsub group - supsub.value.base = base; - - // Rerender the supsub group with its new base, and store that - // result. - supsubGroup = buildGroup( - supsub, options.reset(), prev); - } - - // Build the base group - var body = buildGroup( - base, options.withStyle(options.style.cramp())); - - // Calculate the skew of the accent. This is based on the line "If the - // nucleus is not a single character, let s = 0; otherwise set s to the - // kern amount for the nucleus followed by the \skewchar of its font." - // Note that our skew metrics are just the kern between each character - // and the skewchar. - var skew; - if (isCharacterBox(base)) { - // If the base is a character box, then we want the skew of the - // innermost character. To do that, we find the innermost character: - var baseChar = getBaseElem(base); - // Then, we render its group to get the symbol inside it - var baseGroup = buildGroup( - baseChar, options.withStyle(options.style.cramp())); - // Finally, we pull the skew off of the symbol. - skew = baseGroup.skew; - // Note that we now throw away baseGroup, because the layers we - // removed with getBaseElem might contain things like \color which - // we can't get rid of. - // TODO(emily): Find a better way to get the skew - } else { - skew = 0; - } - - // calculate the amount of space between the body and the accent - var clearance = Math.min(body.height, fontMetrics.metrics.xHeight); - - // Build the accent - var accent = buildCommon.makeSymbol( - group.value.accent, "Main-Regular", "math", options.getColor()); - // Remove the italic correction of the accent, because it only serves to - // shift the accent over to a place we don't want. - accent.italic = 0; - - // The \vec character that the fonts use is a combining character, and - // thus shows up much too far to the left. To account for this, we add a - // specific class which shifts the accent over to where we want it. - // TODO(emily): Fix this in a better way, like by changing the font - var vecClass = group.value.accent === "\\vec" ? "accent-vec" : null; - - var accentBody = makeSpan(["accent-body", vecClass], [ - makeSpan([], [accent])]); - - accentBody = buildCommon.makeVList([ - {type: "elem", elem: body}, - {type: "kern", size: -clearance}, - {type: "elem", elem: accentBody}, - ], "firstBaseline", null, options); - - // Shift the accent over by the skew. Note we shift by twice the skew - // because we are centering the accent, so by adding 2*skew to the left, - // we shift it to the right by 1*skew. - accentBody.children[1].style.marginLeft = 2 * skew + "em"; - - var accentWrap = makeSpan(["mord", "accent"], [accentBody]); - - if (supsubGroup) { - // Here, we replace the "base" child of the supsub with our newly - // generated accent. - supsubGroup.children[0] = accentWrap; - - // Since we don't rerun the height calculation after replacing the - // accent, we manually recalculate height. - supsubGroup.height = Math.max(accentWrap.height, supsubGroup.height); - - // Accents should always be ords, even when their innards are not. - supsubGroup.classes[0] = "mord"; - - return supsubGroup; - } else { - return accentWrap; - } -}; - -groupTypes.phantom = function(group, options, prev) { - var elements = buildExpression( - group.value.value, - options.withPhantom(), - prev - ); - - // \phantom isn't supposed to affect the elements it contains. - // See "color" for more details. - return new buildCommon.makeFragment(elements); -}; - -/** - * buildGroup is the function that takes a group and calls the correct groupType - * function for it. It also handles the interaction of size and style changes - * between parents and children. - */ -var buildGroup = function(group, options, prev) { - if (!group) { - return makeSpan(); - } - - if (groupTypes[group.type]) { - // Call the groupTypes function - var groupNode = groupTypes[group.type](group, options, prev); - var multiplier; - - // If the style changed between the parent and the current group, - // account for the size difference - if (options.style !== options.parentStyle) { - multiplier = options.style.sizeMultiplier / - options.parentStyle.sizeMultiplier; - - groupNode.height *= multiplier; - groupNode.depth *= multiplier; - } - - // If the size changed between the parent and the current group, account - // for that size difference. - if (options.size !== options.parentSize) { - multiplier = buildCommon.sizingMultiplier[options.size] / - buildCommon.sizingMultiplier[options.parentSize]; - - groupNode.height *= multiplier; - groupNode.depth *= multiplier; - } - - return groupNode; - } else { - throw new ParseError( - "Got group of unknown type: '" + group.type + "'"); - } -}; - -/** - * Take an entire parse tree, and build it into an appropriate set of HTML - * nodes. - */ -var buildHTML = function(tree, options) { - // buildExpression is destructive, so we need to make a clone - // of the incoming tree so that it isn't accidentally changed - tree = JSON.parse(JSON.stringify(tree)); - - // Build the expression contained in the tree - var expression = buildExpression(tree, options); - var body = makeSpan(["base", options.style.cls()], expression); - - // Add struts, which ensure that the top of the HTML element falls at the - // height of the expression, and the bottom of the HTML element falls at the - // depth of the expression. - var topStrut = makeSpan(["strut"]); - var bottomStrut = makeSpan(["strut", "bottom"]); - - topStrut.style.height = body.height + "em"; - bottomStrut.style.height = (body.height + body.depth) + "em"; - // We'd like to use `vertical-align: top` but in IE 9 this lowers the - // baseline of the box to the bottom of this strut (instead staying in the - // normal place) so we use an absolute value for vertical-align instead - bottomStrut.style.verticalAlign = -body.depth + "em"; - - // Wrap the struts and body together - var htmlNode = makeSpan(["katex-html"], [topStrut, bottomStrut, body]); - - htmlNode.setAttribute("aria-hidden", "true"); - - return htmlNode; -}; - -module.exports = buildHTML; - -},{"./ParseError":6,"./Style":9,"./buildCommon":10,"./delimiter":14,"./domTree":15,"./fontMetrics":17,"./utils":25}],12:[function(require,module,exports){ -/** - * This file converts a parse tree into a cooresponding MathML tree. The main - * entry point is the `buildMathML` function, which takes a parse tree from the - * parser. - */ - -var buildCommon = require("./buildCommon"); -var fontMetrics = require("./fontMetrics"); -var mathMLTree = require("./mathMLTree"); -var ParseError = require("./ParseError"); -var symbols = require("./symbols"); -var utils = require("./utils"); - -var makeSpan = buildCommon.makeSpan; -var fontMap = buildCommon.fontMap; - -/** - * Takes a symbol and converts it into a MathML text node after performing - * optional replacement from symbols.js. - */ -var makeText = function(text, mode) { - if (symbols[mode][text] && symbols[mode][text].replace) { - text = symbols[mode][text].replace; - } - - return new mathMLTree.TextNode(text); -}; - -/** - * Returns the math variant as a string or null if none is required. - */ -var getVariant = function(group, options) { - var font = options.font; - if (!font) { - return null; - } - - var mode = group.mode; - if (font === "mathit") { - return "italic"; - } - - var value = group.value; - if (utils.contains(["\\imath", "\\jmath"], value)) { - return null; - } - - if (symbols[mode][value] && symbols[mode][value].replace) { - value = symbols[mode][value].replace; - } - - var fontName = fontMap[font].fontName; - if (fontMetrics.getCharacterMetrics(value, fontName)) { - return fontMap[options.font].variant; - } - - return null; -}; - -/** - * Functions for handling the different types of groups found in the parse - * tree. Each function should take a parse group and return a MathML node. - */ -var groupTypes = {}; - -groupTypes.mathord = function(group, options) { - var node = new mathMLTree.MathNode( - "mi", - [makeText(group.value, group.mode)]); - - var variant = getVariant(group, options); - if (variant) { - node.setAttribute("mathvariant", variant); - } - return node; -}; - -groupTypes.textord = function(group, options) { - var text = makeText(group.value, group.mode); - - var variant = getVariant(group, options) || "normal"; - - var node; - if (/[0-9]/.test(group.value)) { - // TODO(kevinb) merge adjacent <mn> nodes - // do it as a post processing step - node = new mathMLTree.MathNode("mn", [text]); - if (options.font) { - node.setAttribute("mathvariant", variant); - } - } else { - node = new mathMLTree.MathNode("mi", [text]); - node.setAttribute("mathvariant", variant); - } - - return node; -}; - -groupTypes.bin = function(group) { - var node = new mathMLTree.MathNode( - "mo", [makeText(group.value, group.mode)]); - - return node; -}; - -groupTypes.rel = function(group) { - var node = new mathMLTree.MathNode( - "mo", [makeText(group.value, group.mode)]); - - return node; -}; - -groupTypes.open = function(group) { - var node = new mathMLTree.MathNode( - "mo", [makeText(group.value, group.mode)]); - - return node; -}; - -groupTypes.close = function(group) { - var node = new mathMLTree.MathNode( - "mo", [makeText(group.value, group.mode)]); - - return node; -}; - -groupTypes.inner = function(group) { - var node = new mathMLTree.MathNode( - "mo", [makeText(group.value, group.mode)]); - - return node; -}; - -groupTypes.punct = function(group) { - var node = new mathMLTree.MathNode( - "mo", [makeText(group.value, group.mode)]); - - node.setAttribute("separator", "true"); - - return node; -}; - -groupTypes.ordgroup = function(group, options) { - var inner = buildExpression(group.value, options); - - var node = new mathMLTree.MathNode("mrow", inner); - - return node; -}; - -groupTypes.text = function(group, options) { - var inner = buildExpression(group.value.body, options); - - var node = new mathMLTree.MathNode("mtext", inner); - - return node; -}; - -groupTypes.color = function(group, options) { - var inner = buildExpression(group.value.value, options); - - var node = new mathMLTree.MathNode("mstyle", inner); - - node.setAttribute("mathcolor", group.value.color); - - return node; -}; - -groupTypes.supsub = function(group, options) { - var children = [buildGroup(group.value.base, options)]; - - if (group.value.sub) { - children.push(buildGroup(group.value.sub, options)); - } - - if (group.value.sup) { - children.push(buildGroup(group.value.sup, options)); - } - - var nodeType; - if (!group.value.sub) { - nodeType = "msup"; - } else if (!group.value.sup) { - nodeType = "msub"; - } else { - nodeType = "msubsup"; - } - - var node = new mathMLTree.MathNode(nodeType, children); - - return node; -}; - -groupTypes.genfrac = function(group, options) { - var node = new mathMLTree.MathNode( - "mfrac", - [buildGroup(group.value.numer, options), - buildGroup(group.value.denom, options)]); - - if (!group.value.hasBarLine) { - node.setAttribute("linethickness", "0px"); - } - - if (group.value.leftDelim != null || group.value.rightDelim != null) { - var withDelims = []; - - if (group.value.leftDelim != null) { - var leftOp = new mathMLTree.MathNode( - "mo", [new mathMLTree.TextNode(group.value.leftDelim)]); - - leftOp.setAttribute("fence", "true"); - - withDelims.push(leftOp); - } - - withDelims.push(node); - - if (group.value.rightDelim != null) { - var rightOp = new mathMLTree.MathNode( - "mo", [new mathMLTree.TextNode(group.value.rightDelim)]); - - rightOp.setAttribute("fence", "true"); - - withDelims.push(rightOp); - } - - var outerNode = new mathMLTree.MathNode("mrow", withDelims); - - return outerNode; - } - - return node; -}; - -groupTypes.array = function(group, options) { - return new mathMLTree.MathNode( - "mtable", group.value.body.map(function(row) { - return new mathMLTree.MathNode( - "mtr", row.map(function(cell) { - return new mathMLTree.MathNode( - "mtd", [buildGroup(cell, options)]); - })); - })); -}; - -groupTypes.sqrt = function(group, options) { - var node; - if (group.value.index) { - node = new mathMLTree.MathNode( - "mroot", [ - buildGroup(group.value.body, options), - buildGroup(group.value.index, options), - ]); - } else { - node = new mathMLTree.MathNode( - "msqrt", [buildGroup(group.value.body, options)]); - } - - return node; -}; - -groupTypes.leftright = function(group, options) { - var inner = buildExpression(group.value.body, options); - - if (group.value.left !== ".") { - var leftNode = new mathMLTree.MathNode( - "mo", [makeText(group.value.left, group.mode)]); - - leftNode.setAttribute("fence", "true"); - - inner.unshift(leftNode); - } - - if (group.value.right !== ".") { - var rightNode = new mathMLTree.MathNode( - "mo", [makeText(group.value.right, group.mode)]); - - rightNode.setAttribute("fence", "true"); - - inner.push(rightNode); - } - - var outerNode = new mathMLTree.MathNode("mrow", inner); - - return outerNode; -}; - -groupTypes.accent = function(group, options) { - var accentNode = new mathMLTree.MathNode( - "mo", [makeText(group.value.accent, group.mode)]); - - var node = new mathMLTree.MathNode( - "mover", - [buildGroup(group.value.base, options), - accentNode]); - - node.setAttribute("accent", "true"); - - return node; -}; - -groupTypes.spacing = function(group) { - var node; - - if (group.value === "\\ " || group.value === "\\space" || - group.value === " " || group.value === "~") { - node = new mathMLTree.MathNode( - "mtext", [new mathMLTree.TextNode("\u00a0")]); - } else { - node = new mathMLTree.MathNode("mspace"); - - node.setAttribute( - "width", buildCommon.spacingFunctions[group.value].size); - } - - return node; -}; - -groupTypes.op = function(group) { - var node; - - // TODO(emily): handle big operators using the `largeop` attribute - - if (group.value.symbol) { - // This is a symbol. Just add the symbol. - node = new mathMLTree.MathNode( - "mo", [makeText(group.value.body, group.mode)]); - } else { - // This is a text operator. Add all of the characters from the - // operator's name. - // TODO(emily): Add a space in the middle of some of these - // operators, like \limsup. - node = new mathMLTree.MathNode( - "mi", [new mathMLTree.TextNode(group.value.body.slice(1))]); - } - - return node; -}; - -groupTypes.katex = function(group) { - var node = new mathMLTree.MathNode( - "mtext", [new mathMLTree.TextNode("KaTeX")]); - - return node; -}; - -groupTypes.font = function(group, options) { - var font = group.value.font; - return buildGroup(group.value.body, options.withFont(font)); -}; - -groupTypes.delimsizing = function(group) { - var children = []; - - if (group.value.value !== ".") { - children.push(makeText(group.value.value, group.mode)); - } - - var node = new mathMLTree.MathNode("mo", children); - - if (group.value.delimType === "open" || - group.value.delimType === "close") { - // Only some of the delimsizing functions act as fences, and they - // return "open" or "close" delimTypes. - node.setAttribute("fence", "true"); - } else { - // Explicitly disable fencing if it's not a fence, to override the - // defaults. - node.setAttribute("fence", "false"); - } - - return node; -}; - -groupTypes.styling = function(group, options) { - var inner = buildExpression(group.value.value, options); - - var node = new mathMLTree.MathNode("mstyle", inner); - - var styleAttributes = { - "display": ["0", "true"], - "text": ["0", "false"], - "script": ["1", "false"], - "scriptscript": ["2", "false"], - }; - - var attr = styleAttributes[group.value.style]; - - node.setAttribute("scriptlevel", attr[0]); - node.setAttribute("displaystyle", attr[1]); - - return node; -}; - -groupTypes.sizing = function(group, options) { - var inner = buildExpression(group.value.value, options); - - var node = new mathMLTree.MathNode("mstyle", inner); - - // TODO(emily): This doesn't produce the correct size for nested size - // changes, because we don't keep state of what style we're currently - // in, so we can't reset the size to normal before changing it. Now - // that we're passing an options parameter we should be able to fix - // this. - node.setAttribute( - "mathsize", buildCommon.sizingMultiplier[group.value.size] + "em"); - - return node; -}; - -groupTypes.overline = function(group, options) { - var operator = new mathMLTree.MathNode( - "mo", [new mathMLTree.TextNode("\u203e")]); - operator.setAttribute("stretchy", "true"); - - var node = new mathMLTree.MathNode( - "mover", - [buildGroup(group.value.body, options), - operator]); - node.setAttribute("accent", "true"); - - return node; -}; - -groupTypes.underline = function(group, options) { - var operator = new mathMLTree.MathNode( - "mo", [new mathMLTree.TextNode("\u203e")]); - operator.setAttribute("stretchy", "true"); - - var node = new mathMLTree.MathNode( - "munder", - [buildGroup(group.value.body, options), - operator]); - node.setAttribute("accentunder", "true"); - - return node; -}; - -groupTypes.rule = function(group) { - // TODO(emily): Figure out if there's an actual way to draw black boxes - // in MathML. - var node = new mathMLTree.MathNode("mrow"); - - return node; -}; - -groupTypes.kern = function(group) { - // TODO(kevin): Figure out if there's a way to add space in MathML - var node = new mathMLTree.MathNode("mrow"); - - return node; -}; - -groupTypes.llap = function(group, options) { - var node = new mathMLTree.MathNode( - "mpadded", [buildGroup(group.value.body, options)]); - - node.setAttribute("lspace", "-1width"); - node.setAttribute("width", "0px"); - - return node; -}; - -groupTypes.rlap = function(group, options) { - var node = new mathMLTree.MathNode( - "mpadded", [buildGroup(group.value.body, options)]); - - node.setAttribute("width", "0px"); - - return node; -}; - -groupTypes.phantom = function(group, options, prev) { - var inner = buildExpression(group.value.value, options); - return new mathMLTree.MathNode("mphantom", inner); -}; - -/** - * Takes a list of nodes, builds them, and returns a list of the generated - * MathML nodes. A little simpler than the HTML version because we don't do any - * previous-node handling. - */ -var buildExpression = function(expression, options) { - var groups = []; - for (var i = 0; i < expression.length; i++) { - var group = expression[i]; - groups.push(buildGroup(group, options)); - } - return groups; -}; - -/** - * Takes a group from the parser and calls the appropriate groupTypes function - * on it to produce a MathML node. - */ -var buildGroup = function(group, options) { - if (!group) { - return new mathMLTree.MathNode("mrow"); - } - - if (groupTypes[group.type]) { - // Call the groupTypes function - return groupTypes[group.type](group, options); - } else { - throw new ParseError( - "Got group of unknown type: '" + group.type + "'"); - } -}; - -/** - * Takes a full parse tree and settings and builds a MathML representation of - * it. In particular, we put the elements from building the parse tree into a - * <semantics> tag so we can also include that TeX source as an annotation. - * - * Note that we actually return a domTree element with a `<math>` inside it so - * we can do appropriate styling. - */ -var buildMathML = function(tree, texExpression, options) { - var expression = buildExpression(tree, options); - - // Wrap up the expression in an mrow so it is presented in the semantics - // tag correctly. - var wrapper = new mathMLTree.MathNode("mrow", expression); - - // Build a TeX annotation of the source - var annotation = new mathMLTree.MathNode( - "annotation", [new mathMLTree.TextNode(texExpression)]); - - annotation.setAttribute("encoding", "application/x-tex"); - - var semantics = new mathMLTree.MathNode( - "semantics", [wrapper, annotation]); - - var math = new mathMLTree.MathNode("math", [semantics]); - - // You can't style <math> nodes, so we wrap the node in a span. - return makeSpan(["katex-mathml"], [math]); -}; - -module.exports = buildMathML; - -},{"./ParseError":6,"./buildCommon":10,"./fontMetrics":17,"./mathMLTree":20,"./symbols":23,"./utils":25}],13:[function(require,module,exports){ -var buildHTML = require("./buildHTML"); -var buildMathML = require("./buildMathML"); -var buildCommon = require("./buildCommon"); -var Options = require("./Options"); -var Settings = require("./Settings"); -var Style = require("./Style"); - -var makeSpan = buildCommon.makeSpan; - -var buildTree = function(tree, expression, settings) { - settings = settings || new Settings({}); - - var startStyle = Style.TEXT; - if (settings.displayMode) { - startStyle = Style.DISPLAY; - } - - // Setup the default options - var options = new Options({ - style: startStyle, - size: "size5", - }); - - // `buildHTML` sometimes messes with the parse tree (like turning bins -> - // ords), so we build the MathML version first. - var mathMLNode = buildMathML(tree, expression, options); - var htmlNode = buildHTML(tree, options); - - var katexNode = makeSpan(["katex"], [ - mathMLNode, htmlNode, - ]); - - if (settings.displayMode) { - return makeSpan(["katex-display"], [katexNode]); - } else { - return katexNode; - } -}; - -module.exports = buildTree; - -},{"./Options":5,"./Settings":8,"./Style":9,"./buildCommon":10,"./buildHTML":11,"./buildMathML":12}],14:[function(require,module,exports){ -/** - * This file deals with creating delimiters of various sizes. The TeXbook - * discusses these routines on page 441-442, in the "Another subroutine sets box - * x to a specified variable delimiter" paragraph. - * - * There are three main routines here. `makeSmallDelim` makes a delimiter in the - * normal font, but in either text, script, or scriptscript style. - * `makeLargeDelim` makes a delimiter in textstyle, but in one of the Size1, - * Size2, Size3, or Size4 fonts. `makeStackedDelim` makes a delimiter out of - * smaller pieces that are stacked on top of one another. - * - * The functions take a parameter `center`, which determines if the delimiter - * should be centered around the axis. - * - * Then, there are three exposed functions. `sizedDelim` makes a delimiter in - * one of the given sizes. This is used for things like `\bigl`. - * `customSizedDelim` makes a delimiter with a given total height+depth. It is - * called in places like `\sqrt`. `leftRightDelim` makes an appropriate - * delimiter which surrounds an expression of a given height an depth. It is - * used in `\left` and `\right`. - */ - -var ParseError = require("./ParseError"); -var Style = require("./Style"); - -var buildCommon = require("./buildCommon"); -var fontMetrics = require("./fontMetrics"); -var symbols = require("./symbols"); -var utils = require("./utils"); - -var makeSpan = buildCommon.makeSpan; - -/** - * Get the metrics for a given symbol and font, after transformation (i.e. - * after following replacement from symbols.js) - */ -var getMetrics = function(symbol, font) { - if (symbols.math[symbol] && symbols.math[symbol].replace) { - return fontMetrics.getCharacterMetrics( - symbols.math[symbol].replace, font); - } else { - return fontMetrics.getCharacterMetrics( - symbol, font); - } -}; - -/** - * Builds a symbol in the given font size (note size is an integer) - */ -var mathrmSize = function(value, size, mode) { - return buildCommon.makeSymbol(value, "Size" + size + "-Regular", mode); -}; - -/** - * Puts a delimiter span in a given style, and adds appropriate height, depth, - * and maxFontSizes. - */ -var styleWrap = function(delim, toStyle, options) { - var span = makeSpan( - ["style-wrap", options.style.reset(), toStyle.cls()], [delim]); - - var multiplier = toStyle.sizeMultiplier / options.style.sizeMultiplier; - - span.height *= multiplier; - span.depth *= multiplier; - span.maxFontSize = toStyle.sizeMultiplier; - - return span; -}; - -/** - * Makes a small delimiter. This is a delimiter that comes in the Main-Regular - * font, but is restyled to either be in textstyle, scriptstyle, or - * scriptscriptstyle. - */ -var makeSmallDelim = function(delim, style, center, options, mode) { - var text = buildCommon.makeSymbol(delim, "Main-Regular", mode); - - var span = styleWrap(text, style, options); - - if (center) { - var shift = - (1 - options.style.sizeMultiplier / style.sizeMultiplier) * - fontMetrics.metrics.axisHeight; - - span.style.top = shift + "em"; - span.height -= shift; - span.depth += shift; - } - - return span; -}; - -/** - * Makes a large delimiter. This is a delimiter that comes in the Size1, Size2, - * Size3, or Size4 fonts. It is always rendered in textstyle. - */ -var makeLargeDelim = function(delim, size, center, options, mode) { - var inner = mathrmSize(delim, size, mode); - - var span = styleWrap( - makeSpan(["delimsizing", "size" + size], - [inner], options.getColor()), - Style.TEXT, options); - - if (center) { - var shift = (1 - options.style.sizeMultiplier) * - fontMetrics.metrics.axisHeight; - - span.style.top = shift + "em"; - span.height -= shift; - span.depth += shift; - } - - return span; -}; - -/** - * Make an inner span with the given offset and in the given font. This is used - * in `makeStackedDelim` to make the stacking pieces for the delimiter. - */ -var makeInner = function(symbol, font, mode) { - var sizeClass; - // Apply the correct CSS class to choose the right font. - if (font === "Size1-Regular") { - sizeClass = "delim-size1"; - } else if (font === "Size4-Regular") { - sizeClass = "delim-size4"; - } - - var inner = makeSpan( - ["delimsizinginner", sizeClass], - [makeSpan([], [buildCommon.makeSymbol(symbol, font, mode)])]); - - // Since this will be passed into `makeVList` in the end, wrap the element - // in the appropriate tag that VList uses. - return {type: "elem", elem: inner}; -}; - -/** - * Make a stacked delimiter out of a given delimiter, with the total height at - * least `heightTotal`. This routine is mentioned on page 442 of the TeXbook. - */ -var makeStackedDelim = function(delim, heightTotal, center, options, mode) { - // There are four parts, the top, an optional middle, a repeated part, and a - // bottom. - var top; - var middle; - var repeat; - var bottom; - top = repeat = bottom = delim; - middle = null; - // Also keep track of what font the delimiters are in - var font = "Size1-Regular"; - - // We set the parts and font based on the symbol. Note that we use - // '\u23d0' instead of '|' and '\u2016' instead of '\\|' for the - // repeats of the arrows - if (delim === "\\uparrow") { - repeat = bottom = "\u23d0"; - } else if (delim === "\\Uparrow") { - repeat = bottom = "\u2016"; - } else if (delim === "\\downarrow") { - top = repeat = "\u23d0"; - } else if (delim === "\\Downarrow") { - top = repeat = "\u2016"; - } else if (delim === "\\updownarrow") { - top = "\\uparrow"; - repeat = "\u23d0"; - bottom = "\\downarrow"; - } else if (delim === "\\Updownarrow") { - top = "\\Uparrow"; - repeat = "\u2016"; - bottom = "\\Downarrow"; - } else if (delim === "[" || delim === "\\lbrack") { - top = "\u23a1"; - repeat = "\u23a2"; - bottom = "\u23a3"; - font = "Size4-Regular"; - } else if (delim === "]" || delim === "\\rbrack") { - top = "\u23a4"; - repeat = "\u23a5"; - bottom = "\u23a6"; - font = "Size4-Regular"; - } else if (delim === "\\lfloor") { - repeat = top = "\u23a2"; - bottom = "\u23a3"; - font = "Size4-Regular"; - } else if (delim === "\\lceil") { - top = "\u23a1"; - repeat = bottom = "\u23a2"; - font = "Size4-Regular"; - } else if (delim === "\\rfloor") { - repeat = top = "\u23a5"; - bottom = "\u23a6"; - font = "Size4-Regular"; - } else if (delim === "\\rceil") { - top = "\u23a4"; - repeat = bottom = "\u23a5"; - font = "Size4-Regular"; - } else if (delim === "(") { - top = "\u239b"; - repeat = "\u239c"; - bottom = "\u239d"; - font = "Size4-Regular"; - } else if (delim === ")") { - top = "\u239e"; - repeat = "\u239f"; - bottom = "\u23a0"; - font = "Size4-Regular"; - } else if (delim === "\\{" || delim === "\\lbrace") { - top = "\u23a7"; - middle = "\u23a8"; - bottom = "\u23a9"; - repeat = "\u23aa"; - font = "Size4-Regular"; - } else if (delim === "\\}" || delim === "\\rbrace") { - top = "\u23ab"; - middle = "\u23ac"; - bottom = "\u23ad"; - repeat = "\u23aa"; - font = "Size4-Regular"; - } else if (delim === "\\lgroup") { - top = "\u23a7"; - bottom = "\u23a9"; - repeat = "\u23aa"; - font = "Size4-Regular"; - } else if (delim === "\\rgroup") { - top = "\u23ab"; - bottom = "\u23ad"; - repeat = "\u23aa"; - font = "Size4-Regular"; - } else if (delim === "\\lmoustache") { - top = "\u23a7"; - bottom = "\u23ad"; - repeat = "\u23aa"; - font = "Size4-Regular"; - } else if (delim === "\\rmoustache") { - top = "\u23ab"; - bottom = "\u23a9"; - repeat = "\u23aa"; - font = "Size4-Regular"; - } else if (delim === "\\surd") { - top = "\ue001"; - bottom = "\u23b7"; - repeat = "\ue000"; - font = "Size4-Regular"; - } - - // Get the metrics of the four sections - var topMetrics = getMetrics(top, font); - var topHeightTotal = topMetrics.height + topMetrics.depth; - var repeatMetrics = getMetrics(repeat, font); - var repeatHeightTotal = repeatMetrics.height + repeatMetrics.depth; - var bottomMetrics = getMetrics(bottom, font); - var bottomHeightTotal = bottomMetrics.height + bottomMetrics.depth; - var middleHeightTotal = 0; - var middleFactor = 1; - if (middle !== null) { - var middleMetrics = getMetrics(middle, font); - middleHeightTotal = middleMetrics.height + middleMetrics.depth; - middleFactor = 2; // repeat symmetrically above and below middle - } - - // Calcuate the minimal height that the delimiter can have. - // It is at least the size of the top, bottom, and optional middle combined. - var minHeight = topHeightTotal + bottomHeightTotal + middleHeightTotal; - - // Compute the number of copies of the repeat symbol we will need - var repeatCount = Math.ceil( - (heightTotal - minHeight) / (middleFactor * repeatHeightTotal)); - - // Compute the total height of the delimiter including all the symbols - var realHeightTotal = - minHeight + repeatCount * middleFactor * repeatHeightTotal; - - // The center of the delimiter is placed at the center of the axis. Note - // that in this context, "center" means that the delimiter should be - // centered around the axis in the current style, while normally it is - // centered around the axis in textstyle. - var axisHeight = fontMetrics.metrics.axisHeight; - if (center) { - axisHeight *= options.style.sizeMultiplier; - } - // Calculate the depth - var depth = realHeightTotal / 2 - axisHeight; - - // Now, we start building the pieces that will go into the vlist - - // Keep a list of the inner pieces - var inners = []; - - // Add the bottom symbol - inners.push(makeInner(bottom, font, mode)); - - var i; - if (middle === null) { - // Add that many symbols - for (i = 0; i < repeatCount; i++) { - inners.push(makeInner(repeat, font, mode)); - } - } else { - // When there is a middle bit, we need the middle part and two repeated - // sections - for (i = 0; i < repeatCount; i++) { - inners.push(makeInner(repeat, font, mode)); - } - inners.push(makeInner(middle, font, mode)); - for (i = 0; i < repeatCount; i++) { - inners.push(makeInner(repeat, font, mode)); - } - } - - // Add the top symbol - inners.push(makeInner(top, font, mode)); - - // Finally, build the vlist - var inner = buildCommon.makeVList(inners, "bottom", depth, options); - - return styleWrap( - makeSpan(["delimsizing", "mult"], [inner], options.getColor()), - Style.TEXT, options); -}; - -// There are three kinds of delimiters, delimiters that stack when they become -// too large -var stackLargeDelimiters = [ - "(", ")", "[", "\\lbrack", "]", "\\rbrack", - "\\{", "\\lbrace", "\\}", "\\rbrace", - "\\lfloor", "\\rfloor", "\\lceil", "\\rceil", - "\\surd", -]; - -// delimiters that always stack -var stackAlwaysDelimiters = [ - "\\uparrow", "\\downarrow", "\\updownarrow", - "\\Uparrow", "\\Downarrow", "\\Updownarrow", - "|", "\\|", "\\vert", "\\Vert", - "\\lvert", "\\rvert", "\\lVert", "\\rVert", - "\\lgroup", "\\rgroup", "\\lmoustache", "\\rmoustache", -]; - -// and delimiters that never stack -var stackNeverDelimiters = [ - "<", ">", "\\langle", "\\rangle", "/", "\\backslash", "\\lt", "\\gt", -]; - -// Metrics of the different sizes. Found by looking at TeX's output of -// $\bigl| // \Bigl| \biggl| \Biggl| \showlists$ -// Used to create stacked delimiters of appropriate sizes in makeSizedDelim. -var sizeToMaxHeight = [0, 1.2, 1.8, 2.4, 3.0]; - -/** - * Used to create a delimiter of a specific size, where `size` is 1, 2, 3, or 4. - */ -var makeSizedDelim = function(delim, size, options, mode) { - // < and > turn into \langle and \rangle in delimiters - if (delim === "<" || delim === "\\lt") { - delim = "\\langle"; - } else if (delim === ">" || delim === "\\gt") { - delim = "\\rangle"; - } - - // Sized delimiters are never centered. - if (utils.contains(stackLargeDelimiters, delim) || - utils.contains(stackNeverDelimiters, delim)) { - return makeLargeDelim(delim, size, false, options, mode); - } else if (utils.contains(stackAlwaysDelimiters, delim)) { - return makeStackedDelim( - delim, sizeToMaxHeight[size], false, options, mode); - } else { - throw new ParseError("Illegal delimiter: '" + delim + "'"); - } -}; - -/** - * There are three different sequences of delimiter sizes that the delimiters - * follow depending on the kind of delimiter. This is used when creating custom - * sized delimiters to decide whether to create a small, large, or stacked - * delimiter. - * - * In real TeX, these sequences aren't explicitly defined, but are instead - * defined inside the font metrics. Since there are only three sequences that - * are possible for the delimiters that TeX defines, it is easier to just encode - * them explicitly here. - */ - -// Delimiters that never stack try small delimiters and large delimiters only -var stackNeverDelimiterSequence = [ - {type: "small", style: Style.SCRIPTSCRIPT}, - {type: "small", style: Style.SCRIPT}, - {type: "small", style: Style.TEXT}, - {type: "large", size: 1}, - {type: "large", size: 2}, - {type: "large", size: 3}, - {type: "large", size: 4}, -]; - -// Delimiters that always stack try the small delimiters first, then stack -var stackAlwaysDelimiterSequence = [ - {type: "small", style: Style.SCRIPTSCRIPT}, - {type: "small", style: Style.SCRIPT}, - {type: "small", style: Style.TEXT}, - {type: "stack"}, -]; - -// Delimiters that stack when large try the small and then large delimiters, and -// stack afterwards -var stackLargeDelimiterSequence = [ - {type: "small", style: Style.SCRIPTSCRIPT}, - {type: "small", style: Style.SCRIPT}, - {type: "small", style: Style.TEXT}, - {type: "large", size: 1}, - {type: "large", size: 2}, - {type: "large", size: 3}, - {type: "large", size: 4}, - {type: "stack"}, -]; - -/** - * Get the font used in a delimiter based on what kind of delimiter it is. - */ -var delimTypeToFont = function(type) { - if (type.type === "small") { - return "Main-Regular"; - } else if (type.type === "large") { - return "Size" + type.size + "-Regular"; - } else if (type.type === "stack") { - return "Size4-Regular"; - } -}; - -/** - * Traverse a sequence of types of delimiters to decide what kind of delimiter - * should be used to create a delimiter of the given height+depth. - */ -var traverseSequence = function(delim, height, sequence, options) { - // Here, we choose the index we should start at in the sequences. In smaller - // sizes (which correspond to larger numbers in style.size) we start earlier - // in the sequence. Thus, scriptscript starts at index 3-3=0, script starts - // at index 3-2=1, text starts at 3-1=2, and display starts at min(2,3-0)=2 - var start = Math.min(2, 3 - options.style.size); - for (var i = start; i < sequence.length; i++) { - if (sequence[i].type === "stack") { - // This is always the last delimiter, so we just break the loop now. - break; - } - - var metrics = getMetrics(delim, delimTypeToFont(sequence[i])); - var heightDepth = metrics.height + metrics.depth; - - // Small delimiters are scaled down versions of the same font, so we - // account for the style change size. - - if (sequence[i].type === "small") { - heightDepth *= sequence[i].style.sizeMultiplier; - } - - // Check if the delimiter at this size works for the given height. - if (heightDepth > height) { - return sequence[i]; - } - } - - // If we reached the end of the sequence, return the last sequence element. - return sequence[sequence.length - 1]; -}; - -/** - * Make a delimiter of a given height+depth, with optional centering. Here, we - * traverse the sequences, and create a delimiter that the sequence tells us to. - */ -var makeCustomSizedDelim = function(delim, height, center, options, mode) { - if (delim === "<" || delim === "\\lt") { - delim = "\\langle"; - } else if (delim === ">" || delim === "\\gt") { - delim = "\\rangle"; - } - - // Decide what sequence to use - var sequence; - if (utils.contains(stackNeverDelimiters, delim)) { - sequence = stackNeverDelimiterSequence; - } else if (utils.contains(stackLargeDelimiters, delim)) { - sequence = stackLargeDelimiterSequence; - } else { - sequence = stackAlwaysDelimiterSequence; - } - - // Look through the sequence - var delimType = traverseSequence(delim, height, sequence, options); - - // Depending on the sequence element we decided on, call the appropriate - // function. - if (delimType.type === "small") { - return makeSmallDelim(delim, delimType.style, center, options, mode); - } else if (delimType.type === "large") { - return makeLargeDelim(delim, delimType.size, center, options, mode); - } else if (delimType.type === "stack") { - return makeStackedDelim(delim, height, center, options, mode); - } -}; - -/** - * Make a delimiter for use with `\left` and `\right`, given a height and depth - * of an expression that the delimiters surround. - */ -var makeLeftRightDelim = function(delim, height, depth, options, mode) { - // We always center \left/\right delimiters, so the axis is always shifted - var axisHeight = - fontMetrics.metrics.axisHeight * options.style.sizeMultiplier; - - // Taken from TeX source, tex.web, function make_left_right - var delimiterFactor = 901; - var delimiterExtend = 5.0 / fontMetrics.metrics.ptPerEm; - - var maxDistFromAxis = Math.max( - height - axisHeight, depth + axisHeight); - - var totalHeight = Math.max( - // In real TeX, calculations are done using integral values which are - // 65536 per pt, or 655360 per em. So, the division here truncates in - // TeX but doesn't here, producing different results. If we wanted to - // exactly match TeX's calculation, we could do - // Math.floor(655360 * maxDistFromAxis / 500) * - // delimiterFactor / 655360 - // (To see the difference, compare - // x^{x^{\left(\rule{0.1em}{0.68em}\right)}} - // in TeX and KaTeX) - maxDistFromAxis / 500 * delimiterFactor, - 2 * maxDistFromAxis - delimiterExtend); - - // Finally, we defer to `makeCustomSizedDelim` with our calculated total - // height - return makeCustomSizedDelim(delim, totalHeight, true, options, mode); -}; - -module.exports = { - sizedDelim: makeSizedDelim, - customSizedDelim: makeCustomSizedDelim, - leftRightDelim: makeLeftRightDelim, -}; - -},{"./ParseError":6,"./Style":9,"./buildCommon":10,"./fontMetrics":17,"./symbols":23,"./utils":25}],15:[function(require,module,exports){ -/** - * These objects store the data about the DOM nodes we create, as well as some - * extra data. They can then be transformed into real DOM nodes with the - * `toNode` function or HTML markup using `toMarkup`. They are useful for both - * storing extra properties on the nodes, as well as providing a way to easily - * work with the DOM. - * - * Similar functions for working with MathML nodes exist in mathMLTree.js. - */ -var unicodeRegexes = require("./unicodeRegexes"); -var utils = require("./utils"); - -/** - * Create an HTML className based on a list of classes. In addition to joining - * with spaces, we also remove null or empty classes. - */ -var createClass = function(classes) { - classes = classes.slice(); - for (var i = classes.length - 1; i >= 0; i--) { - if (!classes[i]) { - classes.splice(i, 1); - } - } - - return classes.join(" "); -}; - -/** - * This node represents a span node, with a className, a list of children, and - * an inline style. It also contains information about its height, depth, and - * maxFontSize. - */ -function span(classes, children, height, depth, maxFontSize, style) { - this.classes = classes || []; - this.children = children || []; - this.height = height || 0; - this.depth = depth || 0; - this.maxFontSize = maxFontSize || 0; - this.style = style || {}; - this.attributes = {}; -} - -/** - * Sets an arbitrary attribute on the span. Warning: use this wisely. Not all - * browsers support attributes the same, and having too many custom attributes - * is probably bad. - */ -span.prototype.setAttribute = function(attribute, value) { - this.attributes[attribute] = value; -}; - -/** - * Convert the span into an HTML node - */ -span.prototype.toNode = function() { - var span = document.createElement("span"); - - // Apply the class - span.className = createClass(this.classes); - - // Apply inline styles - for (var style in this.style) { - if (Object.prototype.hasOwnProperty.call(this.style, style)) { - span.style[style] = this.style[style]; - } - } - - // Apply attributes - for (var attr in this.attributes) { - if (Object.prototype.hasOwnProperty.call(this.attributes, attr)) { - span.setAttribute(attr, this.attributes[attr]); - } - } - - // Append the children, also as HTML nodes - for (var i = 0; i < this.children.length; i++) { - span.appendChild(this.children[i].toNode()); - } - - return span; -}; - -/** - * Convert the span into an HTML markup string - */ -span.prototype.toMarkup = function() { - var markup = "<span"; - - // Add the class - if (this.classes.length) { - markup += " class=\""; - markup += utils.escape(createClass(this.classes)); - markup += "\""; - } - - var styles = ""; - - // Add the styles, after hyphenation - for (var style in this.style) { - if (this.style.hasOwnProperty(style)) { - styles += utils.hyphenate(style) + ":" + this.style[style] + ";"; - } - } - - if (styles) { - markup += " style=\"" + utils.escape(styles) + "\""; - } - - // Add the attributes - for (var attr in this.attributes) { - if (Object.prototype.hasOwnProperty.call(this.attributes, attr)) { - markup += " " + attr + "=\""; - markup += utils.escape(this.attributes[attr]); - markup += "\""; - } - } - - markup += ">"; - - // Add the markup of the children, also as markup - for (var i = 0; i < this.children.length; i++) { - markup += this.children[i].toMarkup(); - } - - markup += "</span>"; - - return markup; -}; - -/** - * This node represents a document fragment, which contains elements, but when - * placed into the DOM doesn't have any representation itself. Thus, it only - * contains children and doesn't have any HTML properties. It also keeps track - * of a height, depth, and maxFontSize. - */ -function documentFragment(children, height, depth, maxFontSize) { - this.children = children || []; - this.height = height || 0; - this.depth = depth || 0; - this.maxFontSize = maxFontSize || 0; -} - -/** - * Convert the fragment into a node - */ -documentFragment.prototype.toNode = function() { - // Create a fragment - var frag = document.createDocumentFragment(); - - // Append the children - for (var i = 0; i < this.children.length; i++) { - frag.appendChild(this.children[i].toNode()); - } - - return frag; -}; - -/** - * Convert the fragment into HTML markup - */ -documentFragment.prototype.toMarkup = function() { - var markup = ""; - - // Simply concatenate the markup for the children together - for (var i = 0; i < this.children.length; i++) { - markup += this.children[i].toMarkup(); - } - - return markup; -}; - -var iCombinations = { - 'î': '\u0131\u0302', - 'ï': '\u0131\u0308', - 'Ã': '\u0131\u0301', - // 'Ä«': '\u0131\u0304', // enable when we add Extended Latin - 'ì': '\u0131\u0300', -}; - -/** - * A symbol node contains information about a single symbol. It either renders - * to a single text node, or a span with a single text node in it, depending on - * whether it has CSS classes, styles, or needs italic correction. - */ -function symbolNode(value, height, depth, italic, skew, classes, style) { - this.value = value || ""; - this.height = height || 0; - this.depth = depth || 0; - this.italic = italic || 0; - this.skew = skew || 0; - this.classes = classes || []; - this.style = style || {}; - this.maxFontSize = 0; - - // Mark CJK characters with specific classes so that we can specify which - // fonts to use. This allows us to render these characters with a serif - // font in situations where the browser would either default to a sans serif - // or render a placeholder character. - if (unicodeRegexes.cjkRegex.test(value)) { - // I couldn't find any fonts that contained Hangul as well as all of - // the other characters we wanted to test there for it gets its own - // CSS class. - if (unicodeRegexes.hangulRegex.test(value)) { - this.classes.push('hangul_fallback'); - } else { - this.classes.push('cjk_fallback'); - } - } - - if (/[îïÃì]/.test(this.value)) { // add Ä« when we add Extended Latin - this.value = iCombinations[this.value]; - } -} - -/** - * Creates a text node or span from a symbol node. Note that a span is only - * created if it is needed. - */ -symbolNode.prototype.toNode = function() { - var node = document.createTextNode(this.value); - var span = null; - - if (this.italic > 0) { - span = document.createElement("span"); - span.style.marginRight = this.italic + "em"; - } - - if (this.classes.length > 0) { - span = span || document.createElement("span"); - span.className = createClass(this.classes); - } - - for (var style in this.style) { - if (this.style.hasOwnProperty(style)) { - span = span || document.createElement("span"); - span.style[style] = this.style[style]; - } - } - - if (span) { - span.appendChild(node); - return span; - } else { - return node; - } -}; - -/** - * Creates markup for a symbol node. - */ -symbolNode.prototype.toMarkup = function() { - // TODO(alpert): More duplication than I'd like from - // span.prototype.toMarkup and symbolNode.prototype.toNode... - var needsSpan = false; - - var markup = "<span"; - - if (this.classes.length) { - needsSpan = true; - markup += " class=\""; - markup += utils.escape(createClass(this.classes)); - markup += "\""; - } - - var styles = ""; - - if (this.italic > 0) { - styles += "margin-right:" + this.italic + "em;"; - } - for (var style in this.style) { - if (this.style.hasOwnProperty(style)) { - styles += utils.hyphenate(style) + ":" + this.style[style] + ";"; - } - } - - if (styles) { - needsSpan = true; - markup += " style=\"" + utils.escape(styles) + "\""; - } - - var escaped = utils.escape(this.value); - if (needsSpan) { - markup += ">"; - markup += escaped; - markup += "</span>"; - return markup; - } else { - return escaped; - } -}; - -module.exports = { - span: span, - documentFragment: documentFragment, - symbolNode: symbolNode, -}; - -},{"./unicodeRegexes":24,"./utils":25}],16:[function(require,module,exports){ -/* eslint no-constant-condition:0 */ -var fontMetrics = require("./fontMetrics"); -var parseData = require("./parseData"); -var ParseError = require("./ParseError"); - -var ParseNode = parseData.ParseNode; - -/** - * Parse the body of the environment, with rows delimited by \\ and - * columns delimited by &, and create a nested list in row-major order - * with one group per cell. - */ -function parseArray(parser, result) { - var row = []; - var body = [row]; - var rowGaps = []; - while (true) { - var cell = parser.parseExpression(false, null); - row.push(new ParseNode("ordgroup", cell, parser.mode)); - var next = parser.nextToken.text; - if (next === "&") { - parser.consume(); - } else if (next === "\\end") { - break; - } else if (next === "\\\\" || next === "\\cr") { - var cr = parser.parseFunction(); - rowGaps.push(cr.value.size); - row = []; - body.push(row); - } else { - throw new ParseError("Expected & or \\\\ or \\end", - parser.nextToken); - } - } - result.body = body; - result.rowGaps = rowGaps; - return new ParseNode(result.type, result, parser.mode); -} - -/* - * An environment definition is very similar to a function definition: - * it is declared with a name or a list of names, a set of properties - * and a handler containing the actual implementation. - * - * The properties include: - * - numArgs: The number of arguments after the \begin{name} function. - * - argTypes: (optional) Just like for a function - * - allowedInText: (optional) Whether or not the environment is allowed inside - * text mode (default false) (not enforced yet) - * - numOptionalArgs: (optional) Just like for a function - * A bare number instead of that object indicates the numArgs value. - * - * The handler function will receive two arguments - * - context: information and references provided by the parser - * - args: an array of arguments passed to \begin{name} - * The context contains the following properties: - * - envName: the name of the environment, one of the listed names. - * - parser: the parser object - * - lexer: the lexer object - * - positions: the positions associated with these arguments from args. - * The handler must return a ParseResult. - */ - -function defineEnvironment(names, props, handler) { - if (typeof names === "string") { - names = [names]; - } - if (typeof props === "number") { - props = { numArgs: props }; - } - // Set default values of environments - var data = { - numArgs: props.numArgs || 0, - argTypes: props.argTypes, - greediness: 1, - allowedInText: !!props.allowedInText, - numOptionalArgs: props.numOptionalArgs || 0, - handler: handler, - }; - for (var i = 0; i < names.length; ++i) { - module.exports[names[i]] = data; - } -} - -// Arrays are part of LaTeX, defined in lttab.dtx so its documentation -// is part of the source2e.pdf file of LaTeX2e source documentation. -defineEnvironment("array", { - numArgs: 1, -}, function(context, args) { - var colalign = args[0]; - colalign = colalign.value.map ? colalign.value : [colalign]; - var cols = colalign.map(function(node) { - var ca = node.value; - if ("lcr".indexOf(ca) !== -1) { - return { - type: "align", - align: ca, - }; - } else if (ca === "|") { - return { - type: "separator", - separator: "|", - }; - } - throw new ParseError( - "Unknown column alignment: " + node.value, - node); - }); - var res = { - type: "array", - cols: cols, - hskipBeforeAndAfter: true, // \@preamble in lttab.dtx - }; - res = parseArray(context.parser, res); - return res; -}); - -// The matrix environments of amsmath builds on the array environment -// of LaTeX, which is discussed above. -defineEnvironment([ - "matrix", - "pmatrix", - "bmatrix", - "Bmatrix", - "vmatrix", - "Vmatrix", -], { -}, function(context) { - var delimiters = { - "matrix": null, - "pmatrix": ["(", ")"], - "bmatrix": ["[", "]"], - "Bmatrix": ["\\{", "\\}"], - "vmatrix": ["|", "|"], - "Vmatrix": ["\\Vert", "\\Vert"], - }[context.envName]; - var res = { - type: "array", - hskipBeforeAndAfter: false, // \hskip -\arraycolsep in amsmath - }; - res = parseArray(context.parser, res); - if (delimiters) { - res = new ParseNode("leftright", { - body: [res], - left: delimiters[0], - right: delimiters[1], - }, context.mode); - } - return res; -}); - -// A cases environment (in amsmath.sty) is almost equivalent to -// \def\arraystretch{1.2}% -// \left\{\begin{array}{@{}l@{\quad}l@{}} … \end{array}\right. -defineEnvironment("cases", { -}, function(context) { - var res = { - type: "array", - arraystretch: 1.2, - cols: [{ - type: "align", - align: "l", - pregap: 0, - postgap: fontMetrics.metrics.quad, - }, { - type: "align", - align: "l", - pregap: 0, - postgap: 0, - }], - }; - res = parseArray(context.parser, res); - res = new ParseNode("leftright", { - body: [res], - left: "\\{", - right: ".", - }, context.mode); - return res; -}); - -// An aligned environment is like the align* environment -// except it operates within math mode. -// Note that we assume \nomallineskiplimit to be zero, -// so that \strut@ is the same as \strut. -defineEnvironment("aligned", { -}, function(context) { - var res = { - type: "array", - cols: [], - }; - res = parseArray(context.parser, res); - var emptyGroup = new ParseNode("ordgroup", [], context.mode); - var numCols = 0; - res.value.body.forEach(function(row) { - var i; - for (i = 1; i < row.length; i += 2) { - row[i].value.unshift(emptyGroup); - } - if (numCols < row.length) { - numCols = row.length; - } - }); - for (var i = 0; i < numCols; ++i) { - var align = "r"; - var pregap = 0; - if (i % 2 === 1) { - align = "l"; - } else if (i > 0) { - pregap = 2; // one \qquad between columns - } - res.value.cols[i] = { - type: "align", - align: align, - pregap: pregap, - postgap: 0, - }; - } - return res; -}); - -},{"./ParseError":6,"./fontMetrics":17,"./parseData":21}],17:[function(require,module,exports){ -/* eslint no-unused-vars:0 */ - -var Style = require("./Style"); -var cjkRegex = require("./unicodeRegexes").cjkRegex; - -/** - * This file contains metrics regarding fonts and individual symbols. The sigma - * and xi variables, as well as the metricMap map contain data extracted from - * TeX, TeX font metrics, and the TTF files. These data are then exposed via the - * `metrics` variable and the getCharacterMetrics function. - */ - -// These font metrics are extracted from TeX by using -// \font\a=cmmi10 -// \showthe\fontdimenX\a -// where X is the corresponding variable number. These correspond to the font -// parameters of the symbol fonts. In TeX, there are actually three sets of -// dimensions, one for each of textstyle, scriptstyle, and scriptscriptstyle, -// but we only use the textstyle ones, and scale certain dimensions accordingly. -// See the TeXbook, page 441. -var sigma1 = 0.025; -var sigma2 = 0; -var sigma3 = 0; -var sigma4 = 0; -var sigma5 = 0.431; -var sigma6 = 1; -var sigma7 = 0; -var sigma8 = 0.677; -var sigma9 = 0.394; -var sigma10 = 0.444; -var sigma11 = 0.686; -var sigma12 = 0.345; -var sigma13 = 0.413; -var sigma14 = 0.363; -var sigma15 = 0.289; -var sigma16 = 0.150; -var sigma17 = 0.247; -var sigma18 = 0.386; -var sigma19 = 0.050; -var sigma20 = 2.390; -var sigma21 = 1.01; -var sigma21Script = 0.81; -var sigma21ScriptScript = 0.71; -var sigma22 = 0.250; - -// These font metrics are extracted from TeX by using -// \font\a=cmex10 -// \showthe\fontdimenX\a -// where X is the corresponding variable number. These correspond to the font -// parameters of the extension fonts (family 3). See the TeXbook, page 441. -var xi1 = 0; -var xi2 = 0; -var xi3 = 0; -var xi4 = 0; -var xi5 = 0.431; -var xi6 = 1; -var xi7 = 0; -var xi8 = 0.04; -var xi9 = 0.111; -var xi10 = 0.166; -var xi11 = 0.2; -var xi12 = 0.6; -var xi13 = 0.1; - -// This value determines how large a pt is, for metrics which are defined in -// terms of pts. -// This value is also used in katex.less; if you change it make sure the values -// match. -var ptPerEm = 10.0; - -// The space between adjacent `|` columns in an array definition. From -// `\showthe\doublerulesep` in LaTeX. -var doubleRuleSep = 2.0 / ptPerEm; - -/** - * This is just a mapping from common names to real metrics - */ -var metrics = { - xHeight: sigma5, - quad: sigma6, - num1: sigma8, - num2: sigma9, - num3: sigma10, - denom1: sigma11, - denom2: sigma12, - sup1: sigma13, - sup2: sigma14, - sup3: sigma15, - sub1: sigma16, - sub2: sigma17, - supDrop: sigma18, - subDrop: sigma19, - axisHeight: sigma22, - defaultRuleThickness: xi8, - bigOpSpacing1: xi9, - bigOpSpacing2: xi10, - bigOpSpacing3: xi11, - bigOpSpacing4: xi12, - bigOpSpacing5: xi13, - ptPerEm: ptPerEm, - emPerEx: sigma5 / sigma6, - doubleRuleSep: doubleRuleSep, - - // TODO(alpert): Missing parallel structure here. We should probably add - // style-specific metrics for all of these. - delim1: sigma20, - getDelim2: function(style) { - if (style.size === Style.TEXT.size) { - return sigma21; - } else if (style.size === Style.SCRIPT.size) { - return sigma21Script; - } else if (style.size === Style.SCRIPTSCRIPT.size) { - return sigma21ScriptScript; - } - throw new Error("Unexpected style size: " + style.size); - }, -}; - -// This map contains a mapping from font name and character code to character -// metrics, including height, depth, italic correction, and skew (kern from the -// character to the corresponding \skewchar) -// This map is generated via `make metrics`. It should not be changed manually. -var metricMap = require("./fontMetricsData"); - -// These are very rough approximations. We default to Times New Roman which -// should have Latin-1 and Cyrillic characters, but may not depending on the -// operating system. The metrics do not account for extra height from the -// accents. In the case of Cyrillic characters which have both ascenders and -// descenders we prefer approximations with ascenders, primarily to prevent -// the fraction bar or root line from intersecting the glyph. -// TODO(kevinb) allow union of multiple glyph metrics for better accuracy. -var extraCharacterMap = { - // Latin-1 - 'À': 'A', - 'Ã': 'A', - 'Â': 'A', - 'Ã': 'A', - 'Ä': 'A', - 'Ã…': 'A', - 'Æ': 'A', - 'Ç': 'C', - 'È': 'E', - 'É': 'E', - 'Ê': 'E', - 'Ë': 'E', - 'ÃŒ': 'I', - 'Ã': 'I', - 'ÃŽ': 'I', - 'Ã': 'I', - 'Ã': 'D', - 'Ñ': 'N', - 'Ã’': 'O', - 'Ó': 'O', - 'Ô': 'O', - 'Õ': 'O', - 'Ö': 'O', - 'Ø': 'O', - 'Ù': 'U', - 'Ú': 'U', - 'Û': 'U', - 'Ãœ': 'U', - 'Ã': 'Y', - 'Þ': 'o', - 'ß': 'B', - 'à ': 'a', - 'á': 'a', - 'â': 'a', - 'ã': 'a', - 'ä': 'a', - 'Ã¥': 'a', - 'æ': 'a', - 'ç': 'c', - 'è': 'e', - 'é': 'e', - 'ê': 'e', - 'ë': 'e', - 'ì': 'i', - 'Ã': 'i', - 'î': 'i', - 'ï': 'i', - 'ð': 'd', - 'ñ': 'n', - 'ò': 'o', - 'ó': 'o', - 'ô': 'o', - 'õ': 'o', - 'ö': 'o', - 'ø': 'o', - 'ù': 'u', - 'ú': 'u', - 'û': 'u', - 'ü': 'u', - 'ý': 'y', - 'þ': 'o', - 'ÿ': 'y', - - // Cyrillic - 'Ð': 'A', - 'Б': 'B', - 'Ð’': 'B', - 'Г': 'F', - 'Д': 'A', - 'Е': 'E', - 'Ж': 'K', - 'З': '3', - 'И': 'N', - 'Й': 'N', - 'К': 'K', - 'Л': 'N', - 'Ðœ': 'M', - 'Ð': 'H', - 'О': 'O', - 'П': 'N', - 'Ð ': 'P', - 'С': 'C', - 'Т': 'T', - 'У': 'y', - 'Ф': 'O', - 'Ð¥': 'X', - 'Ц': 'U', - 'Ч': 'h', - 'Ш': 'W', - 'Щ': 'W', - 'Ъ': 'B', - 'Ы': 'X', - 'Ь': 'B', - 'Ð': '3', - 'Ю': 'X', - 'Я': 'R', - 'а': 'a', - 'б': 'b', - 'в': 'a', - 'г': 'r', - 'д': 'y', - 'е': 'e', - 'ж': 'm', - 'з': 'e', - 'и': 'n', - 'й': 'n', - 'к': 'n', - 'л': 'n', - 'м': 'm', - 'н': 'n', - 'о': 'o', - 'п': 'n', - 'Ñ€': 'p', - 'Ñ': 'c', - 'Ñ‚': 'o', - 'у': 'y', - 'Ñ„': 'b', - 'Ñ…': 'x', - 'ц': 'n', - 'ч': 'n', - 'ш': 'w', - 'щ': 'w', - 'ÑŠ': 'a', - 'Ñ‹': 'm', - 'ÑŒ': 'a', - 'Ñ': 'e', - 'ÑŽ': 'm', - 'Ñ': 'r', -}; - -/** - * This function is a convenience function for looking up information in the - * metricMap table. It takes a character as a string, and a style. - * - * Note: the `width` property may be undefined if fontMetricsData.js wasn't - * built using `Make extended_metrics`. - */ -var getCharacterMetrics = function(character, style) { - var ch = character.charCodeAt(0); - if (character[0] in extraCharacterMap) { - ch = extraCharacterMap[character[0]].charCodeAt(0); - } else if (cjkRegex.test(character[0])) { - ch = 'M'.charCodeAt(0); - } - var metrics = metricMap[style][ch]; - if (metrics) { - return { - depth: metrics[0], - height: metrics[1], - italic: metrics[2], - skew: metrics[3], - width: metrics[4], - }; - } -}; - -module.exports = { - metrics: metrics, - getCharacterMetrics: getCharacterMetrics, -}; - -},{"./Style":9,"./fontMetricsData":18,"./unicodeRegexes":24}],18:[function(require,module,exports){ -module.exports = { - "AMS-Regular": { - "65": [0, 0.68889, 0, 0], - "66": [0, 0.68889, 0, 0], - "67": [0, 0.68889, 0, 0], - "68": [0, 0.68889, 0, 0], - "69": [0, 0.68889, 0, 0], - "70": [0, 0.68889, 0, 0], - "71": [0, 0.68889, 0, 0], - "72": [0, 0.68889, 0, 0], - "73": [0, 0.68889, 0, 0], - "74": [0.16667, 0.68889, 0, 0], - "75": [0, 0.68889, 0, 0], - "76": [0, 0.68889, 0, 0], - "77": [0, 0.68889, 0, 0], - "78": [0, 0.68889, 0, 0], - "79": [0.16667, 0.68889, 0, 0], - "80": [0, 0.68889, 0, 0], - "81": [0.16667, 0.68889, 0, 0], - "82": [0, 0.68889, 0, 0], - "83": [0, 0.68889, 0, 0], - "84": [0, 0.68889, 0, 0], - "85": [0, 0.68889, 0, 0], - "86": [0, 0.68889, 0, 0], - "87": [0, 0.68889, 0, 0], - "88": [0, 0.68889, 0, 0], - "89": [0, 0.68889, 0, 0], - "90": [0, 0.68889, 0, 0], - "107": [0, 0.68889, 0, 0], - "165": [0, 0.675, 0.025, 0], - "174": [0.15559, 0.69224, 0, 0], - "240": [0, 0.68889, 0, 0], - "295": [0, 0.68889, 0, 0], - "710": [0, 0.825, 0, 0], - "732": [0, 0.9, 0, 0], - "770": [0, 0.825, 0, 0], - "771": [0, 0.9, 0, 0], - "989": [0.08167, 0.58167, 0, 0], - "1008": [0, 0.43056, 0.04028, 0], - "8245": [0, 0.54986, 0, 0], - "8463": [0, 0.68889, 0, 0], - "8487": [0, 0.68889, 0, 0], - "8498": [0, 0.68889, 0, 0], - "8502": [0, 0.68889, 0, 0], - "8503": [0, 0.68889, 0, 0], - "8504": [0, 0.68889, 0, 0], - "8513": [0, 0.68889, 0, 0], - "8592": [-0.03598, 0.46402, 0, 0], - "8594": [-0.03598, 0.46402, 0, 0], - "8602": [-0.13313, 0.36687, 0, 0], - "8603": [-0.13313, 0.36687, 0, 0], - "8606": [0.01354, 0.52239, 0, 0], - "8608": [0.01354, 0.52239, 0, 0], - "8610": [0.01354, 0.52239, 0, 0], - "8611": [0.01354, 0.52239, 0, 0], - "8619": [0, 0.54986, 0, 0], - "8620": [0, 0.54986, 0, 0], - "8621": [-0.13313, 0.37788, 0, 0], - "8622": [-0.13313, 0.36687, 0, 0], - "8624": [0, 0.69224, 0, 0], - "8625": [0, 0.69224, 0, 0], - "8630": [0, 0.43056, 0, 0], - "8631": [0, 0.43056, 0, 0], - "8634": [0.08198, 0.58198, 0, 0], - "8635": [0.08198, 0.58198, 0, 0], - "8638": [0.19444, 0.69224, 0, 0], - "8639": [0.19444, 0.69224, 0, 0], - "8642": [0.19444, 0.69224, 0, 0], - "8643": [0.19444, 0.69224, 0, 0], - "8644": [0.1808, 0.675, 0, 0], - "8646": [0.1808, 0.675, 0, 0], - "8647": [0.1808, 0.675, 0, 0], - "8648": [0.19444, 0.69224, 0, 0], - "8649": [0.1808, 0.675, 0, 0], - "8650": [0.19444, 0.69224, 0, 0], - "8651": [0.01354, 0.52239, 0, 0], - "8652": [0.01354, 0.52239, 0, 0], - "8653": [-0.13313, 0.36687, 0, 0], - "8654": [-0.13313, 0.36687, 0, 0], - "8655": [-0.13313, 0.36687, 0, 0], - "8666": [0.13667, 0.63667, 0, 0], - "8667": [0.13667, 0.63667, 0, 0], - "8669": [-0.13313, 0.37788, 0, 0], - "8672": [-0.064, 0.437, 0, 0], - "8674": [-0.064, 0.437, 0, 0], - "8705": [0, 0.825, 0, 0], - "8708": [0, 0.68889, 0, 0], - "8709": [0.08167, 0.58167, 0, 0], - "8717": [0, 0.43056, 0, 0], - "8722": [-0.03598, 0.46402, 0, 0], - "8724": [0.08198, 0.69224, 0, 0], - "8726": [0.08167, 0.58167, 0, 0], - "8733": [0, 0.69224, 0, 0], - "8736": [0, 0.69224, 0, 0], - "8737": [0, 0.69224, 0, 0], - "8738": [0.03517, 0.52239, 0, 0], - "8739": [0.08167, 0.58167, 0, 0], - "8740": [0.25142, 0.74111, 0, 0], - "8741": [0.08167, 0.58167, 0, 0], - "8742": [0.25142, 0.74111, 0, 0], - "8756": [0, 0.69224, 0, 0], - "8757": [0, 0.69224, 0, 0], - "8764": [-0.13313, 0.36687, 0, 0], - "8765": [-0.13313, 0.37788, 0, 0], - "8769": [-0.13313, 0.36687, 0, 0], - "8770": [-0.03625, 0.46375, 0, 0], - "8774": [0.30274, 0.79383, 0, 0], - "8776": [-0.01688, 0.48312, 0, 0], - "8778": [0.08167, 0.58167, 0, 0], - "8782": [0.06062, 0.54986, 0, 0], - "8783": [0.06062, 0.54986, 0, 0], - "8785": [0.08198, 0.58198, 0, 0], - "8786": [0.08198, 0.58198, 0, 0], - "8787": [0.08198, 0.58198, 0, 0], - "8790": [0, 0.69224, 0, 0], - "8791": [0.22958, 0.72958, 0, 0], - "8796": [0.08198, 0.91667, 0, 0], - "8806": [0.25583, 0.75583, 0, 0], - "8807": [0.25583, 0.75583, 0, 0], - "8808": [0.25142, 0.75726, 0, 0], - "8809": [0.25142, 0.75726, 0, 0], - "8812": [0.25583, 0.75583, 0, 0], - "8814": [0.20576, 0.70576, 0, 0], - "8815": [0.20576, 0.70576, 0, 0], - "8816": [0.30274, 0.79383, 0, 0], - "8817": [0.30274, 0.79383, 0, 0], - "8818": [0.22958, 0.72958, 0, 0], - "8819": [0.22958, 0.72958, 0, 0], - "8822": [0.1808, 0.675, 0, 0], - "8823": [0.1808, 0.675, 0, 0], - "8828": [0.13667, 0.63667, 0, 0], - "8829": [0.13667, 0.63667, 0, 0], - "8830": [0.22958, 0.72958, 0, 0], - "8831": [0.22958, 0.72958, 0, 0], - "8832": [0.20576, 0.70576, 0, 0], - "8833": [0.20576, 0.70576, 0, 0], - "8840": [0.30274, 0.79383, 0, 0], - "8841": [0.30274, 0.79383, 0, 0], - "8842": [0.13597, 0.63597, 0, 0], - "8843": [0.13597, 0.63597, 0, 0], - "8847": [0.03517, 0.54986, 0, 0], - "8848": [0.03517, 0.54986, 0, 0], - "8858": [0.08198, 0.58198, 0, 0], - "8859": [0.08198, 0.58198, 0, 0], - "8861": [0.08198, 0.58198, 0, 0], - "8862": [0, 0.675, 0, 0], - "8863": [0, 0.675, 0, 0], - "8864": [0, 0.675, 0, 0], - "8865": [0, 0.675, 0, 0], - "8872": [0, 0.69224, 0, 0], - "8873": [0, 0.69224, 0, 0], - "8874": [0, 0.69224, 0, 0], - "8876": [0, 0.68889, 0, 0], - "8877": [0, 0.68889, 0, 0], - "8878": [0, 0.68889, 0, 0], - "8879": [0, 0.68889, 0, 0], - "8882": [0.03517, 0.54986, 0, 0], - "8883": [0.03517, 0.54986, 0, 0], - "8884": [0.13667, 0.63667, 0, 0], - "8885": [0.13667, 0.63667, 0, 0], - "8888": [0, 0.54986, 0, 0], - "8890": [0.19444, 0.43056, 0, 0], - "8891": [0.19444, 0.69224, 0, 0], - "8892": [0.19444, 0.69224, 0, 0], - "8901": [0, 0.54986, 0, 0], - "8903": [0.08167, 0.58167, 0, 0], - "8905": [0.08167, 0.58167, 0, 0], - "8906": [0.08167, 0.58167, 0, 0], - "8907": [0, 0.69224, 0, 0], - "8908": [0, 0.69224, 0, 0], - "8909": [-0.03598, 0.46402, 0, 0], - "8910": [0, 0.54986, 0, 0], - "8911": [0, 0.54986, 0, 0], - "8912": [0.03517, 0.54986, 0, 0], - "8913": [0.03517, 0.54986, 0, 0], - "8914": [0, 0.54986, 0, 0], - "8915": [0, 0.54986, 0, 0], - "8916": [0, 0.69224, 0, 0], - "8918": [0.0391, 0.5391, 0, 0], - "8919": [0.0391, 0.5391, 0, 0], - "8920": [0.03517, 0.54986, 0, 0], - "8921": [0.03517, 0.54986, 0, 0], - "8922": [0.38569, 0.88569, 0, 0], - "8923": [0.38569, 0.88569, 0, 0], - "8926": [0.13667, 0.63667, 0, 0], - "8927": [0.13667, 0.63667, 0, 0], - "8928": [0.30274, 0.79383, 0, 0], - "8929": [0.30274, 0.79383, 0, 0], - "8934": [0.23222, 0.74111, 0, 0], - "8935": [0.23222, 0.74111, 0, 0], - "8936": [0.23222, 0.74111, 0, 0], - "8937": [0.23222, 0.74111, 0, 0], - "8938": [0.20576, 0.70576, 0, 0], - "8939": [0.20576, 0.70576, 0, 0], - "8940": [0.30274, 0.79383, 0, 0], - "8941": [0.30274, 0.79383, 0, 0], - "8994": [0.19444, 0.69224, 0, 0], - "8995": [0.19444, 0.69224, 0, 0], - "9416": [0.15559, 0.69224, 0, 0], - "9484": [0, 0.69224, 0, 0], - "9488": [0, 0.69224, 0, 0], - "9492": [0, 0.37788, 0, 0], - "9496": [0, 0.37788, 0, 0], - "9585": [0.19444, 0.68889, 0, 0], - "9586": [0.19444, 0.74111, 0, 0], - "9632": [0, 0.675, 0, 0], - "9633": [0, 0.675, 0, 0], - "9650": [0, 0.54986, 0, 0], - "9651": [0, 0.54986, 0, 0], - "9654": [0.03517, 0.54986, 0, 0], - "9660": [0, 0.54986, 0, 0], - "9661": [0, 0.54986, 0, 0], - "9664": [0.03517, 0.54986, 0, 0], - "9674": [0.11111, 0.69224, 0, 0], - "9733": [0.19444, 0.69224, 0, 0], - "10003": [0, 0.69224, 0, 0], - "10016": [0, 0.69224, 0, 0], - "10731": [0.11111, 0.69224, 0, 0], - "10846": [0.19444, 0.75583, 0, 0], - "10877": [0.13667, 0.63667, 0, 0], - "10878": [0.13667, 0.63667, 0, 0], - "10885": [0.25583, 0.75583, 0, 0], - "10886": [0.25583, 0.75583, 0, 0], - "10887": [0.13597, 0.63597, 0, 0], - "10888": [0.13597, 0.63597, 0, 0], - "10889": [0.26167, 0.75726, 0, 0], - "10890": [0.26167, 0.75726, 0, 0], - "10891": [0.48256, 0.98256, 0, 0], - "10892": [0.48256, 0.98256, 0, 0], - "10901": [0.13667, 0.63667, 0, 0], - "10902": [0.13667, 0.63667, 0, 0], - "10933": [0.25142, 0.75726, 0, 0], - "10934": [0.25142, 0.75726, 0, 0], - "10935": [0.26167, 0.75726, 0, 0], - "10936": [0.26167, 0.75726, 0, 0], - "10937": [0.26167, 0.75726, 0, 0], - "10938": [0.26167, 0.75726, 0, 0], - "10949": [0.25583, 0.75583, 0, 0], - "10950": [0.25583, 0.75583, 0, 0], - "10955": [0.28481, 0.79383, 0, 0], - "10956": [0.28481, 0.79383, 0, 0], - "57350": [0.08167, 0.58167, 0, 0], - "57351": [0.08167, 0.58167, 0, 0], - "57352": [0.08167, 0.58167, 0, 0], - "57353": [0, 0.43056, 0.04028, 0], - "57356": [0.25142, 0.75726, 0, 0], - "57357": [0.25142, 0.75726, 0, 0], - "57358": [0.41951, 0.91951, 0, 0], - "57359": [0.30274, 0.79383, 0, 0], - "57360": [0.30274, 0.79383, 0, 0], - "57361": [0.41951, 0.91951, 0, 0], - "57366": [0.25142, 0.75726, 0, 0], - "57367": [0.25142, 0.75726, 0, 0], - "57368": [0.25142, 0.75726, 0, 0], - "57369": [0.25142, 0.75726, 0, 0], - "57370": [0.13597, 0.63597, 0, 0], - "57371": [0.13597, 0.63597, 0, 0], - }, - "Caligraphic-Regular": { - "48": [0, 0.43056, 0, 0], - "49": [0, 0.43056, 0, 0], - "50": [0, 0.43056, 0, 0], - "51": [0.19444, 0.43056, 0, 0], - "52": [0.19444, 0.43056, 0, 0], - "53": [0.19444, 0.43056, 0, 0], - "54": [0, 0.64444, 0, 0], - "55": [0.19444, 0.43056, 0, 0], - "56": [0, 0.64444, 0, 0], - "57": [0.19444, 0.43056, 0, 0], - "65": [0, 0.68333, 0, 0.19445], - "66": [0, 0.68333, 0.03041, 0.13889], - "67": [0, 0.68333, 0.05834, 0.13889], - "68": [0, 0.68333, 0.02778, 0.08334], - "69": [0, 0.68333, 0.08944, 0.11111], - "70": [0, 0.68333, 0.09931, 0.11111], - "71": [0.09722, 0.68333, 0.0593, 0.11111], - "72": [0, 0.68333, 0.00965, 0.11111], - "73": [0, 0.68333, 0.07382, 0], - "74": [0.09722, 0.68333, 0.18472, 0.16667], - "75": [0, 0.68333, 0.01445, 0.05556], - "76": [0, 0.68333, 0, 0.13889], - "77": [0, 0.68333, 0, 0.13889], - "78": [0, 0.68333, 0.14736, 0.08334], - "79": [0, 0.68333, 0.02778, 0.11111], - "80": [0, 0.68333, 0.08222, 0.08334], - "81": [0.09722, 0.68333, 0, 0.11111], - "82": [0, 0.68333, 0, 0.08334], - "83": [0, 0.68333, 0.075, 0.13889], - "84": [0, 0.68333, 0.25417, 0], - "85": [0, 0.68333, 0.09931, 0.08334], - "86": [0, 0.68333, 0.08222, 0], - "87": [0, 0.68333, 0.08222, 0.08334], - "88": [0, 0.68333, 0.14643, 0.13889], - "89": [0.09722, 0.68333, 0.08222, 0.08334], - "90": [0, 0.68333, 0.07944, 0.13889], - }, - "Fraktur-Regular": { - "33": [0, 0.69141, 0, 0], - "34": [0, 0.69141, 0, 0], - "38": [0, 0.69141, 0, 0], - "39": [0, 0.69141, 0, 0], - "40": [0.24982, 0.74947, 0, 0], - "41": [0.24982, 0.74947, 0, 0], - "42": [0, 0.62119, 0, 0], - "43": [0.08319, 0.58283, 0, 0], - "44": [0, 0.10803, 0, 0], - "45": [0.08319, 0.58283, 0, 0], - "46": [0, 0.10803, 0, 0], - "47": [0.24982, 0.74947, 0, 0], - "48": [0, 0.47534, 0, 0], - "49": [0, 0.47534, 0, 0], - "50": [0, 0.47534, 0, 0], - "51": [0.18906, 0.47534, 0, 0], - "52": [0.18906, 0.47534, 0, 0], - "53": [0.18906, 0.47534, 0, 0], - "54": [0, 0.69141, 0, 0], - "55": [0.18906, 0.47534, 0, 0], - "56": [0, 0.69141, 0, 0], - "57": [0.18906, 0.47534, 0, 0], - "58": [0, 0.47534, 0, 0], - "59": [0.12604, 0.47534, 0, 0], - "61": [-0.13099, 0.36866, 0, 0], - "63": [0, 0.69141, 0, 0], - "65": [0, 0.69141, 0, 0], - "66": [0, 0.69141, 0, 0], - "67": [0, 0.69141, 0, 0], - "68": [0, 0.69141, 0, 0], - "69": [0, 0.69141, 0, 0], - "70": [0.12604, 0.69141, 0, 0], - "71": [0, 0.69141, 0, 0], - "72": [0.06302, 0.69141, 0, 0], - "73": [0, 0.69141, 0, 0], - "74": [0.12604, 0.69141, 0, 0], - "75": [0, 0.69141, 0, 0], - "76": [0, 0.69141, 0, 0], - "77": [0, 0.69141, 0, 0], - "78": [0, 0.69141, 0, 0], - "79": [0, 0.69141, 0, 0], - "80": [0.18906, 0.69141, 0, 0], - "81": [0.03781, 0.69141, 0, 0], - "82": [0, 0.69141, 0, 0], - "83": [0, 0.69141, 0, 0], - "84": [0, 0.69141, 0, 0], - "85": [0, 0.69141, 0, 0], - "86": [0, 0.69141, 0, 0], - "87": [0, 0.69141, 0, 0], - "88": [0, 0.69141, 0, 0], - "89": [0.18906, 0.69141, 0, 0], - "90": [0.12604, 0.69141, 0, 0], - "91": [0.24982, 0.74947, 0, 0], - "93": [0.24982, 0.74947, 0, 0], - "94": [0, 0.69141, 0, 0], - "97": [0, 0.47534, 0, 0], - "98": [0, 0.69141, 0, 0], - "99": [0, 0.47534, 0, 0], - "100": [0, 0.62119, 0, 0], - "101": [0, 0.47534, 0, 0], - "102": [0.18906, 0.69141, 0, 0], - "103": [0.18906, 0.47534, 0, 0], - "104": [0.18906, 0.69141, 0, 0], - "105": [0, 0.69141, 0, 0], - "106": [0, 0.69141, 0, 0], - "107": [0, 0.69141, 0, 0], - "108": [0, 0.69141, 0, 0], - "109": [0, 0.47534, 0, 0], - "110": [0, 0.47534, 0, 0], - "111": [0, 0.47534, 0, 0], - "112": [0.18906, 0.52396, 0, 0], - "113": [0.18906, 0.47534, 0, 0], - "114": [0, 0.47534, 0, 0], - "115": [0, 0.47534, 0, 0], - "116": [0, 0.62119, 0, 0], - "117": [0, 0.47534, 0, 0], - "118": [0, 0.52396, 0, 0], - "119": [0, 0.52396, 0, 0], - "120": [0.18906, 0.47534, 0, 0], - "121": [0.18906, 0.47534, 0, 0], - "122": [0.18906, 0.47534, 0, 0], - "8216": [0, 0.69141, 0, 0], - "8217": [0, 0.69141, 0, 0], - "58112": [0, 0.62119, 0, 0], - "58113": [0, 0.62119, 0, 0], - "58114": [0.18906, 0.69141, 0, 0], - "58115": [0.18906, 0.69141, 0, 0], - "58116": [0.18906, 0.47534, 0, 0], - "58117": [0, 0.69141, 0, 0], - "58118": [0, 0.62119, 0, 0], - "58119": [0, 0.47534, 0, 0], - }, - "Main-Bold": { - "33": [0, 0.69444, 0, 0], - "34": [0, 0.69444, 0, 0], - "35": [0.19444, 0.69444, 0, 0], - "36": [0.05556, 0.75, 0, 0], - "37": [0.05556, 0.75, 0, 0], - "38": [0, 0.69444, 0, 0], - "39": [0, 0.69444, 0, 0], - "40": [0.25, 0.75, 0, 0], - "41": [0.25, 0.75, 0, 0], - "42": [0, 0.75, 0, 0], - "43": [0.13333, 0.63333, 0, 0], - "44": [0.19444, 0.15556, 0, 0], - "45": [0, 0.44444, 0, 0], - "46": [0, 0.15556, 0, 0], - "47": [0.25, 0.75, 0, 0], - "48": [0, 0.64444, 0, 0], - "49": [0, 0.64444, 0, 0], - "50": [0, 0.64444, 0, 0], - "51": [0, 0.64444, 0, 0], - "52": [0, 0.64444, 0, 0], - "53": [0, 0.64444, 0, 0], - "54": [0, 0.64444, 0, 0], - "55": [0, 0.64444, 0, 0], - "56": [0, 0.64444, 0, 0], - "57": [0, 0.64444, 0, 0], - "58": [0, 0.44444, 0, 0], - "59": [0.19444, 0.44444, 0, 0], - "60": [0.08556, 0.58556, 0, 0], - "61": [-0.10889, 0.39111, 0, 0], - "62": [0.08556, 0.58556, 0, 0], - "63": [0, 0.69444, 0, 0], - "64": [0, 0.69444, 0, 0], - "65": [0, 0.68611, 0, 0], - "66": [0, 0.68611, 0, 0], - "67": [0, 0.68611, 0, 0], - "68": [0, 0.68611, 0, 0], - "69": [0, 0.68611, 0, 0], - "70": [0, 0.68611, 0, 0], - "71": [0, 0.68611, 0, 0], - "72": [0, 0.68611, 0, 0], - "73": [0, 0.68611, 0, 0], - "74": [0, 0.68611, 0, 0], - "75": [0, 0.68611, 0, 0], - "76": [0, 0.68611, 0, 0], - "77": [0, 0.68611, 0, 0], - "78": [0, 0.68611, 0, 0], - "79": [0, 0.68611, 0, 0], - "80": [0, 0.68611, 0, 0], - "81": [0.19444, 0.68611, 0, 0], - "82": [0, 0.68611, 0, 0], - "83": [0, 0.68611, 0, 0], - "84": [0, 0.68611, 0, 0], - "85": [0, 0.68611, 0, 0], - "86": [0, 0.68611, 0.01597, 0], - "87": [0, 0.68611, 0.01597, 0], - "88": [0, 0.68611, 0, 0], - "89": [0, 0.68611, 0.02875, 0], - "90": [0, 0.68611, 0, 0], - "91": [0.25, 0.75, 0, 0], - "92": [0.25, 0.75, 0, 0], - "93": [0.25, 0.75, 0, 0], - "94": [0, 0.69444, 0, 0], - "95": [0.31, 0.13444, 0.03194, 0], - "96": [0, 0.69444, 0, 0], - "97": [0, 0.44444, 0, 0], - "98": [0, 0.69444, 0, 0], - "99": [0, 0.44444, 0, 0], - "100": [0, 0.69444, 0, 0], - "101": [0, 0.44444, 0, 0], - "102": [0, 0.69444, 0.10903, 0], - "103": [0.19444, 0.44444, 0.01597, 0], - "104": [0, 0.69444, 0, 0], - "105": [0, 0.69444, 0, 0], - "106": [0.19444, 0.69444, 0, 0], - "107": [0, 0.69444, 0, 0], - "108": [0, 0.69444, 0, 0], - "109": [0, 0.44444, 0, 0], - "110": [0, 0.44444, 0, 0], - "111": [0, 0.44444, 0, 0], - "112": [0.19444, 0.44444, 0, 0], - "113": [0.19444, 0.44444, 0, 0], - "114": [0, 0.44444, 0, 0], - "115": [0, 0.44444, 0, 0], - "116": [0, 0.63492, 0, 0], - "117": [0, 0.44444, 0, 0], - "118": [0, 0.44444, 0.01597, 0], - "119": [0, 0.44444, 0.01597, 0], - "120": [0, 0.44444, 0, 0], - "121": [0.19444, 0.44444, 0.01597, 0], - "122": [0, 0.44444, 0, 0], - "123": [0.25, 0.75, 0, 0], - "124": [0.25, 0.75, 0, 0], - "125": [0.25, 0.75, 0, 0], - "126": [0.35, 0.34444, 0, 0], - "168": [0, 0.69444, 0, 0], - "172": [0, 0.44444, 0, 0], - "175": [0, 0.59611, 0, 0], - "176": [0, 0.69444, 0, 0], - "177": [0.13333, 0.63333, 0, 0], - "180": [0, 0.69444, 0, 0], - "215": [0.13333, 0.63333, 0, 0], - "247": [0.13333, 0.63333, 0, 0], - "305": [0, 0.44444, 0, 0], - "567": [0.19444, 0.44444, 0, 0], - "710": [0, 0.69444, 0, 0], - "711": [0, 0.63194, 0, 0], - "713": [0, 0.59611, 0, 0], - "714": [0, 0.69444, 0, 0], - "715": [0, 0.69444, 0, 0], - "728": [0, 0.69444, 0, 0], - "729": [0, 0.69444, 0, 0], - "730": [0, 0.69444, 0, 0], - "732": [0, 0.69444, 0, 0], - "768": [0, 0.69444, 0, 0], - "769": [0, 0.69444, 0, 0], - "770": [0, 0.69444, 0, 0], - "771": [0, 0.69444, 0, 0], - "772": [0, 0.59611, 0, 0], - "774": [0, 0.69444, 0, 0], - "775": [0, 0.69444, 0, 0], - "776": [0, 0.69444, 0, 0], - "778": [0, 0.69444, 0, 0], - "779": [0, 0.69444, 0, 0], - "780": [0, 0.63194, 0, 0], - "824": [0.19444, 0.69444, 0, 0], - "915": [0, 0.68611, 0, 0], - "916": [0, 0.68611, 0, 0], - "920": [0, 0.68611, 0, 0], - "923": [0, 0.68611, 0, 0], - "926": [0, 0.68611, 0, 0], - "928": [0, 0.68611, 0, 0], - "931": [0, 0.68611, 0, 0], - "933": [0, 0.68611, 0, 0], - "934": [0, 0.68611, 0, 0], - "936": [0, 0.68611, 0, 0], - "937": [0, 0.68611, 0, 0], - "8211": [0, 0.44444, 0.03194, 0], - "8212": [0, 0.44444, 0.03194, 0], - "8216": [0, 0.69444, 0, 0], - "8217": [0, 0.69444, 0, 0], - "8220": [0, 0.69444, 0, 0], - "8221": [0, 0.69444, 0, 0], - "8224": [0.19444, 0.69444, 0, 0], - "8225": [0.19444, 0.69444, 0, 0], - "8242": [0, 0.55556, 0, 0], - "8407": [0, 0.72444, 0.15486, 0], - "8463": [0, 0.69444, 0, 0], - "8465": [0, 0.69444, 0, 0], - "8467": [0, 0.69444, 0, 0], - "8472": [0.19444, 0.44444, 0, 0], - "8476": [0, 0.69444, 0, 0], - "8501": [0, 0.69444, 0, 0], - "8592": [-0.10889, 0.39111, 0, 0], - "8593": [0.19444, 0.69444, 0, 0], - "8594": [-0.10889, 0.39111, 0, 0], - "8595": [0.19444, 0.69444, 0, 0], - "8596": [-0.10889, 0.39111, 0, 0], - "8597": [0.25, 0.75, 0, 0], - "8598": [0.19444, 0.69444, 0, 0], - "8599": [0.19444, 0.69444, 0, 0], - "8600": [0.19444, 0.69444, 0, 0], - "8601": [0.19444, 0.69444, 0, 0], - "8636": [-0.10889, 0.39111, 0, 0], - "8637": [-0.10889, 0.39111, 0, 0], - "8640": [-0.10889, 0.39111, 0, 0], - "8641": [-0.10889, 0.39111, 0, 0], - "8656": [-0.10889, 0.39111, 0, 0], - "8657": [0.19444, 0.69444, 0, 0], - "8658": [-0.10889, 0.39111, 0, 0], - "8659": [0.19444, 0.69444, 0, 0], - "8660": [-0.10889, 0.39111, 0, 0], - "8661": [0.25, 0.75, 0, 0], - "8704": [0, 0.69444, 0, 0], - "8706": [0, 0.69444, 0.06389, 0], - "8707": [0, 0.69444, 0, 0], - "8709": [0.05556, 0.75, 0, 0], - "8711": [0, 0.68611, 0, 0], - "8712": [0.08556, 0.58556, 0, 0], - "8715": [0.08556, 0.58556, 0, 0], - "8722": [0.13333, 0.63333, 0, 0], - "8723": [0.13333, 0.63333, 0, 0], - "8725": [0.25, 0.75, 0, 0], - "8726": [0.25, 0.75, 0, 0], - "8727": [-0.02778, 0.47222, 0, 0], - "8728": [-0.02639, 0.47361, 0, 0], - "8729": [-0.02639, 0.47361, 0, 0], - "8730": [0.18, 0.82, 0, 0], - "8733": [0, 0.44444, 0, 0], - "8734": [0, 0.44444, 0, 0], - "8736": [0, 0.69224, 0, 0], - "8739": [0.25, 0.75, 0, 0], - "8741": [0.25, 0.75, 0, 0], - "8743": [0, 0.55556, 0, 0], - "8744": [0, 0.55556, 0, 0], - "8745": [0, 0.55556, 0, 0], - "8746": [0, 0.55556, 0, 0], - "8747": [0.19444, 0.69444, 0.12778, 0], - "8764": [-0.10889, 0.39111, 0, 0], - "8768": [0.19444, 0.69444, 0, 0], - "8771": [0.00222, 0.50222, 0, 0], - "8776": [0.02444, 0.52444, 0, 0], - "8781": [0.00222, 0.50222, 0, 0], - "8801": [0.00222, 0.50222, 0, 0], - "8804": [0.19667, 0.69667, 0, 0], - "8805": [0.19667, 0.69667, 0, 0], - "8810": [0.08556, 0.58556, 0, 0], - "8811": [0.08556, 0.58556, 0, 0], - "8826": [0.08556, 0.58556, 0, 0], - "8827": [0.08556, 0.58556, 0, 0], - "8834": [0.08556, 0.58556, 0, 0], - "8835": [0.08556, 0.58556, 0, 0], - "8838": [0.19667, 0.69667, 0, 0], - "8839": [0.19667, 0.69667, 0, 0], - "8846": [0, 0.55556, 0, 0], - "8849": [0.19667, 0.69667, 0, 0], - "8850": [0.19667, 0.69667, 0, 0], - "8851": [0, 0.55556, 0, 0], - "8852": [0, 0.55556, 0, 0], - "8853": [0.13333, 0.63333, 0, 0], - "8854": [0.13333, 0.63333, 0, 0], - "8855": [0.13333, 0.63333, 0, 0], - "8856": [0.13333, 0.63333, 0, 0], - "8857": [0.13333, 0.63333, 0, 0], - "8866": [0, 0.69444, 0, 0], - "8867": [0, 0.69444, 0, 0], - "8868": [0, 0.69444, 0, 0], - "8869": [0, 0.69444, 0, 0], - "8900": [-0.02639, 0.47361, 0, 0], - "8901": [-0.02639, 0.47361, 0, 0], - "8902": [-0.02778, 0.47222, 0, 0], - "8968": [0.25, 0.75, 0, 0], - "8969": [0.25, 0.75, 0, 0], - "8970": [0.25, 0.75, 0, 0], - "8971": [0.25, 0.75, 0, 0], - "8994": [-0.13889, 0.36111, 0, 0], - "8995": [-0.13889, 0.36111, 0, 0], - "9651": [0.19444, 0.69444, 0, 0], - "9657": [-0.02778, 0.47222, 0, 0], - "9661": [0.19444, 0.69444, 0, 0], - "9667": [-0.02778, 0.47222, 0, 0], - "9711": [0.19444, 0.69444, 0, 0], - "9824": [0.12963, 0.69444, 0, 0], - "9825": [0.12963, 0.69444, 0, 0], - "9826": [0.12963, 0.69444, 0, 0], - "9827": [0.12963, 0.69444, 0, 0], - "9837": [0, 0.75, 0, 0], - "9838": [0.19444, 0.69444, 0, 0], - "9839": [0.19444, 0.69444, 0, 0], - "10216": [0.25, 0.75, 0, 0], - "10217": [0.25, 0.75, 0, 0], - "10815": [0, 0.68611, 0, 0], - "10927": [0.19667, 0.69667, 0, 0], - "10928": [0.19667, 0.69667, 0, 0], - }, - "Main-Italic": { - "33": [0, 0.69444, 0.12417, 0], - "34": [0, 0.69444, 0.06961, 0], - "35": [0.19444, 0.69444, 0.06616, 0], - "37": [0.05556, 0.75, 0.13639, 0], - "38": [0, 0.69444, 0.09694, 0], - "39": [0, 0.69444, 0.12417, 0], - "40": [0.25, 0.75, 0.16194, 0], - "41": [0.25, 0.75, 0.03694, 0], - "42": [0, 0.75, 0.14917, 0], - "43": [0.05667, 0.56167, 0.03694, 0], - "44": [0.19444, 0.10556, 0, 0], - "45": [0, 0.43056, 0.02826, 0], - "46": [0, 0.10556, 0, 0], - "47": [0.25, 0.75, 0.16194, 0], - "48": [0, 0.64444, 0.13556, 0], - "49": [0, 0.64444, 0.13556, 0], - "50": [0, 0.64444, 0.13556, 0], - "51": [0, 0.64444, 0.13556, 0], - "52": [0.19444, 0.64444, 0.13556, 0], - "53": [0, 0.64444, 0.13556, 0], - "54": [0, 0.64444, 0.13556, 0], - "55": [0.19444, 0.64444, 0.13556, 0], - "56": [0, 0.64444, 0.13556, 0], - "57": [0, 0.64444, 0.13556, 0], - "58": [0, 0.43056, 0.0582, 0], - "59": [0.19444, 0.43056, 0.0582, 0], - "61": [-0.13313, 0.36687, 0.06616, 0], - "63": [0, 0.69444, 0.1225, 0], - "64": [0, 0.69444, 0.09597, 0], - "65": [0, 0.68333, 0, 0], - "66": [0, 0.68333, 0.10257, 0], - "67": [0, 0.68333, 0.14528, 0], - "68": [0, 0.68333, 0.09403, 0], - "69": [0, 0.68333, 0.12028, 0], - "70": [0, 0.68333, 0.13305, 0], - "71": [0, 0.68333, 0.08722, 0], - "72": [0, 0.68333, 0.16389, 0], - "73": [0, 0.68333, 0.15806, 0], - "74": [0, 0.68333, 0.14028, 0], - "75": [0, 0.68333, 0.14528, 0], - "76": [0, 0.68333, 0, 0], - "77": [0, 0.68333, 0.16389, 0], - "78": [0, 0.68333, 0.16389, 0], - "79": [0, 0.68333, 0.09403, 0], - "80": [0, 0.68333, 0.10257, 0], - "81": [0.19444, 0.68333, 0.09403, 0], - "82": [0, 0.68333, 0.03868, 0], - "83": [0, 0.68333, 0.11972, 0], - "84": [0, 0.68333, 0.13305, 0], - "85": [0, 0.68333, 0.16389, 0], - "86": [0, 0.68333, 0.18361, 0], - "87": [0, 0.68333, 0.18361, 0], - "88": [0, 0.68333, 0.15806, 0], - "89": [0, 0.68333, 0.19383, 0], - "90": [0, 0.68333, 0.14528, 0], - "91": [0.25, 0.75, 0.1875, 0], - "93": [0.25, 0.75, 0.10528, 0], - "94": [0, 0.69444, 0.06646, 0], - "95": [0.31, 0.12056, 0.09208, 0], - "97": [0, 0.43056, 0.07671, 0], - "98": [0, 0.69444, 0.06312, 0], - "99": [0, 0.43056, 0.05653, 0], - "100": [0, 0.69444, 0.10333, 0], - "101": [0, 0.43056, 0.07514, 0], - "102": [0.19444, 0.69444, 0.21194, 0], - "103": [0.19444, 0.43056, 0.08847, 0], - "104": [0, 0.69444, 0.07671, 0], - "105": [0, 0.65536, 0.1019, 0], - "106": [0.19444, 0.65536, 0.14467, 0], - "107": [0, 0.69444, 0.10764, 0], - "108": [0, 0.69444, 0.10333, 0], - "109": [0, 0.43056, 0.07671, 0], - "110": [0, 0.43056, 0.07671, 0], - "111": [0, 0.43056, 0.06312, 0], - "112": [0.19444, 0.43056, 0.06312, 0], - "113": [0.19444, 0.43056, 0.08847, 0], - "114": [0, 0.43056, 0.10764, 0], - "115": [0, 0.43056, 0.08208, 0], - "116": [0, 0.61508, 0.09486, 0], - "117": [0, 0.43056, 0.07671, 0], - "118": [0, 0.43056, 0.10764, 0], - "119": [0, 0.43056, 0.10764, 0], - "120": [0, 0.43056, 0.12042, 0], - "121": [0.19444, 0.43056, 0.08847, 0], - "122": [0, 0.43056, 0.12292, 0], - "126": [0.35, 0.31786, 0.11585, 0], - "163": [0, 0.69444, 0, 0], - "305": [0, 0.43056, 0, 0.02778], - "567": [0.19444, 0.43056, 0, 0.08334], - "768": [0, 0.69444, 0, 0], - "769": [0, 0.69444, 0.09694, 0], - "770": [0, 0.69444, 0.06646, 0], - "771": [0, 0.66786, 0.11585, 0], - "772": [0, 0.56167, 0.10333, 0], - "774": [0, 0.69444, 0.10806, 0], - "775": [0, 0.66786, 0.11752, 0], - "776": [0, 0.66786, 0.10474, 0], - "778": [0, 0.69444, 0, 0], - "779": [0, 0.69444, 0.1225, 0], - "780": [0, 0.62847, 0.08295, 0], - "915": [0, 0.68333, 0.13305, 0], - "916": [0, 0.68333, 0, 0], - "920": [0, 0.68333, 0.09403, 0], - "923": [0, 0.68333, 0, 0], - "926": [0, 0.68333, 0.15294, 0], - "928": [0, 0.68333, 0.16389, 0], - "931": [0, 0.68333, 0.12028, 0], - "933": [0, 0.68333, 0.11111, 0], - "934": [0, 0.68333, 0.05986, 0], - "936": [0, 0.68333, 0.11111, 0], - "937": [0, 0.68333, 0.10257, 0], - "8211": [0, 0.43056, 0.09208, 0], - "8212": [0, 0.43056, 0.09208, 0], - "8216": [0, 0.69444, 0.12417, 0], - "8217": [0, 0.69444, 0.12417, 0], - "8220": [0, 0.69444, 0.1685, 0], - "8221": [0, 0.69444, 0.06961, 0], - "8463": [0, 0.68889, 0, 0], - }, - "Main-Regular": { - "32": [0, 0, 0, 0], - "33": [0, 0.69444, 0, 0], - "34": [0, 0.69444, 0, 0], - "35": [0.19444, 0.69444, 0, 0], - "36": [0.05556, 0.75, 0, 0], - "37": [0.05556, 0.75, 0, 0], - "38": [0, 0.69444, 0, 0], - "39": [0, 0.69444, 0, 0], - "40": [0.25, 0.75, 0, 0], - "41": [0.25, 0.75, 0, 0], - "42": [0, 0.75, 0, 0], - "43": [0.08333, 0.58333, 0, 0], - "44": [0.19444, 0.10556, 0, 0], - "45": [0, 0.43056, 0, 0], - "46": [0, 0.10556, 0, 0], - "47": [0.25, 0.75, 0, 0], - "48": [0, 0.64444, 0, 0], - "49": [0, 0.64444, 0, 0], - "50": [0, 0.64444, 0, 0], - "51": [0, 0.64444, 0, 0], - "52": [0, 0.64444, 0, 0], - "53": [0, 0.64444, 0, 0], - "54": [0, 0.64444, 0, 0], - "55": [0, 0.64444, 0, 0], - "56": [0, 0.64444, 0, 0], - "57": [0, 0.64444, 0, 0], - "58": [0, 0.43056, 0, 0], - "59": [0.19444, 0.43056, 0, 0], - "60": [0.0391, 0.5391, 0, 0], - "61": [-0.13313, 0.36687, 0, 0], - "62": [0.0391, 0.5391, 0, 0], - "63": [0, 0.69444, 0, 0], - "64": [0, 0.69444, 0, 0], - "65": [0, 0.68333, 0, 0], - "66": [0, 0.68333, 0, 0], - "67": [0, 0.68333, 0, 0], - "68": [0, 0.68333, 0, 0], - "69": [0, 0.68333, 0, 0], - "70": [0, 0.68333, 0, 0], - "71": [0, 0.68333, 0, 0], - "72": [0, 0.68333, 0, 0], - "73": [0, 0.68333, 0, 0], - "74": [0, 0.68333, 0, 0], - "75": [0, 0.68333, 0, 0], - "76": [0, 0.68333, 0, 0], - "77": [0, 0.68333, 0, 0], - "78": [0, 0.68333, 0, 0], - "79": [0, 0.68333, 0, 0], - "80": [0, 0.68333, 0, 0], - "81": [0.19444, 0.68333, 0, 0], - "82": [0, 0.68333, 0, 0], - "83": [0, 0.68333, 0, 0], - "84": [0, 0.68333, 0, 0], - "85": [0, 0.68333, 0, 0], - "86": [0, 0.68333, 0.01389, 0], - "87": [0, 0.68333, 0.01389, 0], - "88": [0, 0.68333, 0, 0], - "89": [0, 0.68333, 0.025, 0], - "90": [0, 0.68333, 0, 0], - "91": [0.25, 0.75, 0, 0], - "92": [0.25, 0.75, 0, 0], - "93": [0.25, 0.75, 0, 0], - "94": [0, 0.69444, 0, 0], - "95": [0.31, 0.12056, 0.02778, 0], - "96": [0, 0.69444, 0, 0], - "97": [0, 0.43056, 0, 0], - "98": [0, 0.69444, 0, 0], - "99": [0, 0.43056, 0, 0], - "100": [0, 0.69444, 0, 0], - "101": [0, 0.43056, 0, 0], - "102": [0, 0.69444, 0.07778, 0], - "103": [0.19444, 0.43056, 0.01389, 0], - "104": [0, 0.69444, 0, 0], - "105": [0, 0.66786, 0, 0], - "106": [0.19444, 0.66786, 0, 0], - "107": [0, 0.69444, 0, 0], - "108": [0, 0.69444, 0, 0], - "109": [0, 0.43056, 0, 0], - "110": [0, 0.43056, 0, 0], - "111": [0, 0.43056, 0, 0], - "112": [0.19444, 0.43056, 0, 0], - "113": [0.19444, 0.43056, 0, 0], - "114": [0, 0.43056, 0, 0], - "115": [0, 0.43056, 0, 0], - "116": [0, 0.61508, 0, 0], - "117": [0, 0.43056, 0, 0], - "118": [0, 0.43056, 0.01389, 0], - "119": [0, 0.43056, 0.01389, 0], - "120": [0, 0.43056, 0, 0], - "121": [0.19444, 0.43056, 0.01389, 0], - "122": [0, 0.43056, 0, 0], - "123": [0.25, 0.75, 0, 0], - "124": [0.25, 0.75, 0, 0], - "125": [0.25, 0.75, 0, 0], - "126": [0.35, 0.31786, 0, 0], - "160": [0, 0, 0, 0], - "168": [0, 0.66786, 0, 0], - "172": [0, 0.43056, 0, 0], - "175": [0, 0.56778, 0, 0], - "176": [0, 0.69444, 0, 0], - "177": [0.08333, 0.58333, 0, 0], - "180": [0, 0.69444, 0, 0], - "215": [0.08333, 0.58333, 0, 0], - "247": [0.08333, 0.58333, 0, 0], - "305": [0, 0.43056, 0, 0], - "567": [0.19444, 0.43056, 0, 0], - "710": [0, 0.69444, 0, 0], - "711": [0, 0.62847, 0, 0], - "713": [0, 0.56778, 0, 0], - "714": [0, 0.69444, 0, 0], - "715": [0, 0.69444, 0, 0], - "728": [0, 0.69444, 0, 0], - "729": [0, 0.66786, 0, 0], - "730": [0, 0.69444, 0, 0], - "732": [0, 0.66786, 0, 0], - "768": [0, 0.69444, 0, 0], - "769": [0, 0.69444, 0, 0], - "770": [0, 0.69444, 0, 0], - "771": [0, 0.66786, 0, 0], - "772": [0, 0.56778, 0, 0], - "774": [0, 0.69444, 0, 0], - "775": [0, 0.66786, 0, 0], - "776": [0, 0.66786, 0, 0], - "778": [0, 0.69444, 0, 0], - "779": [0, 0.69444, 0, 0], - "780": [0, 0.62847, 0, 0], - "824": [0.19444, 0.69444, 0, 0], - "915": [0, 0.68333, 0, 0], - "916": [0, 0.68333, 0, 0], - "920": [0, 0.68333, 0, 0], - "923": [0, 0.68333, 0, 0], - "926": [0, 0.68333, 0, 0], - "928": [0, 0.68333, 0, 0], - "931": [0, 0.68333, 0, 0], - "933": [0, 0.68333, 0, 0], - "934": [0, 0.68333, 0, 0], - "936": [0, 0.68333, 0, 0], - "937": [0, 0.68333, 0, 0], - "8211": [0, 0.43056, 0.02778, 0], - "8212": [0, 0.43056, 0.02778, 0], - "8216": [0, 0.69444, 0, 0], - "8217": [0, 0.69444, 0, 0], - "8220": [0, 0.69444, 0, 0], - "8221": [0, 0.69444, 0, 0], - "8224": [0.19444, 0.69444, 0, 0], - "8225": [0.19444, 0.69444, 0, 0], - "8230": [0, 0.12, 0, 0], - "8242": [0, 0.55556, 0, 0], - "8407": [0, 0.71444, 0.15382, 0], - "8463": [0, 0.68889, 0, 0], - "8465": [0, 0.69444, 0, 0], - "8467": [0, 0.69444, 0, 0.11111], - "8472": [0.19444, 0.43056, 0, 0.11111], - "8476": [0, 0.69444, 0, 0], - "8501": [0, 0.69444, 0, 0], - "8592": [-0.13313, 0.36687, 0, 0], - "8593": [0.19444, 0.69444, 0, 0], - "8594": [-0.13313, 0.36687, 0, 0], - "8595": [0.19444, 0.69444, 0, 0], - "8596": [-0.13313, 0.36687, 0, 0], - "8597": [0.25, 0.75, 0, 0], - "8598": [0.19444, 0.69444, 0, 0], - "8599": [0.19444, 0.69444, 0, 0], - "8600": [0.19444, 0.69444, 0, 0], - "8601": [0.19444, 0.69444, 0, 0], - "8614": [0.011, 0.511, 0, 0], - "8617": [0.011, 0.511, 0, 0], - "8618": [0.011, 0.511, 0, 0], - "8636": [-0.13313, 0.36687, 0, 0], - "8637": [-0.13313, 0.36687, 0, 0], - "8640": [-0.13313, 0.36687, 0, 0], - "8641": [-0.13313, 0.36687, 0, 0], - "8652": [0.011, 0.671, 0, 0], - "8656": [-0.13313, 0.36687, 0, 0], - "8657": [0.19444, 0.69444, 0, 0], - "8658": [-0.13313, 0.36687, 0, 0], - "8659": [0.19444, 0.69444, 0, 0], - "8660": [-0.13313, 0.36687, 0, 0], - "8661": [0.25, 0.75, 0, 0], - "8704": [0, 0.69444, 0, 0], - "8706": [0, 0.69444, 0.05556, 0.08334], - "8707": [0, 0.69444, 0, 0], - "8709": [0.05556, 0.75, 0, 0], - "8711": [0, 0.68333, 0, 0], - "8712": [0.0391, 0.5391, 0, 0], - "8715": [0.0391, 0.5391, 0, 0], - "8722": [0.08333, 0.58333, 0, 0], - "8723": [0.08333, 0.58333, 0, 0], - "8725": [0.25, 0.75, 0, 0], - "8726": [0.25, 0.75, 0, 0], - "8727": [-0.03472, 0.46528, 0, 0], - "8728": [-0.05555, 0.44445, 0, 0], - "8729": [-0.05555, 0.44445, 0, 0], - "8730": [0.2, 0.8, 0, 0], - "8733": [0, 0.43056, 0, 0], - "8734": [0, 0.43056, 0, 0], - "8736": [0, 0.69224, 0, 0], - "8739": [0.25, 0.75, 0, 0], - "8741": [0.25, 0.75, 0, 0], - "8743": [0, 0.55556, 0, 0], - "8744": [0, 0.55556, 0, 0], - "8745": [0, 0.55556, 0, 0], - "8746": [0, 0.55556, 0, 0], - "8747": [0.19444, 0.69444, 0.11111, 0], - "8764": [-0.13313, 0.36687, 0, 0], - "8768": [0.19444, 0.69444, 0, 0], - "8771": [-0.03625, 0.46375, 0, 0], - "8773": [-0.022, 0.589, 0, 0], - "8776": [-0.01688, 0.48312, 0, 0], - "8781": [-0.03625, 0.46375, 0, 0], - "8784": [-0.133, 0.67, 0, 0], - "8800": [0.215, 0.716, 0, 0], - "8801": [-0.03625, 0.46375, 0, 0], - "8804": [0.13597, 0.63597, 0, 0], - "8805": [0.13597, 0.63597, 0, 0], - "8810": [0.0391, 0.5391, 0, 0], - "8811": [0.0391, 0.5391, 0, 0], - "8826": [0.0391, 0.5391, 0, 0], - "8827": [0.0391, 0.5391, 0, 0], - "8834": [0.0391, 0.5391, 0, 0], - "8835": [0.0391, 0.5391, 0, 0], - "8838": [0.13597, 0.63597, 0, 0], - "8839": [0.13597, 0.63597, 0, 0], - "8846": [0, 0.55556, 0, 0], - "8849": [0.13597, 0.63597, 0, 0], - "8850": [0.13597, 0.63597, 0, 0], - "8851": [0, 0.55556, 0, 0], - "8852": [0, 0.55556, 0, 0], - "8853": [0.08333, 0.58333, 0, 0], - "8854": [0.08333, 0.58333, 0, 0], - "8855": [0.08333, 0.58333, 0, 0], - "8856": [0.08333, 0.58333, 0, 0], - "8857": [0.08333, 0.58333, 0, 0], - "8866": [0, 0.69444, 0, 0], - "8867": [0, 0.69444, 0, 0], - "8868": [0, 0.69444, 0, 0], - "8869": [0, 0.69444, 0, 0], - "8872": [0.249, 0.75, 0, 0], - "8900": [-0.05555, 0.44445, 0, 0], - "8901": [-0.05555, 0.44445, 0, 0], - "8902": [-0.03472, 0.46528, 0, 0], - "8904": [0.005, 0.505, 0, 0], - "8942": [0.03, 0.9, 0, 0], - "8943": [-0.19, 0.31, 0, 0], - "8945": [-0.1, 0.82, 0, 0], - "8968": [0.25, 0.75, 0, 0], - "8969": [0.25, 0.75, 0, 0], - "8970": [0.25, 0.75, 0, 0], - "8971": [0.25, 0.75, 0, 0], - "8994": [-0.14236, 0.35764, 0, 0], - "8995": [-0.14236, 0.35764, 0, 0], - "9136": [0.244, 0.744, 0, 0], - "9137": [0.244, 0.744, 0, 0], - "9651": [0.19444, 0.69444, 0, 0], - "9657": [-0.03472, 0.46528, 0, 0], - "9661": [0.19444, 0.69444, 0, 0], - "9667": [-0.03472, 0.46528, 0, 0], - "9711": [0.19444, 0.69444, 0, 0], - "9824": [0.12963, 0.69444, 0, 0], - "9825": [0.12963, 0.69444, 0, 0], - "9826": [0.12963, 0.69444, 0, 0], - "9827": [0.12963, 0.69444, 0, 0], - "9837": [0, 0.75, 0, 0], - "9838": [0.19444, 0.69444, 0, 0], - "9839": [0.19444, 0.69444, 0, 0], - "10216": [0.25, 0.75, 0, 0], - "10217": [0.25, 0.75, 0, 0], - "10222": [0.244, 0.744, 0, 0], - "10223": [0.244, 0.744, 0, 0], - "10229": [0.011, 0.511, 0, 0], - "10230": [0.011, 0.511, 0, 0], - "10231": [0.011, 0.511, 0, 0], - "10232": [0.024, 0.525, 0, 0], - "10233": [0.024, 0.525, 0, 0], - "10234": [0.024, 0.525, 0, 0], - "10236": [0.011, 0.511, 0, 0], - "10815": [0, 0.68333, 0, 0], - "10927": [0.13597, 0.63597, 0, 0], - "10928": [0.13597, 0.63597, 0, 0], - }, - "Math-BoldItalic": { - "47": [0.19444, 0.69444, 0, 0], - "65": [0, 0.68611, 0, 0], - "66": [0, 0.68611, 0.04835, 0], - "67": [0, 0.68611, 0.06979, 0], - "68": [0, 0.68611, 0.03194, 0], - "69": [0, 0.68611, 0.05451, 0], - "70": [0, 0.68611, 0.15972, 0], - "71": [0, 0.68611, 0, 0], - "72": [0, 0.68611, 0.08229, 0], - "73": [0, 0.68611, 0.07778, 0], - "74": [0, 0.68611, 0.10069, 0], - "75": [0, 0.68611, 0.06979, 0], - "76": [0, 0.68611, 0, 0], - "77": [0, 0.68611, 0.11424, 0], - "78": [0, 0.68611, 0.11424, 0], - "79": [0, 0.68611, 0.03194, 0], - "80": [0, 0.68611, 0.15972, 0], - "81": [0.19444, 0.68611, 0, 0], - "82": [0, 0.68611, 0.00421, 0], - "83": [0, 0.68611, 0.05382, 0], - "84": [0, 0.68611, 0.15972, 0], - "85": [0, 0.68611, 0.11424, 0], - "86": [0, 0.68611, 0.25555, 0], - "87": [0, 0.68611, 0.15972, 0], - "88": [0, 0.68611, 0.07778, 0], - "89": [0, 0.68611, 0.25555, 0], - "90": [0, 0.68611, 0.06979, 0], - "97": [0, 0.44444, 0, 0], - "98": [0, 0.69444, 0, 0], - "99": [0, 0.44444, 0, 0], - "100": [0, 0.69444, 0, 0], - "101": [0, 0.44444, 0, 0], - "102": [0.19444, 0.69444, 0.11042, 0], - "103": [0.19444, 0.44444, 0.03704, 0], - "104": [0, 0.69444, 0, 0], - "105": [0, 0.69326, 0, 0], - "106": [0.19444, 0.69326, 0.0622, 0], - "107": [0, 0.69444, 0.01852, 0], - "108": [0, 0.69444, 0.0088, 0], - "109": [0, 0.44444, 0, 0], - "110": [0, 0.44444, 0, 0], - "111": [0, 0.44444, 0, 0], - "112": [0.19444, 0.44444, 0, 0], - "113": [0.19444, 0.44444, 0.03704, 0], - "114": [0, 0.44444, 0.03194, 0], - "115": [0, 0.44444, 0, 0], - "116": [0, 0.63492, 0, 0], - "117": [0, 0.44444, 0, 0], - "118": [0, 0.44444, 0.03704, 0], - "119": [0, 0.44444, 0.02778, 0], - "120": [0, 0.44444, 0, 0], - "121": [0.19444, 0.44444, 0.03704, 0], - "122": [0, 0.44444, 0.04213, 0], - "915": [0, 0.68611, 0.15972, 0], - "916": [0, 0.68611, 0, 0], - "920": [0, 0.68611, 0.03194, 0], - "923": [0, 0.68611, 0, 0], - "926": [0, 0.68611, 0.07458, 0], - "928": [0, 0.68611, 0.08229, 0], - "931": [0, 0.68611, 0.05451, 0], - "933": [0, 0.68611, 0.15972, 0], - "934": [0, 0.68611, 0, 0], - "936": [0, 0.68611, 0.11653, 0], - "937": [0, 0.68611, 0.04835, 0], - "945": [0, 0.44444, 0, 0], - "946": [0.19444, 0.69444, 0.03403, 0], - "947": [0.19444, 0.44444, 0.06389, 0], - "948": [0, 0.69444, 0.03819, 0], - "949": [0, 0.44444, 0, 0], - "950": [0.19444, 0.69444, 0.06215, 0], - "951": [0.19444, 0.44444, 0.03704, 0], - "952": [0, 0.69444, 0.03194, 0], - "953": [0, 0.44444, 0, 0], - "954": [0, 0.44444, 0, 0], - "955": [0, 0.69444, 0, 0], - "956": [0.19444, 0.44444, 0, 0], - "957": [0, 0.44444, 0.06898, 0], - "958": [0.19444, 0.69444, 0.03021, 0], - "959": [0, 0.44444, 0, 0], - "960": [0, 0.44444, 0.03704, 0], - "961": [0.19444, 0.44444, 0, 0], - "962": [0.09722, 0.44444, 0.07917, 0], - "963": [0, 0.44444, 0.03704, 0], - "964": [0, 0.44444, 0.13472, 0], - "965": [0, 0.44444, 0.03704, 0], - "966": [0.19444, 0.44444, 0, 0], - "967": [0.19444, 0.44444, 0, 0], - "968": [0.19444, 0.69444, 0.03704, 0], - "969": [0, 0.44444, 0.03704, 0], - "977": [0, 0.69444, 0, 0], - "981": [0.19444, 0.69444, 0, 0], - "982": [0, 0.44444, 0.03194, 0], - "1009": [0.19444, 0.44444, 0, 0], - "1013": [0, 0.44444, 0, 0], - }, - "Math-Italic": { - "47": [0.19444, 0.69444, 0, 0], - "65": [0, 0.68333, 0, 0.13889], - "66": [0, 0.68333, 0.05017, 0.08334], - "67": [0, 0.68333, 0.07153, 0.08334], - "68": [0, 0.68333, 0.02778, 0.05556], - "69": [0, 0.68333, 0.05764, 0.08334], - "70": [0, 0.68333, 0.13889, 0.08334], - "71": [0, 0.68333, 0, 0.08334], - "72": [0, 0.68333, 0.08125, 0.05556], - "73": [0, 0.68333, 0.07847, 0.11111], - "74": [0, 0.68333, 0.09618, 0.16667], - "75": [0, 0.68333, 0.07153, 0.05556], - "76": [0, 0.68333, 0, 0.02778], - "77": [0, 0.68333, 0.10903, 0.08334], - "78": [0, 0.68333, 0.10903, 0.08334], - "79": [0, 0.68333, 0.02778, 0.08334], - "80": [0, 0.68333, 0.13889, 0.08334], - "81": [0.19444, 0.68333, 0, 0.08334], - "82": [0, 0.68333, 0.00773, 0.08334], - "83": [0, 0.68333, 0.05764, 0.08334], - "84": [0, 0.68333, 0.13889, 0.08334], - "85": [0, 0.68333, 0.10903, 0.02778], - "86": [0, 0.68333, 0.22222, 0], - "87": [0, 0.68333, 0.13889, 0], - "88": [0, 0.68333, 0.07847, 0.08334], - "89": [0, 0.68333, 0.22222, 0], - "90": [0, 0.68333, 0.07153, 0.08334], - "97": [0, 0.43056, 0, 0], - "98": [0, 0.69444, 0, 0], - "99": [0, 0.43056, 0, 0.05556], - "100": [0, 0.69444, 0, 0.16667], - "101": [0, 0.43056, 0, 0.05556], - "102": [0.19444, 0.69444, 0.10764, 0.16667], - "103": [0.19444, 0.43056, 0.03588, 0.02778], - "104": [0, 0.69444, 0, 0], - "105": [0, 0.65952, 0, 0], - "106": [0.19444, 0.65952, 0.05724, 0], - "107": [0, 0.69444, 0.03148, 0], - "108": [0, 0.69444, 0.01968, 0.08334], - "109": [0, 0.43056, 0, 0], - "110": [0, 0.43056, 0, 0], - "111": [0, 0.43056, 0, 0.05556], - "112": [0.19444, 0.43056, 0, 0.08334], - "113": [0.19444, 0.43056, 0.03588, 0.08334], - "114": [0, 0.43056, 0.02778, 0.05556], - "115": [0, 0.43056, 0, 0.05556], - "116": [0, 0.61508, 0, 0.08334], - "117": [0, 0.43056, 0, 0.02778], - "118": [0, 0.43056, 0.03588, 0.02778], - "119": [0, 0.43056, 0.02691, 0.08334], - "120": [0, 0.43056, 0, 0.02778], - "121": [0.19444, 0.43056, 0.03588, 0.05556], - "122": [0, 0.43056, 0.04398, 0.05556], - "915": [0, 0.68333, 0.13889, 0.08334], - "916": [0, 0.68333, 0, 0.16667], - "920": [0, 0.68333, 0.02778, 0.08334], - "923": [0, 0.68333, 0, 0.16667], - "926": [0, 0.68333, 0.07569, 0.08334], - "928": [0, 0.68333, 0.08125, 0.05556], - "931": [0, 0.68333, 0.05764, 0.08334], - "933": [0, 0.68333, 0.13889, 0.05556], - "934": [0, 0.68333, 0, 0.08334], - "936": [0, 0.68333, 0.11, 0.05556], - "937": [0, 0.68333, 0.05017, 0.08334], - "945": [0, 0.43056, 0.0037, 0.02778], - "946": [0.19444, 0.69444, 0.05278, 0.08334], - "947": [0.19444, 0.43056, 0.05556, 0], - "948": [0, 0.69444, 0.03785, 0.05556], - "949": [0, 0.43056, 0, 0.08334], - "950": [0.19444, 0.69444, 0.07378, 0.08334], - "951": [0.19444, 0.43056, 0.03588, 0.05556], - "952": [0, 0.69444, 0.02778, 0.08334], - "953": [0, 0.43056, 0, 0.05556], - "954": [0, 0.43056, 0, 0], - "955": [0, 0.69444, 0, 0], - "956": [0.19444, 0.43056, 0, 0.02778], - "957": [0, 0.43056, 0.06366, 0.02778], - "958": [0.19444, 0.69444, 0.04601, 0.11111], - "959": [0, 0.43056, 0, 0.05556], - "960": [0, 0.43056, 0.03588, 0], - "961": [0.19444, 0.43056, 0, 0.08334], - "962": [0.09722, 0.43056, 0.07986, 0.08334], - "963": [0, 0.43056, 0.03588, 0], - "964": [0, 0.43056, 0.1132, 0.02778], - "965": [0, 0.43056, 0.03588, 0.02778], - "966": [0.19444, 0.43056, 0, 0.08334], - "967": [0.19444, 0.43056, 0, 0.05556], - "968": [0.19444, 0.69444, 0.03588, 0.11111], - "969": [0, 0.43056, 0.03588, 0], - "977": [0, 0.69444, 0, 0.08334], - "981": [0.19444, 0.69444, 0, 0.08334], - "982": [0, 0.43056, 0.02778, 0], - "1009": [0.19444, 0.43056, 0, 0.08334], - "1013": [0, 0.43056, 0, 0.05556], - }, - "Math-Regular": { - "65": [0, 0.68333, 0, 0.13889], - "66": [0, 0.68333, 0.05017, 0.08334], - "67": [0, 0.68333, 0.07153, 0.08334], - "68": [0, 0.68333, 0.02778, 0.05556], - "69": [0, 0.68333, 0.05764, 0.08334], - "70": [0, 0.68333, 0.13889, 0.08334], - "71": [0, 0.68333, 0, 0.08334], - "72": [0, 0.68333, 0.08125, 0.05556], - "73": [0, 0.68333, 0.07847, 0.11111], - "74": [0, 0.68333, 0.09618, 0.16667], - "75": [0, 0.68333, 0.07153, 0.05556], - "76": [0, 0.68333, 0, 0.02778], - "77": [0, 0.68333, 0.10903, 0.08334], - "78": [0, 0.68333, 0.10903, 0.08334], - "79": [0, 0.68333, 0.02778, 0.08334], - "80": [0, 0.68333, 0.13889, 0.08334], - "81": [0.19444, 0.68333, 0, 0.08334], - "82": [0, 0.68333, 0.00773, 0.08334], - "83": [0, 0.68333, 0.05764, 0.08334], - "84": [0, 0.68333, 0.13889, 0.08334], - "85": [0, 0.68333, 0.10903, 0.02778], - "86": [0, 0.68333, 0.22222, 0], - "87": [0, 0.68333, 0.13889, 0], - "88": [0, 0.68333, 0.07847, 0.08334], - "89": [0, 0.68333, 0.22222, 0], - "90": [0, 0.68333, 0.07153, 0.08334], - "97": [0, 0.43056, 0, 0], - "98": [0, 0.69444, 0, 0], - "99": [0, 0.43056, 0, 0.05556], - "100": [0, 0.69444, 0, 0.16667], - "101": [0, 0.43056, 0, 0.05556], - "102": [0.19444, 0.69444, 0.10764, 0.16667], - "103": [0.19444, 0.43056, 0.03588, 0.02778], - "104": [0, 0.69444, 0, 0], - "105": [0, 0.65952, 0, 0], - "106": [0.19444, 0.65952, 0.05724, 0], - "107": [0, 0.69444, 0.03148, 0], - "108": [0, 0.69444, 0.01968, 0.08334], - "109": [0, 0.43056, 0, 0], - "110": [0, 0.43056, 0, 0], - "111": [0, 0.43056, 0, 0.05556], - "112": [0.19444, 0.43056, 0, 0.08334], - "113": [0.19444, 0.43056, 0.03588, 0.08334], - "114": [0, 0.43056, 0.02778, 0.05556], - "115": [0, 0.43056, 0, 0.05556], - "116": [0, 0.61508, 0, 0.08334], - "117": [0, 0.43056, 0, 0.02778], - "118": [0, 0.43056, 0.03588, 0.02778], - "119": [0, 0.43056, 0.02691, 0.08334], - "120": [0, 0.43056, 0, 0.02778], - "121": [0.19444, 0.43056, 0.03588, 0.05556], - "122": [0, 0.43056, 0.04398, 0.05556], - "915": [0, 0.68333, 0.13889, 0.08334], - "916": [0, 0.68333, 0, 0.16667], - "920": [0, 0.68333, 0.02778, 0.08334], - "923": [0, 0.68333, 0, 0.16667], - "926": [0, 0.68333, 0.07569, 0.08334], - "928": [0, 0.68333, 0.08125, 0.05556], - "931": [0, 0.68333, 0.05764, 0.08334], - "933": [0, 0.68333, 0.13889, 0.05556], - "934": [0, 0.68333, 0, 0.08334], - "936": [0, 0.68333, 0.11, 0.05556], - "937": [0, 0.68333, 0.05017, 0.08334], - "945": [0, 0.43056, 0.0037, 0.02778], - "946": [0.19444, 0.69444, 0.05278, 0.08334], - "947": [0.19444, 0.43056, 0.05556, 0], - "948": [0, 0.69444, 0.03785, 0.05556], - "949": [0, 0.43056, 0, 0.08334], - "950": [0.19444, 0.69444, 0.07378, 0.08334], - "951": [0.19444, 0.43056, 0.03588, 0.05556], - "952": [0, 0.69444, 0.02778, 0.08334], - "953": [0, 0.43056, 0, 0.05556], - "954": [0, 0.43056, 0, 0], - "955": [0, 0.69444, 0, 0], - "956": [0.19444, 0.43056, 0, 0.02778], - "957": [0, 0.43056, 0.06366, 0.02778], - "958": [0.19444, 0.69444, 0.04601, 0.11111], - "959": [0, 0.43056, 0, 0.05556], - "960": [0, 0.43056, 0.03588, 0], - "961": [0.19444, 0.43056, 0, 0.08334], - "962": [0.09722, 0.43056, 0.07986, 0.08334], - "963": [0, 0.43056, 0.03588, 0], - "964": [0, 0.43056, 0.1132, 0.02778], - "965": [0, 0.43056, 0.03588, 0.02778], - "966": [0.19444, 0.43056, 0, 0.08334], - "967": [0.19444, 0.43056, 0, 0.05556], - "968": [0.19444, 0.69444, 0.03588, 0.11111], - "969": [0, 0.43056, 0.03588, 0], - "977": [0, 0.69444, 0, 0.08334], - "981": [0.19444, 0.69444, 0, 0.08334], - "982": [0, 0.43056, 0.02778, 0], - "1009": [0.19444, 0.43056, 0, 0.08334], - "1013": [0, 0.43056, 0, 0.05556], - }, - "SansSerif-Regular": { - "33": [0, 0.69444, 0, 0], - "34": [0, 0.69444, 0, 0], - "35": [0.19444, 0.69444, 0, 0], - "36": [0.05556, 0.75, 0, 0], - "37": [0.05556, 0.75, 0, 0], - "38": [0, 0.69444, 0, 0], - "39": [0, 0.69444, 0, 0], - "40": [0.25, 0.75, 0, 0], - "41": [0.25, 0.75, 0, 0], - "42": [0, 0.75, 0, 0], - "43": [0.08333, 0.58333, 0, 0], - "44": [0.125, 0.08333, 0, 0], - "45": [0, 0.44444, 0, 0], - "46": [0, 0.08333, 0, 0], - "47": [0.25, 0.75, 0, 0], - "48": [0, 0.65556, 0, 0], - "49": [0, 0.65556, 0, 0], - "50": [0, 0.65556, 0, 0], - "51": [0, 0.65556, 0, 0], - "52": [0, 0.65556, 0, 0], - "53": [0, 0.65556, 0, 0], - "54": [0, 0.65556, 0, 0], - "55": [0, 0.65556, 0, 0], - "56": [0, 0.65556, 0, 0], - "57": [0, 0.65556, 0, 0], - "58": [0, 0.44444, 0, 0], - "59": [0.125, 0.44444, 0, 0], - "61": [-0.13, 0.37, 0, 0], - "63": [0, 0.69444, 0, 0], - "64": [0, 0.69444, 0, 0], - "65": [0, 0.69444, 0, 0], - "66": [0, 0.69444, 0, 0], - "67": [0, 0.69444, 0, 0], - "68": [0, 0.69444, 0, 0], - "69": [0, 0.69444, 0, 0], - "70": [0, 0.69444, 0, 0], - "71": [0, 0.69444, 0, 0], - "72": [0, 0.69444, 0, 0], - "73": [0, 0.69444, 0, 0], - "74": [0, 0.69444, 0, 0], - "75": [0, 0.69444, 0, 0], - "76": [0, 0.69444, 0, 0], - "77": [0, 0.69444, 0, 0], - "78": [0, 0.69444, 0, 0], - "79": [0, 0.69444, 0, 0], - "80": [0, 0.69444, 0, 0], - "81": [0.125, 0.69444, 0, 0], - "82": [0, 0.69444, 0, 0], - "83": [0, 0.69444, 0, 0], - "84": [0, 0.69444, 0, 0], - "85": [0, 0.69444, 0, 0], - "86": [0, 0.69444, 0.01389, 0], - "87": [0, 0.69444, 0.01389, 0], - "88": [0, 0.69444, 0, 0], - "89": [0, 0.69444, 0.025, 0], - "90": [0, 0.69444, 0, 0], - "91": [0.25, 0.75, 0, 0], - "93": [0.25, 0.75, 0, 0], - "94": [0, 0.69444, 0, 0], - "95": [0.35, 0.09444, 0.02778, 0], - "97": [0, 0.44444, 0, 0], - "98": [0, 0.69444, 0, 0], - "99": [0, 0.44444, 0, 0], - "100": [0, 0.69444, 0, 0], - "101": [0, 0.44444, 0, 0], - "102": [0, 0.69444, 0.06944, 0], - "103": [0.19444, 0.44444, 0.01389, 0], - "104": [0, 0.69444, 0, 0], - "105": [0, 0.67937, 0, 0], - "106": [0.19444, 0.67937, 0, 0], - "107": [0, 0.69444, 0, 0], - "108": [0, 0.69444, 0, 0], - "109": [0, 0.44444, 0, 0], - "110": [0, 0.44444, 0, 0], - "111": [0, 0.44444, 0, 0], - "112": [0.19444, 0.44444, 0, 0], - "113": [0.19444, 0.44444, 0, 0], - "114": [0, 0.44444, 0.01389, 0], - "115": [0, 0.44444, 0, 0], - "116": [0, 0.57143, 0, 0], - "117": [0, 0.44444, 0, 0], - "118": [0, 0.44444, 0.01389, 0], - "119": [0, 0.44444, 0.01389, 0], - "120": [0, 0.44444, 0, 0], - "121": [0.19444, 0.44444, 0.01389, 0], - "122": [0, 0.44444, 0, 0], - "126": [0.35, 0.32659, 0, 0], - "305": [0, 0.44444, 0, 0], - "567": [0.19444, 0.44444, 0, 0], - "768": [0, 0.69444, 0, 0], - "769": [0, 0.69444, 0, 0], - "770": [0, 0.69444, 0, 0], - "771": [0, 0.67659, 0, 0], - "772": [0, 0.60889, 0, 0], - "774": [0, 0.69444, 0, 0], - "775": [0, 0.67937, 0, 0], - "776": [0, 0.67937, 0, 0], - "778": [0, 0.69444, 0, 0], - "779": [0, 0.69444, 0, 0], - "780": [0, 0.63194, 0, 0], - "915": [0, 0.69444, 0, 0], - "916": [0, 0.69444, 0, 0], - "920": [0, 0.69444, 0, 0], - "923": [0, 0.69444, 0, 0], - "926": [0, 0.69444, 0, 0], - "928": [0, 0.69444, 0, 0], - "931": [0, 0.69444, 0, 0], - "933": [0, 0.69444, 0, 0], - "934": [0, 0.69444, 0, 0], - "936": [0, 0.69444, 0, 0], - "937": [0, 0.69444, 0, 0], - "8211": [0, 0.44444, 0.02778, 0], - "8212": [0, 0.44444, 0.02778, 0], - "8216": [0, 0.69444, 0, 0], - "8217": [0, 0.69444, 0, 0], - "8220": [0, 0.69444, 0, 0], - "8221": [0, 0.69444, 0, 0], - }, - "Script-Regular": { - "65": [0, 0.7, 0.22925, 0], - "66": [0, 0.7, 0.04087, 0], - "67": [0, 0.7, 0.1689, 0], - "68": [0, 0.7, 0.09371, 0], - "69": [0, 0.7, 0.18583, 0], - "70": [0, 0.7, 0.13634, 0], - "71": [0, 0.7, 0.17322, 0], - "72": [0, 0.7, 0.29694, 0], - "73": [0, 0.7, 0.19189, 0], - "74": [0.27778, 0.7, 0.19189, 0], - "75": [0, 0.7, 0.31259, 0], - "76": [0, 0.7, 0.19189, 0], - "77": [0, 0.7, 0.15981, 0], - "78": [0, 0.7, 0.3525, 0], - "79": [0, 0.7, 0.08078, 0], - "80": [0, 0.7, 0.08078, 0], - "81": [0, 0.7, 0.03305, 0], - "82": [0, 0.7, 0.06259, 0], - "83": [0, 0.7, 0.19189, 0], - "84": [0, 0.7, 0.29087, 0], - "85": [0, 0.7, 0.25815, 0], - "86": [0, 0.7, 0.27523, 0], - "87": [0, 0.7, 0.27523, 0], - "88": [0, 0.7, 0.26006, 0], - "89": [0, 0.7, 0.2939, 0], - "90": [0, 0.7, 0.24037, 0], - }, - "Size1-Regular": { - "40": [0.35001, 0.85, 0, 0], - "41": [0.35001, 0.85, 0, 0], - "47": [0.35001, 0.85, 0, 0], - "91": [0.35001, 0.85, 0, 0], - "92": [0.35001, 0.85, 0, 0], - "93": [0.35001, 0.85, 0, 0], - "123": [0.35001, 0.85, 0, 0], - "125": [0.35001, 0.85, 0, 0], - "710": [0, 0.72222, 0, 0], - "732": [0, 0.72222, 0, 0], - "770": [0, 0.72222, 0, 0], - "771": [0, 0.72222, 0, 0], - "8214": [-0.00099, 0.601, 0, 0], - "8593": [1e-05, 0.6, 0, 0], - "8595": [1e-05, 0.6, 0, 0], - "8657": [1e-05, 0.6, 0, 0], - "8659": [1e-05, 0.6, 0, 0], - "8719": [0.25001, 0.75, 0, 0], - "8720": [0.25001, 0.75, 0, 0], - "8721": [0.25001, 0.75, 0, 0], - "8730": [0.35001, 0.85, 0, 0], - "8739": [-0.00599, 0.606, 0, 0], - "8741": [-0.00599, 0.606, 0, 0], - "8747": [0.30612, 0.805, 0.19445, 0], - "8748": [0.306, 0.805, 0.19445, 0], - "8749": [0.306, 0.805, 0.19445, 0], - "8750": [0.30612, 0.805, 0.19445, 0], - "8896": [0.25001, 0.75, 0, 0], - "8897": [0.25001, 0.75, 0, 0], - "8898": [0.25001, 0.75, 0, 0], - "8899": [0.25001, 0.75, 0, 0], - "8968": [0.35001, 0.85, 0, 0], - "8969": [0.35001, 0.85, 0, 0], - "8970": [0.35001, 0.85, 0, 0], - "8971": [0.35001, 0.85, 0, 0], - "9168": [-0.00099, 0.601, 0, 0], - "10216": [0.35001, 0.85, 0, 0], - "10217": [0.35001, 0.85, 0, 0], - "10752": [0.25001, 0.75, 0, 0], - "10753": [0.25001, 0.75, 0, 0], - "10754": [0.25001, 0.75, 0, 0], - "10756": [0.25001, 0.75, 0, 0], - "10758": [0.25001, 0.75, 0, 0], - }, - "Size2-Regular": { - "40": [0.65002, 1.15, 0, 0], - "41": [0.65002, 1.15, 0, 0], - "47": [0.65002, 1.15, 0, 0], - "91": [0.65002, 1.15, 0, 0], - "92": [0.65002, 1.15, 0, 0], - "93": [0.65002, 1.15, 0, 0], - "123": [0.65002, 1.15, 0, 0], - "125": [0.65002, 1.15, 0, 0], - "710": [0, 0.75, 0, 0], - "732": [0, 0.75, 0, 0], - "770": [0, 0.75, 0, 0], - "771": [0, 0.75, 0, 0], - "8719": [0.55001, 1.05, 0, 0], - "8720": [0.55001, 1.05, 0, 0], - "8721": [0.55001, 1.05, 0, 0], - "8730": [0.65002, 1.15, 0, 0], - "8747": [0.86225, 1.36, 0.44445, 0], - "8748": [0.862, 1.36, 0.44445, 0], - "8749": [0.862, 1.36, 0.44445, 0], - "8750": [0.86225, 1.36, 0.44445, 0], - "8896": [0.55001, 1.05, 0, 0], - "8897": [0.55001, 1.05, 0, 0], - "8898": [0.55001, 1.05, 0, 0], - "8899": [0.55001, 1.05, 0, 0], - "8968": [0.65002, 1.15, 0, 0], - "8969": [0.65002, 1.15, 0, 0], - "8970": [0.65002, 1.15, 0, 0], - "8971": [0.65002, 1.15, 0, 0], - "10216": [0.65002, 1.15, 0, 0], - "10217": [0.65002, 1.15, 0, 0], - "10752": [0.55001, 1.05, 0, 0], - "10753": [0.55001, 1.05, 0, 0], - "10754": [0.55001, 1.05, 0, 0], - "10756": [0.55001, 1.05, 0, 0], - "10758": [0.55001, 1.05, 0, 0], - }, - "Size3-Regular": { - "40": [0.95003, 1.45, 0, 0], - "41": [0.95003, 1.45, 0, 0], - "47": [0.95003, 1.45, 0, 0], - "91": [0.95003, 1.45, 0, 0], - "92": [0.95003, 1.45, 0, 0], - "93": [0.95003, 1.45, 0, 0], - "123": [0.95003, 1.45, 0, 0], - "125": [0.95003, 1.45, 0, 0], - "710": [0, 0.75, 0, 0], - "732": [0, 0.75, 0, 0], - "770": [0, 0.75, 0, 0], - "771": [0, 0.75, 0, 0], - "8730": [0.95003, 1.45, 0, 0], - "8968": [0.95003, 1.45, 0, 0], - "8969": [0.95003, 1.45, 0, 0], - "8970": [0.95003, 1.45, 0, 0], - "8971": [0.95003, 1.45, 0, 0], - "10216": [0.95003, 1.45, 0, 0], - "10217": [0.95003, 1.45, 0, 0], - }, - "Size4-Regular": { - "40": [1.25003, 1.75, 0, 0], - "41": [1.25003, 1.75, 0, 0], - "47": [1.25003, 1.75, 0, 0], - "91": [1.25003, 1.75, 0, 0], - "92": [1.25003, 1.75, 0, 0], - "93": [1.25003, 1.75, 0, 0], - "123": [1.25003, 1.75, 0, 0], - "125": [1.25003, 1.75, 0, 0], - "710": [0, 0.825, 0, 0], - "732": [0, 0.825, 0, 0], - "770": [0, 0.825, 0, 0], - "771": [0, 0.825, 0, 0], - "8730": [1.25003, 1.75, 0, 0], - "8968": [1.25003, 1.75, 0, 0], - "8969": [1.25003, 1.75, 0, 0], - "8970": [1.25003, 1.75, 0, 0], - "8971": [1.25003, 1.75, 0, 0], - "9115": [0.64502, 1.155, 0, 0], - "9116": [1e-05, 0.6, 0, 0], - "9117": [0.64502, 1.155, 0, 0], - "9118": [0.64502, 1.155, 0, 0], - "9119": [1e-05, 0.6, 0, 0], - "9120": [0.64502, 1.155, 0, 0], - "9121": [0.64502, 1.155, 0, 0], - "9122": [-0.00099, 0.601, 0, 0], - "9123": [0.64502, 1.155, 0, 0], - "9124": [0.64502, 1.155, 0, 0], - "9125": [-0.00099, 0.601, 0, 0], - "9126": [0.64502, 1.155, 0, 0], - "9127": [1e-05, 0.9, 0, 0], - "9128": [0.65002, 1.15, 0, 0], - "9129": [0.90001, 0, 0, 0], - "9130": [0, 0.3, 0, 0], - "9131": [1e-05, 0.9, 0, 0], - "9132": [0.65002, 1.15, 0, 0], - "9133": [0.90001, 0, 0, 0], - "9143": [0.88502, 0.915, 0, 0], - "10216": [1.25003, 1.75, 0, 0], - "10217": [1.25003, 1.75, 0, 0], - "57344": [-0.00499, 0.605, 0, 0], - "57345": [-0.00499, 0.605, 0, 0], - "57680": [0, 0.12, 0, 0], - "57681": [0, 0.12, 0, 0], - "57682": [0, 0.12, 0, 0], - "57683": [0, 0.12, 0, 0], - }, - "Typewriter-Regular": { - "33": [0, 0.61111, 0, 0], - "34": [0, 0.61111, 0, 0], - "35": [0, 0.61111, 0, 0], - "36": [0.08333, 0.69444, 0, 0], - "37": [0.08333, 0.69444, 0, 0], - "38": [0, 0.61111, 0, 0], - "39": [0, 0.61111, 0, 0], - "40": [0.08333, 0.69444, 0, 0], - "41": [0.08333, 0.69444, 0, 0], - "42": [0, 0.52083, 0, 0], - "43": [-0.08056, 0.53055, 0, 0], - "44": [0.13889, 0.125, 0, 0], - "45": [-0.08056, 0.53055, 0, 0], - "46": [0, 0.125, 0, 0], - "47": [0.08333, 0.69444, 0, 0], - "48": [0, 0.61111, 0, 0], - "49": [0, 0.61111, 0, 0], - "50": [0, 0.61111, 0, 0], - "51": [0, 0.61111, 0, 0], - "52": [0, 0.61111, 0, 0], - "53": [0, 0.61111, 0, 0], - "54": [0, 0.61111, 0, 0], - "55": [0, 0.61111, 0, 0], - "56": [0, 0.61111, 0, 0], - "57": [0, 0.61111, 0, 0], - "58": [0, 0.43056, 0, 0], - "59": [0.13889, 0.43056, 0, 0], - "60": [-0.05556, 0.55556, 0, 0], - "61": [-0.19549, 0.41562, 0, 0], - "62": [-0.05556, 0.55556, 0, 0], - "63": [0, 0.61111, 0, 0], - "64": [0, 0.61111, 0, 0], - "65": [0, 0.61111, 0, 0], - "66": [0, 0.61111, 0, 0], - "67": [0, 0.61111, 0, 0], - "68": [0, 0.61111, 0, 0], - "69": [0, 0.61111, 0, 0], - "70": [0, 0.61111, 0, 0], - "71": [0, 0.61111, 0, 0], - "72": [0, 0.61111, 0, 0], - "73": [0, 0.61111, 0, 0], - "74": [0, 0.61111, 0, 0], - "75": [0, 0.61111, 0, 0], - "76": [0, 0.61111, 0, 0], - "77": [0, 0.61111, 0, 0], - "78": [0, 0.61111, 0, 0], - "79": [0, 0.61111, 0, 0], - "80": [0, 0.61111, 0, 0], - "81": [0.13889, 0.61111, 0, 0], - "82": [0, 0.61111, 0, 0], - "83": [0, 0.61111, 0, 0], - "84": [0, 0.61111, 0, 0], - "85": [0, 0.61111, 0, 0], - "86": [0, 0.61111, 0, 0], - "87": [0, 0.61111, 0, 0], - "88": [0, 0.61111, 0, 0], - "89": [0, 0.61111, 0, 0], - "90": [0, 0.61111, 0, 0], - "91": [0.08333, 0.69444, 0, 0], - "92": [0.08333, 0.69444, 0, 0], - "93": [0.08333, 0.69444, 0, 0], - "94": [0, 0.61111, 0, 0], - "95": [0.09514, 0, 0, 0], - "96": [0, 0.61111, 0, 0], - "97": [0, 0.43056, 0, 0], - "98": [0, 0.61111, 0, 0], - "99": [0, 0.43056, 0, 0], - "100": [0, 0.61111, 0, 0], - "101": [0, 0.43056, 0, 0], - "102": [0, 0.61111, 0, 0], - "103": [0.22222, 0.43056, 0, 0], - "104": [0, 0.61111, 0, 0], - "105": [0, 0.61111, 0, 0], - "106": [0.22222, 0.61111, 0, 0], - "107": [0, 0.61111, 0, 0], - "108": [0, 0.61111, 0, 0], - "109": [0, 0.43056, 0, 0], - "110": [0, 0.43056, 0, 0], - "111": [0, 0.43056, 0, 0], - "112": [0.22222, 0.43056, 0, 0], - "113": [0.22222, 0.43056, 0, 0], - "114": [0, 0.43056, 0, 0], - "115": [0, 0.43056, 0, 0], - "116": [0, 0.55358, 0, 0], - "117": [0, 0.43056, 0, 0], - "118": [0, 0.43056, 0, 0], - "119": [0, 0.43056, 0, 0], - "120": [0, 0.43056, 0, 0], - "121": [0.22222, 0.43056, 0, 0], - "122": [0, 0.43056, 0, 0], - "123": [0.08333, 0.69444, 0, 0], - "124": [0.08333, 0.69444, 0, 0], - "125": [0.08333, 0.69444, 0, 0], - "126": [0, 0.61111, 0, 0], - "127": [0, 0.61111, 0, 0], - "305": [0, 0.43056, 0, 0], - "567": [0.22222, 0.43056, 0, 0], - "768": [0, 0.61111, 0, 0], - "769": [0, 0.61111, 0, 0], - "770": [0, 0.61111, 0, 0], - "771": [0, 0.61111, 0, 0], - "772": [0, 0.56555, 0, 0], - "774": [0, 0.61111, 0, 0], - "776": [0, 0.61111, 0, 0], - "778": [0, 0.61111, 0, 0], - "780": [0, 0.56597, 0, 0], - "915": [0, 0.61111, 0, 0], - "916": [0, 0.61111, 0, 0], - "920": [0, 0.61111, 0, 0], - "923": [0, 0.61111, 0, 0], - "926": [0, 0.61111, 0, 0], - "928": [0, 0.61111, 0, 0], - "931": [0, 0.61111, 0, 0], - "933": [0, 0.61111, 0, 0], - "934": [0, 0.61111, 0, 0], - "936": [0, 0.61111, 0, 0], - "937": [0, 0.61111, 0, 0], - "2018": [0, 0.61111, 0, 0], - "2019": [0, 0.61111, 0, 0], - "8242": [0, 0.61111, 0, 0], - }, -}; - -},{}],19:[function(require,module,exports){ -var utils = require("./utils"); -var ParseError = require("./ParseError"); - -/* This file contains a list of functions that we parse, identified by - * the calls to defineFunction. - * - * The first argument to defineFunction is a single name or a list of names. - * All functions named in such a list will share a single implementation. - * - * Each declared function can have associated properties, which - * include the following: - * - * - numArgs: The number of arguments the function takes. - * If this is the only property, it can be passed as a number - * instead of an element of a properties object. - * - argTypes: (optional) An array corresponding to each argument of the - * function, giving the type of argument that should be parsed. Its - * length should be equal to `numArgs + numOptionalArgs`. Valid - * types: - * - "size": A size-like thing, such as "1em" or "5ex" - * - "color": An html color, like "#abc" or "blue" - * - "original": The same type as the environment that the - * function being parsed is in (e.g. used for the - * bodies of functions like \color where the first - * argument is special and the second argument is - * parsed normally) - * Other possible types (probably shouldn't be used) - * - "text": Text-like (e.g. \text) - * - "math": Normal math - * If undefined, this will be treated as an appropriate length - * array of "original" strings - * - greediness: (optional) The greediness of the function to use ungrouped - * arguments. - * - * E.g. if you have an expression - * \sqrt \frac 1 2 - * since \frac has greediness=2 vs \sqrt's greediness=1, \frac - * will use the two arguments '1' and '2' as its two arguments, - * then that whole function will be used as the argument to - * \sqrt. On the other hand, the expressions - * \frac \frac 1 2 3 - * and - * \frac \sqrt 1 2 - * will fail because \frac and \frac have equal greediness - * and \sqrt has a lower greediness than \frac respectively. To - * make these parse, we would have to change them to: - * \frac {\frac 1 2} 3 - * and - * \frac {\sqrt 1} 2 - * - * The default value is `1` - * - allowedInText: (optional) Whether or not the function is allowed inside - * text mode (default false) - * - numOptionalArgs: (optional) The number of optional arguments the function - * should parse. If the optional arguments aren't found, - * `null` will be passed to the handler in their place. - * (default 0) - * - infix: (optional) Must be true if the function is an infix operator. - * - * The last argument is that implementation, the handler for the function(s). - * It is called to handle these functions and their arguments. - * It receives two arguments: - * - context contains information and references provided by the parser - * - args is an array of arguments obtained from TeX input - * The context contains the following properties: - * - funcName: the text (i.e. name) of the function, including \ - * - parser: the parser object - * - lexer: the lexer object - * - positions: the positions in the overall string of the function - * and the arguments. - * The latter three should only be used to produce error messages. - * - * The function should return an object with the following keys: - * - type: The type of element that this is. This is then used in - * buildHTML/buildMathML to determine which function - * should be called to build this node into a DOM node - * Any other data can be added to the object, which will be passed - * in to the function in buildHTML/buildMathML as `group.value`. - */ - -function defineFunction(names, props, handler) { - if (typeof names === "string") { - names = [names]; - } - if (typeof props === "number") { - props = { numArgs: props }; - } - // Set default values of functions - var data = { - numArgs: props.numArgs, - argTypes: props.argTypes, - greediness: (props.greediness === undefined) ? 1 : props.greediness, - allowedInText: !!props.allowedInText, - numOptionalArgs: props.numOptionalArgs || 0, - infix: !!props.infix, - handler: handler, - }; - for (var i = 0; i < names.length; ++i) { - module.exports[names[i]] = data; - } -} - -// A normal square root -defineFunction("\\sqrt", { - numArgs: 1, - numOptionalArgs: 1, -}, function(context, args) { - var index = args[0]; - var body = args[1]; - return { - type: "sqrt", - body: body, - index: index, - }; -}); - -// Some non-mathy text -defineFunction("\\text", { - numArgs: 1, - argTypes: ["text"], - greediness: 2, -}, function(context, args) { - var body = args[0]; - // Since the corresponding buildHTML/buildMathML function expects a - // list of elements, we normalize for different kinds of arguments - // TODO(emily): maybe this should be done somewhere else - var inner; - if (body.type === "ordgroup") { - inner = body.value; - } else { - inner = [body]; - } - - return { - type: "text", - body: inner, - }; -}); - -// A two-argument custom color -defineFunction("\\color", { - numArgs: 2, - allowedInText: true, - greediness: 3, - argTypes: ["color", "original"], -}, function(context, args) { - var color = args[0]; - var body = args[1]; - // Normalize the different kinds of bodies (see \text above) - var inner; - if (body.type === "ordgroup") { - inner = body.value; - } else { - inner = [body]; - } - - return { - type: "color", - color: color.value, - value: inner, - }; -}); - -// An overline -defineFunction("\\overline", { - numArgs: 1, -}, function(context, args) { - var body = args[0]; - return { - type: "overline", - body: body, - }; -}); - -// An underline -defineFunction("\\underline", { - numArgs: 1, -}, function(context, args) { - var body = args[0]; - return { - type: "underline", - body: body, - }; -}); - -// A box of the width and height -defineFunction("\\rule", { - numArgs: 2, - numOptionalArgs: 1, - argTypes: ["size", "size", "size"], -}, function(context, args) { - var shift = args[0]; - var width = args[1]; - var height = args[2]; - return { - type: "rule", - shift: shift && shift.value, - width: width.value, - height: height.value, - }; -}); - -defineFunction("\\kern", { - numArgs: 1, - argTypes: ["size"], -}, function(context, args) { - return { - type: "kern", - dimension: args[0].value, - }; -}); - -// A KaTeX logo -defineFunction("\\KaTeX", { - numArgs: 0, -}, function(context) { - return { - type: "katex", - }; -}); - -defineFunction("\\phantom", { - numArgs: 1, -}, function(context, args) { - var body = args[0]; - var inner; - if (body.type === "ordgroup") { - inner = body.value; - } else { - inner = [body]; - } - - return { - type: "phantom", - value: inner, - }; -}); - -// Extra data needed for the delimiter handler down below -var delimiterSizes = { - "\\bigl" : {type: "open", size: 1}, - "\\Bigl" : {type: "open", size: 2}, - "\\biggl": {type: "open", size: 3}, - "\\Biggl": {type: "open", size: 4}, - "\\bigr" : {type: "close", size: 1}, - "\\Bigr" : {type: "close", size: 2}, - "\\biggr": {type: "close", size: 3}, - "\\Biggr": {type: "close", size: 4}, - "\\bigm" : {type: "rel", size: 1}, - "\\Bigm" : {type: "rel", size: 2}, - "\\biggm": {type: "rel", size: 3}, - "\\Biggm": {type: "rel", size: 4}, - "\\big" : {type: "textord", size: 1}, - "\\Big" : {type: "textord", size: 2}, - "\\bigg" : {type: "textord", size: 3}, - "\\Bigg" : {type: "textord", size: 4}, -}; - -var delimiters = [ - "(", ")", "[", "\\lbrack", "]", "\\rbrack", - "\\{", "\\lbrace", "\\}", "\\rbrace", - "\\lfloor", "\\rfloor", "\\lceil", "\\rceil", - "<", ">", "\\langle", "\\rangle", "\\lt", "\\gt", - "\\lvert", "\\rvert", "\\lVert", "\\rVert", - "\\lgroup", "\\rgroup", "\\lmoustache", "\\rmoustache", - "/", "\\backslash", - "|", "\\vert", "\\|", "\\Vert", - "\\uparrow", "\\Uparrow", - "\\downarrow", "\\Downarrow", - "\\updownarrow", "\\Updownarrow", - ".", -]; - -var fontAliases = { - "\\Bbb": "\\mathbb", - "\\bold": "\\mathbf", - "\\frak": "\\mathfrak", -}; - -// Single-argument color functions -defineFunction([ - "\\blue", "\\orange", "\\pink", "\\red", - "\\green", "\\gray", "\\purple", - "\\blueA", "\\blueB", "\\blueC", "\\blueD", "\\blueE", - "\\tealA", "\\tealB", "\\tealC", "\\tealD", "\\tealE", - "\\greenA", "\\greenB", "\\greenC", "\\greenD", "\\greenE", - "\\goldA", "\\goldB", "\\goldC", "\\goldD", "\\goldE", - "\\redA", "\\redB", "\\redC", "\\redD", "\\redE", - "\\maroonA", "\\maroonB", "\\maroonC", "\\maroonD", "\\maroonE", - "\\purpleA", "\\purpleB", "\\purpleC", "\\purpleD", "\\purpleE", - "\\mintA", "\\mintB", "\\mintC", - "\\grayA", "\\grayB", "\\grayC", "\\grayD", "\\grayE", - "\\grayF", "\\grayG", "\\grayH", "\\grayI", - "\\kaBlue", "\\kaGreen", -], { - numArgs: 1, - allowedInText: true, - greediness: 3, -}, function(context, args) { - var body = args[0]; - var atoms; - if (body.type === "ordgroup") { - atoms = body.value; - } else { - atoms = [body]; - } - - return { - type: "color", - color: "katex-" + context.funcName.slice(1), - value: atoms, - }; -}); - -// There are 2 flags for operators; whether they produce limits in -// displaystyle, and whether they are symbols and should grow in -// displaystyle. These four groups cover the four possible choices. - -// No limits, not symbols -defineFunction([ - "\\arcsin", "\\arccos", "\\arctan", "\\arg", "\\cos", "\\cosh", - "\\cot", "\\coth", "\\csc", "\\deg", "\\dim", "\\exp", "\\hom", - "\\ker", "\\lg", "\\ln", "\\log", "\\sec", "\\sin", "\\sinh", - "\\tan", "\\tanh", -], { - numArgs: 0, -}, function(context) { - return { - type: "op", - limits: false, - symbol: false, - body: context.funcName, - }; -}); - -// Limits, not symbols -defineFunction([ - "\\det", "\\gcd", "\\inf", "\\lim", "\\liminf", "\\limsup", "\\max", - "\\min", "\\Pr", "\\sup", -], { - numArgs: 0, -}, function(context) { - return { - type: "op", - limits: true, - symbol: false, - body: context.funcName, - }; -}); - -// No limits, symbols -defineFunction([ - "\\int", "\\iint", "\\iiint", "\\oint", -], { - numArgs: 0, -}, function(context) { - return { - type: "op", - limits: false, - symbol: true, - body: context.funcName, - }; -}); - -// Limits, symbols -defineFunction([ - "\\coprod", "\\bigvee", "\\bigwedge", "\\biguplus", "\\bigcap", - "\\bigcup", "\\intop", "\\prod", "\\sum", "\\bigotimes", - "\\bigoplus", "\\bigodot", "\\bigsqcup", "\\smallint", -], { - numArgs: 0, -}, function(context) { - return { - type: "op", - limits: true, - symbol: true, - body: context.funcName, - }; -}); - -// Fractions -defineFunction([ - "\\dfrac", "\\frac", "\\tfrac", - "\\dbinom", "\\binom", "\\tbinom", -], { - numArgs: 2, - greediness: 2, -}, function(context, args) { - var numer = args[0]; - var denom = args[1]; - var hasBarLine; - var leftDelim = null; - var rightDelim = null; - var size = "auto"; - - switch (context.funcName) { - case "\\dfrac": - case "\\frac": - case "\\tfrac": - hasBarLine = true; - break; - case "\\dbinom": - case "\\binom": - case "\\tbinom": - hasBarLine = false; - leftDelim = "("; - rightDelim = ")"; - break; - default: - throw new Error("Unrecognized genfrac command"); - } - - switch (context.funcName) { - case "\\dfrac": - case "\\dbinom": - size = "display"; - break; - case "\\tfrac": - case "\\tbinom": - size = "text"; - break; - } - - return { - type: "genfrac", - numer: numer, - denom: denom, - hasBarLine: hasBarLine, - leftDelim: leftDelim, - rightDelim: rightDelim, - size: size, - }; -}); - -// Left and right overlap functions -defineFunction(["\\llap", "\\rlap"], { - numArgs: 1, - allowedInText: true, -}, function(context, args) { - var body = args[0]; - return { - type: context.funcName.slice(1), - body: body, - }; -}); - -// Delimiter functions -defineFunction([ - "\\bigl", "\\Bigl", "\\biggl", "\\Biggl", - "\\bigr", "\\Bigr", "\\biggr", "\\Biggr", - "\\bigm", "\\Bigm", "\\biggm", "\\Biggm", - "\\big", "\\Big", "\\bigg", "\\Bigg", - "\\left", "\\right", -], { - numArgs: 1, -}, function(context, args) { - var delim = args[0]; - if (!utils.contains(delimiters, delim.value)) { - throw new ParseError( - "Invalid delimiter: '" + delim.value + "' after '" + - context.funcName + "'", delim); - } - - // \left and \right are caught somewhere in Parser.js, which is - // why this data doesn't match what is in buildHTML. - if (context.funcName === "\\left" || context.funcName === "\\right") { - return { - type: "leftright", - value: delim.value, - }; - } else { - return { - type: "delimsizing", - size: delimiterSizes[context.funcName].size, - delimType: delimiterSizes[context.funcName].type, - value: delim.value, - }; - } -}); - -// Sizing functions (handled in Parser.js explicitly, hence no handler) -defineFunction([ - "\\tiny", "\\scriptsize", "\\footnotesize", "\\small", - "\\normalsize", "\\large", "\\Large", "\\LARGE", "\\huge", "\\Huge", -], 0, null); - -// Style changing functions (handled in Parser.js explicitly, hence no -// handler) -defineFunction([ - "\\displaystyle", "\\textstyle", "\\scriptstyle", - "\\scriptscriptstyle", -], 0, null); - -defineFunction([ - // styles - "\\mathrm", "\\mathit", "\\mathbf", - - // families - "\\mathbb", "\\mathcal", "\\mathfrak", "\\mathscr", "\\mathsf", - "\\mathtt", - - // aliases - "\\Bbb", "\\bold", "\\frak", -], { - numArgs: 1, - greediness: 2, -}, function(context, args) { - var body = args[0]; - var func = context.funcName; - if (func in fontAliases) { - func = fontAliases[func]; - } - return { - type: "font", - font: func.slice(1), - body: body, - }; -}); - -// Accents -defineFunction([ - "\\acute", "\\grave", "\\ddot", "\\tilde", "\\bar", "\\breve", - "\\check", "\\hat", "\\vec", "\\dot", - // We don't support expanding accents yet - // "\\widetilde", "\\widehat" -], { - numArgs: 1, -}, function(context, args) { - var base = args[0]; - return { - type: "accent", - accent: context.funcName, - base: base, - }; -}); - -// Infix generalized fractions -defineFunction(["\\over", "\\choose"], { - numArgs: 0, - infix: true, -}, function(context) { - var replaceWith; - switch (context.funcName) { - case "\\over": - replaceWith = "\\frac"; - break; - case "\\choose": - replaceWith = "\\binom"; - break; - default: - throw new Error("Unrecognized infix genfrac command"); - } - return { - type: "infix", - replaceWith: replaceWith, - token: context.token, - }; -}); - -// Row breaks for aligned data -defineFunction(["\\\\", "\\cr"], { - numArgs: 0, - numOptionalArgs: 1, - argTypes: ["size"], -}, function(context, args) { - var size = args[0]; - return { - type: "cr", - size: size, - }; -}); - -// Environment delimiters -defineFunction(["\\begin", "\\end"], { - numArgs: 1, - argTypes: ["text"], -}, function(context, args) { - var nameGroup = args[0]; - if (nameGroup.type !== "ordgroup") { - throw new ParseError("Invalid environment name", nameGroup); - } - var name = ""; - for (var i = 0; i < nameGroup.value.length; ++i) { - name += nameGroup.value[i].value; - } - return { - type: "environment", - name: name, - nameGroup: nameGroup, - }; -}); - -},{"./ParseError":6,"./utils":25}],20:[function(require,module,exports){ -/** - * These objects store data about MathML nodes. This is the MathML equivalent - * of the types in domTree.js. Since MathML handles its own rendering, and - * since we're mainly using MathML to improve accessibility, we don't manage - * any of the styling state that the plain DOM nodes do. - * - * The `toNode` and `toMarkup` functions work simlarly to how they do in - * domTree.js, creating namespaced DOM nodes and HTML text markup respectively. - */ - -var utils = require("./utils"); - -/** - * This node represents a general purpose MathML node of any type. The - * constructor requires the type of node to create (for example, `"mo"` or - * `"mspace"`, corresponding to `<mo>` and `<mspace>` tags). - */ -function MathNode(type, children) { - this.type = type; - this.attributes = {}; - this.children = children || []; -} - -/** - * Sets an attribute on a MathML node. MathML depends on attributes to convey a - * semantic content, so this is used heavily. - */ -MathNode.prototype.setAttribute = function(name, value) { - this.attributes[name] = value; -}; - -/** - * Converts the math node into a MathML-namespaced DOM element. - */ -MathNode.prototype.toNode = function() { - var node = document.createElementNS( - "http://www.w3.org/1998/Math/MathML", this.type); - - for (var attr in this.attributes) { - if (Object.prototype.hasOwnProperty.call(this.attributes, attr)) { - node.setAttribute(attr, this.attributes[attr]); - } - } - - for (var i = 0; i < this.children.length; i++) { - node.appendChild(this.children[i].toNode()); - } - - return node; -}; - -/** - * Converts the math node into an HTML markup string. - */ -MathNode.prototype.toMarkup = function() { - var markup = "<" + this.type; - - // Add the attributes - for (var attr in this.attributes) { - if (Object.prototype.hasOwnProperty.call(this.attributes, attr)) { - markup += " " + attr + "=\""; - markup += utils.escape(this.attributes[attr]); - markup += "\""; - } - } - - markup += ">"; - - for (var i = 0; i < this.children.length; i++) { - markup += this.children[i].toMarkup(); - } - - markup += "</" + this.type + ">"; - - return markup; -}; - -/** - * This node represents a piece of text. - */ -function TextNode(text) { - this.text = text; -} - -/** - * Converts the text node into a DOM text node. - */ -TextNode.prototype.toNode = function() { - return document.createTextNode(this.text); -}; - -/** - * Converts the text node into HTML markup (which is just the text itself). - */ -TextNode.prototype.toMarkup = function() { - return utils.escape(this.text); -}; - -module.exports = { - MathNode: MathNode, - TextNode: TextNode, -}; - -},{"./utils":25}],21:[function(require,module,exports){ -/** - * The resulting parse tree nodes of the parse tree. - * - * It is possible to provide position information, so that a ParseNode can - * fulfil a role similar to a Token in error reporting. - * For details on the corresponding properties see Token constructor. - * Providing such information can lead to better error reporting. - * - * @param {string} type type of node, like e.g. "ordgroup" - * @param {?object} value type-specific representation of the node - * @param {string} mode parse mode in action for this node, - * "math" or "text" - * @param {Token=} firstToken first token of the input for this node, - * will omit position information if unset - * @param {Token=} lastToken last token of the input for this node, - * will default to firstToken if unset - */ -function ParseNode(type, value, mode, firstToken, lastToken) { - this.type = type; - this.value = value; - this.mode = mode; - if (firstToken && (!lastToken || lastToken.lexer === firstToken.lexer)) { - this.lexer = firstToken.lexer; - this.start = firstToken.start; - this.end = (lastToken || firstToken).end; - } -} - -module.exports = { - ParseNode: ParseNode, -}; - - -},{}],22:[function(require,module,exports){ -/** - * Provides a single function for parsing an expression using a Parser - * TODO(emily): Remove this - */ - -var Parser = require("./Parser"); - -/** - * Parses an expression using a Parser, then returns the parsed result. - */ -var parseTree = function(toParse, settings) { - if (!(typeof toParse === 'string' || toParse instanceof String)) { - throw new TypeError('KaTeX can only parse string typed expression'); - } - var parser = new Parser(toParse, settings); - - return parser.parse(); -}; - -module.exports = parseTree; - -},{"./Parser":7}],23:[function(require,module,exports){ -/** - * This file holds a list of all no-argument functions and single-character - * symbols (like 'a' or ';'). - * - * For each of the symbols, there are three properties they can have: - * - font (required): the font to be used for this symbol. Either "main" (the - normal font), or "ams" (the ams fonts). - * - group (required): the ParseNode group type the symbol should have (i.e. - "textord", "mathord", etc). - See https://github.com/Khan/KaTeX/wiki/Examining-TeX#group-types - * - replace: the character that this symbol or function should be - * replaced with (i.e. "\phi" has a replace value of "\u03d5", the phi - * character in the main font). - * - * The outermost map in the table indicates what mode the symbols should be - * accepted in (e.g. "math" or "text"). - */ - -module.exports = { - math: {}, - text: {}, -}; - -function defineSymbol(mode, font, group, replace, name) { - module.exports[mode][name] = { - font: font, - group: group, - replace: replace, - }; -} - -// Some abbreviations for commonly used strings. -// This helps minify the code, and also spotting typos using jshint. - -// modes: -var math = "math"; -var text = "text"; - -// fonts: -var main = "main"; -var ams = "ams"; - -// groups: -var accent = "accent"; -var bin = "bin"; -var close = "close"; -var inner = "inner"; -var mathord = "mathord"; -var op = "op"; -var open = "open"; -var punct = "punct"; -var rel = "rel"; -var spacing = "spacing"; -var textord = "textord"; - -// Now comes the symbol table - -// Relation Symbols -defineSymbol(math, main, rel, "\u2261", "\\equiv"); -defineSymbol(math, main, rel, "\u227a", "\\prec"); -defineSymbol(math, main, rel, "\u227b", "\\succ"); -defineSymbol(math, main, rel, "\u223c", "\\sim"); -defineSymbol(math, main, rel, "\u22a5", "\\perp"); -defineSymbol(math, main, rel, "\u2aaf", "\\preceq"); -defineSymbol(math, main, rel, "\u2ab0", "\\succeq"); -defineSymbol(math, main, rel, "\u2243", "\\simeq"); -defineSymbol(math, main, rel, "\u2223", "\\mid"); -defineSymbol(math, main, rel, "\u226a", "\\ll"); -defineSymbol(math, main, rel, "\u226b", "\\gg"); -defineSymbol(math, main, rel, "\u224d", "\\asymp"); -defineSymbol(math, main, rel, "\u2225", "\\parallel"); -defineSymbol(math, main, rel, "\u22c8", "\\bowtie"); -defineSymbol(math, main, rel, "\u2323", "\\smile"); -defineSymbol(math, main, rel, "\u2291", "\\sqsubseteq"); -defineSymbol(math, main, rel, "\u2292", "\\sqsupseteq"); -defineSymbol(math, main, rel, "\u2250", "\\doteq"); -defineSymbol(math, main, rel, "\u2322", "\\frown"); -defineSymbol(math, main, rel, "\u220b", "\\ni"); -defineSymbol(math, main, rel, "\u221d", "\\propto"); -defineSymbol(math, main, rel, "\u22a2", "\\vdash"); -defineSymbol(math, main, rel, "\u22a3", "\\dashv"); -defineSymbol(math, main, rel, "\u220b", "\\owns"); - -// Punctuation -defineSymbol(math, main, punct, "\u002e", "\\ldotp"); -defineSymbol(math, main, punct, "\u22c5", "\\cdotp"); - -// Misc Symbols -defineSymbol(math, main, textord, "\u0023", "\\#"); -defineSymbol(math, main, textord, "\u0026", "\\&"); -defineSymbol(math, main, textord, "\u2135", "\\aleph"); -defineSymbol(math, main, textord, "\u2200", "\\forall"); -defineSymbol(math, main, textord, "\u210f", "\\hbar"); -defineSymbol(math, main, textord, "\u2203", "\\exists"); -defineSymbol(math, main, textord, "\u2207", "\\nabla"); -defineSymbol(math, main, textord, "\u266d", "\\flat"); -defineSymbol(math, main, textord, "\u2113", "\\ell"); -defineSymbol(math, main, textord, "\u266e", "\\natural"); -defineSymbol(math, main, textord, "\u2663", "\\clubsuit"); -defineSymbol(math, main, textord, "\u2118", "\\wp"); -defineSymbol(math, main, textord, "\u266f", "\\sharp"); -defineSymbol(math, main, textord, "\u2662", "\\diamondsuit"); -defineSymbol(math, main, textord, "\u211c", "\\Re"); -defineSymbol(math, main, textord, "\u2661", "\\heartsuit"); -defineSymbol(math, main, textord, "\u2111", "\\Im"); -defineSymbol(math, main, textord, "\u2660", "\\spadesuit"); - -// Math and Text -defineSymbol(math, main, textord, "\u2020", "\\dag"); -defineSymbol(math, main, textord, "\u2021", "\\ddag"); - -// Large Delimiters -defineSymbol(math, main, close, "\u23b1", "\\rmoustache"); -defineSymbol(math, main, open, "\u23b0", "\\lmoustache"); -defineSymbol(math, main, close, "\u27ef", "\\rgroup"); -defineSymbol(math, main, open, "\u27ee", "\\lgroup"); - -// Binary Operators -defineSymbol(math, main, bin, "\u2213", "\\mp"); -defineSymbol(math, main, bin, "\u2296", "\\ominus"); -defineSymbol(math, main, bin, "\u228e", "\\uplus"); -defineSymbol(math, main, bin, "\u2293", "\\sqcap"); -defineSymbol(math, main, bin, "\u2217", "\\ast"); -defineSymbol(math, main, bin, "\u2294", "\\sqcup"); -defineSymbol(math, main, bin, "\u25ef", "\\bigcirc"); -defineSymbol(math, main, bin, "\u2219", "\\bullet"); -defineSymbol(math, main, bin, "\u2021", "\\ddagger"); -defineSymbol(math, main, bin, "\u2240", "\\wr"); -defineSymbol(math, main, bin, "\u2a3f", "\\amalg"); - -// Arrow Symbols -defineSymbol(math, main, rel, "\u27f5", "\\longleftarrow"); -defineSymbol(math, main, rel, "\u21d0", "\\Leftarrow"); -defineSymbol(math, main, rel, "\u27f8", "\\Longleftarrow"); -defineSymbol(math, main, rel, "\u27f6", "\\longrightarrow"); -defineSymbol(math, main, rel, "\u21d2", "\\Rightarrow"); -defineSymbol(math, main, rel, "\u27f9", "\\Longrightarrow"); -defineSymbol(math, main, rel, "\u2194", "\\leftrightarrow"); -defineSymbol(math, main, rel, "\u27f7", "\\longleftrightarrow"); -defineSymbol(math, main, rel, "\u21d4", "\\Leftrightarrow"); -defineSymbol(math, main, rel, "\u27fa", "\\Longleftrightarrow"); -defineSymbol(math, main, rel, "\u21a6", "\\mapsto"); -defineSymbol(math, main, rel, "\u27fc", "\\longmapsto"); -defineSymbol(math, main, rel, "\u2197", "\\nearrow"); -defineSymbol(math, main, rel, "\u21a9", "\\hookleftarrow"); -defineSymbol(math, main, rel, "\u21aa", "\\hookrightarrow"); -defineSymbol(math, main, rel, "\u2198", "\\searrow"); -defineSymbol(math, main, rel, "\u21bc", "\\leftharpoonup"); -defineSymbol(math, main, rel, "\u21c0", "\\rightharpoonup"); -defineSymbol(math, main, rel, "\u2199", "\\swarrow"); -defineSymbol(math, main, rel, "\u21bd", "\\leftharpoondown"); -defineSymbol(math, main, rel, "\u21c1", "\\rightharpoondown"); -defineSymbol(math, main, rel, "\u2196", "\\nwarrow"); -defineSymbol(math, main, rel, "\u21cc", "\\rightleftharpoons"); - -// AMS Negated Binary Relations -defineSymbol(math, ams, rel, "\u226e", "\\nless"); -defineSymbol(math, ams, rel, "\ue010", "\\nleqslant"); -defineSymbol(math, ams, rel, "\ue011", "\\nleqq"); -defineSymbol(math, ams, rel, "\u2a87", "\\lneq"); -defineSymbol(math, ams, rel, "\u2268", "\\lneqq"); -defineSymbol(math, ams, rel, "\ue00c", "\\lvertneqq"); -defineSymbol(math, ams, rel, "\u22e6", "\\lnsim"); -defineSymbol(math, ams, rel, "\u2a89", "\\lnapprox"); -defineSymbol(math, ams, rel, "\u2280", "\\nprec"); -defineSymbol(math, ams, rel, "\u22e0", "\\npreceq"); -defineSymbol(math, ams, rel, "\u22e8", "\\precnsim"); -defineSymbol(math, ams, rel, "\u2ab9", "\\precnapprox"); -defineSymbol(math, ams, rel, "\u2241", "\\nsim"); -defineSymbol(math, ams, rel, "\ue006", "\\nshortmid"); -defineSymbol(math, ams, rel, "\u2224", "\\nmid"); -defineSymbol(math, ams, rel, "\u22ac", "\\nvdash"); -defineSymbol(math, ams, rel, "\u22ad", "\\nvDash"); -defineSymbol(math, ams, rel, "\u22ea", "\\ntriangleleft"); -defineSymbol(math, ams, rel, "\u22ec", "\\ntrianglelefteq"); -defineSymbol(math, ams, rel, "\u228a", "\\subsetneq"); -defineSymbol(math, ams, rel, "\ue01a", "\\varsubsetneq"); -defineSymbol(math, ams, rel, "\u2acb", "\\subsetneqq"); -defineSymbol(math, ams, rel, "\ue017", "\\varsubsetneqq"); -defineSymbol(math, ams, rel, "\u226f", "\\ngtr"); -defineSymbol(math, ams, rel, "\ue00f", "\\ngeqslant"); -defineSymbol(math, ams, rel, "\ue00e", "\\ngeqq"); -defineSymbol(math, ams, rel, "\u2a88", "\\gneq"); -defineSymbol(math, ams, rel, "\u2269", "\\gneqq"); -defineSymbol(math, ams, rel, "\ue00d", "\\gvertneqq"); -defineSymbol(math, ams, rel, "\u22e7", "\\gnsim"); -defineSymbol(math, ams, rel, "\u2a8a", "\\gnapprox"); -defineSymbol(math, ams, rel, "\u2281", "\\nsucc"); -defineSymbol(math, ams, rel, "\u22e1", "\\nsucceq"); -defineSymbol(math, ams, rel, "\u22e9", "\\succnsim"); -defineSymbol(math, ams, rel, "\u2aba", "\\succnapprox"); -defineSymbol(math, ams, rel, "\u2246", "\\ncong"); -defineSymbol(math, ams, rel, "\ue007", "\\nshortparallel"); -defineSymbol(math, ams, rel, "\u2226", "\\nparallel"); -defineSymbol(math, ams, rel, "\u22af", "\\nVDash"); -defineSymbol(math, ams, rel, "\u22eb", "\\ntriangleright"); -defineSymbol(math, ams, rel, "\u22ed", "\\ntrianglerighteq"); -defineSymbol(math, ams, rel, "\ue018", "\\nsupseteqq"); -defineSymbol(math, ams, rel, "\u228b", "\\supsetneq"); -defineSymbol(math, ams, rel, "\ue01b", "\\varsupsetneq"); -defineSymbol(math, ams, rel, "\u2acc", "\\supsetneqq"); -defineSymbol(math, ams, rel, "\ue019", "\\varsupsetneqq"); -defineSymbol(math, ams, rel, "\u22ae", "\\nVdash"); -defineSymbol(math, ams, rel, "\u2ab5", "\\precneqq"); -defineSymbol(math, ams, rel, "\u2ab6", "\\succneqq"); -defineSymbol(math, ams, rel, "\ue016", "\\nsubseteqq"); -defineSymbol(math, ams, bin, "\u22b4", "\\unlhd"); -defineSymbol(math, ams, bin, "\u22b5", "\\unrhd"); - -// AMS Negated Arrows -defineSymbol(math, ams, rel, "\u219a", "\\nleftarrow"); -defineSymbol(math, ams, rel, "\u219b", "\\nrightarrow"); -defineSymbol(math, ams, rel, "\u21cd", "\\nLeftarrow"); -defineSymbol(math, ams, rel, "\u21cf", "\\nRightarrow"); -defineSymbol(math, ams, rel, "\u21ae", "\\nleftrightarrow"); -defineSymbol(math, ams, rel, "\u21ce", "\\nLeftrightarrow"); - -// AMS Misc -defineSymbol(math, ams, rel, "\u25b3", "\\vartriangle"); -defineSymbol(math, ams, textord, "\u210f", "\\hslash"); -defineSymbol(math, ams, textord, "\u25bd", "\\triangledown"); -defineSymbol(math, ams, textord, "\u25ca", "\\lozenge"); -defineSymbol(math, ams, textord, "\u24c8", "\\circledS"); -defineSymbol(math, ams, textord, "\u00ae", "\\circledR"); -defineSymbol(math, ams, textord, "\u2221", "\\measuredangle"); -defineSymbol(math, ams, textord, "\u2204", "\\nexists"); -defineSymbol(math, ams, textord, "\u2127", "\\mho"); -defineSymbol(math, ams, textord, "\u2132", "\\Finv"); -defineSymbol(math, ams, textord, "\u2141", "\\Game"); -defineSymbol(math, ams, textord, "\u006b", "\\Bbbk"); -defineSymbol(math, ams, textord, "\u2035", "\\backprime"); -defineSymbol(math, ams, textord, "\u25b2", "\\blacktriangle"); -defineSymbol(math, ams, textord, "\u25bc", "\\blacktriangledown"); -defineSymbol(math, ams, textord, "\u25a0", "\\blacksquare"); -defineSymbol(math, ams, textord, "\u29eb", "\\blacklozenge"); -defineSymbol(math, ams, textord, "\u2605", "\\bigstar"); -defineSymbol(math, ams, textord, "\u2222", "\\sphericalangle"); -defineSymbol(math, ams, textord, "\u2201", "\\complement"); -defineSymbol(math, ams, textord, "\u00f0", "\\eth"); -defineSymbol(math, ams, textord, "\u2571", "\\diagup"); -defineSymbol(math, ams, textord, "\u2572", "\\diagdown"); -defineSymbol(math, ams, textord, "\u25a1", "\\square"); -defineSymbol(math, ams, textord, "\u25a1", "\\Box"); -defineSymbol(math, ams, textord, "\u25ca", "\\Diamond"); -defineSymbol(math, ams, textord, "\u00a5", "\\yen"); -defineSymbol(math, ams, textord, "\u2713", "\\checkmark"); - -// AMS Hebrew -defineSymbol(math, ams, textord, "\u2136", "\\beth"); -defineSymbol(math, ams, textord, "\u2138", "\\daleth"); -defineSymbol(math, ams, textord, "\u2137", "\\gimel"); - -// AMS Greek -defineSymbol(math, ams, textord, "\u03dd", "\\digamma"); -defineSymbol(math, ams, textord, "\u03f0", "\\varkappa"); - -// AMS Delimiters -defineSymbol(math, ams, open, "\u250c", "\\ulcorner"); -defineSymbol(math, ams, close, "\u2510", "\\urcorner"); -defineSymbol(math, ams, open, "\u2514", "\\llcorner"); -defineSymbol(math, ams, close, "\u2518", "\\lrcorner"); - -// AMS Binary Relations -defineSymbol(math, ams, rel, "\u2266", "\\leqq"); -defineSymbol(math, ams, rel, "\u2a7d", "\\leqslant"); -defineSymbol(math, ams, rel, "\u2a95", "\\eqslantless"); -defineSymbol(math, ams, rel, "\u2272", "\\lesssim"); -defineSymbol(math, ams, rel, "\u2a85", "\\lessapprox"); -defineSymbol(math, ams, rel, "\u224a", "\\approxeq"); -defineSymbol(math, ams, bin, "\u22d6", "\\lessdot"); -defineSymbol(math, ams, rel, "\u22d8", "\\lll"); -defineSymbol(math, ams, rel, "\u2276", "\\lessgtr"); -defineSymbol(math, ams, rel, "\u22da", "\\lesseqgtr"); -defineSymbol(math, ams, rel, "\u2a8b", "\\lesseqqgtr"); -defineSymbol(math, ams, rel, "\u2251", "\\doteqdot"); -defineSymbol(math, ams, rel, "\u2253", "\\risingdotseq"); -defineSymbol(math, ams, rel, "\u2252", "\\fallingdotseq"); -defineSymbol(math, ams, rel, "\u223d", "\\backsim"); -defineSymbol(math, ams, rel, "\u22cd", "\\backsimeq"); -defineSymbol(math, ams, rel, "\u2ac5", "\\subseteqq"); -defineSymbol(math, ams, rel, "\u22d0", "\\Subset"); -defineSymbol(math, ams, rel, "\u228f", "\\sqsubset"); -defineSymbol(math, ams, rel, "\u227c", "\\preccurlyeq"); -defineSymbol(math, ams, rel, "\u22de", "\\curlyeqprec"); -defineSymbol(math, ams, rel, "\u227e", "\\precsim"); -defineSymbol(math, ams, rel, "\u2ab7", "\\precapprox"); -defineSymbol(math, ams, rel, "\u22b2", "\\vartriangleleft"); -defineSymbol(math, ams, rel, "\u22b4", "\\trianglelefteq"); -defineSymbol(math, ams, rel, "\u22a8", "\\vDash"); -defineSymbol(math, ams, rel, "\u22aa", "\\Vvdash"); -defineSymbol(math, ams, rel, "\u2323", "\\smallsmile"); -defineSymbol(math, ams, rel, "\u2322", "\\smallfrown"); -defineSymbol(math, ams, rel, "\u224f", "\\bumpeq"); -defineSymbol(math, ams, rel, "\u224e", "\\Bumpeq"); -defineSymbol(math, ams, rel, "\u2267", "\\geqq"); -defineSymbol(math, ams, rel, "\u2a7e", "\\geqslant"); -defineSymbol(math, ams, rel, "\u2a96", "\\eqslantgtr"); -defineSymbol(math, ams, rel, "\u2273", "\\gtrsim"); -defineSymbol(math, ams, rel, "\u2a86", "\\gtrapprox"); -defineSymbol(math, ams, bin, "\u22d7", "\\gtrdot"); -defineSymbol(math, ams, rel, "\u22d9", "\\ggg"); -defineSymbol(math, ams, rel, "\u2277", "\\gtrless"); -defineSymbol(math, ams, rel, "\u22db", "\\gtreqless"); -defineSymbol(math, ams, rel, "\u2a8c", "\\gtreqqless"); -defineSymbol(math, ams, rel, "\u2256", "\\eqcirc"); -defineSymbol(math, ams, rel, "\u2257", "\\circeq"); -defineSymbol(math, ams, rel, "\u225c", "\\triangleq"); -defineSymbol(math, ams, rel, "\u223c", "\\thicksim"); -defineSymbol(math, ams, rel, "\u2248", "\\thickapprox"); -defineSymbol(math, ams, rel, "\u2ac6", "\\supseteqq"); -defineSymbol(math, ams, rel, "\u22d1", "\\Supset"); -defineSymbol(math, ams, rel, "\u2290", "\\sqsupset"); -defineSymbol(math, ams, rel, "\u227d", "\\succcurlyeq"); -defineSymbol(math, ams, rel, "\u22df", "\\curlyeqsucc"); -defineSymbol(math, ams, rel, "\u227f", "\\succsim"); -defineSymbol(math, ams, rel, "\u2ab8", "\\succapprox"); -defineSymbol(math, ams, rel, "\u22b3", "\\vartriangleright"); -defineSymbol(math, ams, rel, "\u22b5", "\\trianglerighteq"); -defineSymbol(math, ams, rel, "\u22a9", "\\Vdash"); -defineSymbol(math, ams, rel, "\u2223", "\\shortmid"); -defineSymbol(math, ams, rel, "\u2225", "\\shortparallel"); -defineSymbol(math, ams, rel, "\u226c", "\\between"); -defineSymbol(math, ams, rel, "\u22d4", "\\pitchfork"); -defineSymbol(math, ams, rel, "\u221d", "\\varpropto"); -defineSymbol(math, ams, rel, "\u25c0", "\\blacktriangleleft"); -defineSymbol(math, ams, rel, "\u2234", "\\therefore"); -defineSymbol(math, ams, rel, "\u220d", "\\backepsilon"); -defineSymbol(math, ams, rel, "\u25b6", "\\blacktriangleright"); -defineSymbol(math, ams, rel, "\u2235", "\\because"); -defineSymbol(math, ams, rel, "\u22d8", "\\llless"); -defineSymbol(math, ams, rel, "\u22d9", "\\gggtr"); -defineSymbol(math, ams, bin, "\u22b2", "\\lhd"); -defineSymbol(math, ams, bin, "\u22b3", "\\rhd"); -defineSymbol(math, ams, rel, "\u2242", "\\eqsim"); -defineSymbol(math, main, rel, "\u22c8", "\\Join"); -defineSymbol(math, ams, rel, "\u2251", "\\Doteq"); - -// AMS Binary Operators -defineSymbol(math, ams, bin, "\u2214", "\\dotplus"); -defineSymbol(math, ams, bin, "\u2216", "\\smallsetminus"); -defineSymbol(math, ams, bin, "\u22d2", "\\Cap"); -defineSymbol(math, ams, bin, "\u22d3", "\\Cup"); -defineSymbol(math, ams, bin, "\u2a5e", "\\doublebarwedge"); -defineSymbol(math, ams, bin, "\u229f", "\\boxminus"); -defineSymbol(math, ams, bin, "\u229e", "\\boxplus"); -defineSymbol(math, ams, bin, "\u22c7", "\\divideontimes"); -defineSymbol(math, ams, bin, "\u22c9", "\\ltimes"); -defineSymbol(math, ams, bin, "\u22ca", "\\rtimes"); -defineSymbol(math, ams, bin, "\u22cb", "\\leftthreetimes"); -defineSymbol(math, ams, bin, "\u22cc", "\\rightthreetimes"); -defineSymbol(math, ams, bin, "\u22cf", "\\curlywedge"); -defineSymbol(math, ams, bin, "\u22ce", "\\curlyvee"); -defineSymbol(math, ams, bin, "\u229d", "\\circleddash"); -defineSymbol(math, ams, bin, "\u229b", "\\circledast"); -defineSymbol(math, ams, bin, "\u22c5", "\\centerdot"); -defineSymbol(math, ams, bin, "\u22ba", "\\intercal"); -defineSymbol(math, ams, bin, "\u22d2", "\\doublecap"); -defineSymbol(math, ams, bin, "\u22d3", "\\doublecup"); -defineSymbol(math, ams, bin, "\u22a0", "\\boxtimes"); - -// AMS Arrows -defineSymbol(math, ams, rel, "\u21e2", "\\dashrightarrow"); -defineSymbol(math, ams, rel, "\u21e0", "\\dashleftarrow"); -defineSymbol(math, ams, rel, "\u21c7", "\\leftleftarrows"); -defineSymbol(math, ams, rel, "\u21c6", "\\leftrightarrows"); -defineSymbol(math, ams, rel, "\u21da", "\\Lleftarrow"); -defineSymbol(math, ams, rel, "\u219e", "\\twoheadleftarrow"); -defineSymbol(math, ams, rel, "\u21a2", "\\leftarrowtail"); -defineSymbol(math, ams, rel, "\u21ab", "\\looparrowleft"); -defineSymbol(math, ams, rel, "\u21cb", "\\leftrightharpoons"); -defineSymbol(math, ams, rel, "\u21b6", "\\curvearrowleft"); -defineSymbol(math, ams, rel, "\u21ba", "\\circlearrowleft"); -defineSymbol(math, ams, rel, "\u21b0", "\\Lsh"); -defineSymbol(math, ams, rel, "\u21c8", "\\upuparrows"); -defineSymbol(math, ams, rel, "\u21bf", "\\upharpoonleft"); -defineSymbol(math, ams, rel, "\u21c3", "\\downharpoonleft"); -defineSymbol(math, ams, rel, "\u22b8", "\\multimap"); -defineSymbol(math, ams, rel, "\u21ad", "\\leftrightsquigarrow"); -defineSymbol(math, ams, rel, "\u21c9", "\\rightrightarrows"); -defineSymbol(math, ams, rel, "\u21c4", "\\rightleftarrows"); -defineSymbol(math, ams, rel, "\u21a0", "\\twoheadrightarrow"); -defineSymbol(math, ams, rel, "\u21a3", "\\rightarrowtail"); -defineSymbol(math, ams, rel, "\u21ac", "\\looparrowright"); -defineSymbol(math, ams, rel, "\u21b7", "\\curvearrowright"); -defineSymbol(math, ams, rel, "\u21bb", "\\circlearrowright"); -defineSymbol(math, ams, rel, "\u21b1", "\\Rsh"); -defineSymbol(math, ams, rel, "\u21ca", "\\downdownarrows"); -defineSymbol(math, ams, rel, "\u21be", "\\upharpoonright"); -defineSymbol(math, ams, rel, "\u21c2", "\\downharpoonright"); -defineSymbol(math, ams, rel, "\u21dd", "\\rightsquigarrow"); -defineSymbol(math, ams, rel, "\u21dd", "\\leadsto"); -defineSymbol(math, ams, rel, "\u21db", "\\Rrightarrow"); -defineSymbol(math, ams, rel, "\u21be", "\\restriction"); - -defineSymbol(math, main, textord, "\u2018", "`"); -defineSymbol(math, main, textord, "$", "\\$"); -defineSymbol(math, main, textord, "%", "\\%"); -defineSymbol(math, main, textord, "_", "\\_"); -defineSymbol(math, main, textord, "\u2220", "\\angle"); -defineSymbol(math, main, textord, "\u221e", "\\infty"); -defineSymbol(math, main, textord, "\u2032", "\\prime"); -defineSymbol(math, main, textord, "\u25b3", "\\triangle"); -defineSymbol(math, main, textord, "\u0393", "\\Gamma"); -defineSymbol(math, main, textord, "\u0394", "\\Delta"); -defineSymbol(math, main, textord, "\u0398", "\\Theta"); -defineSymbol(math, main, textord, "\u039b", "\\Lambda"); -defineSymbol(math, main, textord, "\u039e", "\\Xi"); -defineSymbol(math, main, textord, "\u03a0", "\\Pi"); -defineSymbol(math, main, textord, "\u03a3", "\\Sigma"); -defineSymbol(math, main, textord, "\u03a5", "\\Upsilon"); -defineSymbol(math, main, textord, "\u03a6", "\\Phi"); -defineSymbol(math, main, textord, "\u03a8", "\\Psi"); -defineSymbol(math, main, textord, "\u03a9", "\\Omega"); -defineSymbol(math, main, textord, "\u00ac", "\\neg"); -defineSymbol(math, main, textord, "\u00ac", "\\lnot"); -defineSymbol(math, main, textord, "\u22a4", "\\top"); -defineSymbol(math, main, textord, "\u22a5", "\\bot"); -defineSymbol(math, main, textord, "\u2205", "\\emptyset"); -defineSymbol(math, ams, textord, "\u2205", "\\varnothing"); -defineSymbol(math, main, mathord, "\u03b1", "\\alpha"); -defineSymbol(math, main, mathord, "\u03b2", "\\beta"); -defineSymbol(math, main, mathord, "\u03b3", "\\gamma"); -defineSymbol(math, main, mathord, "\u03b4", "\\delta"); -defineSymbol(math, main, mathord, "\u03f5", "\\epsilon"); -defineSymbol(math, main, mathord, "\u03b6", "\\zeta"); -defineSymbol(math, main, mathord, "\u03b7", "\\eta"); -defineSymbol(math, main, mathord, "\u03b8", "\\theta"); -defineSymbol(math, main, mathord, "\u03b9", "\\iota"); -defineSymbol(math, main, mathord, "\u03ba", "\\kappa"); -defineSymbol(math, main, mathord, "\u03bb", "\\lambda"); -defineSymbol(math, main, mathord, "\u03bc", "\\mu"); -defineSymbol(math, main, mathord, "\u03bd", "\\nu"); -defineSymbol(math, main, mathord, "\u03be", "\\xi"); -defineSymbol(math, main, mathord, "o", "\\omicron"); -defineSymbol(math, main, mathord, "\u03c0", "\\pi"); -defineSymbol(math, main, mathord, "\u03c1", "\\rho"); -defineSymbol(math, main, mathord, "\u03c3", "\\sigma"); -defineSymbol(math, main, mathord, "\u03c4", "\\tau"); -defineSymbol(math, main, mathord, "\u03c5", "\\upsilon"); -defineSymbol(math, main, mathord, "\u03d5", "\\phi"); -defineSymbol(math, main, mathord, "\u03c7", "\\chi"); -defineSymbol(math, main, mathord, "\u03c8", "\\psi"); -defineSymbol(math, main, mathord, "\u03c9", "\\omega"); -defineSymbol(math, main, mathord, "\u03b5", "\\varepsilon"); -defineSymbol(math, main, mathord, "\u03d1", "\\vartheta"); -defineSymbol(math, main, mathord, "\u03d6", "\\varpi"); -defineSymbol(math, main, mathord, "\u03f1", "\\varrho"); -defineSymbol(math, main, mathord, "\u03c2", "\\varsigma"); -defineSymbol(math, main, mathord, "\u03c6", "\\varphi"); -defineSymbol(math, main, bin, "\u2217", "*"); -defineSymbol(math, main, bin, "+", "+"); -defineSymbol(math, main, bin, "\u2212", "-"); -defineSymbol(math, main, bin, "\u22c5", "\\cdot"); -defineSymbol(math, main, bin, "\u2218", "\\circ"); -defineSymbol(math, main, bin, "\u00f7", "\\div"); -defineSymbol(math, main, bin, "\u00b1", "\\pm"); -defineSymbol(math, main, bin, "\u00d7", "\\times"); -defineSymbol(math, main, bin, "\u2229", "\\cap"); -defineSymbol(math, main, bin, "\u222a", "\\cup"); -defineSymbol(math, main, bin, "\u2216", "\\setminus"); -defineSymbol(math, main, bin, "\u2227", "\\land"); -defineSymbol(math, main, bin, "\u2228", "\\lor"); -defineSymbol(math, main, bin, "\u2227", "\\wedge"); -defineSymbol(math, main, bin, "\u2228", "\\vee"); -defineSymbol(math, main, textord, "\u221a", "\\surd"); -defineSymbol(math, main, open, "(", "("); -defineSymbol(math, main, open, "[", "["); -defineSymbol(math, main, open, "\u27e8", "\\langle"); -defineSymbol(math, main, open, "\u2223", "\\lvert"); -defineSymbol(math, main, open, "\u2225", "\\lVert"); -defineSymbol(math, main, close, ")", ")"); -defineSymbol(math, main, close, "]", "]"); -defineSymbol(math, main, close, "?", "?"); -defineSymbol(math, main, close, "!", "!"); -defineSymbol(math, main, close, "\u27e9", "\\rangle"); -defineSymbol(math, main, close, "\u2223", "\\rvert"); -defineSymbol(math, main, close, "\u2225", "\\rVert"); -defineSymbol(math, main, rel, "=", "="); -defineSymbol(math, main, rel, "<", "<"); -defineSymbol(math, main, rel, ">", ">"); -defineSymbol(math, main, rel, ":", ":"); -defineSymbol(math, main, rel, "\u2248", "\\approx"); -defineSymbol(math, main, rel, "\u2245", "\\cong"); -defineSymbol(math, main, rel, "\u2265", "\\ge"); -defineSymbol(math, main, rel, "\u2265", "\\geq"); -defineSymbol(math, main, rel, "\u2190", "\\gets"); -defineSymbol(math, main, rel, ">", "\\gt"); -defineSymbol(math, main, rel, "\u2208", "\\in"); -defineSymbol(math, main, rel, "\u2209", "\\notin"); -defineSymbol(math, main, rel, "\u2282", "\\subset"); -defineSymbol(math, main, rel, "\u2283", "\\supset"); -defineSymbol(math, main, rel, "\u2286", "\\subseteq"); -defineSymbol(math, main, rel, "\u2287", "\\supseteq"); -defineSymbol(math, ams, rel, "\u2288", "\\nsubseteq"); -defineSymbol(math, ams, rel, "\u2289", "\\nsupseteq"); -defineSymbol(math, main, rel, "\u22a8", "\\models"); -defineSymbol(math, main, rel, "\u2190", "\\leftarrow"); -defineSymbol(math, main, rel, "\u2264", "\\le"); -defineSymbol(math, main, rel, "\u2264", "\\leq"); -defineSymbol(math, main, rel, "<", "\\lt"); -defineSymbol(math, main, rel, "\u2260", "\\ne"); -defineSymbol(math, main, rel, "\u2260", "\\neq"); -defineSymbol(math, main, rel, "\u2192", "\\rightarrow"); -defineSymbol(math, main, rel, "\u2192", "\\to"); -defineSymbol(math, ams, rel, "\u2271", "\\ngeq"); -defineSymbol(math, ams, rel, "\u2270", "\\nleq"); -defineSymbol(math, main, spacing, null, "\\!"); -defineSymbol(math, main, spacing, "\u00a0", "\\ "); -defineSymbol(math, main, spacing, "\u00a0", "~"); -defineSymbol(math, main, spacing, null, "\\,"); -defineSymbol(math, main, spacing, null, "\\:"); -defineSymbol(math, main, spacing, null, "\\;"); -defineSymbol(math, main, spacing, null, "\\enspace"); -defineSymbol(math, main, spacing, null, "\\qquad"); -defineSymbol(math, main, spacing, null, "\\quad"); -defineSymbol(math, main, spacing, "\u00a0", "\\space"); -defineSymbol(math, main, punct, ",", ","); -defineSymbol(math, main, punct, ";", ";"); -defineSymbol(math, main, punct, ":", "\\colon"); -defineSymbol(math, ams, bin, "\u22bc", "\\barwedge"); -defineSymbol(math, ams, bin, "\u22bb", "\\veebar"); -defineSymbol(math, main, bin, "\u2299", "\\odot"); -defineSymbol(math, main, bin, "\u2295", "\\oplus"); -defineSymbol(math, main, bin, "\u2297", "\\otimes"); -defineSymbol(math, main, textord, "\u2202", "\\partial"); -defineSymbol(math, main, bin, "\u2298", "\\oslash"); -defineSymbol(math, ams, bin, "\u229a", "\\circledcirc"); -defineSymbol(math, ams, bin, "\u22a1", "\\boxdot"); -defineSymbol(math, main, bin, "\u25b3", "\\bigtriangleup"); -defineSymbol(math, main, bin, "\u25bd", "\\bigtriangledown"); -defineSymbol(math, main, bin, "\u2020", "\\dagger"); -defineSymbol(math, main, bin, "\u22c4", "\\diamond"); -defineSymbol(math, main, bin, "\u22c6", "\\star"); -defineSymbol(math, main, bin, "\u25c3", "\\triangleleft"); -defineSymbol(math, main, bin, "\u25b9", "\\triangleright"); -defineSymbol(math, main, open, "{", "\\{"); -defineSymbol(math, main, close, "}", "\\}"); -defineSymbol(math, main, open, "{", "\\lbrace"); -defineSymbol(math, main, close, "}", "\\rbrace"); -defineSymbol(math, main, open, "[", "\\lbrack"); -defineSymbol(math, main, close, "]", "\\rbrack"); -defineSymbol(math, main, open, "\u230a", "\\lfloor"); -defineSymbol(math, main, close, "\u230b", "\\rfloor"); -defineSymbol(math, main, open, "\u2308", "\\lceil"); -defineSymbol(math, main, close, "\u2309", "\\rceil"); -defineSymbol(math, main, textord, "\\", "\\backslash"); -defineSymbol(math, main, textord, "\u2223", "|"); -defineSymbol(math, main, textord, "\u2223", "\\vert"); -defineSymbol(math, main, textord, "\u2225", "\\|"); -defineSymbol(math, main, textord, "\u2225", "\\Vert"); -defineSymbol(math, main, rel, "\u2191", "\\uparrow"); -defineSymbol(math, main, rel, "\u21d1", "\\Uparrow"); -defineSymbol(math, main, rel, "\u2193", "\\downarrow"); -defineSymbol(math, main, rel, "\u21d3", "\\Downarrow"); -defineSymbol(math, main, rel, "\u2195", "\\updownarrow"); -defineSymbol(math, main, rel, "\u21d5", "\\Updownarrow"); -defineSymbol(math, math, op, "\u2210", "\\coprod"); -defineSymbol(math, math, op, "\u22c1", "\\bigvee"); -defineSymbol(math, math, op, "\u22c0", "\\bigwedge"); -defineSymbol(math, math, op, "\u2a04", "\\biguplus"); -defineSymbol(math, math, op, "\u22c2", "\\bigcap"); -defineSymbol(math, math, op, "\u22c3", "\\bigcup"); -defineSymbol(math, math, op, "\u222b", "\\int"); -defineSymbol(math, math, op, "\u222b", "\\intop"); -defineSymbol(math, math, op, "\u222c", "\\iint"); -defineSymbol(math, math, op, "\u222d", "\\iiint"); -defineSymbol(math, math, op, "\u220f", "\\prod"); -defineSymbol(math, math, op, "\u2211", "\\sum"); -defineSymbol(math, math, op, "\u2a02", "\\bigotimes"); -defineSymbol(math, math, op, "\u2a01", "\\bigoplus"); -defineSymbol(math, math, op, "\u2a00", "\\bigodot"); -defineSymbol(math, math, op, "\u222e", "\\oint"); -defineSymbol(math, math, op, "\u2a06", "\\bigsqcup"); -defineSymbol(math, math, op, "\u222b", "\\smallint"); -defineSymbol(math, main, inner, "\u2026", "\\ldots"); -defineSymbol(math, main, inner, "\u22ef", "\\cdots"); -defineSymbol(math, main, inner, "\u22f1", "\\ddots"); -defineSymbol(math, main, textord, "\u22ee", "\\vdots"); -defineSymbol(math, main, accent, "\u00b4", "\\acute"); -defineSymbol(math, main, accent, "\u0060", "\\grave"); -defineSymbol(math, main, accent, "\u00a8", "\\ddot"); -defineSymbol(math, main, accent, "\u007e", "\\tilde"); -defineSymbol(math, main, accent, "\u00af", "\\bar"); -defineSymbol(math, main, accent, "\u02d8", "\\breve"); -defineSymbol(math, main, accent, "\u02c7", "\\check"); -defineSymbol(math, main, accent, "\u005e", "\\hat"); -defineSymbol(math, main, accent, "\u20d7", "\\vec"); -defineSymbol(math, main, accent, "\u02d9", "\\dot"); -defineSymbol(math, main, mathord, "\u0131", "\\imath"); -defineSymbol(math, main, mathord, "\u0237", "\\jmath"); - -defineSymbol(text, main, textord, "\u2013", "--"); -defineSymbol(text, main, textord, "\u2014", "---"); -defineSymbol(text, main, textord, "\u2018", "`"); -defineSymbol(text, main, textord, "\u2019", "'"); -defineSymbol(text, main, textord, "\u201c", "``"); -defineSymbol(text, main, textord, "\u201d", "''"); -defineSymbol(math, main, textord, "\u00b0", "\\degree"); -defineSymbol(text, main, textord, "\u00b0", "\\degree"); -defineSymbol(math, main, mathord, "\u00a3", "\\pounds"); -defineSymbol(math, ams, textord, "\u2720", "\\maltese"); -defineSymbol(text, ams, textord, "\u2720", "\\maltese"); - -defineSymbol(text, main, spacing, "\u00a0", "\\ "); -defineSymbol(text, main, spacing, "\u00a0", " "); -defineSymbol(text, main, spacing, "\u00a0", "~"); - -// There are lots of symbols which are the same, so we add them in afterwards. -var i; -var ch; - -// All of these are textords in math mode -var mathTextSymbols = "0123456789/@.\""; -for (i = 0; i < mathTextSymbols.length; i++) { - ch = mathTextSymbols.charAt(i); - defineSymbol(math, main, textord, ch, ch); -} - -// All of these are textords in text mode -var textSymbols = "0123456789!@*()-=+[]\";:?/.,"; -for (i = 0; i < textSymbols.length; i++) { - ch = textSymbols.charAt(i); - defineSymbol(text, main, textord, ch, ch); -} - -// All of these are textords in text mode, and mathords in math mode -var letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; -for (i = 0; i < letters.length; i++) { - ch = letters.charAt(i); - defineSymbol(math, main, mathord, ch, ch); - defineSymbol(text, main, textord, ch, ch); -} - -// Latin-1 letters -for (i = 0x00C0; i <= 0x00D6; i++) { - ch = String.fromCharCode(i); - defineSymbol(text, main, textord, ch, ch); -} - -for (i = 0x00D8; i <= 0x00F6; i++) { - ch = String.fromCharCode(i); - defineSymbol(text, main, textord, ch, ch); -} - -for (i = 0x00F8; i <= 0x00FF; i++) { - ch = String.fromCharCode(i); - defineSymbol(text, main, textord, ch, ch); -} - -// Cyrillic -for (i = 0x0410; i <= 0x044F; i++) { - ch = String.fromCharCode(i); - defineSymbol(text, main, textord, ch, ch); -} - -},{}],24:[function(require,module,exports){ -var hangulRegex = /[\uAC00-\uD7AF]/; - -// This regex combines -// - Hiragana: [\u3040-\u309F] -// - Katakana: [\u30A0-\u30FF] -// - CJK ideograms: [\u4E00-\u9FAF] -// - Hangul syllables: [\uAC00-\uD7AF] -// Notably missing are halfwidth Katakana and Romanji glyphs. -var cjkRegex = - /[\u3040-\u309F]|[\u30A0-\u30FF]|[\u4E00-\u9FAF]|[\uAC00-\uD7AF]/; - -module.exports = { - cjkRegex: cjkRegex, - hangulRegex: hangulRegex, -}; - -},{}],25:[function(require,module,exports){ -/** - * This file contains a list of utility functions which are useful in other - * files. - */ - -/** - * Provide an `indexOf` function which works in IE8, but defers to native if - * possible. - */ -var nativeIndexOf = Array.prototype.indexOf; -var indexOf = function(list, elem) { - if (list == null) { - return -1; - } - if (nativeIndexOf && list.indexOf === nativeIndexOf) { - return list.indexOf(elem); - } - var i = 0; - var l = list.length; - for (; i < l; i++) { - if (list[i] === elem) { - return i; - } - } - return -1; -}; - -/** - * Return whether an element is contained in a list - */ -var contains = function(list, elem) { - return indexOf(list, elem) !== -1; -}; - -/** - * Provide a default value if a setting is undefined - */ -var deflt = function(setting, defaultIfUndefined) { - return setting === undefined ? defaultIfUndefined : setting; -}; - -// hyphenate and escape adapted from Facebook's React under Apache 2 license - -var uppercase = /([A-Z])/g; -var hyphenate = function(str) { - return str.replace(uppercase, "-$1").toLowerCase(); -}; - -var ESCAPE_LOOKUP = { - "&": "&", - ">": ">", - "<": "<", - "\"": """, - "'": "'", -}; - -var ESCAPE_REGEX = /[&><"']/g; - -function escaper(match) { - return ESCAPE_LOOKUP[match]; -} - -/** - * Escapes text to prevent scripting attacks. - * - * @param {*} text Text value to escape. - * @return {string} An escaped string. - */ -function escape(text) { - return ("" + text).replace(ESCAPE_REGEX, escaper); -} - -/** - * A function to set the text content of a DOM element in all supported - * browsers. Note that we don't define this if there is no document. - */ -var setTextContent; -if (typeof document !== "undefined") { - var testNode = document.createElement("span"); - if ("textContent" in testNode) { - setTextContent = function(node, text) { - node.textContent = text; - }; - } else { - setTextContent = function(node, text) { - node.innerText = text; - }; - } -} - -/** - * A function to clear a node. - */ -function clearNode(node) { - setTextContent(node, ""); -} - -module.exports = { - contains: contains, - deflt: deflt, - escape: escape, - hyphenate: hyphenate, - indexOf: indexOf, - setTextContent: setTextContent, - clearNode: clearNode, -}; - -},{}]},{},[1])(1) -});
\ No newline at end of file diff --git a/vendor/assets/stylesheets/katex.scss b/vendor/assets/stylesheets/katex.scss deleted file mode 100644 index b45836716f2..00000000000 --- a/vendor/assets/stylesheets/katex.scss +++ /dev/null @@ -1,977 +0,0 @@ -/* -The MIT License (MIT) - -Copyright (c) 2015 Khan Academy - -This software also uses portions of the underscore.js project, which is -MIT licensed with the following copyright: - -Copyright (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative -Reporters & Editors - -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: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -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. -*/ - -/* - Here is how to build a version of KaTeX that works with gitlab. - - The problem is that the standard procedure for changing font location doesn't work for the empty string. - - 1. Clone KaTeX. Anything later than 4fb9445a9 (is merged into master) will do. - 2. make (requires node) - 3. sed -e 's,fonts/,,' -e 's/url\(([^)]*)\)/url(font-path\1)/g' build/katex.css > build/katex.scss - 4. Copy build/katex.js to gitlab/vendor/assets/javascripts/katex.js, - build/katex.scss to gitlab/vendor/assets/stylesheets/katex.scss and - fonts/* to gitlab/vendor/assets/fonts/. -*/ - -@font-face { - font-family: 'KaTeX_AMS'; - src: url(font-path('KaTeX_AMS-Regular.eot')); - src: url(font-path('KaTeX_AMS-Regular.eot#iefix')) format('embedded-opentype'), url(font-path('KaTeX_AMS-Regular.woff2')) format('woff2'), url(font-path('KaTeX_AMS-Regular.woff')) format('woff'), url(font-path('KaTeX_AMS-Regular.ttf')) format('truetype'); - font-weight: 400; - font-style: normal; -} -@font-face { - font-family: 'KaTeX_Caligraphic'; - src: url(font-path('KaTeX_Caligraphic-Bold.eot')); - src: url(font-path('KaTeX_Caligraphic-Bold.eot#iefix')) format('embedded-opentype'), url(font-path('KaTeX_Caligraphic-Bold.woff2')) format('woff2'), url(font-path('KaTeX_Caligraphic-Bold.woff')) format('woff'), url(font-path('KaTeX_Caligraphic-Bold.ttf')) format('truetype'); - font-weight: 600; - font-style: normal; -} -@font-face { - font-family: 'KaTeX_Caligraphic'; - src: url(font-path('KaTeX_Caligraphic-Regular.eot')); - src: url(font-path('KaTeX_Caligraphic-Regular.eot#iefix')) format('embedded-opentype'), url(font-path('KaTeX_Caligraphic-Regular.woff2')) format('woff2'), url(font-path('KaTeX_Caligraphic-Regular.woff')) format('woff'), url(font-path('KaTeX_Caligraphic-Regular.ttf')) format('truetype'); - font-weight: 400; - font-style: normal; -} -@font-face { - font-family: 'KaTeX_Fraktur'; - src: url(font-path('KaTeX_Fraktur-Bold.eot')); - src: url(font-path('KaTeX_Fraktur-Bold.eot#iefix')) format('embedded-opentype'), url(font-path('KaTeX_Fraktur-Bold.woff2')) format('woff2'), url(font-path('KaTeX_Fraktur-Bold.woff')) format('woff'), url(font-path('KaTeX_Fraktur-Bold.ttf')) format('truetype'); - font-weight: 600; - font-style: normal; -} -@font-face { - font-family: 'KaTeX_Fraktur'; - src: url(font-path('KaTeX_Fraktur-Regular.eot')); - src: url(font-path('KaTeX_Fraktur-Regular.eot#iefix')) format('embedded-opentype'), url(font-path('KaTeX_Fraktur-Regular.woff2')) format('woff2'), url(font-path('KaTeX_Fraktur-Regular.woff')) format('woff'), url(font-path('KaTeX_Fraktur-Regular.ttf')) format('truetype'); - font-weight: 400; - font-style: normal; -} -@font-face { - font-family: 'KaTeX_Main'; - src: url(font-path('KaTeX_Main-Bold.eot')); - src: url(font-path('KaTeX_Main-Bold.eot#iefix')) format('embedded-opentype'), url(font-path('KaTeX_Main-Bold.woff2')) format('woff2'), url(font-path('KaTeX_Main-Bold.woff')) format('woff'), url(font-path('KaTeX_Main-Bold.ttf')) format('truetype'); - font-weight: 600; - font-style: normal; -} -@font-face { - font-family: 'KaTeX_Main'; - src: url(font-path('KaTeX_Main-Italic.eot')); - src: url(font-path('KaTeX_Main-Italic.eot#iefix')) format('embedded-opentype'), url(font-path('KaTeX_Main-Italic.woff2')) format('woff2'), url(font-path('KaTeX_Main-Italic.woff')) format('woff'), url(font-path('KaTeX_Main-Italic.ttf')) format('truetype'); - font-weight: 400; - font-style: italic; -} -@font-face { - font-family: 'KaTeX_Main'; - src: url(font-path('KaTeX_Main-Regular.eot')); - src: url(font-path('KaTeX_Main-Regular.eot#iefix')) format('embedded-opentype'), url(font-path('KaTeX_Main-Regular.woff2')) format('woff2'), url(font-path('KaTeX_Main-Regular.woff')) format('woff'), url(font-path('KaTeX_Main-Regular.ttf')) format('truetype'); - font-weight: 400; - font-style: normal; -} -@font-face { - font-family: 'KaTeX_Math'; - src: url(font-path('KaTeX_Math-Italic.eot')); - src: url(font-path('KaTeX_Math-Italic.eot#iefix')) format('embedded-opentype'), url(font-path('KaTeX_Math-Italic.woff2')) format('woff2'), url(font-path('KaTeX_Math-Italic.woff')) format('woff'), url(font-path('KaTeX_Math-Italic.ttf')) format('truetype'); - font-weight: 400; - font-style: italic; -} -@font-face { - font-family: 'KaTeX_SansSerif'; - src: url(font-path('KaTeX_SansSerif-Regular.eot')); - src: url(font-path('KaTeX_SansSerif-Regular.eot#iefix')) format('embedded-opentype'), url(font-path('KaTeX_SansSerif-Regular.woff2')) format('woff2'), url(font-path('KaTeX_SansSerif-Regular.woff')) format('woff'), url(font-path('KaTeX_SansSerif-Regular.ttf')) format('truetype'); - font-weight: 400; - font-style: normal; -} -@font-face { - font-family: 'KaTeX_Script'; - src: url(font-path('KaTeX_Script-Regular.eot')); - src: url(font-path('KaTeX_Script-Regular.eot#iefix')) format('embedded-opentype'), url(font-path('KaTeX_Script-Regular.woff2')) format('woff2'), url(font-path('KaTeX_Script-Regular.woff')) format('woff'), url(font-path('KaTeX_Script-Regular.ttf')) format('truetype'); - font-weight: 400; - font-style: normal; -} -@font-face { - font-family: 'KaTeX_Size1'; - src: url(font-path('KaTeX_Size1-Regular.eot')); - src: url(font-path('KaTeX_Size1-Regular.eot#iefix')) format('embedded-opentype'), url(font-path('KaTeX_Size1-Regular.woff2')) format('woff2'), url(font-path('KaTeX_Size1-Regular.woff')) format('woff'), url(font-path('KaTeX_Size1-Regular.ttf')) format('truetype'); - font-weight: 400; - font-style: normal; -} -@font-face { - font-family: 'KaTeX_Size2'; - src: url(font-path('KaTeX_Size2-Regular.eot')); - src: url(font-path('KaTeX_Size2-Regular.eot#iefix')) format('embedded-opentype'), url(font-path('KaTeX_Size2-Regular.woff2')) format('woff2'), url(font-path('KaTeX_Size2-Regular.woff')) format('woff'), url(font-path('KaTeX_Size2-Regular.ttf')) format('truetype'); - font-weight: 400; - font-style: normal; -} -@font-face { - font-family: 'KaTeX_Size3'; - src: url(font-path('KaTeX_Size3-Regular.eot')); - src: url(font-path('KaTeX_Size3-Regular.eot#iefix')) format('embedded-opentype'), url(font-path('KaTeX_Size3-Regular.woff2')) format('woff2'), url(font-path('KaTeX_Size3-Regular.woff')) format('woff'), url(font-path('KaTeX_Size3-Regular.ttf')) format('truetype'); - font-weight: 400; - font-style: normal; -} -@font-face { - font-family: 'KaTeX_Size4'; - src: url(font-path('KaTeX_Size4-Regular.eot')); - src: url(font-path('KaTeX_Size4-Regular.eot#iefix')) format('embedded-opentype'), url(font-path('KaTeX_Size4-Regular.woff2')) format('woff2'), url(font-path('KaTeX_Size4-Regular.woff')) format('woff'), url(font-path('KaTeX_Size4-Regular.ttf')) format('truetype'); - font-weight: 400; - font-style: normal; -} -@font-face { - font-family: 'KaTeX_Typewriter'; - src: url(font-path('KaTeX_Typewriter-Regular.eot')); - src: url(font-path('KaTeX_Typewriter-Regular.eot#iefix')) format('embedded-opentype'), url(font-path('KaTeX_Typewriter-Regular.woff2')) format('woff2'), url(font-path('KaTeX_Typewriter-Regular.woff')) format('woff'), url(font-path('KaTeX_Typewriter-Regular.ttf')) format('truetype'); - font-weight: 400; - font-style: normal; -} -.katex-display { - display: block; - margin: 1em 0; - text-align: center; -} -.katex-display > .katex { - display: inline-block; - text-align: initial; -} -.katex { - font: normal 1.21em KaTeX_Main, Times New Roman, serif; - line-height: 1.2; - white-space: nowrap; - text-indent: 0; -} -.katex .katex-html { - display: inline-block; -} -.katex .katex-mathml { - position: absolute; - clip: rect(1px, 1px, 1px, 1px); - padding: 0; - border: 0; - height: 1px; - width: 1px; - overflow: hidden; -} -.katex .base { - display: inline-block; -} -.katex .strut { - display: inline-block; -} -.katex .mathit { - font-family: KaTeX_Math; - font-style: italic; -} -.katex .mathbf { - font-family: KaTeX_Main; - font-weight: 600; -} -.katex .amsrm { - font-family: KaTeX_AMS; -} -.katex .mathbb { - font-family: KaTeX_AMS; -} -.katex .mathcal { - font-family: KaTeX_Caligraphic; -} -.katex .mathfrak { - font-family: KaTeX_Fraktur; -} -.katex .mathtt { - font-family: KaTeX_Typewriter; -} -.katex .mathscr { - font-family: KaTeX_Script; -} -.katex .mathsf { - font-family: KaTeX_SansSerif; -} -.katex .mainit { - font-family: KaTeX_Main; - font-style: italic; -} -.katex .textstyle > .mord + .mop { - margin-left: 0.16667em; -} -.katex .textstyle > .mord + .mbin { - margin-left: 0.22222em; -} -.katex .textstyle > .mord + .mrel { - margin-left: 0.27778em; -} -.katex .textstyle > .mord + .minner { - margin-left: 0.16667em; -} -.katex .textstyle > .mop + .mord { - margin-left: 0.16667em; -} -.katex .textstyle > .mop + .mop { - margin-left: 0.16667em; -} -.katex .textstyle > .mop + .mrel { - margin-left: 0.27778em; -} -.katex .textstyle > .mop + .minner { - margin-left: 0.16667em; -} -.katex .textstyle > .mbin + .mord { - margin-left: 0.22222em; -} -.katex .textstyle > .mbin + .mop { - margin-left: 0.22222em; -} -.katex .textstyle > .mbin + .mopen { - margin-left: 0.22222em; -} -.katex .textstyle > .mbin + .minner { - margin-left: 0.22222em; -} -.katex .textstyle > .mrel + .mord { - margin-left: 0.27778em; -} -.katex .textstyle > .mrel + .mop { - margin-left: 0.27778em; -} -.katex .textstyle > .mrel + .mopen { - margin-left: 0.27778em; -} -.katex .textstyle > .mrel + .minner { - margin-left: 0.27778em; -} -.katex .textstyle > .mclose + .mop { - margin-left: 0.16667em; -} -.katex .textstyle > .mclose + .mbin { - margin-left: 0.22222em; -} -.katex .textstyle > .mclose + .mrel { - margin-left: 0.27778em; -} -.katex .textstyle > .mclose + .minner { - margin-left: 0.16667em; -} -.katex .textstyle > .mpunct + .mord { - margin-left: 0.16667em; -} -.katex .textstyle > .mpunct + .mop { - margin-left: 0.16667em; -} -.katex .textstyle > .mpunct + .mrel { - margin-left: 0.16667em; -} -.katex .textstyle > .mpunct + .mopen { - margin-left: 0.16667em; -} -.katex .textstyle > .mpunct + .mclose { - margin-left: 0.16667em; -} -.katex .textstyle > .mpunct + .mpunct { - margin-left: 0.16667em; -} -.katex .textstyle > .mpunct + .minner { - margin-left: 0.16667em; -} -.katex .textstyle > .minner + .mord { - margin-left: 0.16667em; -} -.katex .textstyle > .minner + .mop { - margin-left: 0.16667em; -} -.katex .textstyle > .minner + .mbin { - margin-left: 0.22222em; -} -.katex .textstyle > .minner + .mrel { - margin-left: 0.27778em; -} -.katex .textstyle > .minner + .mopen { - margin-left: 0.16667em; -} -.katex .textstyle > .minner + .mpunct { - margin-left: 0.16667em; -} -.katex .textstyle > .minner + .minner { - margin-left: 0.16667em; -} -.katex .mord + .mop { - margin-left: 0.16667em; -} -.katex .mop + .mord { - margin-left: 0.16667em; -} -.katex .mop + .mop { - margin-left: 0.16667em; -} -.katex .mclose + .mop { - margin-left: 0.16667em; -} -.katex .minner + .mop { - margin-left: 0.16667em; -} -.katex .reset-textstyle.textstyle { - font-size: 1em; -} -.katex .reset-textstyle.scriptstyle { - font-size: 0.7em; -} -.katex .reset-textstyle.scriptscriptstyle { - font-size: 0.5em; -} -.katex .reset-scriptstyle.textstyle { - font-size: 1.42857em; -} -.katex .reset-scriptstyle.scriptstyle { - font-size: 1em; -} -.katex .reset-scriptstyle.scriptscriptstyle { - font-size: 0.71429em; -} -.katex .reset-scriptscriptstyle.textstyle { - font-size: 2em; -} -.katex .reset-scriptscriptstyle.scriptstyle { - font-size: 1.4em; -} -.katex .reset-scriptscriptstyle.scriptscriptstyle { - font-size: 1em; -} -.katex .style-wrap { - position: relative; -} -.katex .vlist { - display: inline-block; -} -.katex .vlist > span { - display: block; - height: 0; - position: relative; -} -.katex .vlist > span > span { - display: inline-block; -} -.katex .vlist .baseline-fix { - display: inline-table; - table-layout: fixed; -} -.katex .msupsub { - text-align: left; -} -.katex .mfrac > span > span { - text-align: center; -} -.katex .mfrac .frac-line { - width: 100%; -} -.katex .mfrac .frac-line:before { - border-bottom-style: solid; - border-bottom-width: 1px; - content: ""; - display: block; -} -.katex .mfrac .frac-line:after { - border-bottom-style: solid; - border-bottom-width: 0.04em; - content: ""; - display: block; - margin-top: -1px; -} -.katex .mspace { - display: inline-block; -} -.katex .mspace.negativethinspace { - margin-left: -0.16667em; -} -.katex .mspace.thinspace { - width: 0.16667em; -} -.katex .mspace.mediumspace { - width: 0.22222em; -} -.katex .mspace.thickspace { - width: 0.27778em; -} -.katex .mspace.enspace { - width: 0.5em; -} -.katex .mspace.quad { - width: 1em; -} -.katex .mspace.qquad { - width: 2em; -} -.katex .llap, -.katex .rlap { - width: 0; - position: relative; -} -.katex .llap > .inner, -.katex .rlap > .inner { - position: absolute; -} -.katex .llap > .fix, -.katex .rlap > .fix { - display: inline-block; -} -.katex .llap > .inner { - right: 0; -} -.katex .rlap > .inner { - left: 0; -} -.katex .katex-logo .a { - font-size: 0.75em; - margin-left: -0.32em; - position: relative; - top: -0.2em; -} -.katex .katex-logo .t { - margin-left: -0.23em; -} -.katex .katex-logo .e { - margin-left: -0.1667em; - position: relative; - top: 0.2155em; -} -.katex .katex-logo .x { - margin-left: -0.125em; -} -.katex .rule { - display: inline-block; - border: solid 0; - position: relative; -} -.katex .overline .overline-line, -.katex .underline .underline-line { - width: 100%; -} -.katex .overline .overline-line:before, -.katex .underline .underline-line:before { - border-bottom-style: solid; - border-bottom-width: 1px; - content: ""; - display: block; -} -.katex .overline .overline-line:after, -.katex .underline .underline-line:after { - border-bottom-style: solid; - border-bottom-width: 0.04em; - content: ""; - display: block; - margin-top: -1px; -} -.katex .sqrt > .sqrt-sign { - position: relative; -} -.katex .sqrt .sqrt-line { - width: 100%; -} -.katex .sqrt .sqrt-line:before { - border-bottom-style: solid; - border-bottom-width: 1px; - content: ""; - display: block; -} -.katex .sqrt .sqrt-line:after { - border-bottom-style: solid; - border-bottom-width: 0.04em; - content: ""; - display: block; - margin-top: -1px; -} -.katex .sqrt > .root { - margin-left: 0.27777778em; - margin-right: -0.55555556em; -} -.katex .sizing, -.katex .fontsize-ensurer { - display: inline-block; -} -.katex .sizing.reset-size1.size1, -.katex .fontsize-ensurer.reset-size1.size1 { - font-size: 1em; -} -.katex .sizing.reset-size1.size2, -.katex .fontsize-ensurer.reset-size1.size2 { - font-size: 1.4em; -} -.katex .sizing.reset-size1.size3, -.katex .fontsize-ensurer.reset-size1.size3 { - font-size: 1.6em; -} -.katex .sizing.reset-size1.size4, -.katex .fontsize-ensurer.reset-size1.size4 { - font-size: 1.8em; -} -.katex .sizing.reset-size1.size5, -.katex .fontsize-ensurer.reset-size1.size5 { - font-size: 2em; -} -.katex .sizing.reset-size1.size6, -.katex .fontsize-ensurer.reset-size1.size6 { - font-size: 2.4em; -} -.katex .sizing.reset-size1.size7, -.katex .fontsize-ensurer.reset-size1.size7 { - font-size: 2.88em; -} -.katex .sizing.reset-size1.size8, -.katex .fontsize-ensurer.reset-size1.size8 { - font-size: 3.46em; -} -.katex .sizing.reset-size1.size9, -.katex .fontsize-ensurer.reset-size1.size9 { - font-size: 4.14em; -} -.katex .sizing.reset-size1.size10, -.katex .fontsize-ensurer.reset-size1.size10 { - font-size: 4.98em; -} -.katex .sizing.reset-size2.size1, -.katex .fontsize-ensurer.reset-size2.size1 { - font-size: 0.71428571em; -} -.katex .sizing.reset-size2.size2, -.katex .fontsize-ensurer.reset-size2.size2 { - font-size: 1em; -} -.katex .sizing.reset-size2.size3, -.katex .fontsize-ensurer.reset-size2.size3 { - font-size: 1.14285714em; -} -.katex .sizing.reset-size2.size4, -.katex .fontsize-ensurer.reset-size2.size4 { - font-size: 1.28571429em; -} -.katex .sizing.reset-size2.size5, -.katex .fontsize-ensurer.reset-size2.size5 { - font-size: 1.42857143em; -} -.katex .sizing.reset-size2.size6, -.katex .fontsize-ensurer.reset-size2.size6 { - font-size: 1.71428571em; -} -.katex .sizing.reset-size2.size7, -.katex .fontsize-ensurer.reset-size2.size7 { - font-size: 2.05714286em; -} -.katex .sizing.reset-size2.size8, -.katex .fontsize-ensurer.reset-size2.size8 { - font-size: 2.47142857em; -} -.katex .sizing.reset-size2.size9, -.katex .fontsize-ensurer.reset-size2.size9 { - font-size: 2.95714286em; -} -.katex .sizing.reset-size2.size10, -.katex .fontsize-ensurer.reset-size2.size10 { - font-size: 3.55714286em; -} -.katex .sizing.reset-size3.size1, -.katex .fontsize-ensurer.reset-size3.size1 { - font-size: 0.625em; -} -.katex .sizing.reset-size3.size2, -.katex .fontsize-ensurer.reset-size3.size2 { - font-size: 0.875em; -} -.katex .sizing.reset-size3.size3, -.katex .fontsize-ensurer.reset-size3.size3 { - font-size: 1em; -} -.katex .sizing.reset-size3.size4, -.katex .fontsize-ensurer.reset-size3.size4 { - font-size: 1.125em; -} -.katex .sizing.reset-size3.size5, -.katex .fontsize-ensurer.reset-size3.size5 { - font-size: 1.25em; -} -.katex .sizing.reset-size3.size6, -.katex .fontsize-ensurer.reset-size3.size6 { - font-size: 1.5em; -} -.katex .sizing.reset-size3.size7, -.katex .fontsize-ensurer.reset-size3.size7 { - font-size: 1.8em; -} -.katex .sizing.reset-size3.size8, -.katex .fontsize-ensurer.reset-size3.size8 { - font-size: 2.1625em; -} -.katex .sizing.reset-size3.size9, -.katex .fontsize-ensurer.reset-size3.size9 { - font-size: 2.5875em; -} -.katex .sizing.reset-size3.size10, -.katex .fontsize-ensurer.reset-size3.size10 { - font-size: 3.1125em; -} -.katex .sizing.reset-size4.size1, -.katex .fontsize-ensurer.reset-size4.size1 { - font-size: 0.55555556em; -} -.katex .sizing.reset-size4.size2, -.katex .fontsize-ensurer.reset-size4.size2 { - font-size: 0.77777778em; -} -.katex .sizing.reset-size4.size3, -.katex .fontsize-ensurer.reset-size4.size3 { - font-size: 0.88888889em; -} -.katex .sizing.reset-size4.size4, -.katex .fontsize-ensurer.reset-size4.size4 { - font-size: 1em; -} -.katex .sizing.reset-size4.size5, -.katex .fontsize-ensurer.reset-size4.size5 { - font-size: 1.11111111em; -} -.katex .sizing.reset-size4.size6, -.katex .fontsize-ensurer.reset-size4.size6 { - font-size: 1.33333333em; -} -.katex .sizing.reset-size4.size7, -.katex .fontsize-ensurer.reset-size4.size7 { - font-size: 1.6em; -} -.katex .sizing.reset-size4.size8, -.katex .fontsize-ensurer.reset-size4.size8 { - font-size: 1.92222222em; -} -.katex .sizing.reset-size4.size9, -.katex .fontsize-ensurer.reset-size4.size9 { - font-size: 2.3em; -} -.katex .sizing.reset-size4.size10, -.katex .fontsize-ensurer.reset-size4.size10 { - font-size: 2.76666667em; -} -.katex .sizing.reset-size5.size1, -.katex .fontsize-ensurer.reset-size5.size1 { - font-size: 0.5em; -} -.katex .sizing.reset-size5.size2, -.katex .fontsize-ensurer.reset-size5.size2 { - font-size: 0.7em; -} -.katex .sizing.reset-size5.size3, -.katex .fontsize-ensurer.reset-size5.size3 { - font-size: 0.8em; -} -.katex .sizing.reset-size5.size4, -.katex .fontsize-ensurer.reset-size5.size4 { - font-size: 0.9em; -} -.katex .sizing.reset-size5.size5, -.katex .fontsize-ensurer.reset-size5.size5 { - font-size: 1em; -} -.katex .sizing.reset-size5.size6, -.katex .fontsize-ensurer.reset-size5.size6 { - font-size: 1.2em; -} -.katex .sizing.reset-size5.size7, -.katex .fontsize-ensurer.reset-size5.size7 { - font-size: 1.44em; -} -.katex .sizing.reset-size5.size8, -.katex .fontsize-ensurer.reset-size5.size8 { - font-size: 1.73em; -} -.katex .sizing.reset-size5.size9, -.katex .fontsize-ensurer.reset-size5.size9 { - font-size: 2.07em; -} -.katex .sizing.reset-size5.size10, -.katex .fontsize-ensurer.reset-size5.size10 { - font-size: 2.49em; -} -.katex .sizing.reset-size6.size1, -.katex .fontsize-ensurer.reset-size6.size1 { - font-size: 0.41666667em; -} -.katex .sizing.reset-size6.size2, -.katex .fontsize-ensurer.reset-size6.size2 { - font-size: 0.58333333em; -} -.katex .sizing.reset-size6.size3, -.katex .fontsize-ensurer.reset-size6.size3 { - font-size: 0.66666667em; -} -.katex .sizing.reset-size6.size4, -.katex .fontsize-ensurer.reset-size6.size4 { - font-size: 0.75em; -} -.katex .sizing.reset-size6.size5, -.katex .fontsize-ensurer.reset-size6.size5 { - font-size: 0.83333333em; -} -.katex .sizing.reset-size6.size6, -.katex .fontsize-ensurer.reset-size6.size6 { - font-size: 1em; -} -.katex .sizing.reset-size6.size7, -.katex .fontsize-ensurer.reset-size6.size7 { - font-size: 1.2em; -} -.katex .sizing.reset-size6.size8, -.katex .fontsize-ensurer.reset-size6.size8 { - font-size: 1.44166667em; -} -.katex .sizing.reset-size6.size9, -.katex .fontsize-ensurer.reset-size6.size9 { - font-size: 1.725em; -} -.katex .sizing.reset-size6.size10, -.katex .fontsize-ensurer.reset-size6.size10 { - font-size: 2.075em; -} -.katex .sizing.reset-size7.size1, -.katex .fontsize-ensurer.reset-size7.size1 { - font-size: 0.34722222em; -} -.katex .sizing.reset-size7.size2, -.katex .fontsize-ensurer.reset-size7.size2 { - font-size: 0.48611111em; -} -.katex .sizing.reset-size7.size3, -.katex .fontsize-ensurer.reset-size7.size3 { - font-size: 0.55555556em; -} -.katex .sizing.reset-size7.size4, -.katex .fontsize-ensurer.reset-size7.size4 { - font-size: 0.625em; -} -.katex .sizing.reset-size7.size5, -.katex .fontsize-ensurer.reset-size7.size5 { - font-size: 0.69444444em; -} -.katex .sizing.reset-size7.size6, -.katex .fontsize-ensurer.reset-size7.size6 { - font-size: 0.83333333em; -} -.katex .sizing.reset-size7.size7, -.katex .fontsize-ensurer.reset-size7.size7 { - font-size: 1em; -} -.katex .sizing.reset-size7.size8, -.katex .fontsize-ensurer.reset-size7.size8 { - font-size: 1.20138889em; -} -.katex .sizing.reset-size7.size9, -.katex .fontsize-ensurer.reset-size7.size9 { - font-size: 1.4375em; -} -.katex .sizing.reset-size7.size10, -.katex .fontsize-ensurer.reset-size7.size10 { - font-size: 1.72916667em; -} -.katex .sizing.reset-size8.size1, -.katex .fontsize-ensurer.reset-size8.size1 { - font-size: 0.28901734em; -} -.katex .sizing.reset-size8.size2, -.katex .fontsize-ensurer.reset-size8.size2 { - font-size: 0.40462428em; -} -.katex .sizing.reset-size8.size3, -.katex .fontsize-ensurer.reset-size8.size3 { - font-size: 0.46242775em; -} -.katex .sizing.reset-size8.size4, -.katex .fontsize-ensurer.reset-size8.size4 { - font-size: 0.52023121em; -} -.katex .sizing.reset-size8.size5, -.katex .fontsize-ensurer.reset-size8.size5 { - font-size: 0.57803468em; -} -.katex .sizing.reset-size8.size6, -.katex .fontsize-ensurer.reset-size8.size6 { - font-size: 0.69364162em; -} -.katex .sizing.reset-size8.size7, -.katex .fontsize-ensurer.reset-size8.size7 { - font-size: 0.83236994em; -} -.katex .sizing.reset-size8.size8, -.katex .fontsize-ensurer.reset-size8.size8 { - font-size: 1em; -} -.katex .sizing.reset-size8.size9, -.katex .fontsize-ensurer.reset-size8.size9 { - font-size: 1.19653179em; -} -.katex .sizing.reset-size8.size10, -.katex .fontsize-ensurer.reset-size8.size10 { - font-size: 1.43930636em; -} -.katex .sizing.reset-size9.size1, -.katex .fontsize-ensurer.reset-size9.size1 { - font-size: 0.24154589em; -} -.katex .sizing.reset-size9.size2, -.katex .fontsize-ensurer.reset-size9.size2 { - font-size: 0.33816425em; -} -.katex .sizing.reset-size9.size3, -.katex .fontsize-ensurer.reset-size9.size3 { - font-size: 0.38647343em; -} -.katex .sizing.reset-size9.size4, -.katex .fontsize-ensurer.reset-size9.size4 { - font-size: 0.43478261em; -} -.katex .sizing.reset-size9.size5, -.katex .fontsize-ensurer.reset-size9.size5 { - font-size: 0.48309179em; -} -.katex .sizing.reset-size9.size6, -.katex .fontsize-ensurer.reset-size9.size6 { - font-size: 0.57971014em; -} -.katex .sizing.reset-size9.size7, -.katex .fontsize-ensurer.reset-size9.size7 { - font-size: 0.69565217em; -} -.katex .sizing.reset-size9.size8, -.katex .fontsize-ensurer.reset-size9.size8 { - font-size: 0.83574879em; -} -.katex .sizing.reset-size9.size9, -.katex .fontsize-ensurer.reset-size9.size9 { - font-size: 1em; -} -.katex .sizing.reset-size9.size10, -.katex .fontsize-ensurer.reset-size9.size10 { - font-size: 1.20289855em; -} -.katex .sizing.reset-size10.size1, -.katex .fontsize-ensurer.reset-size10.size1 { - font-size: 0.20080321em; -} -.katex .sizing.reset-size10.size2, -.katex .fontsize-ensurer.reset-size10.size2 { - font-size: 0.2811245em; -} -.katex .sizing.reset-size10.size3, -.katex .fontsize-ensurer.reset-size10.size3 { - font-size: 0.32128514em; -} -.katex .sizing.reset-size10.size4, -.katex .fontsize-ensurer.reset-size10.size4 { - font-size: 0.36144578em; -} -.katex .sizing.reset-size10.size5, -.katex .fontsize-ensurer.reset-size10.size5 { - font-size: 0.40160643em; -} -.katex .sizing.reset-size10.size6, -.katex .fontsize-ensurer.reset-size10.size6 { - font-size: 0.48192771em; -} -.katex .sizing.reset-size10.size7, -.katex .fontsize-ensurer.reset-size10.size7 { - font-size: 0.57831325em; -} -.katex .sizing.reset-size10.size8, -.katex .fontsize-ensurer.reset-size10.size8 { - font-size: 0.69477912em; -} -.katex .sizing.reset-size10.size9, -.katex .fontsize-ensurer.reset-size10.size9 { - font-size: 0.8313253em; -} -.katex .sizing.reset-size10.size10, -.katex .fontsize-ensurer.reset-size10.size10 { - font-size: 1em; -} -.katex .delimsizing.size1 { - font-family: KaTeX_Size1; -} -.katex .delimsizing.size2 { - font-family: KaTeX_Size2; -} -.katex .delimsizing.size3 { - font-family: KaTeX_Size3; -} -.katex .delimsizing.size4 { - font-family: KaTeX_Size4; -} -.katex .delimsizing.mult .delim-size1 > span { - font-family: KaTeX_Size1; -} -.katex .delimsizing.mult .delim-size4 > span { - font-family: KaTeX_Size4; -} -.katex .nulldelimiter { - display: inline-block; - width: 0.12em; -} -.katex .op-symbol { - position: relative; -} -.katex .op-symbol.small-op { - font-family: KaTeX_Size1; -} -.katex .op-symbol.large-op { - font-family: KaTeX_Size2; -} -.katex .op-limits > .vlist > span { - text-align: center; -} -.katex .accent > .vlist > span { - text-align: center; -} -.katex .accent .accent-body > span { - width: 0; -} -.katex .accent .accent-body.accent-vec > span { - position: relative; - left: 0.326em; -} -.katex .mtable .vertical-separator { - display: inline-block; - margin: 0 -0.025em; - border-right: 0.05em solid black; -} -.katex .mtable .arraycolsep { - display: inline-block; -} -.katex .mtable .col-align-c > .vlist { - text-align: center; -} -.katex .mtable .col-align-l > .vlist { - text-align: left; -} -.katex .mtable .col-align-r > .vlist { - text-align: right; -} diff --git a/vendor/gitignore/Android.gitignore b/vendor/gitignore/Android.gitignore index addf405e4f5..d57137223ed 100644 --- a/vendor/gitignore/Android.gitignore +++ b/vendor/gitignore/Android.gitignore @@ -54,3 +54,10 @@ google-services.json freeline.py freeline/ freeline_project_description.json + +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md diff --git a/vendor/gitignore/Dart.gitignore b/vendor/gitignore/Dart.gitignore index 4d2a4d6db7c..58950beb4fa 100644 --- a/vendor/gitignore/Dart.gitignore +++ b/vendor/gitignore/Dart.gitignore @@ -1,6 +1,7 @@ # See https://www.dartlang.org/tools/private-files.html # Files and directories created by pub +.dart_tool/ .packages .pub/ build/ diff --git a/vendor/gitignore/Global/JetBrains.gitignore b/vendor/gitignore/Global/JetBrains.gitignore index a30eacf1d98..9c01e12b050 100644 --- a/vendor/gitignore/Global/JetBrains.gitignore +++ b/vendor/gitignore/Global/JetBrains.gitignore @@ -1,4 +1,4 @@ -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 # User-specific stuff: @@ -9,7 +9,6 @@ # Sensitive or high-churn files: .idea/**/dataSources/ .idea/**/dataSources.ids -.idea/**/dataSources.xml .idea/**/dataSources.local.xml .idea/**/sqlDataSources.xml .idea/**/dynamic.xml diff --git a/vendor/gitignore/Python.gitignore b/vendor/gitignore/Python.gitignore index af2f537516d..b989be6ca15 100644 --- a/vendor/gitignore/Python.gitignore +++ b/vendor/gitignore/Python.gitignore @@ -45,6 +45,7 @@ nosetests.xml coverage.xml *.cover .hypothesis/ +.pytest_cache/ # Translations *.mo diff --git a/vendor/gitignore/ROS.gitignore b/vendor/gitignore/ROS.gitignore index 425641f2c3a..35d74bb771f 100644 --- a/vendor/gitignore/ROS.gitignore +++ b/vendor/gitignore/ROS.gitignore @@ -13,6 +13,8 @@ msg/*Feedback.msg msg/*Goal.msg msg/*Result.msg msg/_*.py +build_isolated/ +devel_isolated/ # Generated by dynamic reconfigure *.cfgc diff --git a/vendor/gitignore/TeX.gitignore b/vendor/gitignore/TeX.gitignore index 9bb63365618..5359e544bcf 100644 --- a/vendor/gitignore/TeX.gitignore +++ b/vendor/gitignore/TeX.gitignore @@ -10,6 +10,7 @@ *.fot *.cb *.cb2 +.*.lb ## Intermediate documents: *.dvi diff --git a/vendor/gitignore/VisualStudio.gitignore b/vendor/gitignore/VisualStudio.gitignore index d3d5371b415..c49041ff7d2 100644 --- a/vendor/gitignore/VisualStudio.gitignore +++ b/vendor/gitignore/VisualStudio.gitignore @@ -237,6 +237,7 @@ _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm +ServiceFabricBackup/ # SQL Server files *.mdf diff --git a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml index a7cd2bc972c..0ac8885405e 100644 --- a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml @@ -103,18 +103,22 @@ performance: artifacts: paths: - performance.json + - sitespeed-results/ only: refs: - branches kubernetes: active sast: - image: registry.gitlab.com/gitlab-org/gl-sast:latest + image: docker:latest variables: - POSTGRES_DB: "false" + DOCKER_DRIVER: overlay2 allow_failure: true + services: + - docker:dind script: - - sast . + - setup_docker + - sast artifacts: paths: [gl-sast-report.json] @@ -284,6 +288,12 @@ production: export TILLER_NAMESPACE=$KUBE_NAMESPACE function sast_container() { + if [[ -n "$CI_REGISTRY_USER" ]]; then + echo "Logging to GitLab Container Registry with CI credentials..." + docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY" + echo "" + fi + docker run -d --name db arminc/clair-db:latest docker run -p 6060:6060 --link db:postgres -d --name clair arminc/clair-local-scan:v2.0.1 apk add -U wget ca-certificates @@ -308,7 +318,20 @@ production: function sast() { case "$CI_SERVER_VERSION" in *-ee) - /app/bin/run "$@" + # Extract "MAJOR.MINOR" from CI_SERVER_VERSION and generate "MAJOR-MINOR-stable" + SAST_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/') + + # Deprecation notice for CONFIDENCE_LEVEL variable + if [ -z "$SAST_CONFIDENCE_LEVEL" -a "$CONFIDENCE_LEVEL" ]; then + SAST_CONFIDENCE_LEVEL="$CONFIDENCE_LEVEL" + echo "WARNING: CONFIDENCE_LEVEL is deprecated and MUST be replaced with SAST_CONFIDENCE_LEVEL" + fi + + docker run --env SAST_CONFIDENCE_LEVEL="${SAST_CONFIDENCE_LEVEL:-3}" \ + --env SAST_DISABLE_REMOTE_CHECKS="${SAST_DISABLE_REMOTE_CHECKS:-false}" \ + --volume "$PWD:/code" \ + --volume /var/run/docker.sock:/var/run/docker.sock \ + "registry.gitlab.com/gitlab-org/security-products/sast:$SAST_VERSION" /app/bin/run /code ;; *) echo "GitLab EE is required" @@ -345,6 +368,12 @@ production: replicas="$new_replicas" fi + if [[ "$CI_PROJECT_VISIBILITY" != "public" ]]; then + secret_name='gitlab-registry' + else + secret_name='' + fi + helm upgrade --install \ --wait \ --set service.enabled="$service_enabled" \ @@ -352,6 +381,7 @@ production: --set image.repository="$CI_APPLICATION_REPOSITORY" \ --set image.tag="$CI_APPLICATION_TAG" \ --set image.pullPolicy=IfNotPresent \ + --set image.secrets[0].name="$secret_name" \ --set application.track="$track" \ --set application.database_url="$DATABASE_URL" \ --set service.url="$CI_ENVIRONMENT_URL" \ @@ -481,6 +511,9 @@ production: function create_secret() { echo "Create secret..." + if [[ "$CI_PROJECT_VISIBILITY" == "public" ]]; then + return + fi kubectl create secret -n "$KUBE_NAMESPACE" \ docker-registry gitlab-registry \ @@ -503,16 +536,16 @@ production: export CI_ENVIRONMENT_URL=$(cat environment_url.txt) mkdir gitlab-exporter - wget -O gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/10-3/index.js + wget -O gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/10-5/index.js mkdir sitespeed-results if [ -f .gitlab-urls.txt ] then sed -i -e 's@^@'"$CI_ENVIRONMENT_URL"'@' .gitlab-urls.txt - docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:6.0.3 --plugins.add ./gitlab-exporter --outputFolder sitespeed-results .gitlab-urls.txt + docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:6.3.1 --plugins.add ./gitlab-exporter --outputFolder sitespeed-results .gitlab-urls.txt else - docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:6.0.3 --plugins.add ./gitlab-exporter --outputFolder sitespeed-results "$CI_ENVIRONMENT_URL" + docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:6.3.1 --plugins.add ./gitlab-exporter --outputFolder sitespeed-results "$CI_ENVIRONMENT_URL" fi mv sitespeed-results/data/performance.json performance.json diff --git a/vendor/ingress/values.yaml b/vendor/ingress/values.yaml index cdb7da77e86..a6b499953bf 100644 --- a/vendor/ingress/values.yaml +++ b/vendor/ingress/values.yaml @@ -2,7 +2,8 @@ controller: image: tag: "0.10.2" repository: "quay.io/kubernetes-ingress-controller/nginx-ingress-controller" - stats.enabled: true + stats: + enabled: true podAnnotations: prometheus.io/scrape: "true" prometheus.io/port: "10254" diff --git a/vendor/licenses.csv b/vendor/licenses.csv index e3ccf080f74..8f127468e25 100644 --- a/vendor/licenses.csv +++ b/vendor/licenses.csv @@ -1,5 +1,22 @@ +@babel/code-frame,7.0.0-beta.32,MIT +@babel/helper-function-name,7.0.0-beta.32,MIT +@babel/helper-get-function-arity,7.0.0-beta.32,MIT +@babel/template,7.0.0-beta.32,MIT +@babel/traverse,7.0.0-beta.32,MIT +@babel/types,7.0.0-beta.32,MIT +@gitlab-org/gitlab-svgs,1.8.0,SEE LICENSE IN LICENSE +@types/jquery,2.0.48,MIT +JSONStream,1.3.2,MIT RedCloth,4.3.2,MIT +abbrev,1.0.9,ISC +accepts,1.3.3,MIT ace-rails-ap,4.1.2,MIT +acorn,3.3.0,MIT +acorn,4.0.13,MIT +acorn,5.1.1,MIT +acorn,5.3.0,MIT +acorn-dynamic-import,2.0.2,MIT +acorn-jsx,3.0.1,MIT actionmailer,4.2.10,MIT actionpack,4.2.10,MIT actionview,4.2.10,MIT @@ -9,70 +26,518 @@ activerecord,4.2.10,MIT activesupport,4.2.10,MIT acts-as-taggable-on,4.0.0,MIT addressable,2.5.2,Apache 2.0 +addressparser,1.0.1,MIT +after,0.8.2,MIT +agent-base,2.1.1,MIT +ajv,4.11.8,MIT +ajv,5.2.2,MIT +ajv,5.5.2,MIT +ajv-keywords,1.5.1,MIT +ajv-keywords,2.1.0,MIT akismet,2.0.0,MIT +align-text,0.1.4,MIT allocations,1.0.5,MIT +alphanum-sort,1.0.2,MIT +amdefine,1.0.1,BSD-3-Clause OR MIT +ansi-escapes,1.4.0,MIT +ansi-html,0.0.5,"Apache, Version 2.0" +ansi-html,0.0.7,Apache 2.0 +ansi-regex,2.1.1,MIT +ansi-regex,3.0.0,MIT +ansi-styles,2.2.1,MIT +ansi-styles,3.2.0,MIT +anymatch,1.3.2,ISC +append-transform,0.4.0,MIT +aproba,1.1.2,ISC +are-we-there-yet,1.1.4,ISC arel,6.0.4,MIT +argparse,1.0.9,MIT +arr-diff,2.0.0,MIT +arr-flatten,1.0.1,MIT +array-filter,0.0.1,MIT +array-find,1.0.0,MIT +array-find-index,1.0.2,MIT +array-flatten,1.1.1,MIT +array-flatten,2.1.1,MIT +array-map,0.0.0,MIT +array-reduce,0.0.0,MIT +array-slice,0.2.3,MIT +array-union,1.0.2,MIT +array-uniq,1.0.3,MIT +array-unique,0.2.1,MIT +arraybuffer.slice,0.0.7,MIT +arrify,1.0.1,MIT asana,0.6.0,MIT asciidoctor,1.5.3,MIT asciidoctor-plantuml,0.0.7,MIT +asn1,0.2.3,MIT +asn1.js,4.9.1,MIT +assert,1.4.1,MIT +assert-plus,0.2.0,MIT +assert-plus,1.0.0,MIT asset_sync,2.2.0,MIT +ast-types,0.10.1,MIT +astw,2.2.0,MIT +async,0.9.2,MIT +async,1.5.2,MIT +async,2.1.5,MIT +async,2.4.1,MIT +async-each,1.0.1,MIT +async-limiter,1.0.0,MIT +asynckit,0.4.0,MIT atomic,1.1.99,Apache 2.0 attr_encrypted,3.0.3,MIT attr_required,1.0.0,MIT +autoprefixer,6.7.7,MIT autoprefixer-rails,6.2.3,MIT +autosize,4.0.0,MIT +aws-sign2,0.6.0,Apache 2.0 +aws-sign2,0.7.0,Apache 2.0 +aws4,1.6.0,MIT axiom-types,0.1.1,MIT +axios,0.15.3,MIT +axios,0.17.1,MIT +axios-mock-adapter,1.10.0,MIT +babel-code-frame,6.26.0,MIT +babel-core,6.26.0,MIT +babel-eslint,8.0.2,MIT +babel-generator,6.26.0,MIT +babel-helper-bindify-decorators,6.24.1,MIT +babel-helper-builder-binary-assignment-operator-visitor,6.24.1,MIT +babel-helper-call-delegate,6.24.1,MIT +babel-helper-define-map,6.26.0,MIT +babel-helper-explode-assignable-expression,6.24.1,MIT +babel-helper-explode-class,6.24.1,MIT +babel-helper-function-name,6.24.1,MIT +babel-helper-get-function-arity,6.24.1,MIT +babel-helper-hoist-variables,6.24.1,MIT +babel-helper-optimise-call-expression,6.24.1,MIT +babel-helper-regex,6.26.0,MIT +babel-helper-remap-async-to-generator,6.24.1,MIT +babel-helper-replace-supers,6.24.1,MIT +babel-helpers,6.24.1,MIT +babel-loader,7.1.2,MIT +babel-messages,6.23.0,MIT +babel-plugin-check-es2015-constants,6.22.0,MIT +babel-plugin-istanbul,4.1.5,New BSD +babel-plugin-syntax-async-functions,6.13.0,MIT +babel-plugin-syntax-async-generators,6.13.0,MIT +babel-plugin-syntax-class-properties,6.13.0,MIT +babel-plugin-syntax-decorators,6.13.0,MIT +babel-plugin-syntax-dynamic-import,6.18.0,MIT +babel-plugin-syntax-exponentiation-operator,6.13.0,MIT +babel-plugin-syntax-object-rest-spread,6.13.0,MIT +babel-plugin-syntax-trailing-function-commas,6.22.0,MIT +babel-plugin-transform-async-generator-functions,6.24.1,MIT +babel-plugin-transform-async-to-generator,6.24.1,MIT +babel-plugin-transform-class-properties,6.24.1,MIT +babel-plugin-transform-decorators,6.24.1,MIT +babel-plugin-transform-define,1.3.0,MIT +babel-plugin-transform-es2015-arrow-functions,6.22.0,MIT +babel-plugin-transform-es2015-block-scoped-functions,6.22.0,MIT +babel-plugin-transform-es2015-block-scoping,6.26.0,MIT +babel-plugin-transform-es2015-classes,6.24.1,MIT +babel-plugin-transform-es2015-computed-properties,6.24.1,MIT +babel-plugin-transform-es2015-destructuring,6.23.0,MIT +babel-plugin-transform-es2015-duplicate-keys,6.24.1,MIT +babel-plugin-transform-es2015-for-of,6.23.0,MIT +babel-plugin-transform-es2015-function-name,6.24.1,MIT +babel-plugin-transform-es2015-literals,6.22.0,MIT +babel-plugin-transform-es2015-modules-amd,6.24.1,MIT +babel-plugin-transform-es2015-modules-commonjs,6.26.0,MIT +babel-plugin-transform-es2015-modules-systemjs,6.24.1,MIT +babel-plugin-transform-es2015-modules-umd,6.24.1,MIT +babel-plugin-transform-es2015-object-super,6.24.1,MIT +babel-plugin-transform-es2015-parameters,6.24.1,MIT +babel-plugin-transform-es2015-shorthand-properties,6.24.1,MIT +babel-plugin-transform-es2015-spread,6.22.0,MIT +babel-plugin-transform-es2015-sticky-regex,6.24.1,MIT +babel-plugin-transform-es2015-template-literals,6.22.0,MIT +babel-plugin-transform-es2015-typeof-symbol,6.23.0,MIT +babel-plugin-transform-es2015-unicode-regex,6.24.1,MIT +babel-plugin-transform-exponentiation-operator,6.24.1,MIT +babel-plugin-transform-object-rest-spread,6.23.0,MIT +babel-plugin-transform-regenerator,6.26.0,MIT +babel-plugin-transform-strict-mode,6.24.1,MIT +babel-preset-es2015,6.24.1,MIT +babel-preset-es2016,6.24.1,MIT +babel-preset-es2017,6.24.1,MIT +babel-preset-latest,6.24.1,MIT +babel-preset-stage-2,6.24.1,MIT +babel-preset-stage-3,6.24.1,MIT +babel-register,6.26.0,MIT +babel-runtime,6.26.0,MIT +babel-template,6.26.0,MIT +babel-traverse,6.26.0,MIT +babel-types,6.26.0,MIT babosa,1.0.2,MIT +babylon,6.18.0,MIT +babylon,7.0.0-beta.32,MIT +backo2,1.0.2,MIT +balanced-match,0.4.2,MIT +balanced-match,1.0.0,MIT base32,0.3.2,MIT +base64-arraybuffer,0.1.5,MIT +base64-js,1.2.0,MIT +base64id,1.0.0,MIT +batch,0.6.1,MIT batch-loader,1.2.1,MIT bcrypt,3.1.11,MIT +bcrypt-pbkdf,1.0.1,New BSD bcrypt_pbkdf,1.0.0,MIT +better-assert,1.0.2,MIT +big.js,3.1.3,MIT +binary-extensions,1.10.0,MIT bindata,2.4.1,ruby +bl,1.1.2,MIT +blackst0ne-mermaid,7.1.0-fixed,MIT +blob,0.0.4,MIT* +block-stream,0.0.9,ISC +bluebird,2.11.0,MIT +bluebird,3.5.0,MIT +bn.js,4.11.6,MIT +body-parser,1.17.2,MIT +bonjour,3.5.0,MIT +boom,2.10.1,New BSD +boom,4.3.1,New BSD +boom,5.2.0,New BSD bootstrap-sass,3.3.6,MIT bootstrap_form,2.7.0,MIT +brace-expansion,1.1.8,MIT +braces,0.1.5,MIT +braces,1.8.5,MIT +brorand,1.0.7,MIT browser,2.2.0,MIT +browser-pack,6.0.2,MIT +browser-resolve,1.11.2,MIT +browserify,14.5.0,MIT +browserify-aes,1.0.6,MIT +browserify-cipher,1.0.0,MIT +browserify-des,1.0.0,MIT +browserify-rsa,4.0.1,MIT +browserify-sign,4.0.0,ISC +browserify-zlib,0.1.4,MIT +browserify-zlib,0.2.0,MIT +browserslist,1.7.7,MIT +buffer,4.9.1,MIT +buffer,5.0.8,MIT +buffer-indexof,1.1.0,MIT +buffer-xor,1.0.3,MIT builder,3.2.3,MIT +buildmail,4.0.1,MIT +builtin-modules,1.1.1,MIT +builtin-status-codes,3.0.0,MIT +bytes,2.4.0,MIT +bytes,2.5.0,MIT +bytes,3.0.0,MIT +cached-path-relative,1.0.1,MIT +caller-path,0.1.0,MIT +callsite,1.0.0,MIT* +callsites,0.2.0,MIT +camelcase,1.2.1,MIT +camelcase,2.1.1,MIT +camelcase,3.0.0,MIT +camelcase,4.1.0,MIT +camelcase-keys,2.1.0,MIT +caniuse-api,1.6.1,MIT +caniuse-db,1.0.30000649,CC-BY-4.0 carrierwave,1.2.1,MIT +caseless,0.11.0,Apache 2.0 +caseless,0.12.0,Apache 2.0 cause,0.1,MIT +center-align,0.1.3,MIT +chalk,1.1.3,MIT +chalk,2.3.0,MIT charlock_holmes,0.7.5,MIT +chokidar,1.7.0,MIT chronic,0.10.2,MIT chronic_duration,0.10.6,MIT chunky_png,1.3.5,MIT +cipher-base,1.0.3,MIT +circular-json,0.3.3,MIT +circular-json,0.4.0,MIT citrus,3.0.2,MIT +clap,1.1.3,MIT +classlist-polyfill,1.2.0,Unlicense +cli-cursor,1.0.2,MIT +cli-width,2.1.0,ISC +clipboard,1.7.1,MIT +cliui,2.1.0,ISC +cliui,3.2.0,ISC +clone,1.0.2,MIT +co,3.0.6,MIT +co,4.6.0,MIT +coa,1.0.1,MIT +code-point-at,1.1.0,MIT coercible,1.0.0,MIT +color,0.11.4,MIT +color-convert,1.9.1,MIT +color-name,1.1.2,MIT +color-string,0.3.0,MIT +colormin,1.1.2,MIT +colors,1.1.2,MIT +combine-lists,1.0.1,MIT +combine-source-map,0.7.2,MIT +combined-stream,1.0.5,MIT +commander,2.9.0,MIT +commondir,1.0.1,MIT +component-bind,1.0.0,MIT* +component-emitter,1.2.1,MIT +component-inherit,0.0.3,MIT* +compressible,2.0.11,MIT +compression,1.7.0,MIT +compression-webpack-plugin,1.0.0,MIT +concat-map,0.0.1,MIT +concat-stream,1.5.2,MIT +concat-stream,1.6.0,MIT concurrent-ruby-ext,1.0.5,MIT +configstore,1.4.0,Simplified BSD +connect,3.6.3,MIT +connect-history-api-fallback,1.3.0,MIT connection_pool,2.2.1,MIT +console-browserify,1.1.0,MIT +console-control-strings,1.1.0,ISC +consolidate,0.14.5,MIT +constants-browserify,1.0.0,MIT +contains-path,0.1.0,MIT +content-disposition,0.5.2,MIT +content-type,1.0.2,MIT +convert-source-map,1.1.3,MIT +convert-source-map,1.5.0,MIT +cookie,0.3.1,MIT +cookie-signature,1.0.6,MIT +copy-webpack-plugin,4.0.1,MIT +core-js,2.3.0,MIT +core-js,2.4.1,MIT +core-js,2.5.1,MIT +core-util-is,1.0.2,MIT +cosmiconfig,2.1.1,MIT crack,0.4.3,MIT +create-ecdh,4.0.0,MIT +create-hash,1.1.2,MIT +create-hmac,1.1.4,MIT creole,0.5.0,ruby +cropper,2.3.0,MIT +cross-spawn,5.1.0,MIT +cryptiles,2.0.5,New BSD +cryptiles,3.1.2,New BSD +crypto-browserify,3.11.0,MIT +crypto-browserify,3.12.0,MIT +css-color-names,0.0.4,MIT +css-loader,0.28.0,MIT +css-selector-tokenizer,0.6.0,MIT +css-selector-tokenizer,0.7.0,MIT css_parser,1.5.0,MIT +cssesc,0.1.0,MIT +cssnano,3.10.0,MIT +csso,2.3.2,MIT +currently-unhandled,0.4.1,MIT +custom-event,1.0.1,MIT +d,0.1.1,MIT +d,1.0.0,MIT +d3,3.5.17,New BSD +d3-array,1.2.1,New BSD +d3-axis,1.0.8,New BSD +d3-brush,1.0.4,New BSD +d3-collection,1.0.4,New BSD +d3-color,1.0.3,New BSD +d3-dispatch,1.0.3,New BSD +d3-drag,1.2.1,New BSD +d3-ease,1.0.3,New BSD +d3-format,1.2.1,New BSD +d3-interpolate,1.1.6,New BSD +d3-path,1.0.5,New BSD +d3-scale,1.0.7,New BSD +d3-selection,1.2.0,New BSD +d3-shape,1.2.0,New BSD +d3-time,1.0.8,New BSD +d3-time-format,2.1.1,New BSD +d3-timer,1.0.7,New BSD +d3-transition,1.1.1,New BSD d3_rails,3.5.11,MIT +dagre-d3-renderer,0.4.24,MIT +dagre-layout,0.8.0,MIT +dashdash,1.14.1,MIT +data-uri-to-buffer,1.2.0,MIT +date-format,1.2.0,MIT +date-now,0.1.4,MIT +de-indent,1.0.2,MIT +debug,2.2.0,MIT +debug,2.6.7,MIT +debug,2.6.8,MIT +debug,2.6.9,MIT +debug,3.1.0,MIT debugger-ruby_core_source,1.3.8,MIT +decamelize,1.2.0,MIT deckar01-task_list,2.0.0,MIT declarative,0.0.10,MIT declarative-option,0.1.0,MIT +decompress-response,3.3.0,MIT +deep-equal,1.0.1,MIT +deep-extend,0.4.2,MIT +deep-is,0.1.3,MIT +default-require-extensions,1.0.0,MIT default_value_for,3.0.2,MIT +define-properties,1.1.2,MIT +defined,1.0.0,MIT +degenerator,1.0.4,MIT +del,2.2.2,MIT +del,3.0.0,MIT +delayed-stream,1.0.0,MIT +delegate,3.1.2,MIT +delegates,1.0.0,MIT +depd,1.1.0,MIT +depd,1.1.1,MIT +deps-sort,2.0.0,MIT +des.js,1.0.0,MIT descendants_tracker,0.0.4,MIT +destroy,1.0.4,MIT +detect-indent,4.0.0,MIT +detect-node,2.0.3,ISC +detective,4.7.1,MIT devise,4.2.0,MIT devise-two-factor,3.0.0,MIT +di,0.0.1,MIT +diff,3.4.0,New BSD diff-lcs,1.3,"MIT,Artistic-2.0,GPL-2.0+" +diffie-hellman,5.0.2,MIT diffy,3.1.0,MIT +dns-equal,1.0.0,MIT +dns-packet,1.2.2,MIT +dns-txt,2.0.2,MIT +doctrine,1.5.0,Simplified BSD +doctrine,2.0.0,Apache 2.0 +document-register-element,1.3.0,MIT +dom-serialize,2.2.1,MIT +dom-serializer,0.1.0,MIT +domain-browser,1.1.7,MIT domain_name,0.5.20161021,"Simplified BSD,New BSD,Mozilla Public License 2.0" +domelementtype,1.1.3,Simplified BSD +domelementtype,1.3.0,Simplified BSD +domhandler,2.4.1,Simplified BSD +domutils,1.6.2,Simplified BSD doorkeeper,4.2.6,MIT doorkeeper-openid_connect,1.2.0,MIT +double-ended-queue,2.1.0-0,MIT +dropzone,4.2.0,MIT dropzonejs-rails,0.7.2,MIT +duplexer,0.1.1,MIT +duplexer2,0.1.4,New BSD +duplexer3,0.1.4,New BSD +duplexify,3.5.1,MIT +ecc-jsbn,0.1.1,MIT +ee-first,1.1.1,MIT +ejs,2.5.6,Apache 2.0 +electron-to-chromium,1.3.3,ISC +elliptic,6.3.3,MIT email_reply_trimmer,0.1.6,MIT +emoji-unicode-version,0.2.1,MIT +emojis-list,2.1.0,MIT +encodeurl,1.0.1,MIT encryptor,3.0.0,MIT +end-of-stream,1.4.0,MIT +engine.io,3.1.4,MIT +engine.io-client,3.1.4,MIT +engine.io-parser,2.1.2,MIT +enhanced-resolve,0.9.1,MIT +enhanced-resolve,3.4.1,MIT +ent,2.2.0,MIT +entities,1.1.1,Simplified BSD equalizer,0.0.11,MIT +errno,0.1.4,MIT +error-ex,1.3.0,MIT erubis,2.7.0,MIT +es-abstract,1.8.2,MIT +es-to-primitive,1.1.1,MIT +es5-ext,0.10.24,MIT +es6-iterator,2.0.1,MIT +es6-map,0.1.5,MIT +es6-promise,3.0.2,MIT +es6-set,0.1.5,MIT +es6-symbol,3.1.1,MIT +es6-weak-map,2.0.1,MIT +escape-html,1.0.3,MIT +escape-string-regexp,1.0.5,MIT escape_utils,1.1.1,MIT +escodegen,1.8.1,Simplified BSD +escodegen,1.9.0,Simplified BSD +escope,3.6.0,Simplified BSD +eslint,3.19.0,MIT +eslint-config-airbnb-base,10.0.1,MIT +eslint-import-resolver-node,0.2.3,MIT +eslint-import-resolver-webpack,0.8.3,MIT +eslint-module-utils,2.0.0,MIT +eslint-plugin-filenames,1.1.0,MIT +eslint-plugin-html,2.0.1,ISC +eslint-plugin-import,2.2.0,MIT +eslint-plugin-jasmine,2.2.0,MIT +eslint-plugin-promise,3.5.0,ISC +eslint-plugin-vue,4.0.1,MIT +eslint-scope,3.7.1,Simplified BSD +eslint-visitor-keys,1.0.0,Apache 2.0 +espree,3.5.0,Simplified BSD +espree,3.5.2,Simplified BSD +esprima,2.7.3,Simplified BSD +esprima,3.1.3,Simplified BSD +esprima,4.0.0,Simplified BSD +esquery,1.0.0,New BSD +esrecurse,4.1.0,Simplified BSD +estraverse,1.9.3,Simplified BSD +estraverse,4.1.1,Simplified BSD +estraverse,4.2.0,Simplified BSD +esutils,2.0.2,Simplified BSD et-orbi,1.0.3,MIT +etag,1.8.0,MIT +eve-raphael,0.5.0,Apache 2.0 +event-emitter,0.3.5,MIT +event-stream,3.3.4,MIT +eventemitter3,1.2.0,MIT +events,1.1.1,MIT +eventsource,0.1.6,MIT +evp_bytestokey,1.0.0,MIT excon,0.57.1,MIT +execa,0.7.0,MIT execjs,2.6.0,MIT +exit-hook,1.1.1,MIT +expand-braces,0.1.2,MIT +expand-brackets,0.1.5,MIT +expand-range,0.1.1,MIT +expand-range,1.8.2,MIT +exports-loader,0.6.4,MIT +express,4.15.4,MIT expression_parser,0.9.0,MIT +extend,3.0.1,MIT +extglob,0.3.2,MIT +extsprintf,1.3.0,MIT faraday,0.12.2,MIT faraday_middleware,0.11.0.1,MIT faraday_middleware-multi_json,0.0.6,MIT +fast-deep-equal,1.0.0,MIT +fast-json-stable-stringify,2.0.0,MIT +fast-levenshtein,2.0.6,MIT +fast_blank,1.0.0,MIT fast_gettext,1.4.0,"MIT,ruby" +fastparse,1.1.1,MIT +faye-websocket,0.10.0,MIT +faye-websocket,0.11.1,MIT +faye-websocket,0.7.3,MIT ffi,1.9.18,New BSD +figures,1.7.0,MIT +file-entry-cache,2.0.0,MIT +file-loader,0.11.1,MIT +file-uri-to-path,1.0.0,MIT +filename-regex,2.0.0,MIT +fileset,2.0.3,MIT +filesize,3.3.0,New BSD +filesize,3.5.10,New BSD +fill-range,2.2.3,MIT +finalhandler,1.0.4,MIT +find-cache-dir,1.0.0,MIT +find-root,0.1.2,MIT +find-up,1.1.2,MIT +find-up,2.1.0,MIT +flat-cache,1.2.2,MIT +flatten,1.0.2,MIT flipper,0.11.0,MIT flipper-active_record,0.11.0,MIT flipper-active_support_cache_store,0.11.0,MIT @@ -86,93 +551,452 @@ fog-local,0.3.1,MIT fog-openstack,0.1.21,MIT fog-rackspace,0.1.1,MIT fog-xml,0.1.3,MIT +follow-redirects,1.0.0,MIT +follow-redirects,1.2.6,MIT font-awesome-rails,4.7.0.1,"MIT,SIL Open Font License" +for-each,0.3.2,MIT +for-in,0.1.6,MIT +for-own,0.1.4,MIT +foreach,2.0.5,MIT +forever-agent,0.6.1,Apache 2.0 +form-data,2.0.0,MIT +form-data,2.1.4,MIT +form-data,2.3.1,MIT formatador,0.2.5,MIT +forwarded,0.1.0,MIT +fresh,0.5.0,MIT +from,0.1.7,MIT +fs-access,1.0.1,MIT +fs-extra,0.26.7,MIT +fs.realpath,1.0.0,ISC +fsevents,1.1.2,MIT +fstream,1.0.11,ISC +fstream-ignore,1.0.5,ISC +ftp,0.3.10,MIT +function-bind,1.1.0,MIT +function-bind,1.1.1,MIT +fuzzaldrin-plus,0.5.0,MIT +gauge,2.7.4,ISC gemnasium-gitlab-service,0.2.6,MIT gemojione,3.3.0,MIT +generate-function,2.0.0,MIT +generate-object-property,1.2.0,MIT +get-caller-file,1.0.2,ISC +get-stdin,4.0.1,MIT +get-stream,3.0.0,MIT +get-uri,2.0.1,MIT get_process_mem,0.2.0,MIT +getpass,0.1.7,MIT gettext_i18n_rails,1.8.0,MIT gettext_i18n_rails_js,1.2.0,MIT -gitaly-proto,0.64.0,MIT +gitaly-proto,0.84.0,MIT github-linguist,4.7.6,MIT github-markup,1.6.1,MIT gitlab-flowdock-git-hook,1.0.1,MIT gitlab-grit,2.8.2,MIT gitlab-markup,1.6.3,MIT gitlab_omniauth-ldap,2.0.4,MIT +glob,5.0.15,ISC +glob,6.0.4,ISC +glob,7.1.1,ISC +glob,7.1.2,ISC +glob-base,0.3.0,MIT +glob-parent,2.0.0,ISC globalid,0.4.1,MIT +globals,10.4.0,MIT +globals,9.18.0,MIT +globby,5.0.0,MIT +globby,6.1.0,MIT gollum-grit_adapter,1.0.1,MIT gollum-lib,4.2.7,MIT gollum-rugged_adapter,0.4.4,MIT gon,6.1.0,MIT +good-listener,1.2.2,MIT google-api-client,0.13.6,Apache 2.0 -google-protobuf,3.4.1.1,New BSD +google-protobuf,3.5.1,New BSD +googleapis-common-protos-types,1.0.1,Apache 2.0 googleauth,0.5.3,Apache 2.0 +got,3.3.1,MIT +got,7.1.0,MIT gpgme,2.0.13,LGPL-2.1+ +graceful-fs,4.1.11,ISC +graceful-readlink,1.0.1,MIT grape,1.0.0,MIT grape-entity,0.6.0,MIT grape-route-helpers,2.1.0,MIT grape_logging,1.7.0,MIT -grpc,1.4.5,New BSD +graphlib,2.1.1,MIT +grpc,1.8.3,Apache 2.0 +gzip-size,3.0.0,MIT hamlit,2.6.1,MIT +handle-thing,1.2.5,MIT +handlebars,4.0.6,MIT +har-schema,1.0.5,ISC +har-schema,2.0.0,ISC +har-validator,2.0.6,ISC +har-validator,4.2.1,ISC +har-validator,5.0.3,ISC +has,1.0.1,MIT +has-ansi,2.0.0,MIT +has-binary2,1.0.2,MIT +has-cors,1.1.0,MIT +has-flag,1.0.0,MIT +has-flag,2.0.0,MIT +has-symbol-support-x,1.3.0,MIT +has-to-string-tag-x,1.3.0,MIT +has-unicode,2.0.1,ISC +hash-sum,1.0.2,MIT +hash.js,1.0.3,MIT hashie,3.5.6,MIT hashie-forbidden_attributes,0.1.1,MIT +hawk,3.1.3,New BSD +hawk,6.0.2,New BSD +he,1.1.1,MIT health_check,2.6.0,MIT hipchat,1.5.2,MIT +hipchat-notifier,1.1.0,MIT +hoek,2.16.3,New BSD +hoek,4.2.0,New BSD +home-or-tmp,2.0.0,MIT +hosted-git-info,2.2.0,ISC +hpack.js,2.1.6,MIT +html-comment-regex,1.1.1,MIT +html-entities,1.2.0,MIT html-pipeline,1.11.0,MIT html2text,0.2.0,MIT htmlentities,4.3.4,MIT +htmlescape,1.1.1,MIT +htmlparser2,3.9.2,MIT http,0.9.8,MIT http-cookie,1.0.3,MIT +http-deceiver,1.2.7,MIT +http-errors,1.6.1,MIT +http-errors,1.6.2,MIT http-form_data,1.0.1,MIT +http-proxy,1.16.2,MIT +http-proxy-agent,1.0.0,MIT +http-proxy-middleware,0.17.4,MIT +http-signature,1.1.1,MIT +http-signature,1.2.0,MIT http_parser.rb,0.6.0,MIT httparty,0.13.7,MIT httpclient,2.8.2,ruby +httpntlm,1.6.1,MIT +httpreq,0.4.24,MIT +https-browserify,0.0.1,MIT +https-browserify,1.0.0,MIT +https-proxy-agent,1.0.0,MIT i18n,0.9.1,MIT ice_nine,0.11.2,MIT +iconv-lite,0.4.15,MIT +iconv-lite,0.4.19,MIT +icss-replace-symbols,1.0.2,ISC +ieee754,1.1.8,New BSD +ignore,3.3.3,MIT +ignore-by-default,1.0.1,ISC +immediate,3.0.6,MIT +imports-loader,0.7.1,MIT +imurmurhash,0.1.4,MIT +indent-string,2.1.0,MIT +indexes-of,1.0.1,MIT +indexof,0.0.1,MIT* +infinity-agent,2.0.3,MIT +inflection,1.10.0,MIT +inflection,1.3.8,MIT +inflight,1.0.6,ISC influxdb,0.2.3,MIT +inherits,2.0.1,ISC +inherits,2.0.3,ISC +ini,1.3.4,ISC +inline-source-map,0.6.2,MIT +inquirer,0.12.0,MIT +insert-module-globals,7.0.1,MIT +internal-ip,1.2.0,MIT +interpret,1.0.1,MIT +invariant,2.2.2,New BSD +invert-kv,1.0.0,MIT +ip,1.0.1,MIT +ip,1.1.5,MIT +ipaddr.js,1.4.0,MIT ipaddress,0.8.3,MIT +is-absolute,0.2.6,MIT +is-absolute-url,2.1.0,MIT +is-arrayish,0.2.1,MIT +is-binary-path,1.0.1,MIT +is-buffer,1.1.5,MIT +is-buffer,1.1.6,MIT +is-builtin-module,1.0.0,MIT +is-callable,1.1.3,MIT +is-date-object,1.0.1,MIT +is-dotfile,1.0.2,MIT +is-equal-shallow,0.1.3,MIT +is-extendable,0.1.1,MIT +is-extglob,1.0.0,MIT +is-extglob,2.1.1,MIT +is-finite,1.0.2,MIT +is-fullwidth-code-point,1.0.0,MIT +is-fullwidth-code-point,2.0.0,MIT +is-function,1.0.1,MIT +is-glob,2.0.1,MIT +is-glob,3.1.0,MIT +is-my-json-valid,2.16.0,MIT +is-my-json-valid,2.17.1,MIT +is-npm,1.0.0,MIT +is-number,0.1.1,MIT +is-number,2.1.0,MIT +is-object,1.0.1,MIT +is-path-cwd,1.0.0,MIT +is-path-in-cwd,1.0.0,MIT +is-path-inside,1.0.0,MIT +is-plain-obj,1.1.0,MIT +is-posix-bracket,0.1.1,MIT +is-primitive,2.0.0,MIT +is-property,1.0.2,MIT +is-redirect,1.0.0,MIT +is-regex,1.0.4,MIT +is-relative,0.2.1,MIT +is-resolvable,1.0.0,MIT +is-retry-allowed,1.1.0,MIT +is-stream,1.1.0,MIT +is-svg,2.1.0,MIT +is-symbol,1.0.1,MIT +is-typedarray,1.0.0,MIT +is-unc-path,0.1.2,MIT +is-utf8,0.2.1,MIT +is-windows,0.2.0,MIT +isarray,0.0.1,MIT +isarray,1.0.0,MIT +isarray,2.0.1,MIT +isbinaryfile,3.0.2,MIT +isexe,1.1.2,ISC +isobject,2.1.0,MIT +isstream,0.1.2,MIT +istanbul,0.4.5,New BSD +istanbul-api,1.2.1,New BSD +istanbul-lib-coverage,1.1.1,New BSD +istanbul-lib-hook,1.1.0,New BSD +istanbul-lib-instrument,1.9.1,New BSD +istanbul-lib-report,1.1.2,New BSD +istanbul-lib-source-maps,1.2.2,New BSD +istanbul-reports,1.1.3,New BSD +isurl,1.0.0,MIT +jasmine-core,2.9.0,MIT +jasmine-jquery,2.1.1,MIT +jed,1.1.1,MIT jira-ruby,1.4.1,MIT +jquery,2.2.4,MIT jquery-atwho-rails,1.3.2,MIT jquery-rails,4.3.1,MIT +jquery-ujs,1.2.2,MIT +js-base64,2.1.9,New BSD +js-cookie,2.1.3,MIT +js-tokens,3.0.2,MIT +js-yaml,3.7.0,MIT +js-yaml,3.9.1,MIT +jsbn,0.1.1,MIT +jsesc,0.5.0,MIT +jsesc,1.3.0,MIT json,1.8.6,ruby json-jwt,1.7.2,MIT +json-loader,0.5.7,MIT +json-schema,0.2.3,BSD +json-schema-traverse,0.3.1,MIT +json-stable-stringify,0.0.1,MIT +json-stable-stringify,1.0.1,MIT +json-stringify-safe,5.0.1,ISC +json3,3.3.2,MIT +json5,0.5.1,MIT +jsonfile,2.4.0,MIT +jsonify,0.0.0,Public Domain +jsonparse,1.3.1,MIT +jsonpointer,4.0.1,MIT +jsprim,1.4.1,MIT +jszip,3.1.3,(MIT OR GPL-3.0) +jszip-utils,0.0.2,MIT or GPLv3 jwt,1.5.6,MIT kaminari,1.0.1,MIT kaminari-actionview,1.0.1,MIT kaminari-activerecord,1.0.1,MIT kaminari-core,1.0.1,MIT +karma,2.0.0,MIT +karma-chrome-launcher,2.2.0,MIT +karma-coverage-istanbul-reporter,1.3.3,MIT +karma-jasmine,1.1.1,MIT +karma-mocha-reporter,2.2.5,MIT +karma-sourcemap-loader,0.3.7,MIT +karma-webpack,2.0.7,MIT kgio,2.10.0,LGPL-2.1+ +kind-of,3.1.0,MIT +klaw,1.3.1,MIT kubeclient,2.2.0,MIT +labeled-stream-splicer,2.0.0,MIT +latest-version,1.0.1,MIT +lazy-cache,1.0.4,MIT +lcid,1.0.0,MIT +levn,0.3.0,MIT +lexical-scope,1.2.0,MIT +libbase64,0.1.0,MIT +libmime,3.0.0,MIT +libqp,1.1.0,MIT licensee,8.7.0,MIT +lie,3.1.1,MIT little-plugger,1.1.4,MIT +load-json-file,1.1.0,MIT +load-json-file,2.0.0,MIT +loader-runner,2.3.0,MIT +loader-utils,0.2.16,MIT +loader-utils,1.1.0,MIT locale,2.1.2,"ruby,LGPLv3+" +locate-path,2.0.0,MIT +lodash,3.10.1,MIT +lodash,4.17.4,MIT +lodash._baseassign,3.2.0,MIT +lodash._basecopy,3.0.1,MIT +lodash._baseget,3.7.2,MIT +lodash._bindcallback,3.0.1,MIT +lodash._createassigner,3.1.1,MIT +lodash._getnative,3.9.1,MIT +lodash._isiterateecall,3.0.9,MIT +lodash._topath,3.8.1,MIT +lodash.assign,3.2.0,MIT +lodash.camelcase,4.1.1,MIT +lodash.camelcase,4.3.0,MIT +lodash.capitalize,4.2.1,MIT +lodash.clonedeep,4.5.0,MIT +lodash.cond,4.5.2,MIT +lodash.deburr,4.1.0,MIT +lodash.defaults,3.1.2,MIT +lodash.escaperegexp,4.1.2,MIT +lodash.get,3.7.0,MIT +lodash.isarguments,3.1.0,MIT +lodash.isarray,3.0.4,MIT +lodash.kebabcase,4.0.1,MIT +lodash.keys,3.1.2,MIT +lodash.memoize,3.0.4,MIT +lodash.memoize,4.1.2,MIT +lodash.mergewith,4.6.0,MIT +lodash.restparam,3.6.1,MIT +lodash.snakecase,4.0.1,MIT +lodash.uniq,4.5.0,MIT +lodash.words,4.2.0,MIT +log-symbols,2.1.0,MIT +log4js,2.4.1,Apache 2.0 logging,2.2.2,MIT +loggly,1.1.1,MIT +loglevel,1.4.1,MIT lograge,0.5.1,MIT +longest,1.0.1,MIT loofah,2.0.3,MIT +loose-envify,1.3.1,MIT +loud-rejection,1.6.0,MIT +lowercase-keys,1.0.0,MIT +lru-cache,2.2.4,MIT +lru-cache,2.6.5,ISC +lru-cache,4.1.1,ISC +macaddress,0.2.8,MIT mail,2.7.0,MIT mail_room,0.9.1,MIT +mailcomposer,4.0.1,MIT +mailgun-js,0.7.15,MIT +make-dir,1.0.0,MIT +map-obj,1.0.1,MIT +map-stream,0.1.0,Unknown +marked,0.3.12,MIT +math-expression-evaluator,1.2.16,MIT +media-typer,0.3.0,MIT +mem,1.1.0,MIT memoist,0.16.0,MIT +memory-fs,0.2.0,MIT +memory-fs,0.4.1,MIT +meow,3.7.0,MIT +merge-descriptors,1.0.1,MIT method_source,0.8.2,MIT +methods,1.1.2,MIT +micromatch,2.3.11,MIT +miller-rabin,4.0.0,MIT +mime,1.3.4,MIT +mime,1.6.0,MIT +mime-db,1.27.0,MIT +mime-db,1.29.0,MIT +mime-db,1.30.0,MIT +mime-types,2.1.15,MIT +mime-types,2.1.17,MIT mime-types,3.1,MIT mime-types-data,3.2016.0521,MIT mimemagic,0.3.0,MIT +mimic-fn,1.1.0,MIT +mimic-response,1.0.0,MIT mini_mime,0.1.4,MIT mini_portile2,2.3.0,MIT +minimalistic-assert,1.0.0,ISC +minimatch,3.0.3,ISC +minimatch,3.0.4,ISC +minimist,0.0.8,MIT +minimist,1.2.0,MIT +mkdirp,0.5.1,MIT +module-deps,4.1.1,MIT +moment,2.19.2,MIT +monaco-editor,0.10.0,MIT +mousetrap,1.4.6,Apache 2.0 mousetrap-rails,1.4.6,"MIT,Apache" +ms,0.7.1,MIT +ms,2.0.0,MIT multi_json,1.12.2,MIT multi_xml,0.6.0,MIT +multicast-dns,6.1.1,MIT +multicast-dns-service-types,1.1.0,MIT multipart-post,2.0.0,MIT mustermann,1.0.0,MIT mustermann-grape,1.0.0,MIT +mute-stream,0.0.5,ISC mysql2,0.4.10,MIT +name-all-modules-plugin,1.0.1,MIT +nan,2.6.2,MIT +natural-compare,1.4.0,MIT +negotiator,0.6.1,MIT +nested-error-stacks,1.0.2,MIT net-ldap,0.16.0,MIT net-ssh,4.1.0,MIT +netmask,1.0.6,MIT netrc,0.11.0,MIT -nokogiri,1.8.1,MIT +node-dir,0.1.17,MIT +node-forge,0.6.33,New BSD +node-libs-browser,1.1.1,MIT +node-libs-browser,2.0.0,MIT +node-pre-gyp,0.6.37,New BSD +node-uuid,1.4.8,MIT +nodemailer,2.7.2,MIT +nodemailer-direct-transport,3.3.2,MIT +nodemailer-fetch,1.6.0,MIT +nodemailer-shared,1.1.0,MIT +nodemailer-smtp-pool,2.8.2,MIT +nodemailer-smtp-transport,2.7.2,MIT +nodemailer-wellknown,0.1.10,MIT +nodemon,1.11.0,MIT +nokogiri,1.8.2,MIT +nopt,1.0.10,MIT +nopt,3.0.6,ISC +nopt,4.0.1,ISC +normalize-package-data,2.4.0,Simplified BSD +normalize-path,2.1.1,MIT +normalize-range,0.1.2,MIT +normalize-url,1.9.1,MIT +npm-run-path,2.0.2,MIT +npmlog,4.1.2,ISC +null-check,1.0.0,MIT +num2fraction,1.2.2,MIT +number-is-nan,1.0.1,MIT numerizer,0.1.1,MIT oauth,0.5.1,MIT +oauth-sign,0.8.2,Apache 2.0 oauth2,1.4.0,MIT +object-assign,3.0.0,MIT +object-assign,4.1.1,MIT +object-component,0.0.3,MIT* +object-inspect,1.3.0,MIT +object-keys,1.0.11,MIT +object.omit,2.0.1,MIT +obuf,1.1.1,MIT octokit,4.6.2,MIT oj,2.17.5,MIT omniauth,1.4.2,MIT @@ -193,10 +1017,58 @@ omniauth-saml,1.7.0,MIT omniauth-shibboleth,1.2.1,MIT omniauth-twitter,1.2.1,MIT omniauth_crowd,2.2.3,MIT +on-finished,2.3.0,MIT +on-headers,1.0.1,MIT +once,1.4.0,ISC +onetime,1.1.0,MIT +opener,1.4.3,(WTFPL OR MIT) +opn,4.0.2,MIT +optimist,0.6.1,MIT +optionator,0.8.2,MIT org-ruby,0.9.12,MIT +original,1.0.0,MIT orm_adapter,0.5.0,MIT os,0.9.6,MIT -paranoia,2.3.1,MIT +os-browserify,0.2.1,MIT +os-browserify,0.3.0,MIT +os-homedir,1.0.2,MIT +os-locale,1.4.0,MIT +os-locale,2.1.0,MIT +os-tmpdir,1.0.2,MIT +osenv,0.1.4,ISC +p-cancelable,0.3.0,MIT +p-finally,1.0.0,MIT +p-limit,1.1.0,MIT +p-locate,2.0.0,MIT +p-map,1.1.1,MIT +p-timeout,1.2.0,MIT +pac-proxy-agent,1.1.0,MIT +pac-resolver,2.0.0,MIT +package-json,1.2.0,MIT +pako,0.2.9,MIT +pako,1.0.5,(MIT AND Zlib) +pako,1.0.6,(MIT AND Zlib) +parents,1.0.1,MIT +parse-asn1,5.0.0,ISC +parse-glob,3.0.4,MIT +parse-json,2.2.0,MIT +parseqs,0.0.5,MIT +parseuri,0.0.5,MIT +parseurl,1.3.1,MIT +path-browserify,0.0.0,MIT +path-exists,2.1.0,MIT +path-exists,3.0.0,MIT +path-is-absolute,1.0.1,MIT +path-is-inside,1.0.2,(WTFPL OR MIT) +path-key,2.0.1,MIT +path-parse,1.0.5,MIT +path-platform,0.11.15,MIT +path-proxy,1.0.0,MIT +path-to-regexp,0.1.7,MIT +path-type,1.1.0,MIT +path-type,2.0.0,MIT +pause-stream,0.0.11,Apache 2.0 +pbkdf2,3.0.9,MIT peek,1.0.1,MIT peek-gc,0.0.2,MIT peek-host,1.0.0,MIT @@ -206,18 +1078,98 @@ peek-pg,1.3.0,MIT peek-rblineprof,0.2.0,MIT peek-redis,1.2.0,MIT peek-sidekiq,1.0.3,MIT +performance-now,0.2.0,MIT +performance-now,2.1.0,MIT pg,0.18.4,"BSD,ruby,GPL" +pify,2.3.0,MIT +pify,3.0.0,MIT +pikaday,1.6.1,MIT +pinkie,2.0.4,MIT +pinkie-promise,2.0.1,MIT +pkg-dir,1.0.0,MIT +pkg-dir,2.0.0,MIT +pkg-up,1.0.0,MIT +pluralize,1.2.1,MIT po_to_json,1.0.1,MIT +portfinder,1.0.13,MIT posix-spawn,0.3.13,MIT +postcss,5.2.16,MIT +postcss,6.0.14,MIT +postcss,6.0.15,MIT +postcss-calc,5.3.1,MIT +postcss-colormin,2.2.2,MIT +postcss-convert-values,2.6.1,MIT +postcss-discard-comments,2.0.4,MIT +postcss-discard-duplicates,2.1.0,MIT +postcss-discard-empty,2.1.0,MIT +postcss-discard-overridden,0.1.1,MIT +postcss-discard-unused,2.2.3,MIT +postcss-filter-plugins,2.0.2,MIT +postcss-load-config,1.2.0,MIT +postcss-load-options,1.2.0,MIT +postcss-load-plugins,2.3.0,MIT +postcss-merge-idents,2.1.7,MIT +postcss-merge-longhand,2.0.2,MIT +postcss-merge-rules,2.1.2,MIT +postcss-message-helpers,2.0.0,MIT +postcss-minify-font-values,1.0.5,MIT +postcss-minify-gradients,1.0.5,MIT +postcss-minify-params,1.2.2,MIT +postcss-minify-selectors,2.1.1,MIT +postcss-modules-extract-imports,1.0.1,ISC +postcss-modules-local-by-default,1.1.1,MIT +postcss-modules-scope,1.0.2,ISC +postcss-modules-values,1.2.2,ISC +postcss-normalize-charset,1.1.1,MIT +postcss-normalize-url,3.0.8,MIT +postcss-ordered-values,2.2.3,MIT +postcss-reduce-idents,2.4.0,MIT +postcss-reduce-initial,1.0.1,MIT +postcss-reduce-transforms,1.0.4,MIT +postcss-selector-parser,2.2.3,MIT +postcss-svgo,2.1.6,MIT +postcss-unique-selectors,2.0.2,MIT +postcss-value-parser,3.3.0,MIT +postcss-zindex,2.2.0,MIT +prelude-ls,1.1.2,MIT premailer,1.10.4,New BSD premailer-rails,1.9.7,MIT -prometheus-client-mmap,0.7.0.beta44,Apache 2.0 +prepend-http,1.0.4,MIT +preserve,0.2.0,MIT +prettier,1.8.2,MIT +prettier,1.9.2,MIT +prismjs,1.6.0,MIT +private,0.1.8,MIT +process,0.11.9,MIT +process-nextick-args,1.0.7,MIT +progress,1.1.8,MIT +prometheus-client-mmap,0.9.1,Apache 2.0 +proxy-addr,1.1.5,MIT +proxy-agent,2.0.0,MIT +prr,0.0.0,MIT +ps-tree,1.1.0,MIT +pseudomap,1.0.2,ISC +public-encrypt,4.0.0,MIT public_suffix,3.0.0,MIT +punycode,1.3.2,MIT +punycode,1.4.1,MIT pyu-ruby-sasl,0.0.3.3,MIT +q,1.4.1,MIT +q,1.5.0,MIT +qjobs,1.1.5,MIT +qs,6.2.3,New BSD +qs,6.4.0,New BSD +qs,6.5.0,New BSD +qs,6.5.1,New BSD +query-string,4.3.2,MIT +querystring,0.2.0,MIT +querystring-es3,0.2.1,MIT +querystringify,0.0.4,MIT +querystringify,1.0.0,MIT rack,1.6.8,MIT rack-accept,0.4.5,MIT rack-attack,4.4.1,MIT -rack-cors,0.4.0,MIT +rack-cors,1.0.2,MIT rack-oauth2,1.2.3,MIT rack-protection,1.5.3,MIT rack-proxy,0.6.0,MIT @@ -231,26 +1183,93 @@ railties,4.2.10,MIT rainbow,2.2.2,MIT raindrops,0.18.0,LGPL-2.1+ rake,12.3.0,MIT +randomatic,1.1.6,MIT +randombytes,2.0.3,MIT +randombytes,2.0.6,MIT +randomfill,1.0.3,MIT +range-parser,1.2.0,MIT +raphael,2.2.7,MIT +raven-js,3.22.1,Simplified BSD +raw-body,2.2.0,MIT +raw-body,2.3.2,MIT +raw-loader,0.5.1,MIT +rb-fsevent,0.10.2,MIT +rb-inotify,0.9.10,MIT rbnacl,4.0.2,MIT rbnacl-libsodium,1.0.11,MIT +rc,1.2.1,(BSD-2-Clause OR MIT OR Apache-2.0) rdoc,4.2.2,ruby re2,1.1.1,New BSD +react-dev-utils,0.5.2,New BSD +read-all-stream,3.1.0,MIT +read-only-stream,2.0.0,MIT +read-pkg,1.1.0,MIT +read-pkg,2.0.0,MIT +read-pkg-up,1.0.1,MIT +read-pkg-up,2.0.0,MIT +readable-stream,1.1.14,MIT +readable-stream,2.0.6,MIT +readable-stream,2.3.3,MIT +readdirp,2.1.0,MIT +readline2,1.0.1,MIT recaptcha,3.0.0,MIT +rechoir,0.6.2,MIT recursive-open-struct,1.0.0,MIT +recursive-readdir,2.1.1,MIT redcarpet,3.4.0,MIT +redent,1.0.0,MIT +redis,2.8.0,MIT redis,3.3.5,MIT redis-actionpack,5.0.2,MIT redis-activesupport,5.0.4,MIT +redis-commands,1.3.1,MIT redis-namespace,1.5.2,MIT +redis-parser,2.6.0,MIT redis-rack,2.0.4,MIT redis-rails,5.0.2,MIT redis-store,1.4.1,MIT +reduce-css-calc,1.3.0,MIT +reduce-function-call,1.0.2,MIT +regenerate,1.3.2,MIT +regenerator-runtime,0.11.0,MIT +regenerator-transform,0.10.1,BSD +regex-cache,0.4.3,MIT +regexpu-core,1.0.0,MIT +regexpu-core,2.0.0,MIT +registry-url,3.1.0,MIT +regjsgen,0.2.0,MIT +regjsparser,0.1.5,Simplified BSD +remove-trailing-separator,1.1.0,ISC +repeat-element,1.1.2,MIT +repeat-string,0.2.2,MIT +repeat-string,1.6.1,MIT +repeating,1.1.3,MIT +repeating,2.0.1,MIT representable,3.0.4,MIT +request,2.75.0,Apache 2.0 +request,2.81.0,Apache 2.0 +request,2.83.0,Apache 2.0 request_store,1.3.1,MIT +requestretry,1.12.2,MIT +require-all,2.2.0,MIT +require-directory,2.1.1,MIT +require-from-string,1.2.1,MIT +require-main-filename,1.0.1,ISC +require-uncached,1.0.3,MIT +requires-port,1.0.0,MIT +resolve,1.1.7,MIT +resolve,1.4.0,MIT +resolve,1.5.0,MIT +resolve-from,1.0.1,MIT responders,2.3.0,MIT rest-client,2.0.0,MIT +restore-cursor,1.0.1,MIT +resumer,0.0.0,MIT retriable,3.1.1,MIT +right-align,0.1.3,MIT +rimraf,2.6.1,ISC rinku,2.0.0,ISC +ripemd160,1.0.1,New BSD rotp,2.1.2,MIT rouge,2.2.1,MIT rqrcode,0.7.0,MIT @@ -263,51 +1282,282 @@ rubyntlm,0.6.2,MIT rubypants,0.2.0,BSD rufus-scheduler,3.4.0,MIT rugged,0.26.0,MIT +run-async,0.1.0,MIT +rx-lite,3.1.2,Apache 2.0 +safe-buffer,5.0.1,MIT +safe-buffer,5.1.1,MIT safe_yaml,1.0.4,MIT sanitize,2.1.0,MIT -sass,3.4.22,MIT +sanitize-html,1.16.3,MIT +sass,3.5.5,MIT +sass-listen,4.0.0,MIT sass-rails,5.0.6,MIT sawyer,0.8.1,MIT +sax,1.2.2,ISC +schema-utils,0.3.0,MIT securecompare,1.0.0,MIT -seed-fu,2.3.6,MIT +seed-fu,2.3.7,MIT +select,1.1.2,MIT +select-hose,2.0.0,MIT +select2,3.5.2-browserify,Apache* select2-rails,3.5.9.3,MIT +selfsigned,1.10.1,MIT +semver,5.0.3,ISC +semver,5.3.0,ISC +semver-diff,2.1.0,MIT +send,0.15.4,MIT sentry-raven,2.5.3,Apache 2.0 +serve-index,1.9.0,MIT +serve-static,1.12.4,MIT +set-blocking,2.0.0,ISC +set-immediate-shim,1.0.1,MIT +setimmediate,1.0.5,MIT +setprototypeof,1.0.3,ISC settingslogic,2.0.9,MIT sexp_processor,4.9.0,MIT +sha.js,2.4.8,MIT +sha.js,2.4.9,MIT +shasum,1.0.2,MIT +shebang-command,1.2.0,MIT +shebang-regex,1.0.0,MIT +shell-quote,1.6.1,MIT +shelljs,0.7.8,New BSD sidekiq,5.0.5,LGPL sidekiq-cron,0.6.0,MIT sidekiq-limit_fetch,3.4.0,MIT +signal-exit,3.0.2,ISC signet,0.7.3,Apache 2.0 +slack-node,0.2.0,MIT slack-notifier,1.5.1,MIT +slash,1.0.0,MIT +slice-ansi,0.0.4,MIT +slide,1.1.6,ISC +smart-buffer,1.1.15,MIT +smtp-connection,2.12.0,MIT +sntp,1.0.9,BSD +sntp,2.1.0,BSD +socket.io,2.0.4,MIT +socket.io-adapter,1.1.1,MIT +socket.io-client,2.0.4,MIT +socket.io-parser,3.1.2,MIT +sockjs,0.3.18,MIT +sockjs-client,1.0.1,MIT +sockjs-client,1.1.4,MIT +socks,1.1.10,MIT +socks,1.1.9,MIT +socks-proxy-agent,2.1.1,MIT +sort-keys,1.1.2,MIT +source-list-map,0.1.8,MIT +source-list-map,2.0.0,MIT +source-map,0.2.0,New BSD +source-map,0.4.4,New BSD +source-map,0.5.6,New BSD +source-map,0.5.7,New BSD +source-map,0.6.1,New BSD +source-map-support,0.4.18,MIT +spdx-correct,1.0.2,Apache 2.0 +spdx-expression-parse,1.0.4,(MIT AND CC-BY-3.0) +spdx-license-ids,1.2.2,Unlicense +spdy,3.4.7,MIT +spdy-transport,2.0.20,MIT +split,0.3.3,MIT +sprintf-js,1.0.3,New BSD sprockets,3.7.1,MIT sprockets-rails,3.2.1,MIT +sql.js,0.4.0,MIT +srcset,1.0.0,MIT +sshpk,1.13.1,MIT state_machines,0.4.0,MIT state_machines-activemodel,0.4.0,MIT state_machines-activerecord,0.4.0,MIT +statuses,1.3.1,MIT +stream-browserify,2.0.1,MIT +stream-combiner,0.0.4,MIT +stream-combiner2,1.1.1,MIT +stream-http,2.6.3,MIT +stream-http,2.7.2,MIT +stream-shift,1.0.0,MIT +stream-splicer,2.0.0,MIT +streamroller,0.7.0,MIT +strict-uri-encode,1.1.0,MIT +string-length,1.0.1,MIT +string-width,1.0.2,MIT +string-width,2.0.0,MIT +string.prototype.trim,1.1.2,MIT +string_decoder,0.10.31,MIT +string_decoder,1.0.3,MIT stringex,2.7.1,MIT +stringstream,0.0.5,MIT +strip-ansi,3.0.1,MIT +strip-ansi,4.0.0,MIT +strip-bom,2.0.0,MIT +strip-bom,3.0.0,MIT +strip-eof,1.0.0,MIT +strip-indent,1.0.1,MIT +strip-json-comments,2.0.1,MIT +subarg,1.0.0,MIT +supports-color,2.0.0,MIT +supports-color,3.2.3,MIT +supports-color,4.2.1,MIT +supports-color,4.5.0,MIT +supports-color,5.1.0,MIT +svg4everybody,2.1.9,CC0-1.0 +svgo,0.7.2,MIT +syntax-error,1.3.0,MIT sys-filesystem,1.1.6,Artistic 2.0 +table,3.8.3,New BSD +tapable,0.1.10,MIT +tapable,0.2.8,MIT +tape,4.8.0,MIT +tar,2.2.1,ISC +tar-pack,3.4.0,Simplified BSD temple,0.7.7,MIT +test-exclude,4.1.1,ISC text,1.3.1,MIT +text-table,0.2.0,MIT thor,0.19.4,MIT thread_safe,0.3.6,Apache 2.0 +three,0.84.0,MIT +three-orbit-controls,82.1.0,MIT +three-stl-loader,1.0.4,MIT +through,2.3.8,MIT +through2,2.0.3,MIT +thunkify,2.1.2,MIT +thunky,0.1.0,MIT* tilt,2.0.6,MIT +time-stamp,2.0.0,MIT +timeago.js,3.0.2,MIT +timed-out,2.0.0,MIT +timed-out,4.0.1,MIT +timers-browserify,1.4.2,MIT +timers-browserify,2.0.4,MIT +timespan,2.3.0,MIT timfel-krb5-auth,0.8.3,LGPL +tiny-emitter,2.0.2,MIT +tmp,0.0.31,MIT +tmp,0.0.33,MIT +to-array,0.1.4,MIT +to-arraybuffer,1.0.1,MIT +to-fast-properties,1.0.3,MIT +to-fast-properties,2.0.0,MIT toml-rb,0.3.15,MIT +touch,1.0.0,ISC +tough-cookie,2.3.2,New BSD +tough-cookie,2.3.3,New BSD +traverse,0.6.6,MIT +trim-newlines,1.0.0,MIT +trim-right,1.0.1,MIT truncato,0.7.10,MIT +tryit,1.0.3,MIT +tsscmp,1.0.5,MIT +tty-browserify,0.0.0,MIT +tunnel-agent,0.4.3,Apache 2.0 +tunnel-agent,0.6.0,Apache 2.0 +tweetnacl,0.14.5,Unlicense +type-check,0.3.2,MIT +type-is,1.6.15,MIT +typedarray,0.0.6,MIT tzinfo,1.2.4,MIT u2f,0.2.1,MIT uber,0.1.0,MIT uglifier,2.7.2,MIT +uglify-js,2.8.29,Simplified BSD +uglify-to-browserify,1.0.2,MIT +uglifyjs-webpack-plugin,0.4.6,MIT +uid-number,0.0.6,ISC +ultron,1.1.0,MIT +umd,3.0.1,MIT +unc-path-regex,0.1.2,MIT +undefsafe,0.0.3,MIT / http://rem.mit-license.org +underscore,1.7.0,MIT +underscore,1.8.3,MIT unf,0.1.4,BSD unf_ext,0.0.7.4,MIT unicorn,5.1.0,ruby unicorn-worker-killer,0.4.4,ruby +uniq,1.0.1,MIT +uniqid,4.1.1,MIT +uniqs,2.0.0,MIT +unpipe,1.0.0,MIT +update-notifier,0.5.0,Simplified BSD +url,0.11.0,MIT +url-loader,0.5.8,MIT +url-parse,1.0.5,MIT +url-parse,1.1.7,MIT +url-parse,1.1.9,MIT +url-parse-lax,1.0.0,MIT +url-to-options,1.0.1,MIT url_safe_base64,0.2.2,MIT +user-home,2.0.0,MIT +useragent,2.2.1,MIT +util,0.10.3,MIT +util-deprecate,1.0.2,MIT +utils-merge,1.0.0,MIT +uuid,2.0.3,MIT +uuid,3.1.0,MIT +uws,0.14.5,Zlib +validate-npm-package-license,3.0.1,Apache 2.0 validates_hostname,1.0.6,MIT +vary,1.1.1,MIT +vendors,1.0.1,MIT +verror,1.10.0,MIT version_sorter,2.1.0,MIT virtus,1.0.5,MIT +visibilityjs,1.2.4,MIT +vm-browserify,0.0.4,MIT vmstat,2.3.0,MIT +void-elements,2.0.1,MIT +vue,2.5.13,MIT +vue-eslint-parser,2.0.1,MIT +vue-hot-reload-api,2.2.4,MIT +vue-loader,13.7.0,MIT +vue-resource,1.3.5,MIT +vue-router,3.0.1,MIT +vue-style-loader,3.0.3,MIT +vue-template-compiler,2.5.13,MIT +vue-template-es2015-compiler,1.6.0,MIT +vuex,3.0.1,MIT warden,1.2.6,MIT +watchpack,1.4.0,MIT +wbuf,1.7.2,MIT +webpack,3.5.5,MIT +webpack-bundle-analyzer,2.8.2,MIT +webpack-dev-middleware,1.11.0,MIT +webpack-dev-middleware,1.12.2,MIT +webpack-dev-server,2.7.1,MIT webpack-rails,0.9.10,MIT +webpack-sources,1.0.1,MIT +webpack-stats-plugin,0.1.5,MIT +websocket-driver,0.6.5,MIT +websocket-extensions,0.1.1,MIT +when,3.7.8,MIT +whet.extend,0.9.9,MIT +which,1.2.12,ISC +which-module,1.0.0,ISC +which-module,2.0.0,ISC +wide-align,1.1.2,ISC wikicloth,0.8.1,MIT +window-size,0.1.0,MIT +wordwrap,0.0.2,MIT +wordwrap,0.0.3,MIT +wordwrap,1.0.0,MIT +worker-loader,1.1.0,MIT +wrap-ansi,2.1.0,MIT +wrappy,1.0.2,ISC +write,0.2.1,MIT +write-file-atomic,1.3.4,ISC +ws,2.3.1,MIT +ws,3.3.3,MIT +xdg-basedir,2.0.0,MIT xml-simple,1.1.5,ruby +xmlhttprequest-ssl,1.5.5,MIT +xregexp,2.0.0,MIT +xtend,4.0.1,MIT +y18n,3.2.1,ISC +yallist,2.1.2,ISC +yargs,3.10.0,MIT +yargs,6.6.0,MIT +yargs,8.0.2,MIT +yargs-parser,4.2.1,ISC +yargs-parser,7.0.0,ISC +yeast,0.1.2,MIT diff --git a/yarn.lock b/yarn.lock index 5c7027237a7..95f20a4d3e1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4207,6 +4207,10 @@ jquery-ujs@1.2.2: dependencies: jquery ">=1.8.0" +jquery.waitforimages@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/jquery.waitforimages/-/jquery.waitforimages-2.2.0.tgz#63f23131055a1b060dc913e6d874bcc9b9e6b16b" + "jquery@>= 1.9.1", jquery@>=1.8.0, jquery@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/jquery/-/jquery-2.2.4.tgz#2c89d6889b5eac522a7eea32c14521559c6cbf02" @@ -4401,6 +4405,12 @@ karma@^2.0.0: tmp "0.0.33" useragent "^2.1.12" +katex@^0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/katex/-/katex-0.8.3.tgz#909d99864baf964c3ccae39c4a99a8e0fb9a1bd0" + dependencies: + match-at "^0.1.0" + kind-of@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.1.0.tgz#475d698a5e49ff5e53d14e3e732429dc8bf4cf47" @@ -4791,6 +4801,10 @@ marked@^0.3.12: version "0.3.12" resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.12.tgz#7cf25ff2252632f3fe2406bde258e94eee927519" +match-at@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/match-at/-/match-at-0.1.1.tgz#25d040d291777704d5e6556bbb79230ec2de0540" + math-expression-evaluator@^1.2.14: version "1.2.16" resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.16.tgz#b357fa1ca9faefb8e48d10c14ef2bcb2d9f0a7c9" @@ -7083,6 +7097,13 @@ strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" +style-loader@^0.19.1: + version "0.19.1" + resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.19.1.tgz#591ffc80bcefe268b77c5d9ebc0505d772619f85" + dependencies: + loader-utils "^1.0.2" + schema-utils "^0.3.0" + subarg@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/subarg/-/subarg-1.0.0.tgz#f62cf17581e996b48fc965699f54c06ae268b8d2" |