diff --git a/.flayignore b/.flayignore index 0c4eee10ffa..7faa6c7bb90 100644 --- a/.flayignore +++ b/.flayignore @@ -10,3 +10,7 @@ lib/gitlab/background_migration/* app/models/project_services/kubernetes_service.rb lib/gitlab/workhorse.rb lib/gitlab/ci/trace/chunked_io.rb +lib/gitlab/gitaly_client/ref_service.rb +lib/gitlab/gitaly_client/commit_service.rb +lib/gitlab/git/commit.rb +lib/gitlab/git/tag.rb diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b1445feee58..ced51cf8225 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -126,6 +126,23 @@ stages: <<: *dedicated-no-docs-pull-cache-job <<: *except-docs-and-qa +.single-script-job: &single-script-job + image: ruby:2.4-alpine + before_script: [] + stage: build + cache: {} + dependencies: [] + variables: &single-script-job-variables + GIT_STRATEGY: none + before_script: + # We need to download the script rather than clone the repo since the + # package-and-qa job will not be able to run when the branch gets + # deleted (when merging the MR). + - export SCRIPT_NAME="${SCRIPT_NAME:-$CI_JOB_NAME}" + - apk add --update openssl + - wget $CI_PROJECT_URL/raw/$CI_COMMIT_SHA/scripts/$SCRIPT_NAME + - chmod 755 $SCRIPT_NAME + .rake-exec: &rake-exec <<: *dedicated-no-docs-no-db-pull-cache-job script: @@ -189,7 +206,7 @@ stages: <<: *dedicated-no-docs-and-no-qa-pull-cache-job <<: *use-pg variables: - CREATE_DB_USER: "true" + SETUP_DB: "false" script: # Manually clone gitlab-test and only seed this project in # db/fixtures/development/04_project.rb thanks to SIZE=1 below @@ -207,19 +224,10 @@ stages: .review-docs: &review-docs <<: *dedicated-runner <<: *except-qa - image: ruby:2.4-alpine - before_script: - - gem install gitlab --no-doc - # We need to download the script rather than clone the repo since the - # review-docs-cleanup job will not be able to run when the branch gets - # deleted (when merging the MR). - - apk add --update openssl - - wget https://gitlab.com/gitlab-org/gitlab-ce/raw/master/scripts/trigger-build-docs - - chmod 755 trigger-build-docs - cache: {} - dependencies: [] + <<: *single-script-job variables: - GIT_STRATEGY: none + <<: *single-script-job-variables + SCRIPT_NAME: trigger-build-docs when: manual only: - branches @@ -233,7 +241,7 @@ stages: .migration-paths: &migration-paths <<: *dedicated-no-docs-and-no-qa-pull-cache-job variables: - CREATE_DB_USER: "true" + SETUP_DB: "false" script: - git fetch https://gitlab.com/gitlab-org/gitlab-ce.git v9.3.0 - git checkout -f FETCH_HEAD @@ -242,7 +250,7 @@ stages: - cp config/gitlab.yml.example config/gitlab.yml - bundle exec rake db:drop db:create db:schema:load db:seed_fu - date - - git checkout $CI_COMMIT_SHA + - git checkout -f $CI_COMMIT_SHA - bundle install $BUNDLE_INSTALL_FLAGS - date - . scripts/prepare_build.sh @@ -253,23 +261,14 @@ stages: # Trigger a package build in omnibus-gitlab repository # package-and-qa: - image: ruby:2.4-alpine - before_script: [] - stage: build - cache: {} - when: manual + <<: *single-script-job variables: - GIT_STRATEGY: none + <<: *single-script-job-variables + SCRIPT_NAME: trigger-build-omnibus retry: 0 - before_script: - # We need to download the script rather than clone the repo since the - # package-and-qa job will not be able to run when the branch gets - # deleted (when merging the MR). - - apk add --update openssl - - wget https://gitlab.com/$CI_PROJECT_PATH/raw/$CI_COMMIT_SHA/scripts/trigger-build-omnibus - - chmod 755 trigger-build-omnibus script: - - ./trigger-build-omnibus + - ./$SCRIPT_NAME + when: manual only: - //@gitlab-org/gitlab-ce - //@gitlab-org/gitlab-ee @@ -286,7 +285,8 @@ review-docs-deploy: url: http://$DOCS_GITLAB_REPO_SUFFIX-$CI_COMMIT_REF_SLUG.$DOCS_REVIEW_APPS_DOMAIN/$DOCS_GITLAB_REPO_SUFFIX on_stop: review-docs-cleanup script: - - ./trigger-build-docs deploy + - gem install gitlab --no-ri --no-rdoc + - ./$SCRIPT_NAME deploy # Cleanup remote environment of gitlab-docs review-docs-cleanup: @@ -296,7 +296,26 @@ review-docs-cleanup: name: review-docs/$CI_COMMIT_REF_NAME action: stop script: - - ./trigger-build-docs cleanup + - gem install gitlab --no-ri --no-rdoc + - ./SCRIPT_NAME cleanup + +## +# Trigger a docker image build in CNG (Cloud Native GitLab) repository +# +cloud-native-image: + image: ruby:2.4-alpine + before_script: [] + stage: build + allow_failure: true + cache: {} + before_script: + - gem install gitlab --no-rdoc --no-ri + - chmod 755 ./scripts/trigger-build-cloud-native + script: + - ./scripts/trigger-build-cloud-native + only: + - tags@gitlab-org/gitlab-ce + - tags@gitlab-org/gitlab-ee # Retrieve knapsack and rspec_flaky reports retrieve-tests-metadata: @@ -325,7 +344,7 @@ update-tests-metadata: - rspec_flaky/ policy: push script: - - retry gem install fog-aws mime-types activesupport + - retry gem install fog-aws mime-types activesupport --no-ri --no-rdoc - scripts/merge-reports ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/rspec-pg_node_*.json - scripts/merge-reports ${FLAKY_RSPEC_SUITE_REPORT_PATH} rspec_flaky/all_*_*.json - FLAKY_RSPEC_GENERATE_REPORT=1 scripts/prune-old-flaky-specs ${FLAKY_RSPEC_SUITE_REPORT_PATH} diff --git a/CHANGELOG.md b/CHANGELOG.md index 29047c3ad65..0c90ab1e8bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,189 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 10.8.0 (2018-05-22) + +### Security (3 changes, 1 of them is from the community) + +- Update faraday_middlewar to 0.12.2. !18397 (Takuya Noguchi) +- Serve archive requests with the correct file in all cases. +- Sanitizes user name to avoid XSS attacks. + +### Fixed (47 changes, 11 of them are from the community) + +- Refactor CSS to eliminate vertical misalignment of login nav. !16275 (Takuya Noguchi) +- Fix pipeline status in branch/tag tree page. !17995 +- Allow group owner to enable runners from subgroups (#41981). !18009 +- Fix template selector menu visibility when toggling preview mode in file edit view. !18118 (Fabian Schneider) +- Fix confirmation modal for deleting a protected branch. !18176 (Paul Bonaud @PaulRbR) +- Triggering custom hooks by Wiki UI edit. !18251 +- Now `rake cache:clear` will also clear pipeline status cache. !18257 +- Fix `joined` information on project members page. !18290 (Fabian Schneider) +- Fix missing namespace for some internal users. !18357 +- Show shared projects on group page. !18390 +- Restore label underline color. !18407 (George Tsiolis) +- Fix undefined `html_escape` method during markdown rendering. !18418 +- Fix unassign slash command preview. !18447 +- Correct text and functionality for delete user / delete user and contributions modal. !18463 (Marc Schwede) +- Fix discussions API setting created_at for notable in a group or notable in a project in a group with owners. !18464 +- Don't include lfs_file_locks data in export bundle. !18495 +- Reset milestone filter when clicking "Any Milestone" in dashboard. !18531 +- Ensure member notifications are sent after the member actual creation/update in the DB. !18538 +- Update links to /ci/lint with ones to project ci/lint. !18539 (Takuya Noguchi) +- Fix tabs container styles to make RSS button clickable. !18559 +- Raise NoRepository error for non-valid repositories when calculating repository checksum. !18594 +- Don't automatically remove artifacts for pages jobs after pages:deploy has run. !18628 +- Increase new issue metadata form margin. !18630 (George Tsiolis) +- Add loading icon padding for pipeline environments. !18631 (George Tsiolis) +- ShaAttribute no longer stops startup if database is missing. !18726 +- Fix close keyboard shortcuts dialog using the keyboard shortcut. !18783 (Lars Greiss) +- Fixes database inconsistencies between Community and Enterprise Edition on import state. !18811 +- Add database foreign key constraint between pipelines and build. !18822 +- Fix finding wiki pages when they have invalidly-encoded content. !18856 +- Fix outdated Web IDE welcome copy. !18861 +- fixed copy to blipboard button in embed bar of snippets. !18923 (haseebeqx) +- Disables RBAC on nginx-ingress. !18947 +- Correct skewed Kubernetes popover illustration. !18949 +- Resolve Import/Export ci_cd_settings error updating the project. !46049 +- Fix project creation for user endpoint when jobs_enabled parameter supplied. +- 46210 Display logo and user dropdown on mobile for terms page and fix styling. +- Adds illustration for when job log was erased. +- Ensure web hook 'blocked URL' errors are stored in web hook logs and properly surfaced to the user. +- Make toggle markdown preview shortcut only toggle selected field. +- Verifiy if pipeline has commit idetails and render information in MR widget when branch is deleted. +- Fixed inconsistent protected branch pill baseline. +- Fix setting Gitlab metrics content types. +- Display only generic message on merge error to avoid exposing any potentially sensitive or user unfriendly backend messages. +- Fix label links update on project transfer. +- Breaks commit not found message in pipelines table. +- Adjust issue boards list header label text color. +- Prevent pipeline actions in dropdown to redirct to a new page. + +### Changed (35 changes, 15 of them are from the community) + +- Improve tooltips in collapsed right sidebar. !17714 +- Partition job_queue_duration_seconds with jobs_running_for_project. !17730 +- For group dashboard, we no longer show groups which the visitor is not a member of (this applies to admins and auditors). !17884 (Roger Rüttimann) +- Use RFC 3676 mail signature delimiters. !17979 (Enrico Scholz) +- Add sha filter to pipelines list API. !18125 +- New CI Job live-trace architecture. !18169 +- Make project deploy keys table more clearly structured. !18279 +- Remove green background from unlock button in admin area. !18288 +- Renamed Overview to Project in the contextual navigation at a project level. !18295 (Constance Okoghenun) +- Load branches on new merge request page asynchronously. !18315 +- Create settings section for autodevops. !18321 +- Add a comma to the time estimate system notes. !18326 +- Enable specifying variables when executing a manual pipeline. !18440 +- Fix size and position for fork icon. !18449 (George Tsiolis) +- Refactored activity calendar. !18469 (Enrico Scholz) +- Small improvements to repository checks. !18484 +- Add 2FA filter to users API for admins only. !18503 +- Align project avatar on small viewports. !18513 (George Tsiolis) +- Show group and project LFS settings in the interface to Owners and Masters. !18562 +- Update environment item action buttons icons. !18632 (George Tsiolis) +- Update timeline icon for description edit. !18633 (George Tsiolis) +- Revert discussion counter height. !18656 (George Tsiolis) +- Improve quick actions summary preview. !18659 (George Tsiolis) +- Change font for tables inside diff discussions. !18660 (George Tsiolis) +- Add padding to profile description. !18663 (George Tsiolis) +- Break issue title for board card title and issuable header text. !18674 (George Tsiolis) +- Adds push mirrors to GitLab Community Edition. !18715 +- Inform the user when there are no project import options available. !18716 (George Tsiolis) +- Improve commit message body rendering and fix responsive compare panels. !18725 (Constance Okoghenun) +- Reconcile project templates with Auto DevOps. !18737 +- Remove branch name from the status bar of WebIDE. +- Clean up WebIDE status bar and add useful info. +- Improve interaction on WebIDE commit panel. +- Keep current labels visible when editing them in the sidebar. +- Use VueJS for rendering pipeline stages. + +### Performance (26 changes, 11 of them are from the community) + +- Move WorkInProgress vue component. !17536 (George Tsiolis) +- Move ReadyToMerge vue component. !17545 (George Tsiolis) +- Move BoardBlankState vue component. !17666 (George Tsiolis) +- Improve DB performance of calculating total artifacts size. !17839 +- Add i18n and update specs for UnresolvedDiscussions vue component. !17866 (George Tsiolis) +- Introduce new ProjectCiCdSetting model with group_runners_enabled. !18144 +- Move PipelineFailed vue component. !18277 (George Tsiolis) +- Move TimeTrackingEstimateOnlyPane vue component. !18318 (George Tsiolis) +- Move TimeTrackingHelpState vue component. !18319 (George Tsiolis) +- Reduce queries on merge requests list page for merge requests from forks. !18561 +- Destroy build_chunks efficiently with FastDestroyAll module. !18575 +- Improve performance of a service responsible for creating a pipeline. !18582 +- Replace time_ago_in_words with JS-based one. !18607 (Takuya Noguchi) +- Move TimeTrackingNoTrackingPane vue component. !18676 (George Tsiolis) +- Move SidebarTimeTracking vue component. !18677 (George Tsiolis) +- Move TimeTrackingSpentOnlyPane vue component. !18710 (George Tsiolis) +- Detecting tags containing a commit uses Gitaly by default. +- Increase cluster applications installer availability using alpine linux mirrors. +- Compute notification recipients in background jobs. +- Use persisted diff data instead fetching Git on discussions. +- Detecting branchnames containing a commit uses Gitaly by default. +- Detect repository license on Gitaly by default. +- Finish NamespaceService migration to Gitaly. +- Check if a ref exists is done by Gitaly by default. +- Compute Gitlab::Git::Repository#checksum on Gitaly by default. +- Repository#exists? is always executed through Gitaly. + +### Added (22 changes, 10 of them are from the community) + +- Allow group masters to configure runners for groups. !9646 (Alexis Reigel) +- Adds Embedded Snippets Support. !15695 (haseebeqx) +- Add Copy metadata quick action. !16473 (Mateusz Bajorski) +- Show Runner's description on job's page. !17321 +- Add deprecation message to dynamic milestone pages. !17505 +- Show new branch/mr button even when branch exists. !17712 (Jacopo Beschi @jacopo-beschi) +- API: add languages of project GET /projects/:id/languages. !17770 (Roger Rüttimann) +- Display active sessions and allow the user to revoke any of it. !17867 (Alexis Reigel) +- Add cron job to email users on issue due date. !17985 (Stuart Nelson) +- Rubocop rule to avoid returning from a block. !18000 (Jacopo Beschi @jacopo-beschi) +- Add the signature verfication badge to the compare view. !18245 (Marc Shaw) +- Expose Deploy Token data as environment varialbes on CI/CD jobs. !18414 +- Show group id in group settings. !18482 (George Tsiolis) +- Allow admins to enforce accepting Terms of Service on an instance. !18570 +- Add CI_COMMIT_MESSAGE, CI_COMMIT_TITLE and CI_COMMIT_DESCRIPTION predefined variables. !18672 +- Add GCP signup offer to cluster index / create pages. !18684 +- Output some useful information when running the rails console. !18697 +- Display merge commit SHA in merge widget after merge. !18722 +- git SHA is now displayed alongside the GitLab version on the Admin Dashboard. +- Expose the target commit ID through the tag API. +- Added fuzzy file finder to web IDE. +- Add discussion API for merge requests and commits. + +### Other (22 changes, 8 of them are from the community) + +- Replace the `project/issues/milestones.feature` spinach test with an rspec analog. !18300 (@blackst0ne) +- Replace the `project/commits/branches.feature` spinach test with an rspec analog. !18302 (@blackst0ne) +- Replacing gollum libraries for gitlab custom libs. !18343 +- Replace the `project/commits/comments.feature` spinach test with an rspec analog. !18356 (@blackst0ne) +- Replace "Click" with "Select" to be more inclusive of people with accessibility requirements. !18386 (Mark Lapierre) +- Remove ahead/behind graphs on project branches on mobile. !18415 (Takuya Noguchi) +- Replace the `project/source/markdown_render.feature` spinach test with an rspec analog. !18525 (@blackst0ne) +- Add missing changelog type to docs. !18526 (@blackst0ne) +- Added Webhook SSRF prevention to documentation. !18532 +- Upgrade underscore.js to 1.9.0. !18578 +- Add documentation about how to use variables to define deploy policies for staging/production environments. !18675 +- Replace the `project/builds/artifacts.feature` spinach test with an rspec analog. !18729 (@blackst0ne) +- Block access to the API & git for users that did not accept enforced Terms of Service. !18816 +- Transition to atomic internal ids for all models. !44259 +- Removes modal boards store and mixins from global scope. +- Replace GKE acronym with Google Kubernetes Engine. +- Replace vue resource with axios for pipelines details page. +- Enable prometheus monitoring by default. +- Replace vue resource with axios in pipelines table. +- Bump lograge to 0.10.0 and remove monkey patch. +- Improves wording in new pipeline page. +- Gitaly handles repository forks by default. + + +## 10.7.4 (2018-05-21) + +### Fixed (1 change) + +- Fix error when deleting an empty list of refs. + + ## 10.7.3 (2018-05-02) ### Fixed (8 changes) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5447ebbdd8c..383d13656e2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -168,7 +168,7 @@ hits. They are not always necessary, but very convenient. If you are an expert in a particular area, it makes it easier to find issues to work on. You can also subscribe to those labels to receive an email each time an -issue is labelled with a subject label corresponding to your expertise. +issue is labeled with a subject label corresponding to your expertise. Examples of subject labels are ~wiki, ~"container registry", ~ldap, ~api, ~issues, ~"merge requests", ~labels, and ~"container registry". @@ -296,7 +296,24 @@ any potential community contributor to @-mention per above. ## Implement design & UI elements -Please see the [UX Guide for GitLab]. +For guidance on UX implementation at GitLab, please refer to our [Design System](https://design.gitlab.com/). + +The UX team uses labels to manage their workflow. + +The ~"UX" label on an issue is a signal to the UX team that it will need UX attention. +To better understand the priority by which UX tackles issues, see the [UX section](https://about.gitlab.com/handbook/ux/) of the handbook. + +Once an issue has been worked on and is ready for development, a UXer applies the ~"UX ready" label to that issue. + +The UX team has a special type label called ~"design artifact". This label indicates that the final output +for an issue is a UX solution/design. The solution will be developed by frontend and/or backend in a subsequent milestone. +Any issue labeled ~"design artifact" should not also be labeled ~"frontend" or ~"backend" since no development is +needed until the solution has been decided. + +~"design artifact" issues are like any other issue and should contain a milestone label, ~"Deliverable" or ~"Stretch", when scheduled in the current milestone. + +Once the ~"design artifact" issue has been completed, the UXer removes the ~"design artifact" label and applies the ~"UX ready" label. The Product Manager can use the +existing issue or decide to create a whole new issue for the purpose of development. ## Issue tracker diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 897e21587ed..7bb21aff834 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0.100.0 +0.102.0 diff --git a/Gemfile b/Gemfile index a6cd0ca980a..ba1b3e93b7b 100644 --- a/Gemfile +++ b/Gemfile @@ -174,6 +174,9 @@ gem 'httparty', '~> 0.13.3' # Colored output to console gem 'rainbow', '~> 2.2' +# Progress bar +gem 'ruby-progressbar' + # GitLab settings gem 'settingslogic', '~> 2.0.9' diff --git a/Gemfile.lock b/Gemfile.lock index 18c25cc34b6..2a33c08512a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1150,6 +1150,7 @@ DEPENDENCIES rubocop-rspec (~> 1.22.1) ruby-fogbugz (~> 0.2.1) ruby-prof (~> 0.17.0) + ruby-progressbar ruby_parser (~> 3.8) rufus-scheduler (~> 3.4) rugged (~> 0.27) diff --git a/VERSION b/VERSION index 60919325d67..8ca9077d87b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -10.8.0-pre +11.0.0-pre diff --git a/app/assets/javascripts/environments/components/environments_app.vue b/app/assets/javascripts/environments/components/environments_app.vue index c0be72f7401..3da762446c9 100644 --- a/app/assets/javascripts/environments/components/environments_app.vue +++ b/app/assets/javascripts/environments/components/environments_app.vue @@ -68,8 +68,7 @@ this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', showLoader); this.service.getFolderContent(folder.folder_path) - .then(resp => resp.json()) - .then(response => this.store.setfolderContent(folder, response.environments)) + .then(response => this.store.setfolderContent(folder, response.data.environments)) .then(() => this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', false)) .catch(() => { Flash(s__('Environments|An error occurred while fetching the environments.')); diff --git a/app/assets/javascripts/environments/mixins/environments_mixin.js b/app/assets/javascripts/environments/mixins/environments_mixin.js index 34d18d55120..a7a79dbca70 100644 --- a/app/assets/javascripts/environments/mixins/environments_mixin.js +++ b/app/assets/javascripts/environments/mixins/environments_mixin.js @@ -6,7 +6,6 @@ import Visibility from 'visibilityjs'; import Poll from '../../lib/utils/poll'; import { getParameterByName, - parseQueryStringIntoObject, } from '../../lib/utils/common_utils'; import { s__ } from '../../locale'; import Flash from '../../flash'; @@ -46,17 +45,14 @@ export default { methods: { saveData(resp) { - const headers = resp.headers; - return resp.json().then((response) => { - this.isLoading = false; + this.isLoading = false; - if (_.isEqual(parseQueryStringIntoObject(resp.url.split('?')[1]), this.requestData)) { - this.store.storeAvailableCount(response.available_count); - this.store.storeStoppedCount(response.stopped_count); - this.store.storeEnvironments(response.environments); - this.store.setPagination(headers); - } - }); + if (_.isEqual(resp.config.params, this.requestData)) { + this.store.storeAvailableCount(resp.data.available_count); + this.store.storeStoppedCount(resp.data.stopped_count); + this.store.storeEnvironments(resp.data.environments); + this.store.setPagination(resp.headers); + } }, /** @@ -70,7 +66,7 @@ export default { updateContent(parameters) { this.updateInternalState(parameters); // fetch new data - return this.service.get(this.requestData) + return this.service.fetchEnvironments(this.requestData) .then(response => this.successCallback(response)) .then(() => { // restart polling @@ -105,7 +101,7 @@ export default { fetchEnvironments() { this.isLoading = true; - return this.service.get(this.requestData) + return this.service.fetchEnvironments(this.requestData) .then(this.successCallback) .catch(this.errorCallback); }, @@ -141,7 +137,7 @@ export default { this.poll = new Poll({ resource: this.service, - method: 'get', + method: 'fetchEnvironments', data: this.requestData, successCallback: this.successCallback, errorCallback: this.errorCallback, diff --git a/app/assets/javascripts/environments/services/environments_service.js b/app/assets/javascripts/environments/services/environments_service.js index 03ab74b3338..3b121551aca 100644 --- a/app/assets/javascripts/environments/services/environments_service.js +++ b/app/assets/javascripts/environments/services/environments_service.js @@ -1,25 +1,22 @@ -/* eslint-disable class-methods-use-this */ -import Vue from 'vue'; -import VueResource from 'vue-resource'; - -Vue.use(VueResource); +import axios from '~/lib/utils/axios_utils'; export default class EnvironmentsService { constructor(endpoint) { - this.environments = Vue.resource(endpoint); + this.environmentsEndpoint = endpoint; this.folderResults = 3; } - get(options = {}) { + fetchEnvironments(options = {}) { const { scope, page } = options; - return this.environments.get({ scope, page }); + return axios.get(this.environmentsEndpoint, { params: { scope, page } }); } + // eslint-disable-next-line class-methods-use-this postAction(endpoint) { - return Vue.http.post(endpoint, {}, { emulateJSON: true }); + return axios.post(endpoint, {}, { emulateJSON: true }); } getFolderContent(folderUrl) { - return Vue.http.get(`${folderUrl}.json?per_page=${this.folderResults}`); + return axios.get(`${folderUrl}.json?per_page=${this.folderResults}`); } } diff --git a/app/assets/javascripts/ide/components/commit_sidebar/form.vue b/app/assets/javascripts/ide/components/commit_sidebar/form.vue index 4a645c827ad..81961fe3c57 100644 --- a/app/assets/javascripts/ide/components/commit_sidebar/form.vue +++ b/app/assets/javascripts/ide/components/commit_sidebar/form.vue @@ -5,7 +5,7 @@ import LoadingButton from '~/vue_shared/components/loading_button.vue'; import CommitMessageField from './message_field.vue'; import Actions from './actions.vue'; import SuccessMessage from './success_message.vue'; -import { activityBarViews, MAX_WINDOW_HEIGHT_COMPACT, COMMIT_ITEM_PADDING } from '../../constants'; +import { activityBarViews, MAX_WINDOW_HEIGHT_COMPACT } from '../../constants'; export default { components: { @@ -70,7 +70,7 @@ export default { ? this.$refs.formEl && this.$refs.formEl.offsetHeight : this.$refs.compactEl && this.$refs.compactEl.offsetHeight; - this.componentHeight = elHeight + COMMIT_ITEM_PADDING; + this.componentHeight = elHeight; }, enterTransition() { this.$nextTick(() => { @@ -78,7 +78,7 @@ export default { ? this.$refs.compactEl && this.$refs.compactEl.offsetHeight : this.$refs.formEl && this.$refs.formEl.offsetHeight; - this.componentHeight = elHeight + COMMIT_ITEM_PADDING; + this.componentHeight = elHeight; }); }, afterEndTransition() { diff --git a/app/assets/javascripts/ide/components/repo_file.vue b/app/assets/javascripts/ide/components/repo_file.vue index 14946f8c9fa..7bc865058c6 100644 --- a/app/assets/javascripts/ide/components/repo_file.vue +++ b/app/assets/javascripts/ide/components/repo_file.vue @@ -122,11 +122,11 @@ export default {
+ + · + + class="note-timestamp system-note-separator"> import $ from 'jquery'; -import tooltip from '../../../vue_shared/directives/tooltip'; -import Icon from '../../../vue_shared/components/icon.vue'; -import { dasherize } from '../../../lib/utils/text_utility'; -import eventHub from '../../event_hub'; +import axios from '~/lib/utils/axios_utils'; +import { dasherize } from '~/lib/utils/text_utility'; +import { __ } from '~/locale'; +import createFlash from '~/flash'; +import tooltip from '~/vue_shared/directives/tooltip'; +import Icon from '~/vue_shared/components/icon.vue'; + /** - * Renders either a cancel, retry or play icon pointing to the given path. + * Renders either a cancel, retry or play icon button and handles the post request + * + * Used in: + * - mr widget mini pipeline graph: `mr_widget_pipeline.vue` + * - pipelines table + * - pipelines table in merge request page + * - pipelines table in commit page + * - pipelines detail page in big graph */ export default { components: { @@ -32,16 +42,10 @@ export default { required: true, }, - requestFinishedFor: { - type: String, - required: false, - default: '', - }, }, data() { return { isDisabled: false, - linkRequested: '', }; }, @@ -51,19 +55,28 @@ export default { return `${actionIconDash} js-icon-${actionIconDash}`; }, }, - watch: { - requestFinishedFor() { - if (this.requestFinishedFor === this.linkRequested) { - this.isDisabled = false; - } - }, - }, methods: { + /** + * The request should not be handled here. + * However due to this component being used in several + * different apps it avoids repetition & complexity. + * + */ onClickAction() { $(this.$el).tooltip('hide'); - eventHub.$emit('postAction', this.link); - this.linkRequested = this.link; + this.isDisabled = true; + + axios.post(`${this.link}.json`) + .then(() => { + this.isDisabled = false; + this.$emit('pipelineActionRequestComplete'); + }) + .catch(() => { + this.isDisabled = false; + + createFlash(__('An error occurred while making the request.')); + }); }, }, }; @@ -80,6 +93,6 @@ btn-transparent ci-action-icon-container ci-action-icon-wrapper" data-container="body" :disabled="isDisabled" > - + diff --git a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue b/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue index 4027d26098f..7bfe11ab8cd 100644 --- a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue @@ -42,11 +42,6 @@ export default { type: Object, required: true, }, - requestFinishedFor: { - type: String, - required: false, - default: '', - }, }, computed: { @@ -76,11 +71,15 @@ export default { e.stopPropagation(); }); }, + + pipelineActionRequestComplete() { + this.$emit('pipelineActionRequestComplete'); + }, }, }; diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index f1a8a46dda4..45517416e93 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -279,251 +279,14 @@ ul.indent-list { padding: 10px 0 0 30px; } - // Specific styles for tree list @keyframes spin-avatar { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } -.groups-list-tree-container { - .has-no-search-results { - text-align: center; - padding: $gl-padding; - font-style: italic; - color: $well-light-text-color; - } - - > .group-list-tree > .group-row.has-children:first-child { - border-top: 0; - } -} - -.group-list-tree { - .avatar-container.content-loading { - position: relative; - - > a, - > a .avatar { - height: 100%; - border-radius: 50%; - } - - > a { - padding: 2px; - - .avatar { - border: 2px solid $white-normal; - - &.identicon { - line-height: 15px; - } - } - } - - &::after { - content: ""; - position: absolute; - height: 100%; - width: 100%; - background-color: transparent; - border: 2px outset $kdb-border; - border-radius: 50%; - animation: spin-avatar 3s infinite linear; - } - } - - .folder-toggle-wrap { - float: left; - line-height: $list-text-height; - font-size: 0; - - span { - font-size: $gl-font-size; - } - } - - .folder-caret, - .item-type-icon { - display: inline-block; - } - - .folder-caret { - width: 15px; - - svg { - margin-bottom: 2px; - } - } - - .item-type-icon { - margin-top: 2px; - width: 20px; - } - - > .group-row:not(.has-children) { - .folder-caret { - opacity: 0; - } - } - - .content-list li:last-child { - padding-bottom: 0; - } - - .group-list-tree { - margin-bottom: 0; - margin-left: 30px; - position: relative; - - &::before { - content: ''; - display: block; - width: 0; - position: absolute; - top: 5px; - bottom: 0; - left: -16px; - border-left: 2px solid $border-white-normal; - } - - .group-row { - position: relative; - - &::before { - content: ""; - display: block; - width: 10px; - height: 0; - border-top: 2px solid $border-white-normal; - position: absolute; - top: 30px; - left: -16px; - } - - &:last-child::before { - background: $white-light; - height: auto; - top: 30px; - bottom: 0; - } - - &.being-removed { - opacity: 0.5; - } - } - } - - .group-row { - padding: 0; - - &.has-children { - border-top: 0; - } - - &:first-child { - border-top: 1px solid $white-normal; - } - - &:last-of-type { - .group-row-contents:not(:hover) { - border-bottom: 1px solid transparent; - } - } - } - - .group-row-contents { - padding: 10px 10px 8px; - border-top: solid 1px transparent; - border-bottom: solid 1px $white-normal; - - &:hover { - border-color: $row-hover-border; - background-color: $row-hover; - cursor: pointer; - } - - .avatar-container > a { - width: 100%; - text-decoration: none; - } - - &.has-more-items { - display: block; - padding: 20px 10px; - } - - .stats { - position: relative; - line-height: 46px; - - > span { - display: inline-flex; - align-items: center; - height: 16px; - min-width: 30px; - } - - > span:last-child { - margin-right: 0; - } - - .stat-value { - margin: 2px 0 0 5px; - } - } - - .controls { - margin-left: 5px; - - > .btn { - margin-right: $btn-xs-side-margin; - } - } - } - - .project-row-contents .stats { - line-height: inherit; - - > span:first-child { - margin-left: 25px; - } - - .item-visibility { - margin-right: 0; - } - - .last-updated { - position: absolute; - right: 12px; - min-width: 250px; - text-align: right; - color: $gl-text-color-secondary; - } - } -} - .namespace-title { .tooltip-inner { max-width: 350px; } } - -ul.group-list-tree { - li.group-row { - > .group-row-contents .title { - line-height: $list-text-height; - } - - &.has-description > .group-row-contents .title { - line-height: inherit; - } - } -} - -.js-groups-list-holder { - .groups-list-loading { - font-size: 34px; - text-align: center; - } -} diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index 011d38532b4..6bb40bae9ed 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -1,5 +1,3 @@ -@import './issues/issue_count_badge'; - [v-cloak] { display: none; } diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index 6ee8b33bd39..409b7285f82 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -18,6 +18,10 @@ .group-row { @include basic-list-stats; + + .description p { + margin-bottom: 0; + } } .ldap-group-links { @@ -237,3 +241,231 @@ overflow-y: unset; } } + +.groups-list-tree-container { + .has-no-search-results { + text-align: center; + padding: $gl-padding; + font-style: italic; + color: $well-light-text-color; + } + + > .group-list-tree > .group-row.has-children:first-child { + border-top: 0; + } +} + +.group-list-tree { + .avatar-container.content-loading { + position: relative; + + > a, + > a .avatar { + height: 100%; + border-radius: 50%; + } + + > a { + padding: 2px; + + .avatar { + border: 2px solid $white-normal; + + &.identicon { + line-height: 15px; + } + } + } + + &::after { + content: ""; + position: absolute; + height: 100%; + width: 100%; + background-color: transparent; + border: 2px outset $kdb-border; + border-radius: 50%; + animation: spin-avatar 3s infinite linear; + } + } + + .folder-toggle-wrap { + float: left; + line-height: $list-text-height; + font-size: 0; + + span { + font-size: $gl-font-size; + } + } + + .folder-caret, + .item-type-icon { + display: inline-block; + } + + .folder-caret { + width: 15px; + + svg { + margin-bottom: 2px; + } + } + + .item-type-icon { + margin-top: 2px; + width: 20px; + } + + > .group-row:not(.has-children) { + .folder-caret { + opacity: 0; + } + } + + .content-list li:last-child { + padding-bottom: 0; + } + + .group-list-tree { + margin-bottom: 0; + margin-left: 30px; + position: relative; + + &::before { + content: ''; + display: block; + width: 0; + position: absolute; + top: 5px; + bottom: 0; + left: -16px; + border-left: 2px solid $border-white-normal; + } + + .group-row { + position: relative; + + &::before { + content: ""; + display: block; + width: 10px; + height: 0; + border-top: 2px solid $border-white-normal; + position: absolute; + top: 30px; + left: -16px; + } + + &:last-child::before { + background: $white-light; + height: auto; + top: 30px; + bottom: 0; + } + + &.being-removed { + opacity: 0.5; + } + } + } + + .group-row { + padding: 0; + + &.has-children { + border-top: 0; + } + + &:first-child { + border-top: 1px solid $white-normal; + } + } + + .group-row-contents { + padding: $gl-padding-top; + + &:hover { + border-color: $row-hover-border; + background-color: $row-hover; + cursor: pointer; + } + + .avatar-container > a { + width: 100%; + text-decoration: none; + } + + &.has-more-items { + display: block; + padding: 20px 10px; + } + + .stats { + position: relative; + line-height: 46px; + + > span { + display: inline-flex; + align-items: center; + height: 16px; + min-width: 30px; + } + + > span:last-child { + margin-right: 0; + } + + .stat-value { + margin: 2px 0 0 5px; + } + } + + .controls { + margin-left: 5px; + + > .btn { + margin-right: $btn-xs-side-margin; + } + } + } + + .project-row-contents .stats { + line-height: inherit; + + > span:first-child { + margin-left: 25px; + } + + .item-visibility { + margin-right: 0; + } + + .last-updated { + position: absolute; + right: 12px; + min-width: 250px; + text-align: right; + color: $gl-text-color-secondary; + } + } +} + +ul.group-list-tree { + li.group-row { + > .group-row-contents .title { + line-height: $list-text-height; + } + + &.has-description > .group-row-contents .title { + line-height: inherit; + } + } +} + +.js-groups-list-holder { + .groups-list-loading { + font-size: 34px; + text-align: center; + } +} diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index b2dad4a358a..a8110f069d4 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -197,9 +197,21 @@ } &.assignee { - .author_link:hover { - .author { - text-decoration: underline; + .author_link { + display: block; + padding-left: 42px; + position: relative; + + &:hover { + .author { + text-decoration: underline; + } + } + + .avatar { + left: 0; + position: absolute; + top: 0; } } } diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index b9390450477..0d17b9bae7e 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -1,5 +1,3 @@ -@import "./issues/issue_count_badge"; - .issues-list { .issue { padding: 10px 0 10px $gl-padding; diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 42fe319b931..feee964f9bb 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -455,6 +455,10 @@ ul.notes { white-space: normal; } + .system-note-separator { + color: $gl-text-color-disabled; + } + a:hover { text-decoration: underline; } diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 02803e7b040..1264d977b2f 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -66,13 +66,9 @@ } } - .btn-group { - &.open { - .btn-default { - background-color: $white-normal; - border-color: $border-white-normal; - } - } + .btn-group.open .btn-default { + background-color: $white-normal; + border-color: $border-white-normal; } .btn .text-center { @@ -361,16 +357,14 @@ &:not(:first-child) { margin-left: 44px; - .left-connector { - &::before { - content: ''; - position: absolute; - top: 48%; - left: -44px; - border-top: 2px solid $border-color; - width: 44px; - height: 1px; - } + .left-connector::before { + content: ''; + position: absolute; + top: 48%; + left: -44px; + border-top: 2px solid $border-color; + width: 44px; + height: 1px; } } } @@ -386,22 +380,16 @@ &:last-child { .build { // Remove right connecting horizontal line from first build in last stage - &:first-child { - &::after { - border: 0; - } + &:first-child::after { + border: 0; } // Remove right curved connectors from all builds in last stage - &:not(:first-child) { - &::after { - border: 0; - } + &:not(:first-child)::after { + border: 0; } // Remove opposite curve - .curve { - &::before { - display: none; - } + .curve::before { + display: none; } } } @@ -409,16 +397,12 @@ &:first-child { .build { // Remove left curved connectors from all builds in first stage - &:not(:first-child) { - &::before { - border: 0; - } + &:not(:first-child)::before { + border: 0; } // Remove opposite curve - .curve { - &::after { - display: none; - } + .curve::after { + display: none; } } } diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss index 00457717f00..175d2779bb7 100644 --- a/app/assets/stylesheets/pages/repo.scss +++ b/app/assets/stylesheets/pages/repo.scss @@ -39,12 +39,15 @@ .ide-file-list { flex: 1; + padding-left: $gl-padding; + padding-right: $gl-padding; + padding-bottom: $grid-size; .file { cursor: pointer; &.file-open { - background: $link-active-background; + background: $white-normal; } &.file-active { @@ -84,12 +87,11 @@ .ide-new-btn { display: none; - margin-right: -8px; } &:hover, &:focus { - background: $link-active-background; + background: $white-normal; .ide-new-btn { display: block; @@ -111,12 +113,11 @@ } } -.file-name, -.file-col-commit-message { +.file-name { display: flex; overflow: visible; align-items: center; - padding: 6px 12px; + width: 100%; } .multi-file-loading-container { @@ -306,8 +307,18 @@ } .preview-container { - height: 100%; - overflow: auto; + flex-grow: 1; + position: relative; + + .md-previewer { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + overflow: auto; + padding: $gl-padding; + } .file-container { background-color: $gray-darker; @@ -347,10 +358,6 @@ color: $diff-image-info-color; } } - - .md-previewer { - padding: $gl-padding; - } } .ide-mode-tabs { @@ -501,7 +508,7 @@ align-items: center; margin-bottom: 0; border-bottom: 1px solid $white-dark; - padding: $gl-btn-padding $gl-padding; + padding: 12px 0; } .multi-file-commit-panel-header-title { @@ -523,32 +530,31 @@ .multi-file-commit-list { flex: 1; overflow: auto; - padding: $gl-padding; + padding: $grid-size 0; + margin-left: -$grid-size; + margin-right: -$grid-size; min-height: 60px; + + .multi-file-commit-list-item { + margin-left: 0; + margin-right: 0; + } + + &.help-block { + margin-left: 0; + right: 0; + } } .multi-file-commit-list-item { - display: flex; - padding: 0; - align-items: center; - border-radius: $border-radius-default; - .multi-file-discard-btn { display: none; margin-top: -2px; margin-left: auto; - margin-right: $grid-size; color: $gl-link-color; - - &:focus, - &:hover { - text-decoration: underline; - } } &:hover { - background: $white-normal; - .multi-file-discard-btn { display: flex; } @@ -584,25 +590,39 @@ } } +.multi-file-commit-list-item, +.ide-file-list .file { + display: flex; + align-items: center; + margin-left: -$grid-size; + margin-right: -$grid-size; + padding: $grid-size / 2 $grid-size; + border-radius: $border-radius-default; + text-align: left; + + &:hover, + &:focus { + background: $white-normal; + } +} + .multi-file-commit-list-path { - padding: $grid-size / 2; - padding-left: $grid-size; + padding: 0; background: none; border: 0; text-align: left; width: 100%; - min-width: 0; + + &:hover, + &:focus { + outline: 0; + } svg { min-width: 16px; vertical-align: middle; display: inline-block; } - - &:hover, - &:focus { - outline: 0; - } } .multi-file-commit-list-file-path { @@ -619,12 +639,18 @@ .multi-file-commit-form { position: relative; - padding: $gl-padding; background-color: $white-light; - border-top: 1px solid $white-dark; border-left: 1px solid $white-dark; transition: all 0.3s ease; + > form, + > .commit-form-compact { + padding: $gl-padding 0; + margin-left: $gl-padding; + margin-right: $gl-padding; + border-top: 1px solid $white-dark; + } + .btn { font-size: $gl-font-size; } @@ -787,8 +813,9 @@ display: flex; flex: 1; flex-direction: column; - width: 100%; min-height: 140px; + margin-left: $gl-padding; + margin-right: $gl-padding; &.is-first { border-bottom: 1px solid $white-dark; @@ -979,9 +1006,8 @@ .ide-tree-header { display: flex; align-items: center; - padding: 10px 0; - margin-left: 10px; - margin-right: 10px; + margin-bottom: 8px; + padding: 12px 0; border-bottom: 1px solid $white-dark; .ide-new-btn { @@ -1012,9 +1038,9 @@ .commit-form-slide-up-enter-active, .commit-form-slide-up-leave-active { position: absolute; - top: 16px; - left: 16px; - right: 16px; + top: 0; + left: 0; + right: 0; transition: all 0.3s ease; } diff --git a/app/controllers/boards/issues_controller.rb b/app/controllers/boards/issues_controller.rb index 7d7ff217e5d..09e143c23e8 100644 --- a/app/controllers/boards/issues_controller.rb +++ b/app/controllers/boards/issues_controller.rb @@ -94,7 +94,7 @@ module Boards def serialize_as_json(resource) resource.as_json( - only: [:id, :iid, :project_id, :title, :confidential, :due_date, :relative_position], + only: [:id, :iid, :project_id, :title, :confidential, :due_date, :relative_position, :weight], labels: true, issue_endpoints: true, include_full_project_path: board.group_board?, diff --git a/app/controllers/concerns/accepts_pending_invitations.rb b/app/controllers/concerns/accepts_pending_invitations.rb new file mode 100644 index 00000000000..6e8aef52b52 --- /dev/null +++ b/app/controllers/concerns/accepts_pending_invitations.rb @@ -0,0 +1,15 @@ +module AcceptsPendingInvitations + extend ActiveSupport::Concern + + def accept_pending_invitations + return unless resource.active_for_authentication? + + clear_stored_location_for_resource if resource.accept_pending_invitations!.any? + end + + def clear_stored_location_for_resource + session_key = stored_location_key_for(resource) + + session.delete(session_key) + end +end diff --git a/app/controllers/confirmations_controller.rb b/app/controllers/confirmations_controller.rb index 6d9c38d9581..7bc46a6ccc0 100644 --- a/app/controllers/confirmations_controller.rb +++ b/app/controllers/confirmations_controller.rb @@ -1,4 +1,6 @@ class ConfirmationsController < Devise::ConfirmationsController + include AcceptsPendingInvitations + def almost_there flash[:notice] = nil render layout: "devise_empty" @@ -11,6 +13,8 @@ class ConfirmationsController < Devise::ConfirmationsController end def after_confirmation_path_for(resource_name, resource) + accept_pending_invitations + # incoming resource can either be a :user or an :email if signed_in?(:user) after_sign_in(resource) diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb index f0e5d2aa94e..12a6cd11f80 100644 --- a/app/controllers/profiles/keys_controller.rb +++ b/app/controllers/profiles/keys_controller.rb @@ -23,7 +23,7 @@ class Profiles::KeysController < Profiles::ApplicationController def destroy @key = current_user.keys.find(params[:id]) - @key.destroy + Keys::DestroyService.new(current_user).execute(@key) respond_to do |format| format.html { redirect_to profile_keys_url, status: 302 } diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index b7f548e0e63..1d1184d46d1 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -23,8 +23,12 @@ class Projects::CommitController < Projects::ApplicationController respond_to do |format| format.html { render } - format.diff { render text: @commit.to_diff } - format.patch { render text: @commit.to_patch } + format.diff do + send_git_diff(@project.repository, @commit.diff_refs) + end + format.patch do + send_git_patch(@project.repository, @commit.diff_refs) + end end end diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index f7417a6a5aa..6b40fc2fe68 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -18,19 +18,12 @@ class Projects::PipelinesController < Projects::ApplicationController .page(params[:page]) .per(30) - @running_count = PipelinesFinder - .new(project, scope: 'running').execute.count + @running_count = limited_pipelines_count(project, 'running') + @pending_count = limited_pipelines_count(project, 'pending') + @finished_count = limited_pipelines_count(project, 'finished') + @pipelines_count = limited_pipelines_count(project) - @pending_count = PipelinesFinder - .new(project, scope: 'pending').execute.count - - @finished_count = PipelinesFinder - .new(project, scope: 'finished').execute.count - - @pipelines_count = PipelinesFinder - .new(project).execute.count - - @pipelines.map(&:commit) # List commits for batch loading + Gitlab::Ci::Pipeline::Preloader.preload(@pipelines) respond_to do |format| format.html @@ -41,7 +34,7 @@ class Projects::PipelinesController < Projects::ApplicationController pipelines: PipelineSerializer .new(project: @project, current_user: @current_user) .with_pagination(request, response) - .represent(@pipelines), + .represent(@pipelines, disable_coverage: true), count: { all: @pipelines_count, running: @running_count, @@ -185,4 +178,10 @@ class Projects::PipelinesController < Projects::ApplicationController def authorize_update_pipeline! return access_denied! unless can?(current_user, :update_pipeline, @pipeline) end + + def limited_pipelines_count(project, scope = nil) + finder = PipelinesFinder.new(project, scope: scope) + + view_context.limited_counter_with_delimiter(finder.execute) + end end diff --git a/app/controllers/projects/settings/integrations_controller.rb b/app/controllers/projects/settings/integrations_controller.rb index 1ff08cce8cb..d9fecfecc40 100644 --- a/app/controllers/projects/settings/integrations_controller.rb +++ b/app/controllers/projects/settings/integrations_controller.rb @@ -11,7 +11,14 @@ module Projects @hook = ProjectHook.new # Services - @services = @project.find_or_initialize_services + @services = @project.find_or_initialize_services(exceptions: service_exceptions) + end + + private + + # Returns a list of services that should be hidden from the list + def service_exceptions + @project.disabled_services.dup end end end diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 1848c806c41..f5a222b3a48 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -1,5 +1,6 @@ class RegistrationsController < Devise::RegistrationsController include Recaptcha::Verify + include AcceptsPendingInvitations before_action :whitelist_query_limiting, only: [:destroy] @@ -16,6 +17,7 @@ class RegistrationsController < Devise::RegistrationsController end if !Gitlab::Recaptcha.load_configurations! || verify_recaptcha + accept_pending_invitations super else flash[:alert] = 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.' @@ -60,7 +62,7 @@ class RegistrationsController < Devise::RegistrationsController def after_sign_up_path_for(user) Gitlab::AppLogger.info("User Created: username=#{user.username} email=#{user.email} ip=#{request.remote_ip} confirmed:#{user.confirmed?}") - user.confirmed? ? dashboard_projects_path : users_almost_there_path + user.confirmed? ? stored_location_for(user) || dashboard_projects_path : users_almost_there_path end def after_inactive_sign_up_path_for(resource) diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 7ed9b1fc6d0..c6ef79ce15e 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -6,7 +6,7 @@ # klass - actual class like Issue or MergeRequest # current_user - which user use # params: -# scope: 'created-by-me' or 'assigned-to-me' or 'all' +# scope: 'created_by_me' or 'assigned_to_me' or 'all' # state: 'opened' or 'closed' or 'all' # group_id: integer # project_id: integer @@ -282,9 +282,9 @@ class IssuableFinder return items.none if current_user_related? && !current_user case params[:scope] - when 'created-by-me', 'authored' + when 'created_by_me', 'authored' items.where(author_id: current_user.id) - when 'assigned-to-me' + when 'assigned_to_me' items.assigned_to(current_user) else items @@ -426,6 +426,7 @@ class IssuableFinder end def current_user_related? - params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me' + scope = params[:scope] + scope == 'created_by_me' || scope == 'authored' || scope == 'assigned_to_me' end end diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb index 2a27ff0e386..1787b4899cd 100644 --- a/app/finders/issues_finder.rb +++ b/app/finders/issues_finder.rb @@ -5,7 +5,7 @@ # Arguments: # current_user - which user use # params: -# scope: 'created-by-me' or 'assigned-to-me' or 'all' +# scope: 'created_by_me' or 'assigned_to_me' or 'all' # state: 'open' or 'closed' or 'all' # group_id: integer # project_id: integer diff --git a/app/finders/merge_requests_finder.rb b/app/finders/merge_requests_finder.rb index 64dc1e6af0f..e2240e5e0d8 100644 --- a/app/finders/merge_requests_finder.rb +++ b/app/finders/merge_requests_finder.rb @@ -5,7 +5,7 @@ # Arguments: # current_user - which user use # params: -# scope: 'created-by-me' or 'assigned-to-me' or 'all' +# scope: 'created_by_me' or 'assigned_to_me' or 'all' # state: 'open', 'closed', 'merged', or 'all' # group_id: integer # project_id: integer diff --git a/app/finders/personal_projects_finder.rb b/app/finders/personal_projects_finder.rb index 3ad4bd5f066..5aea0cb8192 100644 --- a/app/finders/personal_projects_finder.rb +++ b/app/finders/personal_projects_finder.rb @@ -13,7 +13,7 @@ class PersonalProjectsFinder < UnionFinder def execute(current_user = nil) segments = all_projects(current_user) - find_union(segments, Project).includes(:namespace).order_id_desc + find_union(segments, Project).includes(:namespace).order_updated_desc end private diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 61c10c427dd..495430931aa 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -184,7 +184,7 @@ module Ci end def playable? - action? && (manual? || complete?) + action? && (manual? || retryable?) end def action? @@ -599,6 +599,7 @@ module Ci break variables unless persisted? variables + .concat(pipeline.persisted_variables) .append(key: 'CI_JOB_ID', value: id.to_s) .append(key: 'CI_JOB_TOKEN', value: token, public: false) .append(key: 'CI_BUILD_ID', value: id.to_s) @@ -661,7 +662,7 @@ module Ci Gitlab::Ci::Variables::Collection.new.tap do |variables| break variables unless gitlab_deploy_token - variables.append(key: 'CI_DEPLOY_USER', value: gitlab_deploy_token.name) + variables.append(key: 'CI_DEPLOY_USER', value: gitlab_deploy_token.username) variables.append(key: 'CI_DEPLOY_PASSWORD', value: gitlab_deploy_token.token, public: false) end end diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index b1412f2bcb2..5eb30f4aaa0 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -411,7 +411,18 @@ module Ci end def has_warnings? - builds.latest.failed_but_allowed.any? + number_of_warnings.positive? + end + + def number_of_warnings + BatchLoader.for(id).batch(default_value: 0) do |pipeline_ids, loader| + Build.where(commit_id: pipeline_ids) + .latest + .failed_but_allowed + .group(:commit_id) + .count + .each { |id, amount| loader.call(id, amount) } + end end def set_config_source @@ -517,9 +528,14 @@ module Ci strong_memoize(:legacy_trigger) { trigger_requests.first } end + def persisted_variables + Gitlab::Ci::Variables::Collection.new.tap do |variables| + variables.append(key: 'CI_PIPELINE_ID', value: id.to_s) if persisted? + end + end + def predefined_variables Gitlab::Ci::Variables::Collection.new - .append(key: 'CI_PIPELINE_ID', value: id.to_s) .append(key: 'CI_PIPELINE_IID', value: iid.to_s) .append(key: 'CI_CONFIG_PATH', value: ci_yaml_file_path) .append(key: 'CI_PIPELINE_SOURCE', value: source.to_s) diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index e6f1ed519be..530eacf4be0 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -75,7 +75,7 @@ module Ci project_type: 3 } - cached_attr_reader :version, :revision, :platform, :architecture, :contacted_at, :ip_address + cached_attr_reader :version, :revision, :platform, :architecture, :ip_address, :contacted_at chronic_duration_attr :maximum_timeout_human_readable, :maximum_timeout diff --git a/app/models/clusters/applications/prometheus.rb b/app/models/clusters/applications/prometheus.rb index 7b25d8c4089..c702c4ee807 100644 --- a/app/models/clusters/applications/prometheus.rb +++ b/app/models/clusters/applications/prometheus.rb @@ -49,6 +49,11 @@ module Clusters # 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) + rescue Kubeclient::HttpError + # If users have mistakenly set parameters or removed the depended clusters, + # `proxy_url` could raise an exception because gitlab can not communicate with the cluster. + # Since `PrometheusAdapter#can_query?` is eargely loaded on environement pages in gitlab, + # we need to silence the exceptions end private diff --git a/app/models/commit.rb b/app/models/commit.rb index b46f9f34689..56d4c86774e 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -224,8 +224,34 @@ class Commit Gitlab::ClosingIssueExtractor.new(project, current_user).closed_by_message(safe_message) end + def lazy_author + BatchLoader.for(author_email.downcase).batch do |emails, loader| + # A Hash that maps user Emails to the corresponding User objects. The + # Emails at this point are the _primary_ Emails of the Users. + users_for_emails = User + .by_any_email(emails) + .each_with_object({}) { |user, hash| hash[user.email] = user } + + users_for_ids = users_for_emails + .values + .each_with_object({}) { |user, hash| hash[user.id] = user } + + # Some commits may have used an alternative Email address. In this case we + # need to query the "emails" table to map those addresses to User objects. + Email + .where(email: emails - users_for_emails.keys) + .pluck(:email, :user_id) + .each { |(email, id)| users_for_emails[email] = users_for_ids[id] } + + users_for_emails.each { |email, user| loader.call(email, user) } + end + end + def author - User.find_by_any_email(author_email.downcase) + # We use __sync so that we get the actual objects back (including an actual + # nil), instead of a wrapper, as returning a wrapped nil breaks a lot of + # code. + lazy_author.__sync end request_cache(:author) { author_email.downcase } diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 97d89422594..a7d05722287 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -2,6 +2,7 @@ class CommitStatus < ActiveRecord::Base include HasStatus include Importable include AfterCommitQueue + include Presentable self.table_name = 'ci_builds' diff --git a/app/models/concerns/redis_cacheable.rb b/app/models/concerns/redis_cacheable.rb index b889f4202dc..b5425295130 100644 --- a/app/models/concerns/redis_cacheable.rb +++ b/app/models/concerns/redis_cacheable.rb @@ -7,7 +7,11 @@ module RedisCacheable class_methods do def cached_attr_reader(*attributes) attributes.each do |attribute| - define_method("#{attribute}") do + define_method(attribute) do + unless self.has_attribute?(attribute) + raise ArgumentError, "`cached_attr_reader` requires the #{self.class.name}\##{attribute} attribute to have a database column" + end + cached_attribute(attribute) || read_attribute(attribute) end end @@ -15,13 +19,16 @@ module RedisCacheable end def cached_attribute(attribute) - (cached_attributes || {})[attribute] + cached_value = (cached_attributes || {})[attribute] + cast_value_from_cache(attribute, cached_value) if cached_value 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 + + clear_memoization(:cached_attributes) end private @@ -38,4 +45,12 @@ module RedisCacheable end end end + + def cast_value_from_cache(attribute, value) + if Gitlab.rails5? + self.class.type_for_attribute(attribute).cast(value) + else + self.class.column_for_attribute(attribute).type_cast_from_database(value) + end + end end diff --git a/app/models/concerns/sortable.rb b/app/models/concerns/sortable.rb index cefa5c13c5f..db7254c27e0 100644 --- a/app/models/concerns/sortable.rb +++ b/app/models/concerns/sortable.rb @@ -12,8 +12,8 @@ module Sortable scope :order_created_asc, -> { reorder(created_at: :asc) } scope :order_updated_desc, -> { reorder(updated_at: :desc) } scope :order_updated_asc, -> { reorder(updated_at: :asc) } - scope :order_name_asc, -> { reorder(name: :asc) } - scope :order_name_desc, -> { reorder(name: :desc) } + scope :order_name_asc, -> { reorder("lower(name) asc") } + scope :order_name_desc, -> { reorder("lower(name) desc") } end module ClassMethods diff --git a/app/models/concerns/time_trackable.rb b/app/models/concerns/time_trackable.rb index 73fc5048dcf..1caf47072bc 100644 --- a/app/models/concerns/time_trackable.rb +++ b/app/models/concerns/time_trackable.rb @@ -53,6 +53,10 @@ module TimeTrackable Gitlab::TimeTrackingFormatter.output(time_estimate) end + def time_estimate=(val) + val.is_a?(Integer) ? super([val, Gitlab::Database::MAX_INT_VALUE].min) : super(val) + end + private def touchable? diff --git a/app/models/project.rb b/app/models/project.rb index 0975e64e995..35c873830a7 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -997,7 +997,7 @@ class Project < ActiveRecord::Base available_services_names = Service.available_services_names - exceptions - available_services_names.map do |service_name| + available_services = available_services_names.map do |service_name| service = find_service(services, service_name) if service @@ -1014,6 +1014,14 @@ class Project < ActiveRecord::Base end end end + + available_services.reject do |service| + disabled_services.include?(service.to_param) + end + end + + def disabled_services + [] end def find_or_initialize_service(name) diff --git a/app/models/user.rb b/app/models/user.rb index 474fde36c02..8ef3c3ceff0 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -860,6 +860,16 @@ class User < ActiveRecord::Base confirmed? && !temp_oauth_email? end + def accept_pending_invitations! + pending_invitations.select do |member| + member.accept_invite!(self) + end + end + + def pending_invitations + Member.where(invite_email: verified_emails).invite + end + def all_emails all_emails = [] all_emails << email unless temp_oauth_email? diff --git a/app/presenters/ci/build_presenter.rb b/app/presenters/ci/build_presenter.rb index 4873d7ce662..e0aaa5cb736 100644 --- a/app/presenters/ci/build_presenter.rb +++ b/app/presenters/ci/build_presenter.rb @@ -1,16 +1,5 @@ module Ci - class BuildPresenter < Gitlab::View::Presenter::Delegated - CALLOUT_FAILURE_MESSAGES = { - unknown_failure: 'There is an unknown failure, please try again', - script_failure: 'There has been a script failure. Check the job log for more information', - api_failure: 'There has been an API failure, please try again', - stuck_or_timeout_failure: 'There has been a timeout failure or the job got stuck. Check your timeout limits or try again', - runner_system_failure: 'There has been a runner system failure, please try again', - missing_dependency_failure: 'There has been a missing dependency failure, check the job log for more information' - }.freeze - - presents :build - + class BuildPresenter < CommitStatusPresenter def erased_by_user? # Build can be erased through API, therefore it does not have # `erased_by` user assigned in that case. @@ -44,14 +33,6 @@ module Ci "#{subject.name} - #{detailed_status.status_tooltip}" end - def callout_failure_message - CALLOUT_FAILURE_MESSAGES[failure_reason.to_sym] - end - - def recoverable? - failed? && !unrecoverable? - end - private def tooltip_for_badge @@ -61,9 +42,5 @@ module Ci def detailed_status @detailed_status ||= subject.detailed_status(user) end - - def unrecoverable? - script_failure? || missing_dependency_failure? - end end end diff --git a/app/presenters/commit_status_presenter.rb b/app/presenters/commit_status_presenter.rb new file mode 100644 index 00000000000..c7f7aa836bd --- /dev/null +++ b/app/presenters/commit_status_presenter.rb @@ -0,0 +1,24 @@ +class CommitStatusPresenter < Gitlab::View::Presenter::Delegated + CALLOUT_FAILURE_MESSAGES = { + unknown_failure: 'There is an unknown failure, please try again', + script_failure: 'There has been a script failure. Check the job log for more information', + api_failure: 'There has been an API failure, please try again', + stuck_or_timeout_failure: 'There has been a timeout failure or the job got stuck. Check your timeout limits or try again', + runner_system_failure: 'There has been a runner system failure, please try again', + missing_dependency_failure: 'There has been a missing dependency failure, check the job log for more information' + }.freeze + + presents :build + + def callout_failure_message + CALLOUT_FAILURE_MESSAGES[failure_reason.to_sym] + end + + def recoverable? + failed? && !unrecoverable? + end + + def unrecoverable? + script_failure? || missing_dependency_failure? + end +end diff --git a/app/presenters/generic_commit_status_presenter.rb b/app/presenters/generic_commit_status_presenter.rb new file mode 100644 index 00000000000..da09df29a37 --- /dev/null +++ b/app/presenters/generic_commit_status_presenter.rb @@ -0,0 +1,2 @@ +class GenericCommitStatusPresenter < CommitStatusPresenter +end diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb index 6457294b285..f782b411b84 100644 --- a/app/serializers/pipeline_entity.rb +++ b/app/serializers/pipeline_entity.rb @@ -4,7 +4,11 @@ class PipelineEntity < Grape::Entity expose :id expose :user, using: UserEntity expose :active?, as: :active - expose :coverage + + # Coverage isn't always necessary (e.g. when displaying project pipelines in + # the UI). Instead of creating an entirely different entity we just allow the + # disabling of this specific field whenever necessary. + expose :coverage, unless: proc { options[:disable_coverage] } expose :source expose :created_at, :updated_at diff --git a/app/services/keys/base_service.rb b/app/services/keys/base_service.rb index f78791932a7..df8e82f5f60 100644 --- a/app/services/keys/base_service.rb +++ b/app/services/keys/base_service.rb @@ -2,7 +2,7 @@ module Keys class BaseService attr_accessor :user, :params - def initialize(user, params) + def initialize(user, params = {}) @user, @params = user, params @ip_address = @params.delete(:ip_address) end diff --git a/app/services/keys/destroy_service.rb b/app/services/keys/destroy_service.rb new file mode 100644 index 00000000000..785cfa3a1d8 --- /dev/null +++ b/app/services/keys/destroy_service.rb @@ -0,0 +1,12 @@ +module Keys + class DestroyService < ::Keys::BaseService + def execute(key) + key.destroy if destroy_possible?(key) + end + + # overriden in EE::Keys::DestroyService + def destroy_possible?(key) + true + end + end +end diff --git a/app/services/lfs/unlock_file_service.rb b/app/services/lfs/unlock_file_service.rb index 6c93dc69bb0..7eb89339a92 100644 --- a/app/services/lfs/unlock_file_service.rb +++ b/app/services/lfs/unlock_file_service.rb @@ -2,14 +2,14 @@ module Lfs class UnlockFileService < BaseService def execute unless can?(current_user, :push_code, project) - raise Gitlab::GitAccess::UnauthorizedError, 'You have no permissions' + 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) + error(_('Lock not found'), 404) rescue => ex error(ex.message, 500) end @@ -24,9 +24,9 @@ module Lfs success(lock: lock, http_status: :ok) elsif forced - error('You must have master access to force delete a lock', 403) + 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) + error(_("%{lock_path} is locked by GitLab User %{lock_user_id}") % { lock_path: lock.path, lock_user_id: lock.user_id }, 403) end end diff --git a/app/services/milestones/base_service.rb b/app/services/milestones/base_service.rb index 4963601ea8b..cce0863d611 100644 --- a/app/services/milestones/base_service.rb +++ b/app/services/milestones/base_service.rb @@ -5,6 +5,7 @@ module Milestones def initialize(parent, user, params = {}) @parent, @current_user, @params = parent, user, params.dup + super end end end diff --git a/app/views/admin/broadcast_messages/_form.html.haml b/app/views/admin/broadcast_messages/_form.html.haml index 5a4ed1c3a2a..813ad451b44 100644 --- a/app/views/admin/broadcast_messages/_form.html.haml +++ b/app/views/admin/broadcast_messages/_form.html.haml @@ -27,11 +27,11 @@ .col-sm-10 = f.color_field :font, class: "form-control" .form-group - = f.label :starts_at, class: 'control-label' + = f.label :starts_at, _("Starts at (UTC)"), class: 'control-label' .col-sm-10.datetime-controls = f.datetime_select :starts_at, {}, class: 'form-control form-control-inline' .form-group - = f.label :ends_at, class: 'control-label' + = f.label :ends_at, _("Ends at (UTC)"), class: 'control-label' .col-sm-10.datetime-controls = f.datetime_select :ends_at, {}, class: 'form-control form-control-inline' .form-actions diff --git a/app/views/admin/runners/_runner.html.haml b/app/views/admin/runners/_runner.html.haml index 6e76e7c2768..99fbbaec487 100644 --- a/app/views/admin/runners/_runner.html.haml +++ b/app/views/admin/runners/_runner.html.haml @@ -33,7 +33,7 @@ = tag %td - if runner.contacted_at - #{time_ago_in_words(runner.contacted_at)} ago + = time_ago_with_tooltip runner.contacted_at - else Never %td.admin-runner-btn-group-cell diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index 1c5b4aecabb..9a3a03a7671 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -121,7 +121,7 @@ %tr %td.shortcut .key g - .key e + .key v %td Go to the project's activity feed %tr @@ -172,6 +172,18 @@ .key m %td Go to merge requests + %tr + %td.shortcut + .key g + .key e + %td + Go to environments + %tr + %td.shortcut + .key g + .key k + %td + Go to kubernetes %tr %td.shortcut .key g @@ -219,6 +231,17 @@ %td.shortcut .key y %td Go to file permalink + %tbody + %tr + %th + %th Web IDE + %tr + %td.shortcut + - if browser.platform.mac? + .key ⌘ p + - else + .key ctrl p + %td Go to file .col-lg-4 %table.shortcut-mappings %tbody.hidden-shortcut.network{ style: 'display:none' } diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml index c3ea592a6b5..f3af15d771d 100644 --- a/app/views/layouts/nav/sidebar/_project.html.haml +++ b/app/views/layouts/nav/sidebar/_project.html.haml @@ -19,7 +19,7 @@ = nav_link(path: 'projects#show', html_options: { class: "fly-out-top-item" } ) do = link_to project_path(@project) do %strong.fly-out-top-item-name - = _('Overview') + = _('Project') %li.divider.fly-out-top-item = nav_link(path: 'projects#show') do = link_to project_path(@project), title: _('Project details'), class: 'shortcuts-project' do @@ -154,7 +154,7 @@ = nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :artifacts]) do = link_to project_pipelines_path(@project), class: 'shortcuts-pipelines' do .nav-icon-container - = sprite_icon('pipeline') + = sprite_icon('rocket') %span.nav-item-name = _('CI / CD') @@ -212,7 +212,7 @@ - if project_nav_tab? :clusters - show_cluster_hint = show_gke_cluster_integration_callout?(@project) = nav_link(controller: [:clusters, :user, :gcp]) do - = link_to project_clusters_path(@project), title: _('Kubernetes'), class: 'shortcuts-cluster' do + = link_to project_clusters_path(@project), title: _('Kubernetes'), class: 'shortcuts-kubernetes' do %span = _('Kubernetes') - if show_cluster_hint diff --git a/app/views/notify/member_invited_email.html.haml b/app/views/notify/member_invited_email.html.haml index b8b75da3f2f..6730172242b 100644 --- a/app/views/notify/member_invited_email.html.haml +++ b/app/views/notify/member_invited_email.html.haml @@ -4,7 +4,7 @@ by = link_to member.created_by.name, user_url(member.created_by) to join the - = link_to member_source.human_name, member_source.web_url + = link_to member_source.human_name, member_source.public? ? member_source.web_url : invite_url(@token) #{member_source.model_name.singular} as #{member.human_access}. %p diff --git a/app/views/projects/forks/new.html.haml b/app/views/projects/forks/new.html.haml old mode 100755 new mode 100644 diff --git a/app/views/projects/registry/repositories/index.html.haml b/app/views/projects/registry/repositories/index.html.haml index 76f57320f99..2a683d5be0f 100644 --- a/app/views/projects/registry/repositories/index.html.haml +++ b/app/views/projects/registry/repositories/index.html.haml @@ -30,7 +30,7 @@ %br %p - deploy_token = link_to(_('deploy token'), help_page_path('user/project/deploy_tokens/index', anchor: 'read-container-registry-images'), target: '_blank') - = s_('ContainerRegistry|You can also %{deploy_token} for read-only access to the registry images.').html_safe % { deploy_token: deploy_token } + = s_('ContainerRegistry|You can also use a %{deploy_token} for read-only access to the registry images.').html_safe % { deploy_token: deploy_token } %br %p = s_('ContainerRegistry|Once you log in, you’re free to create and upload a container image using the common %{build} and %{push} commands').html_safe % { build: "build".html_safe, push: "push".html_safe } diff --git a/app/views/shared/notes/_note.html.haml b/app/views/shared/notes/_note.html.haml index 893a7f26ebd..d4e67b5e7e3 100644 --- a/app/views/shared/notes/_note.html.haml +++ b/app/views/shared/notes/_note.html.haml @@ -41,8 +41,9 @@ - if note.system %span.system-note-message = markdown_field(note, :note) - %a{ href: "##{dom_id(note)}" } - = time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago') + %span.system-note-separator + · + %a.system-note-separator{ href: "##{dom_id(note)}" }= time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago') - unless note.system? .note-actions - if note.for_personal_snippet? diff --git a/app/views/shared/runners/show.html.haml b/app/views/shared/runners/show.html.haml index 480a224b6d5..93c6ba86640 100644 --- a/app/views/shared/runners/show.html.haml +++ b/app/views/shared/runners/show.html.haml @@ -66,6 +66,6 @@ %td Last contact %td - if @runner.contacted_at - #{time_ago_in_words(@runner.contacted_at)} ago + = time_ago_with_tooltip @runner.contacted_at - else Never diff --git a/changelogs/unreleased/10244-add-project-ci-cd-settings.yml b/changelogs/unreleased/10244-add-project-ci-cd-settings.yml deleted file mode 100644 index 89f9a0fe03c..00000000000 --- a/changelogs/unreleased/10244-add-project-ci-cd-settings.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Introduce new ProjectCiCdSetting model with group_runners_enabled -merge_request: 18144 -author: -type: performance diff --git a/changelogs/unreleased/16957-issue-due-email.yml b/changelogs/unreleased/16957-issue-due-email.yml deleted file mode 100644 index 83944ca4f73..00000000000 --- a/changelogs/unreleased/16957-issue-due-email.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add cron job to email users on issue due date -merge_request: 17985 -author: Stuart Nelson -type: added diff --git a/changelogs/unreleased/18524-fix-double-brackets-in-wiki-markdown.yml b/changelogs/unreleased/18524-fix-double-brackets-in-wiki-markdown.yml new file mode 100644 index 00000000000..9287243a7e3 --- /dev/null +++ b/changelogs/unreleased/18524-fix-double-brackets-in-wiki-markdown.yml @@ -0,0 +1,5 @@ +--- +title: Fix double-brackets being linkified in wiki markdown +merge_request: 18524 +author: brewingcode +type: fixed diff --git a/changelogs/unreleased/19861-expand-api-render-an-arbitrary-markdown-document.yml b/changelogs/unreleased/19861-expand-api-render-an-arbitrary-markdown-document.yml new file mode 100644 index 00000000000..a97e8a2b5cc --- /dev/null +++ b/changelogs/unreleased/19861-expand-api-render-an-arbitrary-markdown-document.yml @@ -0,0 +1,5 @@ +--- +title: Add API endpoint to render markdown text +merge_request: 18926 +author: "@blackst0ne" +type: added diff --git a/changelogs/unreleased/21677-run-pipeline-word.yml b/changelogs/unreleased/21677-run-pipeline-word.yml deleted file mode 100644 index 9cc280244e4..00000000000 --- a/changelogs/unreleased/21677-run-pipeline-word.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Improves wording in new pipeline page -merge_request: -author: -type: other diff --git a/changelogs/unreleased/25010-collapsed-sidebar-tooltips.yml b/changelogs/unreleased/25010-collapsed-sidebar-tooltips.yml deleted file mode 100644 index 1226fb4eefd..00000000000 --- a/changelogs/unreleased/25010-collapsed-sidebar-tooltips.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Improve tooltips in collapsed right sidebar -merge_request: 17714 -author: -type: changed diff --git a/changelogs/unreleased/30739-fix-joined-information-on-project-members-page.yml b/changelogs/unreleased/30739-fix-joined-information-on-project-members-page.yml deleted file mode 100644 index f2d5b503661..00000000000 --- a/changelogs/unreleased/30739-fix-joined-information-on-project-members-page.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix `joined` information on project members page -merge_request: 18290 -author: Fabian Schneider -type: fixed diff --git a/changelogs/unreleased/32617-fix-template-selector-menu-visibility.yml b/changelogs/unreleased/32617-fix-template-selector-menu-visibility.yml deleted file mode 100644 index c73be5a901e..00000000000 --- a/changelogs/unreleased/32617-fix-template-selector-menu-visibility.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Fix template selector menu visibility when toggling preview mode in file edit - view -merge_request: 18118 -author: Fabian Schneider -type: fixed diff --git a/changelogs/unreleased/33697-pipelines-json-endpoint.yml b/changelogs/unreleased/33697-pipelines-json-endpoint.yml deleted file mode 100644 index d44e2729415..00000000000 --- a/changelogs/unreleased/33697-pipelines-json-endpoint.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Use VueJS for rendering pipeline stages -merge_request: -author: -type: changed diff --git a/changelogs/unreleased/33697-remove-ujs-action-big-graph.yml b/changelogs/unreleased/33697-remove-ujs-action-big-graph.yml deleted file mode 100644 index 43ce52c1209..00000000000 --- a/changelogs/unreleased/33697-remove-ujs-action-big-graph.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Prevent pipeline actions in dropdown to redirct to a new page -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/34262-show-current-labels-when-editing.yml b/changelogs/unreleased/34262-show-current-labels-when-editing.yml deleted file mode 100644 index d3b15b9ddd1..00000000000 --- a/changelogs/unreleased/34262-show-current-labels-when-editing.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Keep current labels visible when editing them in the sidebar -merge_request: -author: -type: changed diff --git a/changelogs/unreleased/36762-reconcile-project-templates-with-auto-devops.yml b/changelogs/unreleased/36762-reconcile-project-templates-with-auto-devops.yml deleted file mode 100644 index 8169b18f875..00000000000 --- a/changelogs/unreleased/36762-reconcile-project-templates-with-auto-devops.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Reconcile project templates with Auto DevOps -merge_request: 18737 -author: -type: changed diff --git a/changelogs/unreleased/36983-osw-heading-labels-color-fix.yml b/changelogs/unreleased/36983-osw-heading-labels-color-fix.yml deleted file mode 100644 index 082e0544dea..00000000000 --- a/changelogs/unreleased/36983-osw-heading-labels-color-fix.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Adjust issue boards list header label text color -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/39584-nesting-depth-5-pages-pipelines.yml b/changelogs/unreleased/39584-nesting-depth-5-pages-pipelines.yml new file mode 100644 index 00000000000..9f07fcdfa0b --- /dev/null +++ b/changelogs/unreleased/39584-nesting-depth-5-pages-pipelines.yml @@ -0,0 +1,5 @@ +--- +title: Apply NestingDepth (level 5) (pages/pipelines.scss) +merge_request: 18830 +author: Takuya Noguchi +type: other diff --git a/changelogs/unreleased/40402-time-estimate-system-notes-can-be-confusing.yml b/changelogs/unreleased/40402-time-estimate-system-notes-can-be-confusing.yml deleted file mode 100644 index e47577f9058..00000000000 --- a/changelogs/unreleased/40402-time-estimate-system-notes-can-be-confusing.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add a comma to the time estimate system notes -merge_request: 18326 -author: -type: changed diff --git a/changelogs/unreleased/40487-axios-pipelines.yml b/changelogs/unreleased/40487-axios-pipelines.yml deleted file mode 100644 index 437d5e87e1a..00000000000 --- a/changelogs/unreleased/40487-axios-pipelines.yml +++ /dev/null @@ -1,4 +0,0 @@ -title: Replace vue resource with axios in pipelines table -merge_request: -author: -type: other \ No newline at end of file diff --git a/changelogs/unreleased/41059-calculate-artifact-size-more-efficiently.yml b/changelogs/unreleased/41059-calculate-artifact-size-more-efficiently.yml deleted file mode 100644 index e3f94bbf081..00000000000 --- a/changelogs/unreleased/41059-calculate-artifact-size-more-efficiently.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Improve DB performance of calculating total artifacts size -merge_request: 17839 -author: -type: performance diff --git a/changelogs/unreleased/41082-make-deploykeys-table-more-clearly-structured.yml b/changelogs/unreleased/41082-make-deploykeys-table-more-clearly-structured.yml deleted file mode 100644 index 23704c2b37b..00000000000 --- a/changelogs/unreleased/41082-make-deploykeys-table-more-clearly-structured.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Make project deploy keys table more clearly structured -merge_request: 18279 -author: -type: changed diff --git a/changelogs/unreleased/41748-vertical-misalignment-login-box.yml b/changelogs/unreleased/41748-vertical-misalignment-login-box.yml deleted file mode 100644 index 77a97400323..00000000000 --- a/changelogs/unreleased/41748-vertical-misalignment-login-box.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Refactor CSS to eliminate vertical misalignment of login nav -merge_request: 16275 -author: Takuya Noguchi -type: fixed diff --git a/changelogs/unreleased/41981-allow-group-owner-to-enable-runners-from-subgroups.yml b/changelogs/unreleased/41981-allow-group-owner-to-enable-runners-from-subgroups.yml deleted file mode 100644 index 30481e7af84..00000000000 --- a/changelogs/unreleased/41981-allow-group-owner-to-enable-runners-from-subgroups.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: 'Allow group owner to enable runners from subgroups (#41981)' -merge_request: 18009 -author: -type: fixed diff --git a/changelogs/unreleased/42099-port-push-mirroring-to-ce-ce-port-v-2.yml b/changelogs/unreleased/42099-port-push-mirroring-to-ce-ce-port-v-2.yml deleted file mode 100644 index f23521ea416..00000000000 --- a/changelogs/unreleased/42099-port-push-mirroring-to-ce-ce-port-v-2.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Adds push mirrors to GitLab Community Edition -merge_request: 18715 -author: -type: changed diff --git a/changelogs/unreleased/42531-open-invite-404.yml b/changelogs/unreleased/42531-open-invite-404.yml new file mode 100644 index 00000000000..73729f4a929 --- /dev/null +++ b/changelogs/unreleased/42531-open-invite-404.yml @@ -0,0 +1,5 @@ +--- +title: Automatically accepts project/group invite by email after user signup +merge_request: 17634 +author: Jacopo Beschi @jacopo-beschi +type: changed diff --git a/changelogs/unreleased/42543-hide-divergence-graph-on-branches-for-mobile.yml b/changelogs/unreleased/42543-hide-divergence-graph-on-branches-for-mobile.yml deleted file mode 100644 index 7452a264bfd..00000000000 --- a/changelogs/unreleased/42543-hide-divergence-graph-on-branches-for-mobile.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove ahead/behind graphs on project branches on mobile -merge_request: 18415 -author: Takuya Noguchi -type: other diff --git a/changelogs/unreleased/42803-show-new-branch-mr-button.yml b/changelogs/unreleased/42803-show-new-branch-mr-button.yml deleted file mode 100644 index d689ff7f001..00000000000 --- a/changelogs/unreleased/42803-show-new-branch-mr-button.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Show new branch/mr button even when branch exists -merge_request: 17712 -author: Jacopo Beschi @jacopo-beschi -type: added diff --git a/changelogs/unreleased/42889-avoid-return-inside-block.yml b/changelogs/unreleased/42889-avoid-return-inside-block.yml deleted file mode 100644 index e3e1341ddcc..00000000000 --- a/changelogs/unreleased/42889-avoid-return-inside-block.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Rubocop rule to avoid returning from a block -merge_request: 18000 -author: Jacopo Beschi @jacopo-beschi -type: added diff --git a/changelogs/unreleased/42936-improve-ns-factory-avoid-duplicates.yml b/changelogs/unreleased/42936-improve-ns-factory-avoid-duplicates.yml deleted file mode 100644 index 54b523b65a1..00000000000 --- a/changelogs/unreleased/42936-improve-ns-factory-avoid-duplicates.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Fix discussions API setting created_at for notable in a group or notable in - a project in a group with owners -merge_request: 18464 -author: -type: fixed diff --git a/changelogs/unreleased/43111-controller-projects-mergerequestscontroller-index-executes-more-than-100-sql-queries.yml b/changelogs/unreleased/43111-controller-projects-mergerequestscontroller-index-executes-more-than-100-sql-queries.yml deleted file mode 100644 index 120e319acfb..00000000000 --- a/changelogs/unreleased/43111-controller-projects-mergerequestscontroller-index-executes-more-than-100-sql-queries.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Reduce queries on merge requests list page for merge requests from forks -merge_request: 18561 -author: -type: performance diff --git a/changelogs/unreleased/43404-pipelines-commit.yml b/changelogs/unreleased/43404-pipelines-commit.yml deleted file mode 100644 index 0b9a4a6451f..00000000000 --- a/changelogs/unreleased/43404-pipelines-commit.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Breaks commit not found message in pipelines table -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/43466-make-auto-devops-settings-first-class.yml b/changelogs/unreleased/43466-make-auto-devops-settings-first-class.yml deleted file mode 100644 index f5c5415159c..00000000000 --- a/changelogs/unreleased/43466-make-auto-devops-settings-first-class.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Create settings section for autodevops -merge_request: 18321 -author: -type: changed diff --git a/changelogs/unreleased/43469-gcp-account-offer.yml b/changelogs/unreleased/43469-gcp-account-offer.yml deleted file mode 100644 index 323a4b81731..00000000000 --- a/changelogs/unreleased/43469-gcp-account-offer.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add GCP signup offer to cluster index / create pages -merge_request: 18684 -author: -type: added diff --git a/changelogs/unreleased/43557-osw-present-merge-sha-commit.yml b/changelogs/unreleased/43557-osw-present-merge-sha-commit.yml deleted file mode 100644 index a7128f7481e..00000000000 --- a/changelogs/unreleased/43557-osw-present-merge-sha-commit.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Display merge commit SHA in merge widget after merge -merge_request: 18722 -author: -type: added diff --git a/changelogs/unreleased/43567-replace-gke.yml b/changelogs/unreleased/43567-replace-gke.yml deleted file mode 100644 index 8ec79fc3d4d..00000000000 --- a/changelogs/unreleased/43567-replace-gke.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Replace GKE acronym with Google Kubernetes Engine -merge_request: -author: -type: other diff --git a/changelogs/unreleased/43617-mailsig.yml b/changelogs/unreleased/43617-mailsig.yml deleted file mode 100644 index 2c7568e32ca..00000000000 --- a/changelogs/unreleased/43617-mailsig.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Use RFC 3676 mail signature delimiters -merge_request: 17979 -author: Enrico Scholz -type: changed diff --git a/changelogs/unreleased/44059-specify-variables-when-executing-a-manual-pipeline-from-the-ui.yml b/changelogs/unreleased/44059-specify-variables-when-executing-a-manual-pipeline-from-the-ui.yml deleted file mode 100644 index 8854eeb5fba..00000000000 --- a/changelogs/unreleased/44059-specify-variables-when-executing-a-manual-pipeline-from-the-ui.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Enable specifying variables when executing a manual pipeline -merge_request: 18440 -author: -type: changed diff --git a/changelogs/unreleased/44224-remove-gl.yml b/changelogs/unreleased/44224-remove-gl.yml deleted file mode 100644 index 1c792883f09..00000000000 --- a/changelogs/unreleased/44224-remove-gl.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Removes modal boards store and mixins from global scope -merge_request: -author: -type: other diff --git a/changelogs/unreleased/44296-commit-path.yml b/changelogs/unreleased/44296-commit-path.yml deleted file mode 100644 index b658178f8dc..00000000000 --- a/changelogs/unreleased/44296-commit-path.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Verifiy if pipeline has commit idetails and render information in MR widget - when branch is deleted -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/44447-expose-deploy-token-to-ci-cd.yml b/changelogs/unreleased/44447-expose-deploy-token-to-ci-cd.yml deleted file mode 100644 index d01b797b1ff..00000000000 --- a/changelogs/unreleased/44447-expose-deploy-token-to-ci-cd.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Expose Deploy Token data as environment varialbes on CI/CD jobs -merge_request: 18414 -author: -type: added diff --git a/changelogs/unreleased/44541-fix-file-tree-commit-status-cache.yml b/changelogs/unreleased/44541-fix-file-tree-commit-status-cache.yml deleted file mode 100644 index ff734fe0c05..00000000000 --- a/changelogs/unreleased/44541-fix-file-tree-commit-status-cache.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix pipeline status in branch/tag tree page -merge_request: 17995 -author: -type: fixed diff --git a/changelogs/unreleased/44582-clear-pipeline-status-cache.yml b/changelogs/unreleased/44582-clear-pipeline-status-cache.yml deleted file mode 100644 index 1777f2ffaab..00000000000 --- a/changelogs/unreleased/44582-clear-pipeline-status-cache.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Now `rake cache:clear` will also clear pipeline status cache -merge_request: 18257 -author: -type: fixed diff --git a/changelogs/unreleased/44697-prevue.yml b/changelogs/unreleased/44697-prevue.yml deleted file mode 100644 index 9fdce5869ae..00000000000 --- a/changelogs/unreleased/44697-prevue.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Make toggle markdown preview shortcut only toggle selected field -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/44799-api-naming-issue-scope.yml b/changelogs/unreleased/44799-api-naming-issue-scope.yml new file mode 100644 index 00000000000..75c6ea4cd0d --- /dev/null +++ b/changelogs/unreleased/44799-api-naming-issue-scope.yml @@ -0,0 +1,5 @@ +--- +title: Rename issue scope created-by-me to created_by_me, and assigned-to-me to assigned_to_me +merge_request: 44799 +author: +type: deprecated diff --git a/changelogs/unreleased/44833-ide-clean-up-status-bar.yml b/changelogs/unreleased/44833-ide-clean-up-status-bar.yml deleted file mode 100644 index 4c827e57195..00000000000 --- a/changelogs/unreleased/44833-ide-clean-up-status-bar.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Clean up WebIDE status bar and add useful info -merge_request: -author: -type: changed diff --git a/changelogs/unreleased/44834-ide-remove-branch-from-bottom-bar.yml b/changelogs/unreleased/44834-ide-remove-branch-from-bottom-bar.yml deleted file mode 100644 index d3f838ad362..00000000000 --- a/changelogs/unreleased/44834-ide-remove-branch-from-bottom-bar.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove branch name from the status bar of WebIDE -merge_request: -author: -type: changed diff --git a/changelogs/unreleased/44879.yml b/changelogs/unreleased/44879.yml deleted file mode 100644 index b51e057bb7b..00000000000 --- a/changelogs/unreleased/44879.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add the signature verfication badge to the compare view -merge_request: 18245 -author: Marc Shaw -type: added diff --git a/changelogs/unreleased/44985-fix-protected-branch-delete-modal.yml b/changelogs/unreleased/44985-fix-protected-branch-delete-modal.yml deleted file mode 100644 index 4af2af2a561..00000000000 --- a/changelogs/unreleased/44985-fix-protected-branch-delete-modal.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix confirmation modal for deleting a protected branch -merge_request: 18176 -author: Paul Bonaud @PaulRbR -type: fixed diff --git a/changelogs/unreleased/45065-users-projects-json-sort.yml b/changelogs/unreleased/45065-users-projects-json-sort.yml new file mode 100644 index 00000000000..89a1d7eb36f --- /dev/null +++ b/changelogs/unreleased/45065-users-projects-json-sort.yml @@ -0,0 +1,5 @@ +--- +title: Order UsersController#projects.json by updated_at +merge_request: 18227 +author: Takuya Noguchi +type: other diff --git a/changelogs/unreleased/45159-fix-illustration.yml b/changelogs/unreleased/45159-fix-illustration.yml deleted file mode 100644 index 3b9cb45b916..00000000000 --- a/changelogs/unreleased/45159-fix-illustration.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Adds illustration for when job log was erased -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/45397-update-faraday_middleware-to-0-12-2.yml b/changelogs/unreleased/45397-update-faraday_middleware-to-0-12-2.yml deleted file mode 100644 index 3370ec3feba..00000000000 --- a/changelogs/unreleased/45397-update-faraday_middleware-to-0-12-2.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update faraday_middlewar to 0.12.2 -merge_request: 18397 -author: Takuya Noguchi -type: security diff --git a/changelogs/unreleased/45398-fix-rss-button.yml b/changelogs/unreleased/45398-fix-rss-button.yml deleted file mode 100644 index 2c8ee6f0564..00000000000 --- a/changelogs/unreleased/45398-fix-rss-button.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix tabs container styles to make RSS button clickable -merge_request: 18559 -author: -type: fixed diff --git a/changelogs/unreleased/45436-markdown-is-not-rendering-error-loading-viewer-undefined-method-html_escape.yml b/changelogs/unreleased/45436-markdown-is-not-rendering-error-loading-viewer-undefined-method-html_escape.yml deleted file mode 100644 index 0f1d111ca58..00000000000 --- a/changelogs/unreleased/45436-markdown-is-not-rendering-error-loading-viewer-undefined-method-html_escape.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix undefined `html_escape` method during markdown rendering -merge_request: 18418 -author: -type: fixed diff --git a/changelogs/unreleased/45451-user-deletion-modal-with-same-info-for-delete-user-or-delete-user-and-contributions.yml b/changelogs/unreleased/45451-user-deletion-modal-with-same-info-for-delete-user-or-delete-user-and-contributions.yml deleted file mode 100644 index 707a18745c8..00000000000 --- a/changelogs/unreleased/45451-user-deletion-modal-with-same-info-for-delete-user-or-delete-user-and-contributions.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Correct text and functionality for delete user / delete user and contributions - modal. -merge_request: 18463 -author: Marc Schwede -type: fixed diff --git a/changelogs/unreleased/45481-sane-pages-artifacts.yml b/changelogs/unreleased/45481-sane-pages-artifacts.yml deleted file mode 100644 index b9c68b70012..00000000000 --- a/changelogs/unreleased/45481-sane-pages-artifacts.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Don't automatically remove artifacts for pages jobs after pages:deploy has - run -merge_request: 18628 -author: -type: fixed diff --git a/changelogs/unreleased/45572-members-invitations-scheduled-before-commit.yml b/changelogs/unreleased/45572-members-invitations-scheduled-before-commit.yml deleted file mode 100644 index 7cdea436d47..00000000000 --- a/changelogs/unreleased/45572-members-invitations-scheduled-before-commit.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Ensure member notifications are sent after the member actual creation/update in the DB -merge_request: 18538 -author: -type: fixed diff --git a/changelogs/unreleased/45576-fix-create-project-for-user-endpoint.yml b/changelogs/unreleased/45576-fix-create-project-for-user-endpoint.yml deleted file mode 100644 index 12631c75b44..00000000000 --- a/changelogs/unreleased/45576-fix-create-project-for-user-endpoint.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix project creation for user endpoint when jobs_enabled parameter supplied -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/45666-project-ci-lint-links.yml b/changelogs/unreleased/45666-project-ci-lint-links.yml deleted file mode 100644 index dbf803c0921..00000000000 --- a/changelogs/unreleased/45666-project-ci-lint-links.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update links to /ci/lint with ones to project ci/lint -merge_request: 18539 -author: Takuya Noguchi -type: fixed diff --git a/changelogs/unreleased/45761-replace-actionview-time_ago_in_words.yml b/changelogs/unreleased/45761-replace-actionview-time_ago_in_words.yml deleted file mode 100644 index adf4db90407..00000000000 --- a/changelogs/unreleased/45761-replace-actionview-time_ago_in_words.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Replace time_ago_in_words with JS-based one -merge_request: 18607 -author: Takuya Noguchi -type: performance diff --git a/changelogs/unreleased/45934-ide-firefox-scroll-md-preview.yml b/changelogs/unreleased/45934-ide-firefox-scroll-md-preview.yml new file mode 100644 index 00000000000..b9e70bc5679 --- /dev/null +++ b/changelogs/unreleased/45934-ide-firefox-scroll-md-preview.yml @@ -0,0 +1,5 @@ +--- +title: Fix unscrollable Markdown preview of WebIDE on Firefox +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/46049-import-export-import-is-broken-due-to-the-addition-of-a-ci-table.yml b/changelogs/unreleased/46049-import-export-import-is-broken-due-to-the-addition-of-a-ci-table.yml deleted file mode 100644 index 77e4bb50082..00000000000 --- a/changelogs/unreleased/46049-import-export-import-is-broken-due-to-the-addition-of-a-ci-table.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Resolve Import/Export ci_cd_settings error updating the project -merge_request: 46049 -author: -type: fixed diff --git a/changelogs/unreleased/46082-runner-contacted_at-is-not-always-a-time-type.yml b/changelogs/unreleased/46082-runner-contacted_at-is-not-always-a-time-type.yml new file mode 100644 index 00000000000..07f67251b24 --- /dev/null +++ b/changelogs/unreleased/46082-runner-contacted_at-is-not-always-a-time-type.yml @@ -0,0 +1,5 @@ +--- +title: Fix Runner contacted at tooltip cache. +merge_request: 18810 +author: +type: fixed diff --git a/changelogs/unreleased/46177-fix-present-on-generic-commit-status.yml b/changelogs/unreleased/46177-fix-present-on-generic-commit-status.yml new file mode 100644 index 00000000000..2f885c5c927 --- /dev/null +++ b/changelogs/unreleased/46177-fix-present-on-generic-commit-status.yml @@ -0,0 +1,5 @@ +--- +title: Allow CommitStatus class to use presentable methods +merge_request: 18979 +author: +type: fixed diff --git a/changelogs/unreleased/46193-fix-big-estimate.yml b/changelogs/unreleased/46193-fix-big-estimate.yml new file mode 100644 index 00000000000..d0da0c10033 --- /dev/null +++ b/changelogs/unreleased/46193-fix-big-estimate.yml @@ -0,0 +1,5 @@ +--- +title: Fixes 500 error on /estimate BIG_VALUE +merge_request: 18964 +author: Jacopo Beschi @jacopo-beschi +type: fixed diff --git a/changelogs/unreleased/46210-terms-acceptance-dropdown-menu.yml b/changelogs/unreleased/46210-terms-acceptance-dropdown-menu.yml deleted file mode 100644 index 8a7c549e356..00000000000 --- a/changelogs/unreleased/46210-terms-acceptance-dropdown-menu.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: 46210 Display logo and user dropdown on mobile for terms page and fix styling -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/46286-fix-ingress-rbac-default-value.yml b/changelogs/unreleased/46286-fix-ingress-rbac-default-value.yml deleted file mode 100644 index e9cd8977394..00000000000 --- a/changelogs/unreleased/46286-fix-ingress-rbac-default-value.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Disables RBAC on nginx-ingress -merge_request: 18947 -author: -type: fixed diff --git a/changelogs/unreleased/46303_copy_button_fix_embedded_snippets.yml b/changelogs/unreleased/46303_copy_button_fix_embedded_snippets.yml deleted file mode 100644 index c8cdf3672b3..00000000000 --- a/changelogs/unreleased/46303_copy_button_fix_embedded_snippets.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: fixed copy to blipboard button in embed bar of snippets -merge_request: 18923 -author: haseebeqx -type: fixed diff --git a/changelogs/unreleased/46345-kubernetes-popover-illustration-skewed.yml b/changelogs/unreleased/46345-kubernetes-popover-illustration-skewed.yml deleted file mode 100644 index a0e6b39fef6..00000000000 --- a/changelogs/unreleased/46345-kubernetes-popover-illustration-skewed.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Correct skewed Kubernetes popover illustration -merge_request: 18949 -author: -type: fixed diff --git a/changelogs/unreleased/46427-add-keyboard-shortcut-environments.yml b/changelogs/unreleased/46427-add-keyboard-shortcut-environments.yml new file mode 100644 index 00000000000..609968f3230 --- /dev/null +++ b/changelogs/unreleased/46427-add-keyboard-shortcut-environments.yml @@ -0,0 +1,5 @@ +--- +title: Adds keyboard shortcut `g e` for Environments on Project pages +merge_request: 19002 +author: +type: added diff --git a/changelogs/unreleased/46427-add-keyboard-shortcut-kubernetes.yml b/changelogs/unreleased/46427-add-keyboard-shortcut-kubernetes.yml new file mode 100644 index 00000000000..48e51b2615e --- /dev/null +++ b/changelogs/unreleased/46427-add-keyboard-shortcut-kubernetes.yml @@ -0,0 +1,5 @@ +--- +title: Adds keyboard shortcut `g k` for Kubernetes on Project pages +merge_request: 19002 +author: +type: added diff --git a/changelogs/unreleased/winh-new-mergerequest-branch-picker.yml b/changelogs/unreleased/46427-change-keyboard-shortcut-of-activity-feed.yml similarity index 77% rename from changelogs/unreleased/winh-new-mergerequest-branch-picker.yml rename to changelogs/unreleased/46427-change-keyboard-shortcut-of-activity-feed.yml index 401ecd09ef2..9a7cf0d6944 100644 --- a/changelogs/unreleased/winh-new-mergerequest-branch-picker.yml +++ b/changelogs/unreleased/46427-change-keyboard-shortcut-of-activity-feed.yml @@ -1,5 +1,5 @@ --- -title: Load branches on new merge request page asynchronously -merge_request: 18315 +title: Changes keyboard shortcut of Activity feed to `g v` +merge_request: 19002 author: type: changed diff --git a/changelogs/unreleased/46427-remove-outdated-todos-shortcut.yml b/changelogs/unreleased/46427-remove-outdated-todos-shortcut.yml new file mode 100644 index 00000000000..f416e35030e --- /dev/null +++ b/changelogs/unreleased/46427-remove-outdated-todos-shortcut.yml @@ -0,0 +1,5 @@ +--- +title: Removes outdated `g t` shortcut for TODO in favor of `Shift+T` +merge_request: 19002 +author: +type: removed diff --git a/changelogs/unreleased/46454-wrong-value-in-ci-deploy-user.yml b/changelogs/unreleased/46454-wrong-value-in-ci-deploy-user.yml new file mode 100644 index 00000000000..e610e53f71c --- /dev/null +++ b/changelogs/unreleased/46454-wrong-value-in-ci-deploy-user.yml @@ -0,0 +1,5 @@ +--- +title: Fixes deploy token variables on Ci::Build +merge_request: 19047 +author: +type: fixed diff --git a/changelogs/unreleased/4950-unassign-slash-command-preview-fix.yml b/changelogs/unreleased/4950-unassign-slash-command-preview-fix.yml deleted file mode 100644 index 0b8c14ae699..00000000000 --- a/changelogs/unreleased/4950-unassign-slash-command-preview-fix.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix unassign slash command preview -merge_request: 18447 -author: -type: fixed diff --git a/changelogs/unreleased/5750-backport-checksum-git-commanderror-exit-status-128.yml b/changelogs/unreleased/5750-backport-checksum-git-commanderror-exit-status-128.yml deleted file mode 100644 index d778b44c110..00000000000 --- a/changelogs/unreleased/5750-backport-checksum-git-commanderror-exit-status-128.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Raise NoRepository error for non-valid repositories when calculating repository - checksum -merge_request: 18594 -author: -type: fixed diff --git a/changelogs/unreleased/5794-we-should-failover-gracefully-when-we-can-t-connect-to-geo-tracking-database-ce.yml b/changelogs/unreleased/5794-we-should-failover-gracefully-when-we-can-t-connect-to-geo-tracking-database-ce.yml deleted file mode 100644 index 4db0ff4f3a0..00000000000 --- a/changelogs/unreleased/5794-we-should-failover-gracefully-when-we-can-t-connect-to-geo-tracking-database-ce.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: ShaAttribute no longer stops startup if database is missing -merge_request: 18726 -author: -type: fixed diff --git a/changelogs/unreleased/8088_embedded_snippets_support.yml b/changelogs/unreleased/8088_embedded_snippets_support.yml deleted file mode 100644 index 7bd77a69dbd..00000000000 --- a/changelogs/unreleased/8088_embedded_snippets_support.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Adds Embedded Snippets Support -merge_request: 15695 -author: haseebeqx -type: added diff --git a/changelogs/unreleased/ab-44259-atomic-internal-ids-for-all-models.yml b/changelogs/unreleased/ab-44259-atomic-internal-ids-for-all-models.yml deleted file mode 100644 index e154f7dbc85..00000000000 --- a/changelogs/unreleased/ab-44259-atomic-internal-ids-for-all-models.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Transition to atomic internal ids for all models. -merge_request: 44259 -author: -type: other diff --git a/changelogs/unreleased/accessible-text.yml b/changelogs/unreleased/accessible-text.yml deleted file mode 100755 index d39d5a9eb2c..00000000000 --- a/changelogs/unreleased/accessible-text.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Replace "Click" with "Select" to be more inclusive of people with accessibility - requirements -merge_request: 18386 -author: Mark Lapierre -type: other diff --git a/changelogs/unreleased/add-copy-metadata-command.yml b/changelogs/unreleased/add-copy-metadata-command.yml deleted file mode 100644 index 3bf25ae6ce0..00000000000 --- a/changelogs/unreleased/add-copy-metadata-command.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add Copy metadata quick action -merge_request: 16473 -author: Mateusz Bajorski -type: added diff --git a/changelogs/unreleased/add-git-commit-message-predefined-variable.yml b/changelogs/unreleased/add-git-commit-message-predefined-variable.yml deleted file mode 100644 index 183fe69936e..00000000000 --- a/changelogs/unreleased/add-git-commit-message-predefined-variable.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add CI_COMMIT_MESSAGE, CI_COMMIT_TITLE and CI_COMMIT_DESCRIPTION predefined variables -merge_request: 18672 -author: -type: added diff --git a/changelogs/unreleased/add-loading-icon-padding-for-pipeline-environments.yml b/changelogs/unreleased/add-loading-icon-padding-for-pipeline-environments.yml deleted file mode 100644 index a6304418517..00000000000 --- a/changelogs/unreleased/add-loading-icon-padding-for-pipeline-environments.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add loading icon padding for pipeline environments -merge_request: 18631 -author: George Tsiolis -type: fixed diff --git a/changelogs/unreleased/add-padding-to-profile-description.yml b/changelogs/unreleased/add-padding-to-profile-description.yml deleted file mode 100644 index 4628a10eb3f..00000000000 --- a/changelogs/unreleased/add-padding-to-profile-description.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add padding to profile description -merge_request: 18663 -author: George Tsiolis -type: changed diff --git a/changelogs/unreleased/align-project-avatar-on-small-viewports.yml b/changelogs/unreleased/align-project-avatar-on-small-viewports.yml deleted file mode 100644 index 320e7fbc294..00000000000 --- a/changelogs/unreleased/align-project-avatar-on-small-viewports.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Align project avatar on small viewports -merge_request: 18513 -author: George Tsiolis -type: changed diff --git a/changelogs/unreleased/ash-mckenzie-include-sha-with-version.yml b/changelogs/unreleased/ash-mckenzie-include-sha-with-version.yml deleted file mode 100644 index b49c48e0fe1..00000000000 --- a/changelogs/unreleased/ash-mckenzie-include-sha-with-version.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: git SHA is now displayed alongside the GitLab version on the Admin Dashboard -merge_request: -author: -type: added diff --git a/changelogs/unreleased/blackst0ne-add-missing-changelog-type-to-docs.yml b/changelogs/unreleased/blackst0ne-add-missing-changelog-type-to-docs.yml deleted file mode 100644 index f8790fa45aa..00000000000 --- a/changelogs/unreleased/blackst0ne-add-missing-changelog-type-to-docs.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add missing changelog type to docs -merge_request: 18526 -author: "@blackst0ne" -type: other diff --git a/changelogs/unreleased/blackst0ne-replace-spinach-project-builds-artifacts-feature.yml b/changelogs/unreleased/blackst0ne-replace-spinach-project-builds-artifacts-feature.yml deleted file mode 100644 index 98c56cf2b57..00000000000 --- a/changelogs/unreleased/blackst0ne-replace-spinach-project-builds-artifacts-feature.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: 'Replace the `project/builds/artifacts.feature` spinach test with an rspec analog' -merge_request: 18729 -author: '@blackst0ne' -type: other diff --git a/changelogs/unreleased/blackst0ne-replace-spinach-project-commits-branches-feature.yml b/changelogs/unreleased/blackst0ne-replace-spinach-project-commits-branches-feature.yml deleted file mode 100644 index bcfba4ae70d..00000000000 --- a/changelogs/unreleased/blackst0ne-replace-spinach-project-commits-branches-feature.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: "Replace the `project/commits/branches.feature` spinach test with an rspec analog" -merge_request: 18302 -author: "@blackst0ne" -type: other diff --git a/changelogs/unreleased/blackst0ne-replace-spinach-project-commits-comments-feature.yml b/changelogs/unreleased/blackst0ne-replace-spinach-project-commits-comments-feature.yml deleted file mode 100644 index e7077f27555..00000000000 --- a/changelogs/unreleased/blackst0ne-replace-spinach-project-commits-comments-feature.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Replace the `project/commits/comments.feature` spinach test with an rspec analog -merge_request: 18356 -author: "@blackst0ne" -type: other diff --git a/changelogs/unreleased/blackst0ne-replace-spinach-project-issues-milestones-feature.yml b/changelogs/unreleased/blackst0ne-replace-spinach-project-issues-milestones-feature.yml deleted file mode 100644 index 0dcac0a80eb..00000000000 --- a/changelogs/unreleased/blackst0ne-replace-spinach-project-issues-milestones-feature.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Replace the `project/issues/milestones.feature` spinach test with an rspec analog -merge_request: 18300 -author: "@blackst0ne" -type: other diff --git a/changelogs/unreleased/blackst0ne-replace-spinach-project-source-markdown-render-feature.yml b/changelogs/unreleased/blackst0ne-replace-spinach-project-source-markdown-render-feature.yml deleted file mode 100644 index 657ed782880..00000000000 --- a/changelogs/unreleased/blackst0ne-replace-spinach-project-source-markdown-render-feature.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Replace the `project/source/markdown_render.feature` spinach test with an rspec analog -merge_request: 18525 -author: "@blackst0ne" -type: other diff --git a/changelogs/unreleased/break-issue-title-for-board-card-title-and-issueable-header-text.yml b/changelogs/unreleased/break-issue-title-for-board-card-title-and-issueable-header-text.yml deleted file mode 100644 index 7acde223962..00000000000 --- a/changelogs/unreleased/break-issue-title-for-board-card-title-and-issueable-header-text.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Break issue title for board card title and issuable header text -merge_request: 18674 -author: George Tsiolis -type: changed diff --git a/changelogs/unreleased/bvl-enforce-terms.yml b/changelogs/unreleased/bvl-enforce-terms.yml deleted file mode 100644 index 1bb1ecdf623..00000000000 --- a/changelogs/unreleased/bvl-enforce-terms.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allow admins to enforce accepting Terms of Service on an instance -merge_request: 18570 -author: -type: added diff --git a/changelogs/unreleased/bvl-restrict-api-git-for-terms.yml b/changelogs/unreleased/bvl-restrict-api-git-for-terms.yml deleted file mode 100644 index 49cd04b065b..00000000000 --- a/changelogs/unreleased/bvl-restrict-api-git-for-terms.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Block access to the API & git for users that did not accept enforced Terms - of Service -merge_request: 18816 -author: -type: other diff --git a/changelogs/unreleased/bvl-shared-groups-on-group-page.yml b/changelogs/unreleased/bvl-shared-groups-on-group-page.yml deleted file mode 100644 index 6c0703fd138..00000000000 --- a/changelogs/unreleased/bvl-shared-groups-on-group-page.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Show shared projects on group page -merge_request: 18390 -author: -type: fixed diff --git a/changelogs/unreleased/bw-add-console-message.yml b/changelogs/unreleased/bw-add-console-message.yml deleted file mode 100644 index 7994f7caced..00000000000 --- a/changelogs/unreleased/bw-add-console-message.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Output some useful information when running the rails console -merge_request: 18697 -author: -type: added diff --git a/changelogs/unreleased/ccr-incoming-email-regex-anchor.yml b/changelogs/unreleased/ccr-incoming-email-regex-anchor.yml new file mode 100644 index 00000000000..a0d787e570e --- /dev/null +++ b/changelogs/unreleased/ccr-incoming-email-regex-anchor.yml @@ -0,0 +1,3 @@ +title: Add anchor for incoming email regex +merge_request: !18917 +type: added diff --git a/changelogs/unreleased/change-font-for-tables-inside-diff-discussions.yml b/changelogs/unreleased/change-font-for-tables-inside-diff-discussions.yml deleted file mode 100644 index f2810fab208..00000000000 --- a/changelogs/unreleased/change-font-for-tables-inside-diff-discussions.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Change font for tables inside diff discussions -merge_request: 18660 -author: George Tsiolis -type: changed diff --git a/changelogs/unreleased/collapsed-contextual-nav-update.yml b/changelogs/unreleased/collapsed-contextual-nav-update.yml new file mode 100644 index 00000000000..31a32a9e1e9 --- /dev/null +++ b/changelogs/unreleased/collapsed-contextual-nav-update.yml @@ -0,0 +1,6 @@ +--- +title: Renamed 'Overview' to 'Project' in collapsed contextual navigation at a project + level +merge_request: 18996 +author: Constance Okoghenun +type: fixed diff --git a/changelogs/unreleased/create-live-trace-only-if-job-is-complete.yml b/changelogs/unreleased/create-live-trace-only-if-job-is-complete.yml new file mode 100644 index 00000000000..f32c70cf884 --- /dev/null +++ b/changelogs/unreleased/create-live-trace-only-if-job-is-complete.yml @@ -0,0 +1,5 @@ +--- +title: Forbid to patch traces for finished jobs +merge_request: 18969 +author: +type: fixed diff --git a/changelogs/unreleased/deprecation-warning-for-dynamic-milestones.yml b/changelogs/unreleased/deprecation-warning-for-dynamic-milestones.yml deleted file mode 100644 index 3e1ac7b795d..00000000000 --- a/changelogs/unreleased/deprecation-warning-for-dynamic-milestones.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add deprecation message to dynamic milestone pages -merge_request: 17505 -author: -type: added diff --git a/changelogs/unreleased/dm-webhook-catch-blocked-url-exception.yml b/changelogs/unreleased/dm-webhook-catch-blocked-url-exception.yml deleted file mode 100644 index c4f8f7acca6..00000000000 --- a/changelogs/unreleased/dm-webhook-catch-blocked-url-exception.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Ensure web hook 'blocked URL' errors are stored in web hook logs and properly - surfaced to the user -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/docs-use-variables-deploy-policy-for-staging-and-production.yml b/changelogs/unreleased/docs-use-variables-deploy-policy-for-staging-and-production.yml deleted file mode 100644 index aa23a89a175..00000000000 --- a/changelogs/unreleased/docs-use-variables-deploy-policy-for-staging-and-production.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Add documentation about how to use variables to define deploy policies for - staging/production environments -merge_request: 18675 -author: -type: other diff --git a/changelogs/unreleased/dz-add-2fa-filter-admin-api.yml b/changelogs/unreleased/dz-add-2fa-filter-admin-api.yml deleted file mode 100644 index df479e69380..00000000000 --- a/changelogs/unreleased/dz-add-2fa-filter-admin-api.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add 2FA filter to users API for admins only -merge_request: 18503 -author: -type: changed diff --git a/changelogs/unreleased/feature-add-language-in-repository-to-api.yml b/changelogs/unreleased/feature-add-language-in-repository-to-api.yml deleted file mode 100644 index bd9bd377212..00000000000 --- a/changelogs/unreleased/feature-add-language-in-repository-to-api.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: 'API: add languages of project GET /projects/:id/languages' -merge_request: 17770 -author: Roger Rüttimann -type: added diff --git a/changelogs/unreleased/feature-add_target_to_tags.yml b/changelogs/unreleased/feature-add_target_to_tags.yml deleted file mode 100644 index 75816005e1f..00000000000 --- a/changelogs/unreleased/feature-add_target_to_tags.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Expose the target commit ID through the tag API -merge_request: -author: -type: added diff --git a/changelogs/unreleased/feature-display-active-sessions.yml b/changelogs/unreleased/feature-display-active-sessions.yml deleted file mode 100644 index 14cfa66953e..00000000000 --- a/changelogs/unreleased/feature-display-active-sessions.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Display active sessions and allow the user to revoke any of it -merge_request: 17867 -author: Alexis Reigel -type: added diff --git a/changelogs/unreleased/feature-gb-add-regexp-variables-expression.yml b/changelogs/unreleased/feature-gb-add-regexp-variables-expression.yml new file mode 100644 index 00000000000..d77c5b42497 --- /dev/null +++ b/changelogs/unreleased/feature-gb-add-regexp-variables-expression.yml @@ -0,0 +1,5 @@ +--- +title: Add support for variables expression pattern matching syntax +merge_request: 18902 +author: +type: added diff --git a/changelogs/unreleased/feature-runner-per-group.yml b/changelogs/unreleased/feature-runner-per-group.yml deleted file mode 100644 index 162a5fae0a4..00000000000 --- a/changelogs/unreleased/feature-runner-per-group.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allow group masters to configure runners for groups -merge_request: 9646 -author: Alexis Reigel -type: added diff --git a/changelogs/unreleased/feature-show-only-groups-user-is-member-of-in-dashboard.yml b/changelogs/unreleased/feature-show-only-groups-user-is-member-of-in-dashboard.yml deleted file mode 100644 index 6e2273ed9af..00000000000 --- a/changelogs/unreleased/feature-show-only-groups-user-is-member-of-in-dashboard.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: For group dashboard, we no longer show groups which the visitor is not a member of (this applies to admins and auditors) -merge_request: 17884 -author: Roger Rüttimann -type: changed diff --git a/changelogs/unreleased/fix-assignee-name-wrap.yml b/changelogs/unreleased/fix-assignee-name-wrap.yml new file mode 100644 index 00000000000..2407288785f --- /dev/null +++ b/changelogs/unreleased/fix-assignee-name-wrap.yml @@ -0,0 +1,5 @@ +--- +title: Wrapping problem on the issues page has been fixed +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/fix-gb-add-pipeline-builds-foreign-key.yml b/changelogs/unreleased/fix-gb-add-pipeline-builds-foreign-key.yml deleted file mode 100644 index bded7bb7cc4..00000000000 --- a/changelogs/unreleased/fix-gb-add-pipeline-builds-foreign-key.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add database foreign key constraint between pipelines and build -merge_request: 18822 -author: -type: fixed diff --git a/changelogs/unreleased/fix-gb-exclude-persisted-variables-from-environment-name.yml b/changelogs/unreleased/fix-gb-exclude-persisted-variables-from-environment-name.yml new file mode 100644 index 00000000000..92426832f30 --- /dev/null +++ b/changelogs/unreleased/fix-gb-exclude-persisted-variables-from-environment-name.yml @@ -0,0 +1,5 @@ +--- +title: Exclude CI_PIPELINE_ID from variables supported in dynamic environment name +merge_request: 19032 +author: +type: fixed diff --git a/changelogs/unreleased/fix-gb-not-allow-to-trigger-skipped-manual-actions.yml b/changelogs/unreleased/fix-gb-not-allow-to-trigger-skipped-manual-actions.yml new file mode 100644 index 00000000000..c2a788d6ad0 --- /dev/null +++ b/changelogs/unreleased/fix-gb-not-allow-to-trigger-skipped-manual-actions.yml @@ -0,0 +1,5 @@ +--- +title: Do not allow to trigger manual actions that were skipped +merge_request: 18985 +author: +type: fixed diff --git a/changelogs/unreleased/fix-inconsistent-protected-branch-pill-baseline.yml b/changelogs/unreleased/fix-inconsistent-protected-branch-pill-baseline.yml deleted file mode 100644 index fb6dffaf226..00000000000 --- a/changelogs/unreleased/fix-inconsistent-protected-branch-pill-baseline.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fixed inconsistent protected branch pill baseline -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/fix-kube_client-proxy_url-exception.yml b/changelogs/unreleased/fix-kube_client-proxy_url-exception.yml new file mode 100644 index 00000000000..1f64ab9f30f --- /dev/null +++ b/changelogs/unreleased/fix-kube_client-proxy_url-exception.yml @@ -0,0 +1,5 @@ +--- +title: Fix corrupted environment pages with unathorized proxy url +merge_request: 18989 +author: +type: fixed diff --git a/changelogs/unreleased/fix-metrics-content-types.yml b/changelogs/unreleased/fix-metrics-content-types.yml deleted file mode 100644 index a418dccffc3..00000000000 --- a/changelogs/unreleased/fix-metrics-content-types.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix setting Gitlab metrics content types -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/fix-project-mirror-data-schema.yml b/changelogs/unreleased/fix-project-mirror-data-schema.yml deleted file mode 100644 index 107f1fe3b9c..00000000000 --- a/changelogs/unreleased/fix-project-mirror-data-schema.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Fixes database inconsistencies between Community and Enterprise Edition on - import state -merge_request: 18811 -author: -type: fixed diff --git a/changelogs/unreleased/fix-shortcut-close-screen-with-key.yml b/changelogs/unreleased/fix-shortcut-close-screen-with-key.yml deleted file mode 100644 index 9cbc856a075..00000000000 --- a/changelogs/unreleased/fix-shortcut-close-screen-with-key.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix close keyboard shortcuts dialog using the keyboard shortcut -merge_request: 18783 -author: Lars Greiss -type: fixed diff --git a/changelogs/unreleased/fix-wiki-find-page-invalid-encoding.yml b/changelogs/unreleased/fix-wiki-find-page-invalid-encoding.yml deleted file mode 100644 index f003bef8671..00000000000 --- a/changelogs/unreleased/fix-wiki-find-page-invalid-encoding.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix finding wiki pages when they have invalidly-encoded content -merge_request: 18856 -author: -type: fixed diff --git a/changelogs/unreleased/fj-42354-custom-hooks-not-triggered-by-UI-wiki-edit.yml b/changelogs/unreleased/fj-42354-custom-hooks-not-triggered-by-UI-wiki-edit.yml deleted file mode 100644 index 9fe458aba4a..00000000000 --- a/changelogs/unreleased/fj-42354-custom-hooks-not-triggered-by-UI-wiki-edit.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Triggering custom hooks by Wiki UI edit -merge_request: 18251 -author: -type: fixed diff --git a/changelogs/unreleased/fj-45057-improve-ssrf-documentation.yml b/changelogs/unreleased/fj-45057-improve-ssrf-documentation.yml deleted file mode 100644 index b923f442b26..00000000000 --- a/changelogs/unreleased/fj-45057-improve-ssrf-documentation.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Added Webhook SSRF prevention to documentation -merge_request: 18532 -author: -type: other diff --git a/changelogs/unreleased/fj-change-gollum-gems-to-custom-ones.yml b/changelogs/unreleased/fj-change-gollum-gems-to-custom-ones.yml deleted file mode 100644 index 53883e8d907..00000000000 --- a/changelogs/unreleased/fj-change-gollum-gems-to-custom-ones.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Replacing gollum libraries for gitlab custom libs -merge_request: 18343 -author: -type: other diff --git a/changelogs/unreleased/fl-pipelines-details-axios.yml b/changelogs/unreleased/fl-pipelines-details-axios.yml deleted file mode 100644 index 0b72e54cba3..00000000000 --- a/changelogs/unreleased/fl-pipelines-details-axios.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Replace vue resource with axios for pipelines details page -merge_request: -author: -type: other diff --git a/changelogs/unreleased/helm-add-alpine-mirrors.yml b/changelogs/unreleased/helm-add-alpine-mirrors.yml deleted file mode 100644 index 656c4f911d0..00000000000 --- a/changelogs/unreleased/helm-add-alpine-mirrors.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Increase cluster applications installer availability using alpine linux mirrors -merge_request: -author: -type: performance diff --git a/changelogs/unreleased/ide-file-finder.yml b/changelogs/unreleased/ide-file-finder.yml deleted file mode 100644 index 252dd3a30c4..00000000000 --- a/changelogs/unreleased/ide-file-finder.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Added fuzzy file finder to web IDE -merge_request: -author: -type: added diff --git a/changelogs/unreleased/ide-improve-commit-panel.yml b/changelogs/unreleased/ide-improve-commit-panel.yml deleted file mode 100644 index f6237db3039..00000000000 --- a/changelogs/unreleased/ide-improve-commit-panel.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Improve interaction on WebIDE commit panel -merge_request: -author: -type: changed diff --git a/changelogs/unreleased/improve-commit-message-body-rendering.yml b/changelogs/unreleased/improve-commit-message-body-rendering.yml deleted file mode 100644 index 3fb9b03725e..00000000000 --- a/changelogs/unreleased/improve-commit-message-body-rendering.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Improve commit message body rendering and fix responsive compare panels -merge_request: 18725 -author: Constance Okoghenun -type: changed diff --git a/changelogs/unreleased/improve-jobs-queuing-time-metric.yml b/changelogs/unreleased/improve-jobs-queuing-time-metric.yml deleted file mode 100644 index cee8b8523fd..00000000000 --- a/changelogs/unreleased/improve-jobs-queuing-time-metric.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Partition job_queue_duration_seconds with jobs_running_for_project -merge_request: 17730 -author: -type: changed diff --git a/changelogs/unreleased/improve-quick-actions-summary-preview.yml b/changelogs/unreleased/improve-quick-actions-summary-preview.yml deleted file mode 100644 index bc75c169ad7..00000000000 --- a/changelogs/unreleased/improve-quick-actions-summary-preview.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Improve quick actions summary preview -merge_request: 18659 -author: George Tsiolis -type: changed diff --git a/changelogs/unreleased/increase-new-issue-metadata-form-margin.yml b/changelogs/unreleased/increase-new-issue-metadata-form-margin.yml deleted file mode 100644 index a7196f67969..00000000000 --- a/changelogs/unreleased/increase-new-issue-metadata-form-margin.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Increase new issue metadata form margin -merge_request: 18630 -author: George Tsiolis -type: fixed diff --git a/changelogs/unreleased/inform-the-user-when-there-are-no-project-import-options-available.yml b/changelogs/unreleased/inform-the-user-when-there-are-no-project-import-options-available.yml deleted file mode 100644 index c14f21fc644..00000000000 --- a/changelogs/unreleased/inform-the-user-when-there-are-no-project-import-options-available.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Inform the user when there are no project import options available -merge_request: 18716 -author: George Tsiolis -type: changed diff --git a/changelogs/unreleased/issue_43660.yml b/changelogs/unreleased/issue_43660.yml deleted file mode 100644 index d83c0ebcbb5..00000000000 --- a/changelogs/unreleased/issue_43660.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Enable prometheus monitoring by default -merge_request: -author: -type: other diff --git a/changelogs/unreleased/jivl-add-dot-system-notes.yml b/changelogs/unreleased/jivl-add-dot-system-notes.yml new file mode 100644 index 00000000000..2246bab1464 --- /dev/null +++ b/changelogs/unreleased/jivl-add-dot-system-notes.yml @@ -0,0 +1,5 @@ +--- +title: Add dot to separate system notes content +merge_request: 18864 +author: +type: changed diff --git a/changelogs/unreleased/jivl-refactor-activity-calendar.yml b/changelogs/unreleased/jivl-refactor-activity-calendar.yml deleted file mode 100644 index 0702ede4af9..00000000000 --- a/changelogs/unreleased/jivl-refactor-activity-calendar.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Refactored activity calendar -merge_request: 18469 -author: Enrico Scholz -type: changed diff --git a/changelogs/unreleased/jprovazn-commit-notes-api.yml b/changelogs/unreleased/jprovazn-commit-notes-api.yml deleted file mode 100644 index 4665d800ccf..00000000000 --- a/changelogs/unreleased/jprovazn-commit-notes-api.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add discussion API for merge requests and commits -merge_request: -author: -type: added diff --git a/changelogs/unreleased/jprovazn-generic-error.yml b/changelogs/unreleased/jprovazn-generic-error.yml deleted file mode 100644 index ced3b84fe02..00000000000 --- a/changelogs/unreleased/jprovazn-generic-error.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Display only generic message on merge error to avoid exposing any potentially - sensitive or user unfriendly backend messages. -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/jr-33320-lfs-settings-interface.yml b/changelogs/unreleased/jr-33320-lfs-settings-interface.yml deleted file mode 100644 index b39308f5474..00000000000 --- a/changelogs/unreleased/jr-33320-lfs-settings-interface.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Show group and project LFS settings in the interface to Owners and Masters -merge_request: 18562 -author: -type: changed diff --git a/changelogs/unreleased/jr-46209-web-ide-copy.yml b/changelogs/unreleased/jr-46209-web-ide-copy.yml deleted file mode 100644 index 87ccae6ced0..00000000000 --- a/changelogs/unreleased/jr-46209-web-ide-copy.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix outdated Web IDE welcome copy -merge_request: 18861 -author: -type: fixed diff --git a/changelogs/unreleased/jr-web-ide-shortcuts.yml b/changelogs/unreleased/jr-web-ide-shortcuts.yml new file mode 100644 index 00000000000..a895eab432a --- /dev/null +++ b/changelogs/unreleased/jr-web-ide-shortcuts.yml @@ -0,0 +1,5 @@ +--- +title: Add shortcuts to Web IDE docs and modal +merge_request: 19044 +author: +type: changed diff --git a/changelogs/unreleased/jramsay-44880-filter-pipelines-by-sha.yml b/changelogs/unreleased/jramsay-44880-filter-pipelines-by-sha.yml deleted file mode 100644 index 3654aa28ff4..00000000000 --- a/changelogs/unreleased/jramsay-44880-filter-pipelines-by-sha.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add sha filter to pipelines list API -merge_request: 18125 -author: -type: changed diff --git a/changelogs/unreleased/label-links-on-project-transfer.yml b/changelogs/unreleased/label-links-on-project-transfer.yml deleted file mode 100644 index fabedb77cb3..00000000000 --- a/changelogs/unreleased/label-links-on-project-transfer.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix label links update on project transfer -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/live-trace-v2-efficient-destroy-all.yml b/changelogs/unreleased/live-trace-v2-efficient-destroy-all.yml deleted file mode 100644 index ab22739b73d..00000000000 --- a/changelogs/unreleased/live-trace-v2-efficient-destroy-all.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Destroy build_chunks efficiently with FastDestroyAll module -merge_request: 18575 -author: -type: performance diff --git a/changelogs/unreleased/live-trace-v2.yml b/changelogs/unreleased/live-trace-v2.yml deleted file mode 100644 index 875a66bc565..00000000000 --- a/changelogs/unreleased/live-trace-v2.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: New CI Job live-trace architecture -merge_request: 18169 -author: -type: changed diff --git a/changelogs/unreleased/memoize-database-version.yml b/changelogs/unreleased/memoize-database-version.yml new file mode 100644 index 00000000000..575348a53a1 --- /dev/null +++ b/changelogs/unreleased/memoize-database-version.yml @@ -0,0 +1,5 @@ +--- +title: Memoize Gitlab::Database.version +merge_request: +author: +type: performance diff --git a/changelogs/unreleased/move-board-blank-state-vue-component.yml b/changelogs/unreleased/move-board-blank-state-vue-component.yml deleted file mode 100644 index 0a278a8c009..00000000000 --- a/changelogs/unreleased/move-board-blank-state-vue-component.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Move BoardBlankState vue component -merge_request: 17666 -author: George Tsiolis -type: performance diff --git a/changelogs/unreleased/move-estimate-only-pane-vue-component.yml b/changelogs/unreleased/move-estimate-only-pane-vue-component.yml deleted file mode 100644 index b6c538f70b3..00000000000 --- a/changelogs/unreleased/move-estimate-only-pane-vue-component.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Move TimeTrackingEstimateOnlyPane vue component -merge_request: 18318 -author: George Tsiolis -type: performance diff --git a/changelogs/unreleased/move-help-state-vue-component.yml b/changelogs/unreleased/move-help-state-vue-component.yml deleted file mode 100644 index 6108368cde0..00000000000 --- a/changelogs/unreleased/move-help-state-vue-component.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Move TimeTrackingHelpState vue component -merge_request: 18319 -author: George Tsiolis -type: performance diff --git a/changelogs/unreleased/move-notification-service-calls-to-sidekiq.yml b/changelogs/unreleased/move-notification-service-calls-to-sidekiq.yml deleted file mode 100644 index b2517884d3c..00000000000 --- a/changelogs/unreleased/move-notification-service-calls-to-sidekiq.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Compute notification recipients in background jobs -merge_request: -author: -type: performance diff --git a/changelogs/unreleased/move-pipeline-failed-vue-component.yml b/changelogs/unreleased/move-pipeline-failed-vue-component.yml deleted file mode 100644 index 38d42134876..00000000000 --- a/changelogs/unreleased/move-pipeline-failed-vue-component.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Move PipelineFailed vue component -merge_request: 18277 -author: George Tsiolis -type: performance diff --git a/changelogs/unreleased/move-time-tracking-spent-only-pane-vue-component.yml b/changelogs/unreleased/move-time-tracking-spent-only-pane-vue-component.yml deleted file mode 100644 index d2db0df5a04..00000000000 --- a/changelogs/unreleased/move-time-tracking-spent-only-pane-vue-component.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Move TimeTrackingSpentOnlyPane vue component -merge_request: 18710 -author: George Tsiolis -type: performance diff --git a/changelogs/unreleased/osw-use-cached-highlighted-content-for-discussions.yml b/changelogs/unreleased/osw-use-cached-highlighted-content-for-discussions.yml deleted file mode 100644 index 03a11a3038a..00000000000 --- a/changelogs/unreleased/osw-use-cached-highlighted-content-for-discussions.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Use persisted diff data instead fetching Git on discussions -merge_request: -author: -type: performance diff --git a/changelogs/unreleased/performance-gb-improve-pipeline-creation-service.yml b/changelogs/unreleased/performance-gb-improve-pipeline-creation-service.yml deleted file mode 100644 index bd308f37bec..00000000000 --- a/changelogs/unreleased/performance-gb-improve-pipeline-creation-service.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Improve performance of a service responsible for creating a pipeline -merge_request: 18582 -author: -type: performance diff --git a/changelogs/unreleased/pipelines-index-performance.yml b/changelogs/unreleased/pipelines-index-performance.yml new file mode 100644 index 00000000000..928c2ddab72 --- /dev/null +++ b/changelogs/unreleased/pipelines-index-performance.yml @@ -0,0 +1,5 @@ +--- +title: Improve performance of project pipelines pages +merge_request: +author: +type: performance diff --git a/changelogs/unreleased/rd-44635-error-500-when-clicking-ghost-user-or-gitlab-support-bot-in-ui.yml b/changelogs/unreleased/rd-44635-error-500-when-clicking-ghost-user-or-gitlab-support-bot-in-ui.yml deleted file mode 100644 index a08a75ceda6..00000000000 --- a/changelogs/unreleased/rd-44635-error-500-when-clicking-ghost-user-or-gitlab-support-bot-in-ui.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix missing namespace for some internal users -merge_request: 18357 -author: -type: fixed diff --git a/changelogs/unreleased/rd-45502-uploading-project-export-with-lfs-file-locks-fails.yml b/changelogs/unreleased/rd-45502-uploading-project-export-with-lfs-file-locks-fails.yml deleted file mode 100644 index e3266dda629..00000000000 --- a/changelogs/unreleased/rd-45502-uploading-project-export-with-lfs-file-locks-fails.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Don't include lfs_file_locks data in export bundle -merge_request: 18495 -author: -type: fixed diff --git a/changelogs/unreleased/refactor-move-mr-widget-ready-to-merge-vue-component.yml b/changelogs/unreleased/refactor-move-mr-widget-ready-to-merge-vue-component.yml deleted file mode 100644 index 90192fae030..00000000000 --- a/changelogs/unreleased/refactor-move-mr-widget-ready-to-merge-vue-component.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Move ReadyToMerge vue component -merge_request: 17545 -author: George Tsiolis -type: performance diff --git a/changelogs/unreleased/refactor-move-mr-widget-wip-vue-component.yml b/changelogs/unreleased/refactor-move-mr-widget-wip-vue-component.yml deleted file mode 100644 index 0f045431aae..00000000000 --- a/changelogs/unreleased/refactor-move-mr-widget-wip-vue-component.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Move WorkInProgress vue component -merge_request: 17536 -author: George Tsiolis -type: performance diff --git a/changelogs/unreleased/refactor-move-no-tracking-pane-vue-component.yml b/changelogs/unreleased/refactor-move-no-tracking-pane-vue-component.yml deleted file mode 100644 index 4bb088a1e58..00000000000 --- a/changelogs/unreleased/refactor-move-no-tracking-pane-vue-component.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Move TimeTrackingNoTrackingPane vue component -merge_request: 18676 -author: George Tsiolis -type: performance diff --git a/changelogs/unreleased/refactor-move-sidebar-time-tracking-vue-component.yml b/changelogs/unreleased/refactor-move-sidebar-time-tracking-vue-component.yml deleted file mode 100644 index 4f578bfcf26..00000000000 --- a/changelogs/unreleased/refactor-move-sidebar-time-tracking-vue-component.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Move SidebarTimeTracking vue component -merge_request: 18677 -author: George Tsiolis -type: performance diff --git a/changelogs/unreleased/rename-overview-project-sidenav.yml b/changelogs/unreleased/rename-overview-project-sidenav.yml deleted file mode 100644 index 3632ef25c00..00000000000 --- a/changelogs/unreleased/rename-overview-project-sidenav.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Renamed Overview to Project in the contextual navigation at a project level -merge_request: 18295 -author: Constance Okoghenun -type: changed diff --git a/changelogs/unreleased/restore-label-underline-color.yml b/changelogs/unreleased/restore-label-underline-color.yml deleted file mode 100644 index 2e24ece5c36..00000000000 --- a/changelogs/unreleased/restore-label-underline-color.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Restore label underline color -merge_request: 18407 -author: George Tsiolis -type: fixed diff --git a/changelogs/unreleased/restore-size-and-position-for-fork-icon.yml b/changelogs/unreleased/restore-size-and-position-for-fork-icon.yml deleted file mode 100644 index dd8dad0b17d..00000000000 --- a/changelogs/unreleased/restore-size-and-position-for-fork-icon.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix size and position for fork icon -merge_request: 18449 -author: George Tsiolis -type: changed diff --git a/changelogs/unreleased/revert-discussion-counter-height.yml b/changelogs/unreleased/revert-discussion-counter-height.yml deleted file mode 100644 index 331ff997009..00000000000 --- a/changelogs/unreleased/revert-discussion-counter-height.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Revert discussion counter height -merge_request: 18656 -author: George Tsiolis -type: changed diff --git a/changelogs/unreleased/security-45689-fix-archive-cache-bug.yml b/changelogs/unreleased/security-45689-fix-archive-cache-bug.yml deleted file mode 100644 index 0103a7fc430..00000000000 --- a/changelogs/unreleased/security-45689-fix-archive-cache-bug.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Serve archive requests with the correct file in all cases -merge_request: -author: -type: security diff --git a/changelogs/unreleased/security_issue_42029.yml b/changelogs/unreleased/security_issue_42029.yml deleted file mode 100644 index 0772e33f930..00000000000 --- a/changelogs/unreleased/security_issue_42029.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Sanitizes user name to avoid XSS attacks -merge_request: -author: -type: security diff --git a/changelogs/unreleased/sh-bump-lograge.yml b/changelogs/unreleased/sh-bump-lograge.yml deleted file mode 100644 index 65b15153a35..00000000000 --- a/changelogs/unreleased/sh-bump-lograge.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Bump lograge to 0.10.0 and remove monkey patch -merge_request: -author: -type: other diff --git a/changelogs/unreleased/sh-fix-grape-logging-status-code.yml b/changelogs/unreleased/sh-fix-grape-logging-status-code.yml new file mode 100644 index 00000000000..aabf9a84bfb --- /dev/null +++ b/changelogs/unreleased/sh-fix-grape-logging-status-code.yml @@ -0,0 +1,5 @@ +--- +title: Fix api_json.log not always reporting the right HTTP status code +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/sh-move-delete-groups-api-async.yml b/changelogs/unreleased/sh-move-delete-groups-api-async.yml new file mode 100644 index 00000000000..1b200cac5c5 --- /dev/null +++ b/changelogs/unreleased/sh-move-delete-groups-api-async.yml @@ -0,0 +1,5 @@ +--- +title: Move API group deletion to Sidekiq +merge_request: +author: +type: changed diff --git a/changelogs/unreleased/show-group-id-in-group-settings.yml b/changelogs/unreleased/show-group-id-in-group-settings.yml deleted file mode 100644 index b975fe8c71d..00000000000 --- a/changelogs/unreleased/show-group-id-in-group-settings.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Show group id in group settings -merge_request: 18482 -author: George Tsiolis -type: added diff --git a/changelogs/unreleased/show-runners-description-on-jobs-page.yml b/changelogs/unreleased/show-runners-description-on-jobs-page.yml deleted file mode 100644 index d9414a3d021..00000000000 --- a/changelogs/unreleased/show-runners-description-on-jobs-page.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Show Runner's description on job's page -merge_request: 17321 -author: -type: added diff --git a/changelogs/unreleased/tc-repo-verify-mails.yml b/changelogs/unreleased/tc-repo-verify-mails.yml deleted file mode 100644 index b4d3c4b1596..00000000000 --- a/changelogs/unreleased/tc-repo-verify-mails.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Small improvements to repository checks -merge_request: 18484 -author: -type: changed diff --git a/changelogs/unreleased/tz-upgrade-underscore.yml b/changelogs/unreleased/tz-upgrade-underscore.yml deleted file mode 100644 index 5dfd8154ecd..00000000000 --- a/changelogs/unreleased/tz-upgrade-underscore.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Upgrade underscore.js to 1.9.0 -merge_request: 18578 -author: -type: other diff --git a/changelogs/unreleased/unresolved-discussions-vue-component-i18n-and-tests.yml b/changelogs/unreleased/unresolved-discussions-vue-component-i18n-and-tests.yml deleted file mode 100644 index d99a9c93c0b..00000000000 --- a/changelogs/unreleased/unresolved-discussions-vue-component-i18n-and-tests.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add i18n and update specs for UnresolvedDiscussions vue component -merge_request: 17866 -author: George Tsiolis -type: performance diff --git a/changelogs/unreleased/update-environment-item-action-buttons-icons.yml b/changelogs/unreleased/update-environment-item-action-buttons-icons.yml deleted file mode 100644 index 7f06022be3e..00000000000 --- a/changelogs/unreleased/update-environment-item-action-buttons-icons.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update environment item action buttons icons -merge_request: 18632 -author: George Tsiolis -type: changed diff --git a/changelogs/unreleased/update-timeline-icon-for-description-edit.yml b/changelogs/unreleased/update-timeline-icon-for-description-edit.yml deleted file mode 100644 index 560db00e503..00000000000 --- a/changelogs/unreleased/update-timeline-icon-for-description-edit.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update timeline icon for description edit -merge_request: 18633 -author: George Tsiolis -type: changed diff --git a/changelogs/unreleased/use-case-insensitive-ordering-for-dashboard-filters.yml b/changelogs/unreleased/use-case-insensitive-ordering-for-dashboard-filters.yml new file mode 100644 index 00000000000..098e4b1d5fa --- /dev/null +++ b/changelogs/unreleased/use-case-insensitive-ordering-for-dashboard-filters.yml @@ -0,0 +1,5 @@ +--- +title: "Use case in-sensitive ordering by name for dashboard" +merge_request: 18553 +author: "@vedharish" +type: fixed diff --git a/changelogs/unreleased/winh-dashboard-any-milestone.yml b/changelogs/unreleased/winh-dashboard-any-milestone.yml deleted file mode 100644 index 49eecd3da2b..00000000000 --- a/changelogs/unreleased/winh-dashboard-any-milestone.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Reset milestone filter when clicking "Any Milestone" in dashboard -merge_request: 18531 -author: -type: fixed diff --git a/changelogs/unreleased/winh-dropdown-entry-unlocking.yml b/changelogs/unreleased/winh-dropdown-entry-unlocking.yml deleted file mode 100644 index fc669af1f57..00000000000 --- a/changelogs/unreleased/winh-dropdown-entry-unlocking.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove green background from unlock button in admin area -merge_request: 18288 -author: -type: changed diff --git a/changelogs/unreleased/zj-branch-containing-sha-opt-out.yml b/changelogs/unreleased/zj-branch-containing-sha-opt-out.yml deleted file mode 100644 index 3d11ee588ae..00000000000 --- a/changelogs/unreleased/zj-branch-containing-sha-opt-out.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Detecting branchnames containing a commit uses Gitaly by default -merge_request: -author: -type: performance diff --git a/changelogs/unreleased/zj-calculate-checksum-mandator.yml b/changelogs/unreleased/zj-calculate-checksum-mandator.yml new file mode 100644 index 00000000000..83315a3c5dd --- /dev/null +++ b/changelogs/unreleased/zj-calculate-checksum-mandator.yml @@ -0,0 +1,5 @@ +--- +title: Remove shellout implementation for Repository checksums +merge_request: +author: +type: other diff --git a/changelogs/unreleased/zj-find-license-opt-out.yml b/changelogs/unreleased/zj-find-license-opt-out.yml deleted file mode 100644 index be2656601a9..00000000000 --- a/changelogs/unreleased/zj-find-license-opt-out.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Detect repository license on Gitaly by default -merge_request: -author: -type: performance diff --git a/changelogs/unreleased/zj-fork-opt-out.yml b/changelogs/unreleased/zj-fork-opt-out.yml deleted file mode 100644 index 56bf6b8b0f6..00000000000 --- a/changelogs/unreleased/zj-fork-opt-out.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Gitaly handles repository forks by default -merge_request: -author: -type: other diff --git a/changelogs/unreleased/zj-namespace-service-mandatory.yml b/changelogs/unreleased/zj-namespace-service-mandatory.yml deleted file mode 100644 index d890741c51b..00000000000 --- a/changelogs/unreleased/zj-namespace-service-mandatory.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Finish NamespaceService migration to Gitaly -merge_request: -author: -type: performance diff --git a/changelogs/unreleased/zj-ref-exists-opt-out.yml b/changelogs/unreleased/zj-ref-exists-opt-out.yml deleted file mode 100644 index cdffecb0d0a..00000000000 --- a/changelogs/unreleased/zj-ref-exists-opt-out.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Check if a ref exists is done by Gitaly by default -merge_request: -author: -type: performance diff --git a/changelogs/unreleased/zj-repo-checksum-opt-out.yml b/changelogs/unreleased/zj-repo-checksum-opt-out.yml deleted file mode 100644 index 98dfedf7475..00000000000 --- a/changelogs/unreleased/zj-repo-checksum-opt-out.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Compute Gitlab::Git::Repository#checksum on Gitaly by default -merge_request: -author: -type: performance diff --git a/changelogs/unreleased/zj-repository-exist-mandatory.yml b/changelogs/unreleased/zj-repository-exist-mandatory.yml deleted file mode 100644 index 7d83446e90f..00000000000 --- a/changelogs/unreleased/zj-repository-exist-mandatory.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Repository#exists? is always executed through Gitaly -merge_request: -author: -type: performance diff --git a/changelogs/unreleased/zj-tag-containing-sha-opt-out.yml b/changelogs/unreleased/zj-tag-containing-sha-opt-out.yml deleted file mode 100644 index 4774c7811d1..00000000000 --- a/changelogs/unreleased/zj-tag-containing-sha-opt-out.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Detecting tags containing a commit uses Gitaly by default -merge_request: -author: -type: performance diff --git a/changelogs/unreleased/zj-workhorse-archive-mandatory.yml b/changelogs/unreleased/zj-workhorse-archive-mandatory.yml new file mode 100644 index 00000000000..3a4a351a2b9 --- /dev/null +++ b/changelogs/unreleased/zj-workhorse-archive-mandatory.yml @@ -0,0 +1,5 @@ +--- +title: Workhorse will use Gitaly to create archives +merge_request: +author: +type: other diff --git a/changelogs/unreleased/zj-workhorse-commit-patch-diff.yml b/changelogs/unreleased/zj-workhorse-commit-patch-diff.yml new file mode 100644 index 00000000000..bce68692d98 --- /dev/null +++ b/changelogs/unreleased/zj-workhorse-commit-patch-diff.yml @@ -0,0 +1,5 @@ +--- +title: Workhorse to send raw diff and patch for commits +merge_request: +author: +type: other diff --git a/config/initializers/deprecations.rb b/config/initializers/deprecations.rb index c8d7f742bb1..14616e726d9 100644 --- a/config/initializers/deprecations.rb +++ b/config/initializers/deprecations.rb @@ -1,4 +1,4 @@ -if Gitlab.dev_env_or_com? +if Rails.env.development? || ENV['GITLAB_LEGACY_PATH_LOG_MESSAGE'] deprecator = ActiveSupport::Deprecation.new('11.0', 'GitLab') deprecator.behavior = -> (message, callstack) { diff --git a/config/initializers/flipper.rb b/config/initializers/flipper.rb index c60ad535fd5..80cab7273e5 100644 --- a/config/initializers/flipper.rb +++ b/config/initializers/flipper.rb @@ -1,22 +1 @@ -require 'flipper/adapters/active_record' -require 'flipper/adapters/active_support_cache_store' - -Flipper.configure do |config| - config.default do - adapter = Flipper::Adapters::ActiveRecord.new( - feature_class: Feature::FlipperFeature, gate_class: Feature::FlipperGate) - cached_adapter = Flipper::Adapters::ActiveSupportCacheStore.new( - adapter, - Rails.cache, - expires_in: 1.hour) - - Flipper.new(cached_adapter) - end -end - Feature.register_feature_groups - -unless Rails.env.test? - require 'flipper/middleware/memoizer' - Rails.application.config.middleware.use Flipper::Middleware::Memoizer -end diff --git a/db/migrate/20180504195842_project_name_lower_index.rb b/db/migrate/20180504195842_project_name_lower_index.rb new file mode 100644 index 00000000000..d6f25d3d4ab --- /dev/null +++ b/db/migrate/20180504195842_project_name_lower_index.rb @@ -0,0 +1,32 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class ProjectNameLowerIndex < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + INDEX_NAME = 'index_projects_on_lower_name' + + disable_ddl_transaction! + + def up + return unless Gitlab::Database.postgresql? + + disable_statement_timeout + + execute "CREATE INDEX CONCURRENTLY #{INDEX_NAME} ON projects (LOWER(name))" + end + + def down + return unless Gitlab::Database.postgresql? + + disable_statement_timeout + + if supports_drop_index_concurrently? + execute "DROP INDEX CONCURRENTLY IF EXISTS #{INDEX_NAME}" + else + execute "DROP INDEX IF EXISTS #{INDEX_NAME}" + end + end +end diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md index 69600cad25c..411a0fae93f 100644 --- a/doc/administration/monitoring/prometheus/gitlab_metrics.md +++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md @@ -1,7 +1,7 @@ # GitLab Prometheus metrics >**Note:** -Available since [Omnibus GitLab 9.3][29118]. Currently experimental. For +Available since [Omnibus GitLab 9.3][29118]. For installations from source you'll have to configure it yourself. To enable the GitLab Prometheus metrics: @@ -24,7 +24,7 @@ server, because the embedded server configuration is overwritten once every ## Metrics available -In this experimental phase, only a few metrics are available: +The following metrics are available: | Metric | Type | Since | Description | |:--------------------------------- |:--------- |:----- |:----------- | diff --git a/doc/administration/monitoring/prometheus/index.md b/doc/administration/monitoring/prometheus/index.md index 3d24812c66a..f47add48345 100644 --- a/doc/administration/monitoring/prometheus/index.md +++ b/doc/administration/monitoring/prometheus/index.md @@ -120,7 +120,7 @@ To disable the monitoring of Kubernetes: ## GitLab Prometheus metrics -> Introduced as an experimental feature in GitLab 9.3. +> Introduced in GitLab 9.3. GitLab monitors its own internal service metrics, and makes them available at the `/-/metrics` endpoint. Unlike other exporters, this endpoint requires authentication as it is available on the same URL and port as user traffic. diff --git a/doc/api/README.md b/doc/api/README.md index e777fc63d2b..194907accc7 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -33,6 +33,7 @@ following locations: - [Jobs](jobs.md) - [Keys](keys.md) - [Labels](labels.md) +- [Markdown](markdown.md) - [Merge Requests](merge_requests.md) - [Project milestones](milestones.md) - [Group milestones](group_milestones.md) diff --git a/doc/api/groups.md b/doc/api/groups.md index 923fd662a5b..96842ef330f 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -487,6 +487,9 @@ Parameters: - `id` (required) - The ID or path of a user group +This will queue a background job to delete all projects in the group. The +response will be a 202 Accepted if the user has authorization. + ## Search for group Get all groups that match your string in their name or path. diff --git a/doc/api/issues.md b/doc/api/issues.md index 7479c1d2f93..d0063e0b8a2 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -38,8 +38,8 @@ GET /issues?my_reaction_emoji=star | `state` | string | no | Return all issues or just those that are `opened` or `closed` | | `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels | | `milestone` | string | no | The milestone title | -| `scope` | string | no | Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all`. Defaults to `created-by-me` _([Introduced][ce-13004] in GitLab 9.5)_ | -| `author_id` | integer | no | Return issues created by the given user `id`. Combine with `scope=all` or `scope=assigned-to-me`. _([Introduced][ce-13004] in GitLab 9.5)_ | +| `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`. Defaults to `created_by_me`
For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.
_([Introduced][ce-13004] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ | +| `author_id` | integer | no | Return issues created by the given user `id`. Combine with `scope=all` or `scope=assigned_to_me`. _([Introduced][ce-13004] in GitLab 9.5)_ | | `assignee_id` | integer | no | Return issues assigned to the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ | | `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji` _([Introduced][ce-14016] in GitLab 10.0)_ | | `iids[]` | Array[integer] | no | Return only the issues having the given `iid` | @@ -152,7 +152,7 @@ GET /groups/:id/issues?my_reaction_emoji=star | `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels | | `iids[]` | Array[integer] | no | Return only the issues having the given `iid` | | `milestone` | string | no | The milestone title | -| `scope` | string | no | Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all` _([Introduced][ce-13004] in GitLab 9.5)_ | +| `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`.
For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.
_([Introduced][ce-13004] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ | | `author_id` | integer | no | Return issues created by the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ | | `assignee_id` | integer | no | Return issues assigned to the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ | | `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji` _([Introduced][ce-14016] in GitLab 10.0)_ | @@ -266,7 +266,7 @@ GET /projects/:id/issues?my_reaction_emoji=star | `state` | string | no | Return all issues or just those that are `opened` or `closed` | | `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels | | `milestone` | string | no | The milestone title | -| `scope` | string | no | Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all` _([Introduced][ce-13004] in GitLab 9.5)_ | +| `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`.
For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.
_([Introduced][ce-13004] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ | | `author_id` | integer | no | Return issues created by the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ | | `assignee_id` | integer | no | Return issues assigned to the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ | | `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji` _([Introduced][ce-14016] in GitLab 10.0)_ | @@ -1254,3 +1254,4 @@ Example response: [ce-13004]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13004 [ce-14016]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14016 [ce-17042]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17042 +[ce-18935]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18935 diff --git a/doc/api/markdown.md b/doc/api/markdown.md new file mode 100644 index 00000000000..f406838e887 --- /dev/null +++ b/doc/api/markdown.md @@ -0,0 +1,29 @@ +# Markdown API + +> [Introduced][ce-18926] in GitLab 11.0. + +Available only in APIv4. + +## Render an arbitrary Markdown document + +``` +POST /api/v4/markdown +``` + +| Attribute | Type | Required | Description | +| --------- | ------- | ------------- | ------------------------------------------ | +| `text` | string | yes | The markdown text to render | +| `gfm` | boolean | no (optional) | Render text using GitLab Flavored Markdown. Default is `false` | +| `project` | string | no (optional) | Use `project` as a context when creating references using GitLab Flavored Markdown. [Authentication](README.html#authentication) is required if a project is not public. | + +```bash +curl --header Content-Type:application/json --data '{"text":"Hello world! :tada:", "gfm":true, "project":"group_example/project_example"}' https://gitlab.example.com/api/v4/markdown +``` + +Response example: + +```json +{ "html": "

Hello world! 🎉

" } +``` + +[ce-18926]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18926 diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index b9a4f661777..cbd51c9870c 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -28,7 +28,7 @@ GET /merge_requests?milestone=release GET /merge_requests?labels=bug,reproduced GET /merge_requests?author_id=5 GET /merge_requests?my_reaction_emoji=star -GET /merge_requests?scope=assigned-to-me +GET /merge_requests?scope=assigned_to_me ``` Parameters: @@ -45,8 +45,8 @@ Parameters: | `created_before` | datetime | no | Return merge requests created on or before the given time | | `updated_after` | datetime | no | Return merge requests updated on or after the given time | | `updated_before` | datetime | no | Return merge requests updated on or before the given time | -| `scope` | string | no | Return merge requests for the given scope: `created-by-me`, `assigned-to-me` or `all`. Defaults to `created-by-me` | -| `author_id` | integer | no | Returns merge requests created by the given user `id`. Combine with `scope=all` or `scope=assigned-to-me` | +| `scope` | string | no | Return merge requests for the given scope: `created_by_me`, `assigned_to_me` or `all`. Defaults to `created_by_me`
For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead. | +| `author_id` | integer | no | Returns merge requests created by the given user `id`. Combine with `scope=all` or `scope=assigned_to_me` | | `assignee_id` | integer | no | Returns merge requests assigned to the given user `id` | | `my_reaction_emoji` | string | no | Return merge requests reacted by the authenticated user by the given `emoji` _([Introduced][ce-14016] in GitLab 10.0)_ | | `source_branch` | string | no | Return merge requests with the given source branch | @@ -164,7 +164,7 @@ Parameters: | `created_before` | datetime | no | Return merge requests created on or before the given time | | `updated_after` | datetime | no | Return merge requests updated on or after the given time | | `updated_before` | datetime | no | Return merge requests updated on or before the given time | -| `scope` | string | no | Return merge requests for the given scope: `created-by-me`, `assigned-to-me` or `all` _([Introduced][ce-13060] in GitLab 9.5)_ | +| `scope` | string | no | Return merge requests for the given scope: `created_by_me`, `assigned_to_me` or `all`.
For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.
_([Introduced][ce-13060] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ | | `author_id` | integer | no | Returns merge requests created by the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_ | | `assignee_id` | integer | no | Returns merge requests assigned to the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_ | | `my_reaction_emoji` | string | no | Return merge requests reacted by the authenticated user by the given `emoji` _([Introduced][ce-14016] in GitLab 10.0)_ | @@ -1460,3 +1460,4 @@ Example response: [ce-13060]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13060 [ce-14016]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14016 [ce-15454]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15454 +[ce-18935]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18935 diff --git a/doc/ci/environments.md b/doc/ci/environments.md index 517e25f00f7..3a491f0073c 100644 --- a/doc/ci/environments.md +++ b/doc/ci/environments.md @@ -252,6 +252,7 @@ including predefined, secure variables and `.gitlab-ci.yml` [`variables`](yaml/README.md#variables). You however cannot use variables defined under `script` or on the Runner's side. There are other variables that are unsupported in environment name context: +- `CI_PIPELINE_ID` - `CI_JOB_ID` - `CI_JOB_TOKEN` - `CI_BUILD_ID` diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 4a83d4fbe33..bf32f6567a5 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -531,6 +531,16 @@ Below you can find supported syntax reference: `$STAGING` value needs to a string, with length higher than zero. Variable that contains only whitespace characters is not an empty variable. +1. Pattern matching _(added in 11.0)_ + + > Example: `$VARIABLE =~ /^content.*/` + + It is possible perform pattern matching against a variable and regular + expression. Expression like this evaluates to truth if matches are found. + + Pattern matching is case-sensitive by default. Use `i` flag modifier, like + `/pattern/i` to make a pattern case-insensitive. + ### Unsupported predefined variables Because GitLab evaluates variables before creating jobs, we do not support a @@ -544,6 +554,7 @@ We do not support variables containing tokens because of security reasons. You can find a full list of unsupported variables below: +- `CI_PIPELINE_ID` - `CI_JOB_ID` - `CI_JOB_TOKEN` - `CI_BUILD_ID` diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 2a17a51d7f8..3e77a6f58b7 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -344,10 +344,11 @@ job: kubernetes: active ``` -Example of using variables expressions: +Examples of using variables expressions: ```yaml deploy: + script: cap staging deploy only: refs: - branches @@ -356,6 +357,16 @@ deploy: - $STAGING ``` +Another use case is exluding jobs depending on a commit message _(added in 11.0)_: + +```yaml +end-to-end: + script: rake test:end-to-end + except: + variables: + - $CI_COMMIT_MESSAGE =~ /skip-end-to-end-tests/ +``` + Learn more about variables expressions on [a separate page][variables-expressions]. ## `tags` diff --git a/doc/development/fe_guide/vuex.md b/doc/development/fe_guide/vuex.md index 8997a5889dc..858b03c60bf 100644 --- a/doc/development/fe_guide/vuex.md +++ b/doc/development/fe_guide/vuex.md @@ -37,12 +37,13 @@ import state from './state'; Vue.use(Vuex); -export default new Vuex.Store({ +export const createStore = () => new Vuex.Store({ actions, getters, mutations, state, }); +export default createStore(); ``` ### `state.js` @@ -320,10 +321,11 @@ In order to write unit tests for those components, we need to include the store ```javascript //component_spec.js import Vue from 'vue'; -import store from './store'; +import { createStore } from './store'; import component from './component.vue' describe('component', () => { + let store; let vm; let Component; @@ -340,6 +342,8 @@ describe('component', () => { name: 'Foo', age: '30', }; + + store = createStore(); // populate the store store.dispatch('addUser', user); diff --git a/doc/downgrade_ee_to_ce/README.md b/doc/downgrade_ee_to_ce/README.md index f656057e3da..236408762e3 100644 --- a/doc/downgrade_ee_to_ce/README.md +++ b/doc/downgrade_ee_to_ce/README.md @@ -15,9 +15,9 @@ Kerberos and Atlassian Crowd are only available on the Enterprise Edition, so you should disable these mechanisms before downgrading and you should provide alternative authentication methods to your users. -### Remove Jenkins CI Service entries from the database +### Remove Service Integration entries from the database -The `JenkinsService` class is only available on the Enterprise Edition codebase, +The `JenkinsService` and `GithubService` classes are only available in the Enterprise Edition codebase, so if you downgrade to the Community Edition, you'll come across the following error: @@ -30,20 +30,31 @@ column if you didn't intend it to be used for storing the inheritance class or o use another column for that information.) ``` +or + +``` +Completed 500 Internal Server Error in 497ms (ActiveRecord: 32.2ms) + +ActionView::Template::Error (The single-table inheritance mechanism failed to locate the subclass: 'GithubService'. This +error is raised because the column 'type' is reserved for storing the class in case of inheritance. Please rename this +column if you didn't intend it to be used for storing the inheritance class or overwrite Service.inheritance_column to +use another column for that information.) +``` + All services are created automatically for every project you have, so in order to avoid getting this error, you need to remove all instances of the -`JenkinsService` from your database: +`JenkinsService` and `GithubService` from your database: **Omnibus Installation** ``` -$ sudo gitlab-rails runner "Service.where(type: ['JenkinsService', 'JenkinsDeprecatedService']).delete_all" +$ sudo gitlab-rails runner "Service.where(type: ['JenkinsService', 'JenkinsDeprecatedService', 'GithubService']).delete_all" ``` **Source Installation** ``` -$ bundle exec rails runner "Service.where(type: ['JenkinsService', 'JenkinsDeprecatedService']).delete_all" production +$ bundle exec rails runner "Service.where(type: ['JenkinsService', 'JenkinsDeprecatedService', 'GithubService']).delete_all" production ``` ### Secret variables environment scopes diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index 785cc32d590..77139c50d07 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -64,6 +64,13 @@ If you are running GitLab within a Docker container, you can run the backup from docker exec -t gitlab-rake gitlab:backup:create ``` +If you are using the gitlab-omnibus helm chart on a Kubernetes cluster, you can +run the backup task on the gitlab application pod using kubectl + +``` +kubectl exec -it gitlab-rake gitlab:backup:create +``` + Example output: ``` @@ -601,6 +608,34 @@ If there is a GitLab version mismatch between your backup tar file and the insta version of GitLab, the restore command will abort with an error. Install the [correct GitLab version](https://packages.gitlab.com/gitlab/) and try again. +### Restore for Docker image and gitlab-omnibus helm chart + +For GitLab installations using docker image or the gitlab-omnibus helm chart on +a Kubernetes cluster, restore task expects the restore directories to be empty. +However, with docker and Kubernetes volume mounts, some system level directories +may be created at the volume roots, like `lost+found` directory found in Linux +operating systems. These directories are usually owned by `root`, which can +cause access permission errors since the restore rake task runs as `git` user. +So, to restore a GitLab installation, users have to confirm the restore target +directories are empty. + +For both these installation types, the backup tarball has to be available in the +backup location (default location is `/var/opt/gitlab/backups`). + +For docker installations, the restore task can be run from host using the +command + +``` +docker exec -it gitlab-rake gitlab:backup:restore +``` + +Similarly, for gitlab-omnibus helm chart, the restore task can be run on the +gitlab application pod using kubectl + +``` +kubectl exec -it gitlab-rake gitlab:backup:restore +``` + ## Alternative backup strategies If your GitLab server contains a lot of Git repository data you may find the GitLab backup script to be too slow. diff --git a/doc/topics/autodevops/img/rollout_enabled.png b/doc/topics/autodevops/img/rollout_enabled.png new file mode 100644 index 00000000000..d6e7d790cf2 Binary files /dev/null and b/doc/topics/autodevops/img/rollout_enabled.png differ diff --git a/doc/topics/autodevops/img/rollout_staging_disabled.png b/doc/topics/autodevops/img/rollout_staging_disabled.png new file mode 100644 index 00000000000..71e36b440f0 Binary files /dev/null and b/doc/topics/autodevops/img/rollout_staging_disabled.png differ diff --git a/doc/topics/autodevops/img/rollout_staging_enabled.png b/doc/topics/autodevops/img/rollout_staging_enabled.png new file mode 100644 index 00000000000..d0d1d356627 Binary files /dev/null and b/doc/topics/autodevops/img/rollout_staging_enabled.png differ diff --git a/doc/topics/autodevops/img/staging_enabled.png b/doc/topics/autodevops/img/staging_enabled.png new file mode 100644 index 00000000000..0ef1a67d641 Binary files /dev/null and b/doc/topics/autodevops/img/staging_enabled.png differ diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md index 5254e6e3d9a..33e2d710410 100644 --- a/doc/topics/autodevops/index.md +++ b/doc/topics/autodevops/index.md @@ -496,6 +496,7 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac | `POSTGRES_DB` | The PostgreSQL database name; defaults to the value of [`$CI_ENVIRONMENT_SLUG`](../../ci/variables/README.md#predefined-variables-environment-variables). Set it to use a custom database name. | | `BUILDPACK_URL` | The buildpack's full URL. It can point to either Git repositories or a tarball URL. For Git repositories, it is possible to point to a specific `ref`, for example `https://github.com/heroku/heroku-buildpack-ruby.git#v142` | | `STAGING_ENABLED` | From GitLab 10.8, this variable can be used to define a [deploy policy for staging and production environments](#deploy-policy-for-staging-and-production-environments). | +| `INCREMENTAL_ROLLOUT_ENABLED`| From GitLab 10.8, this variable can be used to enable an [incremental rollout](#incremental-rollout-to-production) of your application for the production environment. | TIP: **Tip:** Set up the replica variables using a @@ -578,6 +579,57 @@ If `STAGING_ENABLED` is defined in your project (e.g., set `STAGING_ENABLED` to to a `staging` environment, and a `production_manual` job will be created for you when you're ready to manually deploy to production. +#### Incremental rollout to production **[PREMIUM]** + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5415) in GitLab 10.8. + +When you have a new version of your app to deploy in production, you may want +to use an incremental rollout to replace just a few pods with the latest code. +This will allow you to first check how the app is behaving, and later manually +increasing the rollout up to 100%. + +If `INCREMENTAL_ROLLOUT_ENABLED` is defined in your project (e.g., set +`INCREMENTAL_ROLLOUT_ENABLED` to `1` as a secret variable), then instead of the +standard `production` job, 4 different +[manual jobs](../../ci/pipelines.md#manual-actions-from-the-pipeline-graph) +will be created: + +1. `rollout 10%` +1. `rollout 25%` +1. `rollout 50%` +1. `rollout 100%` + +The percentage is based on the `REPLICAS` variable and defines the number of +pods you want to have for your deployment. If you say `10`, and then you run +the `10%` rollout job, there will be `1` new pod + `9` old ones. + +To start a job, click on the play icon next to the job's name. You are not +required to go from `10%` to `100%`, you can jump to whatever job you want. +You can also scale down by running a lower percentage job, just before hitting +`100%`. Once you get to `100%`, you cannot scale down, and you'd have to roll +back by redeploying the old version using the +[rollback button](../../ci/environments.md#rolling-back-changes) in the +environment page. + +Below, you can see how the pipeline will look if the rollout or staging +variables are defined. + +- **Without `INCREMENTAL_ROLLOUT_ENABLED` and without `STAGING_ENABLED`** + + ![Staging and rollout disabled](img/rollout_staging_disabled.png) + +- **Without `INCREMENTAL_ROLLOUT_ENABLED` and with `STAGING_ENABLED`** + + ![Staging enabled](img/staging_enabled.png) + +- **With `INCREMENTAL_ROLLOUT_ENABLED` and without `STAGING_ENABLED`** + + ![Rollout enabled](img/rollout_enabled.png) + +- **With `INCREMENTAL_ROLLOUT_ENABLED` and with `STAGING_ENABLED`** + + ![Rollout and staging enabled](img/rollout_staging_enabled.png) + ## Currently supported languages NOTE: **Note:** diff --git a/doc/topics/autodevops/quick_start_guide.md b/doc/topics/autodevops/quick_start_guide.md index 15567715c98..0b16af2953b 100644 --- a/doc/topics/autodevops/quick_start_guide.md +++ b/doc/topics/autodevops/quick_start_guide.md @@ -23,6 +23,10 @@ page](https://gitlab.com/auto-devops-examples/minimal-ruby-app) and press the **Fork** button. Soon you should have a project under your namespace with the necessary files. +You can also start a new project from a +[GitLab project template](https://gitlab.com/gitlab-org/project-templates) if +you want to use a different language. + ## Setup your own cluster on Google Kubernetes Engine If you do not already have a Google Cloud account, create one at diff --git a/doc/user/admin_area/settings/img/enforce_terms.png b/doc/user/admin_area/settings/img/enforce_terms.png old mode 100755 new mode 100644 diff --git a/doc/user/admin_area/settings/img/respond_to_terms.png b/doc/user/admin_area/settings/img/respond_to_terms.png old mode 100755 new mode 100644 diff --git a/doc/user/project/deploy_tokens/index.md b/doc/user/project/deploy_tokens/index.md index 7a8b3c75690..c09d5aeba8e 100644 --- a/doc/user/project/deploy_tokens/index.md +++ b/doc/user/project/deploy_tokens/index.md @@ -76,7 +76,7 @@ pull images from your Container Registry. > [Introduced][ce-18414] in GitLab 10.8. There's a special case when it comes to Deploy Tokens, if a user creates one -named `gitlab-deploy-token`, the name and token of the Deploy Token will be +named `gitlab-deploy-token`, the username and token of the Deploy Token will be automatically exposed to the CI/CD jobs as environment variables: `CI_DEPLOY_USER` and `CI_DEPLOY_PASSWORD`, respectively. diff --git a/doc/workflow/shortcuts.md b/doc/workflow/shortcuts.md index 2e1bd6bfe5c..c99505e6bdf 100644 --- a/doc/workflow/shortcuts.md +++ b/doc/workflow/shortcuts.md @@ -46,15 +46,19 @@ You can see GitLab's keyboard shortcuts by using 'shift + ?' | Keyboard Shortcut | Description | | ----------------- | ----------- | | g + p | Go to the project's home page | -| g + e | Go to the project's activity feed | +| g + v | Go to the project's activity feed | | g + f | Go to files | | g + c | Go to commits | -| g + b | Go to jobs | +| g + j | Go to jobs | | g + n | Go to network graph | -| g + g | Go to repository charts | +| g + d | Go to repository charts | | g + i | Go to issues | +| g + b | Go to issue boards | | g + m | Go to merge requests | +| g + e | Go to environments | +| g + k | Go to kubernetes | | g + s | Go to snippets | +| g + w | Go to wiki | | t | Go to finding file | | i | New issue | @@ -84,3 +88,9 @@ You can see GitLab's keyboard shortcuts by using 'shift + ?' | Keyboard Shortcut | Description | | ----------------- | ----------- | | e | Edit wiki page| + +## Web IDE + +| Keyboard Shortcut | Description | +| ----------------- | ----------- | +| ⌘ + p | Go to file | diff --git a/lib/api/api.rb b/lib/api/api.rb index 5139e869c71..de20b2b8e67 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -8,14 +8,15 @@ module API PROJECT_ENDPOINT_REQUIREMENTS = { id: NO_SLASH_URL_PART_REGEX }.freeze COMMIT_ENDPOINT_REQUIREMENTS = PROJECT_ENDPOINT_REQUIREMENTS.merge(sha: NO_SLASH_URL_PART_REGEX).freeze - use GrapeLogging::Middleware::RequestLogger, - logger: Logger.new(LOG_FILENAME), - formatter: Gitlab::GrapeLogging::Formatters::LogrageWithTimestamp.new, - include: [ - GrapeLogging::Loggers::FilterParameters.new, - GrapeLogging::Loggers::ClientEnv.new, - Gitlab::GrapeLogging::Loggers::UserLogger.new - ] + insert_before Grape::Middleware::Error, + GrapeLogging::Middleware::RequestLogger, + logger: Logger.new(LOG_FILENAME), + formatter: Gitlab::GrapeLogging::Formatters::LogrageWithTimestamp.new, + include: [ + GrapeLogging::Loggers::FilterParameters.new, + GrapeLogging::Loggers::ClientEnv.new, + Gitlab::GrapeLogging::Loggers::UserLogger.new + ] allow_access_with_scope :api prefix :api @@ -139,6 +140,7 @@ module API mount ::API::Keys mount ::API::Labels mount ::API::Lint + mount ::API::Markdown mount ::API::Members mount ::API::MergeRequestDiffs mount ::API::MergeRequests diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 0d125cd7831..03b6b30a0d8 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -167,8 +167,10 @@ module API Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/46285') destroy_conditionally!(group) do |group| - ::Groups::DestroyService.new(group, current_user).execute + ::Groups::DestroyService.new(group, current_user).async_execute end + + accepted! end desc 'Get a list of projects in this group.' do diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 2f50f94c897..6d75e8817c4 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -13,6 +13,7 @@ module API args.delete(:id) args[:milestone_title] = args.delete(:milestone) args[:label_name] = args.delete(:labels) + args[:scope] = args[:scope].underscore if args[:scope] issues = IssuesFinder.new(current_user, args).execute .preload(:assignees, :labels, :notes, :timelogs) @@ -36,8 +37,8 @@ module API optional :updated_before, type: DateTime, desc: 'Return issues updated before the specified time' optional :author_id, type: Integer, desc: 'Return issues which are authored by the user with the given ID' optional :assignee_id, type: Integer, desc: 'Return issues which are assigned to the user with the given ID' - optional :scope, type: String, values: %w[created-by-me assigned-to-me all], - desc: 'Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all`' + optional :scope, type: String, values: %w[created-by-me assigned-to-me created_by_me assigned_to_me all], + desc: 'Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`' optional :my_reaction_emoji, type: String, desc: 'Return issues reacted by the authenticated user by the given emoji' use :pagination end @@ -66,8 +67,8 @@ module API optional :state, type: String, values: %w[opened closed all], default: 'all', desc: 'Return opened, closed, or all issues' use :issues_params - optional :scope, type: String, values: %w[created-by-me assigned-to-me all], default: 'created-by-me', - desc: 'Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all`' + optional :scope, type: String, values: %w[created-by-me assigned-to-me created_by_me assigned_to_me all], default: 'created_by_me', + desc: 'Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`' end get do authenticate! unless params[:scope] == 'all' diff --git a/lib/api/markdown.rb b/lib/api/markdown.rb new file mode 100644 index 00000000000..b9ed68aa584 --- /dev/null +++ b/lib/api/markdown.rb @@ -0,0 +1,33 @@ +module API + class Markdown < Grape::API + params do + requires :text, type: String, desc: "The markdown text to render" + optional :gfm, type: Boolean, desc: "Render text using GitLab Flavored Markdown" + optional :project, type: String, desc: "The full path of a project to use as the context when creating references using GitLab Flavored Markdown" + end + resource :markdown do + desc "Render markdown text" do + detail "This feature was introduced in GitLab 11.0." + end + post do + # Explicitly set CommonMark as markdown engine to use. + # Remove this set when https://gitlab.com/gitlab-org/gitlab-ce/issues/43011 is done. + context = { markdown_engine: :common_mark, only_path: false } + + if params[:project] + project = Project.find_by_full_path(params[:project]) + + not_found!("Project") unless can?(current_user, :read_project, project) + + context[:project] = project + else + context[:skip_project_check] = true + end + + context[:pipeline] = params[:gfm] ? :full : :plain_markdown + + { html: Banzai.render(params[:text], context) } + end + end + end +end diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index d4cc18f622b..bc4df16e3a8 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -38,6 +38,7 @@ module API args[:milestone_title] = args.delete(:milestone) args[:label_name] = args.delete(:labels) + args[:scope] = args[:scope].underscore if args[:scope] merge_requests = MergeRequestsFinder.new(current_user, args).execute .reorder(args[:order_by] => args[:sort]) @@ -79,8 +80,8 @@ module API optional :view, type: String, values: %w[simple], desc: 'If simple, returns the `iid`, URL, title, description, and basic state of merge request' optional :author_id, type: Integer, desc: 'Return merge requests which are authored by the user with the given ID' optional :assignee_id, type: Integer, desc: 'Return merge requests which are assigned to the user with the given ID' - optional :scope, type: String, values: %w[created-by-me assigned-to-me all], - desc: 'Return merge requests for the given scope: `created-by-me`, `assigned-to-me` or `all`' + optional :scope, type: String, values: %w[created-by-me assigned-to-me created_by_me assigned_to_me all], + desc: 'Return merge requests for the given scope: `created_by_me`, `assigned_to_me` or `all`' optional :my_reaction_emoji, type: String, desc: 'Return issues reacted by the authenticated user by the given emoji' optional :source_branch, type: String, desc: 'Return merge requests with the given source branch' optional :target_branch, type: String, desc: 'Return merge requests with the given target branch' @@ -95,8 +96,8 @@ module API end params do use :merge_requests_params - optional :scope, type: String, values: %w[created-by-me assigned-to-me all], default: 'created-by-me', - desc: 'Return merge requests for the given scope: `created-by-me`, `assigned-to-me` or `all`' + optional :scope, type: String, values: %w[created-by-me assigned-to-me created_by_me assigned_to_me all], default: 'created_by_me', + desc: 'Return merge requests for the given scope: `created_by_me`, `assigned_to_me` or `all`' end get do authenticate! unless params[:scope] == 'all' diff --git a/lib/api/runner.rb b/lib/api/runner.rb index 649feba1036..a7f1cb1131f 100644 --- a/lib/api/runner.rb +++ b/lib/api/runner.rb @@ -149,6 +149,7 @@ module API end patch '/:id/trace' do job = authenticate_job! + forbidden!('Job is not running') unless job.running? error!('400 Missing header Content-Range', 400) unless request.headers.key?('Content-Range') content_range = request.headers['Content-Range'] diff --git a/lib/api/v3/groups.rb b/lib/api/v3/groups.rb index 3844fd4810d..4fa7d196e50 100644 --- a/lib/api/v3/groups.rb +++ b/lib/api/v3/groups.rb @@ -131,8 +131,9 @@ module API delete ":id" do group = find_group!(params[:id]) authorize! :admin_group, group - Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/46285') - present ::Groups::DestroyService.new(group, current_user).execute, with: Entities::GroupDetail, current_user: current_user + ::Groups::DestroyService.new(group, current_user).async_execute + + accepted! end desc 'Get a list of projects in this group.' do diff --git a/lib/banzai/filter/gollum_tags_filter.rb b/lib/banzai/filter/gollum_tags_filter.rb index f2e9a5a1116..4bc82ecb4d6 100644 --- a/lib/banzai/filter/gollum_tags_filter.rb +++ b/lib/banzai/filter/gollum_tags_filter.rb @@ -58,6 +58,9 @@ module Banzai def call doc.search(".//text()").each do |node| + # Do not perform linking inside blocks + next unless node.ancestors('code').empty? + # A Gollum ToC tag is `[[_TOC_]]`, but due to MarkdownFilter running # before this one, it will be converted into `[[TOC]]`, so it # needs special-case handling diff --git a/lib/banzai/filter/reference_filter.rb b/lib/banzai/filter/reference_filter.rb index b9d5ecf70ec..2f023f4f242 100644 --- a/lib/banzai/filter/reference_filter.rb +++ b/lib/banzai/filter/reference_filter.rb @@ -73,7 +73,7 @@ module Banzai # # Note that while the key might exist, its value could be nil! def validate - needs :project + needs :project unless skip_project_check? end # Iterates over all
and text() nodes in a document. diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb index 8b2f05fffec..a1f24e8b093 100644 --- a/lib/banzai/pipeline/gfm_pipeline.rb +++ b/lib/banzai/pipeline/gfm_pipeline.rb @@ -42,9 +42,9 @@ module Banzai end def self.transform_context(context) - context.merge( - only_path: true, + context[:only_path] = true unless context.key?(:only_path) + context.merge( # EmojiFilter asset_host: Gitlab::Application.config.asset_host, asset_root: Gitlab.config.gitlab.base_url diff --git a/lib/feature.rb b/lib/feature.rb index 8e9ba5c530a..6474de6e56d 100644 --- a/lib/feature.rb +++ b/lib/feature.rb @@ -1,3 +1,6 @@ +require 'flipper/adapters/active_record' +require 'flipper/adapters/active_support_cache_store' + class Feature # Classes to override flipper table names class FlipperFeature < Flipper::Adapters::ActiveRecord::Feature @@ -60,7 +63,8 @@ class Feature end def flipper - @flipper ||= Flipper.instance + Thread.current[:flipper] ||= + Flipper.new(flipper_adapter).tap { |flip| flip.memoize = true } end # This method is called from config/initializers/flipper.rb and can be used @@ -68,5 +72,16 @@ class Feature # See https://docs.gitlab.com/ee/development/feature_flags.html#feature-groups def register_feature_groups end + + def flipper_adapter + active_record_adapter = Flipper::Adapters::ActiveRecord.new( + feature_class: FlipperFeature, + gate_class: FlipperGate) + + Flipper::Adapters::ActiveSupportCacheStore.new( + active_record_adapter, + Rails.cache, + expires_in: 1.hour) + end end end diff --git a/lib/gitlab/auth/ldap/access.rb b/lib/gitlab/auth/ldap/access.rb index 34286900e72..865185eb5db 100644 --- a/lib/gitlab/auth/ldap/access.rb +++ b/lib/gitlab/auth/ldap/access.rb @@ -6,7 +6,7 @@ module Gitlab module Auth module LDAP class Access - attr_reader :provider, :user + attr_reader :provider, :user, :ldap_identity def self.open(user, &block) Gitlab::Auth::LDAP::Adapter.open(user.ldap_identity.provider) do |adapter| @@ -14,9 +14,12 @@ module Gitlab end end - def self.allowed?(user) + def self.allowed?(user, options = {}) self.open(user) do |access| + # Whether user is allowed, or not, we should update + # permissions to keep things clean if access.allowed? + access.update_user Users::UpdateService.new(user, user: user, last_credential_check_at: Time.now).execute true @@ -29,7 +32,8 @@ module Gitlab def initialize(user, adapter = nil) @adapter = adapter @user = user - @provider = user.ldap_identity.provider + @ldap_identity = user.ldap_identity + @provider = adapter&.provider || ldap_identity&.provider end def allowed? @@ -40,7 +44,7 @@ module Gitlab end # Block user in GitLab if he/she was blocked in AD - if Gitlab::Auth::LDAP::Person.disabled_via_active_directory?(user.ldap_identity.extern_uid, adapter) + if Gitlab::Auth::LDAP::Person.disabled_via_active_directory?(ldap_identity.extern_uid, adapter) block_user(user, 'is disabled in Active Directory') false else @@ -64,27 +68,44 @@ module Gitlab Gitlab::Auth::LDAP::Config.new(provider) end + def find_ldap_user + Gitlab::Auth::LDAP::Person.find_by_dn(ldap_identity.extern_uid, adapter) + end + def ldap_user - @ldap_user ||= Gitlab::Auth::LDAP::Person.find_by_dn(user.ldap_identity.extern_uid, adapter) + return unless provider + + @ldap_user ||= find_ldap_user end def block_user(user, reason) user.ldap_block - Gitlab::AppLogger.info( - "LDAP account \"#{user.ldap_identity.extern_uid}\" #{reason}, " \ - "blocking Gitlab user \"#{user.name}\" (#{user.email})" - ) + if provider + Gitlab::AppLogger.info( + "LDAP account \"#{ldap_identity.extern_uid}\" #{reason}, " \ + "blocking Gitlab user \"#{user.name}\" (#{user.email})" + ) + else + Gitlab::AppLogger.info( + "Account is not provided by LDAP, " \ + "blocking Gitlab user \"#{user.name}\" (#{user.email})" + ) + end end def unblock_user(user, reason) user.activate Gitlab::AppLogger.info( - "LDAP account \"#{user.ldap_identity.extern_uid}\" #{reason}, " \ + "LDAP account \"#{ldap_identity.extern_uid}\" #{reason}, " \ "unblocking Gitlab user \"#{user.name}\" (#{user.email})" ) end + + def update_user + # no-op in CE + end end end end diff --git a/lib/gitlab/auth/ldap/config.rb b/lib/gitlab/auth/ldap/config.rb index 77185f52ced..d4415eaa6dc 100644 --- a/lib/gitlab/auth/ldap/config.rb +++ b/lib/gitlab/auth/ldap/config.rb @@ -11,6 +11,8 @@ module Gitlab attr_accessor :provider, :options + InvalidProvider = Class.new(StandardError) + def self.enabled? Gitlab.config.ldap.enabled end @@ -22,6 +24,10 @@ module Gitlab def self.available_servers return [] unless enabled? + _available_servers + end + + def self._available_servers Array.wrap(servers.first) end @@ -34,7 +40,7 @@ module Gitlab end def self.invalid_provider(provider) - raise "Unknown provider (#{provider}). Available providers: #{providers}" + raise InvalidProvider.new("Unknown provider (#{provider}). Available providers: #{providers}") end def initialize(provider) @@ -84,13 +90,17 @@ module Gitlab end def base - options['base'] + @base ||= Person.normalize_dn(options['base']) end def uid options['uid'] end + def label + options['label'] + end + def sync_ssh_keys? sync_ssh_keys.present? end @@ -132,6 +142,10 @@ module Gitlab options['timeout'].to_i end + def external_groups + options['external_groups'] || [] + end + def has_auth? options['password'] || options['bind_dn'] end diff --git a/lib/gitlab/auth/saml/config.rb b/lib/gitlab/auth/saml/config.rb index 2760b1a3247..5fa9581f837 100644 --- a/lib/gitlab/auth/saml/config.rb +++ b/lib/gitlab/auth/saml/config.rb @@ -14,6 +14,10 @@ module Gitlab def external_groups options[:external_groups] end + + def admin_groups + options[:admin_groups] + end end end end diff --git a/lib/gitlab/auth/saml/user.rb b/lib/gitlab/auth/saml/user.rb index cb01cd8004c..b8c84c37cd5 100644 --- a/lib/gitlab/auth/saml/user.rb +++ b/lib/gitlab/auth/saml/user.rb @@ -20,10 +20,8 @@ module Gitlab user ||= find_or_build_ldap_user if auto_link_ldap_user? user ||= build_new_user if signup_enabled? - if external_users_enabled? && user - # Check if there is overlap between the user's groups and the external groups - # setting then set user as external or internal. - user.external = !(auth_hash.groups & saml_config.external_groups).empty? + if user + user.external = !(auth_hash.groups & saml_config.external_groups).empty? if external_users_enabled? end user diff --git a/lib/gitlab/auth/user_auth_finders.rb b/lib/gitlab/auth/user_auth_finders.rb index cf02030c577..4dc23f977da 100644 --- a/lib/gitlab/auth/user_auth_finders.rb +++ b/lib/gitlab/auth/user_auth_finders.rb @@ -1,9 +1,5 @@ module Gitlab module Auth - # - # Exceptions - # - AuthenticationError = Class.new(StandardError) MissingTokenError = Class.new(AuthenticationError) TokenNotFoundError = Class.new(AuthenticationError) @@ -61,6 +57,12 @@ module Gitlab private + def route_authentication_setting + return {} unless respond_to?(:route_setting) + + route_setting(:authentication) || {} + end + def access_token strong_memoize(:access_token) do find_oauth_access_token || find_personal_access_token diff --git a/lib/gitlab/ci/pipeline/expression.rb b/lib/gitlab/ci/pipeline/expression.rb new file mode 100644 index 00000000000..f57df7c5637 --- /dev/null +++ b/lib/gitlab/ci/pipeline/expression.rb @@ -0,0 +1,10 @@ +module Gitlab + module Ci + module Pipeline + module Expression + ExpressionError = Class.new(StandardError) + RuntimeError = Class.new(ExpressionError) + end + end + end +end diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/matches.rb b/lib/gitlab/ci/pipeline/expression/lexeme/matches.rb new file mode 100644 index 00000000000..10957598f76 --- /dev/null +++ b/lib/gitlab/ci/pipeline/expression/lexeme/matches.rb @@ -0,0 +1,29 @@ +module Gitlab + module Ci + module Pipeline + module Expression + module Lexeme + class Matches < Lexeme::Operator + PATTERN = /=~/.freeze + + def initialize(left, right) + @left = left + @right = right + end + + def evaluate(variables = {}) + text = @left.evaluate(variables) + regexp = @right.evaluate(variables) + + regexp.scan(text.to_s).any? + end + + def self.build(_value, behind, ahead) + new(behind, ahead) + end + end + end + end + end + end +end diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb b/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb new file mode 100644 index 00000000000..9b239c29ea4 --- /dev/null +++ b/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb @@ -0,0 +1,33 @@ +module Gitlab + module Ci + module Pipeline + module Expression + module Lexeme + require_dependency 're2' + + class Pattern < Lexeme::Value + PATTERN = %r{^/.+/[ismU]*$}.freeze + + def initialize(regexp) + @value = regexp + + unless Gitlab::UntrustedRegexp.valid?(@value) + raise Lexer::SyntaxError, 'Invalid regular expression!' + end + end + + def evaluate(variables = {}) + Gitlab::UntrustedRegexp.fabricate(@value) + rescue RegexpError + raise Expression::RuntimeError, 'Invalid regular expression!' + end + + def self.build(string) + new(string) + end + end + end + end + end + end +end diff --git a/lib/gitlab/ci/pipeline/expression/lexer.rb b/lib/gitlab/ci/pipeline/expression/lexer.rb index e1c68b7c3c2..4cacb1e62c9 100644 --- a/lib/gitlab/ci/pipeline/expression/lexer.rb +++ b/lib/gitlab/ci/pipeline/expression/lexer.rb @@ -5,15 +5,17 @@ module Gitlab class Lexer include ::Gitlab::Utils::StrongMemoize + SyntaxError = Class.new(Expression::ExpressionError) + LEXEMES = [ Expression::Lexeme::Variable, Expression::Lexeme::String, + Expression::Lexeme::Pattern, Expression::Lexeme::Null, - Expression::Lexeme::Equals + Expression::Lexeme::Equals, + Expression::Lexeme::Matches ].freeze - SyntaxError = Class.new(Statement::StatementError) - MAX_TOKENS = 100 def initialize(statement, max_tokens: MAX_TOKENS) diff --git a/lib/gitlab/ci/pipeline/expression/statement.rb b/lib/gitlab/ci/pipeline/expression/statement.rb index 09a7c98464b..b36f1e0f865 100644 --- a/lib/gitlab/ci/pipeline/expression/statement.rb +++ b/lib/gitlab/ci/pipeline/expression/statement.rb @@ -3,15 +3,16 @@ module Gitlab module Pipeline module Expression class Statement - StatementError = Class.new(StandardError) + StatementError = Class.new(Expression::ExpressionError) GRAMMAR = [ + %w[variable], %w[variable equals string], %w[variable equals variable], %w[variable equals null], %w[string equals variable], %w[null equals variable], - %w[variable] + %w[variable matches pattern] ].freeze def initialize(statement, variables = {}) @@ -35,11 +36,13 @@ module Gitlab def truthful? evaluate.present? + rescue Expression::ExpressionError + false end def valid? parse_tree.is_a?(Lexeme::Base) - rescue StatementError + rescue Expression::ExpressionError false end end diff --git a/lib/gitlab/ci/pipeline/preloader.rb b/lib/gitlab/ci/pipeline/preloader.rb new file mode 100644 index 00000000000..e7a2e5511cf --- /dev/null +++ b/lib/gitlab/ci/pipeline/preloader.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + module Pipeline + # Class for preloading data associated with pipelines such as commit + # authors. + module Preloader + def self.preload(pipelines) + # This ensures that all the pipeline commits are eager loaded before we + # start using them. + pipelines.each(&:commit) + + pipelines.each do |pipeline| + # This preloads the author of every commit. We're using "lazy_author" + # here since "author" immediately loads the data on the first call. + pipeline.commit.try(:lazy_author) + + # This preloads the number of warnings for every pipeline, ensuring + # that Ci::Pipeline#has_warnings? doesn't execute any additional + # queries. + pipeline.number_of_warnings + end + end + end + end + end +end diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb index 76501dd50e8..d49d055c3f2 100644 --- a/lib/gitlab/database.rb +++ b/lib/gitlab/database.rb @@ -43,7 +43,7 @@ module Gitlab end def self.version - database_version.match(/\A(?:PostgreSQL |)([^\s]+).*\z/)[1] + @version ||= database_version.match(/\A(?:PostgreSQL |)([^\s]+).*\z/)[1] end def self.join_lateral_supported? diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb index d78d29b7ac6..156d077a69c 100644 --- a/lib/gitlab/git/blob.rb +++ b/lib/gitlab/git/blob.rb @@ -104,25 +104,22 @@ module Gitlab # file.rb # oid: 4a # # - # Blob.find_entry_by_path(repo, '1a', 'app/file.rb') # => '4a' + # Blob.find_entry_by_path(repo, '1a', 'blog', 'app', 'file.rb') # => '4a' # - def find_entry_by_path(repository, root_id, path) + def find_entry_by_path(repository, root_id, *path_parts) root_tree = repository.lookup(root_id) - # Strip leading slashes - path[%r{^/*}] = '' - path_arr = path.split('/') entry = root_tree.find do |entry| - entry[:name] == path_arr[0] + entry[:name] == path_parts[0] end return nil unless entry - if path_arr.size > 1 + if path_parts.size > 1 return nil unless entry[:type] == :tree - path_arr.shift - find_entry_by_path(repository, entry[:oid], path_arr.join('/')) + path_parts.shift + find_entry_by_path(repository, entry[:oid], *path_parts) else [:blob, :commit].include?(entry[:type]) ? entry : nil end @@ -185,10 +182,13 @@ module Gitlab def find_by_rugged(repository, sha, path, limit:) return unless path + # Strip any leading / characters from the path + path = path.sub(%r{\A/*}, '') + rugged_commit = repository.lookup(sha) root_tree = rugged_commit.tree - blob_entry = find_entry_by_path(repository, root_tree.oid, path) + blob_entry = find_entry_by_path(repository, root_tree.oid, *path.split('/')) return nil unless blob_entry diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb index fabcd46c8e9..89f761dd515 100644 --- a/lib/gitlab/git/commit.rb +++ b/lib/gitlab/git/commit.rb @@ -6,6 +6,7 @@ module Gitlab attr_accessor :raw_commit, :head + MAX_COMMIT_MESSAGE_DISPLAY_SIZE = 10.megabytes MIN_SHA_LENGTH = 7 SERIALIZE_KEYS = [ :id, :message, :parent_ids, @@ -63,9 +64,7 @@ module Gitlab if is_enabled repo.gitaly_commit_client.find_commit(commit_id) else - obj = repo.rev_parse_target(commit_id) - - obj.is_a?(Rugged::Commit) ? obj : nil + rugged_find(repo, commit_id) end end @@ -76,6 +75,12 @@ module Gitlab nil end + def rugged_find(repo, commit_id) + obj = repo.rev_parse_target(commit_id) + + obj.is_a?(Rugged::Commit) ? obj : nil + end + # Get last commit for HEAD # # Ex. @@ -297,11 +302,40 @@ module Gitlab nil end end + + def get_message(repository, commit_id) + BatchLoader.for({ repository: repository, commit_id: commit_id }).batch do |items, loader| + items_by_repo = items.group_by { |i| i[:repository] } + + items_by_repo.each do |repo, items| + commit_ids = items.map { |i| i[:commit_id] } + + messages = get_messages(repository, commit_ids) + + messages.each do |commit_sha, message| + loader.call({ repository: repository, commit_id: commit_sha }, message) + end + end + end + end + + def get_messages(repository, commit_ids) + repository.gitaly_migrate(:commit_messages) do |is_enabled| + if is_enabled + repository.gitaly_commit_client.get_commit_messages(commit_ids) + else + commit_ids.map { |id| [id, rugged_find(repository, id).message] }.to_h + end + end + end end def initialize(repository, raw_commit, head = nil) raise "Nil as raw commit passed" unless raw_commit + @repository = repository + @head = head + case raw_commit when Hash init_from_hash(raw_commit) @@ -312,9 +346,6 @@ module Gitlab else raise "Invalid raw commit type: #{raw_commit.class}" end - - @repository = repository - @head = head end def sha @@ -342,21 +373,6 @@ module Gitlab parent_ids.first end - # Shows the diff between the commit's parent and the commit. - # - # Cuts out the header and stats from #to_patch and returns only the diff. - # - # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/324 - def to_diff - Gitlab::GitalyClient.migrate(:commit_patch, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| - if is_enabled - @repository.gitaly_commit_client.patch(id) - else - rugged_diff_from_parent.patch - end - end - end - # Returns a diff object for the changes from this commit's first parent. # If there is no parent, then the diff is between this commit and an # empty repo. See Repository#diff for keys allowed in the +options+ @@ -432,16 +448,6 @@ module Gitlab Gitlab::Git::CommitStats.new(@repository, self) end - def to_patch(options = {}) - begin - rugged_commit.to_mbox(options) - rescue Rugged::InvalidError => ex - if ex.message =~ /commit \w+ is a merge commit/i - 'Patch format is not currently supported for merge commits.' - end - end - end - # Get ref names collection # # Ex. @@ -543,7 +549,7 @@ module Gitlab # TODO: Once gitaly "takes over" Rugged consider separating the # subject from the message to make it clearer when there's one # available but not the other. - @message = (commit.body.presence || commit.subject).dup + @message = message_from_gitaly_body @authored_date = Time.at(commit.author.date.seconds).utc @author_name = commit.author.name.dup @author_email = commit.author.email.dup @@ -595,6 +601,25 @@ module Gitlab def refs(repo) repo.refs_hash[id] end + + def message_from_gitaly_body + return @raw_commit.subject.dup if @raw_commit.body_size.zero? + return @raw_commit.body.dup if full_body_fetched_from_gitaly? + + if @raw_commit.body_size > MAX_COMMIT_MESSAGE_DISPLAY_SIZE + "#{@raw_commit.subject}\n\n--commit message is too big".strip + else + fetch_body_from_gitaly + end + end + + def full_body_fetched_from_gitaly? + @raw_commit.body.bytesize == @raw_commit.body_size + end + + def fetch_body_from_gitaly + self.class.get_message(@repository, id) + end end end end diff --git a/lib/gitlab/git/path_helper.rb b/lib/gitlab/git/path_helper.rb index 155cf52f050..57b82a37d6c 100644 --- a/lib/gitlab/git/path_helper.rb +++ b/lib/gitlab/git/path_helper.rb @@ -6,7 +6,7 @@ module Gitlab class << self def normalize_path(filename) # Strip all leading slashes so that //foo -> foo - filename[%r{^/*}] = '' + filename = filename.sub(%r{\A/*}, '') # Expand relative paths (e.g. foo/../bar) filename = Pathname.new(filename) diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 061865a7acf..2be604a5b13 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -403,10 +403,10 @@ module Gitlab prefix = archive_prefix(ref, commit.id, append_sha: append_sha) { - 'RepoPath' => path, 'ArchivePrefix' => prefix, 'ArchivePath' => archive_file_path(storage_path, commit.id, prefix, format), - 'CommitId' => commit.id + 'CommitId' => commit.id, + 'GitalyRepository' => gitaly_repository.to_h } end @@ -1473,10 +1473,19 @@ module Gitlab def search_files_by_content(query, ref) return [] if empty? || query.blank? - offset = 2 - args = %W(grep -i -I -n -z --before-context #{offset} --after-context #{offset} -E -e #{Regexp.escape(query)} #{ref || root_ref}) + safe_query = Regexp.escape(query) + ref ||= root_ref - run_git(args).first.scrub.split(/^--\n/) + gitaly_migrate(:search_files_by_content) do |is_enabled| + if is_enabled + gitaly_repository_client.search_files_by_content(ref, safe_query) + else + offset = 2 + args = %W(grep -i -I -n -z --before-context #{offset} --after-context #{offset} -E -e #{safe_query} #{ref}) + + run_git(args).first.scrub.split(/^--\n/) + end + end end def can_be_merged?(source_sha, target_branch) @@ -1491,12 +1500,19 @@ module Gitlab def search_files_by_name(query, ref) safe_query = Regexp.escape(query.sub(%r{^/*}, "")) + ref ||= root_ref return [] if empty? || safe_query.blank? - args = %W(ls-tree -r --name-status --full-tree #{ref || root_ref} -- #{safe_query}) + gitaly_migrate(:search_files_by_name) do |is_enabled| + if is_enabled + gitaly_repository_client.search_files_by_name(ref, safe_query) + else + args = %W(ls-tree -r --name-status --full-tree #{ref} -- #{safe_query}) - run_git(args).first.lines.map(&:strip) + run_git(args).first.lines.map(&:strip) + end + end end def find_commits_by_message(query, ref, path, limit, offset) @@ -1572,14 +1588,12 @@ module Gitlab end def checksum - gitaly_migrate(:calculate_checksum, - status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| - if is_enabled - gitaly_repository_client.calculate_checksum - else - calculate_checksum_by_shelling_out - end - end + # The exists? RPC is much cheaper, so we perform this request first + raise NoRepository, "Repository does not exists" unless exists? + + gitaly_repository_client.calculate_checksum + rescue GRPC::NotFound + raise NoRepository # Guard against data races. end private @@ -1948,7 +1962,12 @@ module Gitlab end target_commit = Gitlab::Git::Commit.find(self, ref.target) - Gitlab::Git::Tag.new(self, ref.name, ref.target, target_commit, message) + Gitlab::Git::Tag.new(self, { + name: ref.name, + target: ref.target, + target_commit: target_commit, + message: message + }) end.sort_by(&:name) end @@ -2348,7 +2367,7 @@ module Gitlab end def gitaly_delete_refs(*ref_names) - gitaly_ref_client.delete_refs(refs: ref_names) + gitaly_ref_client.delete_refs(refs: ref_names) if ref_names.any? end def rugged_remove_remote(remote_name) @@ -2478,36 +2497,6 @@ module Gitlab rev_parse_target(ref).oid end - def calculate_checksum_by_shelling_out - raise NoRepository unless exists? - - args = %W(--git-dir=#{path} show-ref --heads --tags) - output, status = run_git(args) - - if status.nil? || !status.zero? - # Non-valid git repositories return 128 as the status code and an error output - raise InvalidRepository if status == 128 && output.to_s.downcase =~ /not a git repository/ - # Empty repositories returns with a non-zero status and an empty output. - raise ChecksumError, output unless output.blank? - - return EMPTY_REPOSITORY_CHECKSUM - end - - refs = output.split("\n") - - result = refs.inject(nil) do |checksum, ref| - value = Digest::SHA1.hexdigest(ref).hex - - if checksum.nil? - value - else - checksum ^ value - end - end - - result.to_s(16) - end - def build_git_cmd(*args) object_directories = alternate_object_directories.join(File::PATH_SEPARATOR) diff --git a/lib/gitlab/git/repository_mirroring.rb b/lib/gitlab/git/repository_mirroring.rb index 8a01f92e2af..e35ea5762eb 100644 --- a/lib/gitlab/git/repository_mirroring.rb +++ b/lib/gitlab/git/repository_mirroring.rb @@ -35,7 +35,11 @@ module Gitlab next if name =~ /\^\{\}\Z/ target_commit = Gitlab::Git::Commit.find(self, target) - Gitlab::Git::Tag.new(self, name, target, target_commit) + Gitlab::Git::Tag.new(self, { + name: name, + target: target, + target_commit: target_commit + }) end.compact end diff --git a/lib/gitlab/git/tag.rb b/lib/gitlab/git/tag.rb index 8a8f7b051ed..e44284572fd 100644 --- a/lib/gitlab/git/tag.rb +++ b/lib/gitlab/git/tag.rb @@ -1,17 +1,99 @@ module Gitlab module Git class Tag < Ref - attr_reader :object_sha + extend Gitlab::EncodingHelper + + attr_reader :object_sha, :repository + + MAX_TAG_MESSAGE_DISPLAY_SIZE = 10.megabytes + SERIALIZE_KEYS = %i[name target target_commit message].freeze + + attr_accessor *SERIALIZE_KEYS # rubocop:disable Lint/AmbiguousOperator + + class << self + def get_message(repository, tag_id) + BatchLoader.for({ repository: repository, tag_id: tag_id }).batch do |items, loader| + items_by_repo = items.group_by { |i| i[:repository] } + + items_by_repo.each do |repo, items| + tag_ids = items.map { |i| i[:tag_id] } + + messages = get_messages(repository, tag_ids) + + messages.each do |id, message| + loader.call({ repository: repository, tag_id: id }, message) + end + end + end + end + + def get_messages(repository, tag_ids) + repository.gitaly_migrate(:tag_messages) do |is_enabled| + if is_enabled + repository.gitaly_ref_client.get_tag_messages(tag_ids) + else + tag_ids.map do |id| + tag = repository.rugged.lookup(id) + message = tag.is_a?(Rugged::Commit) ? "" : tag.message + + [id, message] + end.to_h + end + end + end + end + + def initialize(repository, raw_tag) + @repository = repository + @raw_tag = raw_tag + + case raw_tag + when Hash + init_from_hash + when Gitaly::Tag + init_from_gitaly + end - def initialize(repository, name, target, target_commit, message = nil) super(repository, name, target, target_commit) + end - @message = message + def init_from_hash + raw_tag = @raw_tag.symbolize_keys + + SERIALIZE_KEYS.each do |key| + send("#{key}=", raw_tag[key]) # rubocop:disable GitlabSecurity/PublicSend + end + end + + def init_from_gitaly + @name = encode!(@raw_tag.name.dup) + @target = @raw_tag.id + @message = message_from_gitaly_tag + + if @raw_tag.target_commit.present? + @target_commit = Gitlab::Git::Commit.decorate(repository, @raw_tag.target_commit) + end end def message encode! @message end + + private + + def message_from_gitaly_tag + return @raw_tag.message.dup if full_message_fetched_from_gitaly? + + if @raw_tag.message_size > MAX_TAG_MESSAGE_DISPLAY_SIZE + '--tag message is too big' + else + self.class.get_message(@repository, target) + end + end + + def full_message_fetched_from_gitaly? + @raw_tag.message.bytesize == @raw_tag.message_size + end end end end diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb index a36e6c822f9..1f5f88bf792 100644 --- a/lib/gitlab/gitaly_client/commit_service.rb +++ b/lib/gitlab/gitaly_client/commit_service.rb @@ -334,6 +334,22 @@ module Gitlab signatures end + def get_commit_messages(commit_ids) + request = Gitaly::GetCommitMessagesRequest.new(repository: @gitaly_repo, commit_ids: commit_ids) + response = GitalyClient.call(@repository.storage, :commit_service, :get_commit_messages, request) + + messages = Hash.new { |h, k| h[k] = ''.b } + current_commit_id = nil + + response.each do |rpc_message| + current_commit_id = rpc_message.commit_id if rpc_message.commit_id.present? + + messages[current_commit_id] << rpc_message.message + end + + messages + end + private def call_commit_diff(request_params, options = {}) diff --git a/lib/gitlab/gitaly_client/operation_service.rb b/lib/gitlab/gitaly_client/operation_service.rb index 831cfd1e014..44b0e517bf0 100644 --- a/lib/gitlab/gitaly_client/operation_service.rb +++ b/lib/gitlab/gitaly_client/operation_service.rb @@ -40,7 +40,7 @@ module Gitlab raise Gitlab::Git::Repository::TagExistsError end - Util.gitlab_tag_from_gitaly_tag(@repository, response.tag) + Gitlab::Git::Tag.new(@repository, response.tag) rescue GRPC::FailedPrecondition => e raise Gitlab::Git::Repository::InvalidRef, e end diff --git a/lib/gitlab/gitaly_client/ref_service.rb b/lib/gitlab/gitaly_client/ref_service.rb index ba6b577fd17..3ac46be6208 100644 --- a/lib/gitlab/gitaly_client/ref_service.rb +++ b/lib/gitlab/gitaly_client/ref_service.rb @@ -171,6 +171,22 @@ module Gitlab consume_ref_contains_sha_response(stream, :branch_names) end + def get_tag_messages(tag_ids) + request = Gitaly::GetTagMessagesRequest.new(repository: @gitaly_repo, tag_ids: tag_ids) + response = GitalyClient.call(@repository.storage, :ref_service, :get_tag_messages, request) + + messages = Hash.new { |h, k| h[k] = ''.b } + current_tag_id = nil + + response.each do |rpc_message| + current_tag_id = rpc_message.tag_id if rpc_message.tag_id.present? + + messages[current_tag_id] << rpc_message.message + end + + messages + end + private def consume_refs_response(response) @@ -210,7 +226,7 @@ module Gitlab def consume_tags_response(response) response.flat_map do |message| - message.tags.map { |gitaly_tag| Util.gitlab_tag_from_gitaly_tag(@repository, gitaly_tag) } + message.tags.map { |gitaly_tag| Gitlab::Git::Tag.new(@repository, gitaly_tag) } end end diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb index 132a5947f17..ee01f5a5bd9 100644 --- a/lib/gitlab/gitaly_client/repository_service.rb +++ b/lib/gitlab/gitaly_client/repository_service.rb @@ -301,6 +301,16 @@ module Gitlab GitalyClient.call(@storage, :repository_service, :get_raw_changes, request) end + + def search_files_by_name(ref, query) + request = Gitaly::SearchFilesByNameRequest.new(repository: @gitaly_repo, ref: ref, query: query) + GitalyClient.call(@storage, :repository_service, :search_files_by_name, request).flat_map(&:files) + end + + def search_files_by_content(ref, query) + request = Gitaly::SearchFilesByContentRequest.new(repository: @gitaly_repo, ref: ref, query: query) + GitalyClient.call(@storage, :repository_service, :search_files_by_content, request).flat_map(&:matches) + end end end end diff --git a/lib/gitlab/gitaly_client/util.rb b/lib/gitlab/gitaly_client/util.rb index 405567db94a..9c19c51d412 100644 --- a/lib/gitlab/gitaly_client/util.rb +++ b/lib/gitlab/gitaly_client/util.rb @@ -21,20 +21,6 @@ module Gitlab gitaly_repository.relative_path, gitaly_repository.gl_repository) end - - def gitlab_tag_from_gitaly_tag(repository, gitaly_tag) - if gitaly_tag.target_commit.present? - commit = Gitlab::Git::Commit.decorate(repository, gitaly_tag.target_commit) - end - - Gitlab::Git::Tag.new( - repository, - Gitlab::EncodingHelper.encode!(gitaly_tag.name.dup), - gitaly_tag.id, - commit, - Gitlab::EncodingHelper.encode!(gitaly_tag.message.chomp) - ) - end end end end diff --git a/lib/gitlab/incoming_email.rb b/lib/gitlab/incoming_email.rb index c9122a23568..d323cb9dadf 100644 --- a/lib/gitlab/incoming_email.rb +++ b/lib/gitlab/incoming_email.rb @@ -57,7 +57,7 @@ module Gitlab regex = Regexp.escape(wildcard_address) regex = regex.sub(Regexp.escape(WILDCARD_PLACEHOLDER), '(.+)') - Regexp.new(regex).freeze + Regexp.new(/\A#{regex}\z/).freeze end end end diff --git a/lib/gitlab/untrusted_regexp.rb b/lib/gitlab/untrusted_regexp.rb index 75ba0799058..dc2d91dfa23 100644 --- a/lib/gitlab/untrusted_regexp.rb +++ b/lib/gitlab/untrusted_regexp.rb @@ -9,7 +9,9 @@ module Gitlab # there is a strict limit on total execution time. See the RE2 documentation # at https://github.com/google/re2/wiki/Syntax for more details. class UntrustedRegexp - delegate :===, to: :regexp + require_dependency 're2' + + delegate :===, :source, to: :regexp def initialize(pattern, multiline: false) if multiline @@ -35,6 +37,10 @@ module Gitlab RE2.Replace(text, regexp, rewrite) end + def ==(other) + self.source == other.source + end + # Handles regular expressions with the preferred RE2 library where possible # via UntustedRegex. Falls back to Ruby's built-in regular expression library # when the syntax would be invalid in RE2. @@ -48,6 +54,24 @@ module Gitlab Regexp.new(pattern) end + def self.valid?(pattern) + !!self.fabricate(pattern) + rescue RegexpError + false + end + + def self.fabricate(pattern) + matches = pattern.match(%r{^/(?.+)/(?[ismU]*)$}) + + raise RegexpError, 'Invalid regular expression!' if matches.nil? + + expression = matches[:regexp] + flags = matches[:flags] + expression.prepend("(?#{flags})") if flags.present? + + self.new(expression, multiline: false) + end + private attr_reader :regexp diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb index 1f060de657d..e893e46ee86 100644 --- a/lib/gitlab/workhorse.rb +++ b/lib/gitlab/workhorse.rb @@ -65,12 +65,7 @@ module Gitlab params = repository.archive_metadata(ref, Gitlab.config.gitlab.repository_downloads_path, format, append_sha: append_sha) raise "Repository or ref not found" if params.empty? - if Gitlab::GitalyClient.feature_enabled?(:workhorse_archive, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) - params.merge!( - 'GitalyServer' => gitaly_server_hash(repository), - 'GitalyRepository' => repository.gitaly_repository.to_h - ) - end + params['GitalyServer'] = gitaly_server_hash(repository) # If present DisableCache must be a Boolean. Otherwise workhorse ignores it. params['DisableCache'] = true if git_archive_cache_disabled? diff --git a/lib/tasks/migrate/setup_postgresql.rake b/lib/tasks/migrate/setup_postgresql.rake index af30ecb0e9b..e7aab50e42a 100644 --- a/lib/tasks/migrate/setup_postgresql.rake +++ b/lib/tasks/migrate/setup_postgresql.rake @@ -8,6 +8,7 @@ task setup_postgresql: :environment do require Rails.root.join('db/migrate/20170503185032_index_redirect_routes_path_for_like') require Rails.root.join('db/migrate/20171220191323_add_index_on_namespaces_lower_name.rb') require Rails.root.join('db/migrate/20180215181245_users_name_lower_index.rb') + require Rails.root.join('db/migrate/20180504195842_project_name_lower_index.rb') require Rails.root.join('db/post_migrate/20180306164012_add_path_index_to_redirect_routes.rb') NamespacesProjectsPathLowerIndexes.new.up @@ -18,5 +19,6 @@ task setup_postgresql: :environment do IndexRedirectRoutesPathForLike.new.up AddIndexOnNamespacesLowerName.new.up UsersNameLowerIndex.new.up + ProjectNameLowerIndex.new.up AddPathIndexToRedirectRoutes.new.up end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 90cdfd0dd03..14228b18332 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-05-15 15:05+0200\n" -"PO-Revision-Date: 2018-05-15 15:05+0200\n" +"POT-Creation-Date: 2018-05-16 18:52+0200\n" +"PO-Revision-Date: 2018-05-16 18:52+0200\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" @@ -1872,6 +1872,9 @@ msgstr "" msgid "Enable the Performance Bar for a given group." msgstr "" +msgid "Ends at (UTC)" +msgstr "" + msgid "Environments" msgstr "" @@ -3780,6 +3783,9 @@ msgstr "" msgid "Started" msgstr "" +msgid "Starts at (UTC)" +msgstr "" + msgid "Status" msgstr "" diff --git a/qa/qa/specs/features/merge_request/create_spec.rb b/qa/qa/specs/features/merge_request/create_spec.rb index fbf9a4d17e5..0931e649e24 100644 --- a/qa/qa/specs/features/merge_request/create_spec.rb +++ b/qa/qa/specs/features/merge_request/create_spec.rb @@ -11,7 +11,7 @@ module QA expect(page).to have_content('This is a merge request') expect(page).to have_content('Great feature') - expect(page).to have_content('Opened less than a minute ago') + expect(page).to have_content(/Opened [\w\s]+ a minute ago/) 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 index bf8fa230244..442ac312b4d 100644 --- a/qa/qa/specs/features/project/deploy_key_clone_spec.rb +++ b/qa/qa/specs/features/project/deploy_key_clone_spec.rb @@ -33,13 +33,15 @@ module QA end keys = [ - Runtime::Key::RSA.new(8192), - Runtime::Key::ECDSA.new(521), - Runtime::Key::ED25519.new + [Runtime::Key::RSA, 8192], + [Runtime::Key::ECDSA, 521], + [Runtime::Key::ED25519] ] - keys.each do |key| - scenario "user sets up a deploy key with #{key.name}(#{key.bits}) to clone code using pipelines" do + keys.each do |(key_class, bits)| + scenario "user sets up a deploy key with #{key_class}(#{bits}) to clone code using pipelines" do + key = key_class.new(*bits) + login Factory::Resource::DeployKey.fabricate! do |resource| diff --git a/scripts/create_mysql_user.sh b/scripts/create_mysql_user.sh index 286b1325f1d..35f68c581f3 100644 --- a/scripts/create_mysql_user.sh +++ b/scripts/create_mysql_user.sh @@ -1,7 +1,6 @@ #!/bin/bash mysql --user=root --host=mysql < read_file_version('GITLAB_SHELL_VERSION'), + 'GITALY_VERSION' => read_file_version('GITALY_SERVER_VERSION'), + 'TRIGGERED_USER' => ENV['GITLAB_USER_NAME'], + 'TRIGGER_SOURCE' => "https://gitlab.com/gitlab-org/#{ENV['CI_PROJECT_NAME']}/-/jobs/#{ENV['CI_JOB_ID']}" + } + + if ee? + params['EE_PIPELINE'] = 'true' + params['GITLAB_EE_VERSION'] = ENV['CI_COMMIT_REF_NAME'] + else + params['CE_PIPELINE'] = 'true' + params['GITLAB_CE_VERSION'] = ENV['CI_COMMIT_REF_NAME'] + end + + params +end + +# +# Trigger a pipeline +# +def trigger_pipeline + # Create the cross project pipeline using CI_JOB_TOKEN + pipeline = Gitlab.run_trigger(GITLAB_CNG_REPO, ENV['CI_JOB_TOKEN'], 'master', params) + + puts "Triggered https://gitlab.com/#{GITLAB_CNG_REPO}/pipelines/#{pipeline.id}" +end + +trigger_pipeline diff --git a/scripts/utils.sh b/scripts/utils.sh index 6faa701f0ce..2d2ba115563 100644 --- a/scripts/utils.sh +++ b/scripts/utils.sh @@ -12,3 +12,21 @@ retry() { done return 1 } + +setup_db_user_only() { + if [ "$GITLAB_DATABASE" = "postgresql" ]; then + . scripts/create_postgres_user.sh + else + . scripts/create_mysql_user.sh + fi +} + +setup_db() { + setup_db_user_only + + bundle exec rake db:drop db:create db:schema:load db:migrate + + if [ "$GITLAB_DATABASE" = "mysql" ]; then + bundle exec rake add_limits_mysql + fi +} diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb index 694c64ae1ad..003fec8ac68 100644 --- a/spec/controllers/projects/commit_controller_spec.rb +++ b/spec/controllers/projects/commit_controller_spec.rb @@ -79,41 +79,18 @@ describe Projects::CommitController do end describe "as diff" do - include_examples "export as", :diff - let(:format) { :diff } + it "triggers workhorse to serve the request" do + go(id: commit.id, format: :diff) - it "should really only be a git diff" do - go(id: '66eceea0db202bb39c4e445e8ca28689645366c5', format: format) - - expect(response.body).to start_with("diff --git") - end - - it "is only be a git diff without whitespace changes" do - go(id: '66eceea0db202bb39c4e445e8ca28689645366c5', format: format, w: 1) - - expect(response.body).to start_with("diff --git") - - # without whitespace option, there are more than 2 diff_splits for other formats - diff_splits = assigns(:diffs).diff_files.first.diff.diff.split("\n") - expect(diff_splits.length).to be <= 2 + expect(response.headers[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-diff:") end end describe "as patch" do - include_examples "export as", :patch - let(:format) { :patch } - let(:commit2) { project.commit('498214de67004b1da3d820901307bed2a68a8ef6') } - - it "is a git email patch" do - go(id: commit2.id, format: format) - - expect(response.body).to start_with("From #{commit2.id}") - end - it "contains a git diff" do - go(id: commit2.id, format: format) + go(id: commit.id, format: :patch) - expect(response.body).to match(/^diff --git/) + expect(response.headers[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-format-patch:") end end diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb index a451bbb97b6..9e7bc20a6d1 100644 --- a/spec/controllers/projects/pipelines_controller_spec.rb +++ b/spec/controllers/projects/pipelines_controller_spec.rb @@ -35,10 +35,16 @@ describe Projects::PipelinesController do expect(json_response).to include('pipelines') expect(json_response['pipelines'].count).to eq 4 - expect(json_response['count']['all']).to eq 4 - expect(json_response['count']['running']).to eq 1 - expect(json_response['count']['pending']).to eq 1 - expect(json_response['count']['finished']).to eq 1 + expect(json_response['count']['all']).to eq '4' + expect(json_response['count']['running']).to eq '1' + expect(json_response['count']['pending']).to eq '1' + expect(json_response['count']['finished']).to eq '1' + end + + it 'does not include coverage data for the pipelines' do + subject + + expect(json_response['pipelines'][0]).not_to include('coverage') end context 'when performing gitaly calls', :request_store do diff --git a/spec/factories/gitaly/tag.rb b/spec/factories/gitaly/tag.rb new file mode 100644 index 00000000000..e99776d524a --- /dev/null +++ b/spec/factories/gitaly/tag.rb @@ -0,0 +1,9 @@ +FactoryBot.define do + factory :gitaly_tag, class: Gitaly::Tag do + skip_create + + name { 'v3.1.4' } + message { 'Pie release' } + target_commit factory: :gitaly_commit + end +end diff --git a/spec/features/invites_spec.rb b/spec/features/invites_spec.rb index e4be6193b8b..a986ddc4abc 100644 --- a/spec/features/invites_spec.rb +++ b/spec/features/invites_spec.rb @@ -5,18 +5,41 @@ describe 'Invites' do let(:owner) { create(:user, name: 'John Doe') } let(:group) { create(:group, name: 'Owned') } let(:project) { create(:project, :repository, namespace: group) } - let(:invite) { group.group_members.invite.last } + let(:group_invite) { group.group_members.invite.last } before do project.add_master(owner) group.add_user(owner, Gitlab::Access::OWNER) group.add_developer('user@example.com', owner) - invite.generate_invite_token! + group_invite.generate_invite_token! + end + + def confirm_email_and_sign_in(new_user) + new_user_token = User.find_by_email(new_user.email).confirmation_token + + visit user_confirmation_path(confirmation_token: new_user_token) + fill_in_sign_in_form(new_user) + end + + def fill_in_sign_up_form(new_user) + fill_in 'new_user_name', with: new_user.name + fill_in 'new_user_username', with: new_user.username + fill_in 'new_user_email', with: new_user.email + fill_in 'new_user_email_confirmation', with: new_user.email + fill_in 'new_user_password', with: new_user.password + click_button "Register" + end + + def fill_in_sign_in_form(user) + fill_in 'user_login', with: user.email + fill_in 'user_password', with: user.password + check 'user_remember_me' + click_button 'Sign in' end context 'when signed out' do before do - visit invite_path(invite.raw_invite_token) + visit invite_path(group_invite.raw_invite_token) end it 'renders sign in page with sign in notice' do @@ -25,12 +48,9 @@ describe 'Invites' do end it 'sign in and redirects to invitation page' do - fill_in 'user_login', with: user.email - fill_in 'user_password', with: user.password - check 'user_remember_me' - click_button 'Sign in' + fill_in_sign_in_form(user) - expect(current_path).to eq(invite_path(invite.raw_invite_token)) + expect(current_path).to eq(invite_path(group_invite.raw_invite_token)) expect(page).to have_content( 'You have been invited by John Doe to join group Owned as Developer.' ) @@ -45,7 +65,7 @@ describe 'Invites' do end it 'shows message user already a member' do - visit invite_path(invite.raw_invite_token) + visit invite_path(group_invite.raw_invite_token) expect(page).to have_content('However, you are already a member of this group.') end end @@ -53,7 +73,7 @@ describe 'Invites' do describe 'accepting the invitation' do before do sign_in(user) - visit invite_path(invite.raw_invite_token) + visit invite_path(group_invite.raw_invite_token) end it 'grants access and redirects to group page' do @@ -69,7 +89,7 @@ describe 'Invites' do context 'when signed in' do before do sign_in(user) - visit invite_path(invite.raw_invite_token) + visit invite_path(group_invite.raw_invite_token) end it 'declines application and redirects to dashboard' do @@ -83,7 +103,7 @@ describe 'Invites' do context 'when signed out' do before do - visit decline_invite_path(invite.raw_invite_token) + visit decline_invite_path(group_invite.raw_invite_token) end it 'declines application and redirects to sign in page' do @@ -94,4 +114,72 @@ describe 'Invites' do end end end + + describe 'invite an user using their email address' do + let(:new_user) { build_stubbed(:user) } + let(:invite_email) { new_user.email } + let(:group_invite) { create(:group_member, :invited, group: group, invite_email: invite_email) } + let!(:project_invite) { create(:project_member, :invited, project: project, invite_email: invite_email) } + + before do + stub_application_setting(send_user_confirmation_email: send_email_confirmation) + visit invite_path(group_invite.raw_invite_token) + end + + context 'email confirmation disabled' do + let(:send_email_confirmation) { false } + + it 'signs up and redirects to the dashboard page with all the projects/groups invitations automatically accepted' do + fill_in_sign_up_form(new_user) + + expect(current_path).to eq(dashboard_projects_path) + expect(page).to have_content(project.full_name) + visit group_path(group) + expect(page).to have_content(group.full_name) + end + + context 'the user sign-up using a different email address' do + let(:invite_email) { build_stubbed(:user).email } + + it 'signs up and redirects to the invitation page' do + fill_in_sign_up_form(new_user) + + expect(current_path).to eq(invite_path(group_invite.raw_invite_token)) + end + end + end + + context 'email confirmation enabled' do + let(:send_email_confirmation) { true } + + it 'signs up and redirects to root page with all the project/groups invitation automatically accepted' do + fill_in_sign_up_form(new_user) + confirm_email_and_sign_in(new_user) + + expect(current_path).to eq(root_path) + expect(page).to have_content(project.full_name) + visit group_path(group) + expect(page).to have_content(group.full_name) + end + + it "doesn't accept invitations until the user confirm his email" do + fill_in_sign_up_form(new_user) + sign_in(owner) + + visit project_project_members_path(project) + expect(page).to have_content 'Invited' + end + + context 'the user sign-up using a different email address' do + let(:invite_email) { build_stubbed(:user).email } + + it 'signs up and redirects to the invitation page' do + fill_in_sign_up_form(new_user) + confirm_email_and_sign_in(new_user) + + expect(current_path).to eq(invite_path(group_invite.raw_invite_token)) + end + end + end + end end diff --git a/spec/features/projects/actve_tabs_spec.rb b/spec/features/projects/actve_tabs_spec.rb index 0bda68b83e7..ce5606b63ae 100644 --- a/spec/features/projects/actve_tabs_spec.rb +++ b/spec/features/projects/actve_tabs_spec.rb @@ -35,7 +35,7 @@ describe 'Project active tab' do visit project_path(project) end - it_behaves_like 'page has active tab', 'Overview' + it_behaves_like 'page has active tab', 'Project' it_behaves_like 'page has active sub tab', 'Details' context 'on project Home/Activity' do @@ -43,7 +43,7 @@ describe 'Project active tab' do click_tab('Activity') end - it_behaves_like 'page has active tab', 'Overview' + it_behaves_like 'page has active tab', 'Project' it_behaves_like 'page has active sub tab', 'Activity' end end diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb index 5248a783db4..f9defa22d35 100644 --- a/spec/features/projects/environments/environments_spec.rb +++ b/spec/features/projects/environments/environments_spec.rb @@ -42,6 +42,22 @@ feature 'Environments page', :js do expect(page).to have_content('You don\'t have any environments right now') end end + + context 'when cluster is not reachable' do + let!(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) } + let!(:application_prometheus) { create(:clusters_applications_prometheus, :installed, cluster: cluster) } + + before do + allow_any_instance_of(Kubeclient::Client).to receive(:proxy_url).and_raise(Kubeclient::HttpError.new(401, 'Unauthorized', nil)) + end + + it 'should show one environment without error' do + visit_environments(project, scope: 'available') + + expect(page).to have_css('.environments-container') + expect(page.all('.environment-name').length).to eq(1) + end + end end describe 'with one stopped environment' do diff --git a/spec/features/projects/user_sees_sidebar_spec.rb b/spec/features/projects/user_sees_sidebar_spec.rb index cf80517b934..8a9255db9e8 100644 --- a/spec/features/projects/user_sees_sidebar_spec.rb +++ b/spec/features/projects/user_sees_sidebar_spec.rb @@ -37,7 +37,7 @@ describe 'Projects > User sees sidebar' do visit project_path(project) within('.nav-sidebar') do - expect(page).to have_content 'Overview' + expect(page).to have_content 'Project' expect(page).to have_content 'Issues' expect(page).to have_content 'Wiki' diff --git a/spec/features/projects/user_uses_shortcuts_spec.rb b/spec/features/projects/user_uses_shortcuts_spec.rb index 47c5a8161d9..495a010b32c 100644 --- a/spec/features/projects/user_uses_shortcuts_spec.rb +++ b/spec/features/projects/user_uses_shortcuts_spec.rb @@ -13,6 +13,8 @@ describe 'User uses shortcuts', :js do context 'when navigating to the Project pages' do it 'redirects to the details page' do + visit project_issues_path(project) + find('body').native.send_key('g') find('body').native.send_key('p') @@ -22,7 +24,7 @@ describe 'User uses shortcuts', :js do it 'redirects to the activity page' do find('body').native.send_key('g') - find('body').native.send_key('e') + find('body').native.send_key('v') expect(page).to have_active_navigation('Project') expect(page).to have_active_sub_navigation('Activity') @@ -72,10 +74,19 @@ describe 'User uses shortcuts', :js do expect(page).to have_active_sub_navigation('List') end + it 'redirects to the issue board page' do + find('body').native.send_key('g') + find('body').native.send_key('b') + + expect(page).to have_active_navigation('Issues') + expect(page).to have_active_sub_navigation('Board') + end + it 'redirects to the new issue page' do find('body').native.send_key('i') expect(page).to have_content(project.title) + expect(page).to have_content('New Issue') end end @@ -88,6 +99,34 @@ describe 'User uses shortcuts', :js do end end + context 'when navigating to the CI / CD pages' do + it 'redirects to the Jobs page' do + find('body').native.send_key('g') + find('body').native.send_key('j') + + expect(page).to have_active_navigation('CI / CD') + expect(page).to have_active_sub_navigation('Jobs') + end + end + + context 'when navigating to the Operations pages' do + it 'redirects to the Environments page' do + find('body').native.send_key('g') + find('body').native.send_key('e') + + expect(page).to have_active_navigation('Operations') + expect(page).to have_active_sub_navigation('Environments') + end + + it 'redirects to the Kubernetes page' do + find('body').native.send_key('g') + find('body').native.send_key('k') + + expect(page).to have_active_navigation('Operations') + expect(page).to have_active_sub_navigation('Kubernetes') + end + end + context 'when navigating to the Snippets pages' do it 'redirects to the snippets page' do find('body').native.send_key('g') diff --git a/spec/features/projects/wiki/markdown_preview_spec.rb b/spec/features/projects/wiki/markdown_preview_spec.rb index 6586ccaa400..e473739a6aa 100644 --- a/spec/features/projects/wiki/markdown_preview_spec.rb +++ b/spec/features/projects/wiki/markdown_preview_spec.rb @@ -155,4 +155,27 @@ feature 'Projects > Wiki > User previews markdown changes', :js do end end end + + it "does not linkify double brackets inside code blocks as expected" do + click_link 'New page' + page.within '#modal-new-wiki' do + fill_in :new_wiki_path, with: 'linkify_test' + click_button 'Create page' + end + + page.within '.wiki-form' do + fill_in :wiki_content, with: <<-HEREDOC + `[[do_not_linkify]]` + ``` + [[also_do_not_linkify]] + ``` + HEREDOC + click_on "Preview" + end + + expect(page).to have_content("do_not_linkify") + + expect(page.html).to include('[[do_not_linkify]]') + expect(page.html).to include('[[also_do_not_linkify]]') + end end diff --git a/spec/features/users/user_browses_projects_on_user_page_spec.rb b/spec/features/users/user_browses_projects_on_user_page_spec.rb index a70637c8370..7bede0b0d48 100644 --- a/spec/features/users/user_browses_projects_on_user_page_spec.rb +++ b/spec/features/users/user_browses_projects_on_user_page_spec.rb @@ -27,8 +27,8 @@ describe 'Users > User browses projects on user page', :js do end it 'paginates projects', :js do - project = create(:project, namespace: user.namespace) - project2 = create(:project, namespace: user.namespace) + project = create(:project, namespace: user.namespace, updated_at: 2.minutes.since) + project2 = create(:project, namespace: user.namespace, updated_at: 1.minute.since) allow(Project).to receive(:default_per_page).and_return(1) sign_in(user) @@ -41,11 +41,11 @@ describe 'Users > User browses projects on user page', :js do wait_for_requests - expect(page).to have_content(project2.name) + expect(page).to have_content(project.name) click_link('Next') - expect(page).to have_content(project.name) + expect(page).to have_content(project2.name) end context 'when not signed in' do diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index 45439640ea3..74e91b02f0f 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -372,7 +372,7 @@ describe IssuesFinder do end context 'personal scope' do - let(:scope) { 'assigned-to-me' } + let(:scope) { 'assigned_to_me' } it 'returns issue assigned to the user' do expect(issues).to contain_exactly(issue1, issue2) diff --git a/spec/finders/personal_projects_finder_spec.rb b/spec/finders/personal_projects_finder_spec.rb index 5e52898e9c0..00c551a1f65 100644 --- a/spec/finders/personal_projects_finder_spec.rb +++ b/spec/finders/personal_projects_finder_spec.rb @@ -4,14 +4,16 @@ describe PersonalProjectsFinder do let(:source_user) { create(:user) } let(:current_user) { create(:user) } let(:finder) { described_class.new(source_user) } - let!(:public_project) { create(:project, :public, namespace: source_user.namespace) } + let!(:public_project) do + create(:project, :public, namespace: source_user.namespace, updated_at: 1.hour.ago) + end let!(:private_project) do - create(:project, :private, namespace: source_user.namespace, path: 'mepmep') + create(:project, :private, namespace: source_user.namespace, updated_at: 3.hours.ago, path: 'mepmep') end let!(:internal_project) do - create(:project, :internal, namespace: source_user.namespace, path: 'C') + create(:project, :internal, namespace: source_user.namespace, updated_at: 2.hours.ago, path: 'C') end before do @@ -28,7 +30,7 @@ describe PersonalProjectsFinder do subject { finder.execute(current_user) } context 'normal user' do - it { is_expected.to eq([internal_project, private_project, public_project]) } + it { is_expected.to eq([public_project, internal_project, private_project]) } end context 'external' do @@ -36,7 +38,7 @@ describe PersonalProjectsFinder do current_user.update_attributes(external: true) end - it { is_expected.to eq([private_project, public_project]) } + it { is_expected.to eq([public_project, private_project]) } end end end diff --git a/spec/javascripts/environments/environments_app_spec.js b/spec/javascripts/environments/environments_app_spec.js index e4c3bf2bef1..615168645b4 100644 --- a/spec/javascripts/environments/environments_app_spec.js +++ b/spec/javascripts/environments/environments_app_spec.js @@ -1,8 +1,8 @@ -import _ from 'underscore'; import Vue from 'vue'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; import environmentsComponent from '~/environments/components/environments_app.vue'; import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import { headersInterceptor } from 'spec/helpers/vue_resource_helper'; import { environment, folder } from './mock_data'; describe('Environment', () => { @@ -18,103 +18,76 @@ describe('Environment', () => { let EnvironmentsComponent; let component; + let mock; beforeEach(() => { + mock = new MockAdapter(axios); + EnvironmentsComponent = Vue.extend(environmentsComponent); }); + afterEach(() => { + component.$destroy(); + mock.restore(); + }); + describe('successfull request', () => { describe('without environments', () => { - const environmentsEmptyResponseInterceptor = (request, next) => { - next(request.respondWith(JSON.stringify([]), { - status: 200, - })); - }; + beforeEach((done) => { + mock.onGet(mockData.endpoint).reply(200, { environments: [] }); - beforeEach(() => { - Vue.http.interceptors.push(environmentsEmptyResponseInterceptor); - Vue.http.interceptors.push(headersInterceptor); - }); - - afterEach(() => { - Vue.http.interceptors = _.without( - Vue.http.interceptors, environmentsEmptyResponseInterceptor, - ); - Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor); - }); - - it('should render the empty state', (done) => { component = mountComponent(EnvironmentsComponent, mockData); setTimeout(() => { - expect( - component.$el.querySelector('.js-new-environment-button').textContent, - ).toContain('New environment'); - - expect( - component.$el.querySelector('.js-blank-state-title').textContent, - ).toContain('You don\'t have any environments right now.'); - done(); }, 0); }); + + it('should render the empty state', () => { + expect( + component.$el.querySelector('.js-new-environment-button').textContent, + ).toContain('New environment'); + + expect( + component.$el.querySelector('.js-blank-state-title').textContent, + ).toContain('You don\'t have any environments right now.'); + }); }); describe('with paginated environments', () => { - let backupInterceptors; - const environmentsResponseInterceptor = (request, next) => { - next((response) => { - response.headers.set('X-nExt-pAge', '2'); - }); - - next(request.respondWith(JSON.stringify({ + beforeEach((done) => { + mock.onGet(mockData.endpoint).reply(200, { environments: [environment], stopped_count: 1, available_count: 0, - }), { - status: 200, - headers: { - 'X-nExt-pAge': '2', - 'x-page': '1', - 'X-Per-Page': '1', - 'X-Prev-Page': '', - 'X-TOTAL': '37', - 'X-Total-Pages': '2', - }, - })); - }; + }, { + 'X-nExt-pAge': '2', + 'x-page': '1', + 'X-Per-Page': '1', + 'X-Prev-Page': '', + 'X-TOTAL': '37', + 'X-Total-Pages': '2', + }); - beforeEach(() => { - backupInterceptors = Vue.http.interceptors; - Vue.http.interceptors = [ - environmentsResponseInterceptor, - headersInterceptor, - ]; component = mountComponent(EnvironmentsComponent, mockData); - }); - afterEach(() => { - Vue.http.interceptors = backupInterceptors; - }); - - it('should render a table with environments', (done) => { setTimeout(() => { - expect(component.$el.querySelectorAll('table')).not.toBeNull(); - expect( - component.$el.querySelector('.environment-name').textContent.trim(), - ).toEqual(environment.name); done(); }, 0); }); + it('should render a table with environments', () => { + expect(component.$el.querySelectorAll('table')).not.toBeNull(); + expect( + component.$el.querySelector('.environment-name').textContent.trim(), + ).toEqual(environment.name); + }); + describe('pagination', () => { - it('should render pagination', (done) => { - setTimeout(() => { - expect( - component.$el.querySelectorAll('.gl-pagination li').length, - ).toEqual(5); - done(); - }, 0); + it('should render pagination', () => { + expect( + component.$el.querySelectorAll('.gl-pagination li').length, + ).toEqual(5); }); it('should make an API request when page is clicked', (done) => { @@ -133,50 +106,39 @@ describe('Environment', () => { expect(component.updateContent).toHaveBeenCalledWith({ scope: 'stopped', page: '1' }); done(); - }); + }, 0); }); }); }); }); describe('unsuccessfull request', () => { - const environmentsErrorResponseInterceptor = (request, next) => { - next(request.respondWith(JSON.stringify([]), { - status: 500, - })); - }; + beforeEach((done) => { + mock.onGet(mockData.endpoint).reply(500, {}); - beforeEach(() => { - Vue.http.interceptors.push(environmentsErrorResponseInterceptor); - }); - - afterEach(() => { - Vue.http.interceptors = _.without( - Vue.http.interceptors, environmentsErrorResponseInterceptor, - ); - }); - - it('should render empty state', (done) => { component = mountComponent(EnvironmentsComponent, mockData); setTimeout(() => { - expect( - component.$el.querySelector('.js-blank-state-title').textContent, - ).toContain('You don\'t have any environments right now.'); done(); }, 0); }); + + it('should render empty state', () => { + expect( + component.$el.querySelector('.js-blank-state-title').textContent, + ).toContain('You don\'t have any environments right now.'); + }); }); describe('expandable folders', () => { - const environmentsResponseInterceptor = (request, next) => { - next(request.respondWith(JSON.stringify({ - environments: [folder], - stopped_count: 0, - available_count: 1, - }), { - status: 200, - headers: { + beforeEach(() => { + mock.onGet(mockData.endpoint).reply(200, + { + environments: [folder], + stopped_count: 0, + available_count: 1, + }, + { 'X-nExt-pAge': '2', 'x-page': '1', 'X-Per-Page': '1', @@ -184,18 +146,11 @@ describe('Environment', () => { 'X-TOTAL': '37', 'X-Total-Pages': '2', }, - })); - }; - - beforeEach(() => { - Vue.http.interceptors.push(environmentsResponseInterceptor); - component = mountComponent(EnvironmentsComponent, mockData); - }); - - afterEach(() => { - Vue.http.interceptors = _.without( - Vue.http.interceptors, environmentsResponseInterceptor, ); + + mock.onGet(environment.folder_path).reply(200, { environments: [environment] }); + + component = mountComponent(EnvironmentsComponent, mockData); }); it('should open a closed folder', (done) => { @@ -211,7 +166,7 @@ describe('Environment', () => { ).not.toContain('display: none'); done(); }); - }); + }, 0); }); it('should close an opened folder', (done) => { @@ -233,7 +188,7 @@ describe('Environment', () => { done(); }); }); - }); + }, 0); }); it('should show children environments and a button to show all environments', (done) => { @@ -242,49 +197,32 @@ describe('Environment', () => { component.$el.querySelector('.folder-name').click(); Vue.nextTick(() => { - const folderInterceptor = (request, next) => { - next(request.respondWith(JSON.stringify({ - environments: [environment], - }), { status: 200 })); - }; - - Vue.http.interceptors.push(folderInterceptor); - // wait for next async request setTimeout(() => { expect(component.$el.querySelectorAll('.js-child-row').length).toEqual(1); expect(component.$el.querySelector('.text-center > a.btn').textContent).toContain('Show all'); - - Vue.http.interceptors = _.without(Vue.http.interceptors, folderInterceptor); done(); }); }); - }); + }, 0); }); }); describe('methods', () => { - const environmentsEmptyResponseInterceptor = (request, next) => { - next(request.respondWith(JSON.stringify([]), { - status: 200, - })); - }; - beforeEach(() => { - Vue.http.interceptors.push(environmentsEmptyResponseInterceptor); - Vue.http.interceptors.push(headersInterceptor); + mock.onGet(mockData.endpoint).reply(200, + { + environments: [], + stopped_count: 0, + available_count: 1, + }, + {}, + ); component = mountComponent(EnvironmentsComponent, mockData); spyOn(history, 'pushState').and.stub(); }); - afterEach(() => { - Vue.http.interceptors = _.without( - Vue.http.interceptors, environmentsEmptyResponseInterceptor, - ); - Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor); - }); - describe('updateContent', () => { it('should set given parameters', (done) => { component.updateContent({ scope: 'stopped', page: '3' }) diff --git a/spec/javascripts/environments/folder/environments_folder_view_spec.js b/spec/javascripts/environments/folder/environments_folder_view_spec.js index 906a1116974..f5ce4df0bfe 100644 --- a/spec/javascripts/environments/folder/environments_folder_view_spec.js +++ b/spec/javascripts/environments/folder/environments_folder_view_spec.js @@ -1,13 +1,15 @@ -import _ from 'underscore'; import Vue from 'vue'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; import environmentsFolderViewComponent from '~/environments/folder/environments_folder_view.vue'; -import { headersInterceptor } from 'spec/helpers/vue_resource_helper'; import mountComponent from 'spec/helpers/vue_mount_component_helper'; import { environmentsList } from '../mock_data'; describe('Environments Folder View', () => { let Component; let component; + let mock; + const mockData = { endpoint: 'environments.json', folderName: 'review', @@ -17,46 +19,35 @@ describe('Environments Folder View', () => { }; beforeEach(() => { + mock = new MockAdapter(axios); + Component = Vue.extend(environmentsFolderViewComponent); }); afterEach(() => { + mock.restore(); + component.$destroy(); }); describe('successfull request', () => { - const environmentsResponseInterceptor = (request, next) => { - next(request.respondWith(JSON.stringify({ + beforeEach(() => { + mock.onGet(mockData.endpoint).reply(200, { environments: environmentsList, stopped_count: 1, available_count: 0, - }), { - status: 200, - headers: { - 'X-nExt-pAge': '2', - 'x-page': '1', - 'X-Per-Page': '2', - 'X-Prev-Page': '', - 'X-TOTAL': '20', - 'X-Total-Pages': '10', - }, - })); - }; - - beforeEach(() => { - Vue.http.interceptors.push(environmentsResponseInterceptor); - Vue.http.interceptors.push(headersInterceptor); + }, { + 'X-nExt-pAge': '2', + 'x-page': '1', + 'X-Per-Page': '2', + 'X-Prev-Page': '', + 'X-TOTAL': '20', + 'X-Total-Pages': '10', + }); component = mountComponent(Component, mockData); }); - afterEach(() => { - Vue.http.interceptors = _.without( - Vue.http.interceptors, environmentsResponseInterceptor, - ); - Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor); - }); - it('should render a table with environments', (done) => { setTimeout(() => { expect(component.$el.querySelectorAll('table')).not.toBeNull(); @@ -135,25 +126,15 @@ describe('Environments Folder View', () => { }); describe('unsuccessfull request', () => { - const environmentsErrorResponseInterceptor = (request, next) => { - next(request.respondWith(JSON.stringify([]), { - status: 500, - })); - }; - beforeEach(() => { - Vue.http.interceptors.push(environmentsErrorResponseInterceptor); - }); + mock.onGet(mockData.endpoint).reply(500, { + environments: [], + }); - afterEach(() => { - Vue.http.interceptors = _.without( - Vue.http.interceptors, environmentsErrorResponseInterceptor, - ); + component = mountComponent(Component, mockData); }); it('should not render a table', (done) => { - component = mountComponent(Component, mockData); - setTimeout(() => { expect( component.$el.querySelector('table'), @@ -190,27 +171,15 @@ describe('Environments Folder View', () => { }); describe('methods', () => { - const environmentsEmptyResponseInterceptor = (request, next) => { - next(request.respondWith(JSON.stringify([]), { - status: 200, - })); - }; - beforeEach(() => { - Vue.http.interceptors.push(environmentsEmptyResponseInterceptor); - Vue.http.interceptors.push(headersInterceptor); + mock.onGet(mockData.endpoint).reply(200, { + environments: [], + }); component = mountComponent(Component, mockData); spyOn(history, 'pushState').and.stub(); }); - afterEach(() => { - Vue.http.interceptors = _.without( - Vue.http.interceptors, environmentsEmptyResponseInterceptor, - ); - Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor); - }); - describe('updateContent', () => { it('should set given parameters', (done) => { component.updateContent({ scope: 'stopped', page: '4' }) diff --git a/spec/javascripts/environments/mock_data.js b/spec/javascripts/environments/mock_data.js index 15e11aa686b..8a1e26935d9 100644 --- a/spec/javascripts/environments/mock_data.js +++ b/spec/javascripts/environments/mock_data.js @@ -82,6 +82,7 @@ export const environment = { stop_path: '/root/review-app/environments/7/stop', created_at: '2017-01-31T10:53:46.894Z', updated_at: '2017-01-31T10:53:46.894Z', + folder_path: '/root/review-app/environments/7', }, }; diff --git a/spec/javascripts/pipelines/graph/action_component_spec.js b/spec/javascripts/pipelines/graph/action_component_spec.js index d646bef96f5..568e679abe9 100644 --- a/spec/javascripts/pipelines/graph/action_component_spec.js +++ b/spec/javascripts/pipelines/graph/action_component_spec.js @@ -1,13 +1,19 @@ import Vue from 'vue'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; import actionComponent from '~/pipelines/components/graph/action_component.vue'; -import eventHub from '~/pipelines/event_hub'; import mountComponent from '../../helpers/vue_mount_component_helper'; describe('pipeline graph action component', () => { let component; + let mock; beforeEach(done => { const ActionComponent = Vue.extend(actionComponent); + mock = new MockAdapter(axios); + + mock.onPost('foo.json').reply(200); + component = mountComponent(ActionComponent, { tooltipText: 'bar', link: 'foo', @@ -18,15 +24,10 @@ describe('pipeline graph action component', () => { }); afterEach(() => { + mock.restore(); component.$destroy(); }); - it('should emit an event with the provided link', () => { - eventHub.$on('postAction', link => { - expect(link).toEqual('foo'); - }); - }); - it('should render the provided title as a bootstrap tooltip', () => { expect(component.$el.getAttribute('data-original-title')).toEqual('bar'); }); @@ -34,10 +35,12 @@ describe('pipeline graph action component', () => { it('should update bootstrap tooltip when title changes', done => { component.tooltipText = 'changed'; - setTimeout(() => { + component.$nextTick() + .then(() => { expect(component.$el.getAttribute('data-original-title')).toBe('changed'); - done(); - }); + }) + .then(done) + .catch(done.fail); }); it('should render an svg', () => { @@ -45,44 +48,18 @@ describe('pipeline graph action component', () => { expect(component.$el.querySelector('svg')).toBeDefined(); }); - it('disables the button when clicked', done => { - component.$el.click(); + describe('on click', () => { + it('emits `pipelineActionRequestComplete` after a successfull request', done => { + spyOn(component, '$emit'); - component.$nextTick(() => { - expect(component.$el.getAttribute('disabled')).toEqual('disabled'); - done(); + component.$el.click(); + + component.$nextTick() + .then(() => { + expect(component.$emit).toHaveBeenCalledWith('pipelineActionRequestComplete'); + }) + .then(done) + .catch(done.fail); }); }); - - it('re-enabled the button when `requestFinishedFor` matches `linkRequested`', done => { - component.$el.click(); - - component - .$nextTick() - .then(() => { - expect(component.$el.getAttribute('disabled')).toEqual('disabled'); - component.requestFinishedFor = 'foo'; - }) - .then(() => { - expect(component.$el.getAttribute('disabled')).toBeNull(); - }) - .then(done) - .catch(done.fail); - }); - - it('does not re-enable the button when `requestFinishedFor` does not matches `linkRequested`', done => { - component.$el.click(); - - component - .$nextTick() - .then(() => { - expect(component.$el.getAttribute('disabled')).toEqual('disabled'); - component.requestFinishedFor = 'bar'; - }) - .then(() => { - expect(component.$el.getAttribute('disabled')).toEqual('disabled'); - }) - .then(done) - .catch(done.fail); - }); }); diff --git a/spec/javascripts/pipelines/stage_spec.js b/spec/javascripts/pipelines/stage_spec.js index 75156e7bdfd..16f6db39d6a 100644 --- a/spec/javascripts/pipelines/stage_spec.js +++ b/spec/javascripts/pipelines/stage_spec.js @@ -102,4 +102,31 @@ describe('Pipelines stage component', () => { }); }); }); + + describe('pipelineActionRequestComplete', () => { + beforeEach(() => { + mock.onGet('path.json').reply(200, stageReply); + + mock.onPost(`${stageReply.latest_statuses[0].status.action.path}.json`).reply(200); + }); + + describe('within pipeline table', () => { + it('emits `refreshPipelinesTable` event when `pipelineActionRequestComplete` is triggered', done => { + spyOn(eventHub, '$emit'); + + component.type = 'PIPELINES_TABLE'; + component.$el.querySelector('button').click(); + + setTimeout(() => { + component.$el.querySelector('.js-ci-action').click(); + component.$nextTick() + .then(() => { + expect(eventHub.$emit).toHaveBeenCalledWith('refreshPipelinesTable'); + }) + .then(done) + .catch(done.fail); + }, 0); + }); + }); + }); }); diff --git a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb index ca76d6f0881..0e178b859c4 100644 --- a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb +++ b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb @@ -91,6 +91,12 @@ describe Banzai::Filter::GollumTagsFilter do expect(doc.at_css('a').text).to eq 'link-text' expect(doc.at_css('a')['href']).to eq expected_path end + + it "inside back ticks will be exempt from linkification" do + doc = filter('[[link-in-backticks]]', project_wiki: project_wiki) + + expect(doc.at_css('code').text).to eq '[[link-in-backticks]]' + end end context 'table of contents' do diff --git a/spec/lib/gitlab/auth/ldap/access_spec.rb b/spec/lib/gitlab/auth/ldap/access_spec.rb index 6b251d824f7..eff21985108 100644 --- a/spec/lib/gitlab/auth/ldap/access_spec.rb +++ b/spec/lib/gitlab/auth/ldap/access_spec.rb @@ -8,6 +8,7 @@ describe Gitlab::Auth::LDAP::Access do describe '.allowed?' do it 'updates the users `last_credential_check_at' do + allow(access).to receive(:update_user) expect(access).to receive(:allowed?) { true } expect(described_class).to receive(:open).and_yield(access) @@ -16,12 +17,21 @@ describe Gitlab::Auth::LDAP::Access do end end + describe '#find_ldap_user' do + it 'finds a user by dn first' do + expect(Gitlab::Auth::LDAP::Person).to receive(:find_by_dn).and_return(:ldap_user) + + access.find_ldap_user + end + end + describe '#allowed?' do subject { access.allowed? } context 'when the user cannot be found' do before do allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_dn).and_return(nil) + allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_email).and_return(nil) end it { is_expected.to be_falsey } @@ -54,7 +64,7 @@ describe Gitlab::Auth::LDAP::Access do end end - context 'and has no disabled flag in active diretory' do + context 'and has no disabled flag in active directory' do before do allow(Gitlab::Auth::LDAP::Person).to receive(:disabled_via_active_directory?).and_return(false) end @@ -100,6 +110,7 @@ describe Gitlab::Auth::LDAP::Access do context 'when user cannot be found' do before do allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_dn).and_return(nil) + allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_email).and_return(nil) end it { is_expected.to be_falsey } diff --git a/spec/lib/gitlab/auth/ldap/config_spec.rb b/spec/lib/gitlab/auth/ldap/config_spec.rb index 82587e2ba55..d3ab599d5a0 100644 --- a/spec/lib/gitlab/auth/ldap/config_spec.rb +++ b/spec/lib/gitlab/auth/ldap/config_spec.rb @@ -23,7 +23,7 @@ describe Gitlab::Auth::LDAP::Config do end it 'raises an error if a unknown provider is used' do - expect { described_class.new 'unknown' }.to raise_error(RuntimeError) + expect { described_class.new 'unknown' }.to raise_error(described_class::InvalidProvider) end end @@ -370,4 +370,38 @@ describe Gitlab::Auth::LDAP::Config do }) end end + + describe '#base' do + context 'when the configured base is not normalized' do + it 'returns the normalized base' do + stub_ldap_config(options: { 'base' => 'DC=example, DC= com' }) + + expect(config.base).to eq('dc=example,dc=com') + end + end + + context 'when the configured base is normalized' do + it 'returns the base unaltered' do + stub_ldap_config(options: { 'base' => 'dc=example,dc=com' }) + + expect(config.base).to eq('dc=example,dc=com') + end + end + + context 'when the configured base is malformed' do + it 'returns the base unaltered' do + stub_ldap_config(options: { 'base' => 'invalid,dc=example,dc=com' }) + + expect(config.base).to eq('invalid,dc=example,dc=com') + end + end + + context 'when the configured base is blank' do + it 'returns the base unaltered' do + stub_ldap_config(options: { 'base' => '' }) + + expect(config.base).to eq('') + end + end + end end diff --git a/spec/lib/gitlab/auth/ldap/user_spec.rb b/spec/lib/gitlab/auth/ldap/user_spec.rb index 653c19942ea..44bb9d20e47 100644 --- a/spec/lib/gitlab/auth/ldap/user_spec.rb +++ b/spec/lib/gitlab/auth/ldap/user_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Gitlab::Auth::LDAP::User do + include LdapHelpers + let(:ldap_user) { described_class.new(auth_hash) } let(:gl_user) { ldap_user.gl_user } let(:info) do @@ -177,8 +179,7 @@ describe Gitlab::Auth::LDAP::User do describe 'blocking' do def configure_block(value) - allow_any_instance_of(Gitlab::Auth::LDAP::Config) - .to receive(:block_auto_created_users).and_return(value) + stub_ldap_config(block_auto_created_users: value) end context 'signup' do diff --git a/spec/lib/gitlab/ci/config/entry/policy_spec.rb b/spec/lib/gitlab/ci/config/entry/policy_spec.rb index 08718c382b9..83d39b82068 100644 --- a/spec/lib/gitlab/ci/config/entry/policy_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/policy_spec.rb @@ -111,7 +111,15 @@ describe Gitlab::Ci::Config::Entry::Policy do context 'when specifying invalid variables expressions token' do let(:config) { { variables: ['$MY_VAR == 123'] } } - it 'reports an error about invalid statement' do + it 'reports an error about invalid expression' do + expect(entry.errors).to include /invalid expression syntax/ + end + end + + context 'when using invalid variables expressions regexp' do + let(:config) { { variables: ['$MY_VAR =~ /some ( thing/'] } } + + it 'reports an error about invalid expression' do expect(entry.errors).to include /invalid expression syntax/ end end diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb new file mode 100644 index 00000000000..49e5af52f4d --- /dev/null +++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb @@ -0,0 +1,80 @@ +require 'fast_spec_helper' +require_dependency 're2' + +describe Gitlab::Ci::Pipeline::Expression::Lexeme::Matches do + let(:left) { double('left') } + let(:right) { double('right') } + + describe '.build' do + it 'creates a new instance of the token' do + expect(described_class.build('=~', left, right)) + .to be_a(described_class) + end + end + + describe '.type' do + it 'is an operator' do + expect(described_class.type).to eq :operator + end + end + + describe '#evaluate' do + it 'returns false when left and right do not match' do + allow(left).to receive(:evaluate).and_return('my-string') + allow(right).to receive(:evaluate) + .and_return(Gitlab::UntrustedRegexp.new('something')) + + operator = described_class.new(left, right) + + expect(operator.evaluate).to eq false + end + + it 'returns true when left and right match' do + allow(left).to receive(:evaluate).and_return('my-awesome-string') + allow(right).to receive(:evaluate) + .and_return(Gitlab::UntrustedRegexp.new('awesome.string$')) + + operator = described_class.new(left, right) + + expect(operator.evaluate).to eq true + end + + it 'supports matching against a nil value' do + allow(left).to receive(:evaluate).and_return(nil) + allow(right).to receive(:evaluate) + .and_return(Gitlab::UntrustedRegexp.new('pattern')) + + operator = described_class.new(left, right) + + expect(operator.evaluate).to eq false + end + + it 'supports multiline strings' do + allow(left).to receive(:evaluate).and_return <<~TEXT + My awesome contents + + My-text-string! + TEXT + + allow(right).to receive(:evaluate) + .and_return(Gitlab::UntrustedRegexp.new('text-string')) + + operator = described_class.new(left, right) + + expect(operator.evaluate).to eq true + end + + it 'supports regexp flags' do + allow(left).to receive(:evaluate).and_return <<~TEXT + My AWESOME content + TEXT + + allow(right).to receive(:evaluate) + .and_return(Gitlab::UntrustedRegexp.new('(?i)awesome')) + + operator = described_class.new(left, right) + + expect(operator.evaluate).to eq true + end + end +end diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb new file mode 100644 index 00000000000..3ebc2e94727 --- /dev/null +++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb @@ -0,0 +1,96 @@ +require 'fast_spec_helper' + +describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern do + describe '.build' do + it 'creates a new instance of the token' do + expect(described_class.build('/.*/')) + .to be_a(described_class) + end + + it 'raises an error if pattern is invalid' do + expect { described_class.build('/ some ( thin/i') } + .to raise_error(Gitlab::Ci::Pipeline::Expression::Lexer::SyntaxError) + end + end + + describe '.type' do + it 'is a value lexeme' do + expect(described_class.type).to eq :value + end + end + + describe '.scan' do + it 'correctly identifies a pattern token' do + scanner = StringScanner.new('/pattern/') + + token = described_class.scan(scanner) + + expect(token).not_to be_nil + expect(token.build.evaluate) + .to eq Gitlab::UntrustedRegexp.new('pattern') + end + + it 'is a greedy scanner for regexp boundaries' do + scanner = StringScanner.new('/some .* / pattern/') + + token = described_class.scan(scanner) + + expect(token).not_to be_nil + expect(token.build.evaluate) + .to eq Gitlab::UntrustedRegexp.new('some .* / pattern') + end + + it 'does not allow to use an empty pattern' do + scanner = StringScanner.new(%(//)) + + token = described_class.scan(scanner) + + expect(token).to be_nil + end + + it 'support single flag' do + scanner = StringScanner.new('/pattern/i') + + token = described_class.scan(scanner) + + expect(token).not_to be_nil + expect(token.build.evaluate) + .to eq Gitlab::UntrustedRegexp.new('(?i)pattern') + end + + it 'support multiple flags' do + scanner = StringScanner.new('/pattern/im') + + token = described_class.scan(scanner) + + expect(token).not_to be_nil + expect(token.build.evaluate) + .to eq Gitlab::UntrustedRegexp.new('(?im)pattern') + end + + it 'does not support arbitrary flags' do + scanner = StringScanner.new('/pattern/x') + + token = described_class.scan(scanner) + + expect(token).to be_nil + end + end + + describe '#evaluate' do + it 'returns a regular expression' do + regexp = described_class.new('/abc/') + + expect(regexp.evaluate).to eq Gitlab::UntrustedRegexp.new('abc') + end + + it 'raises error if evaluated regexp is not valid' do + allow(Gitlab::UntrustedRegexp).to receive(:valid?).and_return(true) + + regexp = described_class.new('/invalid ( .*/') + + expect { regexp.evaluate } + .to raise_error(Gitlab::Ci::Pipeline::Expression::RuntimeError) + end + end +end diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb index 230ceeb07f8..3f11b3f7673 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb @@ -6,7 +6,7 @@ describe Gitlab::Ci::Pipeline::Expression::Lexer do end describe '#tokens' do - it 'tokenss single value' do + it 'returns single value' do tokens = described_class.new('$VARIABLE').tokens expect(tokens).to be_one @@ -20,14 +20,14 @@ describe Gitlab::Ci::Pipeline::Expression::Lexer do expect(tokens).to all(be_an_instance_of(token_class)) end - it 'tokenss multiple values of the same token' do + it 'returns multiple values of the same token' do tokens = described_class.new("$VARIABLE1 $VARIABLE2").tokens expect(tokens.size).to eq 2 expect(tokens).to all(be_an_instance_of(token_class)) end - it 'tokenss multiple values with different tokens' do + it 'returns multiple values with different tokens' do tokens = described_class.new('$VARIABLE "text" "value"').tokens expect(tokens.size).to eq 3 @@ -36,7 +36,7 @@ describe Gitlab::Ci::Pipeline::Expression::Lexer do expect(tokens.third.value).to eq '"value"' end - it 'tokenss tokens and operators' do + it 'returns tokens and operators' do tokens = described_class.new('$VARIABLE == "text"').tokens expect(tokens.size).to eq 3 diff --git a/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb index e8e6f585310..2b78b1dd4a7 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb @@ -1,4 +1,4 @@ -require 'spec_helper' +require 'fast_spec_helper' describe Gitlab::Ci::Pipeline::Expression::Parser do describe '#tree' do diff --git a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb index 6685bf5385b..11e73294f18 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb @@ -1,4 +1,5 @@ -require 'spec_helper' +require 'fast_spec_helper' +require 'rspec-parameterized' describe Gitlab::Ci::Pipeline::Expression::Statement do subject do @@ -36,7 +37,7 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do '== "123"', # invalid left side '"some string"', # only string provided '$VAR ==', # invalid right side - '12345', # unknown syntax + 'null', # missing lexemes '' # empty statement ] @@ -44,7 +45,7 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do context "when expression grammar is #{syntax.inspect}" do let(:text) { syntax } - it 'aises a statement error exception' do + it 'raises a statement error exception' do expect { subject.parse_tree } .to raise_error described_class::StatementError end @@ -82,48 +83,66 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do end describe '#evaluate' do - statements = [ - ['$PRESENT_VARIABLE == "my variable"', true], - ["$PRESENT_VARIABLE == 'my variable'", true], - ['"my variable" == $PRESENT_VARIABLE', true], - ['$PRESENT_VARIABLE == null', false], - ['$EMPTY_VARIABLE == null', false], - ['"" == $EMPTY_VARIABLE', true], - ['$EMPTY_VARIABLE', ''], - ['$UNDEFINED_VARIABLE == null', true], - ['null == $UNDEFINED_VARIABLE', true], - ['$PRESENT_VARIABLE', 'my variable'], - ['$UNDEFINED_VARIABLE', nil] - ] + using RSpec::Parameterized::TableSyntax - statements.each do |expression, value| - context "when using expression `#{expression}`" do - let(:text) { expression } + where(:expression, :value) do + '$PRESENT_VARIABLE == "my variable"' | true + '"my variable" == $PRESENT_VARIABLE' | true + '$PRESENT_VARIABLE == null' | false + '$EMPTY_VARIABLE == null' | false + '"" == $EMPTY_VARIABLE' | true + '$EMPTY_VARIABLE' | '' + '$UNDEFINED_VARIABLE == null' | true + 'null == $UNDEFINED_VARIABLE' | true + '$PRESENT_VARIABLE' | 'my variable' + '$UNDEFINED_VARIABLE' | nil + "$PRESENT_VARIABLE =~ /var.*e$/" | true + "$PRESENT_VARIABLE =~ /^var.*/" | false + "$EMPTY_VARIABLE =~ /var.*/" | false + "$UNDEFINED_VARIABLE =~ /var.*/" | false + "$PRESENT_VARIABLE =~ /VAR.*/i" | true + end - it "evaluates to `#{value.inspect}`" do - expect(subject.evaluate).to eq value - end + with_them do + let(:text) { expression } + + it "evaluates to `#{params[:value].inspect}`" do + expect(subject.evaluate).to eq value end end end describe '#truthful?' do - statements = [ - ['$PRESENT_VARIABLE == "my variable"', true], - ["$PRESENT_VARIABLE == 'no match'", false], - ['$UNDEFINED_VARIABLE == null', true], - ['$PRESENT_VARIABLE', true], - ['$UNDEFINED_VARIABLE', false], - ['$EMPTY_VARIABLE', false] - ] + using RSpec::Parameterized::TableSyntax - statements.each do |expression, value| - context "when using expression `#{expression}`" do - let(:text) { expression } + where(:expression, :value) do + '$PRESENT_VARIABLE == "my variable"' | true + "$PRESENT_VARIABLE == 'no match'" | false + '$UNDEFINED_VARIABLE == null' | true + '$PRESENT_VARIABLE' | true + '$UNDEFINED_VARIABLE' | false + '$EMPTY_VARIABLE' | false + '$INVALID = 1' | false + "$PRESENT_VARIABLE =~ /var.*/" | true + "$UNDEFINED_VARIABLE =~ /var.*/" | false + end - it "returns `#{value.inspect}`" do - expect(subject.truthful?).to eq value - end + with_them do + let(:text) { expression } + + it "returns `#{params[:value].inspect}`" do + expect(subject.truthful?).to eq value + end + end + + context 'when evaluating expression raises an error' do + let(:text) { '$PRESENT_VARIABLE' } + + it 'returns false' do + allow(subject).to receive(:evaluate) + .and_raise(described_class::StatementError) + + expect(subject.truthful?).to be_falsey end end end diff --git a/spec/lib/gitlab/ci/pipeline/expression/token_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/token_spec.rb index 6d7453f0de5..cedfe270f9d 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/token_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/token_spec.rb @@ -1,4 +1,4 @@ -require 'spec_helper' +require 'fast_spec_helper' describe Gitlab::Ci::Pipeline::Expression::Token do let(:value) { '$VARIABLE' } diff --git a/spec/lib/gitlab/ci/pipeline/preloader_spec.rb b/spec/lib/gitlab/ci/pipeline/preloader_spec.rb new file mode 100644 index 00000000000..477c7477df0 --- /dev/null +++ b/spec/lib/gitlab/ci/pipeline/preloader_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Ci::Pipeline::Preloader do + describe '.preload' do + it 'preloads the author of every pipeline commit' do + commit = double(:commit) + pipeline = double(:pipeline, commit: commit) + + expect(commit) + .to receive(:lazy_author) + + expect(pipeline) + .to receive(:number_of_warnings) + + described_class.preload([pipeline]) + end + end +end diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb index 1fe1d3926ad..8ac36ae8bab 100644 --- a/spec/lib/gitlab/database_spec.rb +++ b/spec/lib/gitlab/database_spec.rb @@ -32,6 +32,12 @@ describe Gitlab::Database do end describe '.version' do + around do |example| + described_class.instance_variable_set(:@version, nil) + example.run + described_class.instance_variable_set(:@version, nil) + end + context "on mysql" do it "extracts the version number" do allow(described_class).to receive(:database_version) @@ -49,6 +55,14 @@ describe Gitlab::Database do expect(described_class.version).to eq '9.4.4' end end + + it 'memoizes the result' do + count = ActiveRecord::QueryRecorder + .new { 2.times { described_class.version } } + .count + + expect(count).to eq(1) + end end describe '.join_lateral_supported?' do diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb index a05feaac1ca..08c6d1e55e9 100644 --- a/spec/lib/gitlab/git/commit_spec.rb +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -66,7 +66,8 @@ describe Gitlab::Git::Commit, seed_helper: true do describe "Commit info from gitaly commit" do let(:subject) { "My commit".force_encoding('ASCII-8BIT') } let(:body) { subject + "My body".force_encoding('ASCII-8BIT') } - let(:gitaly_commit) { build(:gitaly_commit, subject: subject, body: body) } + let(:body_size) { body.length } + let(:gitaly_commit) { build(:gitaly_commit, subject: subject, body: body, body_size: body_size) } let(:id) { gitaly_commit.id } let(:committer) { gitaly_commit.committer } let(:author) { gitaly_commit.author } @@ -83,10 +84,30 @@ describe Gitlab::Git::Commit, seed_helper: true do it { expect(commit.committer_email).to eq(committer.email) } it { expect(commit.parent_ids).to eq(gitaly_commit.parent_ids) } - context 'no body' do + context 'body_size != body.size' do let(:body) { "".force_encoding('ASCII-8BIT') } - it { expect(commit.safe_message).to eq(subject) } + context 'zero body_size' do + it { expect(commit.safe_message).to eq(subject) } + end + + context 'body_size less than threshold' do + let(:body_size) { 123 } + + it 'fetches commit message seperately' do + expect(described_class).to receive(:get_message).with(repository, id) + + commit.safe_message + end + end + + context 'body_size greater than threshold' do + let(:body_size) { described_class::MAX_COMMIT_MESSAGE_DISPLAY_SIZE + 1 } + + it 'returns the suject plus a notice about message size' do + expect(commit.safe_message).to eq("My commit\n\n--commit message is too big") + end + end end end @@ -554,24 +575,10 @@ describe Gitlab::Git::Commit, seed_helper: true do it_should_behave_like '#stats' end - describe '#to_diff' do - subject { commit.to_diff } - - it { is_expected.not_to include "From #{SeedRepo::Commit::ID}" } - it { is_expected.to include 'diff --git a/files/ruby/popen.rb b/files/ruby/popen.rb'} - end - describe '#has_zero_stats?' do it { expect(commit.has_zero_stats?).to eq(false) } end - describe '#to_patch' do - subject { commit.to_patch } - - it { is_expected.to include "From #{SeedRepo::Commit::ID}" } - it { is_expected.to include 'diff --git a/files/ruby/popen.rb b/files/ruby/popen.rb'} - end - describe '#to_hash' do let(:hash) { commit.to_hash } subject { hash } @@ -603,6 +610,35 @@ describe Gitlab::Git::Commit, seed_helper: true do it { is_expected.not_to include("feature") } end + describe '.get_message' do + let(:commit_ids) { %w[6d394385cf567f80a8fd85055db1ab4c5295806f cfe32cf61b73a0d5e9f13e774abde7ff789b1660] } + + subject do + commit_ids.map { |id| described_class.get_message(repository, id) } + end + + shared_examples 'getting commit messages' do + it 'gets commit messages' do + expect(subject).to contain_exactly( + "Added contributing guide\n\nSigned-off-by: Dmitriy Zaporozhets \n", + "Add submodule\n\nSigned-off-by: Dmitriy Zaporozhets \n" + ) + end + end + + context 'when Gitaly commit_messages feature is enabled' do + it_behaves_like 'getting commit messages' + + it 'gets messages in one batch', :request_store do + expect { subject.map(&:itself) }.to change { Gitlab::GitalyClient.get_request_count }.by(1) + end + end + + context 'when Gitaly commit_messages feature is disabled', :disable_gitaly do + it_behaves_like 'getting commit messages' + end + end + def sample_commit_hash { author_email: "dmitriy.zaporozhets@gmail.com", diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index fcb690d8aa3..af6a486ab20 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -249,10 +249,6 @@ describe Gitlab::Git::Repository, seed_helper: true do subject(:metadata) { repository.archive_metadata(ref, storage_path, format, append_sha: append_sha) } - it 'sets RepoPath to the repository path' do - expect(metadata['RepoPath']).to eq(repository.path) - end - it 'sets CommitId to the commit SHA' do expect(metadata['CommitId']).to eq(SeedRepo::LastCommit::ID) end @@ -600,6 +596,10 @@ describe Gitlab::Git::Repository, seed_helper: true do end end + it 'does not fail when deleting an empty list of refs' do + expect { repo.delete_refs(*[]) }.not_to raise_error + end + it 'raises an error if it failed' do expect { repo.delete_refs('refs\heads\fix') }.to raise_error(Gitlab::Git::Repository::GitError) end @@ -2247,66 +2247,42 @@ describe Gitlab::Git::Repository, seed_helper: true do end describe '#checksum' do - shared_examples 'calculating checksum' do - it 'calculates the checksum for non-empty repo' do - expect(repository.checksum).to eq '54f21be4c32c02f6788d72207fa03ad3bce725e4' - end - - it 'returns 0000000000000000000000000000000000000000 for an empty repo' do - FileUtils.rm_rf(File.join(storage_path, 'empty-repo.git')) - - system(git_env, *%W(#{Gitlab.config.git.bin_path} init --bare empty-repo.git), - chdir: storage_path, - out: '/dev/null', - err: '/dev/null') - - empty_repo = described_class.new('default', 'empty-repo.git', '') - - expect(empty_repo.checksum).to eq '0000000000000000000000000000000000000000' - end - - it 'raises Gitlab::Git::Repository::InvalidRepository error for non-valid git repo' do - FileUtils.rm_rf(File.join(storage_path, 'non-valid.git')) - - system(git_env, *%W(#{Gitlab.config.git.bin_path} clone --bare #{TEST_REPO_PATH} non-valid.git), - chdir: SEED_STORAGE_PATH, - out: '/dev/null', - err: '/dev/null') - - File.truncate(File.join(storage_path, 'non-valid.git/HEAD'), 0) - - non_valid = described_class.new('default', 'non-valid.git', '') - - expect { non_valid.checksum }.to raise_error(Gitlab::Git::Repository::InvalidRepository) - end - - it 'raises Gitlab::Git::Repository::NoRepository error when there is no repo' do - broken_repo = described_class.new('default', 'a/path.git', '') - - expect { broken_repo.checksum }.to raise_error(Gitlab::Git::Repository::NoRepository) - end + it 'calculates the checksum for non-empty repo' do + expect(repository.checksum).to eq '54f21be4c32c02f6788d72207fa03ad3bce725e4' end - context 'when calculate_checksum Gitaly feature is enabled' do - it_behaves_like 'calculating checksum' + it 'returns 0000000000000000000000000000000000000000 for an empty repo' do + FileUtils.rm_rf(File.join(storage_path, 'empty-repo.git')) + + system(git_env, *%W(#{Gitlab.config.git.bin_path} init --bare empty-repo.git), + chdir: storage_path, + out: '/dev/null', + err: '/dev/null') + + empty_repo = described_class.new('default', 'empty-repo.git', '') + + expect(empty_repo.checksum).to eq '0000000000000000000000000000000000000000' end - context 'when calculate_checksum Gitaly feature is disabled', :disable_gitaly do - it_behaves_like 'calculating checksum' + it 'raises Gitlab::Git::Repository::InvalidRepository error for non-valid git repo' do + FileUtils.rm_rf(File.join(storage_path, 'non-valid.git')) - describe 'when storage is broken', :broken_storage do - it 'raises a storage exception when storage is not available' do - broken_repo = described_class.new('broken', 'a/path.git', '') + system(git_env, *%W(#{Gitlab.config.git.bin_path} clone --bare #{TEST_REPO_PATH} non-valid.git), + chdir: SEED_STORAGE_PATH, + out: '/dev/null', + err: '/dev/null') - expect { broken_repo.rugged }.to raise_error(Gitlab::Git::Storage::Inaccessible) - end - end + File.truncate(File.join(storage_path, 'non-valid.git/HEAD'), 0) - it "raises a Gitlab::Git::Repository::Failure error if the `popen` call to git returns a non-zero exit code" do - allow(repository).to receive(:popen).and_return(['output', nil]) + non_valid = described_class.new('default', 'non-valid.git', '') - expect { repository.checksum }.to raise_error Gitlab::Git::Repository::ChecksumError - end + expect { non_valid.checksum }.to raise_error(Gitlab::Git::Repository::InvalidRepository) + end + + it 'raises Gitlab::Git::Repository::NoRepository error when there is no repo' do + broken_repo = described_class.new('default', 'a/path.git', '') + + expect { broken_repo.checksum }.to raise_error(Gitlab::Git::Repository::NoRepository) end end diff --git a/spec/lib/gitlab/git/tag_spec.rb b/spec/lib/gitlab/git/tag_spec.rb index 6c4f538bf01..be2f5bfb819 100644 --- a/spec/lib/gitlab/git/tag_spec.rb +++ b/spec/lib/gitlab/git/tag_spec.rb @@ -32,4 +32,56 @@ describe Gitlab::Git::Tag, seed_helper: true do context 'when Gitaly tags feature is disabled', :skip_gitaly_mock do it_behaves_like 'Gitlab::Git::Repository#tags' end + + describe '.get_message' do + let(:tag_ids) { %w[f4e6814c3e4e7a0de82a9e7cd20c626cc963a2f8 8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b] } + + subject do + tag_ids.map { |id| described_class.get_message(repository, id) } + end + + shared_examples 'getting tag messages' do + it 'gets tag messages' do + expect(subject[0]).to eq("Release\n") + expect(subject[1]).to eq("Version 1.1.0\n") + end + end + + context 'when Gitaly tag_messages feature is enabled' do + it_behaves_like 'getting tag messages' + + it 'gets messages in one batch', :request_store do + expect { subject.map(&:itself) }.to change { Gitlab::GitalyClient.get_request_count }.by(1) + end + end + + context 'when Gitaly tag_messages feature is disabled', :disable_gitaly do + it_behaves_like 'getting tag messages' + end + end + + describe 'tag into from Gitaly tag' do + context 'message_size != message.size' do + let(:gitaly_tag) { build(:gitaly_tag, message: ''.b, message_size: message_size) } + let(:tag) { described_class.new(repository, gitaly_tag) } + + context 'message_size less than threshold' do + let(:message_size) { 123 } + + it 'fetches tag message seperately' do + expect(described_class).to receive(:get_message).with(repository, gitaly_tag.id) + + tag.message + end + end + + context 'message_size greater than threshold' do + let(:message_size) { described_class::MAX_TAG_MESSAGE_DISPLAY_SIZE + 1 } + + it 'returns a notice about message size' do + expect(tag.message).to eq("--tag message is too big") + end + end + end + end end diff --git a/spec/lib/gitlab/incoming_email_spec.rb b/spec/lib/gitlab/incoming_email_spec.rb index ad087f42e06..4c0c3fcbcc7 100644 --- a/spec/lib/gitlab/incoming_email_spec.rb +++ b/spec/lib/gitlab/incoming_email_spec.rb @@ -83,6 +83,10 @@ describe Gitlab::IncomingEmail do it "returns reply key" do expect(described_class.key_from_address("replies+key@example.com")).to eq("key") end + + it 'does not match emails with extra bits' do + expect(described_class.key_from_address('somereplies+somekey@example.com.someotherdomain.com')).to be nil + end end context 'self.key_from_fallback_message_id' do diff --git a/spec/lib/gitlab/untrusted_regexp_spec.rb b/spec/lib/gitlab/untrusted_regexp_spec.rb index 0ee7fa1e570..0a6ac0aa294 100644 --- a/spec/lib/gitlab/untrusted_regexp_spec.rb +++ b/spec/lib/gitlab/untrusted_regexp_spec.rb @@ -1,6 +1,49 @@ -require 'spec_helper' +require 'fast_spec_helper' +require 'support/shared_examples/malicious_regexp_shared_examples' describe Gitlab::UntrustedRegexp do + describe '.valid?' do + it 'returns true if regexp is valid' do + expect(described_class.valid?('/some ( thing/')) + .to be false + end + + it 'returns true if regexp is invalid' do + expect(described_class.valid?('/some .* thing/')) + .to be true + end + end + + describe '.fabricate' do + context 'when regexp is using /regexp/ scheme with flags' do + it 'fabricates regexp with a single flag' do + regexp = described_class.fabricate('/something/i') + + expect(regexp).to eq described_class.new('(?i)something') + expect(regexp.scan('SOMETHING')).to be_one + end + + it 'fabricates regexp with multiple flags' do + regexp = described_class.fabricate('/something/im') + + expect(regexp).to eq described_class.new('(?im)something') + end + + it 'fabricates regexp without flags' do + regexp = described_class.fabricate('/something/') + + expect(regexp).to eq described_class.new('something') + end + end + + context 'when regexp is a raw pattern' do + it 'raises an error' do + expect { described_class.fabricate('some .* thing') } + .to raise_error(RegexpError) + end + end + end + describe '#initialize' do subject { described_class.new(pattern) } diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index e732b089d44..660671cefaf 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Workhorse do - let(:project) { create(:project, :repository) } + set(:project) { create(:project, :repository) } let(:repository) { project.repository } def decode_workhorse_header(array) @@ -55,16 +55,6 @@ describe Gitlab::Workhorse do end end - context 'when Gitaly workhorse_archive feature is disabled', :disable_gitaly do - it 'sets the header correctly' do - key, command, params = decode_workhorse_header(subject) - - expect(key).to eq('Gitlab-Workhorse-Send-Data') - expect(command).to eq('git-archive') - expect(params).to eq(base_params) - end - end - context "when the repository doesn't have an archive file path" do before do allow(project.repository).to receive(:archive_metadata).and_return(Hash.new) diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 84ddbbbf2ee..8a52c151cc4 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -594,7 +594,7 @@ describe Notify do it 'contains all the useful information' do is_expected.to have_subject "Invitation to join the #{project.full_name} project" is_expected.to have_html_escaped_body_text project.full_name - is_expected.to have_body_text project.web_url + is_expected.to have_body_text project.full_name is_expected.to have_body_text project_member.human_access is_expected.to have_body_text project_member.invite_token end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 490b1f0b54e..67919a0d6b4 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -629,6 +629,14 @@ describe Ci::Build do it { is_expected.to eq('review/host') } end + + context 'when using persisted variables' do + let(:build) do + create(:ci_build, environment: 'review/x$CI_BUILD_ID') + end + + it { is_expected.to eq('review/x') } + end end describe '#starts_environment?' do @@ -1270,6 +1278,46 @@ describe Ci::Build do end end + describe '#playable?' do + context 'when build is a manual action' do + context 'when build has been skipped' do + subject { build_stubbed(:ci_build, :manual, status: :skipped) } + + it { is_expected.not_to be_playable } + end + + context 'when build has been canceled' do + subject { build_stubbed(:ci_build, :manual, status: :canceled) } + + it { is_expected.to be_playable } + end + + context 'when build is successful' do + subject { build_stubbed(:ci_build, :manual, status: :success) } + + it { is_expected.to be_playable } + end + + context 'when build has failed' do + subject { build_stubbed(:ci_build, :manual, status: :failed) } + + it { is_expected.to be_playable } + end + + context 'when build is a manual untriggered action' do + subject { build_stubbed(:ci_build, :manual, status: :manual) } + + it { is_expected.to be_playable } + end + end + + context 'when build is not a manual action' do + subject { build_stubbed(:ci_build, :success) } + + it { is_expected.not_to be_playable } + end + end + describe 'project settings' do describe '#allow_git_fetch' do it 'return project allow_git_fetch configuration' do @@ -1485,6 +1533,7 @@ describe Ci::Build do let(:container_registry_enabled) { false } let(:predefined_variables) do [ + { key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true }, { key: 'CI_JOB_ID', value: build.id.to_s, public: true }, { key: 'CI_JOB_TOKEN', value: build.token, public: false }, { key: 'CI_BUILD_ID', value: build.id.to_s, public: true }, @@ -1516,7 +1565,6 @@ describe Ci::Build do { key: 'CI_PROJECT_NAMESPACE', value: project.namespace.full_path, public: true }, { key: 'CI_PROJECT_URL', value: project.web_url, public: true }, { key: 'CI_PROJECT_VISIBILITY', value: 'private', public: true }, - { key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true }, { key: 'CI_PIPELINE_IID', value: pipeline.iid.to_s, public: true }, { key: 'CI_CONFIG_PATH', value: pipeline.ci_yaml_file_path, public: true }, { key: 'CI_PIPELINE_SOURCE', value: pipeline.source, public: true }, @@ -2045,7 +2093,7 @@ describe Ci::Build do let(:deploy_token_variables) do [ - { key: 'CI_DEPLOY_USER', value: deploy_token.name, public: true }, + { key: 'CI_DEPLOY_USER', value: deploy_token.username, public: true }, { key: 'CI_DEPLOY_PASSWORD', value: deploy_token.token, public: false } ] end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index e0fe62b39e6..314cb3a28ed 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -178,13 +178,40 @@ describe Ci::Pipeline, :mailer do end end + describe '#persisted_variables' do + context 'when pipeline is not persisted yet' do + subject { build(:ci_pipeline).persisted_variables } + + it 'does not contain some variables' do + keys = subject.map { |variable| variable[:key] } + + expect(keys).not_to include 'CI_PIPELINE_ID' + end + end + + context 'when pipeline is persisted' do + subject { build_stubbed(:ci_pipeline).persisted_variables } + + it 'does contains persisted variables' do + keys = subject.map { |variable| variable[:key] } + + expect(keys).to eq %w[CI_PIPELINE_ID] + end + end + end + describe '#predefined_variables' do subject { pipeline.predefined_variables } it 'includes all predefined variables in a valid order' do keys = subject.map { |variable| variable[:key] } - expect(keys).to eq %w[CI_PIPELINE_ID CI_PIPELINE_IID CI_CONFIG_PATH CI_PIPELINE_SOURCE CI_COMMIT_MESSAGE CI_COMMIT_TITLE CI_COMMIT_DESCRIPTION] + expect(keys).to eq %w[CI_PIPELINE_IID + CI_CONFIG_PATH + CI_PIPELINE_SOURCE + CI_COMMIT_MESSAGE + CI_COMMIT_TITLE + CI_COMMIT_DESCRIPTION] end end @@ -785,6 +812,33 @@ describe Ci::Pipeline, :mailer do end end + describe '#number_of_warnings' do + it 'returns the number of warnings' do + create(:ci_build, :allowed_to_fail, :failed, pipeline: pipeline, name: 'rubocop') + + expect(pipeline.number_of_warnings).to eq(1) + end + + it 'supports eager loading of the number of warnings' do + pipeline2 = create(:ci_empty_pipeline, status: :created, project: project) + + create(:ci_build, :allowed_to_fail, :failed, pipeline: pipeline, name: 'rubocop') + create(:ci_build, :allowed_to_fail, :failed, pipeline: pipeline2, name: 'rubocop') + + pipelines = project.pipelines.to_a + + pipelines.each(&:number_of_warnings) + + # To run the queries we need to actually use the lazy objects, which we do + # by just sending "to_i" to them. + amount = ActiveRecord::QueryRecorder + .new { pipelines.each { |p| p.number_of_warnings.to_i } } + .count + + expect(amount).to eq(1) + end + end + shared_context 'with some outdated pipelines' do before do create_pipeline(:canceled, 'ref', 'A', project) diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb index aeca6ee903a..407e2fc598a 100644 --- a/spec/models/clusters/applications/prometheus_spec.rb +++ b/spec/models/clusters/applications/prometheus_spec.rb @@ -85,6 +85,16 @@ describe Clusters::Applications::Prometheus do it 'copies options and headers from kube client to proxy client' do expect(subject.prometheus_client.options).to eq(kube_client.rest_client.options.merge(headers: kube_client.headers)) end + + context 'when cluster is not reachable' do + before do + allow(kube_client).to receive(:proxy_url).and_raise(Kubeclient::HttpError.new(401, 'Unauthorized', nil)) + end + + it 'returns nil' do + expect(subject.prometheus_client).to be_nil + end + end end end diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index 4e6b037a720..090f91168ad 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -52,22 +52,98 @@ describe Commit do end end - describe '#author' do + describe '#author', :request_store do it 'looks up the author in a case-insensitive way' do user = create(:user, email: commit.author_email.upcase) expect(commit.author).to eq(user) end - it 'caches the author', :request_store do + it 'caches the author' do user = create(:user, email: commit.author_email) - expect(User).to receive(:find_by_any_email).and_call_original expect(commit.author).to eq(user) + key = "Commit:author:#{commit.author_email.downcase}" - expect(RequestStore.store[key]).to eq(user) + expect(RequestStore.store[key]).to eq(user) expect(commit.author).to eq(user) end + + context 'using eager loading' do + let!(:alice) { create(:user, email: 'alice@example.com') } + let!(:bob) { create(:user, email: 'hunter2@example.com') } + + let(:alice_commit) do + described_class.new(RepoHelpers.sample_commit, project).tap do |c| + c.author_email = 'alice@example.com' + end + end + + let(:bob_commit) do + # The commit for Bob uses one of his alternative Emails, instead of the + # primary one. + described_class.new(RepoHelpers.sample_commit, project).tap do |c| + c.author_email = 'bob@example.com' + end + end + + let(:eve_commit) do + described_class.new(RepoHelpers.sample_commit, project).tap do |c| + c.author_email = 'eve@example.com' + end + end + + let!(:commits) { [alice_commit, bob_commit, eve_commit] } + + before do + create(:email, user: bob, email: 'bob@example.com') + end + + it 'executes only two SQL queries' do + recorder = ActiveRecord::QueryRecorder.new do + # Running this first ensures we don't run one query for every + # commit. + commits.each(&:lazy_author) + + # This forces the execution of the SQL queries necessary to load the + # data. + commits.each { |c| c.author.try(:id) } + end + + expect(recorder.count).to eq(2) + end + + it "preloads the authors for Commits matching a user's primary Email" do + commits.each(&:lazy_author) + + expect(alice_commit.author).to eq(alice) + end + + it "preloads the authors for Commits using a User's alternative Email" do + commits.each(&:lazy_author) + + expect(bob_commit.author).to eq(bob) + end + + it 'sets the author to Nil if an author could not be found for a Commit' do + commits.each(&:lazy_author) + + expect(eve_commit.author).to be_nil + end + + it 'does not execute SQL queries once the authors are preloaded' do + commits.each(&:lazy_author) + commits.each { |c| c.author.try(:id) } + + recorder = ActiveRecord::QueryRecorder.new do + alice_commit.author + bob_commit.author + eve_commit.author + end + + expect(recorder.count).to be_zero + end + end end describe '#to_reference' do @@ -182,7 +258,6 @@ eos it { is_expected.to respond_to(:date) } it { is_expected.to respond_to(:diffs) } it { is_expected.to respond_to(:id) } - it { is_expected.to respond_to(:to_patch) } end describe '#closes_issues' do diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index 2ed29052dc1..f3f2bc28d2c 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -565,4 +565,10 @@ describe CommitStatus do it_behaves_like 'commit status enqueued' end end + + describe '#present' do + subject { commit_status.present } + + it { is_expected.to be_a(CommitStatusPresenter) } + end end diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index 3d3092b8ac9..bd6bf5b0712 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -266,6 +266,19 @@ describe Issuable do end end + describe '#time_estimate=' do + it 'coerces the value below Gitlab::Database::MAX_INT_VALUE' do + expect { issue.time_estimate = 100 }.to change { issue.time_estimate }.to(100) + expect { issue.time_estimate = Gitlab::Database::MAX_INT_VALUE + 100 }.to change { issue.time_estimate }.to(Gitlab::Database::MAX_INT_VALUE) + end + + it 'skips coercion for not Integer values' do + expect { issue.time_estimate = nil }.to change { issue.time_estimate }.to(nil) + expect { issue.time_estimate = 'invalid time' }.not_to raise_error(StandardError) + expect { issue.time_estimate = 22.33 }.not_to raise_error(StandardError) + end + end + describe '#to_hook_data' do let(:builder) { double } diff --git a/spec/models/concerns/redis_cacheable_spec.rb b/spec/models/concerns/redis_cacheable_spec.rb index 3d7963120b6..23c6c6233e9 100644 --- a/spec/models/concerns/redis_cacheable_spec.rb +++ b/spec/models/concerns/redis_cacheable_spec.rb @@ -1,21 +1,36 @@ require 'spec_helper' describe RedisCacheable do - let(:model) { double } + let(:model) do + Struct.new(:id, :attributes) do + def read_attribute(attribute) + attributes[attribute] + end + + def cast_value_from_cache(attribute, cached_value) + cached_value + end + + def has_attribute?(attribute) + attributes.has_key?(attribute) + end + end + end + + let(:payload) { { name: 'value', time: Time.zone.now } } + let(:instance) { model.new(1, payload) } + let(:cache_key) { instance.__send__(:cache_attribute_key) } before do - model.extend(described_class) - allow(model).to receive(:cache_attribute_key).and_return('key') + model.include(described_class) end describe '#cached_attribute' do - let(:payload) { { attribute: 'value' } } - - subject { model.cached_attribute(payload.keys.first) } + subject { instance.cached_attribute(payload.keys.first) } it 'gets the cache attribute' do Gitlab::Redis::SharedState.with do |redis| - expect(redis).to receive(:get).with('key') + expect(redis).to receive(:get).with(cache_key) .and_return(payload.to_json) end @@ -24,16 +39,62 @@ describe RedisCacheable do end describe '#cache_attributes' do - let(:values) { { name: 'new_name' } } - - subject { model.cache_attributes(values) } + subject { instance.cache_attributes(payload) } it 'sets the cache attributes' do Gitlab::Redis::SharedState.with do |redis| - expect(redis).to receive(:set).with('key', values.to_json, anything) + expect(redis).to receive(:set).with(cache_key, payload.to_json, anything) end subject end end + + describe '#cached_attr_reader', :clean_gitlab_redis_shared_state do + subject { instance.name } + + before do + model.cached_attr_reader(:name) + end + + context 'when there is no cached value' do + it 'reads the attribute' do + expect(instance).to receive(:read_attribute).and_call_original + + expect(subject).to eq(payload[:name]) + end + end + + context 'when there is a cached value' do + it 'reads the cached value' do + expect(instance).not_to receive(:read_attribute) + + instance.cache_attributes(payload) + + expect(subject).to eq(payload[:name]) + end + end + + it 'always returns the latest values' do + expect(instance.name).to eq(payload[:name]) + + instance.cache_attributes(name: 'new_value') + + expect(instance.name).to eq('new_value') + end + end + + describe '#cast_value_from_cache' do + subject { instance.__send__(:cast_value_from_cache, attribute, value) } + + context 'with runner contacted_at' do + let(:instance) { Ci::Runner.new } + let(:attribute) { :contacted_at } + let(:value) { '2018-05-07 13:53:08 UTC' } + + it 'converts cache string to appropriate type' do + expect(subject).to be_an_instance_of(ActiveSupport::TimeWithZone) + end + end + end end diff --git a/spec/models/concerns/sortable_spec.rb b/spec/models/concerns/sortable_spec.rb new file mode 100644 index 00000000000..b821a84d5e0 --- /dev/null +++ b/spec/models/concerns/sortable_spec.rb @@ -0,0 +1,108 @@ +require 'spec_helper' + +describe Sortable do + describe '.order_by' do + let(:relation) { Group.all } + + describe 'ordering by id' do + it 'ascending' do + expect(relation).to receive(:reorder).with(id: :asc) + + relation.order_by('id_asc') + end + + it 'descending' do + expect(relation).to receive(:reorder).with(id: :desc) + + relation.order_by('id_desc') + end + end + + describe 'ordering by created day' do + it 'ascending' do + expect(relation).to receive(:reorder).with(created_at: :asc) + + relation.order_by('created_asc') + end + + it 'descending' do + expect(relation).to receive(:reorder).with(created_at: :desc) + + relation.order_by('created_desc') + end + + it 'order by "date"' do + expect(relation).to receive(:reorder).with(created_at: :desc) + + relation.order_by('created_date') + end + end + + describe 'ordering by name' do + it 'ascending' do + expect(relation).to receive(:reorder).with("lower(name) asc") + + relation.order_by('name_asc') + end + + it 'descending' do + expect(relation).to receive(:reorder).with("lower(name) desc") + + relation.order_by('name_desc') + end + end + + describe 'ordering by Updated Time' do + it 'ascending' do + expect(relation).to receive(:reorder).with(updated_at: :asc) + + relation.order_by('updated_asc') + end + + it 'descending' do + expect(relation).to receive(:reorder).with(updated_at: :desc) + + relation.order_by('updated_desc') + end + end + + it 'does not call reorder in case of unrecognized ordering' do + expect(relation).not_to receive(:reorder) + + relation.order_by('random_ordering') + end + end + + describe 'sorting groups' do + def ordered_group_names(order) + Group.all.order_by(order).map(&:name) + end + + let!(:ref_time) { Time.parse('2018-05-01 00:00:00') } + let!(:group1) { create(:group, name: 'aa', id: 1, created_at: ref_time - 15.seconds, updated_at: ref_time) } + let!(:group2) { create(:group, name: 'AAA', id: 2, created_at: ref_time - 10.seconds, updated_at: ref_time - 5.seconds) } + let!(:group3) { create(:group, name: 'BB', id: 3, created_at: ref_time - 5.seconds, updated_at: ref_time - 10.seconds) } + let!(:group4) { create(:group, name: 'bbb', id: 4, created_at: ref_time, updated_at: ref_time - 15.seconds) } + + it 'sorts groups by id' do + expect(ordered_group_names('id_asc')).to eq(%w(aa AAA BB bbb)) + expect(ordered_group_names('id_desc')).to eq(%w(bbb BB AAA aa)) + end + + it 'sorts groups by name via case-insentitive comparision' do + expect(ordered_group_names('name_asc')).to eq(%w(aa AAA BB bbb)) + expect(ordered_group_names('name_desc')).to eq(%w(bbb BB AAA aa)) + end + + it 'sorts groups by created_at' do + expect(ordered_group_names('created_asc')).to eq(%w(aa AAA BB bbb)) + expect(ordered_group_names('created_desc')).to eq(%w(bbb BB AAA aa)) + expect(ordered_group_names('created_date')).to eq(%w(bbb BB AAA aa)) + end + + it 'sorts groups by updated_at' do + expect(ordered_group_names('updated_asc')).to eq(%w(bbb BB AAA aa)) + expect(ordered_group_names('updated_desc')).to eq(%w(aa AAA BB bbb)) + end + end +end diff --git a/spec/models/generic_commit_status_spec.rb b/spec/models/generic_commit_status_spec.rb index 673049d1cc4..a3e68d2e646 100644 --- a/spec/models/generic_commit_status_spec.rb +++ b/spec/models/generic_commit_status_spec.rb @@ -78,4 +78,10 @@ describe GenericCommitStatus do it { is_expected.not_to be_nil } end end + + describe '#present' do + subject { generic_commit_status.present } + + it { is_expected.to be_a(GenericCommitStatusPresenter) } + end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index ac8d9a32d4e..6bc148a1392 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -671,7 +671,7 @@ describe Repository do end end - describe "search_files_by_content" do + shared_examples "search_files_by_content" do let(:results) { repository.search_files_by_content('feature', 'master') } subject { results } @@ -718,7 +718,7 @@ describe Repository do end end - describe "search_files_by_name" do + shared_examples "search_files_by_name" do let(:results) { repository.search_files_by_name('files', 'master') } it 'returns result' do @@ -758,6 +758,16 @@ describe Repository do end end + describe 'with gitaly enabled' do + it_behaves_like 'search_files_by_content' + it_behaves_like 'search_files_by_name' + end + + describe 'with gitaly disabled', :disable_gitaly do + it_behaves_like 'search_files_by_content' + it_behaves_like 'search_files_by_name' + end + describe '#async_remove_remote' do before do masterrev = repository.find_branch('master').dereferenced_target diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 8d3ddd1f87d..684fa030baf 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -1223,6 +1223,24 @@ describe User do end end + describe '#accept_pending_invitations!' do + let(:user) { create(:user, email: 'user@email.com') } + let!(:project_member_invite) { create(:project_member, :invited, invite_email: user.email) } + let!(:group_member_invite) { create(:group_member, :invited, invite_email: user.email) } + let!(:external_project_member_invite) { create(:project_member, :invited, invite_email: 'external@email.com') } + let!(:external_group_member_invite) { create(:group_member, :invited, invite_email: 'external@email.com') } + + it 'accepts all the user members pending invitations and returns the accepted_members' do + accepted_members = user.accept_pending_invitations! + + expect(accepted_members).to match_array([project_member_invite, group_member_invite]) + expect(group_member_invite.reload).not_to be_invite + expect(project_member_invite.reload).not_to be_invite + expect(external_project_member_invite.reload).to be_invite + expect(external_group_member_invite.reload).to be_invite + end + end + describe '#all_emails' do let(:user) { create(:user) } diff --git a/spec/presenters/ci/build_presenter_spec.rb b/spec/presenters/ci/build_presenter_spec.rb index 4bc005df2fc..efd175247b5 100644 --- a/spec/presenters/ci/build_presenter_spec.rb +++ b/spec/presenters/ci/build_presenter_spec.rb @@ -10,7 +10,7 @@ describe Ci::BuildPresenter do end it 'inherits from Gitlab::View::Presenter::Delegated' do - expect(described_class.superclass).to eq(Gitlab::View::Presenter::Delegated) + expect(described_class.ancestors).to include(Gitlab::View::Presenter::Delegated) end describe '#initialize' do diff --git a/spec/presenters/commit_status_presenter_spec.rb b/spec/presenters/commit_status_presenter_spec.rb new file mode 100644 index 00000000000..f81ee44e371 --- /dev/null +++ b/spec/presenters/commit_status_presenter_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe CommitStatusPresenter do + let(:project) { create(:project) } + let(:pipeline) { create(:ci_pipeline, project: project) } + let(:build) { create(:ci_build, pipeline: pipeline) } + + subject(:presenter) do + described_class.new(build) + end + + it 'inherits from Gitlab::View::Presenter::Delegated' do + expect(described_class.superclass).to eq(Gitlab::View::Presenter::Delegated) + end +end diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index bb0034e3237..7d923932309 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -738,13 +738,16 @@ describe API::Groups do describe "DELETE /groups/:id" do context "when authenticated as user" do it "removes group" do - delete api("/groups/#{group1.id}", user1) + Sidekiq::Testing.fake! do + expect { delete api("/groups/#{group1.id}", user1) }.to change(GroupDestroyWorker.jobs, :size).by(1) + end - expect(response).to have_gitlab_http_status(204) + expect(response).to have_gitlab_http_status(202) end it_behaves_like '412 response' do let(:request) { api("/groups/#{group1.id}", user1) } + let(:success_status) { 202 } end it "does not remove a group if not an owner" do @@ -773,7 +776,7 @@ describe API::Groups do it "removes any existing group" do delete api("/groups/#{group2.id}", admin) - expect(response).to have_gitlab_http_status(204) + expect(response).to have_gitlab_http_status(202) end it "does not remove a non existing group" do diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 60e174ff92a..3106083293f 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -126,6 +126,15 @@ describe API::Issues do it 'returns issues assigned to me' do issue2 = create(:issue, assignees: [user2], project: project) + get api('/issues', user2), scope: 'assigned_to_me' + + expect_paginated_array_response(size: 1) + expect(first_issue['id']).to eq(issue2.id) + end + + it 'returns issues assigned to me (kebab-case)' do + issue2 = create(:issue, assignees: [user2], project: project) + get api('/issues', user2), scope: 'assigned-to-me' expect_paginated_array_response(size: 1) diff --git a/spec/requests/api/markdown_spec.rb b/spec/requests/api/markdown_spec.rb new file mode 100644 index 00000000000..a55796cf343 --- /dev/null +++ b/spec/requests/api/markdown_spec.rb @@ -0,0 +1,112 @@ +require "spec_helper" + +describe API::Markdown do + RSpec::Matchers.define_negated_matcher :exclude, :include + + describe "POST /markdown" do + let(:user) {} # No-op. It gets overwritten in the contexts below. + + before do + post api("/markdown", user), params + end + + shared_examples "rendered markdown text without GFM" do + it "renders markdown text" do + expect(response).to have_http_status(201) + expect(response.headers["Content-Type"]).to eq("application/json") + expect(json_response).to be_a(Hash) + expect(json_response["html"]).to eq("

#{text}

") + end + end + + shared_examples "404 Project Not Found" do + it "responses with 404 Not Found" do + expect(response).to have_http_status(404) + expect(response.headers["Content-Type"]).to eq("application/json") + expect(json_response).to be_a(Hash) + expect(json_response["message"]).to eq("404 Project Not Found") + end + end + + context "when arguments are invalid" do + context "when text is missing" do + let(:params) { {} } + + it "responses with 400 Bad Request" do + expect(response).to have_http_status(400) + expect(response.headers["Content-Type"]).to eq("application/json") + expect(json_response).to be_a(Hash) + expect(json_response["error"]).to eq("text is missing") + end + end + + context "when project is not found" do + let(:params) { { text: "Hello world!", gfm: true, project: "Dummy project" } } + + it_behaves_like "404 Project Not Found" + end + end + + context "when arguments are valid" do + set(:project) { create(:project) } + set(:issue) { create(:issue, project: project) } + let(:text) { ":tada: Hello world! :100: #{issue.to_reference}" } + + context "when not using gfm" do + context "without project" do + let(:params) { { text: text } } + + it_behaves_like "rendered markdown text without GFM" + end + + context "with project" do + let(:params) { { text: text, project: project.full_path } } + + context "when not authorized" do + it_behaves_like "404 Project Not Found" + end + + context "when authorized" do + let(:user) { project.owner } + + it_behaves_like "rendered markdown text without GFM" + end + end + end + + context "when using gfm" do + context "without project" do + let(:params) { { text: text, gfm: true } } + + it "renders markdown text" do + expect(response).to have_http_status(201) + expect(response.headers["Content-Type"]).to eq("application/json") + expect(json_response).to be_a(Hash) + expect(json_response["html"]).to include("Hello world!") + .and include('data-name="tada"') + .and include('data-name="100"') + .and include("#1") + .and exclude("
") + end + end + + context "with project" do + let(:params) { { text: text, gfm: true, project: project.full_path } } + let(:user) { project.owner } + + it "renders markdown text" do + expect(response).to have_http_status(201) + expect(response.headers["Content-Type"]).to eq("application/json") + expect(json_response).to be_a(Hash) + expect(json_response["html"]).to include("Hello world!") + .and include('data-name="tada"') + .and include('data-name="100"') + .and include("") + end + end + end + end + end +end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index f64623d7018..1eeeb4f1045 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -34,8 +34,7 @@ describe API::MergeRequests do it 'returns an array of all merge requests' do get api('/merge_requests', user), scope: 'all' - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array + expect_paginated_array_response end it "returns authentication error without any scope" do @@ -50,11 +49,23 @@ describe API::MergeRequests do expect(response).to have_gitlab_http_status(401) end + it "returns authentication error when scope is assigned_to_me" do + get api("/merge_requests"), scope: 'assigned_to_me' + + expect(response).to have_gitlab_http_status(401) + end + it "returns authentication error when scope is created-by-me" do get api("/merge_requests"), scope: 'created-by-me' expect(response).to have_gitlab_http_status(401) end + + it "returns authentication error when scope is created_by_me" do + get api("/merge_requests"), scope: 'created_by_me' + + expect(response).to have_gitlab_http_status(401) + end end context 'when authenticated' do @@ -62,27 +73,14 @@ describe API::MergeRequests do let!(:merge_request2) { create(:merge_request, :simple, author: user, assignee: user, source_project: project2, target_project: project2) } let(:user2) { create(:user) } - it 'returns an array of all merge requests' do - get api('/merge_requests', user), scope: :all - - 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 { |mr| mr['id'] }) - .to contain_exactly(merge_request.id, merge_request_closed.id, merge_request_merged.id, merge_request2.id) - end - - it 'does not return unauthorized merge requests' do + it 'returns an array of all merge requests except unauthorized ones' do private_project = create(:project, :private) merge_request3 = create(:merge_request, :simple, source_project: private_project, target_project: private_project, source_branch: 'other-branch') get api('/merge_requests', user), scope: :all - 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 { |mr| mr['id'] }) - .not_to include(merge_request3.id) + expect_response_contain_exactly(merge_request2, merge_request_merged, merge_request_closed, merge_request) + expect(json_response.map { |mr| mr['id'] }).not_to include(merge_request3.id) end it 'returns an array of merge requests created by current user if no scope is given' do @@ -90,10 +88,7 @@ describe API::MergeRequests do get api('/merge_requests', user2) - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['id']).to eq(merge_request3.id) + expect_response_ordered_exactly(merge_request3) end it 'returns an array of merge requests authored by the given user' do @@ -101,10 +96,7 @@ describe API::MergeRequests do get api('/merge_requests', user), author_id: user2.id, scope: :all - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['id']).to eq(merge_request3.id) + expect_response_ordered_exactly(merge_request3) end it 'returns an array of merge requests assigned to the given user' do @@ -112,32 +104,39 @@ describe API::MergeRequests do get api('/merge_requests', user), assignee_id: user2.id, scope: :all - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['id']).to eq(merge_request3.id) + expect_response_ordered_exactly(merge_request3) end it 'returns an array of merge requests assigned to me' do merge_request3 = create(:merge_request, :simple, author: user, assignee: user2, source_project: project2, target_project: project2, source_branch: 'other-branch') + get api('/merge_requests', user2), scope: 'assigned_to_me' + + expect_response_ordered_exactly(merge_request3) + end + + it 'returns an array of merge requests assigned to me (kebab-case)' do + merge_request3 = create(:merge_request, :simple, author: user, assignee: user2, source_project: project2, target_project: project2, source_branch: 'other-branch') + get api('/merge_requests', user2), scope: 'assigned-to-me' - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['id']).to eq(merge_request3.id) + expect_response_ordered_exactly(merge_request3) end it 'returns an array of merge requests created by me' do merge_request3 = create(:merge_request, :simple, author: user2, assignee: user, source_project: project2, target_project: project2, source_branch: 'other-branch') + get api('/merge_requests', user2), scope: 'created_by_me' + + expect_response_ordered_exactly(merge_request3) + end + + it 'returns an array of merge requests created by me (kebab-case)' do + merge_request3 = create(:merge_request, :simple, author: user2, assignee: user, source_project: project2, target_project: project2, source_branch: 'other-branch') + get api('/merge_requests', user2), scope: 'created-by-me' - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['id']).to eq(merge_request3.id) + expect_response_ordered_exactly(merge_request3) end it 'returns merge requests reacted by the authenticated user by the given emoji' do @@ -146,19 +145,14 @@ describe API::MergeRequests do get api('/merge_requests', user2), my_reaction_emoji: award_emoji.name, scope: 'all' - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['id']).to eq(merge_request3.id) + expect_response_ordered_exactly(merge_request3) end context 'source_branch param' do it 'returns merge requests with the given source branch' do get api('/merge_requests', user), source_branch: merge_request_closed.source_branch, state: 'all' - expect(json_response.length).to eq(2) - expect(json_response.map { |mr| mr['id'] }) - .to contain_exactly(merge_request_closed.id, merge_request_merged.id) + expect_response_contain_exactly(merge_request_closed, merge_request_merged) end end @@ -166,9 +160,7 @@ describe API::MergeRequests do it 'returns merge requests with the given target branch' do get api('/merge_requests', user), target_branch: merge_request_closed.target_branch, state: 'all' - expect(json_response.length).to eq(2) - expect(json_response.map { |mr| mr['id'] }) - .to contain_exactly(merge_request_closed.id, merge_request_merged.id) + expect_response_contain_exactly(merge_request_closed, merge_request_merged) end end @@ -177,8 +169,7 @@ describe API::MergeRequests do get api('/merge_requests?created_before=2000-01-02T00:00:00.060Z', user) - expect(json_response.size).to eq(1) - expect(json_response.first['id']).to eq(merge_request2.id) + expect_response_ordered_exactly(merge_request2) end it 'returns merge requests created after a specific date' do @@ -186,8 +177,7 @@ describe API::MergeRequests do get api("/merge_requests?created_after=#{merge_request2.created_at}", user) - expect(json_response.size).to eq(1) - expect(json_response.first['id']).to eq(merge_request2.id) + expect_response_ordered_exactly(merge_request2) end it 'returns merge requests updated before a specific date' do @@ -195,8 +185,7 @@ describe API::MergeRequests do get api('/merge_requests?updated_before=2000-01-02T00:00:00.060Z', user) - expect(json_response.size).to eq(1) - expect(json_response.first['id']).to eq(merge_request2.id) + expect_response_ordered_exactly(merge_request2) end it 'returns merge requests updated after a specific date' do @@ -204,8 +193,7 @@ describe API::MergeRequests do get api("/merge_requests?updated_after=#{merge_request2.updated_at}", user) - expect(json_response.size).to eq(1) - expect(json_response.first['id']).to eq(merge_request2.id) + expect_response_ordered_exactly(merge_request2) end context 'search params' do @@ -216,15 +204,13 @@ describe API::MergeRequests do it 'returns merge requests matching given search string for title' do get api("/merge_requests", user), search: merge_request.title - expect(json_response.length).to eq(1) - expect(json_response.first['id']).to eq(merge_request.id) + expect_response_ordered_exactly(merge_request) end it 'returns merge requests for project matching given search string for description' do get api("/merge_requests", user), project_id: project.id, search: merge_request.description - expect(json_response.length).to eq(1) - expect(json_response.first['id']).to eq(merge_request.id) + expect_response_ordered_exactly(merge_request) end end end @@ -235,8 +221,7 @@ describe API::MergeRequests do it 'returns merge requests for public projects' do get api("/projects/#{project.id}/merge_requests") - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array + expect_paginated_array_response end it "returns 404 for non public projects" do @@ -265,10 +250,7 @@ describe API::MergeRequests do it "returns an array of all merge_requests" do get api("/projects/#{project.id}/merge_requests", user) - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(3) + expect_response_ordered_exactly(merge_request_merged, merge_request_closed, merge_request) expect(json_response.last['title']).to eq(merge_request.title) expect(json_response.last).to have_key('web_url') expect(json_response.last['sha']).to eq(merge_request.diff_head_sha) @@ -286,11 +268,8 @@ describe API::MergeRequests do it "returns an array of all merge_requests using simple mode" do get api("/projects/#{project.id}/merge_requests?view=simple", user) - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers + expect_response_ordered_exactly(merge_request_merged, merge_request_closed, merge_request) expect(json_response.last.keys).to match_array(%w(id iid title web_url created_at description project_id state updated_at)) - expect(json_response).to be_an Array - expect(json_response.length).to eq(3) expect(json_response.last['iid']).to eq(merge_request.iid) expect(json_response.last['title']).to eq(merge_request.title) expect(json_response.last).to have_key('web_url') @@ -302,51 +281,36 @@ describe API::MergeRequests do it "returns an array of all merge_requests" do get api("/projects/#{project.id}/merge_requests?state", user) - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(3) + expect_response_ordered_exactly(merge_request_merged, merge_request_closed, merge_request) expect(json_response.last['title']).to eq(merge_request.title) end it "returns an array of open merge_requests" do get api("/projects/#{project.id}/merge_requests?state=opened", user) - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) + expect_response_ordered_exactly(merge_request) expect(json_response.last['title']).to eq(merge_request.title) end it "returns an array of closed merge_requests" do get api("/projects/#{project.id}/merge_requests?state=closed", user) - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) + expect_response_ordered_exactly(merge_request_closed) expect(json_response.first['title']).to eq(merge_request_closed.title) end it "returns an array of merged merge_requests" do get api("/projects/#{project.id}/merge_requests?state=merged", user) - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) + expect_response_ordered_exactly(merge_request_merged) expect(json_response.first['title']).to eq(merge_request_merged.title) end it 'returns merge_request by "iids" array' do get api("/projects/#{project.id}/merge_requests", user), iids: [merge_request.iid, merge_request_closed.iid] - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(2) + expect_response_ordered_exactly(merge_request_closed, merge_request) expect(json_response.first['title']).to eq merge_request_closed.title - expect(json_response.first['id']).to eq merge_request_closed.id end it 'matches V4 response schema' do @@ -359,16 +323,14 @@ describe API::MergeRequests do it 'returns an empty array if no issue matches milestone' do get api("/projects/#{project.id}/merge_requests", user), milestone: '1.0.0' - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array + expect_paginated_array_response expect(json_response.length).to eq(0) end it 'returns an empty array if milestone does not exist' do get api("/projects/#{project.id}/merge_requests", user), milestone: 'foo' - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array + expect_paginated_array_response expect(json_response.length).to eq(0) end @@ -382,17 +344,13 @@ describe API::MergeRequests do it 'returns an array of merge requests matching state in milestone' do get api("/projects/#{project.id}/merge_requests", user), milestone: '0.9', state: 'closed' - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['id']).to eq(merge_request_closed.id) + expect_response_ordered_exactly(merge_request_closed) end it 'returns an array of labeled merge requests' do get api("/projects/#{project.id}/merge_requests?labels=#{label.title}", user) - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array + expect_paginated_array_response expect(json_response.length).to eq(1) expect(json_response.first['labels']).to eq([label2.title, label.title]) end @@ -400,16 +358,14 @@ describe API::MergeRequests do it 'returns an array of labeled merge requests where all labels match' do get api("/projects/#{project.id}/merge_requests?labels=#{label.title},foo,bar", user) - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array + expect_paginated_array_response expect(json_response.length).to eq(0) end it 'returns an empty array if no merge request matches labels' do get api("/projects/#{project.id}/merge_requests?labels=foo,bar", user) - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array + expect_paginated_array_response expect(json_response.length).to eq(0) end @@ -427,13 +383,12 @@ describe API::MergeRequests do get api("/projects/#{project.id}/merge_requests?labels=#{bug_label.title}&milestone=#{milestone1.title}&state=merged", user) - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['id']).to eq(mr2.id) + expect_response_ordered_exactly(mr2) end context "with ordering" do + let(:merge_requests) { [merge_request_merged, merge_request_closed, merge_request] } + before do @mr_later = mr_with_later_created_and_updated_at_time @mr_earlier = mr_with_earlier_created_and_updated_at_time @@ -442,45 +397,25 @@ describe API::MergeRequests do it "returns an array of merge_requests in ascending order" do get api("/projects/#{project.id}/merge_requests?sort=asc", user) - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(3) - response_dates = json_response.map { |merge_request| merge_request['created_at'] } - expect(response_dates).to eq(response_dates.sort) + expect_response_ordered_exactly(*merge_requests.sort_by { |mr| mr['created_at'] }) end it "returns an array of merge_requests in descending order" do get api("/projects/#{project.id}/merge_requests?sort=desc", user) - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(3) - response_dates = json_response.map { |merge_request| merge_request['created_at'] } - expect(response_dates).to eq(response_dates.sort.reverse) + expect_response_ordered_exactly(*merge_requests.sort_by { |mr| mr['created_at'] }.reverse) end it "returns an array of merge_requests ordered by updated_at" do get api("/projects/#{project.id}/merge_requests?order_by=updated_at", user) - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(3) - response_dates = json_response.map { |merge_request| merge_request['updated_at'] } - expect(response_dates).to eq(response_dates.sort.reverse) + expect_response_ordered_exactly(*merge_requests.sort_by { |mr| mr['updated_at'] }.reverse) end it "returns an array of merge_requests ordered by created_at" do get api("/projects/#{project.id}/merge_requests?order_by=created_at&sort=asc", user) - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(3) - response_dates = json_response.map { |merge_request| merge_request['created_at'] } - expect(response_dates).to eq(response_dates.sort) + expect_response_ordered_exactly(*merge_requests.sort_by { |mr| mr['created_at'] }) end end @@ -488,9 +423,7 @@ describe API::MergeRequests do it 'returns merge requests with the given source branch' do get api('/merge_requests', user), source_branch: merge_request_closed.source_branch, state: 'all' - expect(json_response.length).to eq(2) - expect(json_response.map { |mr| mr['id'] }) - .to contain_exactly(merge_request_closed.id, merge_request_merged.id) + expect_response_contain_exactly(merge_request_closed, merge_request_merged) end end @@ -498,9 +431,7 @@ describe API::MergeRequests do it 'returns merge requests with the given target branch' do get api('/merge_requests', user), target_branch: merge_request_closed.target_branch, state: 'all' - expect(json_response.length).to eq(2) - expect(json_response.map { |mr| mr['id'] }) - .to contain_exactly(merge_request_closed.id, merge_request_merged.id) + expect_response_contain_exactly(merge_request_closed, merge_request_merged) end end end @@ -1341,4 +1272,22 @@ describe API::MergeRequests do merge_request_closed.save merge_request_closed end + + def expect_response_contain_exactly(*items) + expect_paginated_array_response + expect(json_response.length).to eq(items.size) + expect(json_response.map { |element| element['id'] }).to contain_exactly(*items.map(&:id)) + end + + def expect_response_ordered_exactly(*items) + expect_paginated_array_response + expect(json_response.length).to eq(items.size) + expect(json_response.map { |element| element['id'] }).to eq(items.map(&:id)) + end + + def expect_paginated_array_response + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + end end diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index da392c5ab81..efb9bddde44 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -918,6 +918,22 @@ describe API::Runner, :clean_gitlab_redis_shared_state do expect(job.reload.trace.raw).to eq 'BUILD TRACE appended appended' end + context 'when job is cancelled' do + before do + job.cancel + end + + context 'when trace is patched' do + before do + patch_the_trace + end + + it 'returns Forbidden ' do + expect(response.status).to eq(403) + end + end + end + context 'when redis data are flushed' do before do redis_shared_state_cleanup! diff --git a/spec/requests/api/v3/groups_spec.rb b/spec/requests/api/v3/groups_spec.rb index a1cdf583de3..34d4b8e9565 100644 --- a/spec/requests/api/v3/groups_spec.rb +++ b/spec/requests/api/v3/groups_spec.rb @@ -458,9 +458,11 @@ describe API::V3::Groups do describe "DELETE /groups/:id" do context "when authenticated as user" do it "removes group" do - delete v3_api("/groups/#{group1.id}", user1) + Sidekiq::Testing.fake! do + expect { delete v3_api("/groups/#{group1.id}", user1) }.to change(GroupDestroyWorker.jobs, :size).by(1) + end - expect(response).to have_gitlab_http_status(200) + expect(response).to have_gitlab_http_status(202) end it "does not remove a group if not an owner" do @@ -489,7 +491,7 @@ describe API::V3::Groups do it "removes any existing group" do delete v3_api("/groups/#{group2.id}", admin) - expect(response).to have_gitlab_http_status(200) + expect(response).to have_gitlab_http_status(202) end it "does not remove a non existing group" do diff --git a/spec/serializers/pipeline_entity_spec.rb b/spec/serializers/pipeline_entity_spec.rb index 2473c561f4b..e67d12b7a89 100644 --- a/spec/serializers/pipeline_entity_spec.rb +++ b/spec/serializers/pipeline_entity_spec.rb @@ -26,6 +26,13 @@ describe PipelineEntity do expect(subject).to include :updated_at, :created_at end + it 'excludes coverage data when disabled' do + entity = described_class + .represent(pipeline, request: request, disable_coverage: true) + + expect(entity.as_json).not_to include(:coverage) + end + it 'contains details' do expect(subject).to include :details expect(subject[:details]) diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index 9a0b6efd8a9..2b88fcc9a96 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -395,7 +395,27 @@ describe Ci::CreatePipelineService do result = execute_service expect(result).to be_persisted - expect(Environment.find_by(name: "review/master")).not_to be_nil + expect(Environment.find_by(name: "review/master")).to be_present + end + end + + context 'with environment name including persisted variables' do + before do + config = YAML.dump( + deploy: { + environment: { name: "review/id1$CI_PIPELINE_ID/id2$CI_BUILD_ID" }, + script: 'ls' + } + ) + + stub_ci_pipeline_yaml_file(config) + end + + it 'skipps persisted variables in environment name' do + result = execute_service + + expect(result).to be_persisted + expect(Environment.find_by(name: "review/id1/id2")).to be_present end end diff --git a/spec/services/keys/destroy_service_spec.rb b/spec/services/keys/destroy_service_spec.rb new file mode 100644 index 00000000000..28ac72ddd42 --- /dev/null +++ b/spec/services/keys/destroy_service_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' + +describe Keys::DestroyService do + let(:user) { create(:user) } + + subject { described_class.new(user) } + + it 'destroys a key' do + key = create(:key) + + expect { subject.execute(key) }.to change(Key, :count).by(-1) + end +end diff --git a/spec/services/projects/update_remote_mirror_service_spec.rb b/spec/services/projects/update_remote_mirror_service_spec.rb index be09afd9f36..723cb374c37 100644 --- a/spec/services/projects/update_remote_mirror_service_spec.rb +++ b/spec/services/projects/update_remote_mirror_service_spec.rb @@ -343,7 +343,11 @@ describe Projects::UpdateRemoteMirrorService do tag = repository.find_tag(name) target = tag.try(:target) target_commit = tag.try(:dereferenced_target) - tags << Gitlab::Git::Tag.new(repository.raw_repository, name, target, target_commit) + tags << Gitlab::Git::Tag.new(repository.raw_repository, { + name: name, + target: target, + target_commit: target_commit + }) end end diff --git a/spec/support/helpers/rake_helpers.rb b/spec/support/helpers/rake_helpers.rb index 86bfeed107c..acd9cce6a67 100644 --- a/spec/support/helpers/rake_helpers.rb +++ b/spec/support/helpers/rake_helpers.rb @@ -13,6 +13,10 @@ module RakeHelpers allow(main_object).to receive(:print) end + def silence_progress_bar + allow_any_instance_of(ProgressBar::Output).to receive(:stream).and_return(double().as_null_object) + end + def main_object @main_object ||= TOPLEVEL_BINDING.eval('self') end diff --git a/spec/support/shared_examples/malicious_regexp_shared_examples.rb b/spec/support/shared_examples/malicious_regexp_shared_examples.rb index ac5d22298bb..65026f1d7c0 100644 --- a/spec/support/shared_examples/malicious_regexp_shared_examples.rb +++ b/spec/support/shared_examples/malicious_regexp_shared_examples.rb @@ -1,3 +1,5 @@ +require 'timeout' + shared_examples 'malicious regexp' do let(:malicious_text) { 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!' } let(:malicious_regexp) { '(?i)^(([a-z])+.)+[A-Z]([a-z])+$' }