summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMayra Cabrera <mcabrera@gitlab.com>2018-05-23 18:52:54 +0000
committerMayra Cabrera <mcabrera@gitlab.com>2018-05-23 18:52:54 +0000
commit07fc650f3cb6d0ed806b638e55ad6fc4c36db9f2 (patch)
treedc32cf2c577500c709dfaeb56fd6fee42054c2a2
parent55e4a0b334139a5b5949c91c8325b330e425989b (diff)
parenta10dca978161ee8effc532a6347f043b0a99af84 (diff)
downloadgitlab-ce-07fc650f3cb6d0ed806b638e55ad6fc4c36db9f2.tar.gz
Merge branch '10-8-stable-patch-1' into '10-8-stable'
Prepare 10.8.1 release See merge request gitlab-org/gitlab-ce!19083
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/ide/components/file_finder/index.vue10
-rw-r--r--app/assets/javascripts/ide/components/ide.vue5
-rw-r--r--app/assets/javascripts/ide/components/repo_editor.vue9
-rw-r--r--app/assets/javascripts/pages/ldap/omniauth_callbacks/index.js3
-rw-r--r--app/assets/javascripts/pipelines/components/graph/action_component.vue55
-rw-r--r--app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue13
-rw-r--r--app/assets/javascripts/pipelines/components/graph/graph_component.vue11
-rw-r--r--app/assets/javascripts/pipelines/components/graph/job_component.vue12
-rw-r--r--app/assets/javascripts/pipelines/components/graph/stage_column_component.vue13
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_table_row.vue3
-rw-r--r--app/assets/javascripts/pipelines/components/stage.vue19
-rw-r--r--app/assets/javascripts/pipelines/constants.js2
-rw-r--r--app/assets/javascripts/pipelines/mixins/pipelines.js2
-rw-r--r--app/assets/javascripts/pipelines/pipeline_details_bundle.js30
-rw-r--r--app/controllers/admin/dashboard_controller.rb2
-rw-r--r--app/controllers/projects/prometheus/metrics_controller.rb2
-rw-r--r--app/helpers/count_helper.rb5
-rw-r--r--app/models/ci/build.rb2
-rw-r--r--app/models/clusters/applications/prometheus.rb5
-rw-r--r--app/models/commit_status.rb1
-rw-r--r--app/presenters/ci/build_presenter.rb25
-rw-r--r--app/presenters/commit_status_presenter.rb24
-rw-r--r--app/presenters/generic_commit_status_presenter.rb2
-rw-r--r--app/views/admin/dashboard/index.html.haml20
-rw-r--r--changelogs/unreleased/46177-fix-present-on-generic-commit-status.yml5
-rw-r--r--changelogs/unreleased/46413-error-when-prometheus-adapter-is-disabled.yml5
-rw-r--r--changelogs/unreleased/46454-wrong-value-in-ci-deploy-user.yml5
-rw-r--r--changelogs/unreleased/46630-add-missing-columns-to-project-mirror-data.yml5
-rw-r--r--changelogs/unreleased/fix-kube_client-proxy_url-exception.yml5
-rw-r--r--changelogs/unreleased/ldap-u2f-fix.yml5
-rw-r--r--changelogs/unreleased/sh-bump-prometheus-client-mmap.yml5
-rw-r--r--changelogs/unreleased/sh-fix-blocked-user-account-ldap.yml5
-rw-r--r--changelogs/unreleased/xeodon-gitlab-ce-fix-45743-master-fix-gitaly-delete-refs.yml5
-rw-r--r--db/migrate/20180503175053_ensure_missing_columns_to_project_mirror_data.rb15
-rw-r--r--doc/raketasks/backup_restore.md35
-rw-r--r--doc/topics/autodevops/img/rollout_enabled.pngbin0 -> 19536 bytes
-rw-r--r--doc/topics/autodevops/img/rollout_staging_disabled.pngbin0 -> 13837 bytes
-rw-r--r--doc/topics/autodevops/img/rollout_staging_enabled.pngbin0 -> 17306 bytes
-rw-r--r--doc/topics/autodevops/img/staging_enabled.pngbin0 -> 17929 bytes
-rw-r--r--doc/topics/autodevops/index.md52
-rw-r--r--doc/topics/autodevops/quick_start_guide.md4
-rw-r--r--doc/user/project/deploy_tokens/index.md2
-rw-r--r--lib/gitlab/auth/blocked_user_tracker.rb4
-rw-r--r--lib/gitlab/database/count.rb48
-rw-r--r--lib/gitlab/git/blob.rb20
-rw-r--r--lib/gitlab/git/path_helper.rb2
-rw-r--r--lib/gitlab/git/repository.rb2
-rw-r--r--spec/controllers/projects/prometheus/metrics_controller_spec.rb73
-rw-r--r--spec/features/projects/environments/environments_spec.rb16
-rw-r--r--spec/javascripts/ide/components/ide_spec.js6
-rw-r--r--spec/javascripts/ide/components/repo_editor_spec.js20
-rw-r--r--spec/javascripts/pipelines/graph/action_component_spec.js67
-rw-r--r--spec/javascripts/pipelines/stage_spec.js27
-rw-r--r--spec/lib/gitlab/auth/blocked_user_tracker_spec.rb44
-rw-r--r--spec/lib/gitlab/database/count_spec.rb62
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb4
-rw-r--r--spec/models/ci/build_spec.rb2
-rw-r--r--spec/models/clusters/applications/prometheus_spec.rb10
-rw-r--r--spec/models/commit_status_spec.rb6
-rw-r--r--spec/models/generic_commit_status_spec.rb6
-rw-r--r--spec/presenters/ci/build_presenter_spec.rb2
-rw-r--r--spec/presenters/commit_status_presenter_spec.rb15
64 files changed, 648 insertions, 222 deletions
diff --git a/Gemfile b/Gemfile
index 89febc9bc0c..2e231b26e7e 100644
--- a/Gemfile
+++ b/Gemfile
@@ -297,7 +297,7 @@ group :metrics do
gem 'influxdb', '~> 0.2', require: false
# Prometheus
- gem 'prometheus-client-mmap', '~> 0.9.1'
+ gem 'prometheus-client-mmap', '~> 0.9.3'
gem 'raindrops', '~> 0.18'
end
diff --git a/Gemfile.lock b/Gemfile.lock
index 2a63ee6a532..d2f7c6fdae5 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -634,7 +634,7 @@ GEM
parser
unparser
procto (0.0.3)
- prometheus-client-mmap (0.9.1)
+ prometheus-client-mmap (0.9.3)
pry (0.10.4)
coderay (~> 1.1.0)
method_source (~> 0.8.1)
@@ -1133,7 +1133,7 @@ DEPENDENCIES
peek-sidekiq (~> 1.0.3)
pg (~> 0.18.2)
premailer-rails (~> 1.9.7)
- prometheus-client-mmap (~> 0.9.1)
+ prometheus-client-mmap (~> 0.9.3)
pry-byebug (~> 3.4.1)
pry-rails (~> 0.3.4)
rack-attack (~> 4.4.1)
diff --git a/app/assets/javascripts/ide/components/file_finder/index.vue b/app/assets/javascripts/ide/components/file_finder/index.vue
index ea2b13a8b21..cabb3f59b17 100644
--- a/app/assets/javascripts/ide/components/file_finder/index.vue
+++ b/app/assets/javascripts/ide/components/file_finder/index.vue
@@ -39,12 +39,10 @@ export default {
return this.allBlobs.slice(0, MAX_FILE_FINDER_RESULTS);
}
- return fuzzaldrinPlus
- .filter(this.allBlobs, searchText, {
- key: 'path',
- maxResults: MAX_FILE_FINDER_RESULTS,
- })
- .sort((a, b) => b.lastOpenedAt - a.lastOpenedAt);
+ return fuzzaldrinPlus.filter(this.allBlobs, searchText, {
+ key: 'path',
+ maxResults: MAX_FILE_FINDER_RESULTS,
+ });
},
filteredBlobsLength() {
return this.filteredBlobs.length;
diff --git a/app/assets/javascripts/ide/components/ide.vue b/app/assets/javascripts/ide/components/ide.vue
index 6c373a92776..1ec69adce09 100644
--- a/app/assets/javascripts/ide/components/ide.vue
+++ b/app/assets/javascripts/ide/components/ide.vue
@@ -52,7 +52,10 @@ export default {
methods: {
...mapActions(['toggleFileFinder']),
mousetrapStopCallback(e, el, combo) {
- if (combo === 't' && el.classList.contains('dropdown-input-field')) {
+ if (
+ (combo === 't' && el.classList.contains('dropdown-input-field')) ||
+ el.classList.contains('inputarea')
+ ) {
return true;
} else if (combo === 'command+p' || combo === 'ctrl+p') {
return false;
diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue
index f8678b602ac..f2178c06c10 100644
--- a/app/assets/javascripts/ide/components/repo_editor.vue
+++ b/app/assets/javascripts/ide/components/repo_editor.vue
@@ -43,9 +43,13 @@ export default {
},
},
watch: {
- file(oldVal, newVal) {
+ file(newVal, oldVal) {
+ if (oldVal.pending) {
+ this.removePendingTab(oldVal);
+ }
+
// Compare key to allow for files opened in review mode to be cached differently
- if (newVal.key !== this.file.key) {
+ if (oldVal.key !== this.file.key) {
this.initMonaco();
if (this.currentActivityView !== activityBarViews.edit) {
@@ -99,6 +103,7 @@ export default {
'setFileViewMode',
'setFileEOL',
'updateViewer',
+ 'removePendingTab',
]),
initMonaco() {
if (this.shouldHideEditor) return;
diff --git a/app/assets/javascripts/pages/ldap/omniauth_callbacks/index.js b/app/assets/javascripts/pages/ldap/omniauth_callbacks/index.js
new file mode 100644
index 00000000000..edd7e38471b
--- /dev/null
+++ b/app/assets/javascripts/pages/ldap/omniauth_callbacks/index.js
@@ -0,0 +1,3 @@
+import initU2F from '../../../shared/sessions/u2f';
+
+document.addEventListener('DOMContentLoaded', initU2F);
diff --git a/app/assets/javascripts/pipelines/components/graph/action_component.vue b/app/assets/javascripts/pipelines/components/graph/action_component.vue
index fd3491c7fe0..1159b8fff4f 100644
--- a/app/assets/javascripts/pipelines/components/graph/action_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/action_component.vue
@@ -1,11 +1,21 @@
<script>
import $ from 'jquery';
-import tooltip from '../../../vue_shared/directives/tooltip';
-import Icon from '../../../vue_shared/components/icon.vue';
-import { dasherize } from '../../../lib/utils/text_utility';
-import eventHub from '../../event_hub';
+import axios from '~/lib/utils/axios_utils';
+import { dasherize } from '~/lib/utils/text_utility';
+import { __ } from '~/locale';
+import createFlash from '~/flash';
+import tooltip from '~/vue_shared/directives/tooltip';
+import Icon from '~/vue_shared/components/icon.vue';
+
/**
- * Renders either a cancel, retry or play icon pointing to the given path.
+ * Renders either a cancel, retry or play icon button and handles the post request
+ *
+ * Used in:
+ * - mr widget mini pipeline graph: `mr_widget_pipeline.vue`
+ * - pipelines table
+ * - pipelines table in merge request page
+ * - pipelines table in commit page
+ * - pipelines detail page in big gra
*/
export default {
components: {
@@ -32,16 +42,10 @@ export default {
required: true,
},
- requestFinishedFor: {
- type: String,
- required: false,
- default: '',
- },
},
data() {
return {
isDisabled: false,
- linkRequested: '',
};
},
@@ -51,19 +55,28 @@ export default {
return `${actionIconDash} js-icon-${actionIconDash}`;
},
},
- watch: {
- requestFinishedFor() {
- if (this.requestFinishedFor === this.linkRequested) {
- this.isDisabled = false;
- }
- },
- },
methods: {
+ /**
+ * The request should not be handled here.
+ * However due to this component being used in several
+ * different apps it avoids repetition & complexity.
+ *
+ */
onClickAction() {
$(this.$el).tooltip('hide');
- eventHub.$emit('postAction', this.link);
- this.linkRequested = this.link;
+
this.isDisabled = true;
+
+ axios.post(`${this.link}.json`)
+ .then(() => {
+ this.isDisabled = false;
+ this.$emit('pipelineActionRequestComplete');
+ })
+ .catch(() => {
+ this.isDisabled = false;
+
+ createFlash(__('An error occurred while making the request.'));
+ });
},
},
};
@@ -80,6 +93,6 @@ btn-transparent ci-action-icon-container ci-action-icon-wrapper"
data-container="body"
:disabled="isDisabled"
>
- <icon :name="actionIcon" />
+ <icon :name="actionIcon"/>
</button>
</template>
diff --git a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue b/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue
index 4027d26098f..7bfe11ab8cd 100644
--- a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue
@@ -42,11 +42,6 @@ export default {
type: Object,
required: true,
},
- requestFinishedFor: {
- type: String,
- required: false,
- default: '',
- },
},
computed: {
@@ -76,11 +71,15 @@ export default {
e.stopPropagation();
});
},
+
+ pipelineActionRequestComplete() {
+ this.$emit('pipelineActionRequestComplete');
+ },
},
};
</script>
<template>
- <div class="ci-job-dropdown-container">
+ <div class="ci-job-dropdown-container dropdown">
<button
v-tooltip
type="button"
@@ -110,7 +109,7 @@ export default {
<job-component
:job="item"
css-class-job-name="mini-pipeline-graph-dropdown-item"
- :request-finished-for="requestFinishedFor"
+ @pipelineActionRequestComplete="pipelineActionRequestComplete"
/>
</li>
</ul>
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
index 7b8a5edcbff..4ec67f6c01b 100644
--- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
@@ -16,11 +16,6 @@ export default {
type: Object,
required: true,
},
- requestFinishedFor: {
- type: String,
- required: false,
- default: '',
- },
},
computed: {
@@ -51,6 +46,10 @@ export default {
return className;
},
+
+ refreshPipelineGraph() {
+ this.$emit('refreshPipelineGraph');
+ },
},
};
</script>
@@ -74,7 +73,7 @@ export default {
:key="stage.name"
:stage-connector-class="stageConnectorClass(index, stage)"
:is-first-column="isFirstColumn(index)"
- :request-finished-for="requestFinishedFor"
+ @refreshPipelineGraph="refreshPipelineGraph"
/>
</ul>
</div>
diff --git a/app/assets/javascripts/pipelines/components/graph/job_component.vue b/app/assets/javascripts/pipelines/components/graph/job_component.vue
index c1f0f051b63..27b938c4985 100644
--- a/app/assets/javascripts/pipelines/components/graph/job_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/job_component.vue
@@ -46,11 +46,6 @@ export default {
required: false,
default: '',
},
- requestFinishedFor: {
- type: String,
- required: false,
- default: '',
- },
},
computed: {
status() {
@@ -84,6 +79,11 @@ export default {
return this.job.status && this.job.status.action && this.job.status.action.path;
},
},
+ methods: {
+ pipelineActionRequestComplete() {
+ this.$emit('pipelineActionRequestComplete');
+ },
+ },
};
</script>
<template>
@@ -126,7 +126,7 @@ export default {
:tooltip-text="status.action.title"
:link="status.action.path"
:action-icon="status.action.icon"
- :request-finished-for="requestFinishedFor"
+ @pipelineActionRequestComplete="pipelineActionRequestComplete"
/>
</div>
</template>
diff --git a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
index 5461fdbbadd..f32368947e8 100644
--- a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
@@ -29,12 +29,6 @@ export default {
required: false,
default: '',
},
-
- requestFinishedFor: {
- type: String,
- required: false,
- default: '',
- },
},
methods: {
@@ -49,6 +43,10 @@ export default {
buildConnnectorClass(index) {
return index === 0 && !this.isFirstColumn ? 'left-connector' : '';
},
+
+ pipelineActionRequestComplete() {
+ this.$emit('refreshPipelineGraph');
+ },
},
};
</script>
@@ -75,12 +73,13 @@ export default {
v-if="job.size === 1"
:job="job"
css-class-job-name="build-content"
+ @pipelineActionRequestComplete="pipelineActionRequestComplete"
/>
<dropdown-job-component
v-if="job.size > 1"
:job="job"
- :request-finished-for="requestFinishedFor"
+ @pipelineActionRequestComplete="pipelineActionRequestComplete"
/>
</li>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
index 4cbd67e0372..1b5ff93b68d 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
@@ -8,6 +8,7 @@
import pipelineUrl from './pipeline_url.vue';
import pipelinesTimeago from './time_ago.vue';
import commitComponent from '../../vue_shared/components/commit.vue';
+ import { PIPELINES_TABLE } from '../constants';
/**
* Pipeline table row.
@@ -44,6 +45,7 @@
required: true,
},
},
+ pipelinesTable: PIPELINES_TABLE,
computed: {
/**
* If provided, returns the commit tag.
@@ -272,6 +274,7 @@
v-for="(stage, index) in pipeline.details.stages"
:key="index">
<pipeline-stage
+ :type="$options.pipelinesTable"
:stage="stage"
:update-dropdown="updateGraphDropdown"
/>
diff --git a/app/assets/javascripts/pipelines/components/stage.vue b/app/assets/javascripts/pipelines/components/stage.vue
index a65485c05eb..f9769815796 100644
--- a/app/assets/javascripts/pipelines/components/stage.vue
+++ b/app/assets/javascripts/pipelines/components/stage.vue
@@ -21,6 +21,7 @@ import Icon from '../../vue_shared/components/icon.vue';
import LoadingIcon from '../../vue_shared/components/loading_icon.vue';
import JobComponent from './graph/job_component.vue';
import tooltip from '../../vue_shared/directives/tooltip';
+import { PIPELINES_TABLE } from '../constants';
export default {
components: {
@@ -44,6 +45,12 @@ export default {
required: false,
default: false,
},
+
+ type: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
data() {
@@ -133,6 +140,16 @@ export default {
isDropdownOpen() {
return this.$el.classList.contains('open');
},
+
+ pipelineActionRequestComplete() {
+ if (this.type === PIPELINES_TABLE) {
+ // warn the table to update
+ eventHub.$emit('refreshPipelinesTable');
+ } else {
+ // close the dropdown in mr widget
+ $(this.$refs.dropdown).dropdown('toggle');
+ }
+ },
},
};
</script>
@@ -151,6 +168,7 @@ export default {
id="stageDropdown"
aria-haspopup="true"
aria-expanded="false"
+ ref="dropdown"
>
<span
@@ -188,6 +206,7 @@ export default {
<job-component
:job="job"
css-class-job-name="mini-pipeline-graph-dropdown-item"
+ @pipelineActionRequestComplete="pipelineActionRequestComplete"
/>
</li>
</ul>
diff --git a/app/assets/javascripts/pipelines/constants.js b/app/assets/javascripts/pipelines/constants.js
index b384c7500e7..eaa11a84cb9 100644
--- a/app/assets/javascripts/pipelines/constants.js
+++ b/app/assets/javascripts/pipelines/constants.js
@@ -1,2 +1,2 @@
-// eslint-disable-next-line import/prefer-default-export
export const CANCEL_REQUEST = 'CANCEL_REQUEST';
+export const PIPELINES_TABLE = 'PIPELINES_TABLE';
diff --git a/app/assets/javascripts/pipelines/mixins/pipelines.js b/app/assets/javascripts/pipelines/mixins/pipelines.js
index 6d87f75ae8e..705fa752822 100644
--- a/app/assets/javascripts/pipelines/mixins/pipelines.js
+++ b/app/assets/javascripts/pipelines/mixins/pipelines.js
@@ -54,10 +54,12 @@ export default {
eventHub.$on('postAction', this.postAction);
eventHub.$on('clickedDropdown', this.updateTable);
+ eventHub.$on('refreshPipelinesTable', this.fetchPipelines);
},
beforeDestroy() {
eventHub.$off('postAction', this.postAction);
eventHub.$off('clickedDropdown', this.updateTable);
+ eventHub.$off('refreshPipelinesTable', this.fetchPipelines);
},
destroyed() {
this.poll.stop();
diff --git a/app/assets/javascripts/pipelines/pipeline_details_bundle.js b/app/assets/javascripts/pipelines/pipeline_details_bundle.js
index 04fe7958fe6..b49a16a87e6 100644
--- a/app/assets/javascripts/pipelines/pipeline_details_bundle.js
+++ b/app/assets/javascripts/pipelines/pipeline_details_bundle.js
@@ -25,30 +25,14 @@ export default () => {
data() {
return {
mediator,
- requestFinishedFor: null,
};
},
- created() {
- eventHub.$on('postAction', this.postAction);
- },
- beforeDestroy() {
- eventHub.$off('postAction', this.postAction);
- },
methods: {
- postAction(action) {
- // Click was made, reset this variable
- this.requestFinishedFor = null;
-
- this.mediator.service
- .postAction(action)
- .then(() => {
- this.mediator.refreshPipeline();
- this.requestFinishedFor = action;
- })
- .catch(() => {
- this.requestFinishedFor = action;
- Flash(__('An error occurred while making the request.'));
- });
+ requestRefreshPipelineGraph() {
+ // When an action is clicked
+ // (wether in the dropdown or in the main nodes, we refresh the big graph)
+ this.mediator.refreshPipeline()
+ .catch(() => Flash(__('An error occurred while making the request.')));
},
},
render(createElement) {
@@ -56,7 +40,9 @@ export default () => {
props: {
isLoading: this.mediator.state.isLoading,
pipeline: this.mediator.store.state.pipeline,
- requestFinishedFor: this.requestFinishedFor,
+ },
+ on: {
+ refreshPipelineGraph: this.requestRefreshPipelineGraph,
},
});
},
diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb
index e85cdcb8db7..d6a6bc7d4a1 100644
--- a/app/controllers/admin/dashboard_controller.rb
+++ b/app/controllers/admin/dashboard_controller.rb
@@ -1,4 +1,6 @@
class Admin::DashboardController < Admin::ApplicationController
+ include CountHelper
+
def index
@projects = Project.order_id_desc.without_deleted.with_route.limit(10)
@users = User.order_id_desc.limit(10)
diff --git a/app/controllers/projects/prometheus/metrics_controller.rb b/app/controllers/projects/prometheus/metrics_controller.rb
index 1dd886409a5..c6b6243b553 100644
--- a/app/controllers/projects/prometheus/metrics_controller.rb
+++ b/app/controllers/projects/prometheus/metrics_controller.rb
@@ -25,7 +25,7 @@ module Projects
end
def require_prometheus_metrics!
- render_404 unless prometheus_adapter.can_query?
+ render_404 unless prometheus_adapter&.can_query?
end
end
end
diff --git a/app/helpers/count_helper.rb b/app/helpers/count_helper.rb
new file mode 100644
index 00000000000..24ee62e68ba
--- /dev/null
+++ b/app/helpers/count_helper.rb
@@ -0,0 +1,5 @@
+module CountHelper
+ def approximate_count_with_delimiters(model)
+ number_with_delimiter(Gitlab::Database::Count.approximate_count(model))
+ end
+end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 61c10c427dd..e76b8d0bf50 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -661,7 +661,7 @@ module Ci
Gitlab::Ci::Variables::Collection.new.tap do |variables|
break variables unless gitlab_deploy_token
- variables.append(key: 'CI_DEPLOY_USER', value: gitlab_deploy_token.name)
+ variables.append(key: 'CI_DEPLOY_USER', value: gitlab_deploy_token.username)
variables.append(key: 'CI_DEPLOY_PASSWORD', value: gitlab_deploy_token.token, public: false)
end
end
diff --git a/app/models/clusters/applications/prometheus.rb b/app/models/clusters/applications/prometheus.rb
index 7b25d8c4089..c702c4ee807 100644
--- a/app/models/clusters/applications/prometheus.rb
+++ b/app/models/clusters/applications/prometheus.rb
@@ -49,6 +49,11 @@ module Clusters
# ensures headers containing auth data are appended to original k8s client options
options = kube_client.rest_client.options.merge(headers: kube_client.headers)
RestClient::Resource.new(proxy_url, options)
+ rescue Kubeclient::HttpError
+ # If users have mistakenly set parameters or removed the depended clusters,
+ # `proxy_url` could raise an exception because gitlab can not communicate with the cluster.
+ # Since `PrometheusAdapter#can_query?` is eargely loaded on environement pages in gitlab,
+ # we need to silence the exceptions
end
private
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 97d89422594..a7d05722287 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -2,6 +2,7 @@ class CommitStatus < ActiveRecord::Base
include HasStatus
include Importable
include AfterCommitQueue
+ include Presentable
self.table_name = 'ci_builds'
diff --git a/app/presenters/ci/build_presenter.rb b/app/presenters/ci/build_presenter.rb
index 4873d7ce662..e0aaa5cb736 100644
--- a/app/presenters/ci/build_presenter.rb
+++ b/app/presenters/ci/build_presenter.rb
@@ -1,16 +1,5 @@
module Ci
- class BuildPresenter < Gitlab::View::Presenter::Delegated
- CALLOUT_FAILURE_MESSAGES = {
- unknown_failure: 'There is an unknown failure, please try again',
- script_failure: 'There has been a script failure. Check the job log for more information',
- api_failure: 'There has been an API failure, please try again',
- stuck_or_timeout_failure: 'There has been a timeout failure or the job got stuck. Check your timeout limits or try again',
- runner_system_failure: 'There has been a runner system failure, please try again',
- missing_dependency_failure: 'There has been a missing dependency failure, check the job log for more information'
- }.freeze
-
- presents :build
-
+ class BuildPresenter < CommitStatusPresenter
def erased_by_user?
# Build can be erased through API, therefore it does not have
# `erased_by` user assigned in that case.
@@ -44,14 +33,6 @@ module Ci
"#{subject.name} - #{detailed_status.status_tooltip}"
end
- def callout_failure_message
- CALLOUT_FAILURE_MESSAGES[failure_reason.to_sym]
- end
-
- def recoverable?
- failed? && !unrecoverable?
- end
-
private
def tooltip_for_badge
@@ -61,9 +42,5 @@ module Ci
def detailed_status
@detailed_status ||= subject.detailed_status(user)
end
-
- def unrecoverable?
- script_failure? || missing_dependency_failure?
- end
end
end
diff --git a/app/presenters/commit_status_presenter.rb b/app/presenters/commit_status_presenter.rb
new file mode 100644
index 00000000000..c7f7aa836bd
--- /dev/null
+++ b/app/presenters/commit_status_presenter.rb
@@ -0,0 +1,24 @@
+class CommitStatusPresenter < Gitlab::View::Presenter::Delegated
+ CALLOUT_FAILURE_MESSAGES = {
+ unknown_failure: 'There is an unknown failure, please try again',
+ script_failure: 'There has been a script failure. Check the job log for more information',
+ api_failure: 'There has been an API failure, please try again',
+ stuck_or_timeout_failure: 'There has been a timeout failure or the job got stuck. Check your timeout limits or try again',
+ runner_system_failure: 'There has been a runner system failure, please try again',
+ missing_dependency_failure: 'There has been a missing dependency failure, check the job log for more information'
+ }.freeze
+
+ presents :build
+
+ def callout_failure_message
+ CALLOUT_FAILURE_MESSAGES[failure_reason.to_sym]
+ end
+
+ def recoverable?
+ failed? && !unrecoverable?
+ end
+
+ def unrecoverable?
+ script_failure? || missing_dependency_failure?
+ end
+end
diff --git a/app/presenters/generic_commit_status_presenter.rb b/app/presenters/generic_commit_status_presenter.rb
new file mode 100644
index 00000000000..da09df29a37
--- /dev/null
+++ b/app/presenters/generic_commit_status_presenter.rb
@@ -0,0 +1,2 @@
+class GenericCommitStatusPresenter < CommitStatusPresenter
+end
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index bbf0e0fb95c..41ef646fc0e 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -10,7 +10,7 @@
= link_to admin_projects_path do
%h3.text-center
Projects:
- = number_with_delimiter(Project.cached_count)
+ = approximate_count_with_delimiters(Project)
%hr
= link_to('New project', new_project_path, class: "btn btn-new")
.col-sm-4
@@ -19,7 +19,7 @@
= link_to admin_users_path do
%h3.text-center
Users:
- = number_with_delimiter(User.count)
+ = approximate_count_with_delimiters(User)
%hr
= link_to 'New user', new_admin_user_path, class: "btn btn-new"
.col-sm-4
@@ -28,7 +28,7 @@
= link_to admin_groups_path do
%h3.text-center
Groups:
- = number_with_delimiter(Group.count)
+ = approximate_count_with_delimiters(Group)
%hr
= link_to 'New group', new_admin_group_path, class: "btn btn-new"
.row
@@ -39,31 +39,31 @@
%p
Forks
%span.light.pull-right
- = number_with_delimiter(ForkedProjectLink.count)
+ = approximate_count_with_delimiters(ForkedProjectLink)
%p
Issues
%span.light.pull-right
- = number_with_delimiter(Issue.count)
+ = approximate_count_with_delimiters(Issue)
%p
Merge Requests
%span.light.pull-right
- = number_with_delimiter(MergeRequest.count)
+ = approximate_count_with_delimiters(MergeRequest)
%p
Notes
%span.light.pull-right
- = number_with_delimiter(Note.count)
+ = approximate_count_with_delimiters(Note)
%p
Snippets
%span.light.pull-right
- = number_with_delimiter(Snippet.count)
+ = approximate_count_with_delimiters(Snippet)
%p
SSH Keys
%span.light.pull-right
- = number_with_delimiter(Key.count)
+ = approximate_count_with_delimiters(Key)
%p
Milestones
%span.light.pull-right
- = number_with_delimiter(Milestone.count)
+ = approximate_count_with_delimiters(Milestone)
%p
Active Users
%span.light.pull-right
diff --git a/changelogs/unreleased/46177-fix-present-on-generic-commit-status.yml b/changelogs/unreleased/46177-fix-present-on-generic-commit-status.yml
new file mode 100644
index 00000000000..2f885c5c927
--- /dev/null
+++ b/changelogs/unreleased/46177-fix-present-on-generic-commit-status.yml
@@ -0,0 +1,5 @@
+---
+title: Allow CommitStatus class to use presentable methods
+merge_request: 18979
+author:
+type: fixed
diff --git a/changelogs/unreleased/46413-error-when-prometheus-adapter-is-disabled.yml b/changelogs/unreleased/46413-error-when-prometheus-adapter-is-disabled.yml
new file mode 100644
index 00000000000..0d20f6aaf54
--- /dev/null
+++ b/changelogs/unreleased/46413-error-when-prometheus-adapter-is-disabled.yml
@@ -0,0 +1,5 @@
+---
+title: Render 404 when prometheus adapter is disabled in Prometheus metrics controller
+merge_request: 19110
+author:
+type: fixed
diff --git a/changelogs/unreleased/46454-wrong-value-in-ci-deploy-user.yml b/changelogs/unreleased/46454-wrong-value-in-ci-deploy-user.yml
new file mode 100644
index 00000000000..e610e53f71c
--- /dev/null
+++ b/changelogs/unreleased/46454-wrong-value-in-ci-deploy-user.yml
@@ -0,0 +1,5 @@
+---
+title: Fixes deploy token variables on Ci::Build
+merge_request: 19047
+author:
+type: fixed
diff --git a/changelogs/unreleased/46630-add-missing-columns-to-project-mirror-data.yml b/changelogs/unreleased/46630-add-missing-columns-to-project-mirror-data.yml
new file mode 100644
index 00000000000..cc153b85440
--- /dev/null
+++ b/changelogs/unreleased/46630-add-missing-columns-to-project-mirror-data.yml
@@ -0,0 +1,5 @@
+---
+title: Fix project mirror database inconsistencies when upgrading from EE to CE
+merge_request: 19109
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-kube_client-proxy_url-exception.yml b/changelogs/unreleased/fix-kube_client-proxy_url-exception.yml
new file mode 100644
index 00000000000..1f64ab9f30f
--- /dev/null
+++ b/changelogs/unreleased/fix-kube_client-proxy_url-exception.yml
@@ -0,0 +1,5 @@
+---
+title: Fix corrupted environment pages with unathorized proxy url
+merge_request: 18989
+author:
+type: fixed
diff --git a/changelogs/unreleased/ldap-u2f-fix.yml b/changelogs/unreleased/ldap-u2f-fix.yml
new file mode 100644
index 00000000000..40b017f3d1b
--- /dev/null
+++ b/changelogs/unreleased/ldap-u2f-fix.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed U2F login when used with LDAP
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-bump-prometheus-client-mmap.yml b/changelogs/unreleased/sh-bump-prometheus-client-mmap.yml
new file mode 100644
index 00000000000..ea8bb0b365b
--- /dev/null
+++ b/changelogs/unreleased/sh-bump-prometheus-client-mmap.yml
@@ -0,0 +1,5 @@
+---
+title: Bump prometheus-client-mmap to 0.9.3 to fix nil exception error
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-blocked-user-account-ldap.yml b/changelogs/unreleased/sh-fix-blocked-user-account-ldap.yml
new file mode 100644
index 00000000000..f7abe763ea8
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-blocked-user-account-ldap.yml
@@ -0,0 +1,5 @@
+---
+title: Fix system hook not firing for blocked users when LDAP sign-in is used
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/xeodon-gitlab-ce-fix-45743-master-fix-gitaly-delete-refs.yml b/changelogs/unreleased/xeodon-gitlab-ce-fix-45743-master-fix-gitaly-delete-refs.yml
new file mode 100644
index 00000000000..94da4d74300
--- /dev/null
+++ b/changelogs/unreleased/xeodon-gitlab-ce-fix-45743-master-fix-gitaly-delete-refs.yml
@@ -0,0 +1,5 @@
+---
+title: Fix error when deleting an empty list of refs
+merge_request:
+author:
+type: fixed
diff --git a/db/migrate/20180503175053_ensure_missing_columns_to_project_mirror_data.rb b/db/migrate/20180503175053_ensure_missing_columns_to_project_mirror_data.rb
new file mode 100644
index 00000000000..970a53d68d0
--- /dev/null
+++ b/db/migrate/20180503175053_ensure_missing_columns_to_project_mirror_data.rb
@@ -0,0 +1,15 @@
+class EnsureMissingColumnsToProjectMirrorData < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ add_column :project_mirror_data, :status, :string unless column_exists?(:project_mirror_data, :status)
+ add_column :project_mirror_data, :jid, :string unless column_exists?(:project_mirror_data, :jid)
+ add_column :project_mirror_data, :last_error, :text unless column_exists?(:project_mirror_data, :last_error)
+ end
+
+ def down
+ # db/migrate/20180502122856_create_project_mirror_data.rb will remove the table
+ end
+end
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 785cc32d590..77139c50d07 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -64,6 +64,13 @@ If you are running GitLab within a Docker container, you can run the backup from
docker exec -t <container name> gitlab-rake gitlab:backup:create
```
+If you are using the gitlab-omnibus helm chart on a Kubernetes cluster, you can
+run the backup task on the gitlab application pod using kubectl
+
+```
+kubectl exec -it <gitlab-gitlab pod> gitlab-rake gitlab:backup:create
+```
+
Example output:
```
@@ -601,6 +608,34 @@ If there is a GitLab version mismatch between your backup tar file and the insta
version of GitLab, the restore command will abort with an error. Install the
[correct GitLab version](https://packages.gitlab.com/gitlab/) and try again.
+### Restore for Docker image and gitlab-omnibus helm chart
+
+For GitLab installations using docker image or the gitlab-omnibus helm chart on
+a Kubernetes cluster, restore task expects the restore directories to be empty.
+However, with docker and Kubernetes volume mounts, some system level directories
+may be created at the volume roots, like `lost+found` directory found in Linux
+operating systems. These directories are usually owned by `root`, which can
+cause access permission errors since the restore rake task runs as `git` user.
+So, to restore a GitLab installation, users have to confirm the restore target
+directories are empty.
+
+For both these installation types, the backup tarball has to be available in the
+backup location (default location is `/var/opt/gitlab/backups`).
+
+For docker installations, the restore task can be run from host using the
+command
+
+```
+docker exec -it <name of container> gitlab-rake gitlab:backup:restore
+```
+
+Similarly, for gitlab-omnibus helm chart, the restore task can be run on the
+gitlab application pod using kubectl
+
+```
+kubectl exec -it <gitlab-gitlab pod> gitlab-rake gitlab:backup:restore
+```
+
## Alternative backup strategies
If your GitLab server contains a lot of Git repository data you may find the GitLab backup script to be too slow.
diff --git a/doc/topics/autodevops/img/rollout_enabled.png b/doc/topics/autodevops/img/rollout_enabled.png
new file mode 100644
index 00000000000..d6e7d790cf2
--- /dev/null
+++ b/doc/topics/autodevops/img/rollout_enabled.png
Binary files differ
diff --git a/doc/topics/autodevops/img/rollout_staging_disabled.png b/doc/topics/autodevops/img/rollout_staging_disabled.png
new file mode 100644
index 00000000000..71e36b440f0
--- /dev/null
+++ b/doc/topics/autodevops/img/rollout_staging_disabled.png
Binary files differ
diff --git a/doc/topics/autodevops/img/rollout_staging_enabled.png b/doc/topics/autodevops/img/rollout_staging_enabled.png
new file mode 100644
index 00000000000..d0d1d356627
--- /dev/null
+++ b/doc/topics/autodevops/img/rollout_staging_enabled.png
Binary files differ
diff --git a/doc/topics/autodevops/img/staging_enabled.png b/doc/topics/autodevops/img/staging_enabled.png
new file mode 100644
index 00000000000..0ef1a67d641
--- /dev/null
+++ b/doc/topics/autodevops/img/staging_enabled.png
Binary files differ
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index 5254e6e3d9a..33e2d710410 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -496,6 +496,7 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac
| `POSTGRES_DB` | The PostgreSQL database name; defaults to the value of [`$CI_ENVIRONMENT_SLUG`](../../ci/variables/README.md#predefined-variables-environment-variables). Set it to use a custom database name. |
| `BUILDPACK_URL` | The buildpack's full URL. It can point to either Git repositories or a tarball URL. For Git repositories, it is possible to point to a specific `ref`, for example `https://github.com/heroku/heroku-buildpack-ruby.git#v142` |
| `STAGING_ENABLED` | From GitLab 10.8, this variable can be used to define a [deploy policy for staging and production environments](#deploy-policy-for-staging-and-production-environments). |
+| `INCREMENTAL_ROLLOUT_ENABLED`| From GitLab 10.8, this variable can be used to enable an [incremental rollout](#incremental-rollout-to-production) of your application for the production environment. |
TIP: **Tip:**
Set up the replica variables using a
@@ -578,6 +579,57 @@ If `STAGING_ENABLED` is defined in your project (e.g., set `STAGING_ENABLED` to
to a `staging` environment, and a `production_manual` job will be created for
you when you're ready to manually deploy to production.
+#### Incremental rollout to production **[PREMIUM]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5415) in GitLab 10.8.
+
+When you have a new version of your app to deploy in production, you may want
+to use an incremental rollout to replace just a few pods with the latest code.
+This will allow you to first check how the app is behaving, and later manually
+increasing the rollout up to 100%.
+
+If `INCREMENTAL_ROLLOUT_ENABLED` is defined in your project (e.g., set
+`INCREMENTAL_ROLLOUT_ENABLED` to `1` as a secret variable), then instead of the
+standard `production` job, 4 different
+[manual jobs](../../ci/pipelines.md#manual-actions-from-the-pipeline-graph)
+will be created:
+
+1. `rollout 10%`
+1. `rollout 25%`
+1. `rollout 50%`
+1. `rollout 100%`
+
+The percentage is based on the `REPLICAS` variable and defines the number of
+pods you want to have for your deployment. If you say `10`, and then you run
+the `10%` rollout job, there will be `1` new pod + `9` old ones.
+
+To start a job, click on the play icon next to the job's name. You are not
+required to go from `10%` to `100%`, you can jump to whatever job you want.
+You can also scale down by running a lower percentage job, just before hitting
+`100%`. Once you get to `100%`, you cannot scale down, and you'd have to roll
+back by redeploying the old version using the
+[rollback button](../../ci/environments.md#rolling-back-changes) in the
+environment page.
+
+Below, you can see how the pipeline will look if the rollout or staging
+variables are defined.
+
+- **Without `INCREMENTAL_ROLLOUT_ENABLED` and without `STAGING_ENABLED`**
+
+ ![Staging and rollout disabled](img/rollout_staging_disabled.png)
+
+- **Without `INCREMENTAL_ROLLOUT_ENABLED` and with `STAGING_ENABLED`**
+
+ ![Staging enabled](img/staging_enabled.png)
+
+- **With `INCREMENTAL_ROLLOUT_ENABLED` and without `STAGING_ENABLED`**
+
+ ![Rollout enabled](img/rollout_enabled.png)
+
+- **With `INCREMENTAL_ROLLOUT_ENABLED` and with `STAGING_ENABLED`**
+
+ ![Rollout and staging enabled](img/rollout_staging_enabled.png)
+
## Currently supported languages
NOTE: **Note:**
diff --git a/doc/topics/autodevops/quick_start_guide.md b/doc/topics/autodevops/quick_start_guide.md
index 15567715c98..0b16af2953b 100644
--- a/doc/topics/autodevops/quick_start_guide.md
+++ b/doc/topics/autodevops/quick_start_guide.md
@@ -23,6 +23,10 @@ page](https://gitlab.com/auto-devops-examples/minimal-ruby-app) and press the
**Fork** button. Soon you should have a project under your namespace with the
necessary files.
+You can also start a new project from a
+[GitLab project template](https://gitlab.com/gitlab-org/project-templates) if
+you want to use a different language.
+
## Setup your own cluster on Google Kubernetes Engine
If you do not already have a Google Cloud account, create one at
diff --git a/doc/user/project/deploy_tokens/index.md b/doc/user/project/deploy_tokens/index.md
index 7a8b3c75690..c09d5aeba8e 100644
--- a/doc/user/project/deploy_tokens/index.md
+++ b/doc/user/project/deploy_tokens/index.md
@@ -76,7 +76,7 @@ pull images from your Container Registry.
> [Introduced][ce-18414] in GitLab 10.8.
There's a special case when it comes to Deploy Tokens, if a user creates one
-named `gitlab-deploy-token`, the name and token of the Deploy Token will be
+named `gitlab-deploy-token`, the username and token of the Deploy Token will be
automatically exposed to the CI/CD jobs as environment variables: `CI_DEPLOY_USER` and
`CI_DEPLOY_PASSWORD`, respectively.
diff --git a/lib/gitlab/auth/blocked_user_tracker.rb b/lib/gitlab/auth/blocked_user_tracker.rb
index dae03a179e4..7609a7b04f6 100644
--- a/lib/gitlab/auth/blocked_user_tracker.rb
+++ b/lib/gitlab/auth/blocked_user_tracker.rb
@@ -17,7 +17,9 @@ module Gitlab
# message passed along by Warden.
return unless message == User::BLOCKED_MESSAGE
- login = env.dig(ACTIVE_RECORD_REQUEST_PARAMS, 'user', 'login')
+ # Check for either LDAP or regular GitLab account logins
+ login = env.dig(ACTIVE_RECORD_REQUEST_PARAMS, 'username') ||
+ env.dig(ACTIVE_RECORD_REQUEST_PARAMS, 'user', 'login')
return unless login.present?
diff --git a/lib/gitlab/database/count.rb b/lib/gitlab/database/count.rb
new file mode 100644
index 00000000000..3374203960e
--- /dev/null
+++ b/lib/gitlab/database/count.rb
@@ -0,0 +1,48 @@
+# For large tables, PostgreSQL can take a long time to count rows due to MVCC.
+# We can optimize this by using the reltuples count as described in https://wiki.postgresql.org/wiki/Slow_Counting.
+module Gitlab
+ module Database
+ module Count
+ CONNECTION_ERRORS =
+ if defined?(PG)
+ [
+ ActionView::Template::Error,
+ ActiveRecord::StatementInvalid,
+ PG::Error
+ ].freeze
+ else
+ [
+ ActionView::Template::Error,
+ ActiveRecord::StatementInvalid
+ ].freeze
+ end
+
+ def self.approximate_count(model)
+ return model.count unless Gitlab::Database.postgresql?
+
+ execute_estimate_if_updated_recently(model) || model.count
+ end
+
+ def self.execute_estimate_if_updated_recently(model)
+ ActiveRecord::Base.connection.select_value(postgresql_estimate_query(model)).to_i if reltuples_updated_recently?(model)
+ rescue *CONNECTION_ERRORS
+ end
+
+ def self.reltuples_updated_recently?(model)
+ time = "to_timestamp(#{1.hour.ago.to_i})"
+ query = <<~SQL
+ SELECT 1 FROM pg_stat_user_tables WHERE relname = '#{model.table_name}' AND
+ (last_vacuum > #{time} OR last_autovacuum > #{time} OR last_analyze > #{time} OR last_autoanalyze > #{time})
+ SQL
+
+ ActiveRecord::Base.connection.select_all(query).count > 0
+ rescue *CONNECTION_ERRORS
+ false
+ end
+
+ def self.postgresql_estimate_query(model)
+ "SELECT reltuples::bigint AS estimate FROM pg_class where relname = '#{model.table_name}'"
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb
index d78d29b7ac6..156d077a69c 100644
--- a/lib/gitlab/git/blob.rb
+++ b/lib/gitlab/git/blob.rb
@@ -104,25 +104,22 @@ module Gitlab
# file.rb # oid: 4a
#
#
- # Blob.find_entry_by_path(repo, '1a', 'app/file.rb') # => '4a'
+ # Blob.find_entry_by_path(repo, '1a', 'blog', 'app', 'file.rb') # => '4a'
#
- def find_entry_by_path(repository, root_id, path)
+ def find_entry_by_path(repository, root_id, *path_parts)
root_tree = repository.lookup(root_id)
- # Strip leading slashes
- path[%r{^/*}] = ''
- path_arr = path.split('/')
entry = root_tree.find do |entry|
- entry[:name] == path_arr[0]
+ entry[:name] == path_parts[0]
end
return nil unless entry
- if path_arr.size > 1
+ if path_parts.size > 1
return nil unless entry[:type] == :tree
- path_arr.shift
- find_entry_by_path(repository, entry[:oid], path_arr.join('/'))
+ path_parts.shift
+ find_entry_by_path(repository, entry[:oid], *path_parts)
else
[:blob, :commit].include?(entry[:type]) ? entry : nil
end
@@ -185,10 +182,13 @@ module Gitlab
def find_by_rugged(repository, sha, path, limit:)
return unless path
+ # Strip any leading / characters from the path
+ path = path.sub(%r{\A/*}, '')
+
rugged_commit = repository.lookup(sha)
root_tree = rugged_commit.tree
- blob_entry = find_entry_by_path(repository, root_tree.oid, path)
+ blob_entry = find_entry_by_path(repository, root_tree.oid, *path.split('/'))
return nil unless blob_entry
diff --git a/lib/gitlab/git/path_helper.rb b/lib/gitlab/git/path_helper.rb
index 155cf52f050..57b82a37d6c 100644
--- a/lib/gitlab/git/path_helper.rb
+++ b/lib/gitlab/git/path_helper.rb
@@ -6,7 +6,7 @@ module Gitlab
class << self
def normalize_path(filename)
# Strip all leading slashes so that //foo -> foo
- filename[%r{^/*}] = ''
+ filename = filename.sub(%r{\A/*}, '')
# Expand relative paths (e.g. foo/../bar)
filename = Pathname.new(filename)
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 29a3a35812c..311c667dba7 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -2403,7 +2403,7 @@ module Gitlab
end
def gitaly_delete_refs(*ref_names)
- gitaly_ref_client.delete_refs(refs: ref_names)
+ gitaly_ref_client.delete_refs(refs: ref_names) if ref_names.any?
end
def rugged_remove_remote(remote_name)
diff --git a/spec/controllers/projects/prometheus/metrics_controller_spec.rb b/spec/controllers/projects/prometheus/metrics_controller_spec.rb
index b2b245dba90..871dcf5c796 100644
--- a/spec/controllers/projects/prometheus/metrics_controller_spec.rb
+++ b/spec/controllers/projects/prometheus/metrics_controller_spec.rb
@@ -12,44 +12,67 @@ describe Projects::Prometheus::MetricsController do
end
describe 'GET #active_common' do
- before do
- allow(controller).to receive(:prometheus_adapter).and_return(prometheus_adapter)
- end
+ context 'when prometheus_adapter can query' do
+ before do
+ allow(controller).to receive(:prometheus_adapter).and_return(prometheus_adapter)
+ end
- context 'when prometheus metrics are enabled' do
- context 'when data is not present' do
- before do
- allow(prometheus_adapter).to receive(:query).with(:matched_metrics).and_return({})
- end
+ context 'when prometheus metrics are enabled' do
+ context 'when data is not present' do
+ before do
+ allow(prometheus_adapter).to receive(:query).with(:matched_metrics).and_return({})
+ end
- it 'returns no content response' do
- get :active_common, project_params(format: :json)
+ it 'returns no content response' do
+ get :active_common, project_params(format: :json)
- expect(response).to have_gitlab_http_status(204)
+ expect(response).to have_gitlab_http_status(204)
+ end
end
- end
- context 'when data is available' do
- let(:sample_response) { { some_data: 1 } }
+ context 'when data is available' do
+ let(:sample_response) { { some_data: 1 } }
+
+ before do
+ allow(prometheus_adapter).to receive(:query).with(:matched_metrics).and_return(sample_response)
+ end
- before do
- allow(prometheus_adapter).to receive(:query).with(:matched_metrics).and_return(sample_response)
+ it 'returns no content response' do
+ get :active_common, project_params(format: :json)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to eq(sample_response.deep_stringify_keys)
+ end
end
- it 'returns no content response' do
- get :active_common, project_params(format: :json)
+ context 'when requesting non json response' do
+ it 'returns not found response' do
+ get :active_common, project_params
- expect(response).to have_gitlab_http_status(200)
- expect(json_response).to eq(sample_response.deep_stringify_keys)
+ expect(response).to have_gitlab_http_status(404)
+ end
end
end
+ end
- context 'when requesting non json response' do
- it 'returns not found response' do
- get :active_common, project_params
+ context 'when prometheus_adapter cannot query' do
+ it 'renders 404' do
+ prometheus_adapter = double('prometheus_adapter', can_query?: false)
- expect(response).to have_gitlab_http_status(404)
- end
+ allow(controller).to receive(:prometheus_adapter).and_return(prometheus_adapter)
+ allow(prometheus_adapter).to receive(:query).with(:matched_metrics).and_return({})
+
+ get :active_common, project_params(format: :json)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ context 'when prometheus_adapter is disabled' do
+ it 'renders 404' do
+ get :active_common, project_params(format: :json)
+
+ expect(response).to have_gitlab_http_status(404)
end
end
end
diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb
index 5248a783db4..f9defa22d35 100644
--- a/spec/features/projects/environments/environments_spec.rb
+++ b/spec/features/projects/environments/environments_spec.rb
@@ -42,6 +42,22 @@ feature 'Environments page', :js do
expect(page).to have_content('You don\'t have any environments right now')
end
end
+
+ context 'when cluster is not reachable' do
+ let!(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
+ let!(:application_prometheus) { create(:clusters_applications_prometheus, :installed, cluster: cluster) }
+
+ before do
+ allow_any_instance_of(Kubeclient::Client).to receive(:proxy_url).and_raise(Kubeclient::HttpError.new(401, 'Unauthorized', nil))
+ end
+
+ it 'should show one environment without error' do
+ visit_environments(project, scope: 'available')
+
+ expect(page).to have_css('.environments-container')
+ expect(page.all('.environment-name').length).to eq(1)
+ end
+ end
end
describe 'with one stopped environment' do
diff --git a/spec/javascripts/ide/components/ide_spec.js b/spec/javascripts/ide/components/ide_spec.js
index 6f580e1f7af..045a60e56a0 100644
--- a/spec/javascripts/ide/components/ide_spec.js
+++ b/spec/javascripts/ide/components/ide_spec.js
@@ -107,5 +107,11 @@ describe('ide component', () => {
vm.mousetrapStopCallback(null, vm.$el.querySelector('.dropdown-input-field'), 't'),
).toBe(true);
});
+
+ it('stops callback in monaco editor', () => {
+ setFixtures('<div class="inputarea"></div>');
+
+ expect(vm.mousetrapStopCallback(null, document.querySelector('.inputarea'), 't')).toBe(true);
+ });
});
});
diff --git a/spec/javascripts/ide/components/repo_editor_spec.js b/spec/javascripts/ide/components/repo_editor_spec.js
index ff500acd849..d3f80e6f9c0 100644
--- a/spec/javascripts/ide/components/repo_editor_spec.js
+++ b/spec/javascripts/ide/components/repo_editor_spec.js
@@ -346,4 +346,24 @@ describe('RepoEditor', () => {
});
});
});
+
+ it('calls removePendingTab when old file is pending', done => {
+ spyOnProperty(vm, 'shouldHideEditor').and.returnValue(true);
+ spyOn(vm, 'removePendingTab');
+
+ vm.file.pending = true;
+
+ vm
+ .$nextTick()
+ .then(() => {
+ vm.file = file('testing');
+
+ return vm.$nextTick();
+ })
+ .then(() => {
+ expect(vm.removePendingTab).toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
});
diff --git a/spec/javascripts/pipelines/graph/action_component_spec.js b/spec/javascripts/pipelines/graph/action_component_spec.js
index d646bef96f5..55856c0bd23 100644
--- a/spec/javascripts/pipelines/graph/action_component_spec.js
+++ b/spec/javascripts/pipelines/graph/action_component_spec.js
@@ -1,13 +1,19 @@
import Vue from 'vue';
import actionComponent from '~/pipelines/components/graph/action_component.vue';
-import eventHub from '~/pipelines/event_hub';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import mountComponent from '../../helpers/vue_mount_component_helper';
describe('pipeline graph action component', () => {
let component;
+ let mock;
beforeEach(done => {
const ActionComponent = Vue.extend(actionComponent);
+ mock = new MockAdapter(axios);
+
+ mock.onPost('foo.json').reply(200);
+
component = mountComponent(ActionComponent, {
tooltipText: 'bar',
link: 'foo',
@@ -18,15 +24,10 @@ describe('pipeline graph action component', () => {
});
afterEach(() => {
+ mock.restore();
component.$destroy();
});
- it('should emit an event with the provided link', () => {
- eventHub.$on('postAction', link => {
- expect(link).toEqual('foo');
- });
- });
-
it('should render the provided title as a bootstrap tooltip', () => {
expect(component.$el.getAttribute('data-original-title')).toEqual('bar');
});
@@ -34,55 +35,29 @@ describe('pipeline graph action component', () => {
it('should update bootstrap tooltip when title changes', done => {
component.tooltipText = 'changed';
- setTimeout(() => {
- expect(component.$el.getAttribute('data-original-title')).toBe('changed');
- done();
- });
- });
-
- it('should render an svg', () => {
- expect(component.$el.querySelector('.ci-action-icon-wrapper')).toBeDefined();
- expect(component.$el.querySelector('svg')).toBeDefined();
- });
-
- it('disables the button when clicked', done => {
- component.$el.click();
-
- component.$nextTick(() => {
- expect(component.$el.getAttribute('disabled')).toEqual('disabled');
- done();
- });
- });
-
- it('re-enabled the button when `requestFinishedFor` matches `linkRequested`', done => {
- component.$el.click();
-
- component
- .$nextTick()
+ component.$nextTick()
.then(() => {
- expect(component.$el.getAttribute('disabled')).toEqual('disabled');
- component.requestFinishedFor = 'foo';
- })
- .then(() => {
- expect(component.$el.getAttribute('disabled')).toBeNull();
+ expect(component.$el.getAttribute('data-original-title')).toBe('changed');
})
.then(done)
.catch(done.fail);
});
- it('does not re-enable the button when `requestFinishedFor` does not matches `linkRequested`', done => {
- component.$el.click();
+ it('should render an svg', () => {
+ expect(component.$el.querySelector('.ci-action-icon-wrapper')).toBeDefined();
+ expect(component.$el.querySelector('svg')).toBeDefined();
+ });
- component
- .$nextTick()
+ describe('on click', () => {
+ it('emits `pipelineActionRequestComplete` after a successfull request', done => {
+ spyOn(component, '$emit');
+ component.$el.click();
+ component.$nextTick()
.then(() => {
- expect(component.$el.getAttribute('disabled')).toEqual('disabled');
- component.requestFinishedFor = 'bar';
- })
- .then(() => {
- expect(component.$el.getAttribute('disabled')).toEqual('disabled');
+ expect(component.$emit).toHaveBeenCalledWith('pipelineActionRequestComplete');
})
.then(done)
.catch(done.fail);
+ });
});
});
diff --git a/spec/javascripts/pipelines/stage_spec.js b/spec/javascripts/pipelines/stage_spec.js
index 75156e7bdfd..16f6db39d6a 100644
--- a/spec/javascripts/pipelines/stage_spec.js
+++ b/spec/javascripts/pipelines/stage_spec.js
@@ -102,4 +102,31 @@ describe('Pipelines stage component', () => {
});
});
});
+
+ describe('pipelineActionRequestComplete', () => {
+ beforeEach(() => {
+ mock.onGet('path.json').reply(200, stageReply);
+
+ mock.onPost(`${stageReply.latest_statuses[0].status.action.path}.json`).reply(200);
+ });
+
+ describe('within pipeline table', () => {
+ it('emits `refreshPipelinesTable` event when `pipelineActionRequestComplete` is triggered', done => {
+ spyOn(eventHub, '$emit');
+
+ component.type = 'PIPELINES_TABLE';
+ component.$el.querySelector('button').click();
+
+ setTimeout(() => {
+ component.$el.querySelector('.js-ci-action').click();
+ component.$nextTick()
+ .then(() => {
+ expect(eventHub.$emit).toHaveBeenCalledWith('refreshPipelinesTable');
+ })
+ .then(done)
+ .catch(done.fail);
+ }, 0);
+ });
+ });
+ });
});
diff --git a/spec/lib/gitlab/auth/blocked_user_tracker_spec.rb b/spec/lib/gitlab/auth/blocked_user_tracker_spec.rb
index 726a3c1c83a..43b68e69131 100644
--- a/spec/lib/gitlab/auth/blocked_user_tracker_spec.rb
+++ b/spec/lib/gitlab/auth/blocked_user_tracker_spec.rb
@@ -17,12 +17,8 @@ describe Gitlab::Auth::BlockedUserTracker do
end
context 'failed login due to blocked user' do
- let(:env) do
- {
- 'warden.options' => { message: User::BLOCKED_MESSAGE },
- described_class::ACTIVE_RECORD_REQUEST_PARAMS => { 'user' => { 'login' => user.username } }
- }
- end
+ let(:base_env) { { 'warden.options' => { message: User::BLOCKED_MESSAGE } } }
+ let(:env) { base_env.merge(request_env) }
subject { described_class.log_if_user_blocked(env) }
@@ -30,23 +26,37 @@ describe Gitlab::Auth::BlockedUserTracker do
expect_any_instance_of(SystemHooksService).to receive(:execute_hooks_for).with(user, :failed_login)
end
- it 'logs a blocked user' do
- user.block!
+ context 'via GitLab login' do
+ let(:request_env) { { described_class::ACTIVE_RECORD_REQUEST_PARAMS => { 'user' => { 'login' => user.username } } } }
- expect(subject).to be_truthy
- end
+ it 'logs a blocked user' do
+ user.block!
+
+ expect(subject).to be_truthy
+ end
- it 'logs a blocked user by e-mail' do
- user.block!
- env[described_class::ACTIVE_RECORD_REQUEST_PARAMS]['user']['login'] = user.email
+ it 'logs a blocked user by e-mail' do
+ user.block!
+ env[described_class::ACTIVE_RECORD_REQUEST_PARAMS]['user']['login'] = user.email
- expect(subject).to be_truthy
+ expect(subject).to be_truthy
+ end
end
- it 'logs a LDAP blocked user' do
- user.ldap_block!
+ context 'via LDAP login' do
+ let(:request_env) { { described_class::ACTIVE_RECORD_REQUEST_PARAMS => { 'username' => user.username } } }
+
+ it 'logs a blocked user' do
+ user.block!
+
+ expect(subject).to be_truthy
+ end
+
+ it 'logs a LDAP blocked user' do
+ user.ldap_block!
- expect(subject).to be_truthy
+ expect(subject).to be_truthy
+ end
end
end
end
diff --git a/spec/lib/gitlab/database/count_spec.rb b/spec/lib/gitlab/database/count_spec.rb
new file mode 100644
index 00000000000..9d9caaabe16
--- /dev/null
+++ b/spec/lib/gitlab/database/count_spec.rb
@@ -0,0 +1,62 @@
+require 'spec_helper'
+
+describe Gitlab::Database::Count do
+ before do
+ create_list(:project, 3)
+ end
+
+ describe '.execute_estimate_if_updated_recently', :postgresql do
+ context 'when reltuples have not been updated' do
+ before do
+ expect(described_class).to receive(:reltuples_updated_recently?).and_return(false)
+ end
+
+ it 'returns nil' do
+ expect(described_class.execute_estimate_if_updated_recently(Project)).to be nil
+ end
+ end
+
+ context 'when reltuples have been updated' do
+ before do
+ ActiveRecord::Base.connection.execute('ANALYZE projects')
+ end
+
+ it 'calls postgresql_estimate_query' do
+ expect(described_class).to receive(:postgresql_estimate_query).with(Project).and_call_original
+ expect(described_class.execute_estimate_if_updated_recently(Project)).to eq(3)
+ end
+ end
+ end
+
+ describe '.approximate_count' do
+ context 'when reltuples have not been updated' do
+ it 'counts all projects the normal way' do
+ allow(described_class).to receive(:reltuples_updated_recently?).and_return(false)
+
+ expect(Project).to receive(:count).and_call_original
+ expect(described_class.approximate_count(Project)).to eq(3)
+ end
+ end
+
+ context 'no permission' do
+ it 'falls back to standard query' do
+ allow(described_class).to receive(:reltuples_updated_recently?).and_raise(PG::InsufficientPrivilege)
+
+ expect(Project).to receive(:count).and_call_original
+ expect(described_class.approximate_count(Project)).to eq(3)
+ end
+ end
+
+ describe 'when reltuples have been updated', :postgresql do
+ before do
+ ActiveRecord::Base.connection.execute('ANALYZE projects')
+ end
+
+ it 'counts all projects in the fast way' do
+ expect(described_class).to receive(:postgresql_estimate_query).with(Project).and_call_original
+
+ expect(described_class.approximate_count(Project)).to eq(3)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index cce84276fe3..664d8a00dd9 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -600,6 +600,10 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
end
+ it 'does not fail when deleting an empty list of refs' do
+ expect { repo.delete_refs(*[]) }.not_to raise_error
+ end
+
it 'raises an error if it failed' do
expect { repo.delete_refs('refs\heads\fix') }.to raise_error(Gitlab::Git::Repository::GitError)
end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index dc810489011..5eb4fffbaaf 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -2044,7 +2044,7 @@ describe Ci::Build do
let(:deploy_token_variables) do
[
- { key: 'CI_DEPLOY_USER', value: deploy_token.name, public: true },
+ { key: 'CI_DEPLOY_USER', value: deploy_token.username, public: true },
{ key: 'CI_DEPLOY_PASSWORD', value: deploy_token.token, public: false }
]
end
diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb
index aeca6ee903a..407e2fc598a 100644
--- a/spec/models/clusters/applications/prometheus_spec.rb
+++ b/spec/models/clusters/applications/prometheus_spec.rb
@@ -85,6 +85,16 @@ describe Clusters::Applications::Prometheus do
it 'copies options and headers from kube client to proxy client' do
expect(subject.prometheus_client.options).to eq(kube_client.rest_client.options.merge(headers: kube_client.headers))
end
+
+ context 'when cluster is not reachable' do
+ before do
+ allow(kube_client).to receive(:proxy_url).and_raise(Kubeclient::HttpError.new(401, 'Unauthorized', nil))
+ end
+
+ it 'returns nil' do
+ expect(subject.prometheus_client).to be_nil
+ end
+ end
end
end
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index 2ed29052dc1..f3f2bc28d2c 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -565,4 +565,10 @@ describe CommitStatus do
it_behaves_like 'commit status enqueued'
end
end
+
+ describe '#present' do
+ subject { commit_status.present }
+
+ it { is_expected.to be_a(CommitStatusPresenter) }
+ end
end
diff --git a/spec/models/generic_commit_status_spec.rb b/spec/models/generic_commit_status_spec.rb
index 673049d1cc4..a3e68d2e646 100644
--- a/spec/models/generic_commit_status_spec.rb
+++ b/spec/models/generic_commit_status_spec.rb
@@ -78,4 +78,10 @@ describe GenericCommitStatus do
it { is_expected.not_to be_nil }
end
end
+
+ describe '#present' do
+ subject { generic_commit_status.present }
+
+ it { is_expected.to be_a(GenericCommitStatusPresenter) }
+ end
end
diff --git a/spec/presenters/ci/build_presenter_spec.rb b/spec/presenters/ci/build_presenter_spec.rb
index 4bc005df2fc..efd175247b5 100644
--- a/spec/presenters/ci/build_presenter_spec.rb
+++ b/spec/presenters/ci/build_presenter_spec.rb
@@ -10,7 +10,7 @@ describe Ci::BuildPresenter do
end
it 'inherits from Gitlab::View::Presenter::Delegated' do
- expect(described_class.superclass).to eq(Gitlab::View::Presenter::Delegated)
+ expect(described_class.ancestors).to include(Gitlab::View::Presenter::Delegated)
end
describe '#initialize' do
diff --git a/spec/presenters/commit_status_presenter_spec.rb b/spec/presenters/commit_status_presenter_spec.rb
new file mode 100644
index 00000000000..f81ee44e371
--- /dev/null
+++ b/spec/presenters/commit_status_presenter_spec.rb
@@ -0,0 +1,15 @@
+require 'spec_helper'
+
+describe CommitStatusPresenter do
+ let(:project) { create(:project) }
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ let(:build) { create(:ci_build, pipeline: pipeline) }
+
+ subject(:presenter) do
+ described_class.new(build)
+ end
+
+ it 'inherits from Gitlab::View::Presenter::Delegated' do
+ expect(described_class.superclass).to eq(Gitlab::View::Presenter::Delegated)
+ end
+end