diff options
43 files changed, 355 insertions, 249 deletions
diff --git a/app/assets/javascripts/persistent_user_callouts.js b/app/assets/javascripts/persistent_user_callouts.js index 3130fe42c3c..7cf4906595b 100644 --- a/app/assets/javascripts/persistent_user_callouts.js +++ b/app/assets/javascripts/persistent_user_callouts.js @@ -24,6 +24,7 @@ const PERSISTENT_USER_CALLOUTS = [ '.js-geo-migrate-hashed-storage-callout', '.js-unlimited-members-during-trial-alert', '.js-branch-rules-info-callout', + '.js-new-navigation-callout', ]; const initCallouts = () => { diff --git a/app/assets/javascripts/work_items/components/notes/work_item_note.vue b/app/assets/javascripts/work_items/components/notes/work_item_note.vue index 39100467081..2996c68374d 100644 --- a/app/assets/javascripts/work_items/components/notes/work_item_note.vue +++ b/app/assets/javascripts/work_items/components/notes/work_item_note.vue @@ -180,7 +180,7 @@ export default { this.isEditing = true; updateDraft(this.autosaveKey, this.note.body); }, - async updateNote(newText) { + async updateNote({ commentText }) { try { this.isEditing = false; await this.$apollo.mutate({ @@ -188,7 +188,7 @@ export default { variables: { input: { id: this.note.id, - body: newText, + body: commentText, }, }, optimisticResponse: { @@ -196,14 +196,14 @@ export default { errors: [], note: { ...this.note, - bodyHtml: renderMarkdown(newText), + bodyHtml: renderMarkdown(commentText), }, }, }, }); clearDraft(this.autosaveKey); } catch (error) { - updateDraft(this.autosaveKey, newText); + updateDraft(this.autosaveKey, commentText); this.isEditing = true; this.$emit('error', __('Something went wrong when updating a comment. Please try again')); Sentry.captureException(error); diff --git a/app/helpers/users/callouts_helper.rb b/app/helpers/users/callouts_helper.rb index af3ac495164..27af19db685 100644 --- a/app/helpers/users/callouts_helper.rb +++ b/app/helpers/users/callouts_helper.rb @@ -16,6 +16,7 @@ module Users WEB_HOOK_DISABLED = 'web_hook_disabled' ULTIMATE_FEATURE_REMOVAL_BANNER = 'ultimate_feature_removal_banner' BRANCH_RULES_INFO_CALLOUT = 'branch_rules_info_callout' + NEW_NAVIGATION_CALLOUT = 'new_navigation_callout' def show_gke_cluster_integration_callout?(project) active_nav_link?(controller: sidebar_operations_paths) && @@ -79,6 +80,10 @@ module Users !user_dismissed?(BRANCH_RULES_INFO_CALLOUT) end + def show_new_navigation_callout? + show_super_sidebar? && !user_dismissed?(NEW_NAVIGATION_CALLOUT) + end + def ultimate_feature_removal_banner_dismissed?(project) return false unless project diff --git a/app/models/namespaces/project_namespace.rb b/app/models/namespaces/project_namespace.rb index cf2612b7f33..bf23fc21124 100644 --- a/app/models/namespaces/project_namespace.rb +++ b/app/models/namespaces/project_namespace.rb @@ -11,7 +11,8 @@ module Namespaces alias_attribute :namespace_id, :parent_id has_one :project, foreign_key: :project_namespace_id, inverse_of: :project_namespace - delegate :execute_hooks, :execute_integrations, to: :project, allow_nil: true + delegate :execute_hooks, :execute_integrations, :group, to: :project, allow_nil: true + delegate :external_references_supported?, :default_issues_tracker?, to: :project def self.sti_name 'Project' diff --git a/app/models/organizations/organization.rb b/app/models/organizations/organization.rb index 425869b1bb0..ee082e12c18 100644 --- a/app/models/organizations/organization.rb +++ b/app/models/organizations/organization.rb @@ -13,8 +13,7 @@ module Organizations validates :name, presence: true, - length: { maximum: 255 }, - uniqueness: { case_sensitive: false } + length: { maximum: 255 } def default? id == DEFAULT_ORGANIZATION_ID diff --git a/app/models/project.rb b/app/models/project.rb index ef87b611fca..44a360c6580 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -2908,7 +2908,13 @@ class Project < ApplicationRecord def default_branch_protected? branch_protection = Gitlab::Access::BranchProtection.new(self.namespace.default_branch_protection) - branch_protection.fully_protected? || branch_protection.developer_can_merge? + branch_protection.fully_protected? || branch_protection.developer_can_merge? || branch_protection.developer_can_initial_push? + end + + def initial_push_to_default_branch_allowed_for_developer? + branch_protection = Gitlab::Access::BranchProtection.new(self.namespace.default_branch_protection) + + !branch_protection.any? || branch_protection.developer_can_push? || branch_protection.developer_can_initial_push? end def environments_for_scope(scope) diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb index 09a0cfc91dc..aebce59a040 100644 --- a/app/models/protected_branch.rb +++ b/app/models/protected_branch.rb @@ -26,10 +26,16 @@ class ProtectedBranch < ApplicationRecord end def self.protected_ref_accessible_to?(ref, user, project:, action:, protected_refs: nil) - # Maintainers, owners and admins are allowed to create the default branch + if project.empty_repo? + member_access = project.team.max_member_access(user.id) - if project.empty_repo? && project.default_branch_protected? + # Admins are always allowed to create the default branch return true if user.admin? || user.can?(:admin_project, project) + + # Developers can push if it is allowed by default branch protection settings + if member_access == Gitlab::Access::DEVELOPER && project.initial_push_to_default_branch_allowed_for_developer? + return true + end end super diff --git a/app/models/users/callout.rb b/app/models/users/callout.rb index 1ec1fb2fdfd..41c682649a8 100644 --- a/app/models/users/callout.rb +++ b/app/models/users/callout.rb @@ -69,7 +69,8 @@ module Users repository_storage_limit_banner_info_threshold: 67, # EE-only repository_storage_limit_banner_warning_threshold: 68, # EE-only repository_storage_limit_banner_alert_threshold: 69, # EE-only - repository_storage_limit_banner_error_threshold: 70 # EE-only + repository_storage_limit_banner_error_threshold: 70, # EE-only + new_navigation_callout: 71 } validates :feature_name, diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index aa80de7f789..b528d5685a7 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -20,6 +20,7 @@ .mobile-overlay = dispensable_render_if_exists 'layouts/header/verification_reminder' .alert-wrapper.gl-force-block-formatting-context + = dispensable_render 'shared/new_nav_announcement' = dispensable_render 'shared/outdated_browser' = dispensable_render_if_exists "layouts/header/licensed_user_count_threshold" = dispensable_render_if_exists "layouts/header/token_expiry_notification" diff --git a/app/views/shared/_new_nav_announcement.html.haml b/app/views/shared/_new_nav_announcement.html.haml new file mode 100644 index 00000000000..8cabab09ec2 --- /dev/null +++ b/app/views/shared/_new_nav_announcement.html.haml @@ -0,0 +1,33 @@ +- return unless show_new_navigation_callout? + +- changes_url = 'https://gitlab.com/groups/gitlab-org/-/epics/9044#whats-different' +- vision_url = 'https://about.gitlab.com/blog/2023/05/01/gitlab-product-navigation/' +- design_url = 'https://about.gitlab.com/blog/2023/05/15/overhauling-the-navigation-is-like-building-a-dream-home/' +- feedback_url = 'https://gitlab.com/gitlab-org/gitlab/-/issues/409005' +- docs_url = help_page_path('tutorials/left_sidebar/index') + +- changes_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: changes_url } +- vision_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: vision_url } +- design_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: design_url } +- link_end = '</a>'.html_safe + +- welcome_text = _('For the next few releases, you can go to your avatar at any time to turn the new navigation on and off.') +- cta_text = _('Read more about the %{changes_link_start}changes%{link_end}, the %{vision_link_start}vision%{link_end}, and the %{design_link_start}design%{link_end}.' % { changes_link_start: changes_link_start, + vision_link_start: vision_link_start, + design_link_start: design_link_start, + link_end: link_end}).html_safe # rubocop:disable Gettext/StaticIdentifier + += render Pajamas::AlertComponent.new(dismissible: true, title: _('Welcome to a new navigation experience'), + alert_options: { class: 'js-new-navigation-callout', data: { feature_id: "new_navigation_callout", dismiss_endpoint: callouts_path }}) do |c| + - c.with_body do + %p + = welcome_text + = cta_text + - c.with_actions do + = render Pajamas::ButtonComponent.new(variant: :confirm, + href: docs_url, + button_options: { class: 'gl-alert-action', data: { track_action: 'click_button', track_label: 'banner_nav_learn_more' } }) do |c| + = _('Learn more') + = render Pajamas::ButtonComponent.new(href: feedback_url, + button_options: { data: { track_action: 'click_button', track_label: 'banner_nav_provide_feedback' } }) do |c| + = _('Provide feedback') diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index c939da9325c..e34cb74449a 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -1263,33 +1263,6 @@ :weight: 1 :idempotent: false :tags: [] -- :name: github_importer:github_import_import_pull_request_merged_by - :worker_name: Gitlab::GithubImport::ImportPullRequestMergedByWorker - :feature_category: :importers - :has_external_dependencies: true - :urgency: :low - :resource_boundary: :cpu - :weight: 1 - :idempotent: false - :tags: [] -- :name: github_importer:github_import_import_pull_request_review - :worker_name: Gitlab::GithubImport::ImportPullRequestReviewWorker - :feature_category: :importers - :has_external_dependencies: true - :urgency: :low - :resource_boundary: :cpu - :weight: 1 - :idempotent: false - :tags: [] -- :name: github_importer:github_import_import_release_attachments - :worker_name: Gitlab::GithubImport::ImportReleaseAttachmentsWorker - :feature_category: :importers - :has_external_dependencies: true - :urgency: :low - :resource_boundary: :unknown - :weight: 1 - :idempotent: false - :tags: [] - :name: github_importer:github_import_pull_requests_import_merged_by :worker_name: Gitlab::GithubImport::PullRequests::ImportMergedByWorker :feature_category: :importers diff --git a/app/workers/gitlab/github_import/import_pull_request_merged_by_worker.rb b/app/workers/gitlab/github_import/import_pull_request_merged_by_worker.rb deleted file mode 100644 index 94472fdf6db..00000000000 --- a/app/workers/gitlab/github_import/import_pull_request_merged_by_worker.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -# TODO: remove in 16.1 milestone -# https://gitlab.com/gitlab-org/gitlab/-/issues/409706 -module Gitlab - module GithubImport - class ImportPullRequestMergedByWorker # rubocop:disable Scalability/IdempotentWorker - include ObjectImporter - - worker_resource_boundary :cpu - - def representation_class - Gitlab::GithubImport::Representation::PullRequest - end - - def importer_class - Importer::PullRequests::MergedByImporter - end - - def object_type - :pull_request_merged_by - end - end - end -end diff --git a/app/workers/gitlab/github_import/import_pull_request_review_worker.rb b/app/workers/gitlab/github_import/import_pull_request_review_worker.rb deleted file mode 100644 index 6b7d19010ec..00000000000 --- a/app/workers/gitlab/github_import/import_pull_request_review_worker.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -# TODO: remove in 16.1 milestone -# https://gitlab.com/gitlab-org/gitlab/-/issues/409706 -module Gitlab - module GithubImport - class ImportPullRequestReviewWorker # rubocop:disable Scalability/IdempotentWorker - include ObjectImporter - - worker_resource_boundary :cpu - - def representation_class - Gitlab::GithubImport::Representation::PullRequestReview - end - - def importer_class - Importer::PullRequests::ReviewImporter - end - - def object_type - :pull_request_review - end - end - end -end diff --git a/app/workers/gitlab/github_import/import_release_attachments_worker.rb b/app/workers/gitlab/github_import/import_release_attachments_worker.rb deleted file mode 100644 index 0d3831789bf..00000000000 --- a/app/workers/gitlab/github_import/import_release_attachments_worker.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -# TODO: remove in 16.1 milestone -# https://gitlab.com/gitlab-org/gitlab/-/issues/409706 -module Gitlab - module GithubImport - class ImportReleaseAttachmentsWorker # rubocop:disable Scalability/IdempotentWorker - include ObjectImporter - - def representation_class - Representation::NoteText - end - - def importer_class - Importer::NoteAttachmentsImporter - end - - def object_type - :release_attachment - end - end - end -end diff --git a/db/migrate/20230522103433_remove_git_hub_import_deprecated_workers.rb b/db/migrate/20230522103433_remove_git_hub_import_deprecated_workers.rb new file mode 100644 index 00000000000..3752836b018 --- /dev/null +++ b/db/migrate/20230522103433_remove_git_hub_import_deprecated_workers.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class RemoveGitHubImportDeprecatedWorkers < Gitlab::Database::Migration[2.1] + DEPRECATED_JOB_CLASSES = %w[ + Gitlab::GithubImport::ImportPullRequestMergedByWorker + Gitlab::GithubImport::ImportPullRequestReviewWorker + Gitlab::GithubImport::ImportReleaseAttachmentsWorker + ] + + disable_ddl_transaction! + def up + sidekiq_remove_jobs(job_klasses: DEPRECATED_JOB_CLASSES) + end + + def down + # This migration removes any instances of deprecated workers and cannot be undone. + end +end diff --git a/db/migrate/20230524095108_remove_index_on_name_on_organization.rb b/db/migrate/20230524095108_remove_index_on_name_on_organization.rb new file mode 100644 index 00000000000..216dca1efcd --- /dev/null +++ b/db/migrate/20230524095108_remove_index_on_name_on_organization.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class RemoveIndexOnNameOnOrganization < Gitlab::Database::Migration[2.1] + disable_ddl_transaction! + + INDEX_NAME = 'unique_organizations_on_name_lower' + + def up + remove_concurrent_index_by_name :organizations, INDEX_NAME + end + + def down + add_concurrent_index :organizations, 'lower(name)', name: INDEX_NAME + end +end diff --git a/db/schema_migrations/20230522103433 b/db/schema_migrations/20230522103433 new file mode 100644 index 00000000000..625410b73a1 --- /dev/null +++ b/db/schema_migrations/20230522103433 @@ -0,0 +1 @@ +b5c1ad2fe2f4d38b39ac9c621b36f62c0a06b74846f5afdad4d88804bb1d7acf
\ No newline at end of file diff --git a/db/schema_migrations/20230524095108 b/db/schema_migrations/20230524095108 new file mode 100644 index 00000000000..8b898896b78 --- /dev/null +++ b/db/schema_migrations/20230524095108 @@ -0,0 +1 @@ +1cafb842d106d59f63dc12d4cdb215af023f8a6d19d80cbd69af3de51f5d4bac
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 99a76247e01..5dfb95d10ef 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -33227,8 +33227,6 @@ CREATE UNIQUE INDEX unique_index_on_system_note_metadata_id ON resource_link_eve CREATE UNIQUE INDEX unique_merge_request_metrics_by_merge_request_id ON merge_request_metrics USING btree (merge_request_id); -CREATE UNIQUE INDEX unique_organizations_on_name_lower ON organizations USING btree (lower(name)); - CREATE UNIQUE INDEX unique_packages_project_id_and_name_and_version_when_debian ON packages_packages USING btree (project_id, name, version) WHERE ((package_type = 9) AND (status <> 4)); CREATE UNIQUE INDEX unique_postgres_async_fk_validations_name_and_table_name ON postgres_async_foreign_key_validations USING btree (name, table_name); diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 1578b5bd328..82798c14793 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -25992,6 +25992,7 @@ Name of the feature that the callout is for. | <a id="usercalloutfeaturenameenumnamespace_storage_limit_banner_info_threshold"></a>`NAMESPACE_STORAGE_LIMIT_BANNER_INFO_THRESHOLD` | Callout feature name for namespace_storage_limit_banner_info_threshold. | | <a id="usercalloutfeaturenameenumnamespace_storage_limit_banner_warning_threshold"></a>`NAMESPACE_STORAGE_LIMIT_BANNER_WARNING_THRESHOLD` | Callout feature name for namespace_storage_limit_banner_warning_threshold. | | <a id="usercalloutfeaturenameenumnamespace_storage_pre_enforcement_banner"></a>`NAMESPACE_STORAGE_PRE_ENFORCEMENT_BANNER` | Callout feature name for namespace_storage_pre_enforcement_banner. | +| <a id="usercalloutfeaturenameenumnew_navigation_callout"></a>`NEW_NAVIGATION_CALLOUT` | Callout feature name for new_navigation_callout. | | <a id="usercalloutfeaturenameenumnew_top_level_group_alert"></a>`NEW_TOP_LEVEL_GROUP_ALERT` | Callout feature name for new_top_level_group_alert. | | <a id="usercalloutfeaturenameenumnew_user_signups_cap_reached"></a>`NEW_USER_SIGNUPS_CAP_REACHED` | Callout feature name for new_user_signups_cap_reached. | | <a id="usercalloutfeaturenameenumpersonal_access_token_expiry"></a>`PERSONAL_ACCESS_TOKEN_EXPIRY` | Callout feature name for personal_access_token_expiry. | diff --git a/doc/api/groups.md b/doc/api/groups.md index 6d295b50a01..be663a6a9c5 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -834,6 +834,7 @@ The `default_branch_protection` attribute determines whether users with the Deve | `1` | Partial protection. Users with the Developer or Maintainer role can: <br>- Push new commits | | `2` | Full protection. Only users with the Maintainer role can: <br>- Push new commits | | `3` | Protected against pushes. Users with the Maintainer role can: <br>- Push new commits<br>- Force push changes<br>- Accept merge requests<br>Users with the Developer role can:<br>- Accept merge requests| +| `4` | Protected against pushes except initial push. User with the Developer rope can: <br>- Push commit to empty repository.<br> Users with the Maintainer role can: <br>- Push new commits<br>- Force push changes<br>- Accept merge requests<br>Users with the Developer role can:<br>- Accept merge requests| ## New Subgroup diff --git a/doc/development/github_importer.md b/doc/development/github_importer.md index 73497c22b65..6ec420af5f9 100644 --- a/doc/development/github_importer.md +++ b/doc/development/github_importer.md @@ -149,10 +149,10 @@ comments. This worker imports note attachments that are linked inside Markdown. For each entity with Markdown text in the project, we schedule a job of: -- `Gitlab::GithubImport::ImportReleaseAttachmentsWorker` for every release. -- `Gitlab::GithubImport::ImportNoteAttachmentsWorker` for every note. -- `Gitlab::GithubImport::ImportIssueAttachmentsWorker` for every issue. -- `Gitlab::GithubImport::ImportMergeRequestAttachmentsWorker` for every merge request. +- `Gitlab::GithubImport::Importer::Attachments::ReleasesImporter` for every release. +- `Gitlab::GithubImport::Importer::Attachments::NotesImporter` for every note. +- `Gitlab::GithubImport::Importer::Attachments::IssuesImporter` for every issue. +- `Gitlab::GithubImport::Importer::Attachments::MergeRequestsImporter` for every merge request. Each job: diff --git a/doc/user/project/repository/branches/default.md b/doc/user/project/repository/branches/default.md index e58cc0bf6a4..96f5f6887d9 100644 --- a/doc/user/project/repository/branches/default.md +++ b/doc/user/project/repository/branches/default.md @@ -95,6 +95,8 @@ unless a subgroup configuration overrides it. ## Protect initial default branches **(FREE SELF)** +> Full protection after initial push [added](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118729) in GitLab 16.0. + GitLab administrators and group owners can define [branch protections](../../../project/protected_branches.md) to apply to every repository's [default branch](#default-branch) at the [instance level](#instance-level-default-branch-protection) and @@ -108,6 +110,8 @@ at the [instance level](#instance-level-default-branch-protection) and but cannot force push. - **Fully protected** - Developers cannot push new commits, but maintainers can. No one can force push. +- **Fully protected after initial push** - Developers can push the initial commit + to a repository, but none afterward. Maintainers can always push. No one can force push. ### Instance-level default branch protection **(FREE SELF)** diff --git a/lib/feature.rb b/lib/feature.rb index 3847f880be0..6c9bbc39844 100644 --- a/lib/feature.rb +++ b/lib/feature.rb @@ -326,7 +326,11 @@ module Feature end def l2_cache_backend - ::Gitlab::Redis::FeatureFlag.cache_store + if !Gitlab::Utils.to_boolean(ENV['GITLAB_USE_FEATURE_FLAG_REDIS']) + Rails.cache + else + ::Gitlab::Redis::FeatureFlag.cache_store + end end def log(key:, action:, **extra) diff --git a/lib/gitlab/access.rb b/lib/gitlab/access.rb index bafda11170a..f1777e059ed 100644 --- a/lib/gitlab/access.rb +++ b/lib/gitlab/access.rb @@ -23,6 +23,7 @@ module Gitlab PROTECTION_DEV_CAN_PUSH = 1 PROTECTION_FULL = 2 PROTECTION_DEV_CAN_MERGE = 3 + PROTECTION_DEV_CAN_INITIAL_PUSH = 4 # Default project creation level NO_ONE_PROJECT_ACCESS = 0 @@ -95,6 +96,11 @@ module Gitlab label: s_('DefaultBranchProtection|Fully protected'), help_text: s_('DefaultBranchProtection|Developers cannot push new commits, but maintainers can. No one can force push.'), value: PROTECTION_FULL + }, + { + label: s_('DefaultBranchProtection|Fully protected after initial push'), + help_text: s_('DefaultBranchProtection|Developers can push the initial commit to a repository, but none afterward. Maintainers can always push. No one can force push.'), + value: PROTECTION_DEV_CAN_INITIAL_PUSH } ] end diff --git a/lib/gitlab/access/branch_protection.rb b/lib/gitlab/access/branch_protection.rb index 339a99eb068..6ac8de407b0 100644 --- a/lib/gitlab/access/branch_protection.rb +++ b/lib/gitlab/access/branch_protection.rb @@ -34,6 +34,10 @@ module Gitlab level == PROTECTION_DEV_CAN_PUSH end + def developer_can_initial_push? + level == PROTECTION_DEV_CAN_INITIAL_PUSH + end + def developer_can_merge? level == PROTECTION_DEV_CAN_MERGE end diff --git a/lib/gitlab/database/gitlab_schema.rb b/lib/gitlab/database/gitlab_schema.rb index 4394c089b22..3d2e9f3f243 100644 --- a/lib/gitlab/database/gitlab_schema.rb +++ b/lib/gitlab/database/gitlab_schema.rb @@ -67,13 +67,6 @@ module Gitlab # All `pg_` tables are marked as `internal` return :gitlab_internal if table_name.start_with?('pg_') - - # Sometimes the name of an index can be interpreted as a table's name. - # For eg, if we execute "ALTER INDEX my_index..", my_index is interpreted as a table name. - # In such cases, we should return the schema of the database table actually - # holding that index. - index_name = table_name - derive_schema_from_index(index_name) end # rubocop:enable Metrics/CyclomaticComplexity @@ -131,15 +124,6 @@ module Gitlab @schema_names ||= self.views_and_tables_to_schema.values.to_set end - private_class_method def self.derive_schema_from_index(index_name) - index = Gitlab::Database::PostgresIndex.find_by(name: index_name, - schema: ApplicationRecord.connection.current_schema) - - return unless index - - table_schema(index.tablename) - end - private_class_method def self.build_dictionary(path_globs) Dir.glob(path_globs).each_with_object({}) do |file_path, dic| data = YAML.load_file(file_path) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 9ec1207980f..141f4068d7f 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -14476,6 +14476,9 @@ msgstr "" msgid "DefaultBranchProtection|Both developers and maintainers can push new commits, force push, or delete the branch." msgstr "" +msgid "DefaultBranchProtection|Developers can push the initial commit to a repository, but none afterward. Maintainers can always push. No one can force push." +msgstr "" + msgid "DefaultBranchProtection|Developers cannot push new commits, but are allowed to accept merge requests to the branch. Maintainers can push to the branch." msgstr "" @@ -14485,6 +14488,9 @@ msgstr "" msgid "DefaultBranchProtection|Fully protected" msgstr "" +msgid "DefaultBranchProtection|Fully protected after initial push" +msgstr "" + msgid "DefaultBranchProtection|Not protected" msgstr "" @@ -19223,6 +19229,9 @@ msgstr "" msgid "For more information, see the File Hooks documentation." msgstr "" +msgid "For the next few releases, you can go to your avatar at any time to turn the new navigation on and off." +msgstr "" + msgid "Forbidden" msgstr "" @@ -22417,6 +22426,9 @@ msgstr "" msgid "I accept the %{terms_link}" msgstr "" +msgid "I am sorry, I am unable to find the issue you are looking for." +msgstr "" + msgid "I do not know." msgstr "" @@ -37316,6 +37328,9 @@ msgstr "" msgid "Read more about GitLab at %{link_to_promo}." msgstr "" +msgid "Read more about the %{changes_link_start}changes%{link_end}, the %{vision_link_start}vision%{link_end}, and the %{design_link_start}design%{link_end}." +msgstr "" + msgid "Read their documentation." msgstr "" @@ -50828,6 +50843,9 @@ msgstr "" msgid "Welcome to GitLab,%{br_tag}%{name}!" msgstr "" +msgid "Welcome to a new navigation experience" +msgstr "" + msgid "Welcome, %{name}!" msgstr "" diff --git a/spec/features/nav/new_nav_callout_spec.rb b/spec/features/nav/new_nav_callout_spec.rb new file mode 100644 index 00000000000..aa20d1eeff1 --- /dev/null +++ b/spec/features/nav/new_nav_callout_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'new navigation callout', :js, feature_category: :navigation do + let_it_be(:callout_title) { _('Welcome to a new navigation experience') } + + before do + sign_in(user) + visit root_path + end + + context 'with new navigation toggled on' do + let_it_be(:user) { create(:user, use_new_navigation: true) } + + it 'shows a callout about the new navigation' do + expect(page).to have_content callout_title + end + + context 'when user dismisses callout' do + it 'hides callout' do + expect(page).to have_content callout_title + + page.within(find('[data-feature-id="new_navigation_callout"]')) do + find('[data-testid="close-icon"]').click + end + + wait_for_requests + + visit root_path + + expect(page).not_to have_content callout_title + end + end + end + + context 'with new navigation toggled off' do + let_it_be(:user) { create(:user, use_new_navigation: false) } + + it 'does not show the callout' do + expect(page).not_to have_content callout_title + end + end +end diff --git a/spec/frontend/work_items/components/notes/work_item_note_spec.js b/spec/frontend/work_items/components/notes/work_item_note_spec.js index 9de799a6b28..645d0e98c0b 100644 --- a/spec/frontend/work_items/components/notes/work_item_note_spec.js +++ b/spec/frontend/work_items/components/notes/work_item_note_spec.js @@ -133,7 +133,7 @@ describe('Work Item Note', () => { findNoteActions().vm.$emit('startEditing'); await nextTick(); - findCommentForm().vm.$emit('submitForm', updatedNoteText); + findCommentForm().vm.$emit('submitForm', { commentText: updatedNoteText }); expect(successHandler).toHaveBeenCalledWith({ input: { @@ -148,7 +148,7 @@ describe('Work Item Note', () => { findNoteActions().vm.$emit('startEditing'); await nextTick(); - findCommentForm().vm.$emit('submitForm', updatedNoteText); + findCommentForm().vm.$emit('submitForm', { commentText: updatedNoteText }); await waitForPromises(); expect(findCommentForm().exists()).toBe(false); @@ -161,7 +161,7 @@ describe('Work Item Note', () => { findNoteActions().vm.$emit('startEditing'); await nextTick(); - findCommentForm().vm.$emit('submitForm', updatedNoteText); + findCommentForm().vm.$emit('submitForm', { commentText: updatedNoteText }); await waitForPromises(); }); diff --git a/spec/lib/feature_spec.rb b/spec/lib/feature_spec.rb index 044415b9952..c75b281ecca 100644 --- a/spec/lib/feature_spec.rb +++ b/spec/lib/feature_spec.rb @@ -212,7 +212,15 @@ RSpec.describe Feature, :clean_gitlab_redis_feature_flag, stub_feature_flags: fa end it { expect(described_class.send(:l1_cache_backend)).to eq(Gitlab::ProcessMemoryCache.cache_backend) } - it { expect(described_class.send(:l2_cache_backend)).to eq(Gitlab::Redis::FeatureFlag.cache_store) } + it { expect(described_class.send(:l2_cache_backend)).to eq(Rails.cache) } + + context 'when GITLAB_USE_FEATURE_FLAG_REDIS is set to true' do + before do + stub_env('GITLAB_USE_FEATURE_FLAG_REDIS', 'true') + end + + it { expect(described_class.send(:l2_cache_backend)).to eq(Gitlab::Redis::FeatureFlag.cache_store) } + end it 'caches the status in L1 and L2 caches', :request_store, :use_clean_rails_memory_store_caching do diff --git a/spec/lib/gitlab/access/branch_protection_spec.rb b/spec/lib/gitlab/access/branch_protection_spec.rb index 44c30d1f596..5ab610dfc8f 100644 --- a/spec/lib/gitlab/access/branch_protection_spec.rb +++ b/spec/lib/gitlab/access/branch_protection_spec.rb @@ -7,10 +7,11 @@ RSpec.describe Gitlab::Access::BranchProtection do describe '#any?' do where(:level, :result) do - Gitlab::Access::PROTECTION_NONE | false - Gitlab::Access::PROTECTION_DEV_CAN_PUSH | true - Gitlab::Access::PROTECTION_DEV_CAN_MERGE | true - Gitlab::Access::PROTECTION_FULL | true + Gitlab::Access::PROTECTION_NONE | false + Gitlab::Access::PROTECTION_DEV_CAN_PUSH | true + Gitlab::Access::PROTECTION_DEV_CAN_MERGE | true + Gitlab::Access::PROTECTION_FULL | true + Gitlab::Access::PROTECTION_DEV_CAN_INITIAL_PUSH | true end with_them do @@ -20,10 +21,11 @@ RSpec.describe Gitlab::Access::BranchProtection do describe '#developer_can_push?' do where(:level, :result) do - Gitlab::Access::PROTECTION_NONE | false - Gitlab::Access::PROTECTION_DEV_CAN_PUSH | true - Gitlab::Access::PROTECTION_DEV_CAN_MERGE | false - Gitlab::Access::PROTECTION_FULL | false + Gitlab::Access::PROTECTION_NONE | false + Gitlab::Access::PROTECTION_DEV_CAN_PUSH | true + Gitlab::Access::PROTECTION_DEV_CAN_MERGE | false + Gitlab::Access::PROTECTION_FULL | false + Gitlab::Access::PROTECTION_DEV_CAN_INITIAL_PUSH | false end with_them do @@ -35,10 +37,11 @@ RSpec.describe Gitlab::Access::BranchProtection do describe '#developer_can_merge?' do where(:level, :result) do - Gitlab::Access::PROTECTION_NONE | false - Gitlab::Access::PROTECTION_DEV_CAN_PUSH | false - Gitlab::Access::PROTECTION_DEV_CAN_MERGE | true - Gitlab::Access::PROTECTION_FULL | false + Gitlab::Access::PROTECTION_NONE | false + Gitlab::Access::PROTECTION_DEV_CAN_PUSH | false + Gitlab::Access::PROTECTION_DEV_CAN_MERGE | true + Gitlab::Access::PROTECTION_FULL | false + Gitlab::Access::PROTECTION_DEV_CAN_INITIAL_PUSH | false end with_them do @@ -50,10 +53,11 @@ RSpec.describe Gitlab::Access::BranchProtection do describe '#fully_protected?' do where(:level, :result) do - Gitlab::Access::PROTECTION_NONE | false - Gitlab::Access::PROTECTION_DEV_CAN_PUSH | false - Gitlab::Access::PROTECTION_DEV_CAN_MERGE | false - Gitlab::Access::PROTECTION_FULL | true + Gitlab::Access::PROTECTION_NONE | false + Gitlab::Access::PROTECTION_DEV_CAN_PUSH | false + Gitlab::Access::PROTECTION_DEV_CAN_MERGE | false + Gitlab::Access::PROTECTION_FULL | true + Gitlab::Access::PROTECTION_DEV_CAN_INITIAL_PUSH | false end with_them do @@ -62,4 +66,20 @@ RSpec.describe Gitlab::Access::BranchProtection do end end end + + describe '#developer_can_initial_push?' do + where(:level, :result) do + Gitlab::Access::PROTECTION_NONE | false + Gitlab::Access::PROTECTION_DEV_CAN_PUSH | false + Gitlab::Access::PROTECTION_DEV_CAN_MERGE | false + Gitlab::Access::PROTECTION_FULL | false + Gitlab::Access::PROTECTION_DEV_CAN_INITIAL_PUSH | true + end + + with_them do + it do + expect(described_class.new(level).developer_can_initial_push?).to eq(result) + end + end + end end diff --git a/spec/lib/gitlab/database/gitlab_schema_spec.rb b/spec/lib/gitlab/database/gitlab_schema_spec.rb index 5d3260a77c9..0499605ee2e 100644 --- a/spec/lib/gitlab/database/gitlab_schema_spec.rb +++ b/spec/lib/gitlab/database/gitlab_schema_spec.rb @@ -20,12 +20,6 @@ RSpec.describe Gitlab::Database::GitlabSchema, feature_category: :database do shared_examples 'maps table name to table schema' do using RSpec::Parameterized::TableSyntax - before do - ApplicationRecord.connection.execute(<<~SQL) - CREATE INDEX index_name_on_table_belonging_to_gitlab_main ON public.projects (name); - SQL - end - where(:name, :classification) do 'ci_builds' | :gitlab_ci 'my_schema.ci_builds' | :gitlab_ci @@ -37,7 +31,6 @@ RSpec.describe Gitlab::Database::GitlabSchema, feature_category: :database do '_test_gitlab_ci_table' | :gitlab_ci '_test_my_table' | :gitlab_shared 'pg_attribute' | :gitlab_internal - 'index_name_on_table_belonging_to_gitlab_main' | :gitlab_main end with_them do @@ -155,6 +148,18 @@ RSpec.describe Gitlab::Database::GitlabSchema, feature_category: :database do it { is_expected.to be_nil } end + + context 'when an index name is used as the table name' do + before do + ApplicationRecord.connection.execute(<<~SQL) + CREATE INDEX index_on_projects ON public.projects USING gin (name gin_trgm_ops) + SQL + end + + let(:name) { 'index_on_projects' } + + it { is_expected.to be_nil } + end end describe '.table_schema!' do diff --git a/spec/lib/gitlab/database/query_analyzers/gitlab_schemas_metrics_spec.rb b/spec/lib/gitlab/database/query_analyzers/gitlab_schemas_metrics_spec.rb index 6a0c4226db8..b5e08f58608 100644 --- a/spec/lib/gitlab/database/query_analyzers/gitlab_schemas_metrics_spec.rb +++ b/spec/lib/gitlab/database/query_analyzers/gitlab_schemas_metrics_spec.rb @@ -7,6 +7,9 @@ RSpec.describe Gitlab::Database::QueryAnalyzers::GitlabSchemasMetrics, query_ana before do allow(Gitlab::Database::QueryAnalyzer.instance).to receive(:all_analyzers).and_return([analyzer]) + ApplicationRecord.connection.execute(<<~SQL) + CREATE INDEX index_on_projects ON public.projects USING gin (name gin_trgm_ops) + SQL end it 'does not increment metrics if feature flag is disabled' do @@ -59,6 +62,11 @@ RSpec.describe Gitlab::Database::QueryAnalyzers::GitlabSchemasMetrics, query_ana sql: "SELECT 1 FROM projects LEFT JOIN not_in_schema ON not_in_schema.project_id=projects.id", expect_error: /Could not find gitlab schema for table not_in_schema/ + }, + "for query altering an INDEX" => { + model: ApplicationRecord, + sql: "ALTER INDEX index_on_projects SET ( fastupdate = false )", + no_op: true } } end @@ -74,6 +82,10 @@ RSpec.describe Gitlab::Database::QueryAnalyzers::GitlabSchemasMetrics, query_ana if expect_error expect { process_sql(model, sql) }.to raise_error(expect_error) + elsif no_op + expect(described_class.schemas_metrics).not_to receive(:increment) + + process_sql(model, sql) else expect(described_class.schemas_metrics).to receive(:increment) .with(expectations).and_call_original diff --git a/spec/models/organizations/organization_spec.rb b/spec/models/organizations/organization_spec.rb index b60a1a34264..fd4676fbfe3 100644 --- a/spec/models/organizations/organization_spec.rb +++ b/spec/models/organizations/organization_spec.rb @@ -15,7 +15,6 @@ RSpec.describe Organizations::Organization, type: :model, feature_category: :cel subject { create(:organization) } it { is_expected.to validate_presence_of(:name) } - it { is_expected.to validate_uniqueness_of(:name).case_insensitive } it { is_expected.to validate_length_of(:name).is_at_most(255) } end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 6cf7c16124f..cb477fdd5dc 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -2707,10 +2707,34 @@ RSpec.describe Project, factory_default: :keep, feature_category: :groups_and_pr subject { project.default_branch_protected? } where(:default_branch_protection_level, :result) do - Gitlab::Access::PROTECTION_NONE | false - Gitlab::Access::PROTECTION_DEV_CAN_PUSH | false - Gitlab::Access::PROTECTION_DEV_CAN_MERGE | true - Gitlab::Access::PROTECTION_FULL | true + Gitlab::Access::PROTECTION_NONE | false + Gitlab::Access::PROTECTION_DEV_CAN_PUSH | false + Gitlab::Access::PROTECTION_DEV_CAN_MERGE | true + Gitlab::Access::PROTECTION_FULL | true + Gitlab::Access::PROTECTION_DEV_CAN_INITIAL_PUSH | true + end + + with_them do + before do + expect(project.namespace).to receive(:default_branch_protection).and_return(default_branch_protection_level) + end + + it { is_expected.to eq(result) } + end + end + + describe 'initial_push_to_default_branch_allowed_for_developer?' do + let_it_be(:namespace) { create(:namespace) } + let_it_be(:project) { create(:project, namespace: namespace) } + + subject { project.initial_push_to_default_branch_allowed_for_developer? } + + where(:default_branch_protection_level, :result) do + Gitlab::Access::PROTECTION_NONE | true + Gitlab::Access::PROTECTION_DEV_CAN_PUSH | true + Gitlab::Access::PROTECTION_DEV_CAN_MERGE | false + Gitlab::Access::PROTECTION_FULL | false + Gitlab::Access::PROTECTION_DEV_CAN_INITIAL_PUSH | true end with_them do diff --git a/spec/models/protected_branch_spec.rb b/spec/models/protected_branch_spec.rb index d14a7dd1a7e..b8357fc30b8 100644 --- a/spec/models/protected_branch_spec.rb +++ b/spec/models/protected_branch_spec.rb @@ -507,6 +507,44 @@ RSpec.describe ProtectedBranch, feature_category: :source_code_management do it { is_expected.to eq(true) } end + + context 'when project is an empty repository' do + before do + allow(project).to receive(:empty_repo?).and_return(true) + end + + context 'when user is an admin' do + let(:current_user) { admin } + + it { is_expected.to eq(true) } + end + + context 'when user is maintainer' do + let(:current_user) { maintainer } + + it { is_expected.to eq(true) } + end + + context 'when user is developer and initial push is allowed' do + let(:current_user) { developer } + + before do + allow(project).to receive(:initial_push_to_default_branch_allowed_for_developer?).and_return(true) + end + + it { is_expected.to eq(true) } + end + + context 'when user is developer and initial push is not allowed' do + let(:current_user) { developer } + + before do + allow(project).to receive(:initial_push_to_default_branch_allowed_for_developer?).and_return(false) + end + + it { is_expected.to eq(false) } + end + end end describe '.by_name' do diff --git a/spec/support/shared_examples/features/work_items_shared_examples.rb b/spec/support/shared_examples/features/work_items_shared_examples.rb index 32611621bce..0e84a5ede1e 100644 --- a/spec/support/shared_examples/features/work_items_shared_examples.rb +++ b/spec/support/shared_examples/features/work_items_shared_examples.rb @@ -32,6 +32,7 @@ end RSpec.shared_examples 'work items comments' do |type| let(:form_selector) { '[data-testid="work-item-add-comment"]' } + let(:edit_button) { '[data-testid="edit-work-item-note"]' } let(:textarea_selector) { '[data-testid="work-item-add-comment"] #work-item-add-or-edit-comment' } let(:is_mac) { page.evaluate_script('navigator.platform').include?('Mac') } let(:modifier_key) { is_mac ? :command : :control } @@ -53,6 +54,22 @@ RSpec.shared_examples 'work items comments' do |type| end end + it 'successfully updates existing comments' do + set_comment + click_button "Comment" + wait_for_all_requests + + find(edit_button).click + send_keys(" updated") + click_button "Save comment" + + wait_for_all_requests + + page.within(".main-notes-list") do + expect(page).to have_content "Test comment updated" + end + end + context 'for work item note actions signed in user with developer role' do it 'shows work item note actions' do set_comment diff --git a/spec/support_specs/database/prevent_cross_joins_spec.rb b/spec/support_specs/database/prevent_cross_joins_spec.rb index 5a80d0c0203..0c130d92c6b 100644 --- a/spec/support_specs/database/prevent_cross_joins_spec.rb +++ b/spec/support_specs/database/prevent_cross_joins_spec.rb @@ -48,6 +48,20 @@ RSpec.describe Database::PreventCrossJoins, :suppress_gitlab_schemas_validate_co expect { ApplicationRecord.connection.execute('SELECT SELECT FROM SELECT') }.to raise_error(ActiveRecord::StatementInvalid) end end + + context 'when an ALTER INDEX query is used' do + before do + ApplicationRecord.connection.execute(<<~SQL) + CREATE INDEX index_on_projects ON public.projects USING gin (name gin_trgm_ops) + SQL + end + + it 'does not raise exception' do + expect do + ApplicationRecord.connection.execute('ALTER INDEX index_on_projects SET ( fastupdate = false )') + end.not_to raise_error + end + end end end diff --git a/spec/workers/every_sidekiq_worker_spec.rb b/spec/workers/every_sidekiq_worker_spec.rb index 467fbd70528..3c9324a5258 100644 --- a/spec/workers/every_sidekiq_worker_spec.rb +++ b/spec/workers/every_sidekiq_worker_spec.rb @@ -268,7 +268,6 @@ RSpec.describe 'Every Sidekiq worker', feature_category: :shared do 'Gitlab::BitbucketServerImport::Stage::ImportPullRequestsWorker' => 3, 'Gitlab::BitbucketServerImport::Stage::ImportRepositoryWorker' => 3, 'Gitlab::GithubImport::AdvanceStageWorker' => 3, - 'Gitlab::GithubImport::ImportReleaseAttachmentsWorker' => 5, 'Gitlab::GithubImport::Attachments::ImportReleaseWorker' => 5, 'Gitlab::GithubImport::Attachments::ImportNoteWorker' => 5, 'Gitlab::GithubImport::Attachments::ImportIssueWorker' => 5, @@ -280,8 +279,6 @@ RSpec.describe 'Every Sidekiq worker', feature_category: :shared do 'Gitlab::GithubImport::ImportNoteWorker' => 5, 'Gitlab::GithubImport::ImportProtectedBranchWorker' => 5, 'Gitlab::GithubImport::ImportCollaboratorWorker' => 5, - 'Gitlab::GithubImport::ImportPullRequestMergedByWorker' => 5, - 'Gitlab::GithubImport::ImportPullRequestReviewWorker' => 5, 'Gitlab::GithubImport::PullRequests::ImportReviewRequestWorker' => 5, 'Gitlab::GithubImport::PullRequests::ImportReviewWorker' => 5, 'Gitlab::GithubImport::PullRequests::ImportMergedByWorker' => 5, diff --git a/spec/workers/gitlab/github_import/import_pull_request_merged_by_worker_spec.rb b/spec/workers/gitlab/github_import/import_pull_request_merged_by_worker_spec.rb deleted file mode 100644 index 4fbdfb1903f..00000000000 --- a/spec/workers/gitlab/github_import/import_pull_request_merged_by_worker_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::GithubImport::ImportPullRequestMergedByWorker, feature_category: :importers do - it { is_expected.to include_module(Gitlab::GithubImport::ObjectImporter) } - - describe '#representation_class' do - it { expect(subject.representation_class).to eq(Gitlab::GithubImport::Representation::PullRequest) } - end - - describe '#importer_class' do - it { expect(subject.importer_class).to eq(Gitlab::GithubImport::Importer::PullRequests::MergedByImporter) } - end -end diff --git a/spec/workers/gitlab/github_import/import_pull_request_review_worker_spec.rb b/spec/workers/gitlab/github_import/import_pull_request_review_worker_spec.rb deleted file mode 100644 index 41f97224bb4..00000000000 --- a/spec/workers/gitlab/github_import/import_pull_request_review_worker_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::GithubImport::ImportPullRequestReviewWorker, feature_category: :importers do - it { is_expected.to include_module(Gitlab::GithubImport::ObjectImporter) } - - describe '#representation_class' do - it { expect(subject.representation_class).to eq(Gitlab::GithubImport::Representation::PullRequestReview) } - end - - describe '#importer_class' do - it { expect(subject.importer_class).to eq(Gitlab::GithubImport::Importer::PullRequests::ReviewImporter) } - end -end diff --git a/spec/workers/gitlab/github_import/import_release_attachments_worker_spec.rb b/spec/workers/gitlab/github_import/import_release_attachments_worker_spec.rb deleted file mode 100644 index 62a9e3446f8..00000000000 --- a/spec/workers/gitlab/github_import/import_release_attachments_worker_spec.rb +++ /dev/null @@ -1,50 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::GithubImport::ImportReleaseAttachmentsWorker, feature_category: :importers do - subject(:worker) { described_class.new } - - describe '#import' do - let(:import_state) { create(:import_state, :started) } - - let(:project) do - instance_double('Project', full_path: 'foo/bar', id: 1, import_state: import_state) - end - - let(:client) { instance_double('Gitlab::GithubImport::Client') } - let(:importer) { instance_double('Gitlab::GithubImport::Importer::NoteAttachmentsImporter') } - - let(:release_hash) do - { - 'record_db_id' => rand(100), - 'record_type' => 'Release', - 'tag' => 'v1.0', - 'text' => <<-TEXT - Some text... - - ![special-image](https://user-images.githubusercontent.com...) - TEXT - } - end - - it 'imports an issue event' do - expect(Gitlab::GithubImport::Importer::NoteAttachmentsImporter) - .to receive(:new) - .with( - an_instance_of(Gitlab::GithubImport::Representation::NoteText), - project, - client - ) - .and_return(importer) - - expect(importer).to receive(:execute) - - expect(Gitlab::GithubImport::ObjectCounter) - .to receive(:increment) - .and_call_original - - worker.import(project, client, release_hash) - end - end -end |