summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--GITLAB_PAGES_VERSION2
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js40
-rw-r--r--app/assets/javascripts/issue_show/components/edit_actions.vue4
-rw-r--r--app/assets/javascripts/issue_show/components/fields/description.vue3
-rw-r--r--app/assets/javascripts/issue_show/components/fields/title.vue2
-rw-r--r--app/assets/javascripts/issue_show/components/title.vue3
-rw-r--r--app/assets/javascripts/notes/components/comment_form.vue15
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue40
-rw-r--r--app/assets/javascripts/pages/snippets/form.js1
-rw-r--r--app/assets/javascripts/shared/milestones/form.js1
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/field.vue1
-rw-r--r--app/controllers/projects/autocomplete_sources_controller.rb4
-rw-r--r--app/controllers/projects_controller.rb1
-rw-r--r--app/finders/issuable_finder.rb7
-rw-r--r--app/finders/issues_finder.rb10
-rw-r--r--app/finders/labels_finder.rb4
-rw-r--r--app/graphql/types/permission_types/project.rb2
-rw-r--r--app/helpers/application_helper.rb3
-rw-r--r--app/helpers/projects_helper.rb6
-rw-r--r--app/models/ci/pipeline.rb28
-rw-r--r--app/models/concerns/diff_positionable_note.rb81
-rw-r--r--app/models/concerns/issuable.rb1
-rw-r--r--app/models/diff_note.rb68
-rw-r--r--app/models/project.rb12
-rw-r--r--app/models/project_feature.rb31
-rw-r--r--app/policies/project_policy.rb5
-rw-r--r--app/serializers/build_details_entity.rb8
-rw-r--r--app/services/merge_requests/base_service.rb6
-rw-r--r--app/services/merge_requests/refresh_service.rb59
-rw-r--r--app/services/projects/autocomplete_service.rb4
-rw-r--r--app/services/projects/update_pages_configuration_service.rb8
-rw-r--r--app/services/projects/update_service.rb10
-rw-r--r--changelogs/unreleased/22188-drop-down-filter-for-project-snippets.yml5
-rw-r--r--changelogs/unreleased/50246-can-t-sort-group-issues-by-popularity-when-searching.yml6
-rw-r--r--changelogs/unreleased/51748-filter-any-milestone-via-api.yml5
-rw-r--r--changelogs/unreleased/auth.yml5
-rw-r--r--changelogs/unreleased/feature-gb-pipeline-only-except-with-modified-paths.yml5
-rw-r--r--changelogs/unreleased/features-unauth-access-ssh-keys.yml5
-rw-r--r--changelogs/unreleased/mao-48221-issues_show_sql_count.yml5
-rw-r--r--config/gitlab.yml.example1
-rw-r--r--config/initializers/1_settings.rb1
-rw-r--r--config/routes/project.rb1
-rw-r--r--db/migrate/20180423204600_add_pages_access_level_to_project_feature.rb16
-rw-r--r--db/schema.rb1
-rw-r--r--doc/administration/pages/index.md75
-rw-r--r--doc/api/issues.md2
-rw-r--r--doc/api/users.md2
-rw-r--r--doc/ci/yaml/README.md49
-rw-r--r--doc/development/contributing/issue_workflow.md2
-rw-r--r--doc/user/permissions.md2
-rw-r--r--lib/api/projects.rb6
-rw-r--r--lib/api/users.rb6
-rw-r--r--lib/banzai/cross_project_reference.rb1
-rw-r--r--lib/banzai/filter/label_reference_filter.rb2
-rw-r--r--lib/gitlab/ci/build/policy/changes.rb25
-rw-r--r--lib/gitlab/ci/config/entry/policy.rb6
-rw-r--r--lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml7
-rw-r--r--lib/gitlab/git/diff_stats_collection.rb4
-rw-r--r--lib/gitlab/git/push.rb56
-rw-r--r--lib/gitlab/gitaly_client.rb2
-rw-r--r--qa/qa.rb3
-rw-r--r--qa/qa/page/component/issuable/common.rb35
-rw-r--r--qa/qa/page/project/issue/show.rb2
-rw-r--r--spec/controllers/groups_controller_spec.rb29
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb12
-rw-r--r--spec/controllers/projects/jobs_controller_spec.rb1
-rw-r--r--spec/factories/projects.rb18
-rw-r--r--spec/features/issues/gfm_autocomplete_spec.rb11
-rw-r--r--spec/finders/issues_finder_spec.rb8
-rw-r--r--spec/fixtures/api/schemas/job/deployment_status.json2
-rw-r--r--spec/graphql/types/permission_types/project_spec.rb2
-rw-r--r--spec/helpers/application_helper_spec.rb4
-rw-r--r--spec/javascripts/gfm_auto_complete_spec.js2
-rw-r--r--spec/lib/banzai/cross_project_reference_spec.rb19
-rw-r--r--spec/lib/banzai/filter/commit_range_reference_filter_spec.rb1
-rw-r--r--spec/lib/gitlab/ci/build/policy/changes_spec.rb107
-rw-r--r--spec/lib/gitlab/ci/config/entry/policy_spec.rb20
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb10
-rw-r--r--spec/lib/gitlab/git/diff_stats_collection_spec.rb8
-rw-r--r--spec/lib/gitlab/git/push_spec.rb166
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml1
-rw-r--r--spec/migrations/add_pages_access_level_to_project_feature_spec.rb30
-rw-r--r--spec/models/ci/pipeline_spec.rb51
-rw-r--r--spec/models/internal_id_spec.rb3
-rw-r--r--spec/models/project_feature_spec.rb31
-rw-r--r--spec/requests/api/issues_spec.rb10
-rw-r--r--spec/requests/api/pages/internal_access_spec.rb88
-rw-r--r--spec/requests/api/pages/private_access_spec.rb88
-rw-r--r--spec/requests/api/pages/public_access_spec.rb88
-rw-r--r--spec/requests/api/users_spec.rb38
-rw-r--r--spec/routing/project_routing_spec.rb3
-rw-r--r--spec/services/projects/update_service_spec.rb21
-rw-r--r--spec/spec_helper.rb5
-rw-r--r--yarn.lock8
94 files changed, 1445 insertions, 253 deletions
diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION
index 9084fa2f716..26aaba0e866 100644
--- a/GITLAB_PAGES_VERSION
+++ b/GITLAB_PAGES_VERSION
@@ -1 +1 @@
-1.1.0
+1.2.0
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index 73b2cd0b2c7..95636a9ccdd 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -15,6 +15,7 @@ export const defaultAutocompleteConfig = {
epics: true,
milestones: true,
labels: true,
+ snippets: true,
};
class GfmAutoComplete {
@@ -50,6 +51,7 @@ class GfmAutoComplete {
if (this.enableMap.milestones) this.setupMilestones($input);
if (this.enableMap.mergeRequests) this.setupMergeRequests($input);
if (this.enableMap.labels) this.setupLabels($input);
+ if (this.enableMap.snippets) this.setupSnippets($input);
// We don't instantiate the quick actions autocomplete for note and issue/MR edit forms
$input.filter('[data-supports-quick-actions="true"]').atwho({
@@ -360,6 +362,39 @@ class GfmAutoComplete {
});
}
+ setupSnippets($input) {
+ $input.atwho({
+ at: '$',
+ alias: 'snippets',
+ searchKey: 'search',
+ displayTpl(value) {
+ let tmpl = GfmAutoComplete.Loading.template;
+ if (value.title != null) {
+ tmpl = GfmAutoComplete.Issues.template;
+ }
+ return tmpl;
+ },
+ data: GfmAutoComplete.defaultLoadingData,
+ // eslint-disable-next-line no-template-curly-in-string
+ insertTpl: '${atwho-at}${id}',
+ callbacks: {
+ ...this.getDefaultCallbacks(),
+ beforeSave(snippets) {
+ return $.map(snippets, (m) => {
+ if (m.title == null) {
+ return m;
+ }
+ return {
+ id: m.id,
+ title: sanitize(m.title),
+ search: `${m.id} ${m.title}`,
+ };
+ });
+ },
+ },
+ });
+ }
+
getDefaultCallbacks() {
const fetchData = this.fetchData.bind(this);
@@ -470,7 +505,7 @@ class GfmAutoComplete {
// The below is taken from At.js source
// Tweaked to commands to start without a space only if char before is a non-word character
// https://github.com/ichord/At.js
- const atSymbolsWithBar = Object.keys(controllers).join('|');
+ const atSymbolsWithBar = Object.keys(controllers).join('|').replace(/[$]/, '\\$&');
const atSymbolsWithoutBar = Object.keys(controllers).join('');
const targetSubtext = subtext.split(GfmAutoComplete.regexSubtext).pop();
const resultantFlag = flag.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&');
@@ -497,6 +532,7 @@ GfmAutoComplete.atTypeMap = {
'~': 'labels',
'%': 'milestones',
'/': 'commands',
+ $: 'snippets',
};
// Emoji
@@ -519,7 +555,7 @@ GfmAutoComplete.Labels = {
// eslint-disable-next-line no-template-curly-in-string
template: '<li><span class="dropdown-label-box" style="background: ${color}"></span> ${title}</li>',
};
-// Issues and MergeRequests
+// Issues, MergeRequests and Snippets
GfmAutoComplete.Issues = {
// eslint-disable-next-line no-template-curly-in-string
template: '<li><small>${id}</small> ${title}</li>',
diff --git a/app/assets/javascripts/issue_show/components/edit_actions.vue b/app/assets/javascripts/issue_show/components/edit_actions.vue
index bcf8686afcc..c3d39082714 100644
--- a/app/assets/javascripts/issue_show/components/edit_actions.vue
+++ b/app/assets/javascripts/issue_show/components/edit_actions.vue
@@ -66,7 +66,7 @@
<button
:class="{ disabled: formState.updateLoading || !isSubmitEnabled }"
:disabled="formState.updateLoading || !isSubmitEnabled"
- class="btn btn-success float-left"
+ class="btn btn-success float-left qa-save-button"
type="submit"
@click.prevent="updateIssuable">
Save changes
@@ -86,7 +86,7 @@
v-if="shouldShowDeleteButton"
:class="{ disabled: deleteLoading }"
:disabled="deleteLoading"
- class="btn btn-danger float-right append-right-default"
+ class="btn btn-danger float-right append-right-default qa-delete-button"
type="button"
@click="deleteIssuable">
Delete
diff --git a/app/assets/javascripts/issue_show/components/fields/description.vue b/app/assets/javascripts/issue_show/components/fields/description.vue
index 97acc5ba385..1a78c59d715 100644
--- a/app/assets/javascripts/issue_show/components/fields/description.vue
+++ b/app/assets/javascripts/issue_show/components/fields/description.vue
@@ -61,7 +61,8 @@
ref="textarea"
slot="textarea"
v-model="formState.description"
- class="note-textarea js-gfm-input js-autosize markdown-area"
+ class="note-textarea js-gfm-input js-autosize markdown-area
+ qa-description-textarea"
data-supports-quick-actions="false"
aria-label="Description"
placeholder="Write a comment or drag your files here…"
diff --git a/app/assets/javascripts/issue_show/components/fields/title.vue b/app/assets/javascripts/issue_show/components/fields/title.vue
index 7d1526a64b4..b7f2b1a6050 100644
--- a/app/assets/javascripts/issue_show/components/fields/title.vue
+++ b/app/assets/javascripts/issue_show/components/fields/title.vue
@@ -22,7 +22,7 @@
<input
id="issuable-title"
v-model="formState.title"
- class="form-control"
+ class="form-control qa-title-input"
type="text"
placeholder="Title"
aria-label="Title"
diff --git a/app/assets/javascripts/issue_show/components/title.vue b/app/assets/javascripts/issue_show/components/title.vue
index cf99e9a9cd8..ed26e53ac0e 100644
--- a/app/assets/javascripts/issue_show/components/title.vue
+++ b/app/assets/javascripts/issue_show/components/title.vue
@@ -79,7 +79,8 @@ export default {
v-if="showInlineEditButton && canUpdate"
v-tooltip
type="button"
- class="btn btn-default btn-edit btn-svg js-issuable-edit"
+ class="btn btn-default btn-edit btn-svg js-issuable-edit
+ qa-edit-button"
title="Edit title and description"
data-placement="bottom"
data-container="body"
diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue
index 7735133c470..b980e43b898 100644
--- a/app/assets/javascripts/notes/components/comment_form.vue
+++ b/app/assets/javascripts/notes/components/comment_form.vue
@@ -7,7 +7,11 @@ import { __, sprintf } from '~/locale';
import Flash from '../../flash';
import Autosave from '../../autosave';
import TaskList from '../../task_list';
-import { capitalizeFirstCharacter, convertToCamelCase, splitCamelCase } from '../../lib/utils/text_utility';
+import {
+ capitalizeFirstCharacter,
+ convertToCamelCase,
+ splitCamelCase,
+} from '../../lib/utils/text_utility';
import * as constants from '../constants';
import eventHub from '../event_hub';
import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
@@ -122,7 +126,9 @@ export default {
return this.getNoteableData.create_note_path;
},
issuableTypeTitle() {
- return this.noteableType === constants.MERGE_REQUEST_NOTEABLE_TYPE ? 'merge request' : 'issue';
+ return this.noteableType === constants.MERGE_REQUEST_NOTEABLE_TYPE
+ ? 'merge request'
+ : 'issue';
},
},
watch: {
@@ -359,7 +365,7 @@ Please check your network connection and try again.`;
:disabled="isSubmitting"
name="note[note]"
class="note-textarea js-vue-comment-form js-note-text
-js-gfm-input js-autosize markdown-area js-vue-textarea"
+js-gfm-input js-autosize markdown-area js-vue-textarea qa-comment-input"
data-supports-quick-actions="true"
aria-label="Description"
placeholder="Write a comment or drag your files here…"
@@ -374,7 +380,8 @@ js-gfm-input js-autosize markdown-area js-vue-textarea"
append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown">
<button
:disabled="isSubmitButtonDisabled"
- class="btn btn-success comment-btn js-comment-button js-comment-submit-button"
+ class="btn btn-create comment-btn js-comment-button js-comment-submit-button
+ qa-comment-button"
type="submit"
@click.prevent="handleSave()">
{{ __(commentButtonTitle) }}
diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
index 875f6928bed..a16f7e6b77c 100644
--- a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
+++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
@@ -52,6 +52,21 @@
required: false,
default: '',
},
+ pagesAvailable: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ pagesAccessControlEnabled: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ pagesHelpPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
data() {
@@ -64,6 +79,7 @@
buildsAccessLevel: 20,
wikiAccessLevel: 20,
snippetsAccessLevel: 20,
+ pagesAccessLevel: 20,
containerRegistryEnabled: true,
lfsEnabled: true,
requestAccessEnabled: true,
@@ -90,6 +106,13 @@
);
},
+ pagesFeatureAccessLevelOptions() {
+ if (this.visibilityLevel !== visibilityOptions.PUBLIC) {
+ return this.featureAccessLevelOptions.concat([[30, 'Everyone']]);
+ }
+ return this.featureAccessLevelOptions;
+ },
+
repositoryEnabled() {
return this.repositoryAccessLevel > 0;
},
@@ -109,6 +132,10 @@
this.buildsAccessLevel = Math.min(10, this.buildsAccessLevel);
this.wikiAccessLevel = Math.min(10, this.wikiAccessLevel);
this.snippetsAccessLevel = Math.min(10, this.snippetsAccessLevel);
+ if (this.pagesAccessLevel === 20) {
+ // When from Internal->Private narrow access for only members
+ this.pagesAccessLevel = 10;
+ }
this.highlightChanges();
} else if (oldValue === visibilityOptions.PRIVATE) {
// if changing away from private, make enabled features more permissive
@@ -118,6 +145,7 @@
if (this.buildsAccessLevel > 0) this.buildsAccessLevel = 20;
if (this.wikiAccessLevel > 0) this.wikiAccessLevel = 20;
if (this.snippetsAccessLevel > 0) this.snippetsAccessLevel = 20;
+ if (this.pagesAccessLevel === 10) this.pagesAccessLevel = 20;
this.highlightChanges();
}
},
@@ -323,6 +351,18 @@
name="project[project_feature_attributes][snippets_access_level]"
/>
</project-setting-row>
+ <project-setting-row
+ v-if="pagesAvailable && pagesAccessControlEnabled"
+ :help-path="pagesHelpPath"
+ label="Pages"
+ help-text="Static website for the project."
+ >
+ <project-feature-setting
+ v-model="pagesAccessLevel"
+ :options="pagesFeatureAccessLevelOptions"
+ name="project[project_feature_attributes][pages_access_level]"
+ />
+ </project-setting-row>
</div>
</div>
</template>
diff --git a/app/assets/javascripts/pages/snippets/form.js b/app/assets/javascripts/pages/snippets/form.js
index f369c7ef9a6..8859557e62d 100644
--- a/app/assets/javascripts/pages/snippets/form.js
+++ b/app/assets/javascripts/pages/snippets/form.js
@@ -11,6 +11,7 @@ export default () => {
epics: false,
milestones: false,
labels: false,
+ snippets: false,
});
new ZenMode(); // eslint-disable-line no-new
};
diff --git a/app/assets/javascripts/shared/milestones/form.js b/app/assets/javascripts/shared/milestones/form.js
index 8681a1776c6..0ff84dc4667 100644
--- a/app/assets/javascripts/shared/milestones/form.js
+++ b/app/assets/javascripts/shared/milestones/form.js
@@ -15,5 +15,6 @@ export default (initGFM = true) => {
epics: initGFM,
milestones: initGFM,
labels: initGFM,
+ snippets: initGFM,
});
};
diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue
index d62537021ca..10e8ddad9cd 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/field.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue
@@ -76,6 +76,7 @@
epics: this.enableAutocomplete,
milestones: this.enableAutocomplete,
labels: this.enableAutocomplete,
+ snippets: this.enableAutocomplete,
});
},
beforeDestroy() {
diff --git a/app/controllers/projects/autocomplete_sources_controller.rb b/app/controllers/projects/autocomplete_sources_controller.rb
index 7c93cf36862..d386fb63d9f 100644
--- a/app/controllers/projects/autocomplete_sources_controller.rb
+++ b/app/controllers/projects/autocomplete_sources_controller.rb
@@ -27,6 +27,10 @@ class Projects::AutocompleteSourcesController < Projects::ApplicationController
render json: @autocomplete_service.commands(target, params[:type])
end
+ def snippets
+ render json: @autocomplete_service.snippets
+ end
+
private
def load_autocomplete_service
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index a9417369ca2..ee438e160f2 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -366,6 +366,7 @@ class ProjectsController < Projects::ApplicationController
repository_access_level
snippets_access_level
wiki_access_level
+ pages_access_level
]
]
end
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 9e24154e4b6..1f98ecf95ca 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -363,6 +363,7 @@ class IssuableFinder
def use_cte_for_search?
return false unless search
return false unless Gitlab::Database.postgresql?
+ return false unless Feature.enabled?(:use_cte_for_group_issues_search, default_enabled: true)
params[:use_cte_for_search]
end
@@ -428,6 +429,10 @@ class IssuableFinder
params[:milestone_title] == Milestone::Upcoming.name
end
+ def filter_by_any_milestone?
+ params[:milestone_title] == Milestone::Any.title
+ end
+
def filter_by_started_milestone?
params[:milestone_title] == Milestone::Started.name
end
@@ -437,6 +442,8 @@ class IssuableFinder
if milestones?
if filter_by_no_milestone?
items = items.left_joins_milestones.where(milestone_id: [-1, nil])
+ elsif filter_by_any_milestone?
+ items = items.any_milestone
elsif filter_by_upcoming_milestone?
upcoming_ids = Milestone.upcoming_ids_by_projects(projects(items))
items = items.left_joins_milestones.where(milestone_id: upcoming_ids)
diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb
index 770e0bfe1a3..abdc47b9866 100644
--- a/app/finders/issues_finder.rb
+++ b/app/finders/issues_finder.rb
@@ -120,9 +120,13 @@ class IssuesFinder < IssuableFinder
return @user_can_see_all_confidential_issues = true if current_user.full_private_access?
@user_can_see_all_confidential_issues =
- project? &&
- project &&
- project.team.max_member_access(current_user.id) >= CONFIDENTIAL_ACCESS_LEVEL
+ if project? && project
+ project.team.max_member_access(current_user.id) >= CONFIDENTIAL_ACCESS_LEVEL
+ elsif group
+ group.max_member_access_for_user(current_user) >= CONFIDENTIAL_ACCESS_LEVEL
+ else
+ false
+ end
end
def user_cannot_see_confidential_issues?
diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb
index 82e0b2ed9e1..d000af21be3 100644
--- a/app/finders/labels_finder.rb
+++ b/app/finders/labels_finder.rb
@@ -129,7 +129,7 @@ class LabelsFinder < UnionFinder
end
def project?
- params[:project_id].present?
+ params[:project].present? || params[:project_id].present?
end
def projects?
@@ -152,7 +152,7 @@ class LabelsFinder < UnionFinder
return @project if defined?(@project)
if project?
- @project = Project.find(params[:project_id])
+ @project = params[:project] || Project.find(params[:project_id])
@project = nil unless authorized_to_read_labels?(@project)
else
@project = nil
diff --git a/app/graphql/types/permission_types/project.rb b/app/graphql/types/permission_types/project.rb
index 066ce64a254..ab37c282fe5 100644
--- a/app/graphql/types/permission_types/project.rb
+++ b/app/graphql/types/permission_types/project.rb
@@ -16,7 +16,7 @@ module Types
:create_deployment, :push_to_delete_protected_branch,
:admin_wiki, :admin_project, :update_pages,
:admin_remote_mirror, :create_label, :update_wiki, :destroy_wiki,
- :create_pages, :destroy_pages
+ :create_pages, :destroy_pages, :read_pages_content
end
end
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 32fc8e5e9ce..4f91e3e4117 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -292,7 +292,8 @@ module ApplicationHelper
mergeRequests: merge_requests_project_autocomplete_sources_path(object),
labels: labels_project_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id]),
milestones: milestones_project_autocomplete_sources_path(object),
- commands: commands_project_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id])
+ commands: commands_project_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id]),
+ snippets: snippets_project_autocomplete_sources_path(object)
}
end
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 8b17e6ef75d..0016f89db5c 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -454,6 +454,7 @@ module ProjectsHelper
buildsAccessLevel: feature.builds_access_level,
wikiAccessLevel: feature.wiki_access_level,
snippetsAccessLevel: feature.snippets_access_level,
+ pagesAccessLevel: feature.pages_access_level,
containerRegistryEnabled: !!project.container_registry_enabled,
lfsEnabled: !!project.lfs_enabled
}
@@ -468,7 +469,10 @@ module ProjectsHelper
registryAvailable: Gitlab.config.registry.enabled,
registryHelpPath: help_page_path('user/project/container_registry'),
lfsAvailable: Gitlab.config.lfs.enabled,
- lfsHelpPath: help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
+ lfsHelpPath: help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs'),
+ pagesAvailable: Gitlab.config.pages.enabled,
+ pagesAccessControlEnabled: Gitlab.config.pages.access_control,
+ pagesHelpPath: help_page_path('user/project/pages/index.md')
}
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index d986e33280d..17024e8a0af 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -633,6 +633,18 @@ module Ci
end
end
+ def branch_updated?
+ strong_memoize(:branch_updated) do
+ push_details.branch_updated?
+ end
+ end
+
+ def modified_paths
+ strong_memoize(:modified_paths) do
+ push_details.modified_paths
+ end
+ end
+
def default_branch?
ref == project.default_branch
end
@@ -660,6 +672,22 @@ module Ci
Gitlab::DataBuilder::Pipeline.build(self)
end
+ def push_details
+ strong_memoize(:push_details) do
+ Gitlab::Git::Push.new(project, before_sha, sha, push_ref)
+ end
+ end
+
+ def push_ref
+ if branch?
+ Gitlab::Git::BRANCH_REF_PREFIX + ref.to_s
+ elsif tag?
+ Gitlab::Git::TAG_REF_PREFIX + ref.to_s
+ else
+ raise ArgumentError, 'Invalid pipeline type!'
+ end
+ end
+
def latest_builds_status
return 'failed' unless yaml_errors.blank?
diff --git a/app/models/concerns/diff_positionable_note.rb b/app/models/concerns/diff_positionable_note.rb
new file mode 100644
index 00000000000..b61bf29e6ad
--- /dev/null
+++ b/app/models/concerns/diff_positionable_note.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+module DiffPositionableNote
+ extend ActiveSupport::Concern
+
+ included do
+ before_validation :set_original_position, on: :create
+ before_validation :update_position, on: :create, if: :on_text?
+
+ serialize :original_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize
+ serialize :position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize
+ serialize :change_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize
+ end
+
+ %i(original_position position change_position).each do |meth|
+ define_method "#{meth}=" do |new_position|
+ if new_position.is_a?(String)
+ new_position = JSON.parse(new_position) rescue nil
+ end
+
+ if new_position.is_a?(Hash)
+ new_position = new_position.with_indifferent_access
+ new_position = Gitlab::Diff::Position.new(new_position)
+ end
+
+ return if new_position == read_attribute(meth)
+
+ super(new_position)
+ end
+ end
+
+ def on_text?
+ position&.position_type == "text"
+ end
+
+ def on_image?
+ position&.position_type == "image"
+ end
+
+ def supported?
+ for_commit? || self.noteable.has_complete_diff_refs?
+ end
+
+ def active?(diff_refs = nil)
+ return false unless supported?
+ return true if for_commit?
+
+ diff_refs ||= noteable.diff_refs
+
+ self.position.diff_refs == diff_refs
+ end
+
+ def set_original_position
+ return unless position
+
+ self.original_position = self.position.dup unless self.original_position&.complete?
+ end
+
+ def update_position
+ return unless supported?
+ return if for_commit?
+
+ return if active?
+ return unless position
+
+ tracer = Gitlab::Diff::PositionTracer.new(
+ project: self.project,
+ old_diff_refs: self.position.diff_refs,
+ new_diff_refs: self.noteable.diff_refs,
+ paths: self.position.paths
+ )
+
+ result = tracer.trace(self.position)
+ return unless result
+
+ if result[:outdated]
+ self.change_position = result[:position]
+ else
+ self.position = result[:position]
+ end
+ end
+end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 5f65fceb7af..2aa52bbaeea 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -76,6 +76,7 @@ module Issuable
scope :recent, -> { reorder(id: :desc) }
scope :of_projects, ->(ids) { where(project_id: ids) }
scope :of_milestones, ->(ids) { where(milestone_id: ids) }
+ scope :any_milestone, -> { where('milestone_id IS NOT NULL') }
scope :with_milestone, ->(title) { left_joins_milestones.where(milestones: { title: title }) }
scope :opened, -> { with_state(:opened) }
scope :only_opened, -> { with_state(:opened) }
diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb
index 047d353b4b5..95694377fe3 100644
--- a/app/models/diff_note.rb
+++ b/app/models/diff_note.rb
@@ -5,14 +5,11 @@
# A note of this type can be resolvable.
class DiffNote < Note
include NoteOnDiff
+ include DiffPositionableNote
include Gitlab::Utils::StrongMemoize
NOTEABLE_TYPES = %w(MergeRequest Commit).freeze
- serialize :original_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize
- serialize :position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize
- serialize :change_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize
-
validates :original_position, presence: true
validates :position, presence: true
validates :line_code, presence: true, line_code: true, if: :on_text?
@@ -21,8 +18,6 @@ class DiffNote < Note
validate :verify_supported
validate :diff_refs_match_commit, if: :for_commit?
- before_validation :set_original_position, on: :create
- before_validation :update_position, on: :create, if: :on_text?
before_validation :set_line_code, if: :on_text?
after_save :keep_around_commits
after_commit :create_diff_file, on: :create
@@ -31,31 +26,6 @@ class DiffNote < Note
DiffDiscussion
end
- %i(original_position position change_position).each do |meth|
- define_method "#{meth}=" do |new_position|
- if new_position.is_a?(String)
- new_position = JSON.parse(new_position) rescue nil
- end
-
- if new_position.is_a?(Hash)
- new_position = new_position.with_indifferent_access
- new_position = Gitlab::Diff::Position.new(new_position)
- end
-
- return if new_position == read_attribute(meth)
-
- super(new_position)
- end
- end
-
- def on_text?
- position.position_type == "text"
- end
-
- def on_image?
- position.position_type == "image"
- end
-
def create_diff_file
return unless should_create_diff_file?
@@ -87,15 +57,6 @@ class DiffNote < Note
self.diff_file.line_code(self.diff_line)
end
- def active?(diff_refs = nil)
- return false unless supported?
- return true if for_commit?
-
- diff_refs ||= noteable.diff_refs
-
- self.position.diff_refs == diff_refs
- end
-
def created_at_diff?(diff_refs)
return false unless supported?
return true if for_commit?
@@ -141,37 +102,10 @@ class DiffNote < Note
for_commit? || self.noteable.has_complete_diff_refs?
end
- def set_original_position
- self.original_position = self.position.dup unless self.original_position&.complete?
- end
-
def set_line_code
self.line_code = self.position.line_code(self.project.repository)
end
- def update_position
- return unless supported?
- return if for_commit?
-
- return if active?
-
- tracer = Gitlab::Diff::PositionTracer.new(
- project: self.project,
- old_diff_refs: self.position.diff_refs,
- new_diff_refs: self.noteable.diff_refs,
- paths: self.position.paths
- )
-
- result = tracer.trace(self.position)
- return unless result
-
- if result[:outdated]
- self.change_position = result[:position]
- else
- self.position = result[:position]
- end
- end
-
def verify_supported
return if supported?
diff --git a/app/models/project.rb b/app/models/project.rb
index 59f088156c7..dc2732cc6c2 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -55,8 +55,8 @@ class Project < ActiveRecord::Base
cache_markdown_field :description, pipeline: :description
delegate :feature_available?, :builds_enabled?, :wiki_enabled?,
- :merge_requests_enabled?, :issues_enabled?, to: :project_feature,
- allow_nil: true
+ :merge_requests_enabled?, :issues_enabled?, :pages_enabled?, :public_pages?,
+ to: :project_feature, allow_nil: true
delegate :base_dir, :disk_path, :ensure_storage_path_exists, to: :storage
@@ -356,7 +356,7 @@ class Project < ActiveRecord::Base
# "enabled" here means "not disabled". It includes private features!
scope :with_feature_enabled, ->(feature) {
access_level_attribute = ProjectFeature.access_level_attribute(feature)
- with_project_feature.where(project_features: { access_level_attribute => [nil, ProjectFeature::PRIVATE, ProjectFeature::ENABLED] })
+ with_project_feature.where(project_features: { access_level_attribute => [nil, ProjectFeature::PRIVATE, ProjectFeature::ENABLED, ProjectFeature::PUBLIC] })
}
# Picks a feature where the level is exactly that given.
@@ -418,15 +418,15 @@ class Project < ActiveRecord::Base
end
end
- # project features may be "disabled", "internal" or "enabled". If "internal",
+ # project features may be "disabled", "internal", "enabled" or "public". If "internal",
# they are only available to team members. This scope returns projects where
- # the feature is either enabled, or internal with permission for the user.
+ # the feature is either public, enabled, or internal with permission for the user.
#
# This method uses an optimised version of `with_feature_access_level` for
# logged in users to more efficiently get private projects with the given
# feature.
def self.with_feature_available_for_user(feature, user)
- visible = [nil, ProjectFeature::ENABLED]
+ visible = [nil, ProjectFeature::ENABLED, ProjectFeature::PUBLIC]
if user&.admin?
with_feature_enabled(feature)
diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb
index 4a0324e8b5c..39f2b8fe0de 100644
--- a/app/models/project_feature.rb
+++ b/app/models/project_feature.rb
@@ -13,14 +13,16 @@ class ProjectFeature < ActiveRecord::Base
# Disabled: not enabled for anyone
# Private: enabled only for team members
# Enabled: enabled for everyone able to access the project
+ # Public: enabled for everyone (only allowed for pages)
#
# Permission levels
DISABLED = 0
PRIVATE = 10
ENABLED = 20
+ PUBLIC = 30
- FEATURES = %i(issues merge_requests wiki snippets builds repository).freeze
+ FEATURES = %i(issues merge_requests wiki snippets builds repository pages).freeze
class << self
def access_level_attribute(feature)
@@ -46,6 +48,7 @@ class ProjectFeature < ActiveRecord::Base
validates :project, presence: true
validate :repository_children_level
+ validate :allowed_access_levels
default_value_for :builds_access_level, value: ENABLED, allows_nil: false
default_value_for :issues_access_level, value: ENABLED, allows_nil: false
@@ -55,6 +58,9 @@ class ProjectFeature < ActiveRecord::Base
default_value_for :repository_access_level, value: ENABLED, allows_nil: false
def feature_available?(feature, user)
+ # This feature might not be behind a feature flag at all, so default to true
+ return false unless ::Feature.enabled?(feature, user, default_enabled: true)
+
get_permission(user, access_level(feature))
end
@@ -78,6 +84,16 @@ class ProjectFeature < ActiveRecord::Base
issues_access_level > DISABLED
end
+ def pages_enabled?
+ pages_access_level > DISABLED
+ end
+
+ def public_pages?
+ return true unless Gitlab.config.pages.access_control
+
+ pages_access_level == PUBLIC || pages_access_level == ENABLED && project.public?
+ end
+
private
# Validates builds and merge requests access level
@@ -92,6 +108,17 @@ class ProjectFeature < ActiveRecord::Base
%i(merge_requests_access_level builds_access_level).each(&validator)
end
+ # Validates access level for other than pages cannot be PUBLIC
+ def allowed_access_levels
+ validator = lambda do |field|
+ level = public_send(field) || ProjectFeature::ENABLED # rubocop:disable GitlabSecurity/PublicSend
+ not_allowed = level > ProjectFeature::ENABLED
+ self.errors.add(field, "cannot have public visibility level") if not_allowed
+ end
+
+ (FEATURES - %i(pages)).each {|f| validator.call("#{f}_access_level")}
+ end
+
def get_permission(user, level)
case level
when DISABLED
@@ -100,6 +127,8 @@ class ProjectFeature < ActiveRecord::Base
user && (project.team.member?(user) || user.full_private_access?)
when ENABLED
true
+ when PUBLIC
+ true
else
true
end
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index f2c246cd969..a76a083bceb 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -110,6 +110,7 @@ class ProjectPolicy < BasePolicy
snippets
wiki
builds
+ pages
]
features.each do |f|
@@ -167,6 +168,7 @@ class ProjectPolicy < BasePolicy
enable :upload_file
enable :read_cycle_analytics
enable :award_emoji
+ enable :read_pages_content
end
# These abilities are not allowed to admins that are not members of the project,
@@ -286,6 +288,8 @@ class ProjectPolicy < BasePolicy
prevent(*create_read_update_admin_destroy(:merge_request))
end
+ rule { pages_disabled }.prevent :read_pages_content
+
rule { issues_disabled & merge_requests_disabled }.policy do
prevent(*create_read_update_admin_destroy(:label))
prevent(*create_read_update_admin_destroy(:milestone))
@@ -345,6 +349,7 @@ class ProjectPolicy < BasePolicy
enable :download_code
enable :download_wiki_code
enable :read_cycle_analytics
+ enable :read_pages_content
# NOTE: may be overridden by IssuePolicy
enable :read_issue
diff --git a/app/serializers/build_details_entity.rb b/app/serializers/build_details_entity.rb
index c85b1790e73..3d508a9a407 100644
--- a/app/serializers/build_details_entity.rb
+++ b/app/serializers/build_details_entity.rb
@@ -1,10 +1,6 @@
# frozen_string_literal: true
class BuildDetailsEntity < JobEntity
- include EnvironmentHelper
- include RequestAwareEntity
- include CiStatusHelper
-
expose :coverage, :erased_at, :duration
expose :tag_list, as: :tags
expose :has_trace?, as: :has_trace
@@ -15,10 +11,6 @@ class BuildDetailsEntity < JobEntity
expose :deployment_status, if: -> (*) { build.has_environment? } do
expose :deployment_status, as: :status
- expose :icon do |build|
- ci_label_for_status(build.status)
- end
-
expose :persisted_environment, as: :environment, with: EnvironmentEntity
end
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index aa5d8406d0f..28c3219b37b 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -57,10 +57,10 @@ module MergeRequests
# Returns all origin and fork merge requests from `@project` satisfying passed arguments.
# rubocop: disable CodeReuse/ActiveRecord
def merge_requests_for(source_branch, mr_states: [:opened])
- MergeRequest
+ @project.source_of_merge_requests
.with_state(mr_states)
- .where(source_branch: source_branch, source_project_id: @project.id)
- .preload(:source_project) # we don't need a #includes since we're just preloading for the #select
+ .where(source_branch: source_branch)
+ .preload(:source_project) # we don't need #includes since we're just preloading for the #select
.select(&:source_project)
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index bcdd752ddc4..d3e4f3def23 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -3,17 +3,16 @@
module MergeRequests
class RefreshService < MergeRequests::BaseService
def execute(oldrev, newrev, ref)
- return true unless Gitlab::Git.branch_ref?(ref)
+ @push = Gitlab::Git::Push.new(@project, oldrev, newrev, ref)
- do_execute(oldrev, newrev, ref)
+ return true unless @push.branch_push?
+
+ refresh_merge_requests!
end
private
- def do_execute(oldrev, newrev, ref)
- @oldrev, @newrev = oldrev, newrev
- @branch_name = Gitlab::Git.ref_name(ref)
-
+ def refresh_merge_requests!
Gitlab::GitalyClient.allow_n_plus_1_calls(&method(:find_new_commits))
# Be sure to close outstanding MRs before reloading them to avoid generating an
# empty diff during a manual merge
@@ -25,7 +24,7 @@ module MergeRequests
cache_merge_requests_closing_issues
# Leave a system note if a branch was deleted/added
- if branch_added? || branch_removed?
+ if @push.branch_added? || @push.branch_removed?
comment_mr_branch_presence_changed
end
@@ -54,8 +53,10 @@ module MergeRequests
# rubocop: disable CodeReuse/ActiveRecord
def post_merge_manually_merged
commit_ids = @commits.map(&:id)
- merge_requests = @project.merge_requests.preload(:latest_merge_request_diff).opened.where(target_branch: @branch_name).to_a
- merge_requests = merge_requests.select(&:diff_head_commit)
+ merge_requests = @project.merge_requests.opened
+ .preload(:latest_merge_request_diff)
+ .where(target_branch: @push.branch_name).to_a
+ .select(&:diff_head_commit)
merge_requests = merge_requests.select do |merge_request|
commit_ids.include?(merge_request.diff_head_sha) &&
@@ -70,24 +71,20 @@ module MergeRequests
end
# rubocop: enable CodeReuse/ActiveRecord
- def force_push?
- Gitlab::Checks::ForcePush.force_push?(@project, @oldrev, @newrev)
- end
-
# Refresh merge request diff if we push to source or target branch of merge request
# Note: we should update merge requests from forks too
# rubocop: disable CodeReuse/ActiveRecord
def reload_merge_requests
merge_requests = @project.merge_requests.opened
- .by_source_or_target_branch(@branch_name).to_a
+ .by_source_or_target_branch(@push.branch_name).to_a
# Fork merge requests
merge_requests += MergeRequest.opened
- .where(source_branch: @branch_name, source_project: @project)
+ .where(source_branch: @push.branch_name, source_project: @project)
.where.not(target_project: @project).to_a
filter_merge_requests(merge_requests).each do |merge_request|
- if merge_request.source_branch == @branch_name || force_push?
+ if merge_request.source_branch == @push.branch_name || @push.force_push?
merge_request.reload_diff(current_user)
else
mr_commit_ids = merge_request.commit_shas
@@ -117,7 +114,7 @@ module MergeRequests
end
def find_new_commits
- if branch_added?
+ if @push.branch_added?
@commits = []
merge_request = merge_requests_for_source_branch.first
@@ -126,28 +123,28 @@ module MergeRequests
begin
# Since any number of commits could have been made to the restored branch,
# find the common root to see what has been added.
- common_ref = @project.repository.merge_base(merge_request.diff_head_sha, @newrev)
+ common_ref = @project.repository.merge_base(merge_request.diff_head_sha, @push.newrev)
# If the a commit no longer exists in this repo, gitlab_git throws
# a Rugged::OdbError. This is fixed in https://gitlab.com/gitlab-org/gitlab_git/merge_requests/52
- @commits = @project.repository.commits_between(common_ref, @newrev) if common_ref
+ @commits = @project.repository.commits_between(common_ref, @push.newrev) if common_ref
rescue
end
- elsif branch_removed?
+ elsif @push.branch_removed?
# No commits for a deleted branch.
@commits = []
else
- @commits = @project.repository.commits_between(@oldrev, @newrev)
+ @commits = @project.repository.commits_between(@push.oldrev, @push.newrev)
end
end
# Add comment about branches being deleted or added to merge requests
def comment_mr_branch_presence_changed
- presence = branch_added? ? :add : :delete
+ presence = @push.branch_added? ? :add : :delete
merge_requests_for_source_branch.each do |merge_request|
SystemNoteService.change_branch_presence(
merge_request, merge_request.project, @current_user,
- :source, @branch_name, presence)
+ :source, @push.branch_name, presence)
end
end
@@ -164,7 +161,7 @@ module MergeRequests
SystemNoteService.add_commits(merge_request, merge_request.project,
@current_user, new_commits,
- existing_commits, @oldrev)
+ existing_commits, @push.oldrev)
notification_service.push_to_merge_request(merge_request, @current_user, new_commits: new_commits, existing_commits: existing_commits)
end
@@ -195,7 +192,7 @@ module MergeRequests
# Call merge request webhook with update branches
def execute_mr_web_hooks
merge_requests_for_source_branch.each do |merge_request|
- execute_hooks(merge_request, 'update', old_rev: @oldrev)
+ execute_hooks(merge_request, 'update', old_rev: @push.oldrev)
end
end
@@ -203,7 +200,7 @@ module MergeRequests
# `MergeRequestsClosingIssues` model (as a performance optimization).
# rubocop: disable CodeReuse/ActiveRecord
def cache_merge_requests_closing_issues
- @project.merge_requests.where(source_branch: @branch_name).each do |merge_request|
+ @project.merge_requests.where(source_branch: @push.branch_name).each do |merge_request|
merge_request.cache_merge_request_closes_issues!(@current_user)
end
end
@@ -215,15 +212,7 @@ module MergeRequests
def merge_requests_for_source_branch(reload: false)
@source_merge_requests = nil if reload
- @source_merge_requests ||= merge_requests_for(@branch_name)
- end
-
- def branch_added?
- Gitlab::Git.blank_ref?(@oldrev)
- end
-
- def branch_removed?
- Gitlab::Git.blank_ref?(@newrev)
+ @source_merge_requests ||= merge_requests_for(@push.branch_name)
end
end
end
diff --git a/app/services/projects/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb
index 7b747171d9c..61f6402a810 100644
--- a/app/services/projects/autocomplete_service.rb
+++ b/app/services/projects/autocomplete_service.rb
@@ -29,6 +29,10 @@ module Projects
QuickActions::InterpretService.new(project, current_user).available_commands(noteable)
end
+ def snippets
+ SnippetsFinder.new(current_user, project: project).execute.select([:id, :title])
+ end
+
def labels_as_hash(target)
super(target, project_id: project.id, include_ancestor_groups: true)
end
diff --git a/app/services/projects/update_pages_configuration_service.rb b/app/services/projects/update_pages_configuration_service.rb
index efbd4c7b323..abf40b3ad7a 100644
--- a/app/services/projects/update_pages_configuration_service.rb
+++ b/app/services/projects/update_pages_configuration_service.rb
@@ -21,7 +21,9 @@ module Projects
def pages_config
{
domains: pages_domains_config,
- https_only: project.pages_https_only?
+ https_only: project.pages_https_only?,
+ id: project.project_id,
+ access_control: !project.public_pages?
}
end
@@ -31,7 +33,9 @@ module Projects
domain: domain.domain,
certificate: domain.certificate,
key: domain.key,
- https_only: project.pages_https_only? && domain.https?
+ https_only: project.pages_https_only? && domain.https?,
+ id: project.project_id,
+ access_control: !project.public_pages?
}
end
end
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index d6d9bacf232..f25a4e30938 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -72,7 +72,11 @@ module Projects
system_hook_service.execute_hooks_for(project, :update)
end
- update_pages_config if changing_pages_https_only?
+ update_pages_config if changing_pages_related_config?
+ end
+
+ def changing_pages_related_config?
+ changing_pages_https_only? || changing_pages_access_level?
end
def update_failed!
@@ -102,6 +106,10 @@ module Projects
params.dig(:project_feature_attributes, :wiki_access_level).to_i > ProjectFeature::DISABLED
end
+ def changing_pages_access_level?
+ params.dig(:project_feature_attributes, :pages_access_level)
+ end
+
def ensure_wiki_exists
ProjectWiki.new(project, project.owner).wiki
rescue ProjectWiki::CouldNotCreateWikiError
diff --git a/changelogs/unreleased/22188-drop-down-filter-for-project-snippets.yml b/changelogs/unreleased/22188-drop-down-filter-for-project-snippets.yml
new file mode 100644
index 00000000000..e24c55e3bad
--- /dev/null
+++ b/changelogs/unreleased/22188-drop-down-filter-for-project-snippets.yml
@@ -0,0 +1,5 @@
+---
+title: Add autocomplete drop down filter for project snippets
+merge_request: 21458
+author: Fabian Schneider
+type: added
diff --git a/changelogs/unreleased/50246-can-t-sort-group-issues-by-popularity-when-searching.yml b/changelogs/unreleased/50246-can-t-sort-group-issues-by-popularity-when-searching.yml
new file mode 100644
index 00000000000..cc7a79d25e5
--- /dev/null
+++ b/changelogs/unreleased/50246-can-t-sort-group-issues-by-popularity-when-searching.yml
@@ -0,0 +1,6 @@
+---
+title: Fix sorting by priority or popularity on group issues page, when also searching
+ issue content
+merge_request: 21521
+author:
+type: fixed
diff --git a/changelogs/unreleased/51748-filter-any-milestone-via-api.yml b/changelogs/unreleased/51748-filter-any-milestone-via-api.yml
new file mode 100644
index 00000000000..30304e5a4ac
--- /dev/null
+++ b/changelogs/unreleased/51748-filter-any-milestone-via-api.yml
@@ -0,0 +1,5 @@
+---
+title: Allows to filter issues by Any milestone in the API
+merge_request: 22080
+author: Jacopo Beschi @jacopo-beschi
+type: added
diff --git a/changelogs/unreleased/auth.yml b/changelogs/unreleased/auth.yml
new file mode 100644
index 00000000000..cd4bbf0059e
--- /dev/null
+++ b/changelogs/unreleased/auth.yml
@@ -0,0 +1,5 @@
+---
+title: Add access control to GitLab pages and make it possible to enable/disable it in project settings
+merge_request: 18589
+author: Tuomo Ala-Vannesluoma
+type: added
diff --git a/changelogs/unreleased/feature-gb-pipeline-only-except-with-modified-paths.yml b/changelogs/unreleased/feature-gb-pipeline-only-except-with-modified-paths.yml
new file mode 100644
index 00000000000..62676cdad62
--- /dev/null
+++ b/changelogs/unreleased/feature-gb-pipeline-only-except-with-modified-paths.yml
@@ -0,0 +1,5 @@
+---
+title: Add support for pipeline only/except policy for modified paths
+merge_request: 21981
+author:
+type: added
diff --git a/changelogs/unreleased/features-unauth-access-ssh-keys.yml b/changelogs/unreleased/features-unauth-access-ssh-keys.yml
new file mode 100644
index 00000000000..bae2bcfaabd
--- /dev/null
+++ b/changelogs/unreleased/features-unauth-access-ssh-keys.yml
@@ -0,0 +1,5 @@
+---
+title: Enable unauthenticated access to public SSH keys via the API
+merge_request: 20118
+author: Ronald Claveau
+type: changed
diff --git a/changelogs/unreleased/mao-48221-issues_show_sql_count.yml b/changelogs/unreleased/mao-48221-issues_show_sql_count.yml
new file mode 100644
index 00000000000..d634d15946e
--- /dev/null
+++ b/changelogs/unreleased/mao-48221-issues_show_sql_count.yml
@@ -0,0 +1,5 @@
+---
+title: Banzai label ref finder - minimize SQL calls by sharing context more aggresively
+merge_request: 22070
+author:
+type: performance
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 67337f4b82f..749cdd0f869 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -210,6 +210,7 @@ production: &base
## GitLab Pages
pages:
enabled: false
+ access_control: false
# The location where pages are stored (default: shared/pages).
# path: shared/pages
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 0caa4962128..bd02b85c7ce 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -200,6 +200,7 @@ Settings.registry['path'] = Settings.absolute(Settings.registry['path
#
Settings['pages'] ||= Settingslogic.new({})
Settings.pages['enabled'] = false if Settings.pages['enabled'].nil?
+Settings.pages['access_control'] = false if Settings.pages['access_control'].nil?
Settings.pages['path'] = Settings.absolute(Settings.pages['path'] || File.join(Settings.shared['path'], "pages"))
Settings.pages['https'] = false if Settings.pages['https'].nil?
Settings.pages['host'] ||= "example.com"
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 356a509da75..9cbd5b644f6 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -32,6 +32,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get 'labels'
get 'milestones'
get 'commands'
+ get 'snippets'
end
end
diff --git a/db/migrate/20180423204600_add_pages_access_level_to_project_feature.rb b/db/migrate/20180423204600_add_pages_access_level_to_project_feature.rb
new file mode 100644
index 00000000000..1d2f8cf9c76
--- /dev/null
+++ b/db/migrate/20180423204600_add_pages_access_level_to_project_feature.rb
@@ -0,0 +1,16 @@
+class AddPagesAccessLevelToProjectFeature < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+ disable_ddl_transaction!
+
+ DOWNTIME = false
+
+ def up
+ add_column_with_default(:project_features, :pages_access_level, :integer, default: ProjectFeature::PUBLIC, allow_null: false)
+
+ change_column_default(:project_features, :pages_access_level, ProjectFeature::ENABLED)
+ end
+
+ def down
+ remove_column :project_features, :pages_access_level
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 82f50e0ecd2..773bfb96b93 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -1580,6 +1580,7 @@ ActiveRecord::Schema.define(version: 20180924201039) do
t.datetime "created_at"
t.datetime "updated_at"
t.integer "repository_access_level", default: 20, null: false
+ t.integer "pages_access_level", default: 20, null: false
end
add_index "project_features", ["project_id"], name: "index_project_features_on_project_id", unique: true, using: :btree
diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md
index 3af0a5759a7..2952a98626a 100644
--- a/doc/administration/pages/index.md
+++ b/doc/administration/pages/index.md
@@ -92,9 +92,8 @@ where `example.io` is the domain under which GitLab Pages will be served
and `192.0.2.1` is the IPv4 address of your GitLab instance and `2001::1` is the
IPv6 address. If you don't have IPv6, you can omit the AAAA record.
-> **Note:**
-You should not use the GitLab domain to serve user pages. For more information
-see the [security section](#security).
+NOTE: **Note:**
+You should not use the GitLab domain to serve user pages. For more information see the [security section](#security).
[wiki-wildcard-dns]: https://en.wikipedia.org/wiki/Wildcard_DNS_record
@@ -107,12 +106,13 @@ since that is needed in all configurations.
### Wildcard domains
-> **Requirements:**
-> - [Wildcard DNS setup](#dns-configuration)
->
-> ---
->
-> URL scheme: `http://page.example.io`
+**Requirements:**
+
+- [Wildcard DNS setup](#dns-configuration)
+
+---
+
+URL scheme: `http://page.example.io`
This is the minimum setup that you can use Pages with. It is the base for all
other setups as described below. Nginx will proxy all requests to the daemon.
@@ -126,18 +126,18 @@ The Pages daemon doesn't listen to the outside world.
1. [Reconfigure GitLab][reconfigure]
-
Watch the [video tutorial][video-admin] for this configuration.
### Wildcard domains with TLS support
-> **Requirements:**
-> - [Wildcard DNS setup](#dns-configuration)
-> - Wildcard TLS certificate
->
-> ---
->
-> URL scheme: `https://page.example.io`
+**Requirements:**
+
+- [Wildcard DNS setup](#dns-configuration)
+- Wildcard TLS certificate
+
+---
+
+URL scheme: `https://page.example.io`
Nginx will proxy all requests to the daemon. Pages daemon doesn't listen to the
outside world.
@@ -168,13 +168,14 @@ you have IPv6 as well as IPv4 addresses, you can use them both.
### Custom domains
-> **Requirements:**
-> - [Wildcard DNS setup](#dns-configuration)
-> - Secondary IP
->
-> ---
->
-> URL scheme: `http://page.example.io` and `http://domain.com`
+**Requirements:**
+
+- [Wildcard DNS setup](#dns-configuration)
+- Secondary IP
+
+---
+
+URL scheme: `http://page.example.io` and `http://domain.com`
In that case, the Pages daemon is running, Nginx still proxies requests to
the daemon but the daemon is also able to receive requests from the outside
@@ -197,14 +198,15 @@ world. Custom domains are supported, but no TLS.
### Custom domains with TLS support
-> **Requirements:**
-> - [Wildcard DNS setup](#dns-configuration)
-> - Wildcard TLS certificate
-> - Secondary IP
->
-> ---
->
-> URL scheme: `https://page.example.io` and `https://domain.com`
+**Requirements:**
+
+- [Wildcard DNS setup](#dns-configuration)
+- Wildcard TLS certificate
+- Secondary IP
+
+---
+
+URL scheme: `https://page.example.io` and `https://domain.com`
In that case, the Pages daemon is running, Nginx still proxies requests to
the daemon but the daemon is also able to receive requests from the outside
@@ -320,12 +322,12 @@ latest previous version.
---
-**GitLab 8.17 ([documentation][8-17-docs])**
+**GitLab 8.17 ([documentation](https://gitlab.com/gitlab-org/gitlab-ce/blob/8-17-stable/doc/administration/pages/index.md))**
- GitLab Pages were ported to Community Edition in GitLab 8.17.
- Documentation was refactored to be more modular and easy to follow.
-**GitLab 8.5 ([documentation][8-5-docs])**
+**GitLab 8.5 ([documentation](https://gitlab.com/gitlab-org/gitlab-ee/blob/8-5-stable-ee/doc/pages/administration.md))**
- In GitLab 8.5 we introduced the [gitlab-pages][] daemon which is now the
recommended way to set up GitLab Pages.
@@ -334,13 +336,10 @@ latest previous version.
- Custom CNAME and TLS certificates support.
- Documentation was moved to one place.
-**GitLab 8.3 ([documentation][8-3-docs])**
+**GitLab 8.3 ([documentation](https://gitlab.com/gitlab-org/gitlab-ee/blob/8-3-stable-ee/doc/pages/administration.md))**
- GitLab Pages feature was introduced.
-[8-3-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-3-stable-ee/doc/pages/administration.md
-[8-5-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-5-stable-ee/doc/pages/administration.md
-[8-17-docs]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-17-stable-ce/doc/administration/pages/index.md
[backup]: ../../raketasks/backup_restore.md
[ce-14605]: https://gitlab.com/gitlab-org/gitlab-ce/issues/14605
[ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80
diff --git a/doc/api/issues.md b/doc/api/issues.md
index f4c0f4ea65b..cc1d6834a20 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -37,7 +37,7 @@ GET /issues?my_reaction_emoji=star
| ------------------- | ---------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `state` | string | no | Return all issues or just those that are `opened` or `closed` |
| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels |
-| `milestone` | string | no | The milestone title. `No+Milestone` lists all issues with no milestone |
+| `milestone` | string | no | The milestone title. `No+Milestone` lists all issues with no milestone. `Any+Milestone` lists all issues that have an assigned milestone |
| `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`. Defaults to `created_by_me`<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.<br> _([Introduced][ce-13004] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ |
| `author_id` | integer | no | Return issues created by the given user `id`. Combine with `scope=all` or `scope=assigned_to_me`. _([Introduced][ce-13004] in GitLab 9.5)_ |
| `assignee_id` | integer | no | Return issues assigned to the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ |
diff --git a/doc/api/users.md b/doc/api/users.md
index 3b41e0f7ec6..07f03f9c827 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -558,7 +558,7 @@ Parameters:
## List SSH keys for user
-Get a list of a specified user's SSH keys. Available only for admin
+Get a list of a specified user's SSH keys.
```
GET /users/:id/keys
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index e38628b288b..15dde36cca8 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -387,6 +387,8 @@ except master.
> `refs` and `kubernetes` policies introduced in GitLab 10.0
>
> `variables` policy introduced in 10.7
+>
+> `changes` policy [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/19232) in 11.4
CAUTION: **Warning:**
This an _alpha_ feature, and it it subject to change at any time without
@@ -398,10 +400,15 @@ policy configuration.
GitLab now supports both, simple and complex strategies, so it is possible to
use an array and a hash configuration scheme.
-Three keys are now available: `refs`, `kubernetes` and `variables`.
+Four keys are now available: `refs`, `kubernetes` and `variables` and `changes`.
+
+### `refs` and `kubernetes`
+
Refs strategy equals to simplified only/except configuration, whereas
kubernetes strategy accepts only `active` keyword.
+### `variables`
+
`variables` keyword is used to define variables expressions. In other words
you can use predefined variables / project / group or
environment-scoped variables to define an expression GitLab is going to
@@ -445,6 +452,46 @@ end-to-end:
Learn more about variables expressions on [a separate page][variables-expressions].
+### `changes`
+
+Using `changes` keyword with `only` or `except` makes it possible to define if
+a job should be created based on files modified by a git push event.
+
+For example:
+
+```yaml
+docker build:
+ script: docker build -t my-image:$CI_COMMIT_REF_SLUG .
+ only:
+ changes:
+ - Dockerfile
+ - docker/scripts/*
+```
+
+In the scenario above, if you are pushing multiple commits to GitLab to an
+existing branch, GitLab creates and triggers `docker build` job, provided that
+one of the commits contains changes to either:
+
+- The `Dockerfile` file.
+- Any of the files inside `docker/scripts/` directory.
+
+CAUTION: **Warning:**
+There are some caveats when using this feature with new branches and tags. See
+the section below.
+
+#### Using `changes` with new branches and tags
+
+If you are pushing a **new** branch or a **new** tag to GitLab, the policy
+always evaluates to true and GitLab will create a job. This feature is not
+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.
+
## `tags`
`tags` is used to select specific Runners from the list of all Runners that are
diff --git a/doc/development/contributing/issue_workflow.md b/doc/development/contributing/issue_workflow.md
index fed29d37b26..2f06677bfec 100644
--- a/doc/development/contributing/issue_workflow.md
+++ b/doc/development/contributing/issue_workflow.md
@@ -60,7 +60,7 @@ people.
The current team labels are:
-- ~Configuration
+- ~Configure
- ~"CI/CD"
- ~Create
- ~Distribution
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index 8369cff2386..4a1ca41ab97 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -110,6 +110,7 @@ which visibility level you select on project settings.
- Disabled: disabled for everyone
- Only team members: only team members will see even if your project is public or internal
- Everyone with access: everyone can see depending on your project visibility level
+- Everyone: enabled for everyone (only available for GitLab Pages)
### Protected branches
@@ -242,6 +243,7 @@ which visibility level you select on project settings.
- Disabled: disabled for everyone
- Only team members: only team members will see even if your project is public or internal
- Everyone with access: everyone can see depending on your project visibility level
+- Everyone: enabled for everyone (only available for GitLab Pages)
## GitLab CI/CD permissions
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 00bad49ebdc..ae2d327e45b 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -287,6 +287,12 @@ module API
present_projects forks
end
+ desc 'Check pages access of this project'
+ get ':id/pages_access' do
+ authorize! :read_pages_content, user_project unless user_project.public_pages?
+ status 200
+ end
+
desc 'Update an existing project' do
success Entities::Project
end
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 11a7f4ef64d..501c5cf1df3 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -256,7 +256,7 @@ module API
end
# rubocop: enable CodeReuse/ActiveRecord
- desc 'Get the SSH keys of a specified user. Available only for admins.' do
+ desc 'Get the SSH keys of a specified user.' do
success Entities::SSHKey
end
params do
@@ -265,10 +265,8 @@ module API
end
# rubocop: disable CodeReuse/ActiveRecord
get ':id/keys' do
- authenticated_as_admin!
-
user = User.find_by(id: params[:id])
- not_found!('User') unless user
+ not_found!('User') unless user && can?(current_user, :read_user, user)
present paginate(user.keys), with: Entities::SSHKey
end
diff --git a/lib/banzai/cross_project_reference.rb b/lib/banzai/cross_project_reference.rb
index 3f1e95d4cc0..43f913a8859 100644
--- a/lib/banzai/cross_project_reference.rb
+++ b/lib/banzai/cross_project_reference.rb
@@ -13,6 +13,7 @@ module Banzai
# Returns a Project, or nil if the reference can't be found
def parent_from_ref(ref)
return context[:project] || context[:group] unless ref
+ return context[:project] if context[:project]&.full_path == ref
Project.find_by_full_path(ref)
end
diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb
index b92e9e55bb9..04ec38209c7 100644
--- a/lib/banzai/filter/label_reference_filter.rb
+++ b/lib/banzai/filter/label_reference_filter.rb
@@ -48,7 +48,7 @@ module Banzai
include_ancestor_groups: true,
only_group_labels: true }
else
- { project_id: parent.id,
+ { project: parent,
include_ancestor_groups: true }
end
diff --git a/lib/gitlab/ci/build/policy/changes.rb b/lib/gitlab/ci/build/policy/changes.rb
new file mode 100644
index 00000000000..7bf51519752
--- /dev/null
+++ b/lib/gitlab/ci/build/policy/changes.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Build
+ module Policy
+ class Changes < Policy::Specification
+ def initialize(globs)
+ @globs = Array(globs)
+ end
+
+ def satisfied_by?(pipeline, seed)
+ return true unless pipeline.branch_updated?
+
+ pipeline.modified_paths.any? do |path|
+ @globs.any? do |glob|
+ File.fnmatch?(glob, path, File::FNM_PATHNAME | File::FNM_DOTMATCH)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/entry/policy.rb b/lib/gitlab/ci/config/entry/policy.rb
index 09e8e52b60f..c92562f8c85 100644
--- a/lib/gitlab/ci/config/entry/policy.rb
+++ b/lib/gitlab/ci/config/entry/policy.rb
@@ -25,17 +25,19 @@ module Gitlab
include Entry::Validatable
include Entry::Attributable
- attributes :refs, :kubernetes, :variables
+ ALLOWED_KEYS = %i[refs kubernetes variables changes].freeze
+ attributes :refs, :kubernetes, :variables, :changes
validations do
validates :config, presence: true
- validates :config, allowed_keys: %i[refs kubernetes variables]
+ validates :config, allowed_keys: ALLOWED_KEYS
validate :variables_expressions_syntax
with_options allow_nil: true do
validates :refs, array_of_strings_or_regexps: true
validates :kubernetes, allowed_values: %w[active]
validates :variables, array_of_strings: true
+ validates :changes, array_of_strings: true
end
def variables_expressions_syntax
diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
index a96595b33a5..ed4ec7e6385 100644
--- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
@@ -689,9 +689,6 @@ rollout 100%:
helm version --client
tiller -version
- helm init --client-only
- helm plugin install https://github.com/adamreese/helm-local
-
curl -L -o /usr/bin/kubectl "https://storage.googleapis.com/kubernetes-release/release/v${KUBERNETES_VERSION}/bin/linux/amd64/kubectl"
chmod +x /usr/bin/kubectl
kubectl version --client
@@ -800,9 +797,9 @@ rollout 100%:
function initialize_tiller() {
echo "Checking Tiller..."
- helm local start
- helm local status
export HELM_HOST=":44134"
+ tiller -listen ${HELM_HOST} -alsologtostderr > /dev/null 2>&1 &
+ echo "Tiller is listening on ${HELM_HOST}"
if ! helm version --debug; then
echo "Failed to init Tiller."
diff --git a/lib/gitlab/git/diff_stats_collection.rb b/lib/gitlab/git/diff_stats_collection.rb
index d4033f56387..998c41497a2 100644
--- a/lib/gitlab/git/diff_stats_collection.rb
+++ b/lib/gitlab/git/diff_stats_collection.rb
@@ -18,6 +18,10 @@ module Gitlab
indexed_by_path[path]
end
+ def paths
+ @collection.map(&:path)
+ end
+
private
def indexed_by_path
diff --git a/lib/gitlab/git/push.rb b/lib/gitlab/git/push.rb
new file mode 100644
index 00000000000..7c1309721fd
--- /dev/null
+++ b/lib/gitlab/git/push.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Git
+ class Push
+ include Gitlab::Utils::StrongMemoize
+
+ attr_reader :oldrev, :newrev
+
+ def initialize(project, oldrev, newrev, ref)
+ @project = project
+ @oldrev = oldrev.presence || Gitlab::Git::BLANK_SHA
+ @newrev = newrev.presence || Gitlab::Git::BLANK_SHA
+ @ref = ref
+ end
+
+ def branch_name
+ strong_memoize(:branch_name) do
+ Gitlab::Git.branch_name(@ref)
+ end
+ end
+
+ def branch_added?
+ Gitlab::Git.blank_ref?(@oldrev)
+ end
+
+ def branch_removed?
+ Gitlab::Git.blank_ref?(@newrev)
+ end
+
+ def branch_updated?
+ branch_push? && !branch_added? && !branch_removed?
+ end
+
+ def force_push?
+ Gitlab::Checks::ForcePush.force_push?(@project, @oldrev, @newrev)
+ end
+
+ def branch_push?
+ strong_memoize(:branch_push) do
+ Gitlab::Git.branch_ref?(@ref)
+ end
+ end
+
+ def modified_paths
+ unless branch_updated?
+ raise ArgumentError, 'Unable to calculate modified paths!'
+ end
+
+ strong_memoize(:modified_paths) do
+ @project.repository.diff_stats(@oldrev, @newrev).paths
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index 500aabcbbb8..4ec87f6a3e7 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -220,7 +220,7 @@ module Gitlab
result
end
- SERVER_FEATURE_FLAGS = %w[gogit_findcommit git_v2].freeze
+ SERVER_FEATURE_FLAGS = %w[gogit_findcommit].freeze
def self.server_feature_flags
SERVER_FEATURE_FLAGS.map do |f|
diff --git a/qa/qa.rb b/qa/qa.rb
index 227d4424b09..8bcaf000bb2 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -257,6 +257,9 @@ module QA
autoload :Dropzone, 'qa/page/component/dropzone'
autoload :GroupsFilter, 'qa/page/component/groups_filter'
autoload :Select2, 'qa/page/component/select2'
+ module Issuable
+ autoload :Common, 'qa/page/component/issuable/common'
+ end
end
end
diff --git a/qa/qa/page/component/issuable/common.rb b/qa/qa/page/component/issuable/common.rb
new file mode 100644
index 00000000000..cfd8ac1e7c8
--- /dev/null
+++ b/qa/qa/page/component/issuable/common.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Component
+ module Issuable
+ module Common
+ def self.included(base)
+ base.view 'app/assets/javascripts/issue_show/components/title.vue' do
+ element :edit_button
+ end
+
+ base.view 'app/assets/javascripts/issue_show/components/fields/title.vue' do
+ element :title_input
+ end
+
+ base.view 'app/assets/javascripts/issue_show/components/fields/description.vue' do
+ element :description_textarea
+ end
+
+ base.view 'app/assets/javascripts/issue_show/components/edit_actions.vue' do
+ element :save_button
+ element :delete_button
+ end
+
+ base.view 'app/assets/javascripts/issue_show/components/edit_actions.vue' do
+ element :save_button
+ element :delete_button
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb
index 587a02163b9..9a738f56202 100644
--- a/qa/qa/page/project/issue/show.rb
+++ b/qa/qa/page/project/issue/show.rb
@@ -5,6 +5,8 @@ module QA
module Project
module Issue
class Show < Page::Base
+ include Page::Component::Issuable::Common
+
view 'app/views/projects/issues/show.html.haml' do
element :issue_details, '.issue-details'
element :title, '.title'
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index 65d6cd1a295..a099cdafa58 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -202,8 +202,8 @@ describe GroupsController do
end
describe 'GET #issues' do
- let(:issue_1) { create(:issue, project: project) }
- let(:issue_2) { create(:issue, project: project) }
+ let(:issue_1) { create(:issue, project: project, title: 'foo') }
+ let(:issue_2) { create(:issue, project: project, title: 'bar') }
before do
create_list(:award_emoji, 3, awardable: issue_2)
@@ -224,6 +224,31 @@ describe GroupsController do
expect(assigns(:issues)).to eq [issue_2, issue_1]
end
end
+
+ context 'searching' do
+ # Remove as part of https://gitlab.com/gitlab-org/gitlab-ce/issues/52271
+ before do
+ stub_feature_flags(use_cte_for_group_issues_search: false)
+ end
+
+ it 'works with popularity sort' do
+ get :issues, id: group.to_param, search: 'foo', sort: 'popularity'
+
+ expect(assigns(:issues)).to eq([issue_1])
+ end
+
+ it 'works with priority sort' do
+ get :issues, id: group.to_param, search: 'foo', sort: 'priority'
+
+ expect(assigns(:issues)).to eq([issue_1])
+ end
+
+ it 'works with label priority sort' do
+ get :issues, id: group.to_param, search: 'foo', sort: 'label_priority'
+
+ expect(assigns(:issues)).to eq([issue_1])
+ end
+ end
end
describe 'GET #merge_requests' do
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 5b347b1109a..44419ed6a2a 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -637,6 +637,18 @@ describe Projects::IssuesController do
project_id: project,
id: id
end
+
+ it 'avoids (most) N+1s loading labels' do
+ label = create(:label, project: project).to_reference
+ labels = create_list(:label, 10, project: project).map(&:to_reference)
+ issue = create(:issue, project: project, description: 'Test issue')
+
+ control_count = ActiveRecord::QueryRecorder.new { issue.update(description: [issue.description, label].join(' ')) }.count
+
+ # Follow-up to get rid of this `2 * label.count` requirement: https://gitlab.com/gitlab-org/gitlab-ce/issues/52230
+ expect { issue.update(description: [issue.description, labels].join(' ')) }
+ .not_to exceed_query_limit(control_count + 2 * labels.count)
+ end
end
describe 'GET #realtime_changes' do
diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index 5b09e4a082c..383d6c1a2a9 100644
--- a/spec/controllers/projects/jobs_controller_spec.rb
+++ b/spec/controllers/projects/jobs_controller_spec.rb
@@ -225,7 +225,6 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to match_schema('job/job_details')
expect(json_response['deployment_status']["status"]).to eq 'creating'
- expect(json_response['deployment_status']["icon"]).to eq 'passed'
expect(json_response['deployment_status']["environment"]).not_to be_nil
end
end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 80801eb1082..e4823a5adf1 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -1,6 +1,8 @@
require_relative '../support/helpers/test_env'
FactoryBot.define do
+ PAGES_ACCESS_LEVEL_SCHEMA_VERSION = 20180423204600
+
# Project without repository
#
# Project does not have bare repository.
@@ -23,6 +25,7 @@ FactoryBot.define do
issues_access_level ProjectFeature::ENABLED
merge_requests_access_level ProjectFeature::ENABLED
repository_access_level ProjectFeature::ENABLED
+ pages_access_level ProjectFeature::ENABLED
# we can't assign the delegated `#ci_cd_settings` attributes directly, as the
# `#ci_cd_settings` relation needs to be created first
@@ -34,13 +37,20 @@ FactoryBot.define do
builds_access_level = [evaluator.builds_access_level, evaluator.repository_access_level].min
merge_requests_access_level = [evaluator.merge_requests_access_level, evaluator.repository_access_level].min
- project.project_feature.update(
+ hash = {
wiki_access_level: evaluator.wiki_access_level,
builds_access_level: builds_access_level,
snippets_access_level: evaluator.snippets_access_level,
issues_access_level: evaluator.issues_access_level,
merge_requests_access_level: merge_requests_access_level,
- repository_access_level: evaluator.repository_access_level)
+ repository_access_level: evaluator.repository_access_level
+ }
+
+ if ActiveRecord::Migrator.current_version >= PAGES_ACCESS_LEVEL_SCHEMA_VERSION
+ hash.store("pages_access_level", evaluator.pages_access_level)
+ end
+
+ project.project_feature.update(hash)
# Normally the class Projects::CreateService is used for creating
# projects, and this class takes care of making sure the owner and current
@@ -244,6 +254,10 @@ FactoryBot.define do
trait(:repository_enabled) { repository_access_level ProjectFeature::ENABLED }
trait(:repository_disabled) { repository_access_level ProjectFeature::DISABLED }
trait(:repository_private) { repository_access_level ProjectFeature::PRIVATE }
+ trait(:pages_public) { pages_access_level ProjectFeature::PUBLIC }
+ trait(:pages_enabled) { pages_access_level ProjectFeature::ENABLED }
+ trait(:pages_disabled) { pages_access_level ProjectFeature::DISABLED }
+ trait(:pages_private) { pages_access_level ProjectFeature::PRIVATE }
trait :auto_devops do
association :auto_devops, factory: :project_auto_devops
diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb
index 98e37d8011a..08bf9bc7243 100644
--- a/spec/features/issues/gfm_autocomplete_spec.rb
+++ b/spec/features/issues/gfm_autocomplete_spec.rb
@@ -5,6 +5,7 @@ describe 'GFM autocomplete', :js do
let(:project) { create(:project) }
let(:label) { create(:label, project: project, title: 'special+') }
let(:issue) { create(:issue, project: project) }
+ let!(:project_snippet) { create(:project_snippet, project: project, title: 'code snippet') }
before do
project.add_maintainer(user)
@@ -301,6 +302,16 @@ describe 'GFM autocomplete', :js do
end
end
+ it 'shows project snippets' do
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys('$')
+ end
+
+ page.within '.atwho-container' do
+ expect(page).to have_content(project_snippet.title)
+ end
+ end
+
private
def expect_to_wrap(should_wrap, item, note, value)
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index d78451112ec..0689c843104 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -125,6 +125,14 @@ describe IssuesFinder do
end
end
+ context 'filtering by any milestone' do
+ let(:params) { { milestone_title: Milestone::Any.title } }
+
+ it 'returns issues with any assigned milestone' do
+ expect(issues).to contain_exactly(issue1)
+ end
+ end
+
context 'filtering by upcoming milestone' do
let(:params) { { milestone_title: Milestone::Upcoming.name } }
diff --git a/spec/fixtures/api/schemas/job/deployment_status.json b/spec/fixtures/api/schemas/job/deployment_status.json
index a90b8b35654..83b1899fdf3 100644
--- a/spec/fixtures/api/schemas/job/deployment_status.json
+++ b/spec/fixtures/api/schemas/job/deployment_status.json
@@ -2,7 +2,6 @@
"type": "object",
"required": [
"status",
- "icon",
"environment"
],
"properties": {
@@ -20,7 +19,6 @@
{ "type": "null" }
]
},
- "icon": { "type": "string" },
"environment": { "$ref": "../environment.json" }
},
"additionalProperties": false
diff --git a/spec/graphql/types/permission_types/project_spec.rb b/spec/graphql/types/permission_types/project_spec.rb
index 89eecef096e..927153adc5b 100644
--- a/spec/graphql/types/permission_types/project_spec.rb
+++ b/spec/graphql/types/permission_types/project_spec.rb
@@ -10,7 +10,7 @@ describe Types::PermissionTypes::Project do
:read_commit_status, :request_access, :create_pipeline, :create_pipeline_schedule,
:create_merge_request_from, :create_wiki, :push_code, :create_deployment, :push_to_delete_protected_branch,
:admin_wiki, :admin_project, :update_pages, :admin_remote_mirror, :create_label,
- :update_wiki, :destroy_wiki, :create_pages, :destroy_pages
+ :update_wiki, :destroy_wiki, :create_pages, :destroy_pages, :read_pages_content
]
expect(described_class).to have_graphql_fields(expected_permissions)
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 1238cfbd1e7..4135f31e051 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -174,9 +174,7 @@ describe ApplicationHelper do
it 'returns paths for autocomplete_sources_controller' do
sources = helper.autocomplete_data_sources(project, noteable_type)
-
- expect(sources.keys).to match_array([:members, :issues, :mergeRequests, :labels, :milestones, :commands])
-
+ expect(sources.keys).to match_array([:members, :issues, :mergeRequests, :labels, :milestones, :commands, :snippets])
sources.keys.each do |key|
expect(sources[key]).not_to be_nil
end
diff --git a/spec/javascripts/gfm_auto_complete_spec.js b/spec/javascripts/gfm_auto_complete_spec.js
index 4f9cacf2724..b57c4943c01 100644
--- a/spec/javascripts/gfm_auto_complete_spec.js
+++ b/spec/javascripts/gfm_auto_complete_spec.js
@@ -103,7 +103,7 @@ describe('GfmAutoComplete', function () {
gfmAutoCompleteCallbacks.matcher.call(context, flag, subtext)
);
- const flagsUseDefaultMatcher = ['@', '#', '!', '~', '%'];
+ const flagsUseDefaultMatcher = ['@', '#', '!', '~', '%', '$'];
const otherFlags = ['/', ':'];
const flags = flagsUseDefaultMatcher.concat(otherFlags);
diff --git a/spec/lib/banzai/cross_project_reference_spec.rb b/spec/lib/banzai/cross_project_reference_spec.rb
index aadfe7637dd..ba995e16be7 100644
--- a/spec/lib/banzai/cross_project_reference_spec.rb
+++ b/spec/lib/banzai/cross_project_reference_spec.rb
@@ -1,16 +1,21 @@
require 'spec_helper'
describe Banzai::CrossProjectReference do
- include described_class
+ let(:including_class) { Class.new.include(described_class).new }
+
+ before do
+ allow(including_class).to receive(:context).and_return({})
+ allow(including_class).to receive(:parent_from_ref).and_call_original
+ end
describe '#parent_from_ref' do
context 'when no project was referenced' do
it 'returns the project from context' do
project = double
- allow(self).to receive(:context).and_return({ project: project })
+ allow(including_class).to receive(:context).and_return({ project: project })
- expect(parent_from_ref(nil)).to eq project
+ expect(including_class.parent_from_ref(nil)).to eq project
end
end
@@ -18,15 +23,15 @@ describe Banzai::CrossProjectReference do
it 'returns the group from context' do
group = double
- allow(self).to receive(:context).and_return({ group: group })
+ allow(including_class).to receive(:context).and_return({ group: group })
- expect(parent_from_ref(nil)).to eq group
+ expect(including_class.parent_from_ref(nil)).to eq group
end
end
context 'when referenced project does not exist' do
it 'returns nil' do
- expect(parent_from_ref('invalid/reference')).to be_nil
+ expect(including_class.parent_from_ref('invalid/reference')).to be_nil
end
end
@@ -37,7 +42,7 @@ describe Banzai::CrossProjectReference do
expect(Project).to receive(:find_by_full_path)
.with('cross/reference').and_return(project2)
- expect(parent_from_ref('cross/reference')).to eq project2
+ expect(including_class.parent_from_ref('cross/reference')).to eq project2
end
end
end
diff --git a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
index e1af5a15371..cbff2fdab14 100644
--- a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
@@ -60,6 +60,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter do
exp = act = "See #{commit1.id.reverse}...#{commit2.id}"
allow(project.repository).to receive(:commit).with(commit1.id.reverse)
+ allow(project.repository).to receive(:commit).with(commit2.id)
expect(reference_filter(act).to_html).to eq exp
end
diff --git a/spec/lib/gitlab/ci/build/policy/changes_spec.rb b/spec/lib/gitlab/ci/build/policy/changes_spec.rb
new file mode 100644
index 00000000000..ab401108c84
--- /dev/null
+++ b/spec/lib/gitlab/ci/build/policy/changes_spec.rb
@@ -0,0 +1,107 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Build::Policy::Changes do
+ set(:project) { create(:project) }
+
+ describe '#satisfied_by?' do
+ describe 'paths matching matching' do
+ let(:pipeline) do
+ build(:ci_empty_pipeline, project: project,
+ ref: 'master',
+ source: :push,
+ sha: '1234abcd',
+ before_sha: '0123aabb')
+ end
+
+ let(:ci_build) do
+ build(:ci_build, pipeline: pipeline, project: project, ref: 'master')
+ end
+
+ let(:seed) { double('build seed', to_resource: ci_build) }
+
+ before do
+ allow(pipeline).to receive(:modified_paths) do
+ %w[some/modified/ruby/file.rb some/other_file.txt some/.dir/file]
+ end
+ end
+
+ it 'is satisfied by matching literal path' do
+ policy = described_class.new(%w[some/other_file.txt])
+
+ expect(policy).to be_satisfied_by(pipeline, seed)
+ end
+
+ it 'is satisfied by matching simple pattern' do
+ policy = described_class.new(%w[some/*.txt])
+
+ expect(policy).to be_satisfied_by(pipeline, seed)
+ end
+
+ it 'is satisfied by matching recusive pattern' do
+ policy = described_class.new(%w[some/**/*.rb])
+
+ expect(policy).to be_satisfied_by(pipeline, seed)
+ end
+
+ it 'is satisfied by matching a pattern with a dot' do
+ policy = described_class.new(%w[some/*/file])
+
+ expect(policy).to be_satisfied_by(pipeline, seed)
+ end
+
+ it 'is not satisfied when pattern does not match path' do
+ policy = described_class.new(%w[some/*.rb])
+
+ expect(policy).not_to be_satisfied_by(pipeline, seed)
+ end
+
+ it 'is not satisfied when pattern does not match' do
+ policy = described_class.new(%w[invalid/*.md])
+
+ expect(policy).not_to be_satisfied_by(pipeline, seed)
+ end
+
+ context 'when pipelines does not run for a branch update' do
+ before do
+ pipeline.before_sha = Gitlab::Git::BLANK_SHA
+ end
+
+ it 'is always satisfied' do
+ policy = described_class.new(%w[invalid/*])
+
+ expect(policy).to be_satisfied_by(pipeline, seed)
+ end
+ end
+ end
+
+ describe 'gitaly integration' do
+ set(:project) { create(:project, :repository) }
+
+ let(:pipeline) do
+ create(:ci_empty_pipeline, project: project,
+ ref: 'master',
+ source: :push,
+ sha: '498214d',
+ before_sha: '281d3a7')
+ end
+
+ let(:build) do
+ create(:ci_build, pipeline: pipeline, project: project, ref: 'master')
+ end
+
+ let(:seed) { double('build seed', to_resource: build) }
+
+ it 'is satisfied by changes introduced by a push' do
+ policy = described_class.new(['with space/*.md'])
+
+ expect(policy).to be_satisfied_by(pipeline, seed)
+ end
+
+ it 'is not satisfied by changes that are not in the push' do
+ policy = described_class.new(%w[files/js/commit.js])
+
+ expect(policy).not_to be_satisfied_by(pipeline, seed)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/entry/policy_spec.rb b/spec/lib/gitlab/ci/config/entry/policy_spec.rb
index 83d39b82068..bef93fe7af7 100644
--- a/spec/lib/gitlab/ci/config/entry/policy_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/policy_spec.rb
@@ -1,4 +1,5 @@
-require 'spec_helper'
+require 'fast_spec_helper'
+require_dependency 'active_model'
describe Gitlab::Ci::Config::Entry::Policy do
let(:entry) { described_class.new(config) }
@@ -124,6 +125,23 @@ describe Gitlab::Ci::Config::Entry::Policy do
end
end
+ context 'when specifying a valid changes policy' do
+ let(:config) { { changes: %w[some/* paths/**/*.rb] } }
+
+ it 'is a correct configuraton' do
+ expect(entry).to be_valid
+ expect(entry.value).to eq(config)
+ end
+ end
+
+ context 'when changes policy is invalid' do
+ let(:config) { { changes: [1, 2] } }
+
+ it 'returns errors' do
+ expect(entry.errors).to include /changes should be an array of strings/
+ end
+ end
+
context 'when specifying unknown policy' do
let(:config) { { refs: ['master'], invalid: :something } }
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index d75c473eb66..85b23edce9f 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -1369,7 +1369,7 @@ module Gitlab
end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec dependencies should be an array of strings")
end
- it 'returns errors if pipeline variables expression is invalid' do
+ it 'returns errors if pipeline variables expression policy is invalid' do
config = YAML.dump({ rspec: { script: 'test', only: { variables: ['== null'] } } })
expect { Gitlab::Ci::YamlProcessor.new(config) }
@@ -1377,6 +1377,14 @@ module Gitlab
'jobs:rspec:only variables invalid expression syntax')
end
+ it 'returns errors if pipeline changes policy is invalid' do
+ config = YAML.dump({ rspec: { script: 'test', only: { changes: [1] } } })
+
+ expect { Gitlab::Ci::YamlProcessor.new(config) }
+ .to raise_error(Gitlab::Ci::YamlProcessor::ValidationError,
+ 'jobs:rspec:only changes should be an array of strings')
+ end
+
it 'returns errors if extended hash configuration is invalid' do
config = YAML.dump({ rspec: { extends: 'something', script: 'test' } })
diff --git a/spec/lib/gitlab/git/diff_stats_collection_spec.rb b/spec/lib/gitlab/git/diff_stats_collection_spec.rb
index 89927cbb3a6..b07690ef39c 100644
--- a/spec/lib/gitlab/git/diff_stats_collection_spec.rb
+++ b/spec/lib/gitlab/git/diff_stats_collection_spec.rb
@@ -14,7 +14,7 @@ describe Gitlab::Git::DiffStatsCollection do
let(:diff_stats) { [stats_a, stats_b] }
let(:collection) { described_class.new(diff_stats) }
- describe '.find_by_path' do
+ describe '#find_by_path' do
it 'returns stats by path when found' do
expect(collection.find_by_path('foo')).to eq(stats_a)
end
@@ -23,4 +23,10 @@ describe Gitlab::Git::DiffStatsCollection do
expect(collection.find_by_path('no-file')).to be_nil
end
end
+
+ describe '#paths' do
+ it 'returns only modified paths' do
+ expect(collection.paths).to eq %w[foo bar]
+ end
+ end
end
diff --git a/spec/lib/gitlab/git/push_spec.rb b/spec/lib/gitlab/git/push_spec.rb
new file mode 100644
index 00000000000..566c8209504
--- /dev/null
+++ b/spec/lib/gitlab/git/push_spec.rb
@@ -0,0 +1,166 @@
+require 'spec_helper'
+
+describe Gitlab::Git::Push do
+ set(:project) { create(:project, :repository) }
+
+ let(:oldrev) { project.commit('HEAD~2').id }
+ let(:newrev) { project.commit.id }
+ let(:ref) { 'refs/heads/some-branch' }
+
+ subject { described_class.new(project, oldrev, newrev, ref) }
+
+ describe '#branch_name' do
+ context 'when it is a branch push' do
+ let(:ref) { 'refs/heads/my-branch' }
+
+ it 'returns branch name' do
+ expect(subject.branch_name).to eq 'my-branch'
+ end
+ end
+
+ context 'when it is a tag push' do
+ let(:ref) { 'refs/tags/my-branch' }
+
+ it 'returns nil' do
+ expect(subject.branch_name).to be_nil
+ end
+ end
+ end
+
+ describe '#branch_push?' do
+ context 'when pushing a branch ref' do
+ let(:ref) { 'refs/heads/my-branch' }
+
+ it { is_expected.to be_branch_push }
+ end
+
+ context 'when it is a tag push' do
+ let(:ref) { 'refs/tags/my-tag' }
+
+ it { is_expected.not_to be_branch_push }
+ end
+ end
+
+ describe '#branch_updated?' do
+ context 'when it is a branch push with correct old and new revisions' do
+ it { is_expected.to be_branch_updated }
+ end
+
+ context 'when it is not a branch push' do
+ let(:ref) { 'refs/tags/my-tag' }
+
+ it { is_expected.not_to be_branch_updated }
+ end
+
+ context 'when old revision is blank' do
+ let(:oldrev) { Gitlab::Git::BLANK_SHA }
+
+ it { is_expected.not_to be_branch_updated }
+ end
+
+ context 'when it is not a branch push' do
+ let(:newrev) { Gitlab::Git::BLANK_SHA }
+
+ it { is_expected.not_to be_branch_updated }
+ end
+
+ context 'when oldrev is nil' do
+ let(:oldrev) { nil }
+
+ it { is_expected.not_to be_branch_updated }
+ end
+ end
+
+ describe '#force_push?' do
+ context 'when old revision is an ancestor of the new revision' do
+ let(:oldrev) { 'HEAD~3' }
+ let(:newrev) { 'HEAD~1' }
+
+ it { is_expected.not_to be_force_push }
+ end
+
+ context 'when old revision is not an ancestor of the new revision' do
+ let(:oldrev) { 'HEAD~3' }
+ let(:newrev) { '123456' }
+
+ it { is_expected.to be_force_push }
+ end
+ end
+
+ describe '#branch_added?' do
+ context 'when old revision is defined' do
+ it { is_expected.not_to be_branch_added }
+ end
+
+ context 'when old revision is not defined' do
+ let(:oldrev) { Gitlab::Git::BLANK_SHA }
+
+ it { is_expected.to be_branch_added }
+ end
+ end
+
+ describe '#branch_removed?' do
+ context 'when new revision is defined' do
+ it { is_expected.not_to be_branch_removed }
+ end
+
+ context 'when new revision is not defined' do
+ let(:newrev) { Gitlab::Git::BLANK_SHA }
+
+ it { is_expected.to be_branch_removed }
+ end
+ end
+
+ describe '#modified_paths' do
+ context 'when a push is a branch update' do
+ let(:newrev) { '498214d' }
+ let(:oldrev) { '281d3a7' }
+
+ it 'returns modified paths' do
+ expect(subject.modified_paths).to eq ['bar/branch-test.txt',
+ 'files/js/commit.coffee',
+ 'with space/README.md']
+ end
+ end
+
+ context 'when a push is not a branch update' do
+ let(:oldrev) { Gitlab::Git::BLANK_SHA }
+
+ it 'raises an error' do
+ expect { subject.modified_paths }.to raise_error(ArgumentError)
+ end
+ end
+ end
+
+ describe '#oldrev' do
+ context 'when a valid oldrev is provided' do
+ it 'returns oldrev' do
+ expect(subject.oldrev).to eq oldrev
+ end
+ end
+
+ context 'when a nil valud is provided' do
+ let(:oldrev) { nil }
+
+ it 'returns blank SHA' do
+ expect(subject.oldrev).to eq Gitlab::Git::BLANK_SHA
+ end
+ end
+ end
+
+ describe '#newrev' do
+ context 'when valid newrev is provided' do
+ it 'returns newrev' do
+ expect(subject.newrev).to eq newrev
+ end
+ end
+
+ context 'when a nil valud is provided' do
+ let(:newrev) { nil }
+
+ it 'returns blank SHA' do
+ expect(subject.newrev).to eq Gitlab::Git::BLANK_SHA
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 1d59cff7ba8..f7935149b23 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -493,6 +493,7 @@ ProjectFeature:
- snippets_access_level
- builds_access_level
- repository_access_level
+- pages_access_level
- created_at
- updated_at
ProtectedBranch::MergeAccessLevel:
diff --git a/spec/migrations/add_pages_access_level_to_project_feature_spec.rb b/spec/migrations/add_pages_access_level_to_project_feature_spec.rb
new file mode 100644
index 00000000000..3946602c5be
--- /dev/null
+++ b/spec/migrations/add_pages_access_level_to_project_feature_spec.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20180423204600_add_pages_access_level_to_project_feature.rb')
+
+describe AddPagesAccessLevelToProjectFeature, :migration do
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:features) { table(:project_features) }
+ let!(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab') }
+ let!(:first_project) { projects.create(name: 'gitlab1', path: 'gitlab1', namespace_id: namespace.id) }
+ let!(:first_project_features) { features.create(project_id: first_project.id) }
+ let!(:second_project) { projects.create(name: 'gitlab2', path: 'gitlab2', namespace_id: namespace.id) }
+ let!(:second_project_features) { features.create(project_id: second_project.id) }
+
+ it 'correctly migrate pages for old projects to be public' do
+ migrate!
+
+ # For old projects pages should be public
+ expect(first_project_features.reload.pages_access_level).to eq ProjectFeature::PUBLIC
+ expect(second_project_features.reload.pages_access_level).to eq ProjectFeature::PUBLIC
+ end
+
+ it 'after migration pages are enabled as default' do
+ migrate!
+
+ # For new project default is enabled
+ third_project = projects.create(name: 'gitlab3', path: 'gitlab3', namespace_id: namespace.id)
+ third_project_features = features.create(project_id: third_project.id)
+ expect(third_project_features.reload.pages_access_level).to eq ProjectFeature::ENABLED
+ end
+end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 5ca140879c3..3b01b39ecab 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -837,6 +837,57 @@ describe Ci::Pipeline, :mailer do
end
end
+ describe '#branch_updated?' do
+ context 'when pipeline has before SHA' do
+ before do
+ pipeline.update_column(:before_sha, 'a1b2c3d4')
+ end
+
+ it 'runs on a branch update push' do
+ expect(pipeline.before_sha).not_to be Gitlab::Git::BLANK_SHA
+ expect(pipeline.branch_updated?).to be true
+ end
+ end
+
+ context 'when pipeline does not have before SHA' do
+ before do
+ pipeline.update_column(:before_sha, Gitlab::Git::BLANK_SHA)
+ end
+
+ it 'does not run on a branch updating push' do
+ expect(pipeline.branch_updated?).to be false
+ end
+ end
+ end
+
+ describe '#modified_paths' do
+ context 'when old and new revisions are set' do
+ let(:project) { create(:project, :repository) }
+
+ before do
+ pipeline.update(before_sha: '1234abcd', sha: '2345bcde')
+ end
+
+ it 'fetches stats for changes between commits' do
+ expect(project.repository)
+ .to receive(:diff_stats).with('1234abcd', '2345bcde')
+ .and_call_original
+
+ pipeline.modified_paths
+ end
+ end
+
+ context 'when either old or new revision is missing' do
+ before do
+ pipeline.update_column(:before_sha, Gitlab::Git::BLANK_SHA)
+ end
+
+ it 'raises an error' do
+ expect { pipeline.modified_paths }.to raise_error(ArgumentError)
+ end
+ end
+ end
+
describe '#has_kubernetes_active?' do
context 'when kubernetes is active' do
shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do
diff --git a/spec/models/internal_id_spec.rb b/spec/models/internal_id_spec.rb
index f2aad455d5f..52c00a74b4b 100644
--- a/spec/models/internal_id_spec.rb
+++ b/spec/models/internal_id_spec.rb
@@ -65,7 +65,8 @@ describe InternalId do
context 'with an insufficient schema version' do
before do
described_class.reset_column_information
- expect(ActiveRecord::Migrator).to receive(:current_version).and_return(InternalId::REQUIRED_SCHEMA_VERSION - 1)
+ # Project factory will also call the current_version
+ expect(ActiveRecord::Migrator).to receive(:current_version).twice.and_return(InternalId::REQUIRED_SCHEMA_VERSION - 1)
end
let(:init) { double('block') }
diff --git a/spec/models/project_feature_spec.rb b/spec/models/project_feature_spec.rb
index cd7f77024da..fee7d65c217 100644
--- a/spec/models/project_feature_spec.rb
+++ b/spec/models/project_feature_spec.rb
@@ -17,7 +17,7 @@ describe ProjectFeature do
end
describe '#feature_available?' do
- let(:features) { %w(issues wiki builds merge_requests snippets repository) }
+ let(:features) { %w(issues wiki builds merge_requests snippets repository pages) }
context 'when features are disabled' do
it "returns false" do
@@ -73,6 +73,22 @@ describe ProjectFeature do
end
end
end
+
+ context 'when feature is disabled by a feature flag' do
+ it 'returns false' do
+ stub_feature_flags(issues: false)
+
+ expect(project.feature_available?(:issues, user)).to eq(false)
+ end
+ end
+
+ context 'when feature is enabled by a feature flag' do
+ it 'returns true' do
+ stub_feature_flags(issues: true)
+
+ expect(project.feature_available?(:issues, user)).to eq(true)
+ end
+ end
end
context 'repository related features' do
@@ -96,6 +112,19 @@ describe ProjectFeature do
end
end
+ context 'public features' do
+ it "does not allow public for other than pages" do
+ features = %w(issues wiki builds merge_requests snippets repository)
+ project_feature = project.project_feature
+
+ features.each do |feature|
+ field = "#{feature}_access_level".to_sym
+ project_feature.update_attribute(field, ProjectFeature::PUBLIC)
+ expect(project_feature.valid?).to be_falsy
+ end
+ end
+ end
+
describe '#*_enabled?' do
let(:features) { %w(wiki builds merge_requests) }
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 1e2e13a723c..9f6cf12f9a7 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -56,6 +56,7 @@ describe API::Issues do
let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) }
let(:no_milestone_title) { URI.escape(Milestone::None.title) }
+ let(:any_milestone_title) { URI.escape(Milestone::Any.title) }
before(:all) do
project.add_reporter(user)
@@ -811,6 +812,15 @@ describe API::Issues do
expect(json_response.first['id']).to eq(confidential_issue.id)
end
+ it 'returns an array of issues with any milestone' do
+ get api("#{base_url}/issues?milestone=#{any_milestone_title}", user)
+
+ response_ids = json_response.map { |issue| issue['id'] }
+
+ expect_paginated_array_response(size: 2)
+ expect(response_ids).to contain_exactly(closed_issue.id, issue.id)
+ end
+
it 'sorts by created_at descending by default' do
get api("#{base_url}/issues", user)
diff --git a/spec/requests/api/pages/internal_access_spec.rb b/spec/requests/api/pages/internal_access_spec.rb
new file mode 100644
index 00000000000..c41eabe0a48
--- /dev/null
+++ b/spec/requests/api/pages/internal_access_spec.rb
@@ -0,0 +1,88 @@
+require 'spec_helper'
+
+describe "Internal Project Pages Access" do
+ using RSpec::Parameterized::TableSyntax
+ include AccessMatchers
+
+ set(:group) { create(:group) }
+ set(:project) { create(:project, :internal, pages_access_level: ProjectFeature::ENABLED, namespace: group) }
+
+ set(:admin) { create(:admin) }
+ set(:owner) { create(:user) }
+ set(:master) { create(:user) }
+ set(:developer) { create(:user) }
+ set(:reporter) { create(:user) }
+ set(:guest) { create(:user) }
+ set(:user) { create(:user) }
+
+ before do
+ allow(Gitlab.config.pages).to receive(:access_control).and_return(true)
+ group.add_owner(owner)
+ project.add_master(master)
+ project.add_developer(developer)
+ project.add_reporter(reporter)
+ project.add_guest(guest)
+ end
+
+ describe "Project should be internal" do
+ describe '#internal?' do
+ subject { project.internal? }
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ describe "GET /projects/:id/pages_access" do
+ context 'access depends on the level' do
+ where(:pages_access_level, :with_user, :expected_result) do
+ ProjectFeature::DISABLED | "admin" | 403
+ ProjectFeature::DISABLED | "owner" | 403
+ ProjectFeature::DISABLED | "master" | 403
+ ProjectFeature::DISABLED | "developer" | 403
+ ProjectFeature::DISABLED | "reporter" | 403
+ ProjectFeature::DISABLED | "guest" | 403
+ ProjectFeature::DISABLED | "user" | 403
+ ProjectFeature::DISABLED | nil | 404
+ ProjectFeature::PUBLIC | "admin" | 200
+ ProjectFeature::PUBLIC | "owner" | 200
+ ProjectFeature::PUBLIC | "master" | 200
+ ProjectFeature::PUBLIC | "developer" | 200
+ ProjectFeature::PUBLIC | "reporter" | 200
+ ProjectFeature::PUBLIC | "guest" | 200
+ ProjectFeature::PUBLIC | "user" | 200
+ ProjectFeature::PUBLIC | nil | 404
+ ProjectFeature::ENABLED | "admin" | 200
+ ProjectFeature::ENABLED | "owner" | 200
+ ProjectFeature::ENABLED | "master" | 200
+ ProjectFeature::ENABLED | "developer" | 200
+ ProjectFeature::ENABLED | "reporter" | 200
+ ProjectFeature::ENABLED | "guest" | 200
+ ProjectFeature::ENABLED | "user" | 200
+ ProjectFeature::ENABLED | nil | 404
+ ProjectFeature::PRIVATE | "admin" | 200
+ ProjectFeature::PRIVATE | "owner" | 200
+ ProjectFeature::PRIVATE | "master" | 200
+ ProjectFeature::PRIVATE | "developer" | 200
+ ProjectFeature::PRIVATE | "reporter" | 200
+ ProjectFeature::PRIVATE | "guest" | 200
+ ProjectFeature::PRIVATE | "user" | 403
+ ProjectFeature::PRIVATE | nil | 404
+ end
+
+ with_them do
+ before do
+ project.project_feature.update(pages_access_level: pages_access_level)
+ end
+ it "correct return value" do
+ if !with_user.nil?
+ user = public_send(with_user)
+ get api("/projects/#{project.id}/pages_access", user)
+ else
+ get api("/projects/#{project.id}/pages_access")
+ end
+
+ expect(response).to have_gitlab_http_status(expected_result)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/pages/private_access_spec.rb b/spec/requests/api/pages/private_access_spec.rb
new file mode 100644
index 00000000000..d69c15b0477
--- /dev/null
+++ b/spec/requests/api/pages/private_access_spec.rb
@@ -0,0 +1,88 @@
+require 'spec_helper'
+
+describe "Private Project Pages Access" do
+ using RSpec::Parameterized::TableSyntax
+ include AccessMatchers
+
+ set(:group) { create(:group) }
+ set(:project) { create(:project, :private, pages_access_level: ProjectFeature::ENABLED, namespace: group) }
+
+ set(:admin) { create(:admin) }
+ set(:owner) { create(:user) }
+ set(:master) { create(:user) }
+ set(:developer) { create(:user) }
+ set(:reporter) { create(:user) }
+ set(:guest) { create(:user) }
+ set(:user) { create(:user) }
+
+ before do
+ allow(Gitlab.config.pages).to receive(:access_control).and_return(true)
+ group.add_owner(owner)
+ project.add_master(master)
+ project.add_developer(developer)
+ project.add_reporter(reporter)
+ project.add_guest(guest)
+ end
+
+ describe "Project should be private" do
+ describe '#private?' do
+ subject { project.private? }
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ describe "GET /projects/:id/pages_access" do
+ context 'access depends on the level' do
+ where(:pages_access_level, :with_user, :expected_result) do
+ ProjectFeature::DISABLED | "admin" | 403
+ ProjectFeature::DISABLED | "owner" | 403
+ ProjectFeature::DISABLED | "master" | 403
+ ProjectFeature::DISABLED | "developer" | 403
+ ProjectFeature::DISABLED | "reporter" | 403
+ ProjectFeature::DISABLED | "guest" | 403
+ ProjectFeature::DISABLED | "user" | 404
+ ProjectFeature::DISABLED | nil | 404
+ ProjectFeature::PUBLIC | "admin" | 200
+ ProjectFeature::PUBLIC | "owner" | 200
+ ProjectFeature::PUBLIC | "master" | 200
+ ProjectFeature::PUBLIC | "developer" | 200
+ ProjectFeature::PUBLIC | "reporter" | 200
+ ProjectFeature::PUBLIC | "guest" | 200
+ ProjectFeature::PUBLIC | "user" | 404
+ ProjectFeature::PUBLIC | nil | 404
+ ProjectFeature::ENABLED | "admin" | 200
+ ProjectFeature::ENABLED | "owner" | 200
+ ProjectFeature::ENABLED | "master" | 200
+ ProjectFeature::ENABLED | "developer" | 200
+ ProjectFeature::ENABLED | "reporter" | 200
+ ProjectFeature::ENABLED | "guest" | 200
+ ProjectFeature::ENABLED | "user" | 404
+ ProjectFeature::ENABLED | nil | 404
+ ProjectFeature::PRIVATE | "admin" | 200
+ ProjectFeature::PRIVATE | "owner" | 200
+ ProjectFeature::PRIVATE | "master" | 200
+ ProjectFeature::PRIVATE | "developer" | 200
+ ProjectFeature::PRIVATE | "reporter" | 200
+ ProjectFeature::PRIVATE | "guest" | 200
+ ProjectFeature::PRIVATE | "user" | 404
+ ProjectFeature::PRIVATE | nil | 404
+ end
+
+ with_them do
+ before do
+ project.project_feature.update(pages_access_level: pages_access_level)
+ end
+ it "correct return value" do
+ if !with_user.nil?
+ user = public_send(with_user)
+ get api("/projects/#{project.id}/pages_access", user)
+ else
+ get api("/projects/#{project.id}/pages_access")
+ end
+
+ expect(response).to have_gitlab_http_status(expected_result)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/pages/public_access_spec.rb b/spec/requests/api/pages/public_access_spec.rb
new file mode 100644
index 00000000000..882ca26ac51
--- /dev/null
+++ b/spec/requests/api/pages/public_access_spec.rb
@@ -0,0 +1,88 @@
+require 'spec_helper'
+
+describe "Public Project Pages Access" do
+ using RSpec::Parameterized::TableSyntax
+ include AccessMatchers
+
+ set(:group) { create(:group) }
+ set(:project) { create(:project, :public, pages_access_level: ProjectFeature::ENABLED, namespace: group) }
+
+ set(:admin) { create(:admin) }
+ set(:owner) { create(:user) }
+ set(:master) { create(:user) }
+ set(:developer) { create(:user) }
+ set(:reporter) { create(:user) }
+ set(:guest) { create(:user) }
+ set(:user) { create(:user) }
+
+ before do
+ allow(Gitlab.config.pages).to receive(:access_control).and_return(true)
+ group.add_owner(owner)
+ project.add_master(master)
+ project.add_developer(developer)
+ project.add_reporter(reporter)
+ project.add_guest(guest)
+ end
+
+ describe "Project should be public" do
+ describe '#public?' do
+ subject { project.public? }
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ describe "GET /projects/:id/pages_access" do
+ context 'access depends on the level' do
+ where(:pages_access_level, :with_user, :expected_result) do
+ ProjectFeature::DISABLED | "admin" | 403
+ ProjectFeature::DISABLED | "owner" | 403
+ ProjectFeature::DISABLED | "master" | 403
+ ProjectFeature::DISABLED | "developer" | 403
+ ProjectFeature::DISABLED | "reporter" | 403
+ ProjectFeature::DISABLED | "guest" | 403
+ ProjectFeature::DISABLED | "user" | 403
+ ProjectFeature::DISABLED | nil | 403
+ ProjectFeature::PUBLIC | "admin" | 200
+ ProjectFeature::PUBLIC | "owner" | 200
+ ProjectFeature::PUBLIC | "master" | 200
+ ProjectFeature::PUBLIC | "developer" | 200
+ ProjectFeature::PUBLIC | "reporter" | 200
+ ProjectFeature::PUBLIC | "guest" | 200
+ ProjectFeature::PUBLIC | "user" | 200
+ ProjectFeature::PUBLIC | nil | 200
+ ProjectFeature::ENABLED | "admin" | 200
+ ProjectFeature::ENABLED | "owner" | 200
+ ProjectFeature::ENABLED | "master" | 200
+ ProjectFeature::ENABLED | "developer" | 200
+ ProjectFeature::ENABLED | "reporter" | 200
+ ProjectFeature::ENABLED | "guest" | 200
+ ProjectFeature::ENABLED | "user" | 200
+ ProjectFeature::ENABLED | nil | 200
+ ProjectFeature::PRIVATE | "admin" | 200
+ ProjectFeature::PRIVATE | "owner" | 200
+ ProjectFeature::PRIVATE | "master" | 200
+ ProjectFeature::PRIVATE | "developer" | 200
+ ProjectFeature::PRIVATE | "reporter" | 200
+ ProjectFeature::PRIVATE | "guest" | 200
+ ProjectFeature::PRIVATE | "user" | 403
+ ProjectFeature::PRIVATE | nil | 403
+ end
+
+ with_them do
+ before do
+ project.project_feature.update(pages_access_level: pages_access_level)
+ end
+ it "correct return value" do
+ if !with_user.nil?
+ user = public_send(with_user)
+ get api("/projects/#{project.id}/pages_access", user)
+ else
+ get api("/projects/#{project.id}/pages_access")
+ end
+
+ expect(response).to have_gitlab_http_status(expected_result)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index b7d62df0663..09c1d016081 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -785,35 +785,25 @@ describe API::Users do
end
describe 'GET /user/:id/keys' do
- before do
- admin
- end
+ it 'returns 404 for non-existing user' do
+ user_id = not_existing_user_id
- context 'when unauthenticated' do
- it 'returns authentication error' do
- get api("/users/#{user.id}/keys")
- expect(response).to have_gitlab_http_status(401)
- end
- end
+ get api("/users/#{user_id}/keys")
- context 'when authenticated' do
- it 'returns 404 for non-existing user' do
- get api('/users/999999/keys', admin)
- expect(response).to have_gitlab_http_status(404)
- expect(json_response['message']).to eq('404 User Not Found')
- end
+ expect(response).to have_gitlab_http_status(404)
+ expect(json_response['message']).to eq('404 User Not Found')
+ end
- it 'returns array of ssh keys' do
- user.keys << key
- user.save
+ it 'returns array of ssh keys' do
+ user.keys << key
+ user.save
- get api("/users/#{user.id}/keys", admin)
+ get api("/users/#{user.id}/keys")
- 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(key.title)
- end
+ 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(key.title)
end
end
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index 56df8dddbc1..bdfb12dc5df 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -133,8 +133,9 @@ describe 'project routing' do
# labels_namespace_project_autocomplete_sources_path GET /:project_id/autocomplete_sources/labels(.:format) projects/autocomplete_sources#labels
# milestones_namespace_project_autocomplete_sources_path GET /:project_id/autocomplete_sources/milestones(.:format) projects/autocomplete_sources#milestones
# commands_namespace_project_autocomplete_sources_path GET /:project_id/autocomplete_sources/commands(.:format) projects/autocomplete_sources#commands
+ # snippets_namespace_project_autocomplete_sources_path GET /:project_id/autocomplete_sources/snippets(.:format) projects/autocomplete_sources#snippets
describe Projects::AutocompleteSourcesController, 'routing' do
- [:members, :issues, :merge_requests, :labels, :milestones, :commands].each do |action|
+ [:members, :issues, :merge_requests, :labels, :milestones, :commands, :snippets].each do |action|
it "to ##{action}" do
expect(get("/gitlab/gitlabhq/autocomplete_sources/#{action}")).to route_to("projects/autocomplete_sources##{action}", namespace_id: 'gitlab', project_id: 'gitlabhq')
end
diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb
index 695b9980548..d58ff2cedc0 100644
--- a/spec/services/projects/update_service_spec.rb
+++ b/spec/services/projects/update_service_spec.rb
@@ -340,6 +340,27 @@ describe Projects::UpdateService do
call_service
end
end
+
+ context 'when updating #pages_access_level' do
+ subject(:call_service) do
+ update_project(project, admin, project_feature_attributes: { pages_access_level: ProjectFeature::PRIVATE })
+ end
+
+ it 'updates the attribute' do
+ expect { call_service }
+ .to change { project.project_feature.pages_access_level }
+ .to(ProjectFeature::PRIVATE)
+ end
+
+ it 'calls Projects::UpdatePagesConfigurationService' do
+ expect(Projects::UpdatePagesConfigurationService)
+ .to receive(:new)
+ .with(project)
+ .and_call_original
+
+ call_service
+ end
+ end
end
describe '#run_auto_devops_pipeline?' do
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index d1337325973..cd69160be10 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -34,6 +34,11 @@ Rainbow.enabled = false
# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.
# Requires helpers, and shared contexts/examples first since they're used in other support files
+
+# Load these first since they may be required by other helpers
+require Rails.root.join("spec/support/helpers/git_helpers.rb")
+
+# Then the rest
Dir[Rails.root.join("spec/support/helpers/*.rb")].each { |f| require f }
Dir[Rails.root.join("spec/support/shared_contexts/*.rb")].each { |f| require f }
Dir[Rails.root.join("spec/support/shared_examples/*.rb")].each { |f| require f }
diff --git a/yarn.lock b/yarn.lock
index 89f6391e48b..7967ad0eb34 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -616,14 +616,10 @@
lodash "^4.17.10"
to-fast-properties "^2.0.0"
-"@gitlab-org/gitlab-svgs@^1.23.0":
- version "1.29.0"
- resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.29.0.tgz#03b65b513f9099bbda6ecf94d673a2952f8c6c70"
- integrity sha512-sCl6nP3ph36+8P3nrw9VanAR648rgOUEBlEoLPHkhKm79xB1dUkXGBtI0uaSJVgbJx40M1/Ts8HSdMv+PF3EIg==
-
-"@gitlab-org/gitlab-svgs@^1.29.0":
+"@gitlab-org/gitlab-svgs@^1.23.0", "@gitlab-org/gitlab-svgs@^1.29.0":
version "1.31.0"
resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.31.0.tgz#495b074669f93af40e34f9978ce887773dea470a"
+ integrity sha512-tJbf99XX/ddFkXCXxQr9a0GJD9rPVoW3qMbU14dkxwG4WBmPEoVg+e7sLvm9OWTD1uUqiVW3qWKp++SGhhcRlw==
"@gitlab-org/gitlab-ui@^1.8.0":
version "1.8.0"