summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Dangerfile1
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/behaviors/markdown/copy_as_gfm.js34
-rw-r--r--app/assets/javascripts/boards/components/board_list.vue2
-rw-r--r--app/assets/javascripts/diffs/store/mutations.js1
-rw-r--r--app/assets/javascripts/diffs/store/utils.js1
-rw-r--r--app/assets/javascripts/notes/components/note_actions.vue6
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/file_row.vue1
-rw-r--r--app/assets/stylesheets/framework/header.scss9
-rw-r--r--app/assets/stylesheets/pages/boards.scss7
-rw-r--r--app/assets/stylesheets/pages/branches.scss13
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss13
-rw-r--r--app/controllers/application_controller.rb2
-rw-r--r--app/controllers/concerns/send_file_upload.rb2
-rw-r--r--app/controllers/help_controller.rb7
-rw-r--r--app/controllers/projects/branches_controller.rb3
-rw-r--r--app/controllers/projects/pages_domains_controller.rb2
-rw-r--r--app/helpers/appearances_helper.rb2
-rw-r--r--app/models/ci/pipeline.rb11
-rw-r--r--app/models/concerns/with_uploads.rb34
-rw-r--r--app/models/discussion.rb2
-rw-r--r--app/models/individual_note_discussion.rb8
-rw-r--r--app/models/project.rb4
-rw-r--r--app/models/releases/link.rb2
-rw-r--r--app/models/repository.rb9
-rw-r--r--app/presenters/project_presenter.rb3
-rw-r--r--app/services/ci/create_pipeline_service.rb9
-rw-r--r--app/services/notes/create_service.rb2
-rw-r--r--app/services/task_list_toggle_service.rb4
-rw-r--r--app/views/layouts/_mailer.html.haml2
-rw-r--r--app/views/layouts/header/_empty.html.haml4
-rw-r--r--app/views/projects/branches/_branch.html.haml28
-rw-r--r--changelogs/unreleased/39676-wiki-api-problems-on-update-parameters-and-500-error.yml5
-rw-r--r--changelogs/unreleased/47150-update-sshkey.yml5
-rw-r--r--changelogs/unreleased/50006-expose-textcolor-from-public-labels-api.yml5
-rw-r--r--changelogs/unreleased/50559-add-milestone-progress-to-api.yml5
-rw-r--r--changelogs/unreleased/54850-pages-domain-show-view-is-not-protected-by-access-control.yml5
-rw-r--r--changelogs/unreleased/55209-tool-tip-hides-menu-item.yml5
-rw-r--r--changelogs/unreleased/56237-api-truncated-commit-title.yml5
-rw-r--r--changelogs/unreleased/57101-api-docs-for-hangouts-chat-service-incorrect.yml5
-rw-r--r--changelogs/unreleased/57160-merge-request-tabs-header-is-missing-bottom-border.yml5
-rw-r--r--changelogs/unreleased/57223-wiki-finder.yml5
-rw-r--r--changelogs/unreleased/57410-api-create-release-link-with-ftp-address-return-400-bad-request.yml5
-rw-r--r--changelogs/unreleased/57564-contributing-button-border.yml5
-rw-r--r--changelogs/unreleased/57579-gitlab-project-import-fails-sidekiq-undefined-method-import_jid.yml5
-rw-r--r--changelogs/unreleased/57589-update-workhorse.yml5
-rw-r--r--changelogs/unreleased/add-title-attribute-to-file-row.yml5
-rw-r--r--changelogs/unreleased/fast-destroy-uploads.yml5
-rw-r--r--changelogs/unreleased/fix_-56347.yml5
-rw-r--r--changelogs/unreleased/improve-performance-for-diverging-commit-counts.yml5
-rw-r--r--changelogs/unreleased/ravlen-fix-spaces-unicode.yml5
-rw-r--r--changelogs/unreleased/sh-import-source-branch-github-forks.yml5
-rw-r--r--changelogs/unreleased/sh-log-rails-queue-duration.yml5
-rw-r--r--changelogs/unreleased/support-only-changes-on-mr-pipelines.yml5
-rw-r--r--config/initializers/lograge.rb3
-rw-r--r--danger/changelog/Dangerfile6
-rw-r--r--danger/documentation/Dangerfile13
-rw-r--r--danger/plugins/helper.rb37
-rw-r--r--danger/roulette/Dangerfile78
-rw-r--r--doc/administration/high_availability/gitlab.md8
-rw-r--r--doc/administration/high_availability/load_balancer.md54
-rw-r--r--doc/administration/logs.md7
-rw-r--r--doc/administration/pages/index.md2
-rw-r--r--doc/administration/uploads.md2
-rw-r--r--doc/api/group_labels.md6
-rw-r--r--doc/api/group_milestones.md1
-rw-r--r--doc/api/labels.md8
-rw-r--r--doc/api/milestones.md1
-rw-r--r--doc/api/releases/index.md14
-rw-r--r--doc/api/releases/links.md1
-rw-r--r--doc/api/repositories.md6
-rw-r--r--doc/api/services.md6
-rw-r--r--doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md2
-rw-r--r--doc/ci/examples/sast_docker.md2
-rw-r--r--doc/ci/merge_request_pipelines/index.md43
-rw-r--r--doc/ci/yaml/README.md26
-rw-r--r--doc/development/README.md1
-rw-r--r--doc/development/code_review.md5
-rw-r--r--doc/development/documentation/styleguide.md2
-rw-r--r--doc/development/ee_features.md23
-rw-r--r--doc/development/fe_guide/index.md19
-rw-r--r--doc/development/kubernetes.md126
-rw-r--r--doc/development/new_fe_guide/event_tracking.md74
-rw-r--r--doc/development/new_fe_guide/index.md4
-rw-r--r--doc/development/testing_guide/ci.md2
-rw-r--r--doc/development/testing_guide/review_apps.md8
-rw-r--r--doc/install/README.md2
-rw-r--r--doc/install/installation.md2
-rw-r--r--doc/integration/github.md4
-rw-r--r--doc/intro/README.md4
-rw-r--r--doc/security/README.md1
-rw-r--r--doc/ssh/README.md11
-rw-r--r--doc/system_hooks/system_hooks.md2
-rw-r--r--doc/topics/authentication/index.md2
-rw-r--r--doc/topics/autodevops/index.md12
-rw-r--r--doc/update/README.md17
-rw-r--r--doc/user/admin_area/settings/third_party_offers.md5
-rw-r--r--doc/user/profile/account/two_factor_authentication.md2
-rw-r--r--doc/user/profile/preferences.md5
-rw-r--r--doc/user/project/integrations/prometheus.md2
-rw-r--r--doc/user/project/integrations/webhooks.md25
-rw-r--r--doc/user/project/issues/issues_functionalities.md74
-rw-r--r--doc/user/project/milestones/index.md5
-rw-r--r--doc/user/project/pages/index.md2
-rw-r--r--doc/user/project/pipelines/job_artifacts.md6
-rw-r--r--doc/user/project/pipelines/settings.md1
-rw-r--r--doc/user/project/repository/web_editor.md4
-rw-r--r--doc/user/project/web_ide/index.md14
-rw-r--r--doc/user/reserved_names.md2
-rw-r--r--doc/workflow/gitlab_flow.md458
-rw-r--r--doc/workflow/time_tracking.md10
-rw-r--r--lib/api/entities.rb8
-rw-r--r--lib/api/milestone_responses.rb8
-rw-r--r--lib/api/services.rb5
-rw-r--r--lib/api/wikis.rb13
-rw-r--r--lib/banzai/filter/footnote_filter.rb23
-rw-r--r--lib/gitlab/ci/build/policy/changes.rb2
-rw-r--r--lib/gitlab/danger/helper.rb131
-rw-r--r--lib/gitlab/danger/teammate.rb42
-rw-r--r--lib/gitlab/git/repository.rb7
-rw-r--r--lib/gitlab/gitaly_client/commit_service.rb11
-rw-r--r--lib/gitlab/github_import/importer/pull_request_importer.rb30
-rw-r--r--lib/gitlab/github_import/representation/pull_request.rb4
-rw-r--r--lib/gitlab/import_export/shared.rb2
-rw-r--r--lib/gitlab/middleware/rails_queue_duration.rb3
-rw-r--r--lib/gitlab/sql/recursive_cte.rb2
-rw-r--r--lib/gitlab/usage_data.rb14
-rw-r--r--lib/gitlab/wiki_file_finder.rb4
-rw-r--r--lib/tasks/gitlab/setup.rake25
-rw-r--r--locale/gitlab.pot3
-rw-r--r--package.json2
-rw-r--r--qa/qa/page/base.rb17
-rw-r--r--qa/qa/page/group/show.rb16
-rw-r--r--qa/qa/page/main/menu.rb2
-rw-r--r--qa/qa/page/main/sign_up.rb2
-rw-r--r--qa/qa/page/project/web_ide/edit.rb2
-rw-r--r--qa/spec/spec_helper.rb67
-rw-r--r--qa/spec/spec_helper_spec.rb332
-rwxr-xr-xscripts/security-harness2
-rw-r--r--spec/controllers/concerns/send_file_upload_spec.rb17
-rw-r--r--spec/controllers/help_controller_spec.rb40
-rw-r--r--spec/controllers/projects/pages_domains_controller_spec.rb17
-rw-r--r--spec/features/dashboard/todos/todos_spec.rb8
-rw-r--r--spec/features/projects/branches/user_views_branches_spec.rb2
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/group_labels.json1
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/milestone.json1
-rw-r--r--spec/javascripts/diffs/store/utils_spec.js32
-rw-r--r--spec/javascripts/notes/components/note_actions_spec.js30
-rw-r--r--spec/javascripts/notes/components/noteable_note_spec.js118
-rw-r--r--spec/javascripts/vue_shared/components/user_avatar/user_avatar_link_spec.js18
-rw-r--r--spec/lib/banzai/filter/footnote_filter_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/policy/changes_spec.rb56
-rw-r--r--spec/lib/gitlab/danger/helper_spec.rb294
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb90
-rw-r--r--spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb38
-rw-r--r--spec/lib/gitlab/github_import/representation/pull_request_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/shared_spec.rb10
-rw-r--r--spec/lib/gitlab/sql/recursive_cte_spec.rb9
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb6
-rw-r--r--spec/models/ci/pipeline_spec.rb22
-rw-r--r--spec/models/project_spec.rb15
-rw-r--r--spec/models/releases/link_spec.rb24
-rw-r--r--spec/requests/api/commits_spec.rb2
-rw-r--r--spec/requests/api/labels_spec.rb5
-rw-r--r--spec/requests/api/wikis_spec.rb40
-rw-r--r--spec/services/notes/create_service_spec.rb9
-rw-r--r--spec/services/task_list_toggle_service_spec.rb32
-rw-r--r--spec/support/api/milestones_shared_examples.rb5
-rw-r--r--spec/support/helpers/stub_feature_flags.rb27
-rw-r--r--spec/support/shared_examples/models/with_uploads_shared_examples.rb20
-rw-r--r--yarn.lock8
175 files changed, 2588 insertions, 805 deletions
diff --git a/Dangerfile b/Dangerfile
index 6a2c5cf2773..715a2bcbbae 100644
--- a/Dangerfile
+++ b/Dangerfile
@@ -11,3 +11,4 @@ danger.import_dangerfile(path: 'danger/commit_messages')
danger.import_dangerfile(path: 'danger/duplicate_yarn_dependencies')
danger.import_dangerfile(path: 'danger/prettier')
danger.import_dangerfile(path: 'danger/eslint')
+danger.import_dangerfile(path: 'danger/roulette')
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 815d5ca06d5..66e2ae6c25c 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-1.19.0
+1.19.1
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index 2bf50aaf17a..56b6be4ebb2 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-8.3.0
+8.3.1
diff --git a/Gemfile b/Gemfile
index 3ff78312559..452860d60b4 100644
--- a/Gemfile
+++ b/Gemfile
@@ -410,7 +410,7 @@ gem 'sys-filesystem', '~> 1.1.6'
# SSH host key support
gem 'net-ssh', '~> 5.0'
-gem 'sshkey', '~> 1.9.0'
+gem 'sshkey', '~> 2.0'
# Required for ED25519 SSH host key support
group :ed25519 do
diff --git a/Gemfile.lock b/Gemfile.lock
index 9aea8919287..92dd5dac0ca 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -855,7 +855,7 @@ GEM
activesupport (>= 4.0)
sprockets (>= 3.0.0)
sqlite3 (1.3.13)
- sshkey (1.9.0)
+ sshkey (2.0.0)
stackprof (0.2.10)
state_machines (0.5.0)
state_machines-activemodel (0.5.1)
@@ -1157,7 +1157,7 @@ DEPENDENCIES
spring (~> 2.0.0)
spring-commands-rspec (~> 1.0.4)
sprockets (~> 3.7.0)
- sshkey (~> 1.9.0)
+ sshkey (~> 2.0)
stackprof (~> 0.2.10)
state_machines-activerecord (~> 0.5.1)
sys-filesystem (~> 1.1.6)
diff --git a/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js b/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js
index 52d9f2f0322..9482a9f166d 100644
--- a/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js
+++ b/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js
@@ -36,13 +36,20 @@ export class CopyAsGFM {
div.appendChild(el.cloneNode(true));
const html = div.innerHTML;
+ clipboardData.setData('text/plain', el.textContent);
+ clipboardData.setData('text/html', html);
+ // We are also setting this as fallback to transform the selection to gfm on paste
+ clipboardData.setData('text/x-gfm-html', html);
+
CopyAsGFM.nodeToGFM(el)
.then(res => {
- clipboardData.setData('text/plain', el.textContent);
clipboardData.setData('text/x-gfm', res);
- clipboardData.setData('text/html', html);
})
- .catch(() => {});
+ .catch(() => {
+ // Not showing the error as Firefox might doesn't allow
+ // it or other browsers who have a time limit on the execution
+ // of the copy event
+ });
}
static pasteGFM(e) {
@@ -51,11 +58,28 @@ export class CopyAsGFM {
const text = clipboardData.getData('text/plain');
const gfm = clipboardData.getData('text/x-gfm');
- if (!gfm) return;
+ const gfmHtml = clipboardData.getData('text/x-gfm-html');
+ if (!gfm && !gfmHtml) return;
e.preventDefault();
- window.gl.utils.insertText(e.target, textBefore => {
+ // We have the original selection already converted to gfm
+ if (gfm) {
+ CopyAsGFM.insertPastedText(e.target, text, gfm);
+ } else {
+ // Due to the async copy call we are not able to produce gfm so we transform the cached HTML
+ const div = document.createElement('div');
+ div.innerHTML = gfmHtml;
+ CopyAsGFM.nodeToGFM(div)
+ .then(transformedGfm => {
+ CopyAsGFM.insertPastedText(e.target, text, transformedGfm);
+ })
+ .catch(() => {});
+ }
+ }
+
+ static insertPastedText(target, text, gfm) {
+ window.gl.utils.insertText(target, textBefore => {
// If the text before the cursor contains an odd number of backticks,
// we are either inside an inline code span that starts with 1 backtick
// or a code block that starts with 3 backticks.
diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue
index a689dfc3768..f3f341ece5c 100644
--- a/app/assets/javascripts/boards/components/board_list.vue
+++ b/app/assets/javascripts/boards/components/board_list.vue
@@ -221,7 +221,7 @@ export default {
</script>
<template>
- <div class="board-list-component d-flex flex-column">
+ <div class="board-list-component">
<div v-if="loading" class="board-list-loading text-center" aria-label="Loading issues">
<gl-loading-icon />
</div>
diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js
index 7bbafe66199..5a27388863c 100644
--- a/app/assets/javascripts/diffs/store/mutations.js
+++ b/app/assets/javascripts/diffs/store/mutations.js
@@ -144,6 +144,7 @@ export default {
if (left || right) {
return {
+ ...line,
left: line.left ? mapDiscussions(line.left) : null,
right: line.right ? mapDiscussions(line.right, () => !left) : null,
};
diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js
index effb6202327..062024b8cdd 100644
--- a/app/assets/javascripts/diffs/store/utils.js
+++ b/app/assets/javascripts/diffs/store/utils.js
@@ -161,6 +161,7 @@ export function addContextLines(options) {
const normalizedParallelLines = contextLines.map(line => ({
left: line,
right: line,
+ line_code: line.line_code,
}));
if (options.bottom) {
diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue
index 91b9e5de374..969d5b69c25 100644
--- a/app/assets/javascripts/notes/components/note_actions.vue
+++ b/app/assets/javascripts/notes/components/note_actions.vue
@@ -126,6 +126,11 @@ export default {
onResolve() {
this.$emit('handleResolve');
},
+ closeTooltip() {
+ this.$nextTick(() => {
+ this.$root.$emit('bv::hide::tooltip');
+ });
+ },
},
};
</script>
@@ -202,6 +207,7 @@ export default {
title="More actions"
class="note-action-button more-actions-toggle btn btn-transparent"
data-toggle="dropdown"
+ @click="closeTooltip"
>
<icon css-classes="icon" name="ellipsis_v" />
</button>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue
index a1d3a09cca4..33963d5e1e6 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue
@@ -73,14 +73,14 @@ export default {
<gl-button
:aria-label="ariaLabel"
variant="blank"
- class="commit-edit-toggle mr-2"
+ class="commit-edit-toggle square s24 mr-2"
@click.stop="toggle()"
>
<icon :name="collapseIcon" :size="16" />
</gl-button>
<span v-if="expanded">{{ __('Collapse') }}</span>
<span v-else>
- <span v-html="message"></span>
+ <span class="vertical-align-middle" v-html="message"></span>
<gl-button variant="link" class="modify-message-button">
{{ modifyLinkMessage }}
</gl-button>
diff --git a/app/assets/javascripts/vue_shared/components/file_row.vue b/app/assets/javascripts/vue_shared/components/file_row.vue
index f54033efc54..0cbcdbf2eb4 100644
--- a/app/assets/javascripts/vue_shared/components/file_row.vue
+++ b/app/assets/javascripts/vue_shared/components/file_row.vue
@@ -136,6 +136,7 @@ export default {
<div
v-else
:class="fileClass"
+ :title="file.name"
class="file-row"
role="button"
@click="clickFile"
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 36dd1cee4de..23dcc1817b1 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -565,15 +565,14 @@
}
.navbar-empty {
+ justify-content: center;
height: $header-height;
background: $white-light;
border-bottom: 1px solid $white-normal;
- .mx-auto {
- .tanuki-logo,
- img {
- height: 36px;
- }
+ .tanuki-logo,
+ .brand-header-logo {
+ max-height: 100%;
}
}
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index bc28ffb3a92..a9324ba2ed0 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -164,6 +164,13 @@
display: none;
}
}
+
+ &:not(.is-collapsed) {
+ .board-list-component {
+ display: flex;
+ flex-direction: column;
+ }
+ }
}
.board-inner {
diff --git a/app/assets/stylesheets/pages/branches.scss b/app/assets/stylesheets/pages/branches.scss
index 38fec3f0aa8..ce0622b3d48 100644
--- a/app/assets/stylesheets/pages/branches.scss
+++ b/app/assets/stylesheets/pages/branches.scss
@@ -11,15 +11,24 @@
}
.divergence-graph {
+ $graph-side-width: 80px;
+ $graph-separator-width: 1px;
+
padding: 0 6px;
.graph-side {
position: relative;
- width: 80px;
+ width: $graph-side-width;
height: 22px;
padding: 5px 0 13px;
float: left;
+ &.full {
+ width: $graph-side-width * 2 + $graph-separator-width;
+ display: flex;
+ justify-content: center;
+ }
+
.bar {
position: absolute;
height: 4px;
@@ -57,7 +66,7 @@
.graph-separator {
position: relative;
- width: 1px;
+ width: $graph-separator-width;
height: 18px;
margin: 5px 0 0;
float: left;
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 135730d71e9..790d438c7e2 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -738,6 +738,8 @@
z-index: 103;
background: $gray-light;
color: $gl-text-color;
+ margin-top: -1px;
+ border-top: 1px solid $border-color;
.mr-version-menus-container {
display: flex;
@@ -789,7 +791,6 @@
position: sticky;
top: $header-height + $mr-tabs-height;
width: 100%;
- border-top: 1px solid $border-color;
&.is-fileTreeOpen {
margin-left: -16px;
@@ -810,10 +811,7 @@
top: $header-height;
z-index: 200;
background-color: $white-light;
-
- @include media-breakpoint-down(md) {
- border-bottom: 1px solid $border-color;
- }
+ border-bottom: 1px solid $border-color;
@include media-breakpoint-up(sm) {
position: sticky;
@@ -1019,3 +1017,8 @@
z-index: 99999;
background: $black-transparent;
}
+
+.source-branch-removal-status {
+ padding-left: 50px;
+ padding-bottom: $gl-padding;
+}
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 26cd5dc801f..af0b0c64814 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -137,6 +137,8 @@ class ApplicationController < ActionController::Base
if response.status == 422 && response.body.present? && response.content_type == 'application/json'.freeze
payload[:response] = response.body
end
+
+ payload[:queue_duration] = request.env[::Gitlab::Middleware::RailsQueueDuration::GITLAB_RAILS_QUEUE_DURATION_KEY]
end
##
diff --git a/app/controllers/concerns/send_file_upload.rb b/app/controllers/concerns/send_file_upload.rb
index 9ca54c5519b..28e4cece548 100644
--- a/app/controllers/concerns/send_file_upload.rb
+++ b/app/controllers/concerns/send_file_upload.rb
@@ -3,7 +3,7 @@
module SendFileUpload
def send_upload(file_upload, send_params: {}, redirect_params: {}, attachment: nil, proxy: false, disposition: 'attachment')
if attachment
- response_disposition = ::Gitlab::ContentDisposition.format(disposition: 'attachment', filename: attachment)
+ response_disposition = ::Gitlab::ContentDisposition.format(disposition: disposition, filename: attachment)
# Response-Content-Type will not override an existing Content-Type in
# Google Cloud Storage, so the metadata needs to be cleared on GCS for
diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb
index e5a1fc9d6ff..a9d6addd4a4 100644
--- a/app/controllers/help_controller.rb
+++ b/app/controllers/help_controller.rb
@@ -13,9 +13,10 @@ class HelpController < ApplicationController
# Remove YAML frontmatter so that it doesn't look weird
@help_index = File.read(Rails.root.join('doc', 'README.md')).sub(YAML_FRONT_MATTER_REGEXP, '')
- # Prefix Markdown links with `help/` unless they are external links
- # See http://rubular.com/r/X3baHTbPO2
- @help_index.gsub!(%r{(?<delim>\]\()(?!.+://)(?!/)(?<link>[^\)\(]+\))}) do
+ # Prefix Markdown links with `help/` unless they are external links.
+ # '//' not necessarily part of URL, e.g., mailto:mail@example.com
+ # See https://rubular.com/r/DFHZl5w8d3bpzV
+ @help_index.gsub!(%r{(?<delim>\]\()(?!\w+:)(?!/)(?<link>[^\)\(]+\))}) do
"#{$~[:delim]}#{Gitlab.config.gitlab.relative_url_root}/help/#{$~[:link]}"
end
end
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index a6bfb913900..32b7f3207ef 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -29,7 +29,8 @@ class Projects::BranchesController < Projects::ApplicationController
Gitlab::GitalyClient.allow_n_plus_1_calls do
@max_commits = @branches.reduce(0) do |memo, branch|
diverging_commit_counts = repository.diverging_commit_counts(branch)
- [memo, diverging_commit_counts[:behind], diverging_commit_counts[:ahead]].max
+ [memo, diverging_commit_counts.values_at(:behind, :ahead, :distance)]
+ .flatten.compact.max
end
end
diff --git a/app/controllers/projects/pages_domains_controller.rb b/app/controllers/projects/pages_domains_controller.rb
index 439ec9b1731..58b1bc54181 100644
--- a/app/controllers/projects/pages_domains_controller.rb
+++ b/app/controllers/projects/pages_domains_controller.rb
@@ -4,7 +4,7 @@ class Projects::PagesDomainsController < Projects::ApplicationController
layout 'project_settings'
before_action :require_pages_enabled!
- before_action :authorize_update_pages!, except: [:show]
+ before_action :authorize_update_pages!
before_action :domain, except: [:new, :create]
def show
diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb
index 473c90c882c..7fbbbb04154 100644
--- a/app/helpers/appearances_helper.rb
+++ b/app/helpers/appearances_helper.rb
@@ -28,7 +28,7 @@ module AppearancesHelper
def brand_header_logo
if current_appearance&.header_logo?
- image_tag current_appearance.header_logo_path
+ image_tag current_appearance.header_logo_path, class: 'brand-header-logo'
else
render 'shared/logo.svg'
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index acef5d2e643..372f6d678f6 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -687,9 +687,18 @@ module Ci
end
end
+ # Returns the modified paths.
+ #
+ # The returned value is
+ # * Array: List of modified paths that should be evaluated
+ # * nil: Modified path can not be evaluated
def modified_paths
strong_memoize(:modified_paths) do
- push_details.modified_paths
+ if merge_request?
+ merge_request.modified_paths
+ elsif branch_updated?
+ push_details.modified_paths
+ end
end
end
diff --git a/app/models/concerns/with_uploads.rb b/app/models/concerns/with_uploads.rb
index d79c0eae77e..6c6febd186c 100644
--- a/app/models/concerns/with_uploads.rb
+++ b/app/models/concerns/with_uploads.rb
@@ -27,40 +27,14 @@ module WithUploads
included do
has_many :uploads, as: :model
- has_many :file_uploads, -> { where(uploader: FILE_UPLOADERS) }, class_name: 'Upload', as: :model
+ has_many :file_uploads, -> { where(uploader: FILE_UPLOADERS) },
+ class_name: 'Upload', as: :model,
+ dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
- # TODO: when feature flag is removed, we can use just dependent: destroy
- # option on :file_uploads
- before_destroy :remove_file_uploads
-
- use_fast_destroy :file_uploads, if: :fast_destroy_enabled?
+ use_fast_destroy :file_uploads
end
def retrieve_upload(_identifier, paths)
uploads.find_by(path: paths)
end
-
- private
-
- # mounted uploads are deleted in carrierwave's after_commit hook,
- # but FileUploaders which are not mounted must be deleted explicitly and
- # it can not be done in after_commit because FileUploader requires loads
- # associated model on destroy (which is already deleted in after_commit)
- def remove_file_uploads
- fast_destroy_enabled? ? delete_uploads : destroy_uploads
- end
-
- def delete_uploads
- file_uploads.delete_all(:delete_all)
- end
-
- def destroy_uploads
- file_uploads.find_each do |upload|
- upload.destroy
- end
- end
-
- def fast_destroy_enabled?
- Feature.enabled?(:fast_destroy_uploads, self)
- end
end
diff --git a/app/models/discussion.rb b/app/models/discussion.rb
index f2678e0597d..32529ebf71d 100644
--- a/app/models/discussion.rb
+++ b/app/models/discussion.rb
@@ -17,8 +17,6 @@ class Discussion
:for_commit?,
:for_merge_request?,
- :save,
-
to: :first_note
def project_id
diff --git a/app/models/individual_note_discussion.rb b/app/models/individual_note_discussion.rb
index aab0ff93468..b4a661ae5b4 100644
--- a/app/models/individual_note_discussion.rb
+++ b/app/models/individual_note_discussion.rb
@@ -17,8 +17,12 @@ class IndividualNoteDiscussion < Discussion
noteable.supports_replying_to_individual_notes? && Feature.enabled?(:reply_to_individual_notes)
end
- def convert_to_discussion!
- first_note.becomes!(Discussion.note_class).to_discussion
+ def convert_to_discussion!(save: false)
+ first_note.becomes!(Discussion.note_class).to_discussion.tap do
+ # Save needs to be called on first_note instead of the transformed note
+ # because of https://gitlab.com/gitlab-org/gitlab-ce/issues/57324
+ first_note.save if save
+ end
end
def reply_attributes
diff --git a/app/models/project.rb b/app/models/project.rb
index 58df4019450..c72d3a3b725 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -2073,6 +2073,10 @@ class Project < ActiveRecord::Base
pool_repository&.link_repository(repository)
end
+ def has_pool_repository?
+ pool_repository.present?
+ end
+
private
def merge_requests_allowing_collaboration(source_branch = nil)
diff --git a/app/models/releases/link.rb b/app/models/releases/link.rb
index 6f639e5a7b2..6c507c47752 100644
--- a/app/models/releases/link.rb
+++ b/app/models/releases/link.rb
@@ -6,7 +6,7 @@ module Releases
belongs_to :release
- validates :url, presence: true, url: true, uniqueness: { scope: :release }
+ validates :url, presence: true, url: { protocols: %w(http https ftp) }, uniqueness: { scope: :release }
validates :name, presence: true, uniqueness: { scope: :release }
scope :sorted, -> { order(created_at: :desc) }
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 7c50b4488e5..ed55a6e572b 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -288,13 +288,16 @@ class Repository
# Rugged seems to throw a `ReferenceError` when given branch_names rather
# than SHA-1 hashes
number_commits_behind, number_commits_ahead =
- raw_repository.count_commits_between(
+ raw_repository.diverging_commit_count(
@root_ref_hash,
branch.dereferenced_target.sha,
- left_right: true,
max_count: MAX_DIVERGING_COUNT)
- { behind: number_commits_behind, ahead: number_commits_ahead }
+ if number_commits_behind + number_commits_ahead >= MAX_DIVERGING_COUNT
+ { distance: MAX_DIVERGING_COUNT }
+ else
+ { behind: number_commits_behind, ahead: number_commits_ahead }
+ end
end
end
diff --git a/app/presenters/project_presenter.rb b/app/presenters/project_presenter.rb
index ea1d941cf83..4cac90c2567 100644
--- a/app/presenters/project_presenter.rb
+++ b/app/presenters/project_presenter.rb
@@ -256,7 +256,8 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
elsif repository.contribution_guide.present?
AnchorData.new(false,
statistic_icon('doc-text') + _('CONTRIBUTING'),
- contribution_guide_path)
+ contribution_guide_path,
+ 'default')
end
end
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index 699b3e8555e..354e53a367c 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -36,7 +36,7 @@ module Ci
project: project,
current_user: current_user,
push_options: params[:push_options],
- **extra_options(**options))
+ **extra_options(options))
sequence = Gitlab::Ci::Pipeline::Chain::Sequence
.new(pipeline, command, SEQUENCE)
@@ -108,7 +108,12 @@ module Ci
end
# rubocop: enable CodeReuse/ActiveRecord
- def extra_options
+ def extra_options(options = {})
+ # In Ruby 2.4, even when options is empty, f(**options) doesn't work when f
+ # doesn't have any parameters. We reproduce the Ruby 2.5 behavior by
+ # checking explicitely that no arguments are given.
+ raise ArgumentError if options.any?
+
{} # overriden in EE
end
end
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index b975c3a8cb6..5a6e7338b42 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -35,7 +35,7 @@ module Notes
if !only_commands && note.save
if note.part_of_discussion? && note.discussion.can_convert_to_discussion?
- note.discussion.convert_to_discussion!.save(touch: false)
+ note.discussion.convert_to_discussion!(save: true)
end
todo_service.new_note(note, current_user)
diff --git a/app/services/task_list_toggle_service.rb b/app/services/task_list_toggle_service.rb
index cfe187d9b12..f6602a35033 100644
--- a/app/services/task_list_toggle_service.rb
+++ b/app/services/task_list_toggle_service.rb
@@ -33,7 +33,7 @@ class TaskListToggleService
markdown_task = source_lines[source_line_index]
# The source in the DB could be using either \n or \r\n line endings
- return unless markdown_task == line_source || markdown_task == line_source + "\r"
+ return unless markdown_task.chomp == line_source
return unless source_checkbox = Taskable::ITEM_PATTERN.match(markdown_task)
currently_checked = TaskList::Item.new(source_checkbox[1]).complete?
@@ -67,6 +67,6 @@ class TaskListToggleService
# When using CommonMark, we should be able to use the embedded `sourcepos` attribute to
# target the exact line in the DOM.
def get_html_checkbox(html)
- html.css(".task-list-item[data-sourcepos^='#{line_number}:'] > input.task-list-item-checkbox").first
+ html.css(".task-list-item[data-sourcepos^='#{line_number}:'] input.task-list-item-checkbox").first
end
end
diff --git a/app/views/layouts/_mailer.html.haml b/app/views/layouts/_mailer.html.haml
index ddc1cdb24b5..26fd34347ec 100644
--- a/app/views/layouts/_mailer.html.haml
+++ b/app/views/layouts/_mailer.html.haml
@@ -49,7 +49,7 @@
%table#body{ border: "0", cellpadding: "0", cellspacing: "0", style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;" }
%tbody
%tr.line
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#6b4fbb;height:4px;font-size:4px;line-height:4px;" }  
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#6b4fbb;height:4px;font-size:4px;line-height:4px;" }
%tr.header
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
= header_logo
diff --git a/app/views/layouts/header/_empty.html.haml b/app/views/layouts/header/_empty.html.haml
index 2dfc787b7a8..348ce18b122 100644
--- a/app/views/layouts/header/_empty.html.haml
+++ b/app/views/layouts/header/_empty.html.haml
@@ -1,4 +1,2 @@
%header.navbar.fixed-top.navbar-empty
- .container
- .mx-auto
- = brand_header_logo
+ = brand_header_logo
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 4b0ea15335e..c64ad1c8147 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -2,6 +2,7 @@
- commit = @repository.commit(branch.dereferenced_target)
- bar_graph_width_factor = @max_commits > 0 ? 100.0/@max_commits : 0
- diverging_commit_counts = @repository.diverging_commit_counts(branch)
+- number_commits_distance = diverging_commit_counts[:distance]
- number_commits_behind = diverging_commit_counts[:behind]
- number_commits_ahead = diverging_commit_counts[:ahead]
- merge_project = merge_request_source_project_for_project(@project)
@@ -28,16 +29,23 @@
= s_('Branches|Cant find HEAD commit for this branch')
- if branch.name != @repository.root_ref
- .divergence-graph.d-none.d-md-block{ title: s_('%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead') % { number_commits_behind: diverging_count_label(number_commits_behind),
- default_branch: @repository.root_ref,
- number_commits_ahead: diverging_count_label(number_commits_ahead) } }
- .graph-side
- .bar.bar-behind{ style: "width: #{number_commits_behind * bar_graph_width_factor}%" }
- %span.count.count-behind= diverging_count_label(number_commits_behind)
- .graph-separator
- .graph-side
- .bar.bar-ahead{ style: "width: #{number_commits_ahead * bar_graph_width_factor}%" }
- %span.count.count-ahead= diverging_count_label(number_commits_ahead)
+ - if number_commits_distance.nil?
+ .divergence-graph.d-none.d-md-block{ title: s_('%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead') % { number_commits_behind: diverging_count_label(number_commits_behind),
+ default_branch: @repository.root_ref,
+ number_commits_ahead: diverging_count_label(number_commits_ahead) } }
+ .graph-side
+ .bar.bar-behind{ style: "width: #{number_commits_behind * bar_graph_width_factor}%" }
+ %span.count.count-behind= diverging_count_label(number_commits_behind)
+ .graph-separator
+ .graph-side
+ .bar.bar-ahead{ style: "width: #{number_commits_ahead * bar_graph_width_factor}%" }
+ %span.count.count-ahead= diverging_count_label(number_commits_ahead)
+ - else
+ .divergence-graph.d-none.d-md-block{ title: s_('More than %{number_commits_distance} commits different with %{default_branch}') % { number_commits_distance: diverging_count_label(number_commits_distance),
+ default_branch: @repository.root_ref} }
+ .graph-side.full
+ .bar{ style: "width: #{number_commits_distance * bar_graph_width_factor}%" }
+ %span.count= diverging_count_label(number_commits_distance)
.controls.d-none.d-md-block<
- if merge_project && create_mr_button?(@repository.root_ref, branch.name)
diff --git a/changelogs/unreleased/39676-wiki-api-problems-on-update-parameters-and-500-error.yml b/changelogs/unreleased/39676-wiki-api-problems-on-update-parameters-and-500-error.yml
new file mode 100644
index 00000000000..1af49fb6a2c
--- /dev/null
+++ b/changelogs/unreleased/39676-wiki-api-problems-on-update-parameters-and-500-error.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: Require only one parameter when updating a wiki'
+merge_request: 25191
+author: Robert Schilling
+type: fixed
diff --git a/changelogs/unreleased/47150-update-sshkey.yml b/changelogs/unreleased/47150-update-sshkey.yml
new file mode 100644
index 00000000000..342bdb1e2bc
--- /dev/null
+++ b/changelogs/unreleased/47150-update-sshkey.yml
@@ -0,0 +1,5 @@
+---
+title: Fix validation of certain ed25519 keys
+merge_request: 25115
+author: Merlijn B. W. Wajer
+type: fixed
diff --git a/changelogs/unreleased/50006-expose-textcolor-from-public-labels-api.yml b/changelogs/unreleased/50006-expose-textcolor-from-public-labels-api.yml
new file mode 100644
index 00000000000..3c8b58f3001
--- /dev/null
+++ b/changelogs/unreleased/50006-expose-textcolor-from-public-labels-api.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: Expose text_color for project and group labels'
+merge_request: 25172
+author: Robert Schilling
+type: added
diff --git a/changelogs/unreleased/50559-add-milestone-progress-to-api.yml b/changelogs/unreleased/50559-add-milestone-progress-to-api.yml
new file mode 100644
index 00000000000..e68e4bd6059
--- /dev/null
+++ b/changelogs/unreleased/50559-add-milestone-progress-to-api.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: Expose milestone progress'
+merge_request: 25173
+author: Robert Schilling
+type: added
diff --git a/changelogs/unreleased/54850-pages-domain-show-view-is-not-protected-by-access-control.yml b/changelogs/unreleased/54850-pages-domain-show-view-is-not-protected-by-access-control.yml
new file mode 100644
index 00000000000..41761213d7b
--- /dev/null
+++ b/changelogs/unreleased/54850-pages-domain-show-view-is-not-protected-by-access-control.yml
@@ -0,0 +1,5 @@
+---
+title: Require maintainer access to show pages domain settings
+merge_request: 24926
+author:
+type: fixed
diff --git a/changelogs/unreleased/55209-tool-tip-hides-menu-item.yml b/changelogs/unreleased/55209-tool-tip-hides-menu-item.yml
new file mode 100644
index 00000000000..44ea4141632
--- /dev/null
+++ b/changelogs/unreleased/55209-tool-tip-hides-menu-item.yml
@@ -0,0 +1,5 @@
+---
+title: Close More Actions tooltip when menu opens
+merge_request: 24285
+author:
+type: fixed
diff --git a/changelogs/unreleased/56237-api-truncated-commit-title.yml b/changelogs/unreleased/56237-api-truncated-commit-title.yml
new file mode 100644
index 00000000000..1a48d0fda1b
--- /dev/null
+++ b/changelogs/unreleased/56237-api-truncated-commit-title.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: Expose full commit title'
+merge_request: 25189
+author: Robert Schilling
+type: fixed
diff --git a/changelogs/unreleased/57101-api-docs-for-hangouts-chat-service-incorrect.yml b/changelogs/unreleased/57101-api-docs-for-hangouts-chat-service-incorrect.yml
new file mode 100644
index 00000000000..2e0ae9c3732
--- /dev/null
+++ b/changelogs/unreleased/57101-api-docs-for-hangouts-chat-service-incorrect.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: Fix docs and parameters for hangouts-chat service'
+merge_request: 25180
+author: Robert Schilling
+type: fixed
diff --git a/changelogs/unreleased/57160-merge-request-tabs-header-is-missing-bottom-border.yml b/changelogs/unreleased/57160-merge-request-tabs-header-is-missing-bottom-border.yml
new file mode 100644
index 00000000000..3146d07db3d
--- /dev/null
+++ b/changelogs/unreleased/57160-merge-request-tabs-header-is-missing-bottom-border.yml
@@ -0,0 +1,5 @@
+---
+title: Return bottom border on MR Tabs
+merge_request: !25198
+author:
+type: fixed
diff --git a/changelogs/unreleased/57223-wiki-finder.yml b/changelogs/unreleased/57223-wiki-finder.yml
new file mode 100644
index 00000000000..5ddf197568d
--- /dev/null
+++ b/changelogs/unreleased/57223-wiki-finder.yml
@@ -0,0 +1,5 @@
+---
+title: Remove BATCH_SIZE from WikiFileFinder
+merge_request: 24933
+author:
+type: other
diff --git a/changelogs/unreleased/57410-api-create-release-link-with-ftp-address-return-400-bad-request.yml b/changelogs/unreleased/57410-api-create-release-link-with-ftp-address-return-400-bad-request.yml
new file mode 100644
index 00000000000..6be6a2115b9
--- /dev/null
+++ b/changelogs/unreleased/57410-api-create-release-link-with-ftp-address-return-400-bad-request.yml
@@ -0,0 +1,5 @@
+---
+title: Add support for FTP assets for releases
+merge_request: 25071
+author: Robert Schilling
+type: added
diff --git a/changelogs/unreleased/57564-contributing-button-border.yml b/changelogs/unreleased/57564-contributing-button-border.yml
new file mode 100644
index 00000000000..e5875ef1c0f
--- /dev/null
+++ b/changelogs/unreleased/57564-contributing-button-border.yml
@@ -0,0 +1,5 @@
+---
+title: Fix the border style of CONTRIBUTING button when it exists
+merge_request: 25124
+author: Takuya Noguchi
+type: fixed
diff --git a/changelogs/unreleased/57579-gitlab-project-import-fails-sidekiq-undefined-method-import_jid.yml b/changelogs/unreleased/57579-gitlab-project-import-fails-sidekiq-undefined-method-import_jid.yml
new file mode 100644
index 00000000000..f7d6a6c4863
--- /dev/null
+++ b/changelogs/unreleased/57579-gitlab-project-import-fails-sidekiq-undefined-method-import_jid.yml
@@ -0,0 +1,5 @@
+---
+title: Fix import_jid error on project import
+merge_request: 25239
+author:
+type: fixed
diff --git a/changelogs/unreleased/57589-update-workhorse.yml b/changelogs/unreleased/57589-update-workhorse.yml
new file mode 100644
index 00000000000..525913bba4c
--- /dev/null
+++ b/changelogs/unreleased/57589-update-workhorse.yml
@@ -0,0 +1,5 @@
+---
+title: Update Workhorse to v8.3.1
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/add-title-attribute-to-file-row.yml b/changelogs/unreleased/add-title-attribute-to-file-row.yml
new file mode 100644
index 00000000000..c68d3d544e7
--- /dev/null
+++ b/changelogs/unreleased/add-title-attribute-to-file-row.yml
@@ -0,0 +1,5 @@
+---
+title: add title attribute to display file name
+merge_request: 25154
+author: Satoshi Nakamatsu @satoshicano
+type: added
diff --git a/changelogs/unreleased/fast-destroy-uploads.yml b/changelogs/unreleased/fast-destroy-uploads.yml
new file mode 100644
index 00000000000..ee3363a6ae9
--- /dev/null
+++ b/changelogs/unreleased/fast-destroy-uploads.yml
@@ -0,0 +1,5 @@
+---
+title: File uploads are deleted asynchronously when deleting a project or group.
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/fix_-56347.yml b/changelogs/unreleased/fix_-56347.yml
new file mode 100644
index 00000000000..1d03ed8864c
--- /dev/null
+++ b/changelogs/unreleased/fix_-56347.yml
@@ -0,0 +1,5 @@
+---
+title: Fix overlapping empty-header logo
+merge_request: 24868
+author: Jonas L.
+type: fixed
diff --git a/changelogs/unreleased/improve-performance-for-diverging-commit-counts.yml b/changelogs/unreleased/improve-performance-for-diverging-commit-counts.yml
new file mode 100644
index 00000000000..76ff15cba5b
--- /dev/null
+++ b/changelogs/unreleased/improve-performance-for-diverging-commit-counts.yml
@@ -0,0 +1,5 @@
+---
+title: Improve performance for diverging commit counts
+merge_request: 24287
+author:
+type: performance
diff --git a/changelogs/unreleased/ravlen-fix-spaces-unicode.yml b/changelogs/unreleased/ravlen-fix-spaces-unicode.yml
new file mode 100644
index 00000000000..fbcbdc53cfe
--- /dev/null
+++ b/changelogs/unreleased/ravlen-fix-spaces-unicode.yml
@@ -0,0 +1,5 @@
+---
+title: Correct non-standard unicode spaces to regular unicode
+merge_request: 24795
+author: Marcel Amirault
+type: other
diff --git a/changelogs/unreleased/sh-import-source-branch-github-forks.yml b/changelogs/unreleased/sh-import-source-branch-github-forks.yml
new file mode 100644
index 00000000000..b5ea60202c0
--- /dev/null
+++ b/changelogs/unreleased/sh-import-source-branch-github-forks.yml
@@ -0,0 +1,5 @@
+---
+title: Create the source branch for a GitHub import
+merge_request: 25064
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-log-rails-queue-duration.yml b/changelogs/unreleased/sh-log-rails-queue-duration.yml
new file mode 100644
index 00000000000..89390aef108
--- /dev/null
+++ b/changelogs/unreleased/sh-log-rails-queue-duration.yml
@@ -0,0 +1,5 @@
+---
+title: Log queue duration in production_json.log
+merge_request: 25075
+author:
+type: other
diff --git a/changelogs/unreleased/support-only-changes-on-mr-pipelines.yml b/changelogs/unreleased/support-only-changes-on-mr-pipelines.yml
new file mode 100644
index 00000000000..fbab898b799
--- /dev/null
+++ b/changelogs/unreleased/support-only-changes-on-mr-pipelines.yml
@@ -0,0 +1,5 @@
+---
+title: 'Support `only: changes:` on MR pipelines'
+merge_request: 24490
+author: Hiroyuki Sato
+type: added
diff --git a/config/initializers/lograge.rb b/config/initializers/lograge.rb
index c897bc30e76..164954d1293 100644
--- a/config/initializers/lograge.rb
+++ b/config/initializers/lograge.rb
@@ -23,7 +23,8 @@ unless Sidekiq.server?
remote_ip: event.payload[:remote_ip],
user_id: event.payload[:user_id],
username: event.payload[:username],
- ua: event.payload[:ua]
+ ua: event.payload[:ua],
+ queue_duration: event.payload[:queue_duration]
}
gitaly_calls = Gitlab::GitalyClient.get_request_count
diff --git a/danger/changelog/Dangerfile b/danger/changelog/Dangerfile
index 530c6638653..63b2f6f5c5c 100644
--- a/danger/changelog/Dangerfile
+++ b/danger/changelog/Dangerfile
@@ -16,16 +16,12 @@ consider adding any of the %<labels>s labels.
#{SEE_DOC}
MSG
-def ee?
- ENV['CI_PROJECT_NAME'] == 'gitlab-ee' || File.exist?('../../CHANGELOG-EE.md')
-end
-
def ee_changelog?(changelog_path)
changelog_path =~ /unreleased-ee/
end
def ce_port_changelog?(changelog_path)
- ee? && !ee_changelog?(changelog_path)
+ helper.ee? && !ee_changelog?(changelog_path)
end
def check_changelog(path)
diff --git a/danger/documentation/Dangerfile b/danger/documentation/Dangerfile
index 188331cc87c..dd0e3f6deb6 100644
--- a/danger/documentation/Dangerfile
+++ b/danger/documentation/Dangerfile
@@ -1,17 +1,6 @@
# frozen_string_literal: true
-# All the files/directories that should be reviewed by the Docs team.
-DOCS_FILES = [
- 'doc/'
-].freeze
-
-def docs_paths_requiring_review(files)
- files.select do |file|
- DOCS_FILES.any? { |pattern| file.start_with?(pattern) }
- end
-end
-
-docs_paths_to_review = docs_paths_requiring_review(helper.all_changed_files)
+docs_paths_to_review = helper.changes_by_category[:documentation]
unless docs_paths_to_review.empty?
message 'This merge request adds or changes files that require a ' \
diff --git a/danger/plugins/helper.rb b/danger/plugins/helper.rb
index f4eb9119266..581c0720083 100644
--- a/danger/plugins/helper.rb
+++ b/danger/plugins/helper.rb
@@ -1,34 +1,15 @@
# frozen_string_literal: true
+require 'net/http'
+require 'yaml'
+
+require_relative '../../lib/gitlab/danger/helper'
+
module Danger
- # Common helper functions for our danger scripts
- # If we find ourselves repeating code in our danger files, we might as well put them in here.
+ # Common helper functions for our danger scripts. See Gitlab::Danger::Helper
+ # for more details
class Helper < Plugin
- # Returns a list of all files that have been added, modified or renamed.
- # `git.modified_files` might contain paths that already have been renamed,
- # so we need to remove them from the list.
- #
- # Considering these changes:
- #
- # - A new_file.rb
- # - D deleted_file.rb
- # - M modified_file.rb
- # - R renamed_file_before.rb -> renamed_file_after.rb
- #
- # it will return
- # ```
- # [ 'new_file.rb', 'modified_file.rb', 'renamed_file_after.rb' ]
- # ```
- #
- # @return [Array<String>]
- def all_changed_files
- Set.new
- .merge(git.added_files.to_a)
- .merge(git.modified_files.to_a)
- .merge(git.renamed_files.map { |x| x[:after] })
- .subtract(git.renamed_files.map { |x| x[:before] })
- .to_a
- .sort
- end
+ # Put the helper code somewhere it can be tested
+ include Gitlab::Danger::Helper
end
end
diff --git a/danger/roulette/Dangerfile b/danger/roulette/Dangerfile
new file mode 100644
index 00000000000..5c3d7a4ca49
--- /dev/null
+++ b/danger/roulette/Dangerfile
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+MESSAGE = <<MARKDOWN
+## Reviewer roulette
+
+Changes that require review have been detected! A merge request is normally
+reviewed by both a reviewer and a maintainer in its primary category (e.g.
+~frontend or ~backend), and by a maintainer in all other categories.
+MARKDOWN
+
+CATEGORY_TABLE_HEADER = <<MARKDOWN
+
+To spread load more evenly across eligible reviewers, Danger has randomly picked
+a candidate for each review slot. Feel free to override this selection if you
+think someone else would be better-suited, or the chosen person is unavailable.
+
+Once you've decided who will review this merge request, mention them as you
+normally would! Danger does not (yet?) automatically notify them for you.
+
+| Category | Reviewer | Maintainer |
+| -------- | -------- | ---------- |
+MARKDOWN
+
+UNKNOWN_FILES_MESSAGE = <<MARKDOWN
+
+These files couldn't be categorised, so Danger was unable to suggest a reviewer.
+Please consider creating a merge request to
+[add support](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/danger/helper.rb)
+for them.
+MARKDOWN
+
+def spin(team, project, category)
+ reviewers = team.select { |member| member.reviewer?(project, category) }
+ maintainers = team.select { |member| member.maintainer?(project, category) }
+
+ # TODO: filter out people who are currently not in the office
+ # TODO: take CODEOWNERS into account?
+
+ reviewer = reviewers[rand(reviewers.size)]
+ maintainer = maintainers[rand(maintainers.size)]
+
+ "| #{helper.label_for_category(category)} | #{reviewer&.markdown_name} | #{maintainer&.markdown_name} |"
+end
+
+def build_list(items)
+ list = items.map { |filename| "* `#{filename}`" }.join("\n")
+
+ if items.size > 10
+ "\n<details>\n\n#{list}\n\n</details>"
+ else
+ list
+ end
+end
+
+changes = helper.changes_by_category
+categories = changes.keys - [:unknown]
+
+unless changes.empty?
+ team =
+ begin
+ helper.project_team
+ rescue => err
+ warn("Reviewer roulette failed to load team data: #{err.message}")
+ []
+ end
+
+ # Exclude the MR author from the team for selection purposes
+ team.delete_if { |teammate| teammate.username == gitlab.mr_author }
+
+ project = helper.project_name
+ unknown = changes.fetch(:unknown, [])
+
+ rows = categories.map { |category| spin(team, project, category) }
+
+ markdown(MESSAGE)
+ markdown(CATEGORY_TABLE_HEADER + rows.join("\n")) unless rows.empty?
+ markdown(UNKNOWN_FILES_MESSAGE + build_list(unknown)) unless unknown.empty?
+end
diff --git a/doc/administration/high_availability/gitlab.md b/doc/administration/high_availability/gitlab.md
index 2ca860bd763..e554c06532e 100644
--- a/doc/administration/high_availability/gitlab.md
+++ b/doc/administration/high_availability/gitlab.md
@@ -58,6 +58,7 @@ for each GitLab application server in your environment.
# Disable components that will not be on the GitLab application server
roles ['application_role']
+ nginx['enable'] = true
# PostgreSQL connection details
gitlab_rails['db_adapter'] = 'postgresql'
@@ -90,6 +91,8 @@ for each GitLab application server in your environment.
certificates are not present, Nginx will fail to start. See
[Nginx documentation](http://docs.gitlab.com/omnibus/settings/nginx.html#enable-https)
for more information.
+ >
+ > **Note:** It is best to set the `uid` and `gid`s prior to the initial reconfigure of GitLab. Omnibus will not recursively `chown` directories if set after the initial reconfigure.
## First GitLab application server
@@ -108,8 +111,9 @@ Additional GitLab servers (servers configured **after** the first GitLab server)
need some extra configuration.
1. Configure shared secrets. These values can be obtained from the primary
- GitLab server in `/etc/gitlab/gitlab-secrets.json`. Add these to
- `/etc/gitlab/gitlab.rb` **prior to** running the first `reconfigure`.
+ GitLab server in `/etc/gitlab/gitlab-secrets.json`. Copy this file to the
+ secondary servers **prior to** running the first `reconfigure` in the steps
+ above.
```ruby
gitlab_shell['secret_token'] = 'fbfb19c355066a9afb030992231c4a363357f77345edd0f2e772359e5be59b02538e1fa6cae8f93f7d23355341cea2b93600dab6d6c3edcdced558fc6d739860'
diff --git a/doc/administration/high_availability/load_balancer.md b/doc/administration/high_availability/load_balancer.md
index 359de0efadb..28b226cacd5 100644
--- a/doc/administration/high_availability/load_balancer.md
+++ b/doc/administration/high_availability/load_balancer.md
@@ -8,7 +8,53 @@ choice already. Some examples including HAProxy (open-source), F5 Big-IP LTM,
and Citrix Net Scaler. This documentation will outline what ports and protocols
you need to use with GitLab.
-## Basic ports
+## SSL
+
+How will you handle SSL in your HA environment? There are several different
+options:
+
+- Each application node terminates SSL
+- The load balancer(s) terminate SSL and communication is not secure between
+ the load balancer(s) and the application nodes
+- The load balancer(s) terminate SSL and communication is *secure* between the
+ load balancer(s) and the application nodes
+
+### Application nodes terminate SSL
+
+Configure your load balancer(s) to pass connections on port 443 as 'TCP' rather
+than 'HTTP(S)' protocol. This will pass the connection to the application nodes
+Nginx service untouched. Nginx will have the SSL certificate and listen on port 443.
+
+See [Nginx HTTPS documentation](https://docs.gitlab.com/omnibus/settings/nginx.html#enable-https)
+for details on managing SSL certificates and configuring Nginx.
+
+### Load Balancer(s) terminate SSL without backend SSL
+
+Configure your load balancer(s) to use the 'HTTP(S)' protocol rather than 'TCP'.
+The load balancer(s) will then be responsible for managing SSL certificates and
+terminating SSL.
+
+Since communication between the load balancer(s) and GitLab will not be secure,
+there is some additional configuration needed. See
+[Nginx Proxied SSL documentation](https://docs.gitlab.com/omnibus/settings/nginx.html#supporting-proxied-ssl)
+for details.
+
+### Load Balancer(s) terminate SSL with backend SSL
+
+Configure your load balancer(s) to use the 'HTTP(S)' protocol rather than 'TCP'.
+The load balancer(s) will be responsible for managing SSL certificates that
+end users will see.
+
+Traffic will also be secure between the load balancer(s) and Nginx in this
+scenario. There is no need to add configuration for proxied SSL since the
+connection will be secure all the way. However, configuration will need to be
+added to GitLab to configure SSL certificates. See
+[Nginx HTTPS documentation](https://docs.gitlab.com/omnibus/settings/nginx.html#enable-https)
+for details on managing SSL certificates and configuring Nginx.
+
+## Ports
+
+### Basic ports
| LB Port | Backend Port | Protocol |
| ------- | ------------ | --------------- |
@@ -16,9 +62,9 @@ you need to use with GitLab.
| 443 | 443 | TCP or HTTPS [^1] [^2] |
| 22 | 22 | TCP |
-## GitLab Pages Ports
+### GitLab Pages Ports
-If you're using GitLab Pages with custom domain support you will need some
+If you're using GitLab Pages with custom domain support you will need some
additional port configurations.
GitLab Pages requires a separate virtual IP address. Configure DNS to point the
`pages_external_url` from `/etc/gitlab/gitlab.rb` at the new virtual IP address. See the
@@ -29,7 +75,7 @@ GitLab Pages requires a separate virtual IP address. Configure DNS to point the
| 80 | Varies [^3] | HTTP |
| 443 | Varies [^3] | TCP [^4] |
-## Alternate SSH Port
+### Alternate SSH Port
Some organizations have policies against opening SSH port 22. In this case,
it may be helpful to configure an alternate SSH hostname that allows users
diff --git a/doc/administration/logs.md b/doc/administration/logs.md
index 698f4caab3a..36dee75bd44 100644
--- a/doc/administration/logs.md
+++ b/doc/administration/logs.md
@@ -23,12 +23,13 @@ requests from the API are logged to a separate file in `api_json.log`.
Each line contains a JSON line that can be ingested by Elasticsearch, Splunk, etc. For example:
```json
-{"method":"GET","path":"/gitlab/gitlab-ce/issues/1234","format":"html","controller":"Projects::IssuesController","action":"show","status":200,"duration":229.03,"view":174.07,"db":13.24,"time":"2017-08-08T20:15:54.821Z","params":[{"key":"param_key","value":"param_value"}],"remote_ip":"18.245.0.1","user_id":1,"username":"admin","gitaly_calls":76}
+{"method":"GET","path":"/gitlab/gitlab-ce/issues/1234","format":"html","controller":"Projects::IssuesController","action":"show","status":200,"duration":229.03,"view":174.07,"db":13.24,"time":"2017-08-08T20:15:54.821Z","params":[{"key":"param_key","value":"param_value"}],"remote_ip":"18.245.0.1","user_id":1,"username":"admin","gitaly_calls":76,"queue_duration": 112.47}
```
In this example, you can see this was a GET request for a specific issue. Notice each line also contains performance data:
-1. `duration`: the total time taken to retrieve the request
+1. `duration`: total time in milliseconds taken to retrieve the request
+1. `queue_duration`: total time in milliseconds that the request was queued inside GitLab Workhorse
1. `view`: total time taken inside the Rails views
1. `db`: total time to retrieve data from the database
1. `gitaly_calls`: total number of calls made to Gitaly
@@ -91,6 +92,8 @@ This entry above shows an access to an internal endpoint to check whether an
associated SSH key can download the project in question via a `git fetch` or
`git clone`. In this example, we see:
+1. `duration`: total time in milliseconds taken to retrieve the request
+1. `queue_duration`: total time in milliseconds that the request was queued inside GitLab Workhorse
1. `method`: The HTTP method used to make the request
1. `path`: The relative path of the query
1. `params`: Key-value pairs passed in a query string or HTTP body. Sensitive parameters (e.g. passwords, tokens, etc.) are filtered out.
diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md
index 10ae8c7dedf..5c809f25fbd 100644
--- a/doc/administration/pages/index.md
+++ b/doc/administration/pages/index.md
@@ -11,7 +11,7 @@ description: 'Learn how to administer GitLab Pages.'
> - This guide is for Omnibus GitLab installations. If you have installed
> GitLab from source, follow the [Pages source installation document](source.md).
> - To learn how to use GitLab Pages, read the [user documentation][pages-userguide].
-> - Does NOT support subgroups. See [this issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/30548) for more information and status.
+> - Support for subgroup project's websites was [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/30548) in GitLab 11.8.
This document describes how to set up the _latest_ GitLab Pages feature. Make
sure to read the [changelog](#changelog) if you are upgrading to a new GitLab
diff --git a/doc/administration/uploads.md b/doc/administration/uploads.md
index 476ae8e8a76..9dfe085425f 100644
--- a/doc/administration/uploads.md
+++ b/doc/administration/uploads.md
@@ -149,7 +149,7 @@ _The uploads are stored by default in
[reconfigure gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure "How to reconfigure Omnibus GitLab"
[restart gitlab]: restart_gitlab.md#installations-from-source "How to restart GitLab"
-[eep]: https://about.gitlab.com/gitlab-ee/ "GitLab Enterprise Edition Premium"
+[eep]: https://about.gitlab.com/gitlab-ee/ "GitLab Premium"
[ce]: https://about.gitlab.com/gitlab-ce/ "GitLab Community Edition"
[ee-3867]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3867
[ce-17358]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17358
diff --git a/doc/api/group_labels.md b/doc/api/group_labels.md
index d4715dec192..3d4b099b49e 100644
--- a/doc/api/group_labels.md
+++ b/doc/api/group_labels.md
@@ -28,6 +28,7 @@ Example response:
"id": 7,
"name": "bug",
"color": "#FF0000",
+ "text_color" : "#FFFFFF",
"description": null,
"open_issues_count": 0,
"closed_issues_count": 0,
@@ -38,6 +39,7 @@ Example response:
"id": 4,
"name": "feature",
"color": "#228B22",
+ "text_color" : "#FFFFFF",
"description": null,
"open_issues_count": 0,
"closed_issues_count": 0,
@@ -73,6 +75,7 @@ Example response:
"id": 9,
"name": "Feature Proposal",
"color": "#FFA500",
+ "text_color" : "#FFFFFF",
"description": "Describes new ideas",
"open_issues_count": 0,
"closed_issues_count": 0,
@@ -108,6 +111,7 @@ Example response:
"id": 9,
"name": "Feature Idea",
"color": "#FFA500",
+ "text_color" : "#FFFFFF",
"description": "Describes new ideas",
"open_issues_count": 0,
"closed_issues_count": 0,
@@ -158,6 +162,7 @@ Example response:
"id": 9,
"name": "Feature Idea",
"color": "#FFA500",
+ "text_color" : "#FFFFFF",
"description": "Describes new ideas",
"open_issues_count": 0,
"closed_issues_count": 0,
@@ -192,6 +197,7 @@ Example response:
"id": 9,
"name": "Feature Idea",
"color": "#FFA500",
+ "text_color" : "#FFFFFF",
"description": "Describes new ideas",
"open_issues_count": 0,
"closed_issues_count": 0,
diff --git a/doc/api/group_milestones.md b/doc/api/group_milestones.md
index 7be01ce9c6d..920acaf3e49 100644
--- a/doc/api/group_milestones.md
+++ b/doc/api/group_milestones.md
@@ -42,6 +42,7 @@ Example Response:
"due_date": "2013-11-29",
"start_date": "2013-11-10",
"state": "active",
+ "percentage_complete" : 66,
"updated_at": "2013-10-02T09:24:18Z",
"created_at": "2013-10-02T09:24:18Z"
}
diff --git a/doc/api/labels.md b/doc/api/labels.md
index aec1a2c7592..5b5b04bc1d7 100644
--- a/doc/api/labels.md
+++ b/doc/api/labels.md
@@ -24,6 +24,7 @@ Example response:
"id" : 1,
"name" : "bug",
"color" : "#d9534f",
+ "text_color" : "#FFFFFF",
"description": "Bug reported by user",
"open_issues_count": 1,
"closed_issues_count": 0,
@@ -34,6 +35,7 @@ Example response:
{
"id" : 4,
"color" : "#d9534f",
+ "text_color" : "#FFFFFF",
"name" : "confirmed",
"description": "Confirmed issue",
"open_issues_count": 2,
@@ -46,6 +48,7 @@ Example response:
"id" : 7,
"name" : "critical",
"color" : "#d9534f",
+ "text_color" : "#FFFFFF",
"description": "Critical issue. Need fix ASAP",
"open_issues_count": 1,
"closed_issues_count": 3,
@@ -57,6 +60,7 @@ Example response:
"id" : 8,
"name" : "documentation",
"color" : "#f0ad4e",
+ "text_color" : "#FFFFFF",
"description": "Issue about documentation",
"open_issues_count": 1,
"closed_issues_count": 0,
@@ -67,6 +71,7 @@ Example response:
{
"id" : 9,
"color" : "#5cb85c",
+ "text_color" : "#FFFFFF",
"name" : "enhancement",
"description": "Enhancement proposal",
"open_issues_count": 1,
@@ -105,6 +110,7 @@ Example response:
"id" : 10,
"name" : "feature",
"color" : "#5843AD",
+ "text_color" : "#FFFFFF",
"description":null,
"open_issues_count": 0,
"closed_issues_count": 0,
@@ -161,6 +167,7 @@ Example response:
"id" : 8,
"name" : "docs",
"color" : "#8E44AD",
+ "text_color" : "#FFFFFF",
"description": "Documentation",
"open_issues_count": 1,
"closed_issues_count": 0,
@@ -196,6 +203,7 @@ Example response:
"id" : 1,
"name" : "bug",
"color" : "#d9534f",
+ "text_color" : "#FFFFFF",
"description": "Bug reported by user",
"open_issues_count": 1,
"closed_issues_count": 0,
diff --git a/doc/api/milestones.md b/doc/api/milestones.md
index fa8f8a0bcf0..21a390442bd 100644
--- a/doc/api/milestones.md
+++ b/doc/api/milestones.md
@@ -39,6 +39,7 @@ Example Response:
"due_date": "2013-11-29",
"start_date": "2013-11-10",
"state": "active",
+ "percentage_complete" : 66,
"updated_at": "2013-10-02T09:24:18Z",
"created_at": "2013-10-02T09:24:18Z"
}
diff --git a/doc/api/releases/index.md b/doc/api/releases/index.md
index 943109a3ea9..61f4ec130fa 100644
--- a/doc/api/releases/index.md
+++ b/doc/api/releases/index.md
@@ -14,7 +14,7 @@ GET /projects/:id/releases
| Attribute | Type | Required | Description |
| ------------- | -------------- | -------- | --------------------------------------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). |
Example request:
@@ -160,7 +160,7 @@ GET /projects/:id/releases/:tag_name
| Attribute | Type | Required | Description |
| ------------- | -------------- | -------- | --------------------------------------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). |
| `tag_name` | string | yes | The tag where the release will be created from. |
Example request:
@@ -239,10 +239,10 @@ POST /projects/:id/releases
| Attribute | Type | Required | Description |
| ------------- | -------------- | -------- | --------------------------------------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). |
| `name` | string | yes | The release name. |
| `tag_name` | string | yes | The tag where the release will be created from. |
-| `description` | string | yes | The description of the release. You can use [markdown](../user/markdown.md). |
+| `description` | string | yes | The description of the release. You can use [markdown](../../user/markdown.md). |
| `ref` | string | no | If `tag_name` doesn't exist, the release will be created from `ref`. It can be a commit SHA, another tag name, or a branch name. |
| `assets:links`| array of hash | no | An array of assets links. |
| `assets:links:name`| string | no (if `assets:links` specified, it's required) | The name of the link. |
@@ -331,10 +331,10 @@ PUT /projects/:id/releases/:tag_name
| Attribute | Type | Required | Description |
| ------------- | -------------- | -------- | --------------------------------------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). |
| `tag_name` | string | yes | The tag where the release will be created from. |
| `name` | string | no | The release name. |
-| `description` | string | no | The description of the release. You can use [markdown](../user/markdown.md). |
+| `description` | string | no | The description of the release. You can use [markdown](../../user/markdown.md). |
Example request:
@@ -412,7 +412,7 @@ DELETE /projects/:id/releases/:tag_name
| Attribute | Type | Required | Description |
| ------------- | -------------- | -------- | --------------------------------------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). |
| `tag_name` | string | yes | The tag where the release will be created from. |
Example request:
diff --git a/doc/api/releases/links.md b/doc/api/releases/links.md
index ae99f3bd8b6..fd7b9d6e6e2 100644
--- a/doc/api/releases/links.md
+++ b/doc/api/releases/links.md
@@ -3,6 +3,7 @@
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/41766) in GitLab 11.7.
Using this API you can manipulate GitLab's [Release](../../user/project/releases/index.md) links. For manipulating other Release assets, see [Release API](index.md).
+GitLab supports links links to `http`, `https`, and `ftp` assets.
## Get links
diff --git a/doc/api/repositories.md b/doc/api/repositories.md
index 104c64a89ce..681dc72c934 100644
--- a/doc/api/repositories.md
+++ b/doc/api/repositories.md
@@ -119,7 +119,11 @@ would send an archive in ZIP format.
Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
-- `sha` (optional) - The commit SHA to download. A tag, branch reference or sha can be used. This defaults to the tip of the default branch if not specified
+- `sha` (optional) - The commit SHA to download. A tag, branch reference, or SHA can be used. This defaults to the tip of the default branch if not specified. For example:
+
+ ```sh
+ curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.com/api/v4/projects/<project_id>/repository/archive?sha=<commit_sha>
+ ```
## Compare branches, tags or commits
diff --git a/doc/api/services.md b/doc/api/services.md
index 2a8ce39e570..5d5aa3e5b3e 100644
--- a/doc/api/services.md
+++ b/doc/api/services.md
@@ -412,7 +412,7 @@ Google GSuite team collaboration tool.
Set Hangouts Chat service for a project.
```
-PUT /projects/:id/services/hangouts_chat
+PUT /projects/:id/services/hangouts-chat
```
>**Note:** Specific event parameters (e.g. `push_events` flag) were [introduced in v10.4][11435]
@@ -438,7 +438,7 @@ Parameters:
Delete Hangouts Chat service for a project.
```
-DELETE /projects/:id/services/hangouts_chat
+DELETE /projects/:id/services/hangouts-chat
```
### Get Hangouts Chat service settings
@@ -446,7 +446,7 @@ DELETE /projects/:id/services/hangouts_chat
Get Hangouts Chat service settings for a project.
```
-GET /projects/:id/services/hangouts_chat
+GET /projects/:id/services/hangouts-chat
```
## Irker (IRC gateway)
diff --git a/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md b/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md
index 6499413baf0..474a481836a 100644
--- a/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md
+++ b/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md
@@ -112,7 +112,7 @@ on GitLab CI/CD. To set the environment variables, navigate to your project's
![Variable Settings in GitLab](img/cloud_foundry_variables.png)
Once set up, GitLab CI/CD will deploy your app to CF at every push to your
-repository's deafult branch. To see the build logs or watch your builds running
+repository's default branch. To see the build logs or watch your builds running
live, navigate to **CI/CD > Pipelines**.
CAUTION: **Caution:**
diff --git a/doc/ci/examples/sast_docker.md b/doc/ci/examples/sast_docker.md
index 9f4a63e296d..3a657b3a3d5 100644
--- a/doc/ci/examples/sast_docker.md
+++ b/doc/ci/examples/sast_docker.md
@@ -1 +1 @@
-This document was moved to [another location](./container_scanning.md). \ No newline at end of file
+This document was moved to [another location](./container_scanning.md).
diff --git a/doc/ci/merge_request_pipelines/index.md b/doc/ci/merge_request_pipelines/index.md
index bf1e61442d4..b7b5c660586 100644
--- a/doc/ci/merge_request_pipelines/index.md
+++ b/doc/ci/merge_request_pipelines/index.md
@@ -56,6 +56,49 @@ The same tag is shown on the pipeline's details:
![Pipeline's details](img/pipeline_detail.png)
+## Excluding certain jobs
+
+The behavior of the `only: merge_requests` rule is such that _only_ jobs with
+that rule are run in the context of a merge request; no other jobs will be run.
+
+However, you may want to reverse this behaviour, having all of your jobs to run _except_
+for one or two. Consider the following pipeline, with jobs `A`, `B`, and `C`. If you want
+all pipelines to always run `A` and `B`, but only want `C` to run for a merge request,
+you can configure your `.gitlab-ci.yml` file as follows:
+
+``` yaml
+.only-default: &only-default
+ only:
+ - master
+ - merge_requests
+ - tags
+
+A:
+ <<: *only-default
+ script:
+ - ...
+
+B:
+ <<: *only-default
+ script:
+ - ...
+
+C:
+ script:
+ - ...
+ only:
+ - merge_requests
+```
+
+Since `A` and `B` are getting the `only:` rule to execute in all cases, they will
+always run. `C` specifies that it should only run for merge requests, so for any
+pipeline except a merge request pipeline, it will not run.
+
+As you can see, this will help you avoid a lot of boilerplate where you'd need
+to add that `only:` rule to all of your jobs in order to make them always run. You
+can use this for scenarios like having only pipelines with merge requests get a
+Review App set up, helping to save resources.
+
## Important notes about merge requests from forked projects
Note that the current behavior is subject to change. In the usual contribution
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 984878b6c9b..8c8a31e9323 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -423,10 +423,28 @@ connected with merge requests yet, and because GitLab is creating pipelines
before an user can create a merge request we don't know a target branch at
this point.
-Without a target branch, it is not possible to know what the common ancestor is,
-thus we always create a job in that case. This feature works best for stable
-branches like `master` because in that case GitLab uses the previous commit
-that is present in a branch to compare against the latest SHA that was pushed.
+#### Using `changes` with `merge_requests`
+
+With [pipelines for merge requests](../merge_request_pipelines/index.md),
+make it possible to define if a job should be created base on files modified
+in a merge request.
+
+For example:
+
+```
+docker build service one:
+ script: docker build -t my-service-one-image:$CI_COMMIT_REF_SLUG .
+ only:
+ refs:
+ - merge_requests
+ changes:
+ - Dockerfile
+ - service-one/**/*
+```
+
+In the scenario above, if you create or update a merge request that changes
+either files in `service-one` folder or `Dockerfile`, GitLab creates and triggers
+the `docker build service one` job.
## `tags`
diff --git a/doc/development/README.md b/doc/development/README.md
index d5829e31343..13646cbfe48 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -49,6 +49,7 @@ description: 'Learn how to contribute to GitLab.'
- [Working with the GitHub importer](github_importer.md)
- [Import/Export development documentation](import_export.md)
- [Working with Merge Request diffs](diffs.md)
+- [Kubernetes integration guidelines](kubernetes.md)
- [Permissions](permissions.md)
- [Prometheus metrics](prometheus_metrics.md)
- [Guidelines for reusing abstractions](reusing_abstractions.md)
diff --git a/doc/development/code_review.md b/doc/development/code_review.md
index 25ea2211b64..4e5fb47e331 100644
--- a/doc/development/code_review.md
+++ b/doc/development/code_review.md
@@ -23,6 +23,11 @@ one of the [Merge request coaches][team].
If you need assistance with security scans or comments, feel free to include the
Security Team (`@gitlab-com/gl-security`) in the review.
+The `danger-review` CI job will randomly pick a reviewer and a maintainer for
+each area of the codebase that your merge request seems to touch. It only makes
+recommendations - feel free to override it if you think someone else is a better
+fit!
+
Depending on the areas your merge request touches, it must be **approved** by one
or more [maintainers](https://about.gitlab.com/handbook/engineering/workflow/code-review/#maintainer):
diff --git a/doc/development/documentation/styleguide.md b/doc/development/documentation/styleguide.md
index cda66447c2c..7a3a8f25c2d 100644
--- a/doc/development/documentation/styleguide.md
+++ b/doc/development/documentation/styleguide.md
@@ -112,7 +112,7 @@ table_display_block: true
## Emphasis
- Use double asterisks (`**`) to mark a word or text in bold (`**bold**`).
-- Use undescore (`_`) for text in italics (`_italic_`).
+- Use underscore (`_`) for text in italics (`_italic_`).
- Use greater than (`>`) for blockquotes.
## Punctuation
diff --git a/doc/development/ee_features.md b/doc/development/ee_features.md
index e0985922443..3e85c0e1995 100644
--- a/doc/development/ee_features.md
+++ b/doc/development/ee_features.md
@@ -831,6 +831,29 @@ should remain working as-is when EE is running without a license.
Instead place EE specs in the `ee/spec` folder.
+### Code in `spec/factories`
+
+Use `FactoryBot.modify` to extend factories already defined in CE.
+
+Note that you cannot define new factories (even nested ones) inside the `FactoryBot.modify` block. You can do so in a
+separate `FactoryBot.define` block as shown in the example below:
+
+```ruby
+# ee/spec/factories/notes.rb
+FactoryBot.modify do
+ factory :note do
+ trait :on_epic do
+ noteable { create(:epic) }
+ project nil
+ end
+ end
+end
+
+FactoryBot.define do
+ factory :note_on_epic, parent: :note, traits: [:on_epic]
+end
+```
+
## JavaScript code in `assets/javascripts/`
To separate EE-specific JS-files we should also move the files into an `ee` folder.
diff --git a/doc/development/fe_guide/index.md b/doc/development/fe_guide/index.md
index 3a3cb77f592..86b8972a69e 100644
--- a/doc/development/fe_guide/index.md
+++ b/doc/development/fe_guide/index.md
@@ -1,7 +1,7 @@
# Frontend Development Guidelines
> **Notice:**
-We are currently in the process of re-writing our development guide to make it easier to find information. The new guide is still WIP but viewable in [development/new_fe_guide](../new_fe_guide/index.md)
+> We are currently in the process of re-writing our development guide to make it easier to find information. The new guide is still WIP but viewable in [development/new_fe_guide](../new_fe_guide/index.md)
This document describes various guidelines to ensure consistency and quality
across GitLab's frontend team.
@@ -32,32 +32,41 @@ For our currently-supported browsers, see our [requirements][requirements].
---
## [Development Process](development_process.md)
+
How we plan and execute the work on the frontend.
## [Architecture](architecture.md)
+
How we go about making fundamental design decisions in GitLab's frontend team
or make changes to our frontend development guidelines.
## [Testing](../testing_guide/frontend_testing.md)
+
How we write frontend tests, run the GitLab test suite, and debug test related
issues.
## [Design Patterns](design_patterns.md)
+
Common JavaScript design patterns in GitLab's codebase.
## [Vue.js Best Practices](vue.md)
+
Vue specific design patterns and practices.
## [Vuex](vuex.md)
+
Vuex specific design patterns and practices.
## [Axios](axios.md)
+
Axios specific practices and gotchas.
## [GraphQL](graphql.md)
+
How to use GraphQL
## [Icons and Illustrations](icons.md)
+
How we use SVG for our Icons and Illustrations.
## [Components](components.md)
@@ -70,7 +79,7 @@ How we use UI components.
### [JavaScript Style Guide](style_guide_js.md)
-We use eslint to enforce our JavaScript style guides. Our guide is based on
+We use eslint to enforce our JavaScript style guides. Our guide is based on
the excellent [Airbnb][airbnb-js-style-guide] style guide with a few small
changes.
@@ -81,23 +90,26 @@ Our SCSS conventions which are enforced through [scss-lint][scss-lint].
---
## [Performance](performance.md)
+
Best practices for monitoring and maximizing frontend performance.
---
## [Security](security.md)
+
Frontend security practices.
---
## [Accessibility](accessibility.md)
+
Our accessibility standards and resources.
## [Internationalization (i18n) and Translations](../i18n/externalization.md)
+
Frontend internationalization support is described in [this document](../i18n/).
The [externalization part of the guide](../i18n/externalization.md) explains the helpers/methods available.
-
[rails]: http://rubyonrails.org/
[haml]: http://haml.info/
[hamlit]: https://github.com/k0kubun/hamlit
@@ -116,6 +128,7 @@ The [externalization part of the guide](../i18n/externalization.md) explains the
---
## [DropLab](droplab/droplab.md)
+
Our internal `DropLab` dropdown library.
- [DropLab](droplab/droplab.md)
diff --git a/doc/development/kubernetes.md b/doc/development/kubernetes.md
new file mode 100644
index 00000000000..4b2d48903ac
--- /dev/null
+++ b/doc/development/kubernetes.md
@@ -0,0 +1,126 @@
+# Kubernetes integration - development guidelines
+
+This document provides various guidelines when developing for GitLab's
+[Kubernetes integration](../user/project/clusters/index.md).
+
+## Development
+
+### Architecture
+
+Some Kubernetes operations, such as creating restricted project
+namespaces are performed on the GitLab Rails application. These
+operations are performed using a [client library](#client-library).
+These operations will carry an element of risk as the operations will be
+run as the same user running the GitLab Rails application, see the
+[security](#security) section below.
+
+Some Kubernetes operations, such as installing cluster applications are
+performed on one-off pods on the Kubernetes cluster itself. These
+installation pods are currently named `install-<application_name>` and
+are created within the `gitlab-managed-apps` namespace.
+
+In terms of code organization, we generally add objects that represent
+Kubernetes resources in
+[`lib/gitlab/kubernetes`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/lib/gitlab/kubernetes).
+
+### Client library
+
+We use the [`kubeclient`](https://rubygems.org/gems/kubeclient) gem to
+perform Kubernetes API calls. As the `kubeclient` gem does not support
+different API Groups (e.g. `apis/rbac.authorization.k8s.io`) from a
+single client, we have created a wrapper class,
+[`Gitlab::Kubernetes::KubeClient`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/kubernetes/kube_client.rb)
+that will enable you to achieve this.
+
+Selected Kubernetes API groups are currently supported. Do add support
+for new API groups or methods to
+[`Gitlab::Kubernetes::KubeClient`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/kubernetes/kube_client.rb)
+if you need to use them. New API groups or API group versions can be
+added to `SUPPORTED_API_GROUPS` - internally, this will create an
+internal client for that group. New methods can be added as a delegation
+to the relevant internal client.
+
+### Performance considerations
+
+All calls to the Kubernetes API must be in a background process. Do not
+perform Kubernetes API calls within a web request as this will block
+unicorn and can easily lead to a Denial Of Service (DoS) attack in GitLab as
+the Kubernetes cluster response times are outside of our control.
+
+The easiest way to ensure your calls happen a background process is to
+delegate any such work to happen in a [sidekiq
+worker](sidekiq_style_guide.md).
+
+There are instances where you would like to make calls to Kubernetes and
+return the response and as such a background worker does not seem to be
+a good fit. For such cases you should make use of [reactive
+caching](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/models/concerns/reactive_caching.rb).
+For example:
+
+```ruby
+ def calculate_reactive_cache!
+ { pods: cluster.platform_kubernetes.kubeclient.get_pods }
+ end
+
+ def pods
+ with_reactive_cache do |data|
+ data[:pods]
+ end
+ end
+```
+
+### Testing
+
+We have some Webmock stubs in
+[`KubernetesHelpers`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/spec/support/helpers/kubernetes_helpers.rb)
+which can help with mocking out calls to Kubernetes API in your tests.
+
+## Security
+
+### SSRF
+
+As URLs for Kubernetes clusters are user controlled it is easily
+susceptible to Server Side Request Forgery (SSRF) attacks. You should
+understand the mitigation strategies if you are adding more API calls to
+a cluster.
+
+Mitigation strategies include:
+
+1. Not allowing redirects to attacker controller resources:
+ [`Kubeclient::KubeClient`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/kubernetes/kube_client.rb#)
+ can be configured to disallow any redirects by passing in
+ `http_max_redirects: 0` as an option.
+1. Not exposing error messages: by doing so, we
+ prevent attackers from triggering errors to expose results from
+ attacker controlled requests. For example, we do not expose (or store)
+ raw error messages:
+
+ ```ruby
+ rescue Kubernetes::HttpError => e
+ # bad
+ # app.make_errored!("Kubernetes error: #{e.message}")
+
+ # good
+ app.make_errored!("Kubernetes error: #{e.error_code}")
+ ```
+
+## Debugging
+
+Logs related to the Kubernetes integration can be found in
+[kubernetes.log](../administration/logs.md#kuberneteslog). On a local
+GDK install, this will be present in `log/kubernetes.log`.
+
+Some services such as
+[`Clusters::Applications::InstallService`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/services/clusters/applications/install_service.rb#L18)
+rescues `StandardError` which can make it harder to debug issues in an
+development environment. The current workaround is to temporarily
+comment out the `rescue` in your local development source.
+
+You can also follow the installation pod logs to debug issues related to
+installation. Once the installation/upgrade is underway, wait for the
+pod to be created. Then run the following to obtain the pods logs as
+they are written:
+
+```bash
+kubectl logs <pod_name> --follow -n gitlab-managed-apps
+```
diff --git a/doc/development/new_fe_guide/event_tracking.md b/doc/development/new_fe_guide/event_tracking.md
new file mode 100644
index 00000000000..1958f1ce528
--- /dev/null
+++ b/doc/development/new_fe_guide/event_tracking.md
@@ -0,0 +1,74 @@
+# Event Tracking
+
+We use [Snowplow](https://github.com/snowplow/snowplow) for tracking custom events.
+
+## Generic tracking function
+
+In addition to Snowplow's built-in method for tracking page views, we use a generic tracking function which enables us to selectively apply listeners to events.
+
+The generic tracking function can be imported in EE-specific JS files as follows:
+
+```javascript
+import { trackEvent } from `ee/stats`;
+```
+
+This gives the user access to the `trackEvent` method, which takes the following parameters:
+
+| parameter | type | description | required |
+| ---------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
+| `category` | string | Describes the page that you're capturing click events on. Unless infeasible, please use the Rails page attribute `document.body.dataset.page` by default. | true |
+| `eventName` | string | Describes the action the user is taking. The first word should always describe the action. For example, clicks should be `click` and activations should be `activate`. Use underscores to describe what was acted on. For example, activating a form field would be `activate_form_input`. Clicking on a dropdown is `click_dropdown`. | true |
+| `additionalData` | object | Additional data such as `label`, `property`, and `value` as described [in our Feature Instrumentation taxonomy](https://about.gitlab.com/handbook/product/feature-instrumentation/#taxonomy). | false |
+
+Read more about instrumentation and the taxonomy in the [Product Handbook](https://about.gitlab.com/handbook/product/feature-instrumentation).
+
+### Tracking in `.js` and `.vue` files
+
+The most simple use case is to add tracking programmatically to an event of interest in Javascript.
+
+The following example demonstrates how to track a click on a button in Javascript by calling the `trackEvent` method explicitly:
+
+```javascript
+import { trackEvent } from `ee/stats`;
+
+trackEvent('dashboard:projects:index', 'click_button', {
+ label: 'create_from_template',
+ property: 'template_preview',
+ value: 'rails',
+});
+```
+
+### Tracking in HAML templates
+
+Sometimes we want to track clicks for multiple elements on a page. Creating event handlers for all elements could soon turn into a tedious task.
+
+There's a more convenient solution to this problem. When working with HAML templates, we can add `data-track-*` attributes to elements of interest. This way, all elements that have both `data-track-label` and `data-track-event` attributes assigned get marked for event tracking. All we have to do is call the `bindTrackableContainer` method on a container which allows for better scoping.
+
+Below is an example of `data-track-*` attributes assigned to a button in HAML:
+
+```ruby
+%button.btn{ data: { track_label: "create_from_template", track_property: "template_preview", track_event: "click_button", track_value: "my-template" } }
+```
+
+By calling `bindTrackableContainer('.my-container')`, click handlers get bound to all elements located in `.my-container` provided that they have the necessary `data-track-*` attributes assigned to them.
+
+```javascript
+import Stats from 'ee/stats';
+
+document.addEventListener('DOMContentLoaded', () => {
+ Stats.bindTrackableContainer('.my-container', 'category');
+});
+```
+
+The second parameter in `bindTrackableContainer` is optional. If omitted, the value of `document.body.dataset.page` will be used as category instead.
+
+Below is a list of supported `data-track-*` attributes:
+
+| attribute | description | required |
+| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
+| `data-track-label` | The `label` in `trackEvent` | true |
+| `data-track-event` | The `eventName` in `trackEvent` | true |
+| `data-track-property` | The `property` in `trackEvent`. If omitted, an empty string will be used as a default value. | false |
+| `data-track-value` | The `value` in `trackEvent`. If omitted, this will be `target.value` or empty string. For checkboxes, the default value being tracked will be the element's checked attribute if `data-track-value` is omitted. | false |
+
+Since Snowplow is an Enterprise Edition feature, it's necessary to create a CE backport when adding `data-track-*` attributes to HAML templates in most cases.
diff --git a/doc/development/new_fe_guide/index.md b/doc/development/new_fe_guide/index.md
index bfcca9cec7b..5fd5af252ef 100644
--- a/doc/development/new_fe_guide/index.md
+++ b/doc/development/new_fe_guide/index.md
@@ -27,6 +27,10 @@ Learn about all the internal JavaScript modules that make up our frontend.
Style guides to keep our code consistent.
+## [Event Tracking with Snowplow](event_tracking.md)
+
+How we use Snowplow to track custom events.
+
## [Tips](tips.md)
Tips from our frontend team to develop more efficiently and effectively.
diff --git a/doc/development/testing_guide/ci.md b/doc/development/testing_guide/ci.md
index d685cacf9ea..5aa668290b4 100644
--- a/doc/development/testing_guide/ci.md
+++ b/doc/development/testing_guide/ci.md
@@ -24,7 +24,7 @@ Our current CI parallelization setup is as follows:
uploaded to S3.
After that, the next pipeline will use the up-to-date
-`knapsack/${CI_PROJECT_NAME}/rspec_report-master.json` file.
+`knapsack/${CI_PROJECT_NAME}/rspec_report-master.json` file.
### Monitoring
diff --git a/doc/development/testing_guide/review_apps.md b/doc/development/testing_guide/review_apps.md
index 3af97717775..85b47f88cc4 100644
--- a/doc/development/testing_guide/review_apps.md
+++ b/doc/development/testing_guide/review_apps.md
@@ -143,17 +143,21 @@ thousands of unused Docker images.**
**How big are the Kubernetes clusters (`review-apps-ce` and `review-apps-ee`)?**
> The clusters are currently set up with a single pool of preemptible nodes,
- with a minimum of 1 node and a maximum of 100 nodes.
+ with a minimum of 1 node and a maximum of 50 nodes.
**What are the machine running on the cluster?**
- > We're currently using `n1-standard-4` (4 vCPUs, 15 GB memory) machines.
+ > We're currently using `n1-standard-16` (16 vCPUs, 60 GB memory) machines.
**How do we secure this from abuse? Apps are open to the world so we need to
find a way to limit it to only us.**
> This isn't enabled for forks.
+## Other resources
+
+* [Review Apps integration for CE/EE (presentation)](https://docs.google.com/presentation/d/1QPLr6FO4LduROU8pQIPkX1yfGvD13GEJIBOenqoKxR8/edit?usp=sharing)
+
[charts-1068]: https://gitlab.com/charts/gitlab/issues/1068
[gitlab-pipeline]: https://gitlab.com/gitlab-org/gitlab-ce/pipelines/44362587
[gitlab:assets:compile]: https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/149511610
diff --git a/doc/install/README.md b/doc/install/README.md
index ae48306e65e..52011526768 100644
--- a/doc/install/README.md
+++ b/doc/install/README.md
@@ -3,7 +3,7 @@ comments: false
description: Read through the GitLab installation methods.
---
-# Installation
+# Installation **[CORE ONLY]**
GitLab can be installed in most GNU/Linux distributions and in a number
of cloud providers. To get the best experience from GitLab you need to balance
diff --git a/doc/install/installation.md b/doc/install/installation.md
index a8064ae046e..fb24d4fa0ef 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -108,7 +108,7 @@ sudo apt-get install -y libcurl4-openssl-dev libexpat1-dev gettext libz-dev libs
# Download and compile from source
cd /tmp
-curl --remote-name --progress https://www.kernel.org/pub/software/scm/git/git-2.18.0.tar.gz
+curl --remote-name --location --progress https://www.kernel.org/pub/software/scm/git/git-2.18.0.tar.gz
echo '94faf2c0b02a7920b0b46f4961d8e9cad08e81418614102898a55f980fa3e7e4 git-2.18.0.tar.gz' | shasum -a256 -c - && tar -xzf git-2.18.0.tar.gz
cd git-2.18.0/
./configure
diff --git a/doc/integration/github.md b/doc/integration/github.md
index eca9aa16499..9bb3579dd84 100644
--- a/doc/integration/github.md
+++ b/doc/integration/github.md
@@ -21,10 +21,10 @@ To get the credentials (a pair of Client ID and Client Secret), you must registe
- Application name: This can be anything. Consider something like `<Organization>'s GitLab` or `<Your Name>'s GitLab` or something else descriptive.
- Homepage URL: the URL to your GitLab installation. e.g., `https://gitlab.company.com`
- Application description: Fill this in if you wish.
- - Authorization callback URL: `http(s)://${YOUR_DOMAIN}/users/auth`. Please make sure the port is included if your GitLab instance is not configured on default port.
+ - Authorization callback URL: `http(s)://${YOUR_DOMAIN}/users/auth/github/callback`. Please make sure the port is included if your GitLab instance is not configured on default port.
![Register OAuth App](img/github_register_app.png)
- NOTE: Be sure to append `/users/auth` to the end of the callback URL
+ NOTE: Be sure to append `/users/auth/github/callback` to the end of the callback URL
to prevent a [OAuth2 convert
redirect](http://tetraph.com/covert_redirect/) vulnerability.
diff --git a/doc/intro/README.md b/doc/intro/README.md
index d9acc5bdeac..9bc7c3d9ea4 100644
--- a/doc/intro/README.md
+++ b/doc/intro/README.md
@@ -9,13 +9,13 @@ comments: false
Create projects and groups.
- [Create a new project](../gitlab-basics/create-project.md)
-- [Create a new group](../gitlab-basics/create-group.md)
+- [Create a new group](../user/group/index.md#create-a-new-group)
## Prioritize
Create issues, labels, milestones, cast your vote, and review issues.
-- [Create a new issue](../user/project/issues/index.md#new-issue)
+- [Create an issue](../user/project/issues/create_new_issue.md)
- [Assign labels to issues](../user/project/labels.md)
- [Use milestones as an overview of your project's tracker](../user/project/milestones/index.md)
- [Use voting to express your like/dislike to issues and merge requests](../workflow/award_emoji.md)
diff --git a/doc/security/README.md b/doc/security/README.md
index e22dc00759d..a90127e0356 100644
--- a/doc/security/README.md
+++ b/doc/security/README.md
@@ -15,3 +15,4 @@ comments: false
- [How we manage the CRIME vulnerability](crime_vulnerability.md)
- [Enforce Two-factor authentication](two_factor_authentication.md)
- [Send email confirmation on sign-up](user_email_confirmation.md)
+- [Security of running jobs](https://docs.gitlab.com/runner/security/)
diff --git a/doc/ssh/README.md b/doc/ssh/README.md
index 1b53be15b44..9c4a391e8da 100644
--- a/doc/ssh/README.md
+++ b/doc/ssh/README.md
@@ -247,6 +247,17 @@ Public SSH keys need to be unique to GitLab, as they will bind to your account.
Your SSH key is the only identifier you'll have when pushing code via SSH,
that's why it needs to uniquely map to a single user.
+## Per-repository SSH keys
+
+If you want to use different keys depending on the repository you are working
+on, you can issue the following command while inside your repository:
+
+```sh
+git config core.sshCommand "ssh -o IdentitiesOnly=yes -i ~/.ssh/private-key-filename-for-this-repository -F /dev/null"
+```
+
+This will not use the SSH Agent and requires at least Git 2.10.
+
## Deploy keys
### Per-repository deploy keys
diff --git a/doc/system_hooks/system_hooks.md b/doc/system_hooks/system_hooks.md
index 6d63906ea4d..55b678d6af5 100644
--- a/doc/system_hooks/system_hooks.md
+++ b/doc/system_hooks/system_hooks.md
@@ -138,7 +138,7 @@ Please refer to `group_rename` and `user_rename` for that case.
"created_at": "2012-07-21T07:30:56Z",
"updated_at": "2012-07-21T07:38:22Z",
"event_name": "user_add_to_team",
- "project_access": "Maintainer",
+ "access_level": "Maintainer",
"project_id": 74,
"project_name": "StoreCloud",
"project_path": "storecloud",
diff --git a/doc/topics/authentication/index.md b/doc/topics/authentication/index.md
index a354d3e7884..df6897002c9 100644
--- a/doc/topics/authentication/index.md
+++ b/doc/topics/authentication/index.md
@@ -21,7 +21,7 @@ This page gathers all the resources for the topic **Authentication** within GitL
- [Enforce Two-factor Authentication (2FA)](../../security/two_factor_authentication.md#enforce-two-factor-authentication-2fa)
- **Articles:**
- [How to Configure LDAP with GitLab CE](../../administration/auth/how_to_configure_ldap_gitlab_ce/index.md)
- - [How to Configure LDAP with GitLab EE](https://docs.gitlab.com/ee/articles/how_to_configure_ldap_gitlab_ee/)
+ - [How to Configure LDAP with GitLab EE](https://docs.gitlab.com/ee/administration/auth/how_to_configure_ldap_gitlab_ee/index.md)
- [Feature Highlight: LDAP Integration](https://about.gitlab.com/2014/07/10/feature-highlight-ldap-sync/)
- [Debugging LDAP](https://about.gitlab.com/handbook/support/workflows/support-engineering/ldap/debugging_ldap.html)
- **Integrations:**
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index 91be3e3d45d..b72138ef24f 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -58,15 +58,15 @@ project in a simple and automatic way:
1. [Auto Build](#auto-build)
1. [Auto Test](#auto-test)
-1. [Auto Code Quality](#auto-code-quality) **[STARTER]**
-1. [Auto SAST (Static Application Security Testing)](#auto-sast) **[ULTIMATE]**
-1. [Auto Dependency Scanning](#auto-dependency-scanning) **[ULTIMATE]**
-1. [Auto License Management](#auto-license-management) **[ULTIMATE]**
+1. [Auto Code Quality](#auto-code-quality-starter) **[STARTER]**
+1. [Auto SAST (Static Application Security Testing)](#auto-sast-ultimate) **[ULTIMATE]**
+1. [Auto Dependency Scanning](#auto-dependency-scanning-ultimate) **[ULTIMATE]**
+1. [Auto License Management](#auto-license-management-ultimate) **[ULTIMATE]**
1. [Auto Container Scanning](#auto-container-scanning)
1. [Auto Review Apps](#auto-review-apps)
-1. [Auto DAST (Dynamic Application Security Testing)](#auto-dast) **[ULTIMATE]**
+1. [Auto DAST (Dynamic Application Security Testing)](#auto-dast-ultimate) **[ULTIMATE]**
1. [Auto Deploy](#auto-deploy)
-1. [Auto Browser Performance Testing](#auto-browser-performance-testing) **[PREMIUM]**
+1. [Auto Browser Performance Testing](#auto-browser-performance-testing-premium) **[PREMIUM]**
1. [Auto Monitoring](#auto-monitoring)
As Auto DevOps relies on many different components, it's good to have a basic
diff --git a/doc/update/README.md b/doc/update/README.md
index d098dc99518..e2fffadb1ea 100644
--- a/doc/update/README.md
+++ b/doc/update/README.md
@@ -5,15 +5,15 @@ update guides.
There are currently 3 official ways to install GitLab:
-- Omnibus packages
-- Source installation
-- Docker installation
+- [Omnibus packages](#omnibus-packages)
+- [Source installation](#installation-from-source)
+- [Docker installation](#installation-using-docker)
Based on your installation, choose a section below that fits your needs.
## Omnibus Packages
-- The [Omnibus update guide](http://docs.gitlab.com/omnibus/update/README.html)
+- The [Omnibus update guide][omni-update]
contains the steps needed to update an Omnibus GitLab package.
## Installation from source
@@ -36,7 +36,7 @@ can still be found in the Git repository:
GitLab provides official Docker images for both Community and Enterprise
editions. They are based on the Omnibus package and instructions on how to
-update them are in [a separate document][omnidocker].
+update them are in [a separate document][omni-docker].
## Upgrading without downtime
@@ -103,6 +103,10 @@ migrations this could potentially lead to hours of downtime, depending on the
size of your database. To work around this you will have to use PostgreSQL and
meet the other online upgrade requirements mentioned above.
+### Steps
+
+Steps to [upgrade without downtime][omni-zero-downtime].
+
## Upgrading between editions
GitLab comes in two flavors: [Community Edition][ce] which is MIT licensed,
@@ -150,3 +154,6 @@ possible.
[ce]: https://about.gitlab.com/features/#community
[ee]: https://about.gitlab.com/features/#enterprise
[omni-ce-ee]: https://docs.gitlab.com/omnibus/update/README.html#updating-community-edition-to-enterprise-edition
+[omni-docker]: https://docs.gitlab.com/omnibus/docker/README.html
+[omni-update]: https://docs.gitlab.com/omnibus/update/README.html
+[omni-zero-downtime]: https://docs.gitlab.com/omnibus/update/README.html#zero-downtime-updates
diff --git a/doc/user/admin_area/settings/third_party_offers.md b/doc/user/admin_area/settings/third_party_offers.md
index 177251b52b9..23311801790 100644
--- a/doc/user/admin_area/settings/third_party_offers.md
+++ b/doc/user/admin_area/settings/third_party_offers.md
@@ -3,4 +3,7 @@
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20379)
> in [GitLab Core](https://about.gitlab.com/pricing/) 11.1
-The display of third party offers can be controlled in the Admin Area -> Settings page.
+Within GitLab, we inform users of available third-party offers they might find valuable in order to enhance the development of their projects.
+An example is the Google Cloud Platform free credit for using [Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine/).
+
+The display of third-party offers can be toggled in the Admin area on the Settings page.
diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md
index 83bc79925e1..0a7c5832a2d 100644
--- a/doc/user/profile/account/two_factor_authentication.md
+++ b/doc/user/profile/account/two_factor_authentication.md
@@ -18,7 +18,7 @@ the second factor of authentication. Once enabled, in addition to supplying your
password to login, you'll be prompted to activate your U2F device (usually by pressing
a button on it), and it will perform secure authentication on your behalf.
-The U2F workflow is only supported by Google Chrome at this point, so we _strongly_ recommend
+The U2F workflow is only [supported by](https://caniuse.com/#search=U2F) Google Chrome, Opera and Firefox at this point, so we _strongly_ recommend
that you set up both methods of two-factor authentication, so you can still access your account
from other browsers.
diff --git a/doc/user/profile/preferences.md b/doc/user/profile/preferences.md
index 363d3db8db1..7387d1810ca 100644
--- a/doc/user/profile/preferences.md
+++ b/doc/user/profile/preferences.md
@@ -57,7 +57,10 @@ and default views of your dashboard and the projects' landing pages.
### Layout width
GitLab can be set up to use different widths depending on your liking. Choose
-between the fixed (max. 1200px) and the fluid (100%) application layout.
+between the fixed (max. `1280px`) and the fluid (`100%`) application layout.
+
+NOTE: **Note:**
+While `1280px` is the standard max width when using fixed layout, some pages still use 100% width, depending on the content.
### Default dashboard
diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md
index a4698fd172a..ed289b0c4eb 100644
--- a/doc/user/project/integrations/prometheus.md
+++ b/doc/user/project/integrations/prometheus.md
@@ -89,7 +89,7 @@ to integrate with.
Once configured, GitLab will attempt to retrieve performance metrics for any
environment which has had a successful deployment.
-GitLab will automatically scan the Prometheus server for metrics from known serves like Kubernetes and NGINX, and attempt to identify individual environment. The supported metrics and scan process is detailed in our [Prometheus Metrics Library documentation](prometheus_library/index.md).
+GitLab will automatically scan the Prometheus server for metrics from known servers like Kubernetes and NGINX, and attempt to identify individual environment. The supported metrics and scan process is detailed in our [Prometheus Metrics Library documentation](prometheus_library/index.md).
You can view the performance dashboard for an environment by [clicking on the monitoring button](../../../ci/environments.md#monitoring-environments).
diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md
index 4d1d95da6f0..60a4c6aaf64 100644
--- a/doc/user/project/integrations/webhooks.md
+++ b/doc/user/project/integrations/webhooks.md
@@ -22,12 +22,37 @@ a new issue is created. You can configure webhooks to listen for specific events
like pushes, issues or merge requests. GitLab will send a POST request with data
to the webhook URL.
+In most cases, you'll need to set up your own [webhook receiver](#example-webhook-receiver)
+to receive information from GitLab, and send it to another app, according to your needs.
+We already have a [built-in receiver](http://docs.gitlab.com/ce/project_services/slack.html)
+for sending [Slack](https://api.slack.com/incoming-webhooks) notifications _per project_.
+
+## Overview
+
+[Webhooks](https://en.wikipedia.org/wiki/Webhook) are "_user-defined HTTP
+callbacks_". They are usually triggered by some
+event, such as pushing code to a repository or a comment being posted to a blog.
+When that event occurs, the source app makes an HTTP request to the URI
+configured for the webhook. The action taken may be anything.
+Common uses are to trigger builds with continuous integration systems or to
+notify bug tracking systems.
+
Webhooks can be used to update an external issue tracker, trigger CI jobs,
update a backup mirror, or even deploy to your production server.
+They are available **per project** for GitLab Community Edition,
+and **per project and per group** for **GitLab Enterprise Edition**.
Navigate to the webhooks page by going to your project's
**Settings âž” Integrations**.
+## Use-cases
+
+- You can set up a webhook in GitLab to send a notification to
+[Slack](https://api.slack.com/incoming-webhooks) every time a build fails, for example
+- You can [integrate with Twilio to be notified via SMS](https://www.datadoghq.com/blog/send-alerts-sms-customizable-webhooks-twilio/)
+every time an issue is created for a specific project or group within GitLab
+- You can use them to [automatically assign labels to merge requests](https://about.gitlab.com/2016/08/19/applying-gitlab-labels-automatically/).
+
## Webhook endpoint tips
If you are writing your own endpoint (web server) that will receive
diff --git a/doc/user/project/issues/issues_functionalities.md b/doc/user/project/issues/issues_functionalities.md
index d78721f8658..e4a3ff52e07 100644
--- a/doc/user/project/issues/issues_functionalities.md
+++ b/doc/user/project/issues/issues_functionalities.md
@@ -4,7 +4,7 @@ Please read through the [GitLab Issue Documentation](index.md) for an overview o
## Issues Functionalities
-The image bellow illustrates how an issue looks like:
+The image below illustrates what an issue looks like:
![Issue view](img/issues_main_view_numbered.jpg)
@@ -13,7 +13,7 @@ You can find all the information on that issue on one screen.
### Issue screen
An issue starts with its status (open or closed), followed by its author,
-and includes many other functionalities, numbered on the image above to
+and includes many other functionalities, numbered in the image above to
explain what they mean, one by one.
Many of the elements of the issue screen refresh automatically, such as the title and description, when they are changed by another user.
@@ -27,7 +27,7 @@ Comments and system notes also appear automatically in response to various actio
#### 2. Todos
-- Add todo: add that issue to your [GitLab Todo](../../../workflow/todos.html) list
+- Add todo: add that issue to your [GitLab Todo](../../../workflow/todos.md) list
- Mark todo as done: mark that issue as done (reflects on the Todo list)
#### 3. Assignee
@@ -43,14 +43,14 @@ assigned to them if they created the issue themselves.
##### 3.1. Multiple Assignees **[STARTER]**
-Often multiple people likely work on the same issue together,
-which can especially be difficult to track in large teams
+Often multiple people work on the same issue together,
+which can be especially difficult to track in large teams
where there is shared ownership of an issue.
-In [GitLab Starter](https://about.gitlab.com/pricing/), you can also
-select multiple assignees to an issue.
+In [GitLab Starter](https://about.gitlab.com/pricing/), you can
+assign multiple people to an issue.
-Learn more on the [Multiple Assignees documentation](https://docs.gitlab.com/ee/user/project/issues/multiple_assignees_for_issues.html).
+Learn more in the [Multiple Assignees documentation](https://docs.gitlab.com/ee/user/project/issues/multiple_assignees_for_issues.html).
#### 4. Milestone
@@ -58,40 +58,39 @@ Learn more on the [Multiple Assignees documentation](https://docs.gitlab.com/ee/
#### 5. Time Tracking
-- Estimate time: add an estimate time in which the issue will be implemented
-- Spend: add the time spent on the implementation of that issue
+- Estimate time: add an estimate of the time it will take to resolve the issue.
+- Spend: add the time spent on the resolution of the issue
> **Note:**
Both estimate and spend times are set via [GitLab Quick Actions](../quick_actions.md).
-Learn more on the [Time Tracking documentation](../../../workflow/time_tracking.md).
+Learn more in the [Time Tracking documentation](../../../workflow/time_tracking.md).
#### 6. Due date
-When you work on a tight schedule, and it's important to
-have a way to set up a deadline for implementations and for solving
-problems. This can be facilitated by the [due date](due_dates.md)). Due dates
+When you work on a tight schedule, it's important to
+have a way to set a deadline for implementations and for solving
+problems. This can be done in the [due date](due_dates.md) element. Due dates
can be changed as many times as needed.
#### 7. Labels
Categorize issues by giving them [labels](../labels.md). They help to
-organize team's workflows, once they enable you to work with the
-[GitLab Issue Board](index.md#gitlab-issue-board).
+organize workflows, and they enable you to work with the
+[GitLab Issue Board](index.md#issue-board).
-Group Labels, which allow you to use the same labels per
+Group Labels, which allow you to use the same labels for a
group of projects, can be also given to issues. They work exactly the same,
but they are immediately available to all projects in the group.
> **Tip:**
-if the label doesn't exist yet, when you click **Edit**, it opens a dropdown menu from which you can select **Create new label**.
+If a label doesn't exist yet, you can click **Edit**, and it opens a dropdown menu from which you can select **Create new label**.
#### 8. Weight **[STARTER]**
-- Attribute a weight (in a 0 to 9 range) to that issue. Easy to complete
-should weight 1 and very hard to complete should weight 9.
+- Assign a weight. Larger values are used to indicate more effort is required to complete the issue. Only positive values or zero are allowed.
-Learn more on the [Issue Weight documentation](https://docs.gitlab.com/ee/workflow/issue_weight.html).
+Learn more in the [Issue Weight documentation](https://docs.gitlab.com/ee/workflow/issue_weight.html).
#### 9. Participants
@@ -102,32 +101,33 @@ Learn more on the [Issue Weight documentation](https://docs.gitlab.com/ee/workfl
- Subscribe: if you are not a participant of the discussion on that issue, but
want to receive notifications on each new input, subscribe to it.
- Unsubscribe: if you are receiving notifications on that issue but no
-longer want to receive them, unsubscribe to it.
+longer want to receive them, unsubscribe from it.
-Read more on the [notifications documentation](../../../workflow/notifications.md#issue-merge-request-events).
+Read more in the [notifications documentation](../../../workflow/notifications.md#issue--merge-request-events).
#### 11. Reference
-- A quick "copy to clipboard" button to that issue's reference, `foo/bar#xxx`, where `foo` is the `username` or `groupname`, `bar`
+- A quick "copy to clipboard" button for that issue's reference, `foo/bar#xxx`, where `foo` is the `username` or `groupname`, `bar`
is the `project-name`, and `xxx` is the issue number.
#### 12. Title and description
-- Title: a plain text title describing the issue's subject.
-- Description: a text field which fully supports [GitLab Flavored Markdown](../../markdown.md#gitlab-flavored-markdown-gfm).
+- Title: a plain text title for describing the subject of the issue.
+- Description: a large text field which fully supports [GitLab Flavored Markdown](../../markdown.md#gitlab-flavored-markdown-gfm),
+ to describe all the details of the issue.
-#### 13. @mentions
+#### 13. Mentions
-- You can either `@mention` a user or a group present in your
- GitLab instance and they will be notified via todos and email, unless that
- person has disabled all notifications in their profile settings.
-- Mentions for yourself (the current logged in user),will be distinctly highlighted
+- You can mention a user or a group present in your GitLab instance with
+ `@username` or `@groupname` and they will be notified via todos and email, unless
+ they have disabled all notifications in their profile settings.
+- Mentions for yourself (the current logged in user), will be highlighted
in a different color, allowing you to easily see which comments involve you,
helping you focus on them quickly.
-To change your [notification settings](../../../workflow/notifications.md) navigate to
+To change your [notification settings](../../../workflow/notifications.md), navigate to
**Profile Settings** > **Notifications** > **Global notification level**
-and choose your preferences from the dropdown menu.
+and choose your preference from the dropdown menu.
> **Tip:**
Avoid mentioning `@all` in issues and merge requests,
@@ -138,14 +138,14 @@ interpreted as spam.
#### 14. Related Merge Requests
- Any merge requests mentioned in that issue's description
-or in the issue thread.
+or in the issue discussion thread.
#### 15. Award emoji
- Award an emoji to that issue.
> **Tip:**
-Posting "+1" as comments in threads spam all
+Posting "+1" as a comment in a thread spams all subscribed
participants of that issue. Awarding an emoji is a way to let them
know you like it without spamming them.
@@ -157,7 +157,7 @@ These text fields also fully support
#### 17. Comment, start a discussion, or comment and close
-Once you wrote your comment, you can either:
+Once you write a comment, you can either:
- Click "Comment" and your comment will be published.
- Click "Start discussion": start a thread within that issue's thread to discuss specific points.
@@ -167,6 +167,6 @@ Once you wrote your comment, you can either:
- Create a new merge request (with a new source branch named after the issue) in one action.
The merge request will automatically inherit the milestone and labels of the issue. The merge
-request will automatically close that issue as soon as merged.
+request will automatically close that issue when it is merged.
- Optionally, you can just create a [new branch](../repository/web_editor.md#create-a-new-branch-from-an-issue)
named after that issue.
diff --git a/doc/user/project/milestones/index.md b/doc/user/project/milestones/index.md
index ac58a0b5c18..e6033ca8655 100644
--- a/doc/user/project/milestones/index.md
+++ b/doc/user/project/milestones/index.md
@@ -28,8 +28,9 @@ the milestone to the issue.
## Project milestones and group milestones
-- **Project milestones** can be assigned to issues or merge requests in that project only.
-- **Group milestones** can be assigned to any issue or merge request of any project in that group.
+- **Project milestones** can be assigned to issues or merge requests in that project only. Navigate to **Issues > Milestones** in a project to view the project milestone list.
+- **Group milestones** can be assigned to any issue or merge request of any project in that group. Navigate to **Issues > Milestones** in a group to view the group milestone list.
+- All milestones you have access to can also be viewed in the dashboard milestones list. Click on **Milestones** on the top navigation bar to view both project milestones and group milestones you have access to.
## Creating milestones
diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md
index 11f6165fcb4..2de3fb7e080 100644
--- a/doc/user/project/pages/index.md
+++ b/doc/user/project/pages/index.md
@@ -66,7 +66,7 @@ publish any website written directly in plain HTML, CSS, and JavaScript.</p>
If you're using GitLab.com, your website will be publicly available to the internet.
If you're using self-managed instances (Core, Starter, Premium, or Ultimate),
your websites will be published on your own server, according to the
-[Pages admin settings](../../../administration/pages/index.md) chosen by your sysdamin,
+[Pages admin settings](../../../administration/pages/index.md) chosen by your sysadmin,
who can opt for making them public or internal to your server.
### How it works
diff --git a/doc/user/project/pipelines/job_artifacts.md b/doc/user/project/pipelines/job_artifacts.md
index 0d0575b1ab4..4ab66063dcf 100644
--- a/doc/user/project/pipelines/job_artifacts.md
+++ b/doc/user/project/pipelines/job_artifacts.md
@@ -191,5 +191,9 @@ artifacts and the job's trace.
1. Click the trash icon at the top right of the job's trace.
1. Confirm the deletion.
+## Retrieve artifacts of private projects when using GitLab CI
+
+In order to retrieve a job artifact of a different project, you might need to use a private token in order to [authenticate and download](../../../api/jobs.md#get-job-artifacts) the artifacts.
+
[expiry date]: ../../../ci/yaml/README.md#artifacts-expire_in
-[ce-14399]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14399
+[ce-14399]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14399 \ No newline at end of file
diff --git a/doc/user/project/pipelines/settings.md b/doc/user/project/pipelines/settings.md
index bb9b4238ee9..cce330aecc7 100644
--- a/doc/user/project/pipelines/settings.md
+++ b/doc/user/project/pipelines/settings.md
@@ -23,6 +23,7 @@ in `.gitlab-ci.yml`.
## Timeout
Timeout defines the maximum amount of time in minutes that a job is able run.
+This is configureable under your project's **Settings > CI/CD > General pipelines settings**.
The default value is 60 minutes. Decrease the time limit if you want to impose
a hard limit on your jobs' running time or increase it otherwise. In any case,
if the job surpasses the threshold, it is marked as failed.
diff --git a/doc/user/project/repository/web_editor.md b/doc/user/project/repository/web_editor.md
index 035028c9266..57e8437697b 100644
--- a/doc/user/project/repository/web_editor.md
+++ b/doc/user/project/repository/web_editor.md
@@ -33,13 +33,13 @@ easy for you.
![First file for your project](img/web_editor_template_dropdown_first_file.png)
-When clicking on either `LICENSE` or `.gitignore`, a dropdown will be displayed
+When clicking on either `LICENSE` or `.gitignore`, etc., a dropdown will be displayed
to provide you with a template which might be suitable for your project.
![MIT license selected](img/web_editor_template_dropdown_mit_license.png)
The license, changelog, contribution guide, or `.gitlab-ci.yml` file could also
-be added through a button on the project page. In the example below the license
+be added through a button on the project page. In the example below, the license
has already been created, which creates a link to the license itself.
![New file button](img/web_editor_template_dropdown_buttons.png)
diff --git a/doc/user/project/web_ide/index.md b/doc/user/project/web_ide/index.md
index 55e53b865af..3e85e97d7a5 100644
--- a/doc/user/project/web_ide/index.md
+++ b/doc/user/project/web_ide/index.md
@@ -3,8 +3,8 @@
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/4539) in [GitLab Ultimate][ee] 10.4.
> [Brought to GitLab Core](https://gitlab.com/gitlab-org/gitlab-ce/issues/44157) in 10.7.
-The Web IDE makes it faster and easier to contribute changes to your projects
-by providing an advanced editor with commit staging.
+The Web IDE editor makes it faster and easier to contribute changes to your
+projects by providing an advanced editor with commit staging.
## Open the Web IDE
@@ -22,7 +22,7 @@ searching. The file finder is launched using the keyboard shortcut `Command-p`,
`Control-p`, or `t` (when editor is not in focus). Type the filename or
file path fragments to start seeing results.
-## Syntax highligting
+## Syntax highlighting
As expected from an IDE, syntax highlighting for many languages within
the Web IDE will make your direct editing even easier.
@@ -47,10 +47,10 @@ Single file editing is based on the [Ace Editor](https://ace.c9.io).
After making your changes, click the Commit button in the bottom left to
review the list of changed files. Click on each file to review the changes and
-click the tick icon to stage the file.
+click the tick icon to stage the file.
Once you have staged some changes, you can add a commit message and commit the
-staged changes. Unstaged changes will not be commited.
+staged changes. Unstaged changes will not be committed.
![Commit changes](img/commit_changes.png)
@@ -80,7 +80,7 @@ left.
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/19318) in [GitLab Core][ce] 11.0.
Switching between your authored and assigned merge requests can be done without
-leaving the Web IDE. Click the dropdown in the top of the sidebar to open a list
+leaving the Web IDE. Click the dropdown in the top of the sidebar to open a list
of merge requests. You will need to commit or discard all your changes before
switching to a different merge request.
@@ -89,7 +89,7 @@ switching to a different merge request.
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20850) in [GitLab Core][ce] 11.2.
Switching between branches of the current project repository can be done without
-leaving the Web IDE. Click the dropdown in the top of the sidebar to open a list
+leaving the Web IDE. Click the dropdown in the top of the sidebar to open a list
of branches. You will need to commit or discard all your changes before
switching to a different branch.
diff --git a/doc/user/reserved_names.md b/doc/user/reserved_names.md
index 45c306f5988..a14df6c8402 100644
--- a/doc/user/reserved_names.md
+++ b/doc/user/reserved_names.md
@@ -61,8 +61,6 @@ Currently the following names are reserved as top level groups:
- favicon.ico
- favicon.png
- groups
-- header_logo_dark.png
-- header_logo_light.png
- health_check
- help
- import
diff --git a/doc/workflow/gitlab_flow.md b/doc/workflow/gitlab_flow.md
index a7313082fac..1b9fb504b15 100644
--- a/doc/workflow/gitlab_flow.md
+++ b/doc/workflow/gitlab_flow.md
@@ -2,314 +2,324 @@
# Introduction to GitLab Flow
-Version management with git makes branching and merging much easier than older versioning systems such as SVN.
-This allows a wide variety of branching strategies and workflows.
-Almost all of these are an improvement over the methods used before git.
-But many organizations end up with a workflow that is not clearly defined, overly complex or not integrated with issue tracking systems.
-Therefore we propose the GitLab flow as clearly defined set of best practices.
-It combines [feature driven development](https://en.wikipedia.org/wiki/Feature-driven_development) and [feature branches](http://martinfowler.com/bliki/FeatureBranch.html) with issue tracking.
+Git allows a wide variety of branching strategies and workflows.
+Because of this, many organizations end up with workflows that are too complicated, not clearly defined, or not integrated with issue tracking systems.
+Therefore, we propose GitLab flow as a clearly defined set of best practices.
+It combines [feature-driven development](https://en.wikipedia.org/wiki/Feature-driven_development) and [feature branches](https://martinfowler.com/bliki/FeatureBranch.html) with issue tracking.
-Organizations coming to git from other version control systems frequently find it hard to develop an effective workflow.
-This article describes the GitLab flow that integrates the git workflow with an issue tracking system.
-It offers a simple, transparent and effective way to work with git.
+Organizations coming to Git from other version control systems frequently find it hard to develop a productive workflow.
+This article describes GitLab flow, which integrates the Git workflow with an issue tracking system.
+It offers a simple, transparent, and effective way to work with Git.
![Four stages (working copy, index, local repo, remote repo) and three steps between them](four_stages.png)
-When converting to git you have to get used to the fact that there are three steps before a commit is shared with colleagues.
-Most version control systems have only one step, committing from the working copy to a shared server.
-In git you add files from the working copy to the staging area. After that you commit them to the local repo.
+When converting to Git, you have to get used to the fact that it takes three steps to share a commit with colleagues.
+Most version control systems have only one step: committing from the working copy to a shared server.
+In Git, you add files from the working copy to the staging area. After that, you commit them to your local repo.
The third step is pushing to a shared remote repository.
-After getting used to these three steps the branching model becomes the challenge.
+After getting used to these three steps, the next challenge is the branching model.
-![Multiple long running branches and merging in all directions](messy_flow.png)
+![Multiple long-running branches and merging in all directions](messy_flow.png)
-Since many organizations new to git have no conventions how to work with it, it can quickly become a mess.
-The biggest problem they run into is that many long running branches that each contain part of the changes are around.
-People have a hard time figuring out which branch they should develop on or deploy to production.
-Frequently the reaction to this problem is to adopt a standardized pattern such as [git flow](http://nvie.com/posts/a-successful-git-branching-model/) and [GitHub flow](http://scottchacon.com/2011/08/31/github-flow.html).
-We think there is still room for improvement and will detail a set of practices we call GitLab flow.
+Since many organizations new to Git have no conventions for how to work with it, their repositories can quickly become messy.
+The biggest problem is that many long-running branches emerge that all contain part of the changes.
+People have a hard time figuring out which branch has the latest code, or which branch to deploy to production.
+Frequently, the reaction to this problem is to adopt a standardized pattern such as [Git flow](https://nvie.com/posts/a-successful-git-branching-model/) and [GitHub flow](http://scottchacon.com/2011/08/31/github-flow.html).
+We think there is still room for improvement. In this document, we describe a set of practices we call GitLab flow.
## Git flow and its problems
![Git Flow timeline by Vincent Driessen, used with permission](gitdashflow.png)
-Git flow was one of the first proposals to use git branches and it has gotten a lot of attention.
-It advocates a master branch and a separate develop branch as well as supporting branches for features, releases and hotfixes.
-The development happens on the develop branch, moves to a release branch and is finally merged into the master branch.
-Git flow is a well defined standard but its complexity introduces two problems.
-The first problem is that developers must use the develop branch and not master, master is reserved for code that is released to production.
-It is a convention to call your default branch master and to mostly branch from and merge to this.
-Since most tools automatically make the master branch the default one and display that one by default it is annoying to have to switch to another one.
-The second problem of git flow is the complexity introduced by the hotfix and release branches.
+Git flow was one of the first proposals to use Git branches, and it has received a lot of attention.
+It suggests a `master` branch and a separate `develop` branch, as well as supporting branches for features, releases, and hotfixes.
+The development happens on the `develop` branch, moves to a release branch, and is finally merged into the `master` branch.
+
+Git flow is a well-defined standard, but its complexity introduces two problems.
+The first problem is that developers must use the `develop` branch and not `master`. `master` is reserved for code that is released to production.
+It is a convention to call your default branch `master` and to mostly branch from and merge to this.
+Since most tools automatically use the `master` branch as the default, it is annoying to have to switch to another branch.
+
+The second problem of Git flow is the complexity introduced by the hotfix and release branches.
These branches can be a good idea for some organizations but are overkill for the vast majority of them.
-Nowadays most organizations practice continuous delivery which means that your default branch can be deployed.
-This means that hotfix and release branches can be prevented including all the ceremony they introduce.
+Nowadays, most organizations practice continuous delivery, which means that your default branch can be deployed.
+Continuous delivery removes the need for hotfix and release branches, including all the ceremony they introduce.
An example of this ceremony is the merging back of release branches.
Though specialized tools do exist to solve this, they require documentation and add complexity.
-Frequently developers make a mistake and for example changes are only merged into master and not into the develop branch.
-The root cause of these errors is that git flow is too complex for most of the use cases.
-And doing releases doesn't automatically mean also doing hotfixes.
+Frequently, developers make mistakes such as merging changes only into `master` and not into the `develop` branch.
+The reason for these errors is that Git flow is too complicated for most use cases.
+For example, many projects do releases but don't need to do hotfixes.
## GitHub flow as a simpler alternative
![Master branch with feature branches merged in](github_flow.png)
-In reaction to git flow a simpler alternative was detailed, [GitHub flow](https://guides.github.com/introduction/flow/index.html).
-This flow has only feature branches and a master branch.
-This is very simple and clean, many organizations have adopted it with great success.
-Atlassian recommends [a similar strategy](http://blogs.atlassian.com/2014/01/simple-git-workflow-simple/) although they rebase feature branches.
-Merging everything into the master branch and deploying often means you minimize the amount of code in 'inventory' which is in line with the lean and continuous delivery best practices.
-But this flow still leaves a lot of questions unanswered regarding deployments, environments, releases and integrations with issues.
-With GitLab flow we offer additional guidance for these questions.
+In reaction to Git flow, GitHub created a simpler alternative.
+[GitHub flow](https://guides.github.com/introduction/flow/index.html) has only feature branches and a `master` branch.
+This flow is clean and straightforward, and many organizations have adopted it with great success.
+Atlassian recommends [a similar strategy](https://www.atlassian.com/blog/archives/simple-git-workflow-simple), although they rebase feature branches.
+Merging everything into the `master` branch and frequently deploying means you minimize the amount of unreleased code, which is in line with lean and continuous delivery best practices.
+However, this flow still leaves a lot of questions unanswered regarding deployments, environments, releases, and integrations with issues.
+With GitLab flow, we offer additional guidance for these questions.
## Production branch with GitLab flow
-![Master branch and production branch with arrow that indicate deployments](production_branch.png)
+![Master branch and production branch with an arrow that indicates a deployment](production_branch.png)
-GitHub flow does assume you are able to deploy to production every time you merge a feature branch.
-This is possible for e.g. SaaS applications, but there are many cases where this is not possible.
-One would be a situation where you are not in control of the exact release moment, for example an iOS application that needs to pass App Store validation.
-Another example is when you have deployment windows (workdays from 10am to 4pm when the operations team is at full capacity) but you also merge code at other times.
-In these cases you can make a production branch that reflects the deployed code.
-You can deploy a new version by merging in master to the production branch.
-If you need to know what code is in production you can just checkout the production branch to see.
+GitHub flow assumes you can deploy to production every time you merge a feature branch.
+While this is possible in some cases, such as SaaS applications, there are many cases where this is not possible.
+One case is where you don't control the timing of a release, for example, an iOS application that is released when it passes App Store validation.
+Another case is when you have deployment windows &mdash; for example, workdays from 10&nbsp;AM to 4&nbsp;PM when the operations team is at full capacity &mdash; but you also merge code at other times.
+In these cases, you can make a production branch that reflects the deployed code.
+You can deploy a new version by merging `master` into the production branch.
+If you need to know what code is in production, you can just checkout the production branch to see.
The approximate time of deployment is easily visible as the merge commit in the version control system.
This time is pretty accurate if you automatically deploy your production branch.
-If you need a more exact time you can have your deployment script create a tag on each deployment.
-This flow prevents the overhead of releasing, tagging and merging that is common to git flow.
+If you need a more exact time, you can have your deployment script create a tag on each deployment.
+This flow prevents the overhead of releasing, tagging, and merging that happens with Git flow.
## Environment branches with GitLab flow
![Multiple branches with the code cascading from one to another](environment_branches.png)
-It might be a good idea to have an environment that is automatically updated to the master branch.
-Only in this case, the name of this environment might differ from the branch name.
-Suppose you have a staging environment, a pre-production environment and a production environment.
-In this case the master branch is deployed on staging. When someone wants to deploy to pre-production they create a merge request from the master branch to the pre-production branch.
-And going live with code happens by merging the pre-production branch into the production branch.
-This workflow where commits only flow downstream ensures that everything has been tested on all environments.
-If you need to cherry-pick a commit with a hotfix it is common to develop it on a feature branch and merge it into master with a merge request, do not delete the feature branch.
-If master is good to go (it should be if you are practicing [continuous delivery](http://martinfowler.com/bliki/ContinuousDelivery.html)) you then merge it to the other branches.
-If this is not possible because more manual testing is required you can send merge requests from the feature branch to the downstream branches.
+It might be a good idea to have an environment that is automatically updated to the `master` branch.
+Only, in this case, the name of this environment might differ from the branch name.
+Suppose you have a staging environment, a pre-production environment, and a production environment.
+In this case, deploy the `master` branch to staging.
+To deploy to pre-production, create a merge request from the `master` branch to the pre-production branch.
+Go live by merging the pre-production branch into the production branch.
+This workflow, where commits only flow downstream, ensures that everything is tested in all environments.
+If you need to cherry-pick a commit with a hotfix, it is common to develop it on a feature branch and merge it into `master` with a merge request.
+In this case, do not delete the feature branch yet.
+If `master` passes automatic testing, you then merge the feature branch into the other branches.
+If this is not possible because more manual testing is required, you can send merge requests from the feature branch to the downstream branches.
## Release branches with GitLab flow
![Master and multiple release branches that vary in length with cherry-picks from master](release_branches.png)
-Only in case you need to release software to the outside world you need to work with release branches.
-In this case, each branch contains a minor version (2-3-stable, 2-4-stable, etc.).
-The stable branch uses master as a starting point and is created as late as possible.
-By branching as late as possible you minimize the time you have to apply bug fixes to multiple branches.
-After a release branch is announced, only serious bug fixes are included in the release branch.
-If possible these bug fixes are first merged into master and then cherry-picked into the release branch.
-This way you can't forget to cherry-pick them into master and encounter the same bug on subsequent releases.
-This is called an 'upstream first' policy that is also practiced by [Google](https://www.chromium.org/chromium-os/chromiumos-design-docs/upstream-first) and [Red Hat](https://www.redhat.com/about/news/archive/2013/5/a-community-for-using-openstack-with-red-hat-rdo).
-Every time a bug-fix is included in a release branch the patch version is raised (to comply with [Semantic Versioning](http://semver.org/)) by setting a new tag.
+You only need to work with release branches if you need to release software to the outside world.
+In this case, each branch contains a minor version, for example, 2-3-stable, 2-4-stable, etc.
+Create stable branches using `master` as a starting point, and branch as late as possible.
+By doing this, you minimize the length of time during which you have to apply bug fixes to multiple branches.
+After announcing a release branch, only add serious bug fixes to the branch.
+If possible, first merge these bug fixes into `master`, and then cherry-pick them into the release branch.
+If you start by merging into the release branch, you might forget to cherry-pick them into `master`, and then you'd encounter the same bug in subsequent releases.
+Merging into `master` and then cherry-picking into release is called an "upstream first" policy, which is also practiced by [Google](https://www.chromium.org/chromium-os/chromiumos-design-docs/upstream-first) and [Red Hat](https://www.redhat.com/en/blog/a-community-for-using-openstack-with-red-hat-rdo).
+Every time you include a bug fix in a release branch, increase the patch version (to comply with [Semantic Versioning](https://semver.org/)) by setting a new tag.
Some projects also have a stable branch that points to the same commit as the latest released branch.
-In this flow it is not common to have a production branch (or git flow master branch).
+In this flow, it is not common to have a production branch (or Git flow `master` branch).
## Merge/pull requests with GitLab flow
-![Merge request with line comments](mr_inline_comments.png)
+![Merge request with inline comments](mr_inline_comments.png)
-Merge or pull requests are created in a git management application and ask an assigned person to merge two branches.
-Tools such as GitHub and Bitbucket choose the name pull request since the first manual action would be to pull the feature branch.
-Tools such as GitLab and others choose the name merge request since that is the final action that is requested of the assignee.
-In this article we'll refer to them as merge requests.
+Merge or pull requests are created in a Git management application. They ask an assigned person to merge two branches.
+Tools such as GitHub and Bitbucket choose the name "pull request" since the first manual action is to pull the feature branch.
+Tools such as GitLab and others choose the name "merge request" since the final action is to merge the feature branch.
+In this article, we'll refer to them as merge requests.
-If you work on a feature branch for more than a few hours it is good to share the intermediate result with the rest of the team.
-This can be done by creating a merge request without assigning it to anyone, instead you mention people in the description or a comment (/cc @mark @susan).
-This means it is not ready to be merged but feedback is welcome.
+If you work on a feature branch for more than a few hours, it is good to share the intermediate result with the rest of the team.
+To do this, create a merge request without assigning it to anyone.
+Instead, mention people in the description or a comment, for example, "/cc @mark @susan."
+This indicates that the merge request is not ready to be merged yet, but feedback is welcome.
Your team members can comment on the merge request in general or on specific lines with line comments.
-The merge requests serves as a code review tool and no separate tools such as Gerrit and reviewboard should be needed.
-If the review reveals shortcomings anyone can commit and push a fix.
-Commonly the person to do this is the creator of the merge/pull request.
-The diff in the merge/pull requests automatically updates when new commits are pushed on the branch.
+The merge request serves as a code review tool, and no separate code review tools should be needed.
+If the review reveals shortcomings, anyone can commit and push a fix.
+Usually, the person to do this is the creator of the merge request.
+The diff in the merge request automatically updates when new commits are pushed to the branch.
+
+When you are ready for your feature branch to be merged, assign the merge request to the person who knows most about the codebase you are changing.
+Also, mention any other people from whom you would like feedback.
+After the assigned person feels comfortable with the result, they can merge the branch.
+If the assigned person does not feel comfortable, they can request more changes or close the merge request without merging.
+
+In GitLab, it is common to protect the long-lived branches, e.g., the `master` branch, so that [most developers can't modify them](../permissions/permissions.md).
+So, if you want to merge into a protected branch, assign your merge request to someone with maintainer permissions.
-When you feel comfortable with it to be merged you assign it to the person that knows most about the codebase you are changing and mention any other people you would like feedback from.
-There is room for more feedback and after the assigned person feels comfortable with the result the branch is merged.
-If the assigned person does not feel comfortable they can close the merge request without merging.
+After you merge a feature branch, you should remove it from the source control software.
+In GitLab, you can do this when merging.
+Removing finished branches ensures that the list of branches shows only work in progress.
+It also ensures that if someone reopens the issue, they can use the same branch name without causing problems.
-In GitLab it is common to protect the long-lived branches (e.g. the master branch) so that normal developers [can't modify these protected branches](http://docs.gitlab.com/ce/permissions/permissions.html).
-So if you want to merge it into a protected branch you assign it to someone with maintainer authorizations.
+NOTE: **Note:**
+When you reopen an issue you need to create a new merge request.
+
+![Remove checkbox for branch in merge requests](remove_checkbox.png)
## Issue tracking with GitLab flow
-![Merge request with the branch name 15-require-a-password-to-change-it and assignee field shown](merge_request.png)
+![Merge request with the branch name "15-require-a-password-to-change-it" and assignee field shown](merge_request.png)
GitLab flow is a way to make the relation between the code and the issue tracker more transparent.
-Any significant change to the code should start with an issue where the goal is described.
-Having a reason for every code change is important to inform everyone on the team and to help people keep the scope of a feature branch small.
-In GitLab each change to the codebase starts with an issue in the issue tracking system.
-If there is no issue yet it should be created first provided there is significant work involved (more than 1 hour).
-For many organizations this will be natural since the issue will have to be estimated for the sprint.
-Issue titles should describe the desired state of the system, e.g. "As an administrator I want to remove users without receiving an error" instead of "Admin can't remove users.".
-
-When you are ready to code you start a branch for the issue from the master branch.
-The name of this branch should start with the issue number, for example '15-require-a-password-to-change-it'.
-
-When you are done or want to discuss the code you open a merge request.
-This is an online place to discuss the change and review the code.
-Opening a merge request is a manual action since you do not always want to merge a new branch you push, it could be a long-running environment or release branch.
-If you open the merge request but do not assign it to anyone it is a 'Work In Progress' merge request.
-These are used to discuss the proposed implementation but are not ready for inclusion in the master branch yet.
-_Pro tip:_ Start the title of the merge request with `[WIP]` or `WIP:` to prevent it from being merged before it's ready.
-
-When the author thinks the code is ready the merge request is assigned to reviewer.
-The reviewer presses the merge button when they think the code is ready for inclusion in the master branch.
-In this case the code is merged and a merge commit is generated that makes this event easily visible later on.
-Merge requests always create a merge commit even when the commit could be added without one.
-This merge strategy is called 'no fast-forward' in git.
-After the merge the feature branch is deleted since it is no longer needed, in GitLab this deletion is an option when merging.
+Any significant change to the code should start with an issue that describes the goal.
+Having a reason for every code change helps to inform the rest of the team and to keep the scope of a feature branch small.
+In GitLab, each change to the codebase starts with an issue in the issue tracking system.
+If there is no issue yet, create the issue, as long as the change will take a significant amount of work, i.e., more than 1 hour.
+In many organizations, raising an issue is part of the development process because they are used in sprint planning.
+The issue title should describe the desired state of the system.
+For example, the issue title "As an administrator, I want to remove users without receiving an error" is better than "Admin can't remove users."
+
+When you are ready to code, create a branch for the issue from the `master` branch.
+This branch is the place for any work related to this change.
+
+NOTE: **Note:**
+The name of a branch might be dictated by organizational standards.
+For example, in GitLab, any branches in GitLab EE that are equivalent to branches in GitLab CE [must end in `-ee`](https://docs.gitlab.com/ee/development/automatic_ce_ee_merge.html#cherry-picking-from-ce-to-ee).
+
+When you are done or want to discuss the code, open a merge request.
+A merge request is an online place to discuss the change and review the code.
+
+If you open the merge request but do not assign it to anyone, it is a "Work In Progress" merge request.
+These are used to discuss the proposed implementation but are not ready for inclusion in the `master` branch yet.
+Start the title of the merge request with "[WIP]" or "WIP:" to prevent it from being merged before it's ready.
+
+When you think the code is ready, assign the merge request to a reviewer.
+The reviewer can merge the changes when they think the code is ready for inclusion in the `master` branch.
+When they press the merge button, GitLab merges the code and creates a merge commit that makes this event easily visible later on.
+Merge requests always create a merge commit, even when the branch could be merged without one.
+This merge strategy is called "no fast-forward" in Git.
+After the merge, delete the feature branch since it is no longer needed.
+In GitLab, this deletion is an option when merging.
Suppose that a branch is merged but a problem occurs and the issue is reopened.
-In this case it is no problem to reuse the same branch name since it was deleted when the branch was merged.
-At any time there is at most one branch for every issue.
+In this case, it is no problem to reuse the same branch name since the first branch was deleted when it was merged.
+At any time, there is at most one branch for every issue.
It is possible that one feature branch solves more than one issue.
## Linking and closing issues from merge requests
![Merge request showing the linked issues that will be closed](close_issue_mr.png)
-Linking to issues can happen by mentioning them in commit messages (fixes #14, closes #67, etc.) or in the merge request description.
-GitLab then creates links to the mentioned issues and creates comments in the corresponding issues linking back to the merge request.
+Link to issues by mentioning them in commit messages or the description of a merge request, for example, "Fixes #16" or "Duck typing is preferred. See #12."
+GitLab then creates links to the mentioned issues and creates comments in the issues linking back to the merge request.
-These issues are closed once code is merged into the default branch.
+To automatically close linked issues, mention them with the words "fixes" or "closes," for example, "fixes #14" or "closes #67." GitLab closes these issues when the code is merged into the default branch.
-If you only want to make the reference without closing the issue you can also just mention it: "Duck typing is preferred. #12".
-
-If you have an issue that spans across multiple repositories, the best thing is to create an issue for each repository and link all issues to a parent issue.
+If you have an issue that spans across multiple repositories, create an issue for each repository and link all issues to a parent issue.
## Squashing commits with rebase
![Vim screen showing the rebase view](rebase.png)
-With git you can use an interactive rebase (`rebase -i`) to squash multiple commits into one and reorder them.
-In GitLab EE and .com you can also [rebase before merge](http://docs.gitlab.com/ee/workflow/rebase_before_merge.html) from the web interface.
-This functionality is useful if you made a couple of commits for small changes during development and want to replace them with a single commit or if you want to make the order more logical.
-However you should never rebase commits you have pushed to a remote server.
-Somebody can have referred to the commits or cherry-picked them.
-When you rebase you change the identifier (SHA-1) of the commit and this is confusing.
-If you do that the same change will be known under multiple identifiers and this can cause much confusion.
-If people already reviewed your code it will be hard for them to review only the improvements you made since then if you have rebased everything into one commit.
-Another reasons not to rebase is that you lose authorship information, maybe someone created a merge request, another person pushed a commit on there to improve it and a third one merged it.
-In this case rebasing all the commits into one prevent the other authors from being properly attributed and sharing part of the [git blame](https://git-scm.com/docs/git-blame).
-
-People are encouraged to commit often and to frequently push to the remote repository so other people are aware what everyone is working on.
-This will lead to many commits per change which makes the history harder to understand.
-But the advantages of having stable identifiers outweigh this drawback.
-And to understand a change in context one can always look at the merge commit that groups all the commits together when the code is merged into the master branch.
-
-After you merge multiple commits from a feature branch into the master branch this is harder to undo.
-If you had squashed all the commits into one you could have just reverted this commit but as we indicated you should not rebase commits after they are pushed.
-Fortunately [reverting a merge made some time ago](https://git-scm.com/blog/2010/03/02/undoing-merges.html) can be done with git.
-This however, requires having specific merge commits for the commits your want to revert.
-If you revert a merge and you change your mind, revert the revert instead of merging again since git will not allow you to merge the code again otherwise.
-
-Being able to revert a merge is a good reason always to create a merge commit when you merge manually with the `--no-ff` option.
-Git management software will always create a merge commit when you accept a merge request.
-
-## Do not order commits with rebase
+With Git, you can use an interactive rebase (`rebase -i`) to squash multiple commits into one or reorder them.
+This functionality is useful if you want to replace a couple of small commits with a single commit, or if you want to make the order more logical.
+
+However, you should never rebase commits you have pushed to a remote server.
+Rebasing creates new commits for all your changes, which can cause confusion because the same change would have multiple identifiers.
+It also causes merge errors for anyone working on the same branch because their history would not match with yours.
+Also, if someone has already reviewed your code, rebasing makes it hard to tell what changed since the last review.
+
+You should also never rebase commits authored by other people.
+Not only does this rewrite history, but it also loses authorship information.
+Rebasing prevents the other authors from being attributed and sharing part of the [`git blame`](https://git-scm.com/docs/git-blame).
+
+If a merge involves many commits, it may seem more difficult to undo.
+You might think to solve this by squashing all the changes into one commit before merging, but as discussed earlier, it is a bad idea to rebase commits that you have already pushed.
+Fortunately, there is an easy way to undo a merge with all its commits.
+The way to do this is by reverting the merge commit.
+Preserving this ability to revert a merge is a good reason to always use the "no fast-forward" (`--no-ff`) strategy when you merge manually.
+
+NOTE: **Note:**
+If you revert a merge commit and then change your mind, revert the revert commit to redo the merge.
+Git does not allow you to merge the code again otherwise.
+
+## Reducing merge commits in feature branches
![List of sequential merge commits](merge_commits.png)
-With git you can also rebase your feature branch commits to order them after the commits on the master branch.
-This prevents creating a merge commit when merging master into your feature branch and creates a nice linear history.
-However, just like with squashing you should never rebase commits you have pushed to a remote server.
-This makes it impossible to rebase work in progress that you already shared with your team which is something we recommend.
-When using rebase to keep your feature branch updated you [need to resolve similar conflicts again and again](https://blogs.atlassian.com/2013/10/git-team-workflows-merge-or-rebase/).
-You can reuse recorded resolutions (rerere) sometimes, but without rebasing you only have to solve the conflicts one time and you’re set.
-There has to be a better way to avoid many merge commits.
-
-The way to prevent creating many merge commits is to not frequently merge master into the feature branch.
-We'll discuss the three reasons to merge in master: leveraging code, merge conflicts, and long running branches.
-If you need to leverage some code that was introduced in master after you created the feature branch you can sometimes solve this by just cherry-picking a commit.
-If your feature branch has a merge conflict, creating a merge commit is a normal way of solving this.
-You can prevent some merge conflicts by using [gitattributes](http://git-scm.com/docs/gitattributes) for files that can be in a random order.
-For example in GitLab our changelog file is specified in .gitattributes as `CHANGELOG.md merge=union` so that there are fewer merge conflicts in it.
-The last reason for creating merge commits is having long lived branches that you want to keep up to date with the latest state of the project.
-Martin Fowler, in [his article about feature branches](http://martinfowler.com/bliki/FeatureBranch.html) talks about this Continuous Integration (CI).
-At GitLab we are guilty of confusing CI with branch testing. Quoting Martin Fowler: "I've heard people say they are doing CI because they are running builds, perhaps using a CI server, on every branch with every commit.
-That's continuous building, and a Good Thing, but there's no integration, so it's not CI.".
-The solution to prevent many merge commits is to keep your feature branches short-lived, the vast majority should take less than one day of work.
-If your feature branches commonly take more than a day of work, look into ways to create smaller units of work and/or use [feature toggles](http://martinfowler.com/bliki/FeatureToggle.html).
-As for the long running branches that take more than one day there are two strategies.
-In a CI strategy you can merge in master at the start of the day to prevent painful merges at a later time.
-In a synchronization point strategy you only merge in from well defined points in time, for example a tagged release.
-This strategy is [advocated by Linus Torvalds](https://www.mail-archive.com/dri-devel@lists.sourceforge.net/msg39091.html) because the state of the code at these points is better known.
-
-In conclusion, we can say that you should try to prevent merge commits, but not eliminate them.
-Your codebase should be clean but your history should represent what actually happened.
-Developing software happen in small messy steps and it is OK to have your history reflect this.
-You can use tools to view the network graphs of commits and understand the messy history that created your code.
-If you rebase code the history is incorrect, and there is no way for tools to remedy this because they can't deal with changing commit identifiers.
+Having lots of merge commits can make your repository history messy.
+Therefore, you should try to avoid merge commits in feature branches.
+Often, people avoid merge commits by just using rebase to reorder their commits after the commits on the `master` branch.
+Using rebase prevents a merge commit when merging `master` into your feature branch, and it creates a neat linear history.
+However, as discussed in [the section about rebasing](#squashing-commits-with-rebase), you should never rebase commits you have pushed to a remote server.
+This restriction makes it impossible to rebase work in progress that you already shared with your team, which is something we recommend.
-## Award emojis on issues and merge requests
+Rebasing also creates more work, since every time you rebase, you have to resolve similar conflicts.
+Sometimes you can reuse recorded resolutions (`rerere`), but merging is better since you only have to resolve conflicts once.
+Atlassian has a more thorough explanation of the tradeoffs between merging and rebasing [on their blog](https://www.atlassian.com/blog/git/git-team-workflows-merge-or-rebase).
-![Emoji bar in GitLab](award_emoji.png)
+A good way to prevent creating many merge commits is to not frequently merge `master` into the feature branch.
+There are three reasons to merge in `master`: utilizing new code, resolving merge conflicts, and updating long-running branches.
-It is common to voice approval or disapproval by using +1 or -1. In GitLab you
-can use emojis to give a virtual high five on issues and merge requests.
+If you need to utilize some code that was introduced in `master` after you created the feature branch, you can often solve this by just cherry-picking a commit.
-## Pushing and removing branches
+If your feature branch has a merge conflict, creating a merge commit is a standard way of solving this.
-![Remove checkbox for branch in merge requests](remove_checkbox.png)
+NOTE: **Note:**
+Sometimes you can use .gitattributes to reduce merge conflicts.
+For example, you can set your changelog file to use the [union merge driver](https://git-scm.com/docs/gitattributes#gitattributes-union) so that multiple new entries don't conflict with each other.
-We recommend that people push their feature branches frequently, even when they are not ready for review yet.
-By doing this you prevent team members from accidentally starting to work on the same issue.
-Of course this situation should already be prevented by assigning someone to the issue in the issue tracking software.
-However sometimes one of the two parties forgets to assign someone in the issue tracking software.
-After a branch is merged it should be removed from the source control software.
-In GitLab and similar systems this is an option when merging.
-This ensures that the branch overview in the repository management software shows only work in progress.
-This also ensures that when someone reopens the issue a new branch with the same name can be used without problem.
-When you reopen an issue you need to create a new merge request.
+The last reason for creating merge commits is to keep long-running feature branches up-to-date with the latest state of the project.
+The solution here is to keep your feature branches short-lived.
+Most feature branches should take less than one day of work.
+If your feature branches often take more than a day of work, try to split your features into smaller units of work.
+
+If you need to keep a feature branch open for more than a day, there are a few strategies to keep it up-to-date.
+One option is to use continuous integration (CI) to merge in `master` at the start of the day.
+Another option is to only merge in from well-defined points in time, for example, a tagged release.
+You could also use [feature toggles](https://martinfowler.com/bliki/FeatureToggle.html) to hide incomplete features so you can still merge back into `master` every day.
+
+> **Note:** Don't confuse automatic branch testing with continuous integration.
+> Martin Fowler makes this distinction in [his article about feature branches](https://martinfowler.com/bliki/FeatureBranch.html):
+>
+> "I've heard people say they are doing CI because they are running builds, perhaps using a CI server, on every branch with every commit.
+> That's continuous building, and a Good Thing, but there's no *integration*, so it's not CI."
+
+In conclusion, you should try to prevent merge commits, but not eliminate them.
+Your codebase should be clean, but your history should represent what actually happened.
+Developing software happens in small, messy steps, and it is OK to have your history reflect this.
+You can use tools to view the network graphs of commits and understand the messy history that created your code.
+If you rebase code, the history is incorrect, and there is no way for tools to remedy this because they can't deal with changing commit identifiers.
+
+## Commit often and push frequently
+
+Another way to make your development work easier is to commit often.
+Every time you have a working set of tests and code, you should make a commit.
+Splitting up work into individual commits provides context for developers looking at your code later.
+Smaller commits make it clear how a feature was developed, and they make it easy to roll back to a specific good point in time or to revert one code change without reverting several unrelated changes.
+
+Committing often also makes it easy to share your work, which is important so that everyone is aware of what you are working on.
+You should push your feature branch frequently, even when it is not yet ready for review.
+By sharing your work in a feature branch or [a merge request](#mergepull-requests-with-gitlab-flow), you prevent your team members from duplicating work.
+Sharing your work before it's complete also allows for discussion and feedback about the changes, which can help improve the code before it gets to review.
-## Committing often and with the right message
+## How to write a good commit message
![Good and bad commit message](good_commit.png)
-We recommend to commit early and often.
-Each time you have a functioning set of tests and code a commit can be made.
-The advantage is that when an extension or refactor goes wrong it is easy to revert to a working version.
-This is quite a change for programmers that used SVN before, they used to commit when their work was ready to share.
-The trick is to use the merge/pull request with multiple commits when your work is ready to share.
-The commit message should reflect your intention, not the contents of the commit.
-The contents of the commit can be easily seen anyway, the question is why you did it.
-An example of a good commit message is: "Combine templates to dry up the user views.".
-Some words that are bad commit messages because they don't contain much information are: change, improve and refactor.
-The word fix or fixes is also a red flag, unless it comes after the commit sentence and references an issue number.
-To see more information about the formatting of commit messages please see this great [blog post by Tim Pope](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
+A commit message should reflect your intention, not just the contents of the commit.
+It is easy to see the changes in a commit, so the commit message should explain why you made those changes.
+An example of a good commit message is: "Combine templates to reduce duplicate code in the user views."
+The words "change," "improve," "fix," and "refactor" don't add much information to a commit message.
+For example, "Improve XML generation" could be better written as "Properly escape special characters in XML generation."
+For more information about formatting commit messages, please see this excellent [blog post by Tim Pope](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
## Testing before merging
-![Merge requests showing the test states, red, yellow and green](ci_mr.png)
-
-In old workflows the Continuous Integration (CI) server commonly ran tests on the master branch only.
-Developers had to ensure their code did not break the master branch.
-When using GitLab flow developers create their branches from this master branch so it is essential it is green.
-Therefore each merge request must be tested before it is accepted.
-CI software like Travis and GitLab CI show the build results right in the merge request itself to make this easy.
-One drawback is that they are testing the feature branch itself and not the merged result.
-What one can do to improve this is to test the merged result itself.
-The problem is that the merge result changes every time something is merged into master.
-Retesting on every commit to master is computationally expensive and means you are more frequently waiting for test results.
-If there are no merge conflicts and the feature branches are short lived the risk is acceptable.
-If there are merge conflicts you merge the master branch into the feature branch and the CI server will rerun the tests.
-If you have long lived feature branches that last for more than a few days you should make your issues smaller.
+![Merge requests showing the test states: red, yellow, and green](ci_mr.png)
-## Working with feature branches
+In old workflows, the continuous integration (CI) server commonly ran tests on the `master` branch only.
+Developers had to ensure their code did not break the `master` branch.
+When using GitLab flow, developers create their branches from this `master` branch, so it is essential that it never breaks.
+Therefore, each merge request must be tested before it is accepted.
+CI software like Travis CI and GitLab CI show the build results right in the merge request itself to make this easy.
-![Shell output showing git pull output](git_pull.png)
+There is one drawback to testing merge requests: the CI server only tests the feature branch itself, not the merged result.
+Ideally, the server could also test the `master` branch after each change.
+However, retesting on every commit to `master` is computationally expensive and means you are more frequently waiting for test results.
+Since feature branches should be short-lived, testing just the branch is an acceptable risk.
+If new commits in `master` cause merge conflicts with the feature branch, merge `master` back into the branch to make the CI server re-run the tests.
+As said before, if you often have feature branches that last for more than a few days, you should make your issues smaller.
-When initiating a feature branch, always start with an up to date master to branch off from.
-If you know beforehand that your work absolutely depends on another branch you can also branch from there.
-If you need to merge in another branch after starting explain the reason in the merge commit.
-If you have not pushed your commits to a shared location yet you can also rebase on master or another feature branch.
-Do not merge in upstream if your code will work and merge cleanly without doing so, Linus even says that [you should never merge in upstream at random points, only at major releases](https://lwn.net/Articles/328438/).
-Merging only when needed prevents creating merge commits in your feature branch that later end up littering the master history.
+## Working with feature branches
-### References
+![Shell output showing git pull output](git_pull.png)
-- [Git Flow by Vincent Driessen](http://nvie.com/posts/a-successful-git-branching-model/)
+When creating a feature branch, always branch from an up-to-date `master`.
+If you know before you start that your work depends on another branch, you can also branch from there.
+If you need to merge in another branch after starting, explain the reason in the merge commit.
+If you have not pushed your commits to a shared location yet, you can also incorporate changes by rebasing on `master` or another feature branch.
+Do not merge from upstream again if your code can work and merge cleanly without doing so.
+Merging only when needed prevents creating merge commits in your feature branch that later end up littering the `master` history.
diff --git a/doc/workflow/time_tracking.md b/doc/workflow/time_tracking.md
index 2bb140b99d0..ad43189148c 100644
--- a/doc/workflow/time_tracking.md
+++ b/doc/workflow/time_tracking.md
@@ -38,13 +38,14 @@ Adding time entries (time spent or estimates) is limited to project members.
To enter an estimate, write `/estimate`, followed by the time. For example, if
you need to enter an estimate of 3 days, 5 hours and 10 minutes, you would write
-`/estimate 3d 5h 10m`.
+`/estimate 3d 5h 10m`. Time units that we support are listed at the bottom of
+this help page.
Every time you enter a new time estimate, any previous time estimates will be
overridden by this new value. There should only be one valid estimate in an
issue or a merge request.
-To remove an estimation entirely, use `/remove_estimation`.
+To remove an estimation entirely, use `/remove_estimate`.
### Time spent
@@ -72,5 +73,8 @@ The following time units are available:
Default conversion rates are 1mo = 4w, 1w = 5d and 1d = 8h.
-[landing]: https://about.gitlab.com/features/time-tracking
+Other interesting links:
+
+- [Time Tracking landing page on about.gitlab.com](https://about.gitlab.com/features/time-tracking)
+
[quick actions]: ../user/project/quick_actions.md
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index beb8ce349b4..0ef56067b95 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -369,8 +369,9 @@ module API
end
class Commit < Grape::Entity
- expose :id, :short_id, :title, :created_at
+ expose :id, :short_id, :created_at
expose :parent_ids
+ expose :full_title, as: :title
expose :safe_message, as: :message
expose :author_name, :author_email, :authored_date
expose :committer_name, :committer_email, :committed_date
@@ -501,6 +502,9 @@ module API
expose :state, :created_at, :updated_at
expose :due_date
expose :start_date
+ expose :percentage_complete do |milestone, options|
+ milestone.percent_complete(options[:current_user])
+ end
expose :web_url do |milestone, _options|
Gitlab::UrlBuilder.build(milestone)
@@ -1003,7 +1007,7 @@ module API
end
class LabelBasic < Grape::Entity
- expose :id, :name, :color, :description
+ expose :id, :name, :color, :description, :text_color
end
class Label < LabelBasic
diff --git a/lib/api/milestone_responses.rb b/lib/api/milestone_responses.rb
index a0ca39b69d4..4c68c568aaa 100644
--- a/lib/api/milestone_responses.rb
+++ b/lib/api/milestone_responses.rb
@@ -35,19 +35,19 @@ module API
milestones = filter_by_iid(milestones, params[:iids]) if params[:iids].present?
milestones = filter_by_search(milestones, params[:search]) if params[:search]
- present paginate(milestones), with: Entities::Milestone
+ present paginate(milestones), with: Entities::Milestone, current_user: current_user
end
def get_milestone_for(parent)
milestone = parent.milestones.find(params[:milestone_id])
- present milestone, with: Entities::Milestone
+ present milestone, with: Entities::Milestone, current_user: current_user
end
def create_milestone_for(parent)
milestone = ::Milestones::CreateService.new(parent, current_user, declared_params).execute
if milestone.valid?
- present milestone, with: Entities::Milestone
+ present milestone, with: Entities::Milestone, current_user: current_user
else
render_api_error!("Failed to create milestone #{milestone.errors.messages}", 400)
end
@@ -60,7 +60,7 @@ module API
milestone = ::Milestones::UpdateService.new(parent, current_user, milestone_params).execute(milestone)
if milestone.valid?
- present milestone, with: Entities::Milestone
+ present milestone, with: Entities::Milestone, current_user: current_user
else
render_api_error!("Failed to update milestone #{milestone.errors.messages}", 400)
end
diff --git a/lib/api/services.rb b/lib/api/services.rb
index 36bdba2d765..163c7505a65 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -368,8 +368,9 @@ module API
name: :webhook,
type: String,
desc: 'The Hangouts Chat webhook. e.g. https://chat.googleapis.com/v1/spaces…'
- }
- ],
+ },
+ CHAT_NOTIFICATION_EVENTS
+ ].flatten,
'irker' => [
{
required: true,
diff --git a/lib/api/wikis.rb b/lib/api/wikis.rb
index ef0e3decc2c..994074ddc67 100644
--- a/lib/api/wikis.rb
+++ b/lib/api/wikis.rb
@@ -11,9 +11,7 @@ module API
}
end
- params :wiki_page_params do
- requires :content, type: String, desc: 'Content of a wiki page'
- requires :title, type: String, desc: 'Title of a wiki page'
+ params :common_wiki_page_params do
optional :format,
type: String,
values: ProjectWiki::MARKUPS.values.map(&:to_s),
@@ -54,7 +52,9 @@ module API
success Entities::WikiPage
end
params do
- use :wiki_page_params
+ requires :title, type: String, desc: 'Title of a wiki page'
+ requires :content, type: String, desc: 'Content of a wiki page'
+ use :common_wiki_page_params
end
post ':id/wikis' do
authorize! :create_wiki, user_project
@@ -72,7 +72,10 @@ module API
success Entities::WikiPage
end
params do
- use :wiki_page_params
+ optional :title, type: String, desc: 'Title of a wiki page'
+ optional :content, type: String, desc: 'Content of a wiki page'
+ use :common_wiki_page_params
+ at_least_one_of :content, :title, :format
end
put ':id/wikis/:slug' do
authorize! :create_wiki, user_project
diff --git a/lib/banzai/filter/footnote_filter.rb b/lib/banzai/filter/footnote_filter.rb
index 97527976437..de133774dfa 100644
--- a/lib/banzai/filter/footnote_filter.rb
+++ b/lib/banzai/filter/footnote_filter.rb
@@ -29,21 +29,30 @@ module Banzai
# Sanitization stripped off the section wrapper - add it back in
first_footnote.parent.wrap('<section class="footnotes">')
rand_suffix = "-#{random_number}"
+ modified_footnotes = {}
doc.css('sup > a[id]').each do |link_node|
ref_num = link_node[:id].delete_prefix(FOOTNOTE_LINK_ID_PREFIX)
footnote_node = doc.at_css("li[id=#{fn_id(ref_num)}]")
- backref_node = footnote_node.at_css("a[href=\"##{fnref_id(ref_num)}\"]")
- if ref_num =~ INTEGER_PATTERN && footnote_node && backref_node
- link_node[:href] += rand_suffix
- link_node[:id] += rand_suffix
- footnote_node[:id] += rand_suffix
- backref_node[:href] += rand_suffix
+ if INTEGER_PATTERN.match?(ref_num) && (footnote_node || modified_footnotes[ref_num])
+ link_node[:href] += rand_suffix
+ link_node[:id] += rand_suffix
# Sanitization stripped off class - add it back in
link_node.parent.append_class('footnote-ref')
- backref_node.append_class('footnote-backref')
+
+ unless modified_footnotes[ref_num]
+ footnote_node[:id] += rand_suffix
+ backref_node = footnote_node.at_css("a[href=\"##{fnref_id(ref_num)}\"]")
+
+ if backref_node
+ backref_node[:href] += rand_suffix
+ backref_node.append_class('footnote-backref')
+ end
+
+ modified_footnotes[ref_num] = true
+ end
end
end
diff --git a/lib/gitlab/ci/build/policy/changes.rb b/lib/gitlab/ci/build/policy/changes.rb
index 1663c875426..9c705a1cd3e 100644
--- a/lib/gitlab/ci/build/policy/changes.rb
+++ b/lib/gitlab/ci/build/policy/changes.rb
@@ -10,7 +10,7 @@ module Gitlab
end
def satisfied_by?(pipeline, seed)
- return true unless pipeline.branch_updated?
+ return true if pipeline.modified_paths.nil?
pipeline.modified_paths.any? do |path|
@globs.any? do |glob|
diff --git a/lib/gitlab/danger/helper.rb b/lib/gitlab/danger/helper.rb
new file mode 100644
index 00000000000..80760e41c7d
--- /dev/null
+++ b/lib/gitlab/danger/helper.rb
@@ -0,0 +1,131 @@
+# frozen_string_literal: true
+require 'net/http'
+require 'json'
+
+require_relative 'teammate'
+
+module Gitlab
+ module Danger
+ module Helper
+ ROULETTE_DATA_URL = URI.parse('https://about.gitlab.com/roulette.json').freeze
+
+ # Returns a list of all files that have been added, modified or renamed.
+ # `git.modified_files` might contain paths that already have been renamed,
+ # so we need to remove them from the list.
+ #
+ # Considering these changes:
+ #
+ # - A new_file.rb
+ # - D deleted_file.rb
+ # - M modified_file.rb
+ # - R renamed_file_before.rb -> renamed_file_after.rb
+ #
+ # it will return
+ # ```
+ # [ 'new_file.rb', 'modified_file.rb', 'renamed_file_after.rb' ]
+ # ```
+ #
+ # @return [Array<String>]
+ def all_changed_files
+ Set.new
+ .merge(git.added_files.to_a)
+ .merge(git.modified_files.to_a)
+ .merge(git.renamed_files.map { |x| x[:after] })
+ .subtract(git.renamed_files.map { |x| x[:before] })
+ .to_a
+ .sort
+ end
+
+ def ee?
+ ENV['CI_PROJECT_NAME'] == 'gitlab-ee' || File.exist?('../../CHANGELOG-EE.md')
+ end
+
+ def project_name
+ ee? ? 'gitlab-ee' : 'gitlab-ce'
+ end
+
+ # Looks up the current list of GitLab team members and parses it into a
+ # useful form
+ #
+ # @return [Array<Teammate>]
+ def team
+ @team ||=
+ begin
+ rsp = Net::HTTP.get_response(ROULETTE_DATA_URL)
+ raise "Failed to read #{ROULETTE_DATA_URL}: #{rsp.code} #{rsp.message}" unless
+ rsp.is_a?(Net::HTTPSuccess)
+
+ data = JSON.parse(rsp.body)
+ data.map { |hash| ::Gitlab::Danger::Teammate.new(hash) }
+ rescue JSON::ParserError
+ raise "Failed to parse JSON response from #{ROULETTE_DATA_URL}"
+ end
+ end
+
+ # Like +team+, but only returns teammates in the current project, based on
+ # project_name.
+ #
+ # @return [Array<Teammate>]
+ def project_team
+ team.select { |member| member.in_project?(project_name) }
+ end
+
+ # @return [Hash<String,Array<String>>]
+ def changes_by_category
+ all_changed_files.each_with_object(Hash.new { |h, k| h[k] = [] }) do |file, hash|
+ hash[category_for_file(file)] << file
+ end
+ end
+
+ # Determines the category a file is in, e.g., `:frontend` or `:backend`
+ # @return[Symbol]
+ def category_for_file(file)
+ _, category = CATEGORIES.find { |regexp, _| regexp.match?(file) }
+
+ category || :unknown
+ end
+
+ # Returns the GFM for a category label, making its best guess if it's not
+ # a category we know about.
+ #
+ # @return[String]
+ def label_for_category(category)
+ CATEGORY_LABELS.fetch(category, "~#{category}")
+ end
+
+ CATEGORY_LABELS = {
+ docs: "~Documentation",
+ qa: "~QA"
+ }.freeze
+
+ # rubocop:disable Style/RegexpLiteral
+ CATEGORIES = {
+ %r{\Adoc/} => :docs,
+ %r{\A(CONTRIBUTING|LICENSE|MAINTENANCE|PHILOSOPHY|PROCESS|README)(\.md)?\z} => :docs,
+
+ %r{\A(ee/)?app/(assets|views)/} => :frontend,
+ %r{\A(ee/)?public/} => :frontend,
+ %r{\A(ee/)?spec/javascripts/} => :frontend,
+ %r{\A(ee/)?vendor/assets/} => :frontend,
+ %r{\A(jest\.config\.js|package\.json|yarn\.lock)\z} => :frontend,
+
+ %r{\A(ee/)?app/(?!assets|views)[^/]+} => :backend,
+ %r{\A(ee/)?(bin|config|danger|generator_templates|lib|rubocop|scripts)/} => :backend,
+ %r{\A(ee/)?spec/(?!javascripts)[^/]+} => :backend,
+ %r{\A(ee/)?vendor/(?!assets)[^/]+} => :backend,
+ %r{\A(ee/)?vendor/(languages\.yml|licenses\.csv)\z} => :backend,
+ %r{\A(Dangerfile|Gemfile|Gemfile.lock|Procfile|Rakefile)\z} => :backend,
+ %r{\A[A-Z_]+_VERSION\z} => :backend,
+
+ %r{\A(ee/)?db/} => :database,
+ %r{\A(ee/)?qa/} => :qa,
+
+ # Fallbacks in case the above patterns miss anything
+ %r{\.rb\z} => :backend,
+ %r{\.(md|txt)\z} => :docs,
+ %r{\.js\z} => :frontend
+ }.freeze
+ # rubocop:enable Style/RegexpLiteral
+ end
+ end
+end
diff --git a/lib/gitlab/danger/teammate.rb b/lib/gitlab/danger/teammate.rb
new file mode 100644
index 00000000000..4b822aa86c5
--- /dev/null
+++ b/lib/gitlab/danger/teammate.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Danger
+ class Teammate
+ attr_reader :name, :username, :projects
+
+ def initialize(options = {})
+ @name = options['name']
+ @username = options['username']
+ @projects = options['projects']
+ end
+
+ def markdown_name
+ "[#{name}](https://gitlab.com/#{username}) (`@#{username}`)"
+ end
+
+ def in_project?(name)
+ projects&.has_key?(name)
+ end
+
+ # Traintainers also count as reviewers
+ def reviewer?(project, category)
+ capabilities(project) == "reviewer #{category}" || traintainer?(project, category)
+ end
+
+ def traintainer?(project, category)
+ capabilities(project) == "trainee_maintainer #{category}"
+ end
+
+ def maintainer?(project, category)
+ capabilities(project) == "maintainer #{category}"
+ end
+
+ private
+
+ def capabilities(project)
+ projects.fetch(project, '')
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 54bbd531398..593a3676519 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -491,6 +491,13 @@ module Gitlab
end
end
+ # Return total diverging commits count
+ def diverging_commit_count(from, to, max_count:)
+ wrapped_gitaly_errors do
+ gitaly_commit_client.diverging_commit_count(from, to, max_count: max_count)
+ end
+ end
+
# Mimic the `git clean` command and recursively delete untracked files.
# Valid keys that can be passed in the +options+ hash are:
#
diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb
index 4e46cb9f05c..ea12424eb4a 100644
--- a/lib/gitlab/gitaly_client/commit_service.rb
+++ b/lib/gitlab/gitaly_client/commit_service.rb
@@ -150,6 +150,17 @@ module Gitlab
GitalyClient.call(@repository.storage, :commit_service, :count_commits, request, timeout: GitalyClient.medium_timeout).count
end
+ def diverging_commit_count(from, to, max_count:)
+ request = Gitaly::CountDivergingCommitsRequest.new(
+ repository: @gitaly_repo,
+ from: encode_binary(from),
+ to: encode_binary(to),
+ max_count: max_count
+ )
+ response = GitalyClient.call(@repository.storage, :commit_service, :count_diverging_commits, request, timeout: GitalyClient.medium_timeout)
+ [response.left_count, response.right_count]
+ end
+
def list_last_commits_for_tree(revision, path, offset: 0, limit: 25)
request = Gitaly::ListLastCommitsForTreeRequest.new(
repository: @gitaly_repo,
diff --git a/lib/gitlab/github_import/importer/pull_request_importer.rb b/lib/gitlab/github_import/importer/pull_request_importer.rb
index ae7c4cf1b38..e294173f992 100644
--- a/lib/gitlab/github_import/importer/pull_request_importer.rb
+++ b/lib/gitlab/github_import/importer/pull_request_importer.rb
@@ -67,6 +67,36 @@ module Gitlab
def insert_git_data(merge_request, already_exists)
insert_or_replace_git_data(merge_request, pull_request.source_branch_sha, pull_request.target_branch_sha, already_exists)
+ # We need to create the branch after the merge request is
+ # populated to ensure the merge request is in the right state
+ # when the branch is created.
+ create_source_branch_if_not_exists(merge_request)
+ end
+
+ # An imported merge request will not be mergeable unless the
+ # source branch exists. For pull requests from forks, the source
+ # branch will be in the form of
+ # "github/fork/{project-name}/{source_branch}". This branch will never
+ # exist, so we create it here.
+ #
+ # Note that we only create the branch if the merge request is still open.
+ # For projects that have many pull requests, we assume that if it's closed
+ # the branch has already been deleted.
+ def create_source_branch_if_not_exists(merge_request)
+ return unless merge_request.open?
+
+ source_branch = pull_request.formatted_source_branch
+
+ return if project.repository.branch_exists?(source_branch)
+
+ project.repository.add_branch(merge_request.author, source_branch, pull_request.source_branch_sha)
+ rescue Gitlab::Git::CommandError => e
+ Gitlab::Sentry.track_acceptable_exception(e,
+ extra: {
+ source_branch: source_branch,
+ project_id: merge_request.project.id,
+ merge_request_id: merge_request.id
+ })
end
end
end
diff --git a/lib/gitlab/github_import/representation/pull_request.rb b/lib/gitlab/github_import/representation/pull_request.rb
index 593b491a837..0ccc4bfaed3 100644
--- a/lib/gitlab/github_import/representation/pull_request.rb
+++ b/lib/gitlab/github_import/representation/pull_request.rb
@@ -76,10 +76,10 @@ module Gitlab
# Returns a formatted source branch.
#
# For cross-project pull requests the branch name will be in the format
- # `owner-name:branch-name`.
+ # `github/fork/owner-name/branch-name`.
def formatted_source_branch
if cross_project? && source_repository_owner
- "#{source_repository_owner}:#{source_branch}"
+ "github/fork/#{source_repository_owner}/#{source_branch}"
elsif source_branch == target_branch
# Sometimes the source and target branch are the same, but GitLab
# doesn't support this. This can happen when both the user and
diff --git a/lib/gitlab/import_export/shared.rb b/lib/gitlab/import_export/shared.rb
index 947caaaefee..725c1101d70 100644
--- a/lib/gitlab/import_export/shared.rb
+++ b/lib/gitlab/import_export/shared.rb
@@ -61,7 +61,7 @@ module Gitlab
def log_base_data
{
importer: 'Import/Export',
- import_jid: @project&.import_state&.import_jid,
+ import_jid: @project&.import_state&.jid,
project_id: @project&.id,
project_path: @project&.full_path
}
diff --git a/lib/gitlab/middleware/rails_queue_duration.rb b/lib/gitlab/middleware/rails_queue_duration.rb
index 96c6a0a7d28..a147e165262 100644
--- a/lib/gitlab/middleware/rails_queue_duration.rb
+++ b/lib/gitlab/middleware/rails_queue_duration.rb
@@ -7,6 +7,8 @@
module Gitlab
module Middleware
class RailsQueueDuration
+ GITLAB_RAILS_QUEUE_DURATION_KEY = 'GITLAB_RAILS_QUEUE_DURATION'
+
def initialize(app)
@app = app
end
@@ -19,6 +21,7 @@ module Gitlab
duration = Time.now.to_f * 1_000 - proxy_start.to_f / 1_000_000
trans.set(:rails_queue_duration, duration)
metric_rails_queue_duration_seconds.observe(trans.labels, duration / 1_000)
+ env[GITLAB_RAILS_QUEUE_DURATION_KEY] = duration.round(2)
end
@app.call(env)
diff --git a/lib/gitlab/sql/recursive_cte.rb b/lib/gitlab/sql/recursive_cte.rb
index ec1f00a3a91..e45ac5d4765 100644
--- a/lib/gitlab/sql/recursive_cte.rb
+++ b/lib/gitlab/sql/recursive_cte.rb
@@ -48,7 +48,7 @@ module Gitlab
#
# alias_table - The Arel table to use as the alias.
def alias_to(alias_table)
- Arel::Nodes::As.new(table, alias_table)
+ Arel::Nodes::As.new(table, Arel::Table.new(alias_table.name.tr('.', '_')))
end
# Applies the CTE to the given relation, returning a new one that will
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index 6bfcf83f388..a65f4a8639c 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -90,8 +90,14 @@ module Gitlab
todos: count(Todo),
uploads: count(Upload),
web_hooks: count(WebHook)
- }.merge(services_usage).merge(approximate_counts)
- }
+ }
+ .merge(services_usage)
+ .merge(approximate_counts)
+ }.tap do |data|
+ if Feature.enabled?(:group_overview_security_dashboard)
+ data[:counts][:user_preferences] = user_preferences_usage
+ end
+ end
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -159,6 +165,10 @@ module Gitlab
}
end
+ def user_preferences_usage
+ {} # augmented in EE
+ end
+
def count(relation, fallback: -1)
relation.count
rescue ActiveRecord::StatementInvalid
diff --git a/lib/gitlab/wiki_file_finder.rb b/lib/gitlab/wiki_file_finder.rb
index 5303b3582ab..e9be6db50da 100644
--- a/lib/gitlab/wiki_file_finder.rb
+++ b/lib/gitlab/wiki_file_finder.rb
@@ -2,8 +2,6 @@
module Gitlab
class WikiFileFinder < FileFinder
- BATCH_SIZE = 100
-
attr_reader :repository
def initialize(project, ref)
@@ -19,7 +17,7 @@ module Gitlab
safe_query = Regexp.new(safe_query, Regexp::IGNORECASE)
filenames = repository.ls_files(ref)
- filenames.grep(safe_query).first(BATCH_SIZE)
+ filenames.grep(safe_query)
end
end
end
diff --git a/lib/tasks/gitlab/setup.rake b/lib/tasks/gitlab/setup.rake
index f71e69987cb..e876b23d43f 100644
--- a/lib/tasks/gitlab/setup.rake
+++ b/lib/tasks/gitlab/setup.rake
@@ -25,6 +25,11 @@ namespace :gitlab do
puts ""
end
+ # In production, we might want to prevent ourselves from shooting
+ # ourselves in the foot, so let's only do this in a test or
+ # development environment.
+ terminate_all_connections unless Rails.env.production?
+
Rake::Task["db:reset"].invoke
Rake::Task["add_limits_mysql"].invoke
Rake::Task["setup_postgresql"].invoke
@@ -33,4 +38,24 @@ namespace :gitlab do
puts "Quitting...".color(:red)
exit 1
end
+
+ # If there are any clients connected to the DB, PostgreSQL won't let
+ # you drop the database. It's possible that Sidekiq, Unicorn, or
+ # some other client will be hanging onto a connection, preventing
+ # the DROP DATABASE from working. To workaround this problem, this
+ # method terminates all the connections so that a subsequent DROP
+ # will work.
+ def self.terminate_all_connections
+ return false unless Gitlab::Database.postgresql?
+
+ cmd = <<~SQL
+ SELECT pg_terminate_backend(pg_stat_activity.pid)
+ FROM pg_stat_activity
+ WHERE datname = current_database()
+ AND pid <> pg_backend_pid();
+ SQL
+
+ ActiveRecord::Base.connection.execute(cmd)&.result_status == PG::PGRES_TUPLES_OK
+ rescue ActiveRecord::NoDatabaseError
+ end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 8d7ed7a5b88..7cbba0779f7 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -4722,6 +4722,9 @@ msgstr ""
msgid "More information is available|here"
msgstr ""
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr ""
diff --git a/package.json b/package.json
index c88ade87af6..3cd023c0b0c 100644
--- a/package.json
+++ b/package.json
@@ -28,7 +28,7 @@
"@babel/plugin-syntax-import-meta": "^7.2.0",
"@babel/preset-env": "^7.3.1",
"@gitlab/csslab": "^1.8.0",
- "@gitlab/svgs": "^1.51.0",
+ "@gitlab/svgs": "^1.52.0",
"@gitlab/ui": "^2.0.2",
"apollo-boost": "^0.1.20",
"apollo-client": "^2.4.5",
diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb
index 69a8e691ceb..01ac161d26d 100644
--- a/qa/qa/page/base.rb
+++ b/qa/qa/page/base.rb
@@ -24,7 +24,7 @@ module QA
end
end
- def with_retry(max_attempts: 3, reload: false)
+ def retry_until(max_attempts: 3, reload: false)
attempts = 0
while attempts < max_attempts
@@ -39,6 +39,21 @@ module QA
false
end
+ def retry_on_exception(max_attempts: 3, reload: false, sleep_interval: 0.0)
+ attempts = 0
+
+ begin
+ yield
+ rescue StandardError
+ sleep sleep_interval
+ refresh if reload
+ attempts += 1
+
+ retry if attempts < max_attempts
+ raise
+ end
+ end
+
def scroll_to(selector, text: nil)
page.execute_script <<~JS
var elements = Array.from(document.querySelectorAll('#{selector}'));
diff --git a/qa/qa/page/group/show.rb b/qa/qa/page/group/show.rb
index 6dd9ff997a4..9d6bd338027 100644
--- a/qa/qa/page/group/show.rb
+++ b/qa/qa/page/group/show.rb
@@ -45,15 +45,17 @@ module QA
private
def select_kind(kind)
- within_element(:new_project_or_subgroup_dropdown) do
- # May need to click again because it is possible to click the button quicker than the JS is bound
- wait(reload: false) do
- click_element :new_project_or_subgroup_dropdown_toggle
+ retry_on_exception(sleep_interval: 1.0) do
+ within_element(:new_project_or_subgroup_dropdown) do
+ # May need to click again because it is possible to click the button quicker than the JS is bound
+ wait(reload: false) do
+ click_element :new_project_or_subgroup_dropdown_toggle
- has_element?(kind)
- end
+ has_element?(kind)
+ end
- click_element kind
+ click_element kind
+ end
end
end
end
diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/main/menu.rb
index 616d50f47fc..55500e831c6 100644
--- a/qa/qa/page/main/menu.rb
+++ b/qa/qa/page/main/menu.rb
@@ -57,7 +57,7 @@ module QA
end
def go_to_profile_settings
- with_retry(reload: false) do
+ retry_until(reload: false) do
within_user_menu do
click_link 'Settings'
end
diff --git a/qa/qa/page/main/sign_up.rb b/qa/qa/page/main/sign_up.rb
index 9ca498012eb..46a105003d0 100644
--- a/qa/qa/page/main/sign_up.rb
+++ b/qa/qa/page/main/sign_up.rb
@@ -23,7 +23,7 @@ module QA
check_element :new_user_accept_terms if has_element?(:new_user_accept_terms)
- signed_in = with_retry do
+ signed_in = retry_until do
click_element :new_user_register_button
Page::Main::Menu.act { has_personal_area? }
diff --git a/qa/qa/page/project/web_ide/edit.rb b/qa/qa/page/project/web_ide/edit.rb
index a3e126b51da..2b6c01888d5 100644
--- a/qa/qa/page/project/web_ide/edit.rb
+++ b/qa/qa/page/project/web_ide/edit.rb
@@ -80,7 +80,7 @@ module QA
# Retry the attempt to click :commit_button just in case part of the
# animation is still in process even when the buttons have the
# expected visibility.
- commit_success_msg_shown = with_retry do
+ commit_success_msg_shown = retry_until do
click_element :commit_button
wait(reload: false) do
diff --git a/qa/spec/spec_helper.rb b/qa/spec/spec_helper.rb
index 3e7a6dd26ee..f1b4203d422 100644
--- a/qa/spec/spec_helper.rb
+++ b/qa/spec/spec_helper.rb
@@ -19,26 +19,16 @@ RSpec.configure do |config|
end
end
- config.before do |example|
- QA::Runtime::Logger.debug("Starting test: #{example.full_description}") if QA::Runtime::Env.debug?
-
- # If quarantine is tagged, skip tests that have other metadata unless
- # they're also tagged. This lets us run quarantined tests in a particular
- # category without running tests in other categories.
- # E.g., if a test is tagged 'smoke' and 'quarantine', and another is tagged
- # 'ldap' and 'quarantine', if we wanted to run just quarantined smoke tests
- # using `--tag quarantine --tag smoke`, without this check we'd end up
- # running that ldap test as well.
- if config.inclusion_filter[:quarantine]
- skip("Running tests tagged with all of #{config.inclusion_filter.rules.keys}") unless quarantine_and_optional_other_tag?(example, config)
+ config.before(:context) do
+ if self.class.metadata.keys.include?(:quarantine)
+ skip_or_run_quarantined_tests(self.class.metadata.keys, config.inclusion_filter.rules.keys)
end
end
- config.before(:each, :quarantine) do |example|
- # Skip tests in quarantine unless we explicitly focus on them
- # We could use an exclusion filter, but this way the test report will list
- # the quarantined tests when they're not run so that we're aware of them
- skip('In quarantine') unless config.inclusion_filter[:quarantine]
+ config.before do |example|
+ QA::Runtime::Logger.debug("Starting test: #{example.full_description}") if QA::Runtime::Env.debug?
+
+ skip_or_run_quarantined_tests(example.metadata.keys, config.inclusion_filter.rules.keys)
end
config.expect_with :rspec do |expectations|
@@ -57,18 +47,41 @@ RSpec.configure do |config|
Kernel.srand config.seed
end
+# Skip tests in quarantine unless we explicitly focus on them.
+# Skip the entire context if a context is tagged. This avoids running before
+# blocks unnecessarily.
+# If quarantine is focussed, skip tests/contexts that have other metadata
+# unless they're also focussed. This lets us run quarantined tests in a
+# particular category without running tests in other categories.
+# E.g., if a test is tagged 'smoke' and 'quarantine', and another is tagged
+# 'ldap' and 'quarantine', if we wanted to run just quarantined smoke tests
+# using `--tag quarantine --tag smoke`, without this check we'd end up
+# running that ldap test as well.
+# We could use an exclusion filter, but this way the test report will list
+# the quarantined tests when they're not run so that we're aware of them
+def skip_or_run_quarantined_tests(metadata_keys, filter_keys)
+ included_filters = filters_other_than_quarantine(filter_keys)
+
+ if filter_keys.include?(:quarantine)
+ skip("Only running tests tagged with :quarantine and any of #{included_filters}") unless quarantine_and_optional_other_tag?(metadata_keys, included_filters)
+ else
+ skip('In quarantine') if metadata_keys.include?(:quarantine)
+ end
+end
+
+def filters_other_than_quarantine(filter_keys)
+ filter_keys.reject { |key| key == :quarantine }
+end
+
# Checks if a test has the 'quarantine' tag and other tags in the inclusion filter.
#
# Returns true if
-# - the example metadata includes the quarantine tag
-# - and the metadata and inclusion filter both have any other tag
-# - or no other tags are in the inclusion filter
-def quarantine_and_optional_other_tag?(example, config)
- return false unless example.metadata.keys.include? :quarantine
-
- filters_other_than_quarantine = config.inclusion_filter.rules.keys.reject { |key| key == :quarantine }
-
- return true if filters_other_than_quarantine.empty?
+# - the metadata includes the quarantine tag
+# - and the metadata and inclusion filter both have any other tag
+# - or no other tags are in the inclusion filter
+def quarantine_and_optional_other_tag?(metadata_keys, included_filters)
+ return false unless metadata_keys.include? :quarantine
+ return true if included_filters.empty?
- filters_other_than_quarantine.any? { |key| example.metadata.keys.include? key }
+ included_filters.any? { |key| metadata_keys.include? key }
end
diff --git a/qa/spec/spec_helper_spec.rb b/qa/spec/spec_helper_spec.rb
index f001200fb52..2427999e110 100644
--- a/qa/spec/spec_helper_spec.rb
+++ b/qa/spec/spec_helper_spec.rb
@@ -10,79 +10,79 @@ describe 'rspec config tests' do
end
end
+ context 'default' do
+ it_behaves_like 'passing tests'
+ end
+
context 'foo', :foo do
it_behaves_like 'passing tests'
end
- context 'default' do
+ context 'quarantine', :quarantine do
+ it_behaves_like 'passing tests'
+ end
+
+ context 'bar quarantine', :bar, :quarantine do
it_behaves_like 'passing tests'
end
end
end
- context 'default config' do
- it 'tests are skipped if in quarantine' do
+ context 'with no tags focussed' do
+ before do
group.run
+ end
- foo_context = group.children.find { |c| c.description == "foo" }
- foo_examples = foo_context.descendant_filtered_examples
- expect(foo_examples.count).to eq(2)
-
- ex = foo_examples.find { |e| e.description == "not in quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
+ context 'in a context tagged :foo' do
+ it 'skips tests in quarantine' do
+ context = group.children.find { |c| c.description == "foo" }
+ examples = context.descendant_filtered_examples
+ expect(examples.count).to eq(2)
- ex = foo_examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
- expect(ex.execution_result.pending_message).to eq('In quarantine')
+ ex = examples.find { |e| e.description == "not in quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
- default_context = group.children.find { |c| c.description == "default" }
- default_examples = default_context.descendant_filtered_examples
- expect(default_examples.count).to eq(2)
+ ex = examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
+ expect(ex.execution_result.pending_message).to eq('In quarantine')
+ end
+ end
- ex = default_examples.find { |e| e.description == "not in quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
+ context 'in an untagged context' do
+ it 'skips tests in quarantine' do
+ context = group.children.find { |c| c.description == "default" }
+ examples = context.descendant_filtered_examples
+ expect(examples.count).to eq(2)
- ex = default_examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
- expect(ex.execution_result.pending_message).to eq('In quarantine')
- end
- end
+ ex = examples.find { |e| e.description == "not in quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
- context "with 'quarantine' tagged" do
- before do
- RSpec.configure do |config|
- config.inclusion_filter = :quarantine
- end
- end
- after do
- RSpec.configure do |config|
- config.inclusion_filter.clear
+ ex = examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
+ expect(ex.execution_result.pending_message).to eq('In quarantine')
end
end
- it "only quarantined tests are run" do
- group.run
-
- foo_context = group.children.find { |c| c.description == "foo" }
- foo_examples = foo_context.descendant_filtered_examples
- expect(foo_examples.count).to be(1)
-
- ex = foo_examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
+ context 'in a context tagged :quarantine' do
+ it 'skips all tests' do
+ context = group.children.find { |c| c.description == "quarantine" }
+ examples = context.descendant_filtered_examples
+ expect(examples.count).to eq(2)
- default_context = group.children.find { |c| c.description == "default" }
- default_examples = default_context.descendant_filtered_examples
- expect(default_examples.count).to be(1)
+ ex = examples.find { |e| e.description == "not in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
- ex = default_examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
+ ex = examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
+ expect(ex.execution_result.pending_message).to eq('In quarantine')
+ end
end
end
- context "with 'foo' tagged" do
+ context 'with :quarantine focussed' do
before do
RSpec.configure do |config|
- config.inclusion_filter = :foo
+ config.inclusion_filter = :quarantine
end
group.run
@@ -93,30 +93,50 @@ describe 'rspec config tests' do
end
end
- it "tests are not run if not tagged 'foo'" do
- default_context = group.children.find { |c| c.description == "default" }
- expect(default_context.descendant_filtered_examples.count).to eq(0)
+ context 'in an untagged context' do
+ it 'only runs quarantined tests' do
+ context = group.children.find { |c| c.description == "default" }
+ examples = context.descendant_filtered_examples
+ expect(examples.count).to be(1)
+
+ ex = examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
+ end
end
- it "tests are skipped if in quarantine" do
- foo_context = group.children.find { |c| c.description == "foo" }
- foo_examples = foo_context.descendant_filtered_examples
- expect(foo_examples.count).to eq(2)
+ context 'in a context tagged :foo' do
+ it 'only runs quarantined tests' do
+ context = group.children.find { |c| c.description == "foo" }
+ examples = context.descendant_filtered_examples
+ expect(examples.count).to be(1)
- ex = foo_examples.find { |e| e.description == "not in quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
+ ex = examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
+ end
+ end
+
+ context 'in a context tagged :quarantine' do
+ it 'runs all tests' do
+ context = group.children.find { |c| c.description == "quarantine" }
+ examples = context.descendant_filtered_examples
+ expect(examples.count).to be(2)
+
+ ex = examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
- ex = foo_examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
- expect(ex.execution_result.pending_message).to eq('In quarantine')
+ ex = examples.find { |e| e.description == "not in quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
+ end
end
end
- context "with 'quarantine' and 'foo' tagged" do
+ context 'with a non-quarantine tag (:foo) focussed' do
before do
RSpec.configure do |config|
- config.inclusion_filter = { quarantine: true, foo: true }
+ config.inclusion_filter = :foo
end
+
+ group.run
end
after do
RSpec.configure do |config|
@@ -124,38 +144,43 @@ describe 'rspec config tests' do
end
end
- it 'of tests tagged foo, only tests in quarantine run' do
- group.run
+ context 'in an untagged context' do
+ it 'runs no tests' do
+ context = group.children.find { |c| c.description == "default" }
+ expect(context.descendant_filtered_examples.count).to eq(0)
+ end
+ end
- foo_context = group.children.find { |c| c.description == "foo" }
- foo_examples = foo_context.descendant_filtered_examples
- expect(foo_examples.count).to eq(2)
+ context 'in a context tagged :foo' do
+ it 'skips quarantined tests' do
+ context = group.children.find { |c| c.description == "foo" }
+ examples = context.descendant_filtered_examples
+ expect(examples.count).to be(2)
- ex = foo_examples.find { |e| e.description == "not in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
- expect(ex.execution_result.pending_message).to eq('Running tests tagged with all of [:quarantine, :foo]')
+ ex = examples.find { |e| e.description == "not in quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
- ex = foo_examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
+ ex = examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
+ expect(ex.execution_result.pending_message).to eq('In quarantine')
+ end
end
- it 'if tests are not tagged they are skipped, even if they are in quarantine' do
- group.run
- default_context = group.children.find { |c| c.description == "default" }
- default_examples = default_context.descendant_filtered_examples
- expect(default_examples.count).to eq(1)
-
- ex = default_examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
- expect(ex.execution_result.pending_message).to eq('Running tests tagged with all of [:quarantine, :foo]')
+ context 'in a context tagged :quarantine' do
+ it 'runs no tests' do
+ context = group.children.find { |c| c.description == "quarantine" }
+ expect(context.descendant_filtered_examples.count).to eq(0)
+ end
end
end
- context "with 'foo' and 'bar' tagged" do
+ context 'with :quarantine and a non-quarantine tag (:foo) focussed' do
before do
RSpec.configure do |config|
- config.inclusion_filter = { bar: true, foo: true }
+ config.inclusion_filter = { quarantine: true, foo: true }
end
+
+ group.run
end
after do
RSpec.configure do |config|
@@ -163,67 +188,67 @@ describe 'rspec config tests' do
end
end
- it "runs tests tagged either 'foo' or 'bar'" do
- group = RSpec.describe do
- example 'foo', :foo do
- end
- example 'bar', :bar do
- end
- example 'foo and bar', :foo, :bar do
- end
- end
-
- group.run
- expect(group.examples.count).to eq(3)
+ context 'in an untagged context' do
+ it 'ignores untagged tests and skips tests even if in quarantine' do
+ context = group.children.find { |c| c.description == "default" }
+ examples = context.descendant_filtered_examples
+ expect(examples.count).to eq(1)
- ex = group.examples.find { |e| e.description == "foo" }
- expect(ex.execution_result.status).to eq(:passed)
+ ex = examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
+ end
+ end
- ex = group.examples.find { |e| e.description == "bar" }
- expect(ex.execution_result.status).to eq(:passed)
+ context 'in a context tagged :foo' do
+ it 'only runs quarantined tests' do
+ context = group.children.find { |c| c.description == "foo" }
+ examples = context.descendant_filtered_examples
+ expect(examples.count).to be(2)
- ex = group.examples.find { |e| e.description == "foo and bar" }
- expect(ex.execution_result.status).to eq(:passed)
- end
+ ex = examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
- it "skips quarantined tests tagged 'foo' and/or 'bar'" do
- group = RSpec.describe do
- example 'foo in quarantine', :foo, :quarantine do
- end
- example 'foo and bar in quarantine', :foo, :bar, :quarantine do
- end
+ ex = examples.find { |e| e.description == "not in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
end
+ end
- group.run
- expect(group.examples.count).to eq(2)
+ context 'in a context tagged :quarantine' do
+ it 'skips all tests' do
+ context = group.children.find { |c| c.description == "quarantine" }
+ examples = context.descendant_filtered_examples
+ expect(examples.count).to be(2)
- ex = group.examples.find { |e| e.description == "foo in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
- expect(ex.execution_result.pending_message).to eq('In quarantine')
+ ex = examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
- ex = group.examples.find { |e| e.description == "foo and bar in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
- expect(ex.execution_result.pending_message).to eq('In quarantine')
+ ex = examples.find { |e| e.description == "not in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
+ end
end
- it "ignores quarantined tests not tagged either 'foo' or 'bar'" do
- group = RSpec.describe do
- example 'in quarantine', :quarantine do
- end
- end
+ context 'in a context tagged :bar and :quarantine' do
+ it 'skips all tests' do
+ context = group.children.find { |c| c.description == "quarantine" }
+ examples = context.descendant_filtered_examples
+ expect(examples.count).to be(2)
- group.run
+ ex = examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
- ex = group.examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to be_nil
+ ex = examples.find { |e| e.description == "not in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
+ end
end
end
- context "with 'foo' and 'bar' and 'quarantined' tagged" do
+ context 'with :quarantine and multiple non-quarantine tags focussed' do
before do
RSpec.configure do |config|
config.inclusion_filter = { bar: true, foo: true, quarantine: true }
end
+
+ group.run
end
after do
RSpec.configure do |config|
@@ -231,34 +256,49 @@ describe 'rspec config tests' do
end
end
- it "runs tests tagged 'quarantine' and 'foo' or 'bar'" do
- group = RSpec.describe do
- example 'foo', :foo do
- end
- example 'bar and quarantine', :bar, :quarantine do
- end
- example 'foo and bar', :foo, :bar do
- end
- example 'foo, bar, and quarantine', :foo, :bar, :quarantine do
- end
+ context 'in a context tagged :foo' do
+ it 'only runs quarantined tests' do
+ context = group.children.find { |c| c.description == "foo" }
+ examples = context.descendant_filtered_examples
+ expect(examples.count).to be(2)
+
+ ex = examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
+
+ ex = examples.find { |e| e.description == "not in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
+ expect(ex.execution_result.pending_message).to eq('Only running tests tagged with :quarantine and any of [:bar, :foo]')
end
+ end
- group.run
- expect(group.examples.count).to eq(4)
+ context 'in a context tagged :quarantine' do
+ it 'skips all tests' do
+ context = group.children.find { |c| c.description == "quarantine" }
+ examples = context.descendant_filtered_examples
+ expect(examples.count).to be(2)
- ex = group.examples.find { |e| e.description == "foo" }
- expect(ex.execution_result.status).to eq(:pending)
- expect(ex.execution_result.pending_message).to eq('Running tests tagged with all of [:bar, :foo, :quarantine]')
+ ex = examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
+ expect(ex.execution_result.pending_message).to eq('Only running tests tagged with :quarantine and any of [:bar, :foo]')
- ex = group.examples.find { |e| e.description == "bar and quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
+ ex = examples.find { |e| e.description == "not in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
+ expect(ex.execution_result.pending_message).to eq('Only running tests tagged with :quarantine and any of [:bar, :foo]')
+ end
+ end
- ex = group.examples.find { |e| e.description == "foo and bar" }
- expect(ex.execution_result.status).to eq(:pending)
- expect(ex.execution_result.pending_message).to eq('Running tests tagged with all of [:bar, :foo, :quarantine]')
+ context 'in a context tagged :bar and :quarantine' do
+ it 'runs all tests' do
+ context = group.children.find { |c| c.description == "bar quarantine" }
+ examples = context.descendant_filtered_examples
+ expect(examples.count).to be(2)
- ex = group.examples.find { |e| e.description == "foo, bar, and quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
+ ex = examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
+
+ ex = examples.find { |e| e.description == "not in quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
+ end
end
end
end
diff --git a/scripts/security-harness b/scripts/security-harness
index c60b3410095..8369cf06223 100755
--- a/scripts/security-harness
+++ b/scripts/security-harness
@@ -37,7 +37,7 @@ else
end
__END__
-#!/bin/sh
+#!/bin/bash
set -e
diff --git a/spec/controllers/concerns/send_file_upload_spec.rb b/spec/controllers/concerns/send_file_upload_spec.rb
index a07113a6156..cf3b24f50a3 100644
--- a/spec/controllers/concerns/send_file_upload_spec.rb
+++ b/spec/controllers/concerns/send_file_upload_spec.rb
@@ -52,6 +52,23 @@ describe SendFileUpload do
end
end
+ context 'with inline image' do
+ let(:filename) { 'test.png' }
+ let(:params) { { disposition: 'inline', attachment: filename } }
+
+ it 'sends a file with inline disposition' do
+ # Notice the filename= is omitted from the disposition; this is because
+ # Rails 5 will append this header in send_file
+ expected_params = {
+ filename: 'test.png',
+ disposition: "inline; filename*=UTF-8''test.png"
+ }
+ expect(controller).to receive(:send_file).with(uploader.path, expected_params)
+
+ subject
+ end
+ end
+
context 'with attachment' do
let(:filename) { 'test.js' }
let(:params) { { attachment: filename } }
diff --git a/spec/controllers/help_controller_spec.rb b/spec/controllers/help_controller_spec.rb
index 5cb284e7e2d..dca67c18caa 100644
--- a/spec/controllers/help_controller_spec.rb
+++ b/spec/controllers/help_controller_spec.rb
@@ -37,6 +37,46 @@ describe HelpController do
expect(assigns[:help_index]).to eq '[external](https://some.external.link)'
end
end
+
+ context 'when relative url with external on same line' do
+ it 'prefix it with /help/' do
+ stub_readme("[API](api/README.md) [external](https://some.external.link)")
+
+ get :index
+
+ expect(assigns[:help_index]).to eq '[API](/help/api/README.md) [external](https://some.external.link)'
+ end
+ end
+
+ context 'when relative url with http:// in query' do
+ it 'prefix it with /help/' do
+ stub_readme("[API](api/README.md?go=https://example.com/)")
+
+ get :index
+
+ expect(assigns[:help_index]).to eq '[API](/help/api/README.md?go=https://example.com/)'
+ end
+ end
+
+ context 'when mailto URL' do
+ it 'do not change it' do
+ stub_readme("[report bug](mailto:bugs@example.com)")
+
+ get :index
+
+ expect(assigns[:help_index]).to eq '[report bug](mailto:bugs@example.com)'
+ end
+ end
+
+ context 'when protocol-relative link' do
+ it 'do not change it' do
+ stub_readme("[protocol-relative](//example.com)")
+
+ get :index
+
+ expect(assigns[:help_index]).to eq '[protocol-relative](//example.com)'
+ end
+ end
end
describe 'GET #show' do
diff --git a/spec/controllers/projects/pages_domains_controller_spec.rb b/spec/controllers/projects/pages_domains_controller_spec.rb
index 8b7f7587701..ffb9867a203 100644
--- a/spec/controllers/projects/pages_domains_controller_spec.rb
+++ b/spec/controllers/projects/pages_domains_controller_spec.rb
@@ -23,12 +23,27 @@ describe Projects::PagesDomainsController do
end
describe 'GET show' do
- it "displays the 'show' page" do
+ def make_request
get(:show, params: request_params.merge(id: pages_domain.domain))
+ end
+ it "displays the 'show' page" do
+ make_request
expect(response).to have_gitlab_http_status(200)
expect(response).to render_template('show')
end
+
+ context 'when user is developer' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'renders 404 page' do
+ make_request
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
end
describe 'GET new' do
diff --git a/spec/features/dashboard/todos/todos_spec.rb b/spec/features/dashboard/todos/todos_spec.rb
index 51f158d3045..fd8677feab5 100644
--- a/spec/features/dashboard/todos/todos_spec.rb
+++ b/spec/features/dashboard/todos/todos_spec.rb
@@ -126,7 +126,7 @@ describe 'Dashboard Todos' do
it 'shows you added a todo message' do
page.within('.js-todos-all') do
- expect(page).to have_content("You added a todo for issue #{issue.to_reference(full: true)}")
+ expect(page).to have_content("You added a todo for issue #{issue.to_reference(full: true)}")
expect(page).not_to have_content('to yourself')
end
end
@@ -140,7 +140,7 @@ describe 'Dashboard Todos' do
it 'shows you mentioned yourself message' do
page.within('.js-todos-all') do
- expect(page).to have_content("You mentioned yourself on issue #{issue.to_reference(full: true)}")
+ expect(page).to have_content("You mentioned yourself on issue #{issue.to_reference(full: true)}")
expect(page).not_to have_content('to yourself')
end
end
@@ -154,7 +154,7 @@ describe 'Dashboard Todos' do
it 'shows you directly addressed yourself message' do
page.within('.js-todos-all') do
- expect(page).to have_content("You directly addressed yourself on issue #{issue.to_reference(full: true)}")
+ expect(page).to have_content("You directly addressed yourself on issue #{issue.to_reference(full: true)}")
expect(page).not_to have_content('to yourself')
end
end
@@ -170,7 +170,7 @@ describe 'Dashboard Todos' do
it 'shows you set yourself as an approver message' do
page.within('.js-todos-all') do
- expect(page).to have_content("You set yourself as an approver for merge request #{merge_request.to_reference(full: true)}")
+ expect(page).to have_content("You set yourself as an approver for merge request #{merge_request.to_reference(full: true)}")
expect(page).not_to have_content('to yourself')
end
end
diff --git a/spec/features/projects/branches/user_views_branches_spec.rb b/spec/features/projects/branches/user_views_branches_spec.rb
index 62ae793151c..777d30fdffd 100644
--- a/spec/features/projects/branches/user_views_branches_spec.rb
+++ b/spec/features/projects/branches/user_views_branches_spec.rb
@@ -15,6 +15,8 @@ describe "User views branches" do
it "shows branches" do
expect(page).to have_content("Branches").and have_content("master")
+
+ expect(page.all(".graph-side")).to all( have_content(/\d+/) )
end
end
diff --git a/spec/fixtures/api/schemas/public_api/v4/group_labels.json b/spec/fixtures/api/schemas/public_api/v4/group_labels.json
index f6c327abfdd..fbde45f2904 100644
--- a/spec/fixtures/api/schemas/public_api/v4/group_labels.json
+++ b/spec/fixtures/api/schemas/public_api/v4/group_labels.json
@@ -6,6 +6,7 @@
"id" : { "type": "integer" },
"name" : { "type": "string "},
"color" : { "type": "string "},
+ "text_color" : { "type": "string "},
"description" : { "type": "string "},
"open_issues_count" : { "type": "integer "},
"closed_issues_count" : { "type": "integer "},
diff --git a/spec/fixtures/api/schemas/public_api/v4/milestone.json b/spec/fixtures/api/schemas/public_api/v4/milestone.json
index 6ca2e88ae91..971f7980f46 100644
--- a/spec/fixtures/api/schemas/public_api/v4/milestone.json
+++ b/spec/fixtures/api/schemas/public_api/v4/milestone.json
@@ -8,6 +8,7 @@
"title": { "type": "string" },
"description": { "type": ["string", "null"] },
"state": { "type": "string" },
+ "percentage_complete": { "type": "integer" },
"created_at": { "type": "date" },
"updated_at": { "type": "date" },
"start_date": { "type": "date" },
diff --git a/spec/javascripts/diffs/store/utils_spec.js b/spec/javascripts/diffs/store/utils_spec.js
index c5e413a29d8..baf6e111f9f 100644
--- a/spec/javascripts/diffs/store/utils_spec.js
+++ b/spec/javascripts/diffs/store/utils_spec.js
@@ -14,7 +14,7 @@ import { MERGE_REQUEST_NOTEABLE_TYPE } from '~/notes/constants';
import diffFileMockData from '../mock_data/diff_file';
import { noteableDataMock } from '../../notes/mock_data';
-const getDiffFileMock = () => Object.assign({}, diffFileMockData);
+const getDiffFileMock = () => JSON.parse(JSON.stringify(diffFileMockData));
describe('DiffsStoreUtils', () => {
describe('findDiffFile', () => {
@@ -80,30 +80,44 @@ describe('DiffsStoreUtils', () => {
});
describe('addContextLines', () => {
- it('should add context lines properly with bottom parameter', () => {
+ it('should add context lines', () => {
const diffFile = getDiffFileMock();
const inlineLines = diffFile.highlighted_diff_lines;
const parallelLines = diffFile.parallel_diff_lines;
const lineNumbers = { oldLineNumber: 3, newLineNumber: 5 };
- const contextLines = [{ lineNumber: 42 }];
- const options = { inlineLines, parallelLines, contextLines, lineNumbers, bottom: true };
+ const contextLines = [{ lineNumber: 42, line_code: '123' }];
+ const options = { inlineLines, parallelLines, contextLines, lineNumbers };
const inlineIndex = utils.findIndexInInlineLines(inlineLines, lineNumbers);
const parallelIndex = utils.findIndexInParallelLines(parallelLines, lineNumbers);
const normalizedParallelLine = {
left: options.contextLines[0],
right: options.contextLines[0],
+ line_code: '123',
};
utils.addContextLines(options);
- expect(inlineLines[inlineLines.length - 1]).toEqual(contextLines[0]);
- expect(parallelLines[parallelLines.length - 1]).toEqual(normalizedParallelLine);
+ expect(inlineLines[inlineIndex]).toEqual(contextLines[0]);
+ expect(parallelLines[parallelIndex]).toEqual(normalizedParallelLine);
+ });
+
+ it('should add context lines properly with bottom parameter', () => {
+ const diffFile = getDiffFileMock();
+ const inlineLines = diffFile.highlighted_diff_lines;
+ const parallelLines = diffFile.parallel_diff_lines;
+ const lineNumbers = { oldLineNumber: 3, newLineNumber: 5 };
+ const contextLines = [{ lineNumber: 42, line_code: '123' }];
+ const options = { inlineLines, parallelLines, contextLines, lineNumbers, bottom: true };
+ const normalizedParallelLine = {
+ left: options.contextLines[0],
+ right: options.contextLines[0],
+ line_code: '123',
+ };
- delete options.bottom;
utils.addContextLines(options);
- expect(inlineLines[inlineIndex]).toEqual(contextLines[0]);
- expect(parallelLines[parallelIndex]).toEqual(normalizedParallelLine);
+ expect(inlineLines[inlineLines.length - 1]).toEqual(contextLines[0]);
+ expect(parallelLines[parallelLines.length - 1]).toEqual(normalizedParallelLine);
});
});
diff --git a/spec/javascripts/notes/components/note_actions_spec.js b/spec/javascripts/notes/components/note_actions_spec.js
index 0c1962912b4..d604e90b529 100644
--- a/spec/javascripts/notes/components/note_actions_spec.js
+++ b/spec/javascripts/notes/components/note_actions_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount, createLocalVue, createWrapper } from '@vue/test-utils';
import createStore from '~/notes/stores';
import noteActions from '~/notes/components/note_actions.vue';
import { TEST_HOST } from 'spec/test_constants';
@@ -10,7 +10,7 @@ describe('noteActions', () => {
let store;
let props;
- const createWrapper = propsData => {
+ const shallowMountNoteActions = propsData => {
const localVue = createLocalVue();
return shallowMount(noteActions, {
store,
@@ -44,7 +44,7 @@ describe('noteActions', () => {
beforeEach(() => {
store.dispatch('setUserData', userDataMock);
- wrapper = createWrapper(props);
+ wrapper = shallowMountNoteActions(props);
});
it('should render access level badge', () => {
@@ -90,13 +90,27 @@ describe('noteActions', () => {
it('should be possible to delete comment', () => {
expect(wrapper.find('.js-note-delete').exists()).toBe(true);
});
+
+ it('closes tooltip when dropdown opens', done => {
+ wrapper.find('.more-actions-toggle').trigger('click');
+
+ const rootWrapper = createWrapper(wrapper.vm.$root);
+ Vue.nextTick()
+ .then(() => {
+ const emitted = Object.keys(rootWrapper.emitted());
+
+ expect(emitted).toEqual(['bv::hide::tooltip']);
+ done();
+ })
+ .catch(done.fail);
+ });
});
});
describe('user is not logged in', () => {
beforeEach(() => {
store.dispatch('setUserData', {});
- wrapper = createWrapper({
+ wrapper = shallowMountNoteActions({
...props,
canDelete: false,
canEdit: false,
@@ -127,7 +141,7 @@ describe('noteActions', () => {
describe('for showReply = true', () => {
beforeEach(() => {
- wrapper = createWrapper({
+ wrapper = shallowMountNoteActions({
...props,
showReply: true,
});
@@ -142,7 +156,7 @@ describe('noteActions', () => {
describe('for showReply = false', () => {
beforeEach(() => {
- wrapper = createWrapper({
+ wrapper = shallowMountNoteActions({
...props,
showReply: false,
});
@@ -169,7 +183,7 @@ describe('noteActions', () => {
describe('for showReply = true', () => {
beforeEach(() => {
- wrapper = createWrapper({
+ wrapper = shallowMountNoteActions({
...props,
showReply: true,
});
@@ -184,7 +198,7 @@ describe('noteActions', () => {
describe('for showReply = false', () => {
beforeEach(() => {
- wrapper = createWrapper({
+ wrapper = shallowMountNoteActions({
...props,
showReply: false,
});
diff --git a/spec/javascripts/notes/components/noteable_note_spec.js b/spec/javascripts/notes/components/noteable_note_spec.js
index 8ade6fc2ced..9420713ceca 100644
--- a/spec/javascripts/notes/components/noteable_note_spec.js
+++ b/spec/javascripts/notes/components/noteable_note_spec.js
@@ -1,86 +1,138 @@
-import $ from 'jquery';
import _ from 'underscore';
-import Vue from 'vue';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
import createStore from '~/notes/stores';
import issueNote from '~/notes/components/noteable_note.vue';
+import NoteHeader from '~/notes/components/note_header.vue';
+import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
+import NoteActions from '~/notes/components/note_actions.vue';
+import NoteBody from '~/notes/components/note_body.vue';
import { noteableDataMock, notesDataMock, note } from '../mock_data';
describe('issue_note', () => {
let store;
- let vm;
+ let wrapper;
beforeEach(() => {
- const Component = Vue.extend(issueNote);
-
store = createStore();
store.dispatch('setNoteableData', noteableDataMock);
store.dispatch('setNotesData', notesDataMock);
- vm = new Component({
+ const localVue = createLocalVue();
+ wrapper = shallowMount(issueNote, {
store,
propsData: {
note,
},
- }).$mount();
+ sync: false,
+ localVue,
+ });
});
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
});
it('should render user information', () => {
- expect(vm.$el.querySelector('.user-avatar-link img').getAttribute('src')).toEqual(
- note.author.avatar_url,
- );
+ const { author } = note;
+ const avatar = wrapper.find(UserAvatarLink);
+ const avatarProps = avatar.props();
+
+ expect(avatarProps.linkHref).toBe(author.path);
+ expect(avatarProps.imgSrc).toBe(author.avatar_url);
+ expect(avatarProps.imgAlt).toBe(author.name);
+ expect(avatarProps.imgSize).toBe(40);
});
it('should render note header content', () => {
- const el = vm.$el.querySelector('.note-header .note-header-author-name');
+ const noteHeader = wrapper.find(NoteHeader);
+ const noteHeaderProps = noteHeader.props();
- expect(el.textContent.trim()).toEqual(note.author.name);
+ expect(noteHeaderProps.author).toEqual(note.author);
+ expect(noteHeaderProps.createdAt).toEqual(note.created_at);
+ expect(noteHeaderProps.noteId).toEqual(note.id);
});
it('should render note actions', () => {
- expect(vm.$el.querySelector('.note-actions')).toBeDefined();
+ const { author } = note;
+ const noteActions = wrapper.find(NoteActions);
+ const noteActionsProps = noteActions.props();
+
+ expect(noteActionsProps.authorId).toBe(author.id);
+ expect(noteActionsProps.noteId).toBe(note.id);
+ expect(noteActionsProps.noteUrl).toBe(note.noteable_note_url);
+ expect(noteActionsProps.accessLevel).toBe(note.human_access);
+ expect(noteActionsProps.canEdit).toBe(note.current_user.can_edit);
+ expect(noteActionsProps.canAwardEmoji).toBe(note.current_user.can_award_emoji);
+ expect(noteActionsProps.canDelete).toBe(note.current_user.can_edit);
+ expect(noteActionsProps.canReportAsAbuse).toBe(true);
+ expect(noteActionsProps.canResolve).toBe(false);
+ expect(noteActionsProps.reportAbusePath).toBe(note.report_abuse_path);
+ expect(noteActionsProps.resolvable).toBe(false);
+ expect(noteActionsProps.isResolved).toBe(false);
+ expect(noteActionsProps.isResolving).toBe(false);
+ expect(noteActionsProps.resolvedBy).toEqual({});
});
it('should render issue body', () => {
- expect(vm.$el.querySelector('.note-text').innerHTML).toEqual(note.note_html);
+ const noteBody = wrapper.find(NoteBody);
+ const noteBodyProps = noteBody.props();
+
+ expect(noteBodyProps.note).toEqual(note);
+ expect(noteBodyProps.line).toBe(null);
+ expect(noteBodyProps.canEdit).toBe(note.current_user.can_edit);
+ expect(noteBodyProps.isEditing).toBe(false);
+ expect(noteBodyProps.helpPagePath).toBe('');
});
it('prevents note preview xss', done => {
const imgSrc = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
const noteBody = `<img src="${imgSrc}" onload="alert(1)" />`;
const alertSpy = spyOn(window, 'alert');
- vm.updateNote = () => new Promise($.noop);
+ store.hotUpdate({
+ actions: {
+ updateNote() {},
+ },
+ });
+ const noteBodyComponent = wrapper.find(NoteBody);
- vm.formUpdateHandler(noteBody, null, $.noop);
+ noteBodyComponent.vm.$emit('handleFormUpdate', noteBody, null, () => {});
setTimeout(() => {
expect(alertSpy).not.toHaveBeenCalled();
- expect(vm.note.note_html).toEqual(_.escape(noteBody));
+ expect(wrapper.vm.note.note_html).toEqual(_.escape(noteBody));
done();
}, 0);
});
describe('cancel edit', () => {
it('restores content of updated note', done => {
- const noteBody = 'updated note text';
- vm.updateNote = () => Promise.resolve();
-
- vm.formUpdateHandler(noteBody, null, $.noop);
-
- setTimeout(() => {
- expect(vm.note.note_html).toEqual(noteBody);
-
- vm.formCancelHandler();
-
- setTimeout(() => {
- expect(vm.note.note_html).toEqual(noteBody);
-
- done();
- });
+ const updatedText = 'updated note text';
+ store.hotUpdate({
+ actions: {
+ updateNote() {},
+ },
});
+ const noteBody = wrapper.find(NoteBody);
+ noteBody.vm.resetAutoSave = () => {};
+
+ noteBody.vm.$emit('handleFormUpdate', updatedText, null, () => {});
+
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ const noteBodyProps = noteBody.props();
+
+ expect(noteBodyProps.note.note_html).toBe(updatedText);
+ noteBody.vm.$emit('cancelForm');
+ })
+ .then(() => wrapper.vm.$nextTick())
+ .then(() => {
+ const noteBodyProps = noteBody.props();
+
+ expect(noteBodyProps.note.note_html).toBe(note.note_html);
+ })
+ .then(done)
+ .catch(done.fail);
});
});
});
diff --git a/spec/javascripts/vue_shared/components/user_avatar/user_avatar_link_spec.js b/spec/javascripts/vue_shared/components/user_avatar/user_avatar_link_spec.js
index f2472fd377c..80aa75847ae 100644
--- a/spec/javascripts/vue_shared/components/user_avatar/user_avatar_link_spec.js
+++ b/spec/javascripts/vue_shared/components/user_avatar/user_avatar_link_spec.js
@@ -1,13 +1,14 @@
import _ from 'underscore';
import Vue from 'vue';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
+import { TEST_HOST } from 'spec/test_constants';
describe('User Avatar Link Component', function() {
beforeEach(function() {
this.propsData = {
- linkHref: 'myavatarurl.com',
+ linkHref: `${TEST_HOST}/myavatarurl.com`,
imgSize: 99,
- imgSrc: 'myavatarurl.com',
+ imgSrc: `${TEST_HOST}/myavatarurl.com`,
imgAlt: 'mydisplayname',
imgCssClasses: 'myextraavatarclass',
tooltipText: 'tooltip text',
@@ -37,11 +38,18 @@ describe('User Avatar Link Component', function() {
});
it('should render <a> as a child element', function() {
- expect(this.userAvatarLink.$el.tagName).toBe('A');
+ const link = this.userAvatarLink.$el;
+
+ expect(link.tagName).toBe('A');
+ expect(link.href).toBe(this.propsData.linkHref);
});
- it('should have <img> as a child element', function() {
- expect(this.userAvatarLink.$el.querySelector('img')).not.toBeNull();
+ it('renders imgSrc with imgSize as image', function() {
+ const { imgSrc, imgSize } = this.propsData;
+ const image = this.userAvatarLink.$el.querySelector('img');
+
+ expect(image).not.toBeNull();
+ expect(image.src).toBe(`${imgSrc}?width=${imgSize}`);
});
it('should return necessary props as defined', function() {
diff --git a/spec/lib/banzai/filter/footnote_filter_spec.rb b/spec/lib/banzai/filter/footnote_filter_spec.rb
index 2e50e4e2351..c6dcb4e46fd 100644
--- a/spec/lib/banzai/filter/footnote_filter_spec.rb
+++ b/spec/lib/banzai/filter/footnote_filter_spec.rb
@@ -11,6 +11,7 @@ describe Banzai::Filter::FootnoteFilter do
let(:footnote) do
<<~EOF
<p>first<sup><a href="#fn1" id="fnref1">1</a></sup> and second<sup><a href="#fn2" id="fnref2">2</a></sup></p>
+ <p>same reference<sup><a href="#fn1" id="fnref1">1</a></sup></p>
<ol>
<li id="fn1">
<p>one <a href="#fnref1">↩</a></p>
@@ -25,6 +26,7 @@ describe Banzai::Filter::FootnoteFilter do
let(:filtered_footnote) do
<<~EOF
<p>first<sup class="footnote-ref"><a href="#fn1-#{identifier}" id="fnref1-#{identifier}">1</a></sup> and second<sup class="footnote-ref"><a href="#fn2-#{identifier}" id="fnref2-#{identifier}">2</a></sup></p>
+ <p>same reference<sup class="footnote-ref"><a href="#fn1-#{identifier}" id="fnref1-#{identifier}">1</a></sup></p>
<section class="footnotes"><ol>
<li id="fn1-#{identifier}">
<p>one <a href="#fnref1-#{identifier}" class="footnote-backref">↩</a></p>
diff --git a/spec/lib/gitlab/ci/build/policy/changes_spec.rb b/spec/lib/gitlab/ci/build/policy/changes_spec.rb
index 5fee37bb43e..dc3329061d1 100644
--- a/spec/lib/gitlab/ci/build/policy/changes_spec.rb
+++ b/spec/lib/gitlab/ci/build/policy/changes_spec.rb
@@ -73,9 +73,9 @@ describe Gitlab::Ci::Build::Policy::Changes do
expect(policy).not_to be_satisfied_by(pipeline, seed)
end
- context 'when pipelines does not run for a branch update' do
+ context 'when modified paths can not be evaluated' do
before do
- pipeline.before_sha = Gitlab::Git::BLANK_SHA
+ allow(pipeline).to receive(:modified_paths) { nil }
end
it 'is always satisfied' do
@@ -115,5 +115,57 @@ describe Gitlab::Ci::Build::Policy::Changes do
expect(policy).not_to be_satisfied_by(pipeline, seed)
end
end
+
+ context 'when branch is created' do
+ let(:pipeline) do
+ create(:ci_empty_pipeline, project: project,
+ ref: 'feature',
+ source: source,
+ sha: '0b4bc9a4',
+ before_sha: Gitlab::Git::BLANK_SHA,
+ merge_request: merge_request)
+ end
+
+ let(:ci_build) do
+ build(:ci_build, pipeline: pipeline, project: project, ref: 'feature')
+ end
+
+ let(:seed) { double('build seed', to_resource: ci_build) }
+
+ context 'when source is merge request' do
+ let(:source) { :merge_request }
+
+ let(:merge_request) do
+ create(:merge_request,
+ source_project: project,
+ source_branch: 'feature',
+ target_project: project,
+ target_branch: 'master')
+ end
+
+ it 'is satified by changes in the merge request' do
+ policy = described_class.new(%w[files/ruby/feature.rb])
+
+ expect(policy).to be_satisfied_by(pipeline, seed)
+ end
+
+ it 'is not satified by changes not in the merge request' do
+ policy = described_class.new(%w[foo.rb])
+
+ expect(policy).not_to be_satisfied_by(pipeline, seed)
+ end
+ end
+
+ context 'when source is push' do
+ let(:source) { :push }
+ let(:merge_request) { nil }
+
+ it 'is always satified' do
+ policy = described_class.new(%w[foo.rb])
+
+ expect(policy).to be_satisfied_by(pipeline, seed)
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/danger/helper_spec.rb b/spec/lib/gitlab/danger/helper_spec.rb
new file mode 100644
index 00000000000..75080aacd96
--- /dev/null
+++ b/spec/lib/gitlab/danger/helper_spec.rb
@@ -0,0 +1,294 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'rspec-parameterized'
+require 'webmock/rspec'
+
+require 'gitlab/danger/helper'
+
+describe Gitlab::Danger::Helper do
+ using RSpec::Parameterized::TableSyntax
+
+ class FakeDanger
+ include Gitlab::Danger::Helper
+
+ attr_reader :git
+
+ def initialize(git:)
+ @git = git
+ end
+ end
+
+ let(:teammate_json) do
+ <<~JSON
+ [
+ {
+ "username": "in-gitlab-ce",
+ "name": "CE maintainer",
+ "projects":{ "gitlab-ce": "maintainer backend" }
+ },
+ {
+ "username": "in-gitlab-ee",
+ "name": "EE reviewer",
+ "projects":{ "gitlab-ee": "reviewer frontend" }
+ }
+ ]
+ JSON
+ end
+
+ let(:ce_teammate_matcher) do
+ satisfy do |teammate|
+ teammate.username == 'in-gitlab-ce' &&
+ teammate.name == 'CE maintainer' &&
+ teammate.projects == { 'gitlab-ce' => 'maintainer backend' }
+ end
+ end
+
+ let(:ee_teammate_matcher) do
+ satisfy do |teammate|
+ teammate.username == 'in-gitlab-ee' &&
+ teammate.name == 'EE reviewer' &&
+ teammate.projects == { 'gitlab-ee' => 'reviewer frontend' }
+ end
+ end
+
+ let(:fake_git) { double('fake-git') }
+
+ subject(:helper) { FakeDanger.new(git: fake_git) }
+
+ describe '#all_changed_files' do
+ subject { helper.all_changed_files }
+
+ it 'interprets a list of changes from the danger git plugin' do
+ expect(fake_git).to receive(:added_files) { %w[a b c.old] }
+ expect(fake_git).to receive(:modified_files) { %w[d e] }
+ expect(fake_git)
+ .to receive(:renamed_files)
+ .at_least(:once)
+ .and_return([{ before: 'c.old', after: 'c.new' }])
+
+ is_expected.to contain_exactly('a', 'b', 'c.new', 'd', 'e')
+ end
+ end
+
+ describe '#ee?' do
+ subject { helper.ee? }
+
+ it 'returns true if CI_PROJECT_NAME if set to gitlab-ee' do
+ stub_env('CI_PROJECT_NAME', 'gitlab-ee')
+ expect(File).not_to receive(:exist?)
+
+ is_expected.to be_truthy
+ end
+
+ it 'delegates to CHANGELOG-EE.md existence if CI_PROJECT_NAME is set to something else' do
+ stub_env('CI_PROJECT_NAME', 'something else')
+ expect(File).to receive(:exist?).with('../../CHANGELOG-EE.md') { true }
+
+ is_expected.to be_truthy
+ end
+
+ it 'returns true if CHANGELOG-EE.md exists' do
+ stub_env('CI_PROJECT_NAME', nil)
+ expect(File).to receive(:exist?).with('../../CHANGELOG-EE.md') { true }
+
+ is_expected.to be_truthy
+ end
+
+ it "returns false if CHANGELOG-EE.md doesn't exist" do
+ stub_env('CI_PROJECT_NAME', nil)
+ expect(File).to receive(:exist?).with('../../CHANGELOG-EE.md') { false }
+
+ is_expected.to be_falsy
+ end
+ end
+
+ describe '#project_name' do
+ subject { helper.project_name }
+
+ it 'returns gitlab-ee if ee? returns true' do
+ expect(helper).to receive(:ee?) { true }
+
+ is_expected.to eq('gitlab-ee')
+ end
+
+ it 'returns gitlab-ce if ee? returns false' do
+ expect(helper).to receive(:ee?) { false }
+
+ is_expected.to eq('gitlab-ce')
+ end
+ end
+
+ describe '#team' do
+ subject(:team) { helper.team }
+
+ context 'HTTP failure' do
+ before do
+ WebMock
+ .stub_request(:get, 'https://about.gitlab.com/roulette.json')
+ .to_return(status: 404)
+ end
+
+ it 'raises a pretty error' do
+ expect { team }.to raise_error(/Failed to read/)
+ end
+ end
+
+ context 'JSON failure' do
+ before do
+ WebMock
+ .stub_request(:get, 'https://about.gitlab.com/roulette.json')
+ .to_return(body: 'INVALID JSON')
+ end
+
+ it 'raises a pretty error' do
+ expect { team }.to raise_error(/Failed to parse/)
+ end
+ end
+
+ context 'success' do
+ before do
+ WebMock
+ .stub_request(:get, 'https://about.gitlab.com/roulette.json')
+ .to_return(body: teammate_json)
+ end
+
+ it 'returns an array of teammates' do
+ is_expected.to contain_exactly(ce_teammate_matcher, ee_teammate_matcher)
+ end
+
+ it 'memoizes the result' do
+ expect(team.object_id).to eq(helper.team.object_id)
+ end
+ end
+ end
+
+ describe '#project_team' do
+ subject { helper.project_team }
+
+ before do
+ WebMock
+ .stub_request(:get, 'https://about.gitlab.com/roulette.json')
+ .to_return(body: teammate_json)
+ end
+
+ it 'filters team by project_name' do
+ expect(helper)
+ .to receive(:project_name)
+ .at_least(:once)
+ .and_return('gitlab-ce')
+
+ is_expected.to contain_exactly(ce_teammate_matcher)
+ end
+ end
+
+ describe '#changes_by_category' do
+ it 'categorizes changed files' do
+ expect(fake_git).to receive(:added_files) { %w[foo foo.md foo.rb foo.js db/foo qa/foo] }
+ allow(fake_git).to receive(:modified_files) { [] }
+ allow(fake_git).to receive(:renamed_files) { [] }
+
+ expect(helper.changes_by_category).to eq(
+ backend: %w[foo.rb],
+ database: %w[db/foo],
+ docs: %w[foo.md],
+ frontend: %w[foo.js],
+ qa: %w[qa/foo],
+ unknown: %w[foo]
+ )
+ end
+ end
+
+ describe '#category_for_file' do
+ where(:path, :expected_category) do
+ 'doc/foo' | :docs
+ 'CONTRIBUTING.md' | :docs
+ 'LICENSE' | :docs
+ 'MAINTENANCE.md' | :docs
+ 'PHILOSOPHY.md' | :docs
+ 'PROCESS.md' | :docs
+ 'README.md' | :docs
+
+ 'ee/doc/foo' | :unknown
+ 'ee/README' | :unknown
+
+ 'app/assets/foo' | :frontend
+ 'app/views/foo' | :frontend
+ 'public/foo' | :frontend
+ 'spec/javascripts/foo' | :frontend
+ 'vendor/assets/foo' | :frontend
+ 'jest.config.js' | :frontend
+ 'package.json' | :frontend
+ 'yarn.lock' | :frontend
+
+ 'ee/app/assets/foo' | :frontend
+ 'ee/app/views/foo' | :frontend
+ 'ee/spec/javascripts/foo' | :frontend
+
+ 'app/models/foo' | :backend
+ 'bin/foo' | :backend
+ 'config/foo' | :backend
+ 'danger/foo' | :backend
+ 'lib/foo' | :backend
+ 'rubocop/foo' | :backend
+ 'scripts/foo' | :backend
+ 'spec/foo' | :backend
+ 'spec/foo/bar' | :backend
+
+ 'ee/app/foo' | :backend
+ 'ee/bin/foo' | :backend
+ 'ee/spec/foo' | :backend
+ 'ee/spec/foo/bar' | :backend
+
+ 'generator_templates/foo' | :backend
+ 'vendor/languages.yml' | :backend
+ 'vendor/licenses.csv' | :backend
+
+ 'Dangerfile' | :backend
+ 'Gemfile' | :backend
+ 'Gemfile.lock' | :backend
+ 'Procfile' | :backend
+ 'Rakefile' | :backend
+ 'FOO_VERSION' | :backend
+
+ 'ee/FOO_VERSION' | :unknown
+
+ 'db/foo' | :database
+ 'qa/foo' | :qa
+
+ 'ee/db/foo' | :database
+ 'ee/qa/foo' | :qa
+
+ 'FOO' | :unknown
+ 'foo' | :unknown
+
+ 'foo/bar.rb' | :backend
+ 'foo/bar.js' | :frontend
+ 'foo/bar.txt' | :docs
+ 'foo/bar.md' | :docs
+ end
+
+ with_them do
+ subject { helper.category_for_file(path) }
+
+ it { is_expected.to eq(expected_category) }
+ end
+ end
+
+ describe '#label_for_category' do
+ where(:category, :expected_label) do
+ :backend | '~backend'
+ :database | '~database'
+ :docs | '~Documentation'
+ :foo | '~foo'
+ :frontend | '~frontend'
+ :qa | '~QA'
+ end
+
+ with_them do
+ subject { helper.label_for_category(category) }
+
+ it { is_expected.to eq(expected_label) }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index cf9e0cccc71..8a9e78ba3c3 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -283,6 +283,96 @@ describe Gitlab::Git::Repository, :seed_helper do
end
end
+ describe '#diverging_commit_count' do
+ it 'counts 0 for the same branch' do
+ expect(repository.diverging_commit_count('master', 'master', max_count: 1000)).to eq([0, 0])
+ end
+
+ context 'max count does not truncate results' do
+ where(:left, :right, :expected) do
+ 1 | 1 | [1, 1]
+ 4 | 4 | [4, 4]
+ 2 | 2 | [2, 2]
+ 2 | 4 | [2, 4]
+ 4 | 2 | [4, 2]
+ 10 | 10 | [10, 10]
+ end
+
+ with_them do
+ before do
+ repository.create_branch('left-branch', 'master')
+ repository.create_branch('right-branch', 'master')
+
+ left.times do
+ new_commit_edit_new_file_on_branch(repository_rugged, 'encoding/CHANGELOG', 'left-branch', 'some more content for a', 'some stuff')
+ end
+
+ right.times do
+ new_commit_edit_new_file_on_branch(repository_rugged, 'encoding/CHANGELOG', 'right-branch', 'some more content for b', 'some stuff')
+ end
+ end
+
+ after do
+ repository.delete_branch('left-branch')
+ repository.delete_branch('right-branch')
+ end
+
+ it 'returns the correct count bounding at max_count' do
+ branch_a_sha = repository_rugged.branches['left-branch'].target.oid
+ branch_b_sha = repository_rugged.branches['right-branch'].target.oid
+
+ count = repository.diverging_commit_count(branch_a_sha, branch_b_sha, max_count: 1000)
+
+ expect(count).to eq(expected)
+ end
+ end
+ end
+
+ context 'max count truncates results' do
+ where(:left, :right, :max_count) do
+ 1 | 1 | 1
+ 4 | 4 | 4
+ 2 | 2 | 3
+ 2 | 4 | 3
+ 4 | 2 | 5
+ 10 | 10 | 10
+ end
+
+ with_them do
+ before do
+ repository.create_branch('left-branch', 'master')
+ repository.create_branch('right-branch', 'master')
+
+ left.times do
+ new_commit_edit_new_file_on_branch(repository_rugged, 'encoding/CHANGELOG', 'left-branch', 'some more content for a', 'some stuff')
+ end
+
+ right.times do
+ new_commit_edit_new_file_on_branch(repository_rugged, 'encoding/CHANGELOG', 'right-branch', 'some more content for b', 'some stuff')
+ end
+ end
+
+ after do
+ repository.delete_branch('left-branch')
+ repository.delete_branch('right-branch')
+ end
+
+ it 'returns the correct count bounding at max_count' do
+ branch_a_sha = repository_rugged.branches['left-branch'].target.oid
+ branch_b_sha = repository_rugged.branches['right-branch'].target.oid
+
+ results = repository.diverging_commit_count(branch_a_sha, branch_b_sha, max_count: max_count)
+
+ expect(results[0] + results[1]).to eq(max_count)
+ end
+ end
+ end
+
+ it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::CommitService, :diverging_commit_count do
+ subject { repository.diverging_commit_count('master', 'master', max_count: 1000) }
+ end
+ end
+
describe '#has_local_branches?' do
context 'check for local branches' do
it { expect(repository.has_local_branches?).to eq(true) }
diff --git a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb
index 0f21b8843b6..15e59718dce 100644
--- a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb
@@ -89,7 +89,7 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi
description: 'This is my pull request',
source_project_id: project.id,
target_project_id: project.id,
- source_branch: 'alice:feature',
+ source_branch: 'github/fork/alice/feature',
target_branch: 'master',
state: :merged,
milestone_id: milestone.id,
@@ -134,7 +134,7 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi
description: "*Created by: alice*\n\nThis is my pull request",
source_project_id: project.id,
target_project_id: project.id,
- source_branch: 'alice:feature',
+ source_branch: 'github/fork/alice/feature',
target_branch: 'master',
state: :merged,
milestone_id: milestone.id,
@@ -259,6 +259,40 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi
.and_return(user.id)
end
+ it 'does not create the source branch if merge request is merged' do
+ mr, exists = importer.create_merge_request
+
+ importer.insert_git_data(mr, exists)
+
+ expect(project.repository.branch_exists?(mr.source_branch)).to be_falsey
+ expect(project.repository.branch_exists?(mr.target_branch)).to be_truthy
+ end
+
+ it 'creates the source branch if merge request is open' do
+ mr, exists = importer.create_merge_request
+ mr.state = 'opened'
+ mr.save
+
+ importer.insert_git_data(mr, exists)
+
+ expect(project.repository.branch_exists?(mr.source_branch)).to be_truthy
+ expect(project.repository.branch_exists?(mr.target_branch)).to be_truthy
+ end
+
+ it 'ignores Git errors when creating a branch' do
+ mr, exists = importer.create_merge_request
+ mr.state = 'opened'
+ mr.save
+
+ expect(project.repository).to receive(:add_branch).and_raise(Gitlab::Git::CommandError)
+ expect(Gitlab::Sentry).to receive(:track_acceptable_exception).and_call_original
+
+ importer.insert_git_data(mr, exists)
+
+ expect(project.repository.branch_exists?(mr.source_branch)).to be_falsey
+ expect(project.repository.branch_exists?(mr.target_branch)).to be_truthy
+ end
+
it 'creates the merge request diffs' do
mr, exists = importer.create_merge_request
diff --git a/spec/lib/gitlab/github_import/representation/pull_request_spec.rb b/spec/lib/gitlab/github_import/representation/pull_request_spec.rb
index 33f6ff0ae6a..d478e5ae899 100644
--- a/spec/lib/gitlab/github_import/representation/pull_request_spec.rb
+++ b/spec/lib/gitlab/github_import/representation/pull_request_spec.rb
@@ -238,7 +238,7 @@ describe Gitlab::GithubImport::Representation::PullRequest do
target_repository_id: 2
)
- expect(pr.formatted_source_branch).to eq('foo:branch')
+ expect(pr.formatted_source_branch).to eq('github/fork/foo/branch')
end
end
diff --git a/spec/lib/gitlab/import_export/shared_spec.rb b/spec/lib/gitlab/import_export/shared_spec.rb
index f2d750c6595..2c288cff6ef 100644
--- a/spec/lib/gitlab/import_export/shared_spec.rb
+++ b/spec/lib/gitlab/import_export/shared_spec.rb
@@ -14,6 +14,16 @@ describe Gitlab::ImportExport::Shared do
expect(subject.errors).to eq(['Error importing into [FILTERED] Permission denied @ unlink_internal - [FILTERED]'])
end
+ it 'updates the import JID' do
+ import_state = create(:import_state, project: project, jid: 'jid-test')
+
+ expect_next_instance_of(Gitlab::Import::Logger) do |logger|
+ expect(logger).to receive(:error).with(hash_including(import_jid: import_state.jid))
+ end
+
+ subject.error(error)
+ end
+
it 'calls the error logger with the full message' do
expect(subject).to receive(:log_error).with(hash_including(message: error.message))
diff --git a/spec/lib/gitlab/sql/recursive_cte_spec.rb b/spec/lib/gitlab/sql/recursive_cte_spec.rb
index 25146860615..7fe39dd5a96 100644
--- a/spec/lib/gitlab/sql/recursive_cte_spec.rb
+++ b/spec/lib/gitlab/sql/recursive_cte_spec.rb
@@ -31,6 +31,15 @@ describe Gitlab::SQL::RecursiveCTE, :postgresql do
expect(cte.alias_to(table).to_sql).to eq("#{source_name} AS #{alias_name}")
end
+
+ it 'replaces dots with an underscore' do
+ table = Arel::Table.new('gitlab.kittens')
+
+ source_name = ActiveRecord::Base.connection.quote_table_name(:cte_name)
+ alias_name = ActiveRecord::Base.connection.quote_table_name(:gitlab_kittens)
+
+ expect(cte.alias_to(table).to_sql).to eq("#{source_name} AS #{alias_name}")
+ end
end
describe '#apply_to' do
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index 4f5993ba226..d3eae80cc56 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -124,9 +124,15 @@ describe Gitlab::UsageData do
todos
uploads
web_hooks
+ user_preferences
))
end
+ it 'does not gather user preferences usage data when the feature is disabled' do
+ stub_feature_flags(group_overview_security_dashboard: false)
+ expect(subject[:counts].keys).not_to include(:user_preferences)
+ end
+
it 'gathers projects data correctly' do
count_data = subject[:counts]
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 72a0df96a80..460b5c8cd31 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -1172,8 +1172,26 @@ describe Ci::Pipeline, :mailer do
pipeline.update_column(:before_sha, Gitlab::Git::BLANK_SHA)
end
- it 'raises an error' do
- expect { pipeline.modified_paths }.to raise_error(ArgumentError)
+ it 'returns nil' do
+ expect(pipeline.modified_paths).to be_nil
+ end
+ end
+
+ context 'when source is merge request' do
+ let(:pipeline) do
+ create(:ci_pipeline, source: :merge_request, merge_request: merge_request)
+ end
+
+ let(:merge_request) do
+ create(:merge_request,
+ source_project: project,
+ source_branch: 'feature',
+ target_project: project,
+ target_branch: 'master')
+ end
+
+ it 'returns merge request modified paths' do
+ expect(pipeline.modified_paths).to match(merge_request.modified_paths)
end
end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 7b364395faf..1f9088c2e6b 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -4602,6 +4602,21 @@ describe Project do
end
end
+ describe '#has_pool_repsitory?' do
+ it 'returns false when it does not have a pool repository' do
+ subject = create(:project, :repository)
+
+ expect(subject.has_pool_repository?).to be false
+ end
+
+ it 'returns true when it has a pool repository' do
+ pool = create(:pool_repository, :ready)
+ subject = create(:project, :repository, pool_repository: pool)
+
+ expect(subject.has_pool_repository?).to be true
+ end
+ end
+
def rugged_config
rugged_repo(project.repository).config
end
diff --git a/spec/models/releases/link_spec.rb b/spec/models/releases/link_spec.rb
index 06ed1438688..4dd26c976cc 100644
--- a/spec/models/releases/link_spec.rb
+++ b/spec/models/releases/link_spec.rb
@@ -77,4 +77,28 @@ describe Releases::Link do
it { is_expected.to be_truthy }
end
+
+ describe 'supported protocols' do
+ where(:protocol) do
+ %w(http https ftp)
+ end
+
+ with_them do
+ let(:link) { build(:release_link, url: protocol + '://assets.com/download') }
+
+ it 'will be valid' do
+ expect(link).to be_valid
+ end
+ end
+ end
+
+ describe 'unsupported protocol' do
+ context 'for torrent' do
+ let(:link) { build(:release_link, url: 'torrent://assets.com/download') }
+
+ it 'will be invalid' do
+ expect(link).to be_invalid
+ end
+ end
+ end
end
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 6b9bc6eda6a..bc892523cf8 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -237,7 +237,7 @@ describe API::Commits do
end
describe 'create' do
- let(:message) { 'Created file' }
+ let(:message) { 'Created a new file with a very very looooooooooooooooooooooooooooooooooooooooooooooong commit message' }
let(:invalid_c_params) do
{
branch: 'master',
diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb
index 49eea2e362b..606fa9185d8 100644
--- a/spec/requests/api/labels_spec.rb
+++ b/spec/requests/api/labels_spec.rb
@@ -20,7 +20,7 @@ describe API::Labels do
create(:labeled_merge_request, labels: [priority_label], author: user, source_project: project )
expected_keys = %w(
- id name color description
+ id name color text_color description
open_issues_count closed_issues_count open_merge_requests_count
subscribed priority
)
@@ -43,6 +43,7 @@ describe API::Labels do
expect(label1_response['open_merge_requests_count']).to eq(0)
expect(label1_response['name']).to eq(label1.name)
expect(label1_response['color']).to be_present
+ expect(label1_response['text_color']).to be_present
expect(label1_response['description']).to be_nil
expect(label1_response['priority']).to be_nil
expect(label1_response['subscribed']).to be_falsey
@@ -52,6 +53,7 @@ describe API::Labels do
expect(group_label_response['open_merge_requests_count']).to eq(0)
expect(group_label_response['name']).to eq(group_label.name)
expect(group_label_response['color']).to be_present
+ expect(group_label_response['text_color']).to be_present
expect(group_label_response['description']).to be_nil
expect(group_label_response['priority']).to be_nil
expect(group_label_response['subscribed']).to be_falsey
@@ -61,6 +63,7 @@ describe API::Labels do
expect(priority_label_response['open_merge_requests_count']).to eq(1)
expect(priority_label_response['name']).to eq(priority_label.name)
expect(priority_label_response['color']).to be_present
+ expect(priority_label_response['text_color']).to be_present
expect(priority_label_response['description']).to be_nil
expect(priority_label_response['priority']).to eq(3)
expect(priority_label_response['subscribed']).to be_falsey
diff --git a/spec/requests/api/wikis_spec.rb b/spec/requests/api/wikis_spec.rb
index 6109829aad1..d1b58aac104 100644
--- a/spec/requests/api/wikis_spec.rb
+++ b/spec/requests/api/wikis_spec.rb
@@ -100,6 +100,8 @@ describe API::Wikis do
shared_examples_for 'updates wiki page' do
it 'updates the wiki page' do
+ put(api(url, user), params: payload)
+
expect(response).to have_gitlab_http_status(200)
expect(json_response.size).to eq(4)
expect(json_response.keys).to match_array(expected_keys_with_content)
@@ -107,6 +109,16 @@ describe API::Wikis do
expect(json_response['slug']).to eq(payload[:title].tr(' ', '-'))
expect(json_response['title']).to eq(payload[:title])
end
+
+ [:title, :content, :format].each do |part|
+ it "it updates with wiki with missing #{part}" do
+ payload.delete(part)
+
+ put(api(url, user), params: payload)
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
end
shared_examples_for '403 Forbidden' do
@@ -528,8 +540,6 @@ describe API::Wikis do
context 'when user is developer' do
before do
project.add_developer(user)
-
- put(api(url, user), params: payload)
end
include_examples 'updates wiki page'
@@ -537,6 +547,10 @@ describe API::Wikis do
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
+ before do
+ put(api(url, user), params: payload)
+ end
+
include_examples '404 Wiki Page Not Found'
end
end
@@ -544,8 +558,6 @@ describe API::Wikis do
context 'when user is maintainer' do
before do
project.add_maintainer(user)
-
- put(api(url, user), params: payload)
end
include_examples 'updates wiki page'
@@ -553,6 +565,10 @@ describe API::Wikis do
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
+ before do
+ put(api(url, user), params: payload)
+ end
+
include_examples '404 Wiki Page Not Found'
end
end
@@ -572,8 +588,6 @@ describe API::Wikis do
context 'when user is developer' do
before do
project.add_developer(user)
-
- put(api(url, user), params: payload)
end
include_examples 'updates wiki page'
@@ -581,6 +595,10 @@ describe API::Wikis do
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
+ before do
+ put(api(url, user), params: payload)
+ end
+
include_examples '404 Wiki Page Not Found'
end
end
@@ -588,8 +606,6 @@ describe API::Wikis do
context 'when user is maintainer' do
before do
project.add_maintainer(user)
-
- put(api(url, user), params: payload)
end
include_examples 'updates wiki page'
@@ -597,6 +613,10 @@ describe API::Wikis do
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
+ before do
+ put(api(url, user), params: payload)
+ end
+
include_examples '404 Wiki Page Not Found'
end
end
@@ -605,10 +625,6 @@ describe API::Wikis do
context 'when wiki belongs to a group project' do
let(:project) { create(:project, :wiki_repo, namespace: group) }
- before do
- put(api(url, user), params: payload)
- end
-
include_examples 'updates wiki page'
end
end
diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb
index 48f1d696ff6..1645b67c329 100644
--- a/spec/services/notes/create_service_spec.rb
+++ b/spec/services/notes/create_service_spec.rb
@@ -311,7 +311,14 @@ describe Notes::CreateService do
end
it 'converts existing note to DiscussionNote' do
- expect { subject }.to change { existing_note.reload.type }.from(nil).to('DiscussionNote')
+ expect do
+ existing_note
+
+ Timecop.freeze(Time.now + 1.minute) { subject }
+
+ existing_note.reload
+ end.to change { existing_note.type }.from(nil).to('DiscussionNote')
+ .and change { existing_note.updated_at }
end
end
end
diff --git a/spec/services/task_list_toggle_service_spec.rb b/spec/services/task_list_toggle_service_spec.rb
index 7c5480d382f..b1260cf740a 100644
--- a/spec/services/task_list_toggle_service_spec.rb
+++ b/spec/services/task_list_toggle_service_spec.rb
@@ -12,6 +12,10 @@ describe TaskListToggleService do
1. [X] Item 1
- [ ] Sub-item 1
+
+ - [ ] loose list
+
+ with an embedded paragraph
EOT
end
@@ -26,16 +30,22 @@ describe TaskListToggleService do
</li>
</ul>
<p data-sourcepos="4:1-4:11" dir="auto">A paragraph</p>
- <ol data-sourcepos="6:1-7:19" class="task-list" dir="auto">
- <li data-sourcepos="6:1-7:19" class="task-list-item">
- <input type="checkbox" class="task-list-item-checkbox" disabled checked> Item 1
- <ul data-sourcepos="7:4-7:19" class="task-list">
- <li data-sourcepos="7:4-7:19" class="task-list-item">
- <input type="checkbox" class="task-list-item-checkbox" disabled> Sub-item 1
+ <ol data-sourcepos="6:1-8:0" class="task-list" dir="auto">
+ <li data-sourcepos="6:1-8:0" class="task-list-item">
+ <input type="checkbox" class="task-list-item-checkbox" checked="" disabled=""> Item 1
+ <ul data-sourcepos="7:4-8:0" class="task-list">
+ <li data-sourcepos="7:4-8:0" class="task-list-item">
+ <input type="checkbox" class="task-list-item-checkbox" disabled=""> Sub-item 1
</li>
</ul>
</li>
</ol>
+ <ul data-sourcepos="9:1-11:28" class="task-list" dir="auto">
+ <li data-sourcepos="9:1-11:28" class="task-list-item">
+ <p data-sourcepos="9:3-9:16"><input type="checkbox" class="task-list-item-checkbox" disabled=""> loose list</p>
+ <p data-sourcepos="11:3-11:28">with an embedded paragraph</p>
+ </li>
+ </ul>
EOT
end
@@ -59,6 +69,16 @@ describe TaskListToggleService do
expect(toggler.updated_markdown_html).to include('disabled> Item 1')
end
+ it 'checks task in loose list' do
+ toggler = described_class.new(markdown, markdown_html,
+ toggle_as_checked: true,
+ line_source: '- [ ] loose list', line_number: 9)
+
+ expect(toggler.execute).to be_truthy
+ expect(toggler.updated_markdown.lines[8]).to eq "- [x] loose list\n"
+ expect(toggler.updated_markdown_html).to include('disabled checked> loose list')
+ end
+
it 'returns false if line_source does not match the text' do
toggler = described_class.new(markdown, markdown_html,
toggle_as_checked: false,
diff --git a/spec/support/api/milestones_shared_examples.rb b/spec/support/api/milestones_shared_examples.rb
index 5f709831ce1..b426fadb001 100644
--- a/spec/support/api/milestones_shared_examples.rb
+++ b/spec/support/api/milestones_shared_examples.rb
@@ -8,12 +8,17 @@ shared_examples_for 'group and project milestones' do |route_definition|
describe "GET #{route_definition}" do
it 'returns milestones list' do
+ create(:issue, project: project, milestone: milestone)
+ create(:closed_issue, project: project, milestone: milestone)
+ create(:closed_issue, project: project, milestone: milestone)
+
get api(route, 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.first['title']).to eq(milestone.title)
+ expect(json_response.first['percentage_complete']).to eq(66)
end
it 'returns a 401 error if user not authenticated' do
diff --git a/spec/support/helpers/stub_feature_flags.rb b/spec/support/helpers/stub_feature_flags.rb
index 4061a8d1bc9..48258692304 100644
--- a/spec/support/helpers/stub_feature_flags.rb
+++ b/spec/support/helpers/stub_feature_flags.rb
@@ -1,11 +1,30 @@
module StubFeatureFlags
# Stub Feature flags with `flag_name: true/false`
#
- # @param [Hash] features where key is feature name and value is boolean whether enabled or not
+ # @param [Hash] features where key is feature name and value is boolean whether enabled or not.
+ # Alternatively, you can specify Hash to enable the flag on a specific thing.
+ #
+ # Examples
+ # - `stub_feature_flags(ci_live_trace: false)` ... Disable `ci_live_trace`
+ # feature flag globally.
+ # - `stub_feature_flags(ci_live_trace: { enabled: false, thing: project })` ...
+ # Disable `ci_live_trace` feature flag on the specified project.
def stub_feature_flags(features)
- features.each do |feature_name, enabled|
- allow(Feature).to receive(:enabled?).with(feature_name, any_args) { enabled }
- allow(Feature).to receive(:enabled?).with(feature_name.to_s, any_args) { enabled }
+ features.each do |feature_name, option|
+ if option.is_a?(Hash)
+ enabled, thing = option.values_at(:enabled, :thing)
+ else
+ enabled = option
+ thing = nil
+ end
+
+ if thing
+ allow(Feature).to receive(:enabled?).with(feature_name, thing, any_args) { enabled }
+ allow(Feature).to receive(:enabled?).with(feature_name.to_s, thing, any_args) { enabled }
+ else
+ allow(Feature).to receive(:enabled?).with(feature_name, any_args) { enabled }
+ allow(Feature).to receive(:enabled?).with(feature_name.to_s, any_args) { enabled }
+ end
end
end
end
diff --git a/spec/support/shared_examples/models/with_uploads_shared_examples.rb b/spec/support/shared_examples/models/with_uploads_shared_examples.rb
index 1d11b855459..43033a2d256 100644
--- a/spec/support/shared_examples/models/with_uploads_shared_examples.rb
+++ b/spec/support/shared_examples/models/with_uploads_shared_examples.rb
@@ -44,26 +44,6 @@ shared_examples_for 'model with uploads' do |supports_fileuploads|
model_object.destroy
end
end
-
- describe 'destroy strategy depending on feature flag' do
- let!(:upload) { create(:upload, uploader: FileUploader, model: model_object) }
-
- it 'does not destroy uploads by default' do
- expect(model_object).to receive(:delete_uploads)
- expect(model_object).not_to receive(:destroy_uploads)
-
- model_object.destroy
- end
-
- it 'uses before destroy callback if feature flag is disabled' do
- stub_feature_flags(fast_destroy_uploads: false)
-
- expect(model_object).to receive(:destroy_uploads)
- expect(model_object).not_to receive(:delete_uploads)
-
- model_object.destroy
- end
- end
end
end
end
diff --git a/yarn.lock b/yarn.lock
index 8bff9c59113..8ddcd305d48 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -653,10 +653,10 @@
eslint-plugin-promise "^4.0.1"
eslint-plugin-vue "^5.0.0"
-"@gitlab/svgs@^1.51.0":
- version "1.51.0"
- resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.51.0.tgz#1b608f68dfb74284401b1cbdb823440f6e8b0091"
- integrity sha512-B1Wdhfy5ZClkHuaaCUUZyOBF8CFxxHqxGGhveRekOowtlMExa3tx+YkqNa5XPsEVMF6Aqnh8evQmmN4b+zrHVQ==
+"@gitlab/svgs@^1.52.0":
+ version "1.52.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.52.0.tgz#870b0112a18b10d2cde5470a48b05025193cd207"
+ integrity sha512-xqDYIaSY1MJuCa7lRIDTTPoXn6x57So2qchxwmELE+SAJxlYlpYgDKCNWcGawhyTZRDZuG/qFBWp0sMeTQD//A==
"@gitlab/ui@^2.0.2":
version "2.0.2"